Contents

Test attitude determination with sun, earth and magnetometer.

dT is the time step for the attitude determination system. You can numerically integrate with a smaller time step by setting nInt > 1. The initial attitude is unknown. This demo uses the sun earth and magnetic field as attitude sources. The earth provides roll and pitch while the sun provides yaw. The magnetic field provide orientation with respect to the local field lines.

When using the UKF it is important to pick an appropriate plant noise matrix. If it is too high the filter may go unstable and produce large errors. With a gyro it is generally small. The terms for the delta quaternion and the bias are not the same (although they are in this demo). The choice of the UKF alpha parameter is also important. You will notice that it takes a few sun measurements for the filter to properly estimate the bias.

%--------------------------------------------------------------------------
%  See also SunMagAttDet, RVFromKepler, SunV1, FPSensors, RIGOut, BDipole
%--------------------------------------------------------------------------
%--------------------------------------------------------------------------
%   Copyright (c) 2017 Princeton Satellite Systems, Inc.
%   All rights reserved.
%--------------------------------------------------------------------------

rng('default');

Constants

degToRad  = pi/180;

Simulation data

nInt      = 2;
useMag    = false;

Spacecraft data

inr       = [1000 0 0;0 2000 0; 0 0 2000];
invInr    = inv(inr);
torque    = zeros(3,1);

Gyro data

uRIG      = eye(3);

% The sensor boresight is the z axis
% Column 1 is the earth sensor, Column 2 is the sun sensor
qBToS     = [1 cos(pi/4);0 0;0 sin(pi/4);0 0];
dQ        = [0;0;0.1;0.1];
fov       = [30 30;30 30]*degToRad;
fScale    = 1;
nRWRIG    = zeros(3,1);
nBRIG     = zeros(3,1);
nORIG     = zeros(3,1);
betaRIG   = zeros(3,1);

% Time
tEnd      = 600;
t         = 0;
dT        = 0.25;
nSim      = tEnd/dT;

Set up arrays for plotting

xPlot     = zeros(13,nSim);
qPlot     = zeros( 4,nSim);
pPlot     = zeros( 6,nSim);
bPlot     = zeros( 3,nSim);
tPlot     = zeros( 1,nSim);
qEPlot    = zeros( 4,nSim);
vMPlot    = zeros( 2,nSim);

angInc    = zeros(3,1);
xRIGOld   = zeros(3,1);

Estimator initialization

b         = zeros(3,1);

% Noise values
r         = struct('fP',0.0001,'uB',0.0001);

Generate the orbit

tOrbit    = (0:(nSim-1))*dT;

% el = [a,i,W,w,e,M]. The spacecraft is in geosynchronous orbit
[rECI, vECI]  =  RVFromKepler( [42167 0 0 0 0 pi/2], tOrbit );

Initial conditions at equinox

jD            = Date2JD([2000 3 20 7 26 0]) + (tOrbit + 6*3600)/86400;

Sun and earth vectors

uNadir        = -Unit( rECI );
uSun          =  SunV1( jD, rECI );
uB            =  Unit(BDipole( rECI, jD ));
xBAxis        = [1;0;0];
yBAxis        = [0;1;0];

% Initialize the uKF
ukf           = SunEarthMagAttDet;

Initial state

omega   = [0;0;0.05];
bias    = [-0.0001; -0.0001;  0.000];
intRate = [0;0;0];

qSim    = QLVLH(rECI(:,1),vECI(:,1));

% Quaternion, body rates, integrated rate, gyro biases
x = [qSim; omega; intRate; bias];

% Estimated quaternion
q = QUnit(qSim + dQ);

Run the simulation

for k = 1:nSim

  % Quaternion
  qSim                    = x(1:4);

  % Sun and Earth measurements
  catalog                 = [uNadir(:,k) uSun(:,k)];
  sensorData.fP           = FPSensors( qSim, qBToS, catalog, fov, fScale );
  sensorData.fP.catalog   = catalog;
  sensorData.fP.qBToS     = qBToS;
  sensorData.fP.fScale    = fScale;

  % Magnetic field measurements
  uBB                     = Unit(QForm( qSim, uB(:,k) ));
  [az, el]                = U2AzEl( uBB, xBAxis, yBAxis );
  sensorData.b            = struct('az',az,'el',el,'rECI', rECI(:,k), 'jD', jD(k), 'valid', useMag,'xAxis',xBAxis,'yAxis', yBAxis);

  % Rate integrating gyro
  [angInc, xRIGOld]       = RIGOut( x(8:10), xRIGOld, nORIG );

  % Attitude determination
  [q,b,ukf]               = SunEarthMagAttDet( angInc, q, b, ukf, r, dT, sensorData );
  err_x_q_b               = [QMult( QPose(q), qSim ), qSim, q, [0;b], [0;x(11:13)]];

  % Plotting
  qPlot(:,k)              = err_x_q_b(:,1);
  pPlot(:,k)              = diag(ukf.p);
  bPlot(:,k)              = b;
  xPlot(:,k)              = x;
  tPlot(k)                = t;
  qEPlot(:,k)             = q;
  vMPlot(:,k)             = sensorData.fP.valid;

  % Simulation
  for j = 1:nInt
    x      = RK4( @FRBwRIG, x, dT/nInt, t, inr, invInr, torque, uRIG, nRWRIG, nBRIG, betaRIG );
    x(1:4) = QUnit(x(1:4));
    t      = t + dT/nInt;
  end
end

Plot results

j       = 1:k;
[t,tL]  = TimeLabl(tPlot(j));
yL      = {'Quaternion Error' 'Bias Error'};
Plot2D( t, [qPlot(2:4,j);xPlot(11:13,j)-bPlot(:,j)],tL,yL,'Attitude Determination Errors','lin',['1:3';'4:6'])
legend('x axis','y axis','z axis')

yL      = {'True' 'Estimated'};
Plot2D( t, [xPlot( 1: 4,j);qEPlot(:,j)],tL,yL,'Quaternion','lin',['1:4';'5:8'])
Plot2D( t, pPlot(:,j),tL,'Covariance','Covariance')
legend('x','y','z','b_x','b_y','b_z');

Plot2D( t, vMPlot(:,j),tL,'Status','Valid Measurements')
legend('Earth Sensor','Sun Sensor')

% Making the axis labels easier to understand
set(gca,'ylim',[0 1.2]);
set(gca,'yticklabel',{'Invalid' '' '' '' '' 'Valid' ''});


%--------------------------------------