1use libm::{atan2f, cosf, fabsf, sinf};
2use crate::{fusion_degrees_to_radians, FusionAhrs, FusionAhrsFlags, FusionAhrsSettings, FusionConvention, FusionQuaternion, FusionVector};
3use crate::FusionConvention::NWU;
4
5const INITIAL_GAIN: f32 = 10.0f32;
9const INITIALISATION_PERIOD: f32 = 3.0f32;
13
14impl FusionAhrs {
15 pub fn new() -> Self {
16 let settings = FusionAhrsSettings::new();
17 let gain = settings.gain;
18 let recovery_trigger_period = settings.recovery_trigger_period;
19 Self {
20 settings,
21 quaternion: FusionQuaternion::identity(),
22 acc: FusionVector::zero(),
23 initialising: true,
24 ramped_gain: INITIAL_GAIN,
25 ramped_gain_step: (INITIAL_GAIN - gain) / INITIALISATION_PERIOD,
26 angular_rate_recovery: false,
27 half_accelerometer_feedback: FusionVector::zero(),
28 half_magnetometer_feedback: FusionVector::zero(),
29 accelerometer_ignored: false,
30 acceleration_recovery_trigger: 0,
31 acceleration_recovery_timeout: recovery_trigger_period,
32 magnetometer_ignored: false,
33 magnetic_recovery_trigger: 0,
34 magnetic_recovery_timeout: recovery_trigger_period,
35 }
36 }
37
38 pub fn update_settings(&mut self, settings: FusionAhrsSettings) {
39 self.settings.convention = settings.convention;
40 self.settings.gain = settings.gain;
41 self.settings.gyr_range = if settings.gyr_range == 0.0f32 { f32::MAX } else { 0.98f32 * settings.gyr_range };
42 self.settings.acc_rejection = if settings.acc_rejection == 0.0f32 {
43 f32::MAX
44 } else {
45 let v = 0.5f32 * sinf(fusion_degrees_to_radians(settings.acc_rejection));
46 v * v
47 };
48 self.settings.mag_rejection = if settings.mag_rejection == 0.0f32 {
49 f32::MAX
50 } else {
51 let v = 0.5f32 * sinf(fusion_degrees_to_radians(settings.mag_rejection));
52 v * v
53 };
54 self.settings.recovery_trigger_period = settings.recovery_trigger_period;
55 self.acceleration_recovery_timeout = self.settings.recovery_trigger_period;
56 self.magnetic_recovery_timeout = self.settings.recovery_trigger_period;
57 if settings.gain == 0.0f32 || settings.recovery_trigger_period == 0 { self.settings.acc_rejection = f32::MAX;
59 self.settings.mag_rejection = f32::MAX;
60 }
61 if !self.initialising {
62 self.ramped_gain = settings.gain;
63 }
64 self.ramped_gain_step = (INITIAL_GAIN - self.settings.gain) / INITIALISATION_PERIOD;
65 }
66
67 pub fn update_no_mag(&mut self, gyr: FusionVector, acc: FusionVector, dt: f32) {
68 self.update(gyr, acc, FusionVector::zero(), dt);
69 if self.initialising == true {
71 self.set_heading(0.0f32);
72 }
73 }
74
75 pub fn update_external_heading(&mut self, gyr: FusionVector, acc: FusionVector, heading: f32, dt: f32) {
76 let q = self.quaternion;
77 let roll = atan2f(q.w * q.x + q.y * q.z, 0.5f32 - q.y * q.y - q.x * q.x);
79 let heading_radians = fusion_degrees_to_radians(heading);
81 let sin_heading_radians = sinf(heading_radians);
82 let magnetometer = FusionVector {
83 x: cosf(heading_radians),
84 y: -1.0f32 * cosf(roll) * sin_heading_radians,
85 z: sin_heading_radians * sinf(roll),
86 };
87 self.update(gyr, acc, magnetometer, dt);
89 }
90
91 pub fn update(&mut self, gyr: FusionVector, acc: FusionVector, mag: FusionVector, dt: f32) {
92 self.acc = acc;
94 if fabsf(gyr.x) > self.settings.gyr_range || fabsf(gyr.y) > self.settings.gyr_range || fabsf(gyr.z) > self.settings.gyr_range {
96 let quaternion = self.quaternion;
97 self.reset();
98 self.quaternion = quaternion;
99 self.angular_rate_recovery = true;
100 }
101 if self.initialising == true {
103 self.ramped_gain -= self.ramped_gain_step * dt;
104 if self.ramped_gain < self.settings.gain || self.settings.gain == 0.0f32 {
105 self.ramped_gain = self.settings.gain;
106 self.initialising = false;
107 self.angular_rate_recovery = false;
108 }
109 }
110 let half_gravity = self.calculate_half_gravity();
112
113 let mut half_accelerometer_feedback = FusionVector::zero();
115 self.accelerometer_ignored = true;
116 if !self.acc.is_zero() {
117 self.half_accelerometer_feedback = self.feedback(self.acc.normalize(), half_gravity);
119 if self.initialising == true || self.half_accelerometer_feedback.magnitude() <= self.settings.acc_rejection {
121 self.accelerometer_ignored = false;
122 self.acceleration_recovery_trigger -= 9;
123 } else {
124 self.acceleration_recovery_trigger += 1;
125 }
126 if self.acceleration_recovery_trigger > self.acceleration_recovery_timeout {
128 self.acceleration_recovery_timeout = 0;
129 self.accelerometer_ignored = false;
130 } else {
131 self.acceleration_recovery_timeout = self.settings.recovery_trigger_period;
132 }
133 self.acceleration_recovery_trigger = clamp(self.acceleration_recovery_trigger, 0, self.settings.recovery_trigger_period);
134 if !self.accelerometer_ignored {
136 half_accelerometer_feedback = self.half_accelerometer_feedback;
137 }
138 }
139 let mut half_magnetometer_feedback = FusionVector::zero();
141 self.magnetometer_ignored = true;
142 if !mag.is_zero() {
143 let half_magnetic = self.calculate_half_magnetic();
145 self.half_magnetometer_feedback = self.feedback(half_gravity.cross_product(&mag).normalize(), half_magnetic);
148 if self.initialising == true || self.half_magnetometer_feedback.magnitude() <= self.settings.mag_rejection {
150 self.magnetometer_ignored = false;
151 self.magnetic_recovery_trigger -= 9;
152 } else {
153 self.magnetic_recovery_trigger += 1;
154 }
155 if self.magnetic_recovery_trigger > self.magnetic_recovery_timeout {
157 self.magnetic_recovery_timeout = 0;
158 self.magnetometer_ignored = false;
159 } else {
160 self.magnetic_recovery_timeout = self.settings.recovery_trigger_period;
161 }
162 self.magnetic_recovery_trigger = clamp(self.magnetic_recovery_trigger, 0, self.settings.recovery_trigger_period);
163 if !self.magnetometer_ignored {
165 half_magnetometer_feedback = self.half_magnetometer_feedback;
166 }
167 }
168
169 let half_gyroscope = gyr * fusion_degrees_to_radians(0.5f32);
171 let adjusted_half_gyroscope = half_gyroscope + (half_accelerometer_feedback + half_magnetometer_feedback) * self.ramped_gain;
173 self.quaternion = self.quaternion + self.quaternion * (adjusted_half_gyroscope * dt);
175 self.quaternion = self.quaternion.normalize();
177 }
178
179
180 fn feedback(&self, sensor: FusionVector, reference: FusionVector) -> FusionVector {
181 if sensor.dot_product(&reference) < 0.0f32 { sensor.cross_product(&reference).normalize()
183 } else {
184 sensor.cross_product(&reference)
185 }
186 }
187
188 fn set_heading(&mut self, heading: f32) {
189 let q = self.quaternion;
190 let yaw = atan2f(q.w * q.z + q.x * q.y, 0.5f32 - q.y * q.y - q.z * q.z);
191 let half_yaw_minus_heading = 0.5f32 * (yaw - fusion_degrees_to_radians(heading));
192 let rotation = FusionQuaternion {
193 w: cosf(half_yaw_minus_heading),
194 x: 0.0f32,
195 y: 0.0f32,
196 z: -1.0f32 * sinf(half_yaw_minus_heading),
197 };
198 self.quaternion = rotation * self.quaternion;
199 }
200
201 pub fn reset(&mut self) {
202 self.quaternion = FusionQuaternion::identity();
203 self.acc = FusionVector::zero();
204 self.initialising = true;
205 self.ramped_gain = INITIAL_GAIN;
206 self.angular_rate_recovery = false;
207 self.half_accelerometer_feedback = FusionVector::zero();
208 self.half_magnetometer_feedback = FusionVector::zero();
209 self.accelerometer_ignored = false;
210 self.acceleration_recovery_trigger = 0;
211 self.acceleration_recovery_timeout = self.settings.recovery_trigger_period;
212 self.magnetometer_ignored = false;
213 self.magnetic_recovery_trigger = 0;
214 self.magnetic_recovery_timeout = self.settings.recovery_trigger_period;
215 }
216
217 pub fn calculate_half_gravity(&self) -> FusionVector {
218 let q = self.quaternion;
219 match self.settings.convention {
220 FusionConvention::ENU | FusionConvention::NWU => {
221 FusionVector {
222 x: q.x * q.z - q.w * q.y,
223 y: q.y * q.z + q.w * q.x,
224 z: q.w * q.w - 0.5f32 + q.z * q.z,
225 }
226 }
227 FusionConvention::NED => {
228 FusionVector {
229 x: q.w * q.y - q.x * q.z,
230 y: -1.0f32 * (q.y * q.z + q.w * q.x),
231 z: 0.5f32 - q.w * q.w - q.z * q.z,
232 }
233 }
234 }
235 }
236
237 pub fn calculate_half_magnetic(&self) -> FusionVector {
238 let q = self.quaternion;
239 match self.settings.convention {
240 FusionConvention::NWU => {
241 let half_magnetic = FusionVector {
242 x: q.x * q.y + q.w * q.z,
243 y: q.w * q.w - 0.5f32 + q.y * q.y,
244 z: q.y * q.z - q.w * q.x,
245 };
246 half_magnetic
247 }
248 FusionConvention::ENU => {
249 let half_magnetic = FusionVector {
250 x: 0.5f32 - q.w * q.w - q.x * q.x,
251 y: q.w * q.z - q.x * q.y,
252 z: -1.0f32 * (q.x * q.z + q.w * q.y),
253 };
254 half_magnetic
255 }
256 FusionConvention::NED => {
257 let half_magnetic = FusionVector {
258 x: -1.0f32 * (q.x * q.y + q.w * q.z),
259 y: 0.5f32 - q.w * q.w - q.y * q.y,
260 z: q.w * q.x - q.y * q.z,
261 };
262 half_magnetic
263 }
264 }
265 }
266 pub fn earth_acc(&self) -> FusionVector {
267 let q = self.quaternion;
269 let a = self.acc;
270 let qwqw = q.w * q.w; let qwqx = q.w * q.x;
272 let qwqy = q.w * q.y;
273 let qwqz = q.w * q.z;
274 let qxqy = q.x * q.y;
275 let qxqz = q.x * q.z;
276 let qyqz = q.y * q.z;
277 let mut accelerometer = FusionVector {
278 x: 2.0f32 * ((qwqw - 0.5f32 + q.x * q.x) * a.x + (qxqy - qwqz) * a.y + (qxqz + qwqy) * a.z),
279 y: 2.0f32 * ((qxqy + qwqz) * a.x + (qwqw - 0.5f32 + q.y * q.y) * a.y + (qyqz - qwqx) * a.z),
280 z: 2.0f32 * ((qxqz - qwqy) * a.x + (qyqz + qwqx) * a.y + (qwqw - 0.5f32 + q.z * q.z) * a.z),
281 };
282 match self.settings.convention {
284 FusionConvention::ENU | FusionConvention::NWU => {
285 accelerometer.z -= 1.0f32;
286 accelerometer
287 }
288 FusionConvention::NED => {
289 accelerometer.z += 1.0f32;
290 accelerometer
291 }
292 }
293 }
294
295 pub fn linear_acc(&self) -> FusionVector {
296 let q = self.quaternion;
298 let gravity = FusionVector {
300 x: 2.0f32 * (q.x * q.z - q.w * q.y),
301 y: 2.0f32 * (q.y * q.z + q.w * q.x),
302 z: 2.0f32 * (q.w * q.w - 0.5f32 + q.z * q.z),
303 };
304
305 match self.settings.convention {
307 FusionConvention::ENU | FusionConvention::NWU => {
308 self.acc - gravity
309 }
310 FusionConvention::NED => {
311 self.acc + gravity
312 }
313 }
314 }
315
316 pub fn flags(&self) -> FusionAhrsFlags {
317 FusionAhrsFlags {
318 initializing: self.initialising,
319 angular_rate_recovery: self.angular_rate_recovery,
320 acceleration_recovery: self.acceleration_recovery_trigger
321 > self.acceleration_recovery_timeout,
322 magnetic_recovery: self.magnetic_recovery_trigger > self.magnetic_recovery_timeout,
323 }
324 }
325}
326
327impl FusionAhrsSettings {
328 pub fn new() -> Self {
329 Self {
330 convention: NWU,
331 gain: 0.5f32,
332 gyr_range: 0.0f32,
333 acc_rejection: 90.0f32,
334 mag_rejection: 90.0f32,
335 recovery_trigger_period: 0,
336 }
337 }
338}
339
340fn clamp<T: Ord>(value: T, min: T, max: T) -> T {
341 if value < min {
342 min
343 } else if value > max {
344 max
345 } else {
346 value
347 }
348}