1 /*
2  * Copyright (c) 2021 Marti Riba Pons
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 #include <math.h>
9 #include <errno.h>
10 #include <zsl/orientation/fusion/fusion.h>
11 #include <zsl/orientation/fusion/mahony.h>
12 
13 static uint32_t zsl_fus_mahn_freq;
14 
zsl_fus_mahony_imu(struct zsl_vec * g,struct zsl_vec * a,zsl_real_t * Kp,zsl_real_t * Ki,struct zsl_vec * integralFB,zsl_real_t integral_limit,zsl_real_t * incl,struct zsl_quat * q)15 static int zsl_fus_mahony_imu(struct zsl_vec *g, struct zsl_vec *a,
16 			      zsl_real_t *Kp, zsl_real_t *Ki,
17 			      struct zsl_vec *integralFB,
18 				  zsl_real_t integral_limit,
19 				  zsl_real_t *incl,
20 			      struct zsl_quat *q)
21 {
22 	int rc = 0;
23 
24 #if CONFIG_ZSL_BOUNDS_CHECKS
25 	/* Make sure that the input vectors are tridimensional. */
26 	if ((a != NULL && (a->sz != 3)) || g->sz != 3 || integralFB->sz != 3) {
27 		rc = -EINVAL;
28 		goto err;
29 	}
30 	/* Make sure that the input quaternion is not zero. */
31 	if (ZSL_ABS(zsl_quat_magn(q)) < 1E-6) {
32 		rc = -EINVAL;
33 		goto err;
34 	}
35 #endif
36 
37 	/* Continue with the calculations only if the data from the accelerometer
38 	 * is valid (non zero). */
39 	if ((a != NULL) && ZSL_ABS(zsl_vec_norm(a)) > 1E-6) {
40 
41 		/* Normalise the accelerometer vector. */
42 		zsl_vec_to_unit(a);
43 
44 		/* Define the normalized quaternion of acceleration on the earth's
45 		 * reference frame, which only has a vertical component. */
46 		struct zsl_quat qa = {
47 			.r = 0.0,
48 			.i = 0.0,
49 			.j = 0.0,
50 			.k = 1.0
51 		};
52 
53 		/* Calculate an estimation of the gravity field 'v' using the input
54 		 * quaternion to rotate the normalised acceleration vector in the
55 		 * earth's reference frame 'qa'. */
56 		ZSL_VECTOR_DEF(v, 3);
57 		struct zsl_quat qaq;
58 		zsl_quat_rot(q, &qa, &qaq);
59 		v.data[0] = qaq.i;
60 		v.data[1] = qaq.j;
61 		v.data[2] = qaq.k;
62 
63 		/* The orientatiom error is the cross product between measured
64 		 * direction of gravity 'a' and estimated direction of gravity 'v'. */
65 		ZSL_VECTOR_DEF(e, 3);
66 		zsl_vec_cross(a, &v, &e);
67 
68 		/* Compute integral feedback. */
69 		integralFB->data[0] += e.data[0] * (1.0 / zsl_fus_mahn_freq);
70 		integralFB->data[1] += e.data[1] * (1.0 / zsl_fus_mahn_freq);
71 		integralFB->data[2] += e.data[2] * (1.0 / zsl_fus_mahn_freq);
72 
73 		/* Limit integral values */
74 		if(integralFB->data[0] > integral_limit) {
75 			integralFB->data[0] = integral_limit;
76 		} else if (integralFB->data[0] < -integral_limit) {
77 			integralFB->data[0] = -integral_limit;
78 		}
79 
80 		if(integralFB->data[1] > integral_limit) {
81 			integralFB->data[1] = integral_limit;
82 		} else if (integralFB->data[1] < -integral_limit) {
83 			integralFB->data[1] = -integral_limit;
84 		}
85 
86 		if(integralFB->data[2] > integral_limit) {
87 			integralFB->data[2] = integral_limit;
88 		} else if (integralFB->data[2] < -integral_limit) {
89 			integralFB->data[2] = -integral_limit;
90 		}
91 
92 		/* Apply integral feedback multiplied by Ki. */
93 		g->data[0] += *Ki * integralFB->data[0];
94 		g->data[1] += *Ki * integralFB->data[1];
95 		g->data[2] += *Ki * integralFB->data[2];
96 
97 		/* Apply proportional feedback. */
98 		g->data[0] += *Kp * e.data[0];
99 		g->data[1] += *Kp * e.data[1];
100 		g->data[2] += *Kp * e.data[2];
101 	}
102 
103 	/* Integrate rate of change of the input quaternion using the modified
104 	 * angular velocity data from the gyroscope. */
105 	zsl_quat_from_ang_vel(g, q, 1.0 / zsl_fus_mahn_freq, q);
106 
107 	/* Normalize the output quaternion. */
108 	zsl_quat_to_unit_d(q);
109 
110 err:
111 	return rc;
112 }
113 
zsl_fus_mahony(struct zsl_vec * g,struct zsl_vec * a,struct zsl_vec * m,zsl_real_t * Kp,zsl_real_t * Ki,struct zsl_vec * integralFB,zsl_real_t integral_limit,zsl_real_t * incl,struct zsl_quat * q)114 static int zsl_fus_mahony(struct zsl_vec *g, struct zsl_vec *a,
115 			  struct zsl_vec *m, zsl_real_t *Kp, zsl_real_t *Ki,
116 			  struct zsl_vec *integralFB,
117 			  zsl_real_t integral_limit, zsl_real_t *incl, struct zsl_quat *q)
118 {
119 	int rc = 0;
120 
121 #if CONFIG_ZSL_BOUNDS_CHECKS
122 	/* Make sure that the input vectors are tridimensional. */
123 	if ((a != NULL && (a->sz != 3)) || (m != NULL && (m->sz != 3)) ||
124 	    g->sz != 3 || integralFB->sz != 3) {
125 		rc = -EINVAL;
126 		goto err;
127 	}
128 	/* Make sure that the input quaternion is not zero. */
129 	if (ZSL_ABS(zsl_quat_magn(q)) < 1E-6) {
130 		rc = -EINVAL;
131 		goto err;
132 	}
133 #endif
134 
135 	/* Use IMU algorithm if the magnetometer measurement is invalid. */
136 	if ((m == NULL) || (ZSL_ABS(zsl_vec_norm(m)) < 1E-6)) {
137 		return zsl_fus_mahony_imu(g, a, Kp, Ki, integralFB, integral_limit, incl, q);
138 	}
139 
140 	/* Continue with the calculations only if the data from the accelerometer
141 	 * is valid (non zero). */
142 	if ((a != NULL) && ZSL_ABS(zsl_vec_norm(a)) > 1E-6) {
143 
144 		/* Normalise the accelerometer vector. */
145 		zsl_vec_to_unit(a);
146 
147 		/* Normalise the magnetometer vector. */
148 		zsl_vec_to_unit(m);
149 
150 		/* Define the normalized quaternion of acceleration on the earth's
151 		 * reference frame, which only has a vertical component. */
152 		struct zsl_quat qa = {
153 			.r = 0.0,
154 			.i = 0.0,
155 			.j = 0.0,
156 			.k = 1.0
157 		};
158 
159 		/* Calculate an estimation of the gravity field 'v' using the input
160 		 * quaternion to rotate the normalised acceleration vector in the
161 		 * earth's reference frame 'qa'. */
162 		ZSL_VECTOR_DEF(v, 3);
163 		struct zsl_quat qaq;
164 		zsl_quat_rot(q, &qa, &qaq);
165 		v.data[0] = qaq.i;
166 		v.data[1] = qaq.j;
167 		v.data[2] = qaq.k;
168 
169 		/* The gravity orientatiom error is the cross product between measured
170 		 * direction of gravity 'a' and estimated direction of gravity 'v'. */
171 		ZSL_VECTOR_DEF(e_g, 3);
172 		zsl_vec_cross(a, &v, &e_g);
173 
174 		/* Turn the data of the magnetometer into a pure quaterion. */
175 		struct zsl_quat qm = {
176 			.r = 0.0,
177 			.i = m->data[0],
178 			.j = m->data[1],
179 			.k = m->data[2]
180 		};
181 
182 		/* Define the normalized quaternion 'b' of magnetic field on the
183 		 * earth's reference frame, which only has a x (north) and z (vertical)
184 		 * components. If the declination angle is known, use it to calculate
185 		 * this vector. */
186 		zsl_real_t bx;
187 		zsl_real_t bz;
188 
189 		if (incl == NULL) {
190 			struct zsl_quat h;
191 			zsl_quat_rot(q, &qm, &h);
192 			bx = ZSL_SQRT(h.i * h.i + h.j * h.j);
193 			bz = h.k;
194 		} else {
195 			bx = ZSL_COS(*incl * ZSL_PI / 180.0);
196 			bz = ZSL_SIN(*incl * ZSL_PI / 180.0);
197 		}
198 
199 		struct zsl_quat b = { .r = 0.0, .i = bx, .j = 0.0, .k = bz };
200 
201 		/* Calculate an estimation of the magnetic field 'bf' using the input
202 		 * quaternion to rotate the normalised magnetic field quaternion in the
203 		 * earth's reference frame 'b'. */
204 		ZSL_VECTOR_DEF(bf, 3);
205 		struct zsl_quat qbq;
206 		zsl_quat_rot(q, &b, &qbq);
207 		bf.data[0] = qbq.i;
208 		bf.data[1] = qbq.j;
209 		bf.data[2] = qbq.k;
210 
211 		/* The magnetic field orientation error is the cross product between
212 		 * measured direction of magnetic field 'm' and estimated direction of
213 		 * magnetic field 'bf'. */
214 		ZSL_VECTOR_DEF(e_b, 3);
215 		zsl_vec_cross(m, &bf, &e_b);
216 
217 		/* The total orientation error is the sum of the gravity and the
218 		 * magnetic field errors. */
219 		ZSL_VECTOR_DEF(e, 3);
220 		zsl_vec_add(&e_g, &e_b, &e);
221 
222 		/* Compute and apply integral feedback if enabled. */
223 		integralFB->data[0] += e.data[0] * (1.0 / zsl_fus_mahn_freq);
224 		integralFB->data[1] += e.data[1] * (1.0 / zsl_fus_mahn_freq);
225 		integralFB->data[2] += e.data[2] * (1.0 / zsl_fus_mahn_freq);
226 
227 		/* Limit integral values */
228 		if(integralFB->data[0] > integral_limit) {
229 			integralFB->data[0] = integral_limit;
230 		} else if (integralFB->data[0] < -integral_limit) {
231 			integralFB->data[0] = -integral_limit;
232 		}
233 
234 		if(integralFB->data[1] > integral_limit) {
235 			integralFB->data[1] = integral_limit;
236 		} else if (integralFB->data[1] < -integral_limit) {
237 			integralFB->data[1] = -integral_limit;
238 		}
239 
240 		if(integralFB->data[2] > integral_limit) {
241 			integralFB->data[2] = integral_limit;
242 		} else if (integralFB->data[2] < -integral_limit) {
243 			integralFB->data[2] = -integral_limit;
244 		}
245 
246 		/* Apply integral feedback multiplied by Ki. */
247 		g->data[0] += *Ki * integralFB->data[0];
248 		g->data[1] += *Ki * integralFB->data[1];
249 		g->data[2] += *Ki * integralFB->data[2];
250 
251 		/* Apply proportional feedback. */
252 		g->data[0] += *Kp * e.data[0];
253 		g->data[1] += *Kp * e.data[1];
254 		g->data[2] += *Kp * e.data[2];
255 	}
256 
257 	/* Integrate rate of change of the input quaternion using the modified
258 	 * angular velocity data from the gyroscope. */
259 	zsl_quat_from_ang_vel(g, q, 1.0 / zsl_fus_mahn_freq, q);
260 
261 	/* Normalize the output quaternion. */
262 	zsl_quat_to_unit_d(q);
263 
264 err:
265 	return rc;
266 }
267 
zsl_fus_mahn_init(uint32_t freq,void * cfg)268 int zsl_fus_mahn_init(uint32_t freq, void *cfg)
269 {
270 	int rc = 0;
271 
272 	struct zsl_fus_mahn_cfg *mcfg = cfg;
273 
274 	(void)mcfg;
275 
276 #if CONFIG_ZSL_BOUNDS_CHECKS
277 	/* Make sure that the sample frequency is positive. */
278 	if (freq <= 0.0) {
279 		rc = -EINVAL;
280 		goto err;
281 	}
282 #endif
283 
284 	zsl_fus_mahn_freq = freq;
285 
286 err:
287 	return rc;
288 }
289 
zsl_fus_mahn_feed(struct zsl_vec * a,struct zsl_vec * m,struct zsl_vec * g,zsl_real_t * incl,struct zsl_quat * q,void * cfg)290 int zsl_fus_mahn_feed(struct zsl_vec *a, struct zsl_vec *m, struct zsl_vec *g,
291 		      zsl_real_t *incl, struct zsl_quat *q, void *cfg)
292 {
293 	struct zsl_fus_mahn_cfg *mcfg = cfg;
294 
295 	if (mcfg->kp < 0.0 || mcfg->ki < 0.0) {
296 		return -EINVAL;
297 	}
298 
299 	return zsl_fus_mahony(g, a, m, &(mcfg->kp), &(mcfg->ki), &(mcfg->intfb),
300 			      mcfg->integral_limit, incl, q);
301 }
302 
zsl_fus_mahn_error(int error)303 void zsl_fus_mahn_error(int error)
304 {
305 	/* ToDo: Log error in default handler. */
306 }
307