Dawn Framework 1.0
Universal data acquisition framework for embedded systems
ahrs.cxx
1// dawn/src/prog/ahrs.cxx
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#include "dawn/prog/ahrs.hxx"
7
8#include <ctime>
9#include <new>
10
11#include "dawn/debug.hxx"
12#include "dawn/io/common.hxx"
13
14using namespace dawn;
15
16namespace
17{
18constexpr float GRAVITY = 9.80665f;
19constexpr float RAD2DEG = 57.29577951308232f;
20constexpr float DT_MIN = 0.001f; // clamp window for measured dt
21constexpr float DT_MAX = 0.1f;
22
23uint64_t monotonicNs()
24{
25 struct timespec ts;
26
27 clock_gettime(CLOCK_MONOTONIC, &ts);
28 return static_cast<uint64_t>(ts.tv_sec) * 1000000000ull + ts.tv_nsec;
29}
30} // namespace
31
32CProgAhrs::CProgAhrs(CDescObject &desc)
33 : CProgCommon(desc)
34 , accel(nullptr)
35 , gyro(nullptr)
36 , mag(nullptr)
37 , output(nullptr)
38 , accelId(0)
39 , gyroId(0)
40 , magId(0)
41 , outputId(0)
42 , outData(nullptr)
43 , active(false)
44 , registered(false)
45 , pGain(0.5f)
46 , pRejection(10.0f)
47 , pPeriod(5.0f)
48 , pRate(50.0f)
49 , haveAccel(false)
50 , haveMag(false)
51 , lastNs(0)
52{
53}
54
55CProgAhrs::~CProgAhrs()
56{
57 deinit();
58}
59
60int CProgAhrs::configureDesc(const CDescObject &desc)
61{
63 const SObjectId::UObjectId *ids;
64 size_t offset = 0;
65
66 for (size_t i = 0; i < desc.getSize(); i++)
67 {
68 item = desc.objectCfgItemNext(offset);
69
70 if (item->cfgid.s.cls != CProgCommon::PROG_CLASS_AHRS)
71 {
72 DAWNERR("ahrs: unsupported cfg class 0x%" PRIx32 "\n", item->cfgid.v);
73 return -EINVAL;
74 }
75
76 switch (item->cfgid.s.id)
77 {
78 case PROG_AHRS_CFG_ACCEL:
79 {
80 ids = reinterpret_cast<const SObjectId::UObjectId *>(item->data);
81 accelId = ids[0].v;
82 setObjectMapItem(accelId, nullptr);
83 break;
84 }
85
86 case PROG_AHRS_CFG_GYRO:
87 {
88 ids = reinterpret_cast<const SObjectId::UObjectId *>(item->data);
89 gyroId = ids[0].v;
90 setObjectMapItem(gyroId, nullptr);
91 break;
92 }
93
94 case PROG_AHRS_CFG_MAG:
95 {
96 ids = reinterpret_cast<const SObjectId::UObjectId *>(item->data);
97 magId = ids[0].v;
98 setObjectMapItem(magId, nullptr);
99 break;
100 }
101
102 case PROG_AHRS_CFG_OUTPUT:
103 {
104 ids = reinterpret_cast<const SObjectId::UObjectId *>(item->data);
105 outputId = ids[0].v;
106 setObjectMapItem(outputId, nullptr);
107 break;
108 }
109
110 case PROG_AHRS_CFG_PARAMS:
111 {
112 if (item->cfgid.s.size != sizeof(SProgAhrsParams) / 4)
113 {
114 DAWNERR("ahrs: invalid PARAMS size %d, expected %zu\n",
115 item->cfgid.s.size,
116 sizeof(SProgAhrsParams) / 4);
117 return -EINVAL;
118 }
119
120 const SProgAhrsParams *params = reinterpret_cast<const SProgAhrsParams *>(item->data);
121
122 pGain = SObjectCfg::cfgToF(params->gain);
123 pRejection = SObjectCfg::cfgToF(params->accel_rejection);
124 pPeriod = SObjectCfg::cfgToF(params->recovery_period);
125 pRate = SObjectCfg::cfgToF(params->rate);
126 break;
127 }
128
129 default:
130 DAWNERR("ahrs: unsupported cfg id %u\n", item->cfgid.s.id);
131 return -EINVAL;
132 }
133 }
134
135 if (accelId == 0 || gyroId == 0 || outputId == 0)
136 {
137 DAWNERR("ahrs: accel, gyro and output are required\n");
138 return -EINVAL;
139 }
140
141 if (!paramsValid(pGain, pRejection, pPeriod, pRate))
142 {
143 DAWNERR("ahrs: params out of range\n");
144 return -EINVAL;
145 }
146
147 return OK;
148}
149
151{
152 return configureDesc(getDesc());
153}
154
155bool CProgAhrs::paramsValid(float gain, float rejection, float period, float rate)
156{
157 return gain >= 0.0f && gain <= 10.0f && rejection >= 0.0f && rejection <= 90.0f &&
158 period >= 0.0f && period <= 60.0f && rate >= 1.0f && rate <= 1000.0f;
159}
160
161int CProgAhrs::validateInput(CIOCommon *io) const
162{
163 if (io == nullptr)
164 {
165 return -EIO;
166 }
167
168 if (!io->isRead() || !io->isNotify() || io->getDtype() != SObjectId::DTYPE_FLOAT ||
169 io->getDataDim() < 3)
170 {
171 return -EINVAL;
172 }
173
174 return OK;
175}
176
177void CProgAhrs::applySettings()
178{
179 FusionAhrsSettings settings;
180
181 settings.convention = FusionConventionNwu;
182 settings.gain = pGain;
183 settings.gyroscopeRange = 2000.0f;
184 settings.accelerationRejection = pRejection;
185 // The optional magnetometer shares the rejection knob with the accel.
186 settings.magneticRejection = (magId != 0) ? pRejection : 0.0f;
187 settings.recoveryTriggerPeriod = static_cast<unsigned int>(pPeriod * pRate);
188
189 FusionAhrsSetSettings(&ahrs, &settings);
190}
191
193{
194 int ret;
195
196 accel = getIO(accelId);
197 gyro = getIO(gyroId);
198 output = getIO(outputId);
199
200 if (accel == nullptr || gyro == nullptr || output == nullptr)
201 {
202 DAWNERR("ahrs: IO not found\n");
203 return -EIO;
204 }
205
206 ret = validateInput(accel);
207 if (ret != OK)
208 {
209 DAWNERR("ahrs: accel IO 0x%" PRIx32 " incompatible\n", accelId);
210 return ret;
211 }
212
213 ret = validateInput(gyro);
214 if (ret != OK)
215 {
216 DAWNERR("ahrs: gyro IO 0x%" PRIx32 " incompatible\n", gyroId);
217 return ret;
218 }
219
220 if (magId != 0)
221 {
222 mag = getIO(magId);
223 ret = validateInput(mag);
224 if (ret != OK)
225 {
226 DAWNERR("ahrs: mag IO 0x%" PRIx32 " incompatible\n", magId);
227 return ret;
228 }
229 }
230
231 ret = prepareWritableTarget(output, 3, true);
232 if (ret != OK)
233 {
234 DAWNERR("ahrs: output prepare failed %d\n", ret);
235 return ret;
236 }
237
238 outData = output->ddata_alloc(1);
239 if (outData == nullptr)
240 {
241 DAWNERR("ahrs: data allocation failed\n");
242 return -ENOMEM;
243 }
244
245 FusionOffsetInitialise(&offset, static_cast<unsigned int>(pRate));
246 FusionAhrsInitialise(&ahrs);
247 applySettings();
248 haveAccel = false;
249 haveMag = false;
250 lastNs = 0;
251
252 return OK;
253}
254
256{
257 doStop();
258 delete outData;
259 outData = nullptr;
260 accel = nullptr;
261 gyro = nullptr;
262 mag = nullptr;
263 output = nullptr;
264 accelId = 0;
265 gyroId = 0;
266 magId = 0;
267 outputId = 0;
268 return OK;
269}
270
271int CProgAhrs::accelNotifierCb(void *priv, io_ddata_t *data)
272{
273 CProgAhrs *obj = static_cast<CProgAhrs *>(priv);
274
275 if (obj == nullptr || !obj->active || data == nullptr)
276 {
277 return OK;
278 }
279
280 const float *v = reinterpret_cast<const float *>(data->getDataPtr());
281
282 obj->accelG.axis.x = v[0] / GRAVITY;
283 obj->accelG.axis.y = v[1] / GRAVITY;
284 obj->accelG.axis.z = v[2] / GRAVITY;
285 obj->haveAccel = true;
286 return OK;
287}
288
289int CProgAhrs::magNotifierCb(void *priv, io_ddata_t *data)
290{
291 CProgAhrs *obj = static_cast<CProgAhrs *>(priv);
292
293 if (obj == nullptr || !obj->active || data == nullptr)
294 {
295 return OK;
296 }
297
298 const float *v = reinterpret_cast<const float *>(data->getDataPtr());
299
300 obj->magRaw.axis.x = v[0];
301 obj->magRaw.axis.y = v[1];
302 obj->magRaw.axis.z = v[2];
303 obj->haveMag = true;
304 return OK;
305}
306
307int CProgAhrs::gyroNotifierCb(void *priv, io_ddata_t *data)
308{
309 CProgAhrs *obj = static_cast<CProgAhrs *>(priv);
310
311 if (obj == nullptr || !obj->active || data == nullptr)
312 {
313 return OK;
314 }
315
316 obj->handleGyro(data);
317 return OK;
318}
319
320void CProgAhrs::handleGyro(io_ddata_t *data)
321{
322 if (!haveAccel || outData == nullptr || output == nullptr)
323 {
324 // No attitude reference yet - do not integrate.
325
326 return;
327 }
328
329 const float *v = reinterpret_cast<const float *>(data->getDataPtr());
330 FusionVector g;
331
332 g.axis.x = v[0] * RAD2DEG;
333 g.axis.y = v[1] * RAD2DEG;
334 g.axis.z = v[2] * RAD2DEG;
335
336 // dt from the monotonic clock; fall back to the nominal rate outside the
337 // plausible window (first sample, scheduling hiccup, paused stream).
338
339 uint64_t now = monotonicNs();
340 float dt = 1.0f / pRate;
341
342 if (lastNs != 0)
343 {
344 float measured = static_cast<float>(now - lastNs) * 1e-9f;
345 if (measured >= DT_MIN && measured <= DT_MAX)
346 {
347 dt = measured;
348 }
349 }
350
351 lastNs = now;
352
353 g = FusionOffsetUpdate(&offset, g);
354 if (haveMag)
355 {
356 FusionAhrsUpdate(&ahrs, g, accelG, magRaw, dt);
357 }
358 else
359 {
360 FusionAhrsUpdateNoMagnetometer(&ahrs, g, accelG, dt);
361 }
362
363 const FusionVector earth = FusionAhrsGetEarthAcceleration(&ahrs);
364 float *out = reinterpret_cast<float *>(outData->getDataPtr());
365
366 out[0] = earth.axis.x * GRAVITY;
367 out[1] = earth.axis.y * GRAVITY;
368 out[2] = earth.axis.z * GRAVITY;
369
370 int ret = output->setData(*outData);
371 if (ret != OK)
372 {
373 DAWNERR("ahrs: output setData failed %d\n", ret);
374 }
375}
376
377int CProgAhrs::onSetObjConfig(SObjectCfg::ObjectCfgId objcfg, uint32_t *data, size_t len)
378{
379 if (SObjectCfg::objectCfgGetId(objcfg) != PROG_AHRS_CFG_PARAMS)
380 {
381 return OK;
382 }
383
384 if (data == nullptr || len != sizeof(SProgAhrsParams) / sizeof(uint32_t))
385 {
386 return -EINVAL;
387 }
388
389 const SProgAhrsParams *params = reinterpret_cast<const SProgAhrsParams *>(data);
390 float gain = SObjectCfg::cfgToF(params->gain);
391 float rejection = SObjectCfg::cfgToF(params->accel_rejection);
392 float period = SObjectCfg::cfgToF(params->recovery_period);
393 float rate = SObjectCfg::cfgToF(params->rate);
394
395 if (!paramsValid(gain, rejection, period, rate))
396 {
397 return -EINVAL;
398 }
399
400 bool rateChanged = rate != pRate;
401
402 pGain = gain;
403 pRejection = rejection;
404 pPeriod = period;
405 pRate = rate;
406 applySettings();
407
408 if (rateChanged)
409 {
410 FusionOffsetInitialise(&offset, static_cast<unsigned int>(pRate));
411 }
412
413 return OK;
414}
415
417{
418 int ret;
419
420 if (!registered)
421 {
422 ret = accel->setNotifier(accelNotifierCb, 0, this);
423 if (ret != OK)
424 {
425 DAWNERR("ahrs: accel setNotifier failed %d\n", ret);
426 return ret;
427 }
428
429 ret = gyro->setNotifier(gyroNotifierCb, 0, this);
430 if (ret != OK)
431 {
432 DAWNERR("ahrs: gyro setNotifier failed %d\n", ret);
433 accel->setNotifier(nullptr, 0, nullptr);
434 return ret;
435 }
436
437 if (mag != nullptr)
438 {
439 ret = mag->setNotifier(magNotifierCb, 0, this);
440 if (ret != OK)
441 {
442 DAWNERR("ahrs: mag setNotifier failed %d\n", ret);
443 accel->setNotifier(nullptr, 0, nullptr);
444 gyro->setNotifier(nullptr, 0, nullptr);
445 return ret;
446 }
447 }
448
449 registered = true;
450 }
451
452 active = true;
453 return OK;
454}
455
457{
458 if (registered)
459 {
460 if (accel != nullptr)
461 {
462 accel->setNotifier(nullptr, 0, nullptr);
463 }
464
465 if (gyro != nullptr)
466 {
467 gyro->setNotifier(nullptr, 0, nullptr);
468 }
469
470 if (mag != nullptr)
471 {
472 mag->setNotifier(nullptr, 0, nullptr);
473 }
474
475 registered = false;
476 }
477
478 active = false;
479 return OK;
480}
481
483{
484 return false;
485}
CIOCommon * getIO(SObjectId::ObjectId id)
Get an I/O object by ID.
Definition bindable.cxx:41
void setObjectMapItem(SObjectId::ObjectId id, CObject *obj)
Set an item in the object map.
Definition bindable.cxx:23
Descriptor wrapper for individual object configuration.
size_t getSize() const
Get number of configuration items for this object.
SObjectCfg::SObjectCfgItem * objectCfgItemNext(size_t &offset) const
Get config item at current offset and advance past it.
Base class for all I/O objects.
Definition common.hxx:27
int setData(IODataCmn &data, size_t offset=0)
Set data for I/O (public interface with stats tracking).
Definition common.hxx:414
virtual bool isNotify() const =0
Check if IO supports notifications.
virtual size_t getDataDim() const =0
Get data vector dimension.
io_ddata_t * ddata_alloc(size_t batch, size_t chunk_size=0)
Allocate data buffer for this I/O.
Definition common.cxx:247
virtual bool isRead() const =0
Check if IO supports read operations.
CDescObject & getDesc()
Get descriptor object for this object.
Definition object.cxx:190
uint8_t getDtype() const
Get data type field.
Definition object.cxx:175
AHRS sensor fusion (x-io Fusion): accel + gyro IOs in, world-frame linear acceleration (gravity remov...
Definition ahrs.hxx:23
int init()
One-time initialize object after bindings are resolved.
Definition ahrs.cxx:192
int doStart()
Start implementation hook.
Definition ahrs.cxx:416
bool hasThread() const
Check if a background thread is active.
Definition ahrs.cxx:482
int deinit()
De-initialize object.
Definition ahrs.cxx:255
int onSetObjConfig(SObjectCfg::ObjectCfgId objcfg, uint32_t *data, size_t len)
Pre-update hook for runtime configuration writes.
Definition ahrs.cxx:377
int configure()
Configure object from descriptor data.
Definition ahrs.cxx:150
int doStop()
Stop implementation hook.
Definition ahrs.cxx:456
Base class for all PROG (processing) objects.
Definition common.hxx:27
uint32_t ObjectCfgId
ConfigID type - single 32-bit value.
Definition objectcfg.hxx:60
static float cfgToF(ObjectCfgData_t x)
Convert ObjectCfgData_t to float.
static uint8_t objectCfgGetId(const ObjectCfgId objcfg)
Extract configuration identifier from ConfigID.
Out-of-tree user-extension hooks for Dawn.
Definition bindable.hxx:13
Single configuration item within object.
ObjectCfgData_t data[]
Configuration data array (flexible, size from cfgid.s.size).
UObjectCfgId cfgid
Configuration ID header (type, class, id, size, rw, dtype).
@ DTYPE_FLOAT
IEEE 754 single-precision floating point (32-bit).
Definition objectid.hxx:112
Heap-allocated dynamic I/O data buffer.
Definition ddata.hxx:21
void * getDataPtr(size_t batch=0)
Get pointer to data only (skips timestamp if present).
Definition ddata.hxx:180
ObjectCfgId v
Raw 32-bit ConfigID value (for storage, comparison).
Definition objectcfg.hxx:82
uint32_t cls
Object class (bits 21-29, max 511).
uint32_t id
Configuration identifier (bits 0-4, max 31).
Definition objectcfg.hxx:94
uint32_t size
Configuration data size in 32-bit words (bits 5-14, max 1023).
struct dawn::SObjectCfg::UObjectCfgId::@10 s
Bit-field structure for named member access.
32-bit encoded object identifier (union with bit field).
Definition objectid.hxx:218
ObjectId v
Raw 32-bit ObjectID value (for comparison, hashing, storage).
Definition objectid.hxx:221