%%WOD Measurements %Read coarse sun sensor data from WOD file along with unix time css = readmatrix('C:\Users\kevin\Desktop\Uni Work - Kevin\Masters\Disso\Matlab\WOD.xlsx', 'Range', 'H113926:I122919'); csslength = 1499; %Number of measurements from each sensor csst = css(1:csslength,2); %Time Stamps css = [css(1:csslength,1), css((csslength + 1):(2*csslength),1), css((2*csslength + 1):(3*csslength),1), ... css((3*csslength + 1):(4*csslength),1), css((4*csslength + 1):(5*csslength),1), css((5*csslength + 1):(6*csslength),1)]; %CSS data sorted in 6 columns %Remove the sensor data captured during eclipse css(65, :) = []; %Corrupted data from excel removed css(65, :) = []; %Corrupted data from excel removed css(111:210, :) = []; css(306:405, :) = []; css(501:601, :) = []; css(695:795, :) = []; css(890:989, :) = []; csst(65) = []; csst(65) = []; csst(111:210) = []; csst(306:405) = []; csst(501:601) = []; csst(695:795) = []; csst(890:989) = []; %Convert sun sensor data into engineering values (i.e. angles) css = css./255; %Normalise the values to get them in the range [0, 1] cssangle = acos(css); %Get sun angle in radians cssd = css * (180/pi); %Or in degrees %Separate the 6 sun sensors css1 = css(:, 1); %Xb css2 = css(:, 2); %-Xb css3 = css(:, 3); %Yb css4 = css(:, 4); %-Yb css5 = css(:, 5); %Zb css6 = css(:, 6); %-Zb %Similarly for magnetometers mgm = readmatrix('C:\Users\kevin\Desktop\Uni Work - Kevin\Masters\Disso\Matlab\WOD.xlsx', 'Range', 'H127417:I131913'); mgmlength = 1499; mgm = [mgm(1:mgmlength,1), mgm((mgmlength + 1):(2*mgmlength),1), mgm((2*mgmlength + 1):(3*mgmlength),1)]; %Magnetometer measurements sorted in 3 columns mgmt = csst; mgm(65, :) = []; %Corrupted data from excel removed mgm(65, :) = []; %Corrupted data from excel removed mgm(111:210, :) = []; mgm(306:405, :) = []; mgm(501:601, :) = []; mgm(695:795, :) = []; mgm(890:989, :) = []; %Read Y axis angular rate measurements w_obs = readmatrix('C:\Users\kevin\Desktop\Uni Work - Kevin\Masters\Disso\Matlab\WOD.xlsx', 'Range', 'H83946:H86943'); w_obs(2:2:end) = []; w_obs = w_obs'; w_obs(65) = []; w_obs(65) = []; w_obs(111:210) = []; w_obs(306:405) = []; w_obs(501:601) = []; w_obs(695:795) = []; w_obs(890:989) = []; for i = 1:length(w_obs) w_obs(i) = deg2rad(w_obs(i)); end %% Orbit Simulation %TLE for ALSAT 1N at the time closest to 1st sensor measurement %1 41789U 16059G 18144.44923147 .00000100 00000+0 28090-4 0 9995 %2 41789 98.1194 207.5640 0028815 159.8203 200.4146 14.64153751 88548 %Calculate orbit period and semi-major axis from TLE R_ = 6378.137; %Earth radius in km mu = 398600; %km^3/s^2 T = 86400/(14.64153751); a = (((T/(2*pi))^2)*mu)^(1/3); inclination = deg2rad(98.1194); RAAN = deg2rad(207.5640); e = 0.0028815; w = deg2rad(159.8203); n = sqrt(mu/(a^3)); M0 = deg2rad(200.4146) + n*(36*60+38); %Propagate mean anomaly to the time of first measurement %Timespan from 1st to last sensor measurement t = 0:20:(8*60*60 + 19*60 + 20); M = M0 + n.*t; theta = M; %Orbit is near circular so assume M = True Anomaly coe = zeros(6, length(csst)); X = zeros(6, length(csst)); for tt = 1:length(t) coe(:, tt) = [a, e, inclination, RAAN, w, theta(tt)]'; X(:, tt) = COE2RV(coe(:, tt), mu); end Xi = X(:, 1); Xf = X(:, end); [Xe,Ye,Ze] = sphere(50); % Create figure figure('units','normalized','outerposition',[0 0 1 1]) % Plot Earth surf(R_*Xe, R_*Ye, R_*Ze, 'EdgeColor', 'none', 'FaceColor', 'c'); hold on; axis equal; xlabel('X, ECI (km)'); ylabel('Y, ECI (km)'); zlabel('Z, ECI (km)'); svectoreci = [6.877390932857540E+07 1.238448303642737E+08 5.368708797204571E+07]; % Plot ECI axes quiver3(0, 0, 0, 1, 0, 0, 1e4, 'k', 'LineWidth', 2); % I-axis quiver3(0, 0, 0, 0, 1, 0, 1e4, 'k', 'LineWidth', 2); % J-axis quiver3(0, 0, 0, 0, 0, 1, 1e4, 'k', 'LineWidth', 2); % K-axis quiver3(0, 0, 0, svectoreci(1), svectoreci(2), svectoreci(3), 6.6e-5, 'k', 'LineWidth', 2); text(1e4, 0, 0, 'I', 'FontSize', 20, 'Interpreter', 'tex', 'Color', 'k') text(0, 1e4, 0, 'J', 'FontSize', 20, 'Interpreter', 'tex', 'Color', 'k') text(0, 0, 1e4, 'K', 'FontSize', 20, 'Interpreter', 'tex', 'Color', 'k') text(svectoreci(1)*6.6e-5, svectoreci(2)*6.6e-5, svectoreci(3)*6.6e-5, 'svectoreci', 'FontSize', 20, 'Interpreter', 'tex', 'Color', 'k') % 113 212 % Plot Trajectory plot3(X(1,:), X(2,:), X(3,:), 'k', 'LineWidth', 2); plot3(X(1,1), X(2,1), X(3,1), 'o', 'MarkerFaceColor', 'b'); plot3(X(1,end), X(2,end), X(3,end), 'o', 'MarkerFaceColor', 'r'); plot3(X(1, 113), X(2, 113), X(3, 113), 'o', 'MarkerFaceColor', 'm'); plot3(X(1, 212), X(2, 212), X(3, 212), 'o', 'MarkerFaceColor', 'm'); legend('Earth', 'I-axis', 'J-axis', 'K-axis', 'Sun Vector from Geocentre', 'Orbital Path', 'Start of Simulation', 'End of Simulation', 'Start of Eclipse', 'End of Eclipse'); %% Get latitude and longitude in order to call IGRF function later rECEF = zeros(3, length(csst)); long = zeros(1, length(csst)); lat = zeros(1, length(csst)); R3 = zeros(3, 3, length(csst)); utc = datetime(csst, 'ConvertFrom', 'posixtime', 'TimeZone', 'UTC'); year = year(utc); month = month(utc); day = day(utc); hour = hour(utc); minute = minute(utc); second = second(utc); UTC = [year, month, day, hour, minute, second]; deltaAT = 38; %TAI - UTC in seconds... https://celestrak.com/SpaceData/ deltaUT1 = 0.0827070; %UT1 - UTC in seconds x = 0.102644; y = 0.447763; polarmotion = [x, y]; for i = 1:size(UTC, 1) R3(:, :, i) = dcmeci2ecef('IAU-2000/2006',UTC(i, :),deltaAT,deltaUT1,polarmotion); rECEF(:, i) = R3(:, :, i)*X(1:3, i); long(:, i) = rad2deg(atan2(rECEF(2, i), rECEF(1, i))); if rECEF(2, i) >= 0 && rECEF(1, i) >= 0 long(:, i) = rad2deg(atan2(rECEF(2, i), rECEF(1, i))); elseif rECEF(2, i) < 0 && rECEF(1, i) >= 0 long(:, i) = 360 + rad2deg(atan2(rECEF(2, i), rECEF(1, i))); elseif rECEF(2, i) >= 0 && rECEF(1, i) < 0 long(:, i) = rad2deg(atan2(rECEF(2, i), rECEF(1, i))); elseif rECEF(2, i) < 0 && rECEF(1, i) < 0 long(:, i) = 360 + rad2deg(atan2(rECEF(2, i), rECEF(1, i))); end lat(:, i) = rad2deg(asin(rECEF(3, i)/norm(X(1:3, i)))); end %% Attitude Estimation %Solar Ephemeris at the time of first measurement in ICRF/ECI coordinates. Obtained from JPL (Horizon). %2458262.972222222 = A.D. 2018-May-24 11:20:00.0000 TDB % X = 6.877390932857540E+07 Y = 1.238448303642737E+08 Z = 5.368708797204571E+07 X(:, 65) = []; X(:, 65) = []; X(:, 111:210) = []; X(:, 306:405) = []; X(:, 501:601) = []; X(:, 695:795) = []; X(:, 890:989) = []; sunvectorsim = ones(3, length(csst)) + [6.877390932857540E+07; 1.238448303642737E+08; 5.368708797204571E+07] - X(1:3, :); %Simulated sun vector in ECI frame / "Computed" theta = linspace(0, 2*pi, 100); % parameter for circle css_body = zeros(3, size(css, 1)); for i = 1:size(css, 1) if cssangle(i, 1) < cssangle(i, 2) r1 = 0.7*cos(cssangle(i, 1))*tan(cssangle(i, 1)); % radius of the circle z1 = r1 * cos(theta); % z coordinates (2D) y1 = r1 * sin(theta); % y coordinates (2D) phi1 = asin(sqrt(z1.^2 + y1.^2)); % polar angle lambda1 = atan2(y1, z1); % azimuthal angle X1 = cos(phi1); % X coordinates (3D) Y1 = sin(lambda1) .* sin(phi1); % Y coordinates (3D) Z1 = cos(lambda1) .* sin(phi1); % Z coordinates (3D) elseif cssangle(i, 1) > cssangle(i, 2) r1 = 0.7*cos(cssangle(i, 2))*tan(cssangle(i, 2)); z1 = r1 * cos(theta); y1 = r1 * sin(theta); phi1 = asin(sqrt(z1.^2 + y1.^2)); % polar angle lambda1 = atan2(y1, z1); % azimuthal angle X1 = -cos(phi1); Y1 = sin(lambda1) .* sin(phi1); Z1 = cos(lambda1) .* sin(phi1); end if cssangle(i, 3) < cssangle(i, 4) r2 = 0.9*cos(cssangle(i, 3))*tan(cssangle(i, 3)); x2 = r2 * cos(theta); z2 = r2 * sin(theta); phi2 = asin(sqrt(x2.^2 + z2.^2)); lambda2 = atan2(z2, x2); X2 = sin(lambda2) .* sin(phi2); Y2 = cos(phi2); Z2 = cos(lambda2) .* sin(phi2); elseif cssangle(i, 3) > cssangle(i, 4) r2 = 0.9*cos(cssangle(i, 4))*tan(cssangle(i, 4)); x2 = r2 * cos(theta); z2 = r2 * sin(theta); phi2 = asin(sqrt(x2.^2 + z2.^2)); lambda2 = atan2(z2, x2); X2 = sin(lambda2) .* sin(phi2); Y2 = -cos(phi2); Z2 = cos(lambda2) .* sin(phi2); end if cssangle(i, 5) < cssangle(i, 6) r3 = 0.9*cos(cssangle(i, 5))*tan(cssangle(i, 5)); x3 = r3 * cos(theta); y3 = r3 * sin(theta); phi3 = asin(sqrt(x3.^2 + y3.^2)); lambda3 = atan2(y3, x3); X3 = sin(lambda3) .* sin(phi3); Y3 = cos(lambda3) .* sin(phi3); Z3 = cos(phi3); elseif cssangle(i, 5) > cssangle(i, 6) r3 = 0.9*cos(cssangle(i, 6))*tan(cssangle(i, 6)); x3 = r3 * cos(theta); y3 = r3 * sin(theta); phi3 = asin(sqrt(x3.^2 + y3.^2)); lambda3 = atan2(y3, x3); X3 = sin(lambda3) .* sin(phi3); Y3 = cos(lambda3) .* sin(phi3); Z3 = -cos(phi3); end initial_guess = [0.5, 0.5, 0.5]; options = optimoptions('fminunc', 'Display', 'iter', 'Algorithm', 'quasi-newton'); [solution, fval] = fminunc(@(vars) objectiveFunc(vars, X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3), initial_guess, options); X_sol = solution(1); Y_sol = solution(2); Z_sol = solution(3); css_body(:, i) = [X_sol; Y_sol; Z_sol]/norm([X_sol; Y_sol; Z_sol]); end wgs84 = wgs84Ellipsoid('kilometer'); for i = 1:length(mgmt) u2ms = unix_to_matlab_serial(mgmt(i)); B(:, i) = igrf(u2ms, lat(i), long(i), norm(X(1:3, i)), 'geocentric'); [x, y, z] = ned2ecef(B(1, i), B(2, i), B(3, i), lat(i), long(i), norm(X(1:3, i)) - 6328, wgs84); %Verify transformation B(:, i) = [x, y, z]'; B(:, i) = R3(:, :, i)'*B(:, i); end C = zeros(3, 3, length(mgmt)); q = zeros(4, length(mgmt)); mgm = mgm'; for i = 1:length(mgmt) C(:, :, i) = triad([sunvectorsim(:, i)/norm(sunvectorsim(:, i)), B(:, i)/norm(B(:, i))], [css_body(:, i), mgm(:, i)/norm(mgm(:, i))]); q(:, i) = dcm2quat(C(:, :, i)); %Quaternion representing the satellite attitude wrt ECI frame end %% Angular Rate Estimation w_est = zeros(3, size(q, 2)); % Bdot = zeros(3, size(q, 2)); % % for i = 3:size(q, 2) % Bdot(:, i) = (mgm(:, i) - mgm(:, i-2))/40; % Bcross = [0, -mgm(3, i-1), mgm(2, i-1); % mgm(3, i-1), 0, -mgm(1, i-1); % -mgm(2, i-1), mgm(1, i-1), 0]; % w_est(:, i) = pinv(Bcross)*Bdot(:, i); % end for i = 2:size(q, 2) qdot = (q(:, i) - q(:, i-1))/20; q1 = q(1, i-1); q2 = q(2, i-1); q3 = q(3, i-1); q4 = q(4, i-1); Q = [-q2, -q3, -q4; q1, -q4, q3; q4, q1, -q2; -q3, q2, q1]; Qinv = pinv(Q); w_est(:, i) = 2*Qinv*qdot; end %% Plot attitude quaternion time evolution % figure(2) % plot(tt, q(1, :), 'b', tt, q(2, :), 'r', tt, q(3, :), 'y', tt, q(4, :), 'g'); % legend('q0', 'q1', 'q2', 'q3'); %% Plot estimated and measured angular rates against time for comparison tt = 0:20:20*(size(q, 2)-1); figure(2) plot(tt, rad2deg(w_obs), 'b', tt, rad2deg(w_est(2, :)), 'r') title('Estimated and measured Y-axis body angular rates in degrees per second') legend('Measured', 'Estimated') figure(3) plot(tt, rad2deg(abs(w_est(2, :) -w_obs))) title('Error in degrees per second between estimated and measured angular rates') %% Celestial Sphere Sun Vector Visualisation num_vectors = size(css_body, 2); % Create a unit sphere [phi, theta] = meshgrid(linspace(0, 2*pi, 100), linspace(0, pi, 50)); x = sin(theta) .* cos(phi); y = sin(theta) .* sin(phi); z = cos(theta); % Set up the figure and plot the celestial sphere figure(4) b_sphere = surf(x, y, z, 'FaceAlpha', 0.1, 'EdgeColor', 'none'); hold on; axis equal; xlabel('X'); ylabel('Y'); zlabel('Z'); title('Celestial Sphere with Animated Sun Vectors'); grid on; %Plot body axes quiver3(0, 0, 0, 1, 0, 0, 'k', 'LineWidth', 2); quiver3(0, 0, 0, 0, 1, 0, 'k', 'LineWidth', 2); quiver3(0, 0, 0, 0, 0, 1, 'k', 'LineWidth', 2); text(1, 0, 0, 'X', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); text(0, 1, 0, 'Y', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); text(0, 0, 1, 'Z', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); % Initialize plot for the sun vector b_vector = plot3([0, css_body(1, 1)], [0, css_body(2, 1)], [0, css_body(3, 1)], '-o', 'LineWidth', 2); % Animation loop for i = 1:num_vectors % Update the sun vector plot set(b_vector, 'XData', [0, css_body(1, i)], ... 'YData', [0, css_body(2, i)], ... 'ZData', [0, css_body(3, i)]); % Pause to create the animation effect pause(1/50); % Adjust the pause duration as needed % Update the title to indicate the time step or vector index figure(4) title(['Celestial Sphere with Sun Vectors - Frame ' num2str(i)]); end %% Celestial Sphere Magnetic Field Vector Visualisation num_vectors = size(mgm, 2); % Create a unit sphere [phi, theta] = meshgrid(linspace(0, 2*pi, 100), linspace(0, pi, 50)); x = sin(theta) .* cos(phi); y = sin(theta) .* sin(phi); z = cos(theta); % Set up the figure and plot the celestial sphere figure(5) b_sphere = surf(x, y, z, 'FaceAlpha', 0.1, 'EdgeColor', 'none'); hold on; axis equal; xlabel('X'); ylabel('Y'); zlabel('Z'); title('Celestial Sphere with Animated Magnetic Field Vectors'); grid on; %Plot body axes quiver3(0, 0, 0, 1, 0, 0, 'k', 'LineWidth', 2); quiver3(0, 0, 0, 0, 1, 0, 'k', 'LineWidth', 2); quiver3(0, 0, 0, 0, 0, 1, 'k', 'LineWidth', 2); text(1, 0, 0, 'X', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); text(0, 1, 0, 'Y', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); text(0, 0, 1, 'Z', 'FontSize', 16, 'Interpreter', 'tex', 'Color', 'k'); % Initialize plot for the magnetic field vector b_vector = plot3([0, mgm(1, 1)/norm(mgm(:, 1))], [0, mgm(2, 1)/norm(mgm(:, 1))], [0, mgm(3, 1)/norm(mgm(:, 1))], '-o', 'LineWidth', 2); % Animation loop for i = 1:num_vectors % Update the magnetic field vector plot set(b_vector, 'XData', [0, mgm(1, i)/norm(mgm(:, i))], ... 'YData', [0, mgm(2, i)/norm(mgm(:, i))], ... 'ZData', [0, mgm(3, i)/norm(mgm(:, i))]); % Pause to create the animation effect pause(1/50); % Adjust the pause duration as needed % Update the title to indicate the time step or vector index figure(5) title(['Celestial Sphere with Magnetic Field Vectors - Frame ' num2str(i)]); end