38 n = 13; % number of states
39 m = 13; % number of measurements
42 properties (Access =
public)
44 %> sampling time (
default 1/200 s)
45 dt (1,1) double = 1/200;
47 %> zero velocity flag (1 x N)
50 %> @name Measurement Vector
52 %> attributes regarding the measurement vector
54 %> number of observations
56 %> accelerometer measurements (3 x N) [m/s^2]
59 %> velocity measurements (3 x N)
60 %> @note these are usually all set to zero as the velocity is applied
61 %> as a pseudo measurement during ZUPTs
64 %> gyroscope measurements (3 x N) [rad/s]
67 %> position pseudo measurements (3 x N) [m]
70 %> yaw pseudo measurements (1 x N) [rad]
73 %> measurement vector (m x N)
76 %> @} % Measurement Vector
80 %> attributes regarding the estimated state vectors
82 %> state vector, forward, a priori (n x N)
85 %> state vector, forward, a posteriori (n x N)
88 %> state vector, backward, a posteriori (n x N)
93 %> @name Initial State Vector
95 %> attributes regarding the initial state vector
97 %> initial irientation quaternion
98 q_0 (4,1) double = [1,0,0,0]';
101 v_0 (3,1)
double = [0,0,0]';
104 p_0 (3,1)
double = [0,0,0]';
106 %> initial gyroscope bias
107 b_w_0 (3,1)
double = [0,0,0]';
109 %> initial state vector (n x 1)
112 %> @} % Initial State Vector
114 %> @name State Covariance
116 %> attributes regarding the state covariance matrix
118 %> state covariance matrix, forward, a priori (n x n x N)
121 %> state covariance matrix, forward, a posteriori (n x n x N)
124 %> state covariance matrix, backward, a posteriori (n x n x N)
127 %> @} % State Covariance
129 %> @name Initial State Covariance
131 %> attributes regarding the initial state covariance matrix
133 %> initial orientation standard deviation (
default 5e-4)
134 %> @remark based on experimental tuning
135 P_0_q (1,1) double = 5e-4;
137 %> initial velocity standard deviation (default 1e-7 m/s)
138 %> @remark based on experimental tuning
139 P_0_v (1,1)
double = 1e-7;
141 %> initial position standard deviation (default 1e-7 m)
142 %> @remark based on experimental tuning
143 P_0_p (1,1)
double = 1e-7;
145 %> initial gyroscope bias standard deviation (default 5e-3 rad/s)
146 %> @remark based on experimental tuning
147 P_0_b_w (1,1)
double = 5e-3;
149 %> initial state covariance matrix (n x n)
150 %> @remark based on experimental tuning
153 %> @} % Initial State Covariance
155 %> @name Measurement Noise
157 %> attributes regarding the measurement noise
159 %> accelerometer variance, x- and y-axis (
default 0.045 m/s^2)
160 %> @remark based on allan variance analysis
161 var_acc_xy (1,1) double = 0.045;
163 %> accelerometer variance, z-axis (default 0.07 m/s^2)
164 %> @remark based on allan variance analysis
165 var_acc_z (1,1)
double = 0.07;
167 %> variance of velocity 'measurements' during ZUPTs (default 1e-6)
168 %> @remark based on experimental tuning
169 var_vel_ZUPT (1,1)
double = 1e-6;
171 %> gyroscope variance (default 1.22e-3 rad/s)
172 %> @remark based on allan variance analysis
173 var_gyr (1,1)
double = 1.22e-3; % 0.07 * pi / 180;
175 %> variance of x-position 'measurements' during ZUPTs (default 1e-6 m)
176 %> @remark based on experimental tuning
179 %> variance of y-position 'measurements' during ZUPTs (default 1e-6 m)
180 %> @remark based on experimental tuning
183 %> variance of z-position 'measurements' during ZUPTs (default 1e-6 m)
184 %> @remark based on experimental tuning
187 %> variance of yaw pseudo-measurements (default TODO:)
188 %> @remark based on experimental tuning
189 var_yaw_pseudo = 0.0001;
191 %> measurement noise covariance matrix (m x m)
194 %> @} % Measurement Noise
196 %> @name Process Noise
198 %> attributes regarding the process noise
199 %> @note the process noise covariance matrix is calculated
for each
200 %> iteration based on the current state vector (see @ref
Q_cov)
202 %> angular velocity standard deviation (
default 876e-6 rad/s)
203 %> @remark based on allan variance analysis
204 %> @note as the prediction is based on the measurement, the measurement
205 %> noise applies. but since the prediction is based on the preprocessed
206 %> angular velocity which is the mean angular velocity between two
207 %> samples, the standard deviation is divided by sqrt(2) to get the
208 %> standard deviation of the mean angular velocity.
209 sigma_w_p (1,1)
double = sqrt(0.071) * pi / 180 / sqrt(2);
211 %> acceleration variance (default 0.045 m/s^2)
212 %> @remark based on experimental tuning
213 var_a (1,1)
double = 0.045;
215 %> gyroscope bias variance (default 1e-9 rad/s)
216 %> @remark based on experimental tuning
217 %> @note the value from the allan variance analysis seems to be too
218 %> high. the value is set to a lower value based on experimental tuning.
219 var_b_w (1,1)
double = 1e-9;
221 %> @} % Process Noise
225 methods (Access = public)
227 % ==================================================================== %
228 %> @brief Constructor of the
EKF1 class
230 %> constructs an instance of the
EKF1 class with the default values
231 % ==================================================================== %
232 function obj =
EKF1()
236 % ==================================================================== %
237 %> @brief filters and smooths the given data
239 %> this function creates all needed vectors and matrices and also checks
240 %> the input data. it then filters the data and smoothes the data
242 %> @param options options for the filter
244 %> @retval smoothed smoothed state vector
245 %> @retval filtered filtered state vector
247 %> this function creates all needed vectors and matrices and also checks
248 %> the input data. it then filters the data and smoothes the data
249 % ==================================================================== %
250 function [smoothed, filtered] = run(obj, options)
254 options.plot (1,1) logical = false
260 % check measurements length
261 obj.N = max(size(obj.z_acc));
262 assert(size(obj.z_vel, 2) == obj.N, "The number of velocity measurements must be equal to the number of accelerometer measurements. You provided %d velocity measurements and %d accelerometer measurements.", size(obj.z_vel, 2), obj.N);
263 assert(size(obj.z_gyr, 2) == obj.N, "The number of gyroscope measurements must be equal to the number of accelerometer measurements. You provided %d gyroscope measurements and %d accelerometer measurements.", size(obj.z_gyr, 2), obj.N);
264 assert(size(obj.z_pos, 2) == obj.N, "The number of position measurements must be equal to the number of accelerometer measurements. You provided %d position measurements and %d accelerometer measurements.", size(obj.z_pos, 2), obj.N);
265 assert(size(obj.z_yaw, 2) == obj.N, "The number of yaw measurements must be equal to the number of accelerometer measurements. You provided %d yaw measurements and %d accelerometer measurements.", size(obj.z_yaw, 2), obj.N);
267 % check zero velocity flag
268 assert(numel(obj.zv) == obj.N, "The number of zero velocity flags must be equal to the number of accelerometer measurements. You provided %d zero velocity flags and %d accelerometer measurements.", numel(obj.zv), obj.N);
270 % create measurement vector
271 obj.z = [obj.z_acc; obj.z_vel; obj.z_gyr; obj.z_pos; obj.z_yaw];
276 % create state vectors
277 obj.x_fm = nan(obj.n, obj.N);
278 obj.x_fp = nan(obj.n, obj.N);
279 obj.x_bp = nan(obj.n, obj.N);
281 % create initial state vector
282 obj.x_0 = [obj.q_0; obj.v_0; obj.p_0; obj.b_w_0];
285 % state covariance matrices
287 % create state covariance matrices
288 obj.P_fm = nan(obj.n, obj.n, obj.N);
289 obj.P_fp = nan(obj.n, obj.n, obj.N);
290 obj.P_bp = nan(obj.n, obj.n, obj.N);
292 % create initial state covariance matrix
293 obj.P_0 = diag([obj.P_0_q * ones(1,4), obj.P_0_v * ones(1,3), obj.P_0_p * ones(1,3), obj.P_0_b_w * ones(1,3)]) .^ 2;
298 % create measurement noise covariance matrix
299 obj.R = diag([obj.var_acc_xy * ones(1,2), obj.var_acc_z, obj.var_vel_ZUPT * ones(1,3), obj.var_gyr * ones(1,3), obj.var_pos_x, obj.var_pos_y, obj.var_pos_z, obj.var_yaw_pseudo]);
300 assert(all(size(obj.R) == [obj.m, obj.m]), "The measurement noise covariance matrix must be of size %d x %d. You provided a matrix of size %d x %d.", obj.m, obj.m, size(obj.R, 1), size(obj.R, 2));
305 % the process noise covariance matrix is calculated in the
Q_cov function
332 % ==================================================================== %
333 %> @brief exports the tuning parameters of the filter to a JSON
336 %> this function exports the tuning parameters of the filter to a JSON
339 %> @param obj instance of the
EKF1 class (implicit)
340 %> @param filename name of the JSON file (full or relative path, default
343 %> @retval status 1 if the file was written successfully, 0 otherwise
345 %> @note The following parameters are exported:
346 %> - initial covariance
347 %> - measurement noise
350 % ==================================================================== %
351 function status = exportTuning(obj, filename)
355 filename (1,1)
string = "tuning.json"
362 tuning.P_0_q = obj.P_0_q;
363 tuning.P_0_v = obj.P_0_v;
364 tuning.P_0_p = obj.P_0_p;
365 tuning.P_0_b_w = obj.P_0_b_w;
368 tuning.var_acc_xy = obj.var_acc_xy;
369 tuning.var_acc_z = obj.var_acc_z;
370 tuning.var_vel_ZUPT = obj.var_vel_ZUPT;
371 tuning.var_gyr = obj.var_gyr;
372 tuning.var_pos_x = obj.var_pos_x;
373 tuning.var_pos_y = obj.var_pos_y;
374 tuning.var_pos_z = obj.var_pos_z;
375 tuning.var_yaw_pseudo = obj.var_yaw_pseudo;
378 tuning.sigma_w_p = obj.sigma_w_p;
379 tuning.var_a = obj.var_a;
380 tuning.var_b_w = obj.var_b_w;
384 writestruct(tuning, filename, FileType="json", PrettyPrint=true);
392 % ==================================================================== %
393 %> @brief imports the tuning parameters of the filter from a JSON
396 %> this function imports the tuning parameters of the filter from a JSON
399 %> @param obj instance of the
EKF1 class (implicit)
400 %> @param filename name of the JSON file (full or relative path)
402 %> @retval status 1 if the file was read successfully, 0 otherwise
404 %> @note the function does only import the parameters that are present
405 %> in the JSON file. if a parameter is not present, the existing value
406 %> is kept. therefore, **no check** is performed if all parameters are
408 % ==================================================================== %
409 function status = importTuning(obj, filename)
413 filename (1,1)
string {mustBeFile}
418 tuning = readstruct(filename, FileType=
"json");
425 % get all the fields in the
struct and the filter
426 fields = fieldnames(tuning);
427 field_used =
false(1, numel(fields));
428 props_filter = string(properties(obj));
430 % loop through all fields from the
struct
431 for i = 1:numel(fields)
434 % check
if the field is a
property of the filter,
if not, skip
435 if ismember(field, props_filter)
436 obj.(field) = tuning.(field);
437 field_used(i) =
true;
439 warning(
"The field %s from the imported tuning file is not used.", field);
444 % check
if all fields from the imported
struct are used
446 warning(
"The following fields from the imported tuning file are not used: %s", strjoin(fields(~field_used),
", "));
453 methods (Access =
private)
455 % ==================================================================== %
456 %> @brief forward pass of the
EKF1 filter
458 %>
this function performs the forward pass of the
EKF1 filter
460 %> @param obj instance of the
EKF1 class
462 % ==================================================================== %
463 function forward(obj)
465 % make local copies of often used variables
473 bad_iterations =
false(1,obj.N);
478 [loc_x_fm(:, k), w] = obj.f_fun(obj.x_0, loc_z(:, k), loc_z(:, k), loc_zv(k));
480 loc_P_fm(:, :, k) = F * obj.P_0 * F
' + obj.Q_cov(obj.x_0);
483 [loc_x_fm(:, k), w]= obj.f_fun(loc_x_fp(:, k-1), loc_z(:, k-1), loc_z(:, k), loc_zv(k));
485 loc_P_fm(:, :, k) = F * loc_P_fp(:, :, k-1) * F' + obj.
Q_cov(loc_x_fp(:, k-1));
488 % normalize quaternion
489 loc_x_fm(1:4, k) = loc_x_fm(1:4, k) ./ norm(loc_x_fm(1:4, k), 2);
491 h = obj.h_fun(loc_x_fm(:, k));
492 H = obj.H_jac(loc_x_fm(:, k));
496 % there are no updates during movement
497 % TODO: the computing intensive part can be skipped completely instead of setting the H-Matrix to zero
502 % check
if the x position is unavailable
503 if isnan(loc_z(10, k))
504 % set the corresponding row in the H matrix to zero
508 % check
if the y position is unavailable
509 if isnan(loc_z(11, k))
510 % set the corresponding row in the H matrix to zero
514 % check
if the z position is unavailable
515 if isnan(loc_z(12, k))
516 % set the corresponding row in the H matrix to zero
520 % check
if the yaw is unavailable
521 if isnan(loc_z(13, k))
522 % set the corresponding row in the H matrix to zero
528 % fill missing values with zeros to prevent NaN values in the
529 % kalman gain. these zeros are not used in the update step
530 % since the corresponding rows in the H matrix are set to zero
531 this_z = fillmissing(loc_z(:, k),
'constant', 0);
534 K = loc_P_fm(:, :, k) * H
' * pinv(H * loc_P_fm(:, :, k) * H' + obj.R);
537 loc_x_fp(:, k) = loc_x_fm(:, k) + K * (this_z - h);
538 loc_P_fp(:, :, k) = (eye(obj.n) - K * H) * loc_P_fm(:, :, k) * (eye(obj.n) - K * H)
' + K * obj.R * K';
540 % normalize quaternion
541 loc_x_fp(1:4, k) = loc_x_fp(1:4, k) ./ norm(loc_x_fp(1:4, k), 2);
543 % check
if a warning occured
544 [~, warnID] = lastwarn;
545 if strcmp(warnID,
'MATLAB:nearlySingularMatrix') || strcmp(warnID,
'MATLAB:singularMatrix')
546 bad_iterations(k) = true;
547 lastwarn("",""); % reset warning
550 % check for NaN values
551 if any(isnan(loc_x_fp(:,k)), "all") || any(isnan(loc_P_fp(:,:,k)), "all")
552 error("NaN detected in forward pass at iteration %d.", k);
557 if any(bad_iterations)
558 warning("A total of %d bad iterations in the forward pass were detected.", sum(bad_iterations));
561 % update
object properties
569 % ==================================================================== %
570 %> @brief backward pass of the
EKF1 filter
572 %> this function performs the backward pass of the
EKF1 filter
574 %> @param obj instance of the
EKF1 class
576 % ==================================================================== %
577 function backward(obj)
579 % make local copies of often used variables
591 loc_x_bp(:, obj.N) = loc_x_fp(:, obj.N);
592 loc_P_bp(:, :, obj.N) = loc_P_fp(:, :, obj.N);
595 F = obj.
F_jac(loc_x_fp(:, k));
597 I = pinv(loc_P_fm(:, :, k+1));
598 K = loc_P_fp(:, :, k) * F' * I;
599 loc_x_bp(:,k) = loc_x_fp(:,k) + K * (loc_x_bp(:,k+1) - loc_x_fm(:,k+1));
600 loc_P_bp(:,:,k) = loc_P_fp(:,:,k) - K * (loc_P_fm(:,:,k+1) - loc_P_bp(:,:,k+1)) * K';
602 if any(isnan(loc_x_bp(:,k)), "all") || any(isnan(loc_P_bp(:,:,k)), "all")
603 error("NaN detected in backward pass at iteration %d.", k);
608 % update
object properties
614 % ==================================================================== %
615 %> @brief calculates the state transition function for the
EKF1 filter
617 %> this function calculates the state transition, also called the state
618 %> prediction/extrapolation.
620 %> @param x_this state vector at time k
621 %> @param z_this measurement vector at time k
622 %> @param z_next measurement vector at time k+1
623 %> @param zv zero velocity flag at time k
625 %> @retval x_next state vector at time k+1
626 %> @retval w preprocessed angular velocity
627 % ==================================================================== %
628 function [x_next, w] =
f_fun(obj, x_this, z_this, z_next, zv)
632 x_this (:,1)
double {mustBeNumeric}
633 z_this (:,1) double {mustBeNumeric}
634 z_next (:,1) double {mustBeNumeric}
639 x_next = nan(size(x_this));
643 q_this = x_this(1:4);
644 gyro_this = z_this(7:9);
645 gyro_next = z_next(7:9);
646 gyro_bias = x_this(11:13);
649 w_matrix = [0, -w(1) -w(2) -w(3); ...
650 w(1) 0 w(3) -w(2); ...
651 w(2) -w(3) 0 w(1); ...
654 q_next = (cos(w_norm * obj.dt / 2) * eye(4) + 1/(w_norm + epsilon) * sin(w_norm * obj.dt / 2) * w_matrix) * q_this;
655 x_next(1:4) = q_next;
658 acc_this = z_this(1:3);
659 acc_next = z_next(1:3);
660 acc = obj.getPreprocessedAcc(acc_this, acc_next, q_this, q_next, zv);
663 x_next(5:7) = x_this(5:7) + acc * obj.dt;
666 x_next(8:10) = x_this(8:10) + x_this(5:7) * obj.dt + 0.5 * acc * obj.dt^2;
669 x_next(11:13) = x_this(11:13);
674 % ==================================================================== %
675 %> @brief calculates the jacobian of the state transition function
677 %>
this function calculates the jacobian of the state transition
680 %> @param w preprocessed angular velocity at time k
682 %> @retval F jacobian of the state transition function at time k
683 % ==================================================================== %
684 function F =
F_jac(obj, w)
688 w (:,1) double {mustBeNumeric}
691 % F1 ( quaternion and angular velocity )
694 alpha = cos(w_norm * obj.dt / 2) * w_norm;
695 beta = sin(w_norm * obj.dt / 2);
697 F_1_1 = 1/(w_norm + epsilon) * [alpha, -w(1) * beta, -w(2) * beta, -w(3) * beta; ...
698 w(1) * beta, alpha, w(3) * beta, -w(2) * beta; ...
699 w(2) * beta, -w(3) * beta, alpha, w(1) * beta; ...
700 w(3) * beta, w(2) * beta, -w(1) * beta, alpha];
702 F_1 = [F_1_1, zeros(4,9)];
704 % F2 (position, velocity, acceleration)
707 F_2_1(4:6,1:3) = obj.dt * eye(3);
709 F_2 = [zeros(6,4), F_2_1, zeros(6,3)];
713 F_3(:,11:13) = eye(3);
720 % ==================================================================== %
721 %> @brief calculates the process noise covariance matrix
723 %>
this function calculates the process noise covariance matrix
725 %> @param x state vector at time k
727 %> @retval Q process noise covariance matrix
728 % ==================================================================== %
729 function Q =
Q_cov(obj, x)
733 x (:,1) double {mustBeNumeric}
737 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
738 Q_q = (obj.dt * obj.sigma_w_p / 2)^2 * [...
739 1 - q0^2, -q0 * q1, -q0 * q2, -q0 * q3; ...
740 -q0 * q1, 1 - q1^2, -q1 * q2, -q1 * q3; ...
741 -q0 * q2, -q1 * q2, 1 - q2^2, -q2 * q3; ...
742 -q0 * q3, -q1 * q3, -q2 * q3, 1 - q3^2];
744 % acceleration, velocity and position
746 Q_a(1:3, 1:3) = obj.dt^2 * eye(3);
747 Q_a(4:6, 4:6) = obj.dt^4 / 4 * eye(3);
748 Q_a(4:6, 1:3) = obj.dt * eye(3);
749 Q_a(4:6, 1:3) = obj.dt^3 / 2 * eye(3);
750 Q_a = (triu(Q_a.
',1) + tril(Q_a)) * obj.var_a;
753 Q_b_w = eye(3) * obj.var_b_w;
756 Q = blkdiag(Q_q, Q_a, Q_b_w);
759 assert(all(size(Q) == [obj.n, obj.n]), "The process noise covariance matrix must be of size %d x %d. You provided a matrix of size %d x %d.", obj.n, obj.n, size(Q, 1), size(Q, 2));
765 methods (Static, Access = private)
767 % ==================================================================== %
768 %> @brief calculates the jacobian of the measurement function from a
769 %> given state vector x
771 %> @param x state vector at time k
773 %> @retval H jacobian of the measurement function at time k
774 % ==================================================================== %
775 function H = H_jac(x)
778 x (:,1) double {mustBeNumeric}
784 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
786 H_1_1 = (2 * g * [ q2, -q3, q0, -q1; ...
787 -q1, -q0, -q3, -q2; ...
790 H_1 = [H_1_1, zeros(3,9)];
793 H_2 = [zeros(3,4), eye(3), zeros(3,6); ...
794 zeros(3,10), eye(3)];
796 % H3 (position pseudo measurement)
797 H_3 = [zeros(3,7), eye(3), zeros(3,3)];
799 % H4 (yaw pseudo measurement)
800 H_4_fac = 1/(2*q1*q3 - 2*q0*q2 + 1) + 1/(2*q0*q2 - 2*q1*q3 + 1);
801 H_4_1 = H_4_fac * ...
802 [2 * q3 * q1^2 - 2 * q0 * q2 * q1 - q3, ...
803 -2 * q1^2 * q2 - 2 * q0 * q1 * q3 - 2 * q2^3 - 2 * q2 * q3^2 + q2, ...
804 -2 * q1 * q3^2 + 2 * q0 * q2 * q3 + q1, ...
805 -2 * q0 * q2^2 + 2 * q1 * q3 * q2 + q0 ];
806 H_4 = [H_4_1, zeros(1,9)];
808 H = [H_1; H_2; H_3; H_4];
812 % ==================================================================== %
813 %> @brief calculates the measurement function from a given state vector
815 %> @param x state vector at time k
817 %> @retval h measurement vector at time k
818 % ==================================================================== %
822 x (:,1) double {mustBeNumeric}
825 g = [0;0;-9.81]; % gravity vector in world frame
828 RotMat = quaternion(x(1:4)').rotmat(
"frame");
830 h1 = RotMat * g; % acceleration in global frame
833 v_g = x(5:7); % velocity in global frame
834 b_w = x(11:13); % gyro biases
837 % position pseudo measurement
838 h3 = x(8:10); % position in global frame
840 % yaw pseudo measurement
841 q0 = x(1); q1 = x(2); q2 = x(3); q3 = x(4);
842 h4 = atan2(2 * q1 * q2 + 2 * q0 * q3, q1^2 + q0^2 - q3^2 -q2^2);
844 h = [h1; h2; h3; h4];
848 % ==================================================================== %
849 %> @brief calculates the preprocessed acceleration measurement
851 %>
this function calculates the preprocessed acceleration measurement as
852 %> explained in the derivation. The acceleration measurements are
853 %> preprocessed to get the bias free mean acceleration without gravity
854 %> between two samples.
856 %> @param acc_this acceleration measurement at time k
857 %> @param acc_next acceleration measurement at time k+1
858 %> @param q_this orientation quaternion at time k
859 %> @param q_next orientation quaternion at time k+1
860 %> @param zv zero velocity flag at time k
862 %> @retval acc preprocessed acceleration
863 % ==================================================================== %
867 acc_this (3,1)
double {mustBeNumeric}
868 acc_next (3,1) double {mustBeNumeric}
869 q_this (4,1) double {mustBeNumeric}
870 q_next (4,1) double {mustBeNumeric}
874 % if zero velocity is detected, the acceleration measurements are set to zero
878 % rotate acceleration measurements to global frame
879 acc_this = quaternion(q_this').rotmat("frame")' * (acc_this);
880 acc_next = quaternion(q_next').rotmat("frame")' * (acc_next);
882 % calculate mean acceleration
883 acc = (acc_this + acc_next) / 2;
886 acc = acc - [0;0;-9.81];
892 % ==================================================================== %
893 %> @brief calculates the preprocessed gyro measurement
895 %> this function calculates the preprocessed gyro measurement as
896 %> explained in the derivation. The gyro measurements are preprocessed
897 %> to get the bias free mean angular velocity between two samples.
899 %> @param gyro_this gyro measurement at time k
900 %> @param gyro_next gyro measurement at time k+1
901 %> @param bias bias of the gyroscope at time k
902 %> @param zv zero velocity flag at time k (not used at the moment)
904 %> @retval w preprocessed angular velocity
906 %> @note since a zero angular velocity leads to zeros in the F matrix
907 %> which leads to zero entries in the covariance matrix, the
908 %> differentiation cases are disabled. in some later implementations
909 %> (e.g. UKF or particle filter approach) this could be enabled again.
910 % ==================================================================== %
914 gyro_this (3,1)
double {mustBeNumeric}
915 gyro_next (3,1) double {mustBeNumeric}
916 bias (3,1) double {mustBeNumeric}
920 % if zero velocity is detected, the gyro measurements are set to zero
924 w = (gyro_this + gyro_next) / 2 - bias;
929 % ==================================================================== %
930 %> @brief displays the results of the
EKF1 filter
932 %> this function displays the results of the
EKF1 filter
934 %> @param x_fp filtered state vector
935 %> @param x_bp smoothed state vector
936 %> @param zv zero velocity flag
938 %> @param options.monitor monitor to display the results
939 % ==================================================================== %
950 ori_filt = quaternion(x_fp(1:4,:)').rotvecd;
951 pos_filt = x_fp(8:10,:)';
954 ori_smoo = quaternion(x_bp(1:4,:)').rotvecd;
955 pos_smoo = x_bp(8:10,:)';
957 disps = get(0, 'MonitorPositions');
959 f.Position = disps(options.monitor,:);
960 f.WindowState = 'maximized';
963 pl(1) = subplot(3,3,1);
964 plot(pos_filt(:,1), 'DisplayName', 'Filter');
966 plot(pos_smoo(:,1), 'DisplayName', 'Smoother', 'LineWidth',2);
973 pl(2) = subplot(3,3,2);
974 plot(pos_filt(:,2), 'DisplayName', 'Filter');
976 plot(pos_smoo(:,2), 'DisplayName', 'Smoother', 'LineWidth',2);
983 pl(3) = subplot(3,3,3);
984 plot(pos_filt(:,3), 'DisplayName', 'Filter');
986 plot(pos_smoo(:,3), 'DisplayName', 'Smoother', 'LineWidth',2);
992 % x,y plot (over two subplots)
993 pl(4) = subplot(3,3,[4,5]);
995 % filter path (thin line)
1000 surface([x;x], [y;y], [z;z], [col;col], "LineWidth",1, "EdgeColor","interp");
1004 % smooth path (fat line)
1009 surface([x;x], [y;y], [z;z], [col;col], "LineWidth",3, "EdgeColor","interp");
1012 xlabel("x position [m]")
1013 ylabel("y position [m]")
1017 pl(5) = subplot(3,3,6);
1019 plot(x_fp(11:13,:)', 'DisplayName', 'Filter');
1020 ylabel("Bias [rad/s]");
1022 plot(x_bp(11:13,:)', 'DisplayName', 'Smoother', 'LineWidth',2);
1023 ylabel("Bias [rad/s]");
1026 title("Bias Gyroscope");
1029 pl(7) = subplot(3,3,7);
1030 plot(ori_filt(:,1), 'DisplayName', 'Roll (filtered)');
1032 plot(ori_filt(:,2), 'DisplayName', 'Pitch (filtered)');
1033 plot(ori_smoo(:,1), 'LineWidth',2, 'DisplayName', 'Roll (smoothed)');
1034 plot(ori_smoo(:,2), 'LineWidth',2, 'DisplayName', 'Pitch (smoothed)');
1035 plot(ori_smoo(:,3), 'LineWidth',2, 'DisplayName', 'Yaw (smoothed)');
1039 ylabel("Angle [°]");
1040 title("Orientation");
1041 legend("Location", "best");
1044 pl(8) = subplot(3,3,8);
1045 plot(x_fp(5:7,:)', 'DisplayName', 'Filter');
1047 plot(x_bp(5:7,:)', 'DisplayName', 'Smoother', 'LineWidth',2);
1051 ylabel("Velocity [m/s]");
1054 % zero velocity detection plot
1055 pl(9) = subplot(3,3,9);
1056 plot(zv, '.-', 'DisplayName', 'Zero Velocity Detection', 'MarkerSize', 10);
1060 ylabel("Zero Velocity Detection");
1061 title("Zero Velocity Detection");
1064 linkaxes(pl([1:3,6:9]), 'x');