SensorLib 0.5.0
Multi-platform sensor driver library for Arduino, PlatformIO, and ESP-IDF
Loading...
Searching...
No Matches
SensorQMI8658.cpp
Go to the documentation of this file.
1
30#include "../../../SensorBuildOpt.h"
31#if !SENSORLIB_EXCLUDE_QMI8658
32
33#include "SensorQMI8658.hpp"
34#include "../../AccelerometerUtils.hpp"
35#include <string.h>
36#include <math.h>
37#include "SensorQMI8658_Reg.hpp"
38
39using namespace QMI8658Regs;
40
41namespace
42{
43
44uint8_t toQmiLpfMode(SensorQMI8658::LpfMode mode)
45{
46 switch (mode) {
48 return LPF_MODE_0;
50 return LPF_MODE_1;
52 return LPF_MODE_2;
54 return LPF_MODE_3;
55 default:
56 return LPF_MODE_0;
57 }
58}
59
60uint8_t toQmiFifoMode(SensorQMI8658::FifoMode mode)
61{
62 switch (mode) {
64 return FIFO_MODE_BYPASS;
66 return FIFO_MODE_FIFO;
68 return FIFO_MODE_STREAM;
69 default:
70 return FIFO_MODE_BYPASS;
71 }
72}
73
74uint8_t toQmiFifoSamples(SensorQMI8658::FifoSamples samples)
75{
76 switch (samples) {
78 return FIFO_SAMPLES_16;
80 return FIFO_SAMPLES_32;
82 return FIFO_SAMPLES_64;
84 return FIFO_SAMPLES_128;
85 default:
86 return FIFO_SAMPLES_16;
87 }
88}
89
90uint8_t toQmiTapPriority(SensorQMI8658::TapPriority priority)
91{
92 switch (priority) {
94 return TAP_PRIORITY_X_GT_Y_GT_Z;
96 return TAP_PRIORITY_X_GT_Z_GT_Y;
98 return TAP_PRIORITY_Y_GT_X_GT_Z;
100 return TAP_PRIORITY_Y_GT_Z_GT_X;
102 return TAP_PRIORITY_Z_GT_X_GT_Y;
104 return TAP_PRIORITY_Z_GT_Y_GT_X;
105 default:
106 return TAP_PRIORITY_X_GT_Y_GT_Z;
107 }
108}
109
110bool toQmiInterruptBit(SensorQMI8658::IntPin pin, uint8_t &bit_pos, uint8_t &mask)
111{
112 switch (pin) {
113 case SensorQMI8658::IntPin::PIN1:
114 bit_pos = 3;
115 mask = 0x01;
116 return true;
117 case SensorQMI8658::IntPin::PIN2:
118 bit_pos = 4;
119 mask = 0x02;
120 return true;
121 default:
122 return false;
123 }
124}
125
126// ==================== Legacy Mapping ====================
127
128float legacyAccelOdrToFloat(SensorQMI8658::AccelODR odr)
129{
130 switch (odr) {
132 return 7.006f;
134 return 14.0125f;
136 return 28.025f;
138 return 56.05f;
140 return 112.1f;
142 return 224.2f;
144 return 448.4f;
146 return 896.8f;
148 return 1793.6f;
150 return 3587.2f;
152 return 7174.4f;
154 return 3.0f;
156 return 11.0f;
158 return 21.0f;
160 return 128.0f;
161 default:
162 return 112.0f;
163 }
164}
165
166float legacyGyroOdrToFloat(SensorQMI8658::GyroODR odr)
167{
168 switch (odr) {
170 return 7.006f;
172 return 14.0125f;
174 return 28.025f;
176 return 56.05f;
178 return 112.1f;
180 return 224.2f;
182 return 448.4f;
184 return 896.8f;
186 return 1793.6f;
188 return 3587.2f;
190 return 7174.4f;
191 default:
192 return 112.0f;
193 }
194}
195
196} // namespace
197
199 : _accel_range(AccelFullScaleRange::FS_8G)
200 , _gyro_range(GyroFullScaleRange::FS_1000_DPS)
201 , _accel_scale(ACCEL_SCALE_8G)
202 , _gyro_scale(GYRO_SCALE_1024DPS)
203 , _accel_odr(896.8f)
204 , _gyro_odr(896.8f)
205 , _motion_any_configured(false)
206 , _motion_no_configured(false)
207 , _motion_sig_configured(false)
208 , _motion_any_thr{0, 0, 0}
209 , _motion_no_thr{0, 0, 0}
210 , _motion_any_window(4)
211 , _motion_no_window(10)
212 , _motion_sig_wait_window(0)
213 , _motion_sig_confirm_window(8)
214 , _accel_enabled(false)
215 , _gyro_enabled(false)
216 , _sync_mode(false)
217 , _fifo_enabled(false)
218 , _fifo_mode(0)
219 , _fifo_watermark(16)
220 , _int_pin(-1)
221 , _int_pin_mask(0)
222 , _int_enabled(false)
223 , _last_step_count(0)
224 , _tap_event_active(false)
225 , _last_tap_event(TapEvent::INVALID)
226 , _last_tap_event_ms(0)
227 , _last_timestamp(0)
228 , _firmware_version(0)
229 , _fifo_buffer(nullptr)
230 , _fifo_buffer_size(0)
231 , _axis_layout(Layout::LAYOUT_DEFAULT)
232 , _static_cali_sample_count(0)
233 , _static_cali_enabled(false)
234 , _static_cali_complete(false)
235 , _dyn_cal_enabled(false)
236 , _dyn_static_delay(0)
237 , _dyn_static_flag(0)
238 , _dyn_cali_sample_count(0)
239 , _gyro_static_deviation(0)
240 , _accel_static_deviation(0)
241 , _dyn_offset_applied(0)
242{
243 memset(_usid, 0, sizeof(_usid));
244 memset(_accel_cali_sum, 0, sizeof(_accel_cali_sum));
245 memset(_gyro_cali_sum, 0, sizeof(_gyro_cali_sum));
246 memset(_accel_cali_offset, 0, sizeof(_accel_cali_offset));
247 memset(_gyro_cali_offset, 0, sizeof(_gyro_cali_offset));
248 memset(_gyro_speed_buffer, 0, sizeof(_gyro_speed_buffer));
249 memset(_accel_speed_buffer, 0, sizeof(_accel_speed_buffer));
250 memset(_dyn_gyro_offset, 0, sizeof(_dyn_gyro_offset));
251 memset(_dyn_gyro_sum, 0, sizeof(_dyn_gyro_sum));
252 memset(_hw_st_accel_result, 0, sizeof(_hw_st_accel_result));
253 memset(_hw_st_gyro_result, 0, sizeof(_hw_st_gyro_result));
254}
255
257{
258 if (_fifo_buffer) {
259 free(_fifo_buffer);
260 _fifo_buffer = nullptr;
262 }
263}
264
265int SensorQMI8658::writeCommand(uint8_t cmd, uint32_t timeout_ms)
266{
267 if (!hal) return -1;
268
269 if (writeReg(REG_CTRL9, cmd) != 0) {
270 return -1;
271 }
272
273 uint32_t start = hal->millis();
274 while (hal->millis() - start < timeout_ms) {
275 uint8_t status = readReg(REG_STATUS_INT);
276 if (status != 0xFF && (status & MASK_INT_CTRL9_DONE)) {
277 break;
278 }
279 hal->delay(1);
280 }
281
282 if (writeReg(REG_CTRL9, CTRL_CMD_ACK) != 0) {
283 return -1;
284 }
285
286 start = hal->millis();
287 while (hal->millis() - start < timeout_ms) {
288 uint8_t status = readReg(REG_STATUS_INT);
289 if (status != 0xFF && !(status & MASK_INT_CTRL9_DONE)) {
290 return 0;
291 }
292 hal->delay(1);
293 }
294
295 return -1;
296}
297
299{
300 if (!hal) return false;
301
302 writeReg(REG_RESET, QMI8658_RESET_VAL);
303
304 uint32_t start = hal->millis();
305 while (hal->millis() - start < 500) {
306 uint8_t val = readReg(REG_RST_RESULT);
307 if (val != 0xFF && (val & MASK_RST_RESULT)) {
308 setRegBit(REG_CTRL1, 6);
309 return true;
310 }
311 hal->delay(10);
312 }
313
314 SENSORLIB_LOG_E("QMI8658 reset timeout");
315 return false;
316}
317
319{
320 return selfTestAccel() && selfTestGyro();
321}
322
324{
325 return getChipID() == QMI8658_WHO_AM_I_VAL;
326}
327
329{
330 return readReg(REG_WHO_AM_I);
331}
332
337
339{
340 switch (mode) {
342 disableAccel();
343 disableGyro();
344 return setRegBit(REG_CTRL1, SHIFT_SENSOR_DISABLE);
346 return clrRegBit(REG_CTRL1, SHIFT_SENSOR_DISABLE);
347 default:
348 return false;
349 }
350}
351
353{
354 uint8_t buffer[2];
355 if (readRegBuff(REG_TEMPERATURE_L, buffer, 2) == 0) {
356 return static_cast<float>(buffer[1]) + (static_cast<float>(buffer[0]) / 256.0f);
357 }
358 return NAN;
359}
360
362{
363 uint8_t buffer[3];
364 if (readRegBuff(REG_TIMESTAMP_L, buffer, 3) == 0) {
365 uint32_t ts = (uint32_t)(buffer[2] << 16) | (uint32_t)(buffer[1] << 8) | buffer[0];
366 if (ts > _last_timestamp) {
367 _last_timestamp = ts;
368 } else {
369 _last_timestamp = (ts + 0x1000000 - _last_timestamp);
370 }
371 return _last_timestamp;
372 }
373 return _last_timestamp;
374}
375
377{
378 int16_t x, y, z;
379 if (!readAccelRaw(x, y, z)) {
380 return false;
381 }
382
383 out.raw.x = x;
384 out.raw.y = y;
385 out.raw.z = z;
390
391 return true;
392}
393
394bool SensorQMI8658::readAccelRaw(int16_t &x, int16_t &y, int16_t &z)
395{
396 if (!_accel_enabled) return false;
397
398 uint8_t buffer[6];
399 if (readRegBuff(REG_AX_L, buffer, 6) != 0) {
400 return false;
401 }
402
403 x = (int16_t)((buffer[1] << 8) | buffer[0]);
404 y = (int16_t)((buffer[3] << 8) | buffer[2]);
405 z = (int16_t)((buffer[5] << 8) | buffer[4]);
406
407 return true;
408}
409
411{
412 if (setRegBit(REG_CTRL7, 0)) {
413 _accel_enabled = true;
414 return true;
415 }
416 return false;
417}
418
420{
421 if (clrRegBit(REG_CTRL7, 0)) {
422 _accel_enabled = false;
423 return true;
424 }
425 return false;
426}
427
432
434{
435 uint8_t reg_val = getAccelRangeRegValue(range);
436 int ret = updateBits(REG_CTRL2, 0x70, (reg_val << 4));
437 if (ret == 0) {
438 _accel_range = range;
440 return true;
441 }
442 return false;
443}
444
446{
447 uint8_t reg_val = findClosestAccelOdr(data_rate_hz);
448 if (updateBits(REG_CTRL2, 0x0F, reg_val) != 0) {
449 return false;
450 }
451 _accel_odr = data_rate_hz;
452 return true;
453}
454
456{
457 switch (range) {
458 case AccelFullScaleRange::FS_2G: return ACCEL_RANGE_2G;
459 case AccelFullScaleRange::FS_4G: return ACCEL_RANGE_4G;
460 case AccelFullScaleRange::FS_8G: return ACCEL_RANGE_8G;
461 case AccelFullScaleRange::FS_16G: return ACCEL_RANGE_16G;
462 default: return ACCEL_RANGE_8G;
463 }
464}
465
467{
468 switch (range) {
469 case AccelFullScaleRange::FS_2G: return ACCEL_SCALE_2G;
470 case AccelFullScaleRange::FS_4G: return ACCEL_SCALE_4G;
471 case AccelFullScaleRange::FS_8G: return ACCEL_SCALE_8G;
472 case AccelFullScaleRange::FS_16G: return ACCEL_SCALE_16G;
473 default: return ACCEL_SCALE_8G;
474 }
475}
476
478{
479 int16_t x, y, z;
480 if (!readGyroRaw(x, y, z)) {
481 return false;
482 }
483
484 out.raw.x = x;
485 out.raw.y = y;
486 out.raw.z = z;
487
488 int16_t accel_tmp[3] = {0, 0, 0};
489 int16_t gyro_tmp[3] = {x, y, z};
491 uint8_t abuf[6];
492 if (readRegBuff(REG_AX_L, abuf, 6) == 0) {
493 accel_tmp[0] = static_cast<int16_t>((abuf[1] << 8) | abuf[0]);
494 accel_tmp[1] = static_cast<int16_t>((abuf[3] << 8) | abuf[2]);
495 accel_tmp[2] = static_cast<int16_t>((abuf[5] << 8) | abuf[4]);
496 }
497 }
498 axisConvert(accel_tmp, gyro_tmp);
499 x = gyro_tmp[0];
500 y = gyro_tmp[1];
501 z = gyro_tmp[2];
502
504 processStaticCalibration(accel_tmp, gyro_tmp);
505 }
506 if (_dyn_cal_enabled) {
507 processDynamicCalibration(accel_tmp, gyro_tmp);
508 }
509
510 int16_t bias_x = 0;
511 int16_t bias_y = 0;
512 int16_t bias_z = 0;
514 bias_x = _dyn_gyro_offset[0];
515 bias_y = _dyn_gyro_offset[1];
516 bias_z = _dyn_gyro_offset[2];
517 } else if (_static_cali_complete) {
518 bias_x = _gyro_cali_offset[0];
519 bias_y = _gyro_cali_offset[1];
520 bias_z = _gyro_cali_offset[2];
521 }
522 float x_corr = static_cast<float>(x - bias_x);
523 float y_corr = static_cast<float>(y - bias_y);
524 float z_corr = static_cast<float>(z - bias_z);
525 out.dps.x = x_corr * _gyro_scale;
526 out.dps.y = y_corr * _gyro_scale;
527 out.dps.z = z_corr * _gyro_scale;
529
530 return true;
531}
532
533bool SensorQMI8658::readGyroRaw(int16_t &x, int16_t &y, int16_t &z)
534{
535 if (!_gyro_enabled) return false;
536
537 uint8_t buffer[6];
538 if (readRegBuff(REG_GX_L, buffer, 6) != 0) {
539 return false;
540 }
541
542 x = (int16_t)((buffer[1] << 8) | buffer[0]);
543 y = (int16_t)((buffer[3] << 8) | buffer[2]);
544 z = (int16_t)((buffer[5] << 8) | buffer[4]);
545
546 return true;
547}
548
550{
551 if (setRegBit(REG_CTRL7, 1)) {
552 _gyro_enabled = true;
553 return true;
554 }
555 return false;
556}
557
559{
560 if (clrRegBit(REG_CTRL7, 1)) {
561 _gyro_enabled = false;
562 return true;
563 }
564 return false;
565}
566
568{
569 return _gyro_enabled;
570}
571
573{
574 uint8_t reg_val = getGyroRangeRegValue(range);
575 int ret = updateBits(REG_CTRL3, 0x70, (reg_val << 4));
576 if (ret == 0) {
577 _gyro_range = range;
579 return true;
580 }
581 return false;
582}
583
585{
586 uint8_t reg_val = findClosestGyroOdr(data_rate_hz);
587 if (updateBits(REG_CTRL3, 0x0F, reg_val) != 0) {
588 return false;
589 }
590 _gyro_odr = data_rate_hz;
591 return true;
592}
593
595{
596 switch (range) {
597 case GyroFullScaleRange::FS_125_DPS: return GYRO_RANGE_128DPS;
598 case GyroFullScaleRange::FS_250_DPS: return GYRO_RANGE_256DPS;
599 case GyroFullScaleRange::FS_500_DPS: return GYRO_RANGE_512DPS;
600 case GyroFullScaleRange::FS_1000_DPS: return GYRO_RANGE_1024DPS;
601 case GyroFullScaleRange::FS_2000_DPS: return GYRO_RANGE_2048DPS;
602 default: return GYRO_RANGE_1024DPS;
603 }
604}
605
607{
608 switch (range) {
609 case GyroFullScaleRange::FS_125_DPS: return GYRO_SCALE_128DPS;
610 case GyroFullScaleRange::FS_250_DPS: return GYRO_SCALE_256DPS;
611 case GyroFullScaleRange::FS_500_DPS: return GYRO_SCALE_512DPS;
612 case GyroFullScaleRange::FS_1000_DPS: return GYRO_SCALE_1024DPS;
613 case GyroFullScaleRange::FS_2000_DPS: return GYRO_SCALE_2048DPS;
614 default: return GYRO_SCALE_1024DPS;
615 }
616}
617
619{
620 if (setRegBit(REG_CTRL7, 7)) {
621 _sync_mode = true;
622 return true;
623 }
624 return false;
625}
626
628{
629 if (clrRegBit(REG_CTRL7, 7)) {
630 _sync_mode = false;
631 return true;
632 }
633 return false;
634}
635
636bool SensorQMI8658::configAccel(AccelFullScaleRange range, float data_rate_hz, LpfMode lpf)
637{
638 bool was_enabled = _accel_enabled;
639 if (was_enabled) {
640 disableAccel();
641 }
642
643 uint8_t range_val = getAccelRangeRegValue(range);
644 uint8_t odr_val = findClosestAccelOdr(data_rate_hz);
645 uint8_t ctrl2 = static_cast<uint8_t>((range_val << 4) | odr_val);
646 if (writeReg(REG_CTRL2, ctrl2) != 0) {
647 return false;
648 }
649 _accel_range = range;
651 _accel_odr = data_rate_hz;
652
653 if (lpf != LpfMode::OFF) {
654 uint8_t lpf_val = toQmiLpfMode(lpf);
655 updateBits(REG_CTRL5, 0x06, static_cast<uint8_t>(lpf_val << 1));
656 setRegBit(REG_CTRL5, 0);
657 } else {
658 clrRegBit(REG_CTRL5, 0);
659 }
660
661 if (was_enabled) {
662 enableAccel();
663 }
664
665 return true;
666}
667
668bool SensorQMI8658::configGyro(GyroFullScaleRange range, float data_rate_hz, LpfMode lpf)
669{
670 bool was_enabled = _gyro_enabled;
671 if (was_enabled) {
672 disableGyro();
673 }
674
675 uint8_t range_val = getGyroRangeRegValue(range);
676 uint8_t odr_val = findClosestGyroOdr(data_rate_hz);
677 uint8_t ctrl3 = static_cast<uint8_t>((range_val << 4) | odr_val);
678 if (writeReg(REG_CTRL3, ctrl3) != 0) {
679 return false;
680 }
681 _gyro_range = range;
683 _gyro_odr = data_rate_hz;
684
685 if (lpf != LpfMode::OFF) {
686 uint8_t lpf_val = toQmiLpfMode(lpf);
687 updateBits(REG_CTRL5, 0x60, static_cast<uint8_t>(lpf_val << 5));
688 setRegBit(REG_CTRL5, 4);
689 } else {
690 clrRegBit(REG_CTRL5, 4);
691 }
692
693 if (was_enabled) {
694 enableGyro();
695 }
696
697 return true;
698}
699
701{
702 return odr;
703}
704
706{
707 if (hz <= 28.0f) return ACCEL_ODR_28_025HZ;
708 if (hz <= 56.0f) return ACCEL_ODR_56_05HZ;
709 if (hz <= 112.0f) return ACCEL_ODR_112_1HZ;
710 if (hz <= 224.0f) return ACCEL_ODR_224_2HZ;
711 if (hz <= 448.0f) return ACCEL_ODR_448_4HZ;
712 if (hz <= 896.0f) return ACCEL_ODR_896_8HZ;
713 if (hz <= 1792.0f) return ACCEL_ODR_1793_6HZ;
714 if (hz <= 3584.0f) return ACCEL_ODR_3587_2HZ;
715 return ACCEL_ODR_7174_4HZ;
716}
717
719{
720 if (hz <= 28.0f) return GYRO_ODR_28_025HZ;
721 if (hz <= 56.0f) return GYRO_ODR_56_05HZ;
722 if (hz <= 112.0f) return GYRO_ODR_112_1HZ;
723 if (hz <= 224.0f) return GYRO_ODR_224_2HZ;
724 if (hz <= 448.0f) return GYRO_ODR_448_4HZ;
725 if (hz <= 896.0f) return GYRO_ODR_896_8HZ;
726 if (hz <= 1792.0f) return GYRO_ODR_1793_6HZ;
727 if (hz <= 3584.0f) return GYRO_ODR_3587_2HZ;
728 return GYRO_ODR_7174_4HZ;
729}
730
732{
733 if (hz <= 3.0f) return ACCEL_ODR_LP_3HZ;
734 if (hz <= 11.0f) return ACCEL_ODR_LP_11HZ;
735 if (hz <= 21.0f) return ACCEL_ODR_LP_21HZ;
736 return ACCEL_ODR_LP_128HZ;
737}
738
740{
741 uint8_t bit_pos = 0;
742 uint8_t mask = 0;
743 if (!toQmiInterruptBit(pin, bit_pos, mask)) {
744 return false;
745 }
746
747 setRegBit(REG_CTRL1, bit_pos);
748 _int_pin_mask |= mask;
749
750 _int_enabled = true;
751 return true;
752}
753
755{
756 uint8_t bit_pos = 0;
757 uint8_t mask = 0;
758 if (!toQmiInterruptBit(pin, bit_pos, mask)) {
759 return false;
760 }
761
762 clrRegBit(REG_CTRL1, bit_pos);
763 _int_pin_mask &= ~mask;
764
765 if (_int_pin_mask == 0) {
766 _int_enabled = false;
767 }
768 return true;
769}
770
772{
773 if (pin != IntPin::PIN2) {
774 return false;
775 }
776 enableInterrupt(pin);
777 return clrRegBit(REG_CTRL7, 5);
778}
779
781{
782 return readReg(REG_STATUS_INT);
783}
784
786{
787 if (_int_enabled && _int_pin >= 0 && hal) {
788 if (hal->digitalRead(_int_pin) == 0) {
789 return false;
790 }
791 }
792
793 if (_sync_mode) {
794 return getRegBit(REG_STATUS_INT, 0);
795 }
796
797 uint8_t status0 = readReg(REG_STATUS0);
798 uint8_t req = mask & 0x03;
799 if (req == 0) {
800 bool accel_enabled = _accel_enabled;
801 bool gyro_enabled = _gyro_enabled;
802 if (accel_enabled && gyro_enabled) {
803 req = 0x03;
804 } else if (accel_enabled) {
805 req = 0x01;
806 } else if (gyro_enabled) {
807 req = 0x02;
808 } else {
809 req = 0x03;
810 }
811 }
812 return (status0 & req) == req;
813}
814
816{
817 _int_pin = pin;
818 if (hal && pin >= 0) {
819 hal->pinMode(pin, INPUT);
820 }
821}
822
824{
825 setPins(pin);
826}
827
828bool SensorQMI8658::configureFifo(bool enable, uint8_t watermark_samples)
829{
830 bool gyro_en = _gyro_enabled;
831 bool accel_en = _accel_enabled;
832
833 if (gyro_en) disableGyro();
834 if (accel_en) disableAccel();
835
836 if (!enable) {
837 resetFifo();
838 _fifo_enabled = false;
839 if (gyro_en) enableGyro();
840 if (accel_en) enableAccel();
841 return true;
842 }
843
845 SENSORLIB_LOG_E("Failed to reset FIFO");
846 return false;
847 }
848
849 _fifo_watermark = watermark_samples;
850 if (writeReg(REG_FIFO_WTM_TH, watermark_samples) != 0) {
851 return false;
852 }
853
854 _fifo_mode = (FIFO_SAMPLES_16 << SHIFT_FIFO_SAMPLES) | FIFO_MODE_STREAM;
855 if (writeReg(REG_FIFO_CTRL, _fifo_mode) != 0) {
856 return false;
857 }
858
859 _fifo_enabled = true;
860
861 if (gyro_en) enableGyro();
862 if (accel_en) enableAccel();
863
864 return true;
865}
866
867bool SensorQMI8658::configFifo(FifoMode mode, FifoSamples samples, uint8_t watermark_samples)
868{
869 bool gyro_en = _gyro_enabled;
870 bool accel_en = _accel_enabled;
871
872 if (gyro_en) disableGyro();
873 if (accel_en) disableAccel();
874
875 if (mode == FifoMode::BYPASS) {
876 resetFifo();
877 _fifo_enabled = false;
878 if (gyro_en) enableGyro();
879 if (accel_en) enableAccel();
880 return true;
881 }
882
884 return false;
885 }
886
887 uint8_t wm = watermark_samples;
888 switch (samples) {
890 wm = 16;
891 break;
893 wm = 32;
894 break;
896 wm = 64;
897 break;
899 wm = 128;
900 break;
901 }
902
903 _fifo_watermark = wm;
904 if (writeReg(REG_FIFO_WTM_TH, wm) != 0) {
905 return false;
906 }
907
908 uint8_t mode_val = toQmiFifoMode(mode);
909 uint8_t samples_val = toQmiFifoSamples(samples);
910 _fifo_mode = (samples_val << 2) | mode_val;
911 if (writeReg(REG_FIFO_CTRL, _fifo_mode) != 0) {
912 return false;
913 }
914
915 _fifo_enabled = true;
916
917 if (gyro_en) enableGyro();
918 if (accel_en) enableAccel();
919
920 return true;
921}
922
924{
925 return writeCommand(CTRL_CMD_RST_FIFO) == 0;
926}
927
929{
930 return readReg(REG_FIFO_STATUS);
931}
932
933uint16_t SensorQMI8658::readFromFifo(AccelerometerData *accel_data, uint16_t accel_count,
934 GyroscopeData *gyro_data, uint16_t gyro_count)
935{
936 if (!_fifo_enabled || !hal) return 0;
937
938 if (!_accel_enabled && !_gyro_enabled) return 0;
939
940 uint8_t status = getFifoStatus();
941 if (!(status & MASK_FIFO_EMPTY)) {
942 SENSORLIB_LOG_D("FIFO is empty");
943 return 0;
944 }
945
946 uint8_t count_buf[2];
947 if (readRegBuff(REG_FIFO_SMPL_CNT_L, count_buf, 2) != 0) {
948 return 0;
949 }
950 // FIFO sample count is expressed in bytes:
951 // FIFO_Bytes = 2 * ((FIFO_SMPL_CNT_H[1:0] << 8) | FIFO_SMPL_CNT_L)
952 uint16_t fifo_bytes = static_cast<uint16_t>(2 * (((count_buf[1] & 0x03) << 8) | count_buf[0]));
953
954 if (fifo_bytes == 0 || fifo_bytes > 1536 || (fifo_bytes % 6) != 0) {
955 return 0;
956 }
957
958 size_t buffer_size = fifo_bytes;
960 if (_fifo_buffer) free(_fifo_buffer);
961 _fifo_buffer = (uint8_t *)malloc(buffer_size);
962 if (!_fifo_buffer) {
963 SENSORLIB_LOG_E("Failed to allocate FIFO buffer");
964 return 0;
965 }
967 }
968
970 SENSORLIB_LOG_E("Failed to request FIFO");
971 return 0;
972 }
973
974 // Some I2C implementations cannot read >255 bytes in a single request.
975 // Read FIFO payload in chunks to avoid transport-level truncation.
976 static constexpr uint16_t kFifoReadChunk = 240;
977 uint16_t remaining = fifo_bytes;
978 uint16_t offset = 0;
979 while (remaining > 0) {
980 uint16_t chunk = remaining > kFifoReadChunk ? kFifoReadChunk : remaining;
981 if (readRegBuff(REG_FIFO_DATA, _fifo_buffer + offset, chunk) != 0) {
982 SENSORLIB_LOG_E("Failed to read FIFO data");
983 return 0;
984 }
985 offset += chunk;
986 remaining -= chunk;
987 }
988
989 writeReg(REG_FIFO_CTRL, _fifo_mode);
990
991 uint16_t accel_idx = 0;
992 uint16_t gyro_idx = 0;
993
995 // FIFO frame layout in 6DOF mode is [acc_xyz(6 bytes), gyro_xyz(6 bytes)] per sample set.
996 uint16_t sample_sets = static_cast<uint16_t>(fifo_bytes / 12);
997 uint16_t usable = sample_sets;
998 if (accel_data && usable > accel_count) usable = accel_count;
999 if (gyro_data && usable > gyro_count) usable = gyro_count;
1000
1001 for (uint16_t i = 0; i < usable; ++i) {
1002 uint16_t off = static_cast<uint16_t>(i * 12);
1003
1004 int16_t ax = (int16_t)((_fifo_buffer[off + 1] << 8) | _fifo_buffer[off + 0]);
1005 int16_t ay = (int16_t)((_fifo_buffer[off + 3] << 8) | _fifo_buffer[off + 2]);
1006 int16_t az = (int16_t)((_fifo_buffer[off + 5] << 8) | _fifo_buffer[off + 4]);
1007
1008 int16_t gx = (int16_t)((_fifo_buffer[off + 7] << 8) | _fifo_buffer[off + 6]);
1009 int16_t gy = (int16_t)((_fifo_buffer[off + 9] << 8) | _fifo_buffer[off + 8]);
1010 int16_t gz = (int16_t)((_fifo_buffer[off + 11] << 8) | _fifo_buffer[off + 10]);
1011
1012 if (accel_data) {
1013 accel_data[accel_idx].raw.x = ax;
1014 accel_data[accel_idx].raw.y = ay;
1015 accel_data[accel_idx].raw.z = az;
1016 accel_data[accel_idx].mps2.x = AccelerometerUtils::gToMps2(ax * _accel_scale);
1017 accel_data[accel_idx].mps2.y = AccelerometerUtils::gToMps2(ay * _accel_scale);
1018 accel_data[accel_idx].mps2.z = AccelerometerUtils::gToMps2(az * _accel_scale);
1019 accel_idx++;
1020 }
1021
1022 if (gyro_data) {
1023 gyro_data[gyro_idx].raw.x = gx;
1024 gyro_data[gyro_idx].raw.y = gy;
1025 gyro_data[gyro_idx].raw.z = gz;
1026 int16_t bias_x = 0;
1027 int16_t bias_y = 0;
1028 int16_t bias_z = 0;
1030 bias_x = _dyn_gyro_offset[0];
1031 bias_y = _dyn_gyro_offset[1];
1032 bias_z = _dyn_gyro_offset[2];
1033 } else if (_static_cali_complete) {
1034 bias_x = _gyro_cali_offset[0];
1035 bias_y = _gyro_cali_offset[1];
1036 bias_z = _gyro_cali_offset[2];
1037 }
1038 float gx_corr = static_cast<float>(gx - bias_x);
1039 float gy_corr = static_cast<float>(gy - bias_y);
1040 float gz_corr = static_cast<float>(gz - bias_z);
1041 gyro_data[gyro_idx].dps.x = gx_corr * _gyro_scale;
1042 gyro_data[gyro_idx].dps.y = gy_corr * _gyro_scale;
1043 gyro_data[gyro_idx].dps.z = gz_corr * _gyro_scale;
1044 gyro_idx++;
1045 }
1046 }
1047 return usable;
1048 }
1049
1050 uint16_t samples_read = static_cast<uint16_t>(fifo_bytes / 6);
1051 for (uint16_t i = 0; i < samples_read; ++i) {
1052 int16_t x = (int16_t)((_fifo_buffer[i * 6 + 1] << 8) | _fifo_buffer[i * 6 + 0]);
1053 int16_t y = (int16_t)((_fifo_buffer[i * 6 + 3] << 8) | _fifo_buffer[i * 6 + 2]);
1054 int16_t z = (int16_t)((_fifo_buffer[i * 6 + 5] << 8) | _fifo_buffer[i * 6 + 4]);
1055
1056 if (_accel_enabled && accel_idx < accel_count && accel_data) {
1057 accel_data[accel_idx].raw.x = x;
1058 accel_data[accel_idx].raw.y = y;
1059 accel_data[accel_idx].raw.z = z;
1060 accel_data[accel_idx].mps2.x = AccelerometerUtils::gToMps2(x * _accel_scale);
1061 accel_data[accel_idx].mps2.y = AccelerometerUtils::gToMps2(y * _accel_scale);
1062 accel_data[accel_idx].mps2.z = AccelerometerUtils::gToMps2(z * _accel_scale);
1063 accel_idx++;
1064 } else if (_gyro_enabled && gyro_idx < gyro_count && gyro_data) {
1065 gyro_data[gyro_idx].raw.x = x;
1066 gyro_data[gyro_idx].raw.y = y;
1067 gyro_data[gyro_idx].raw.z = z;
1068 int16_t bias_x = 0;
1069 int16_t bias_y = 0;
1070 int16_t bias_z = 0;
1072 bias_x = _dyn_gyro_offset[0];
1073 bias_y = _dyn_gyro_offset[1];
1074 bias_z = _dyn_gyro_offset[2];
1075 } else if (_static_cali_complete) {
1076 bias_x = _gyro_cali_offset[0];
1077 bias_y = _gyro_cali_offset[1];
1078 bias_z = _gyro_cali_offset[2];
1079 }
1080 float x_corr = static_cast<float>(x - bias_x);
1081 float y_corr = static_cast<float>(y - bias_y);
1082 float z_corr = static_cast<float>(z - bias_z);
1083 gyro_data[gyro_idx].dps.x = x_corr * _gyro_scale;
1084 gyro_data[gyro_idx].dps.y = y_corr * _gyro_scale;
1085 gyro_data[gyro_idx].dps.z = z_corr * _gyro_scale;
1086 gyro_idx++;
1087 }
1088 }
1089
1090 return samples_read;
1091}
1092
1094{
1096 if (writeReg(REG_CAL1_L, 0x01) != 0) {
1097 return false;
1098 }
1100}
1101
1103{
1105 if (writeReg(REG_CAL1_L, 0x00) != 0) {
1106 return false;
1107 }
1109}
1110
1111bool SensorQMI8658::calibrate(uint16_t *gyro_x_gain, uint16_t *gyro_y_gain, uint16_t *gyro_z_gain)
1112{
1113 if (!hal) return false;
1114
1116 disableAccel();
1117 disableGyro();
1118 }
1119
1121 SENSORLIB_LOG_E("Calibration command failed");
1122 return false;
1123 }
1124
1125 hal->delay(1600);
1126
1127 uint8_t status = readReg(REG_COD_STATUS);
1128 if (status & MASK_COD_FAIL) {
1129 SENSORLIB_LOG_E("Calibration failed");
1130 return false;
1131 }
1132 if (status & MASK_COD_GYRO_ENABLED_ERR) {
1133 SENSORLIB_LOG_E("Gyro enabled during calibration");
1134 return false;
1135 }
1136 if (status & MASK_COD_GYRO_STARTUP_ERR) {
1137 SENSORLIB_LOG_E("Gyro startup error during calibration");
1138 return false;
1139 }
1140 if (status & MASK_COD_ACCEL_ERR) {
1141 SENSORLIB_LOG_E("Accelerometer error during calibration");
1142 return false;
1143 }
1144
1145 uint8_t buffer[6];
1146 if (readRegBuff(REG_DVX_L, buffer, 6) == 0) {
1147 if (gyro_x_gain) *gyro_x_gain = (uint16_t)buffer[0] | ((uint16_t)buffer[1] << 8);
1148 if (gyro_y_gain) *gyro_y_gain = (uint16_t)buffer[2] | ((uint16_t)buffer[3] << 8);
1149 if (gyro_z_gain) *gyro_z_gain = (uint16_t)buffer[4] | ((uint16_t)buffer[5] << 8);
1150 }
1151
1152 return true;
1153}
1154
1155bool SensorQMI8658::writeCalibration(uint16_t gyro_x_gain, uint16_t gyro_y_gain, uint16_t gyro_z_gain)
1156{
1158 disableAccel();
1159 disableGyro();
1160 }
1161
1162 uint8_t buffer[6] = {
1163 (uint8_t)(gyro_x_gain & 0xFF),
1164 (uint8_t)((gyro_x_gain >> 8) & 0xFF),
1165 (uint8_t)(gyro_y_gain & 0xFF),
1166 (uint8_t)((gyro_y_gain >> 8) & 0xFF),
1167 (uint8_t)(gyro_z_gain & 0xFF),
1168 (uint8_t)((gyro_z_gain >> 8) & 0xFF),
1169 };
1170
1171 if (writeRegBuff(REG_CAL1_L, buffer, 6) != 0) {
1172 return false;
1173 }
1174
1176}
1177
1178void SensorQMI8658::setAccelOffset(int16_t x, int16_t y, int16_t z)
1179{
1180 uint8_t data[2];
1181 data[0] = (uint8_t)(x & 0xFF);
1182 data[1] = (uint8_t)((x >> 8) & 0xFF);
1183 writeRegBuff(REG_CAL1_L, data, 2);
1184 data[0] = (uint8_t)(y & 0xFF);
1185 data[1] = (uint8_t)((y >> 8) & 0xFF);
1186 writeRegBuff(REG_CAL2_L, data, 2);
1187 data[0] = (uint8_t)(z & 0xFF);
1188 data[1] = (uint8_t)((z >> 8) & 0xFF);
1189 writeRegBuff(REG_CAL3_L, data, 2);
1191}
1192
1193void SensorQMI8658::setGyroOffset(int16_t x, int16_t y, int16_t z)
1194{
1195 uint8_t data[2];
1196 data[0] = (uint8_t)(x & 0xFF);
1197 data[1] = (uint8_t)((x >> 8) & 0xFF);
1198 writeRegBuff(REG_CAL1_L, data, 2);
1199 data[0] = (uint8_t)(y & 0xFF);
1200 data[1] = (uint8_t)((y >> 8) & 0xFF);
1201 writeRegBuff(REG_CAL2_L, data, 2);
1202 data[0] = (uint8_t)(z & 0xFF);
1203 data[1] = (uint8_t)((z >> 8) & 0xFF);
1204 writeRegBuff(REG_CAL3_L, data, 2);
1206}
1207
1209{
1210 if (!hal) return false;
1211
1212 disableAccel();
1213 disableGyro();
1214
1215 if (writeReg(REG_CTRL7, 0x00) != 0) {
1216 return false;
1217 }
1218
1219 if (updateBits(REG_CTRL2, 0xF0, (ACCEL_ODR_7174_4HZ | 0x80)) != 0) {
1220 return false;
1221 }
1222
1223 uint8_t retry = 50;
1224 while (retry-- > 0) {
1225 uint8_t status = readReg(REG_STATUS_INT);
1226 if (status != 0xFF && (status & MASK_INT_AVAIL)) {
1227 break;
1228 }
1229 hal->delay(20);
1230 }
1231
1232 if (retry == 0) {
1233 SENSORLIB_LOG_E("Accelerometer self-test timeout");
1234 return false;
1235 }
1236
1237 clrRegBit(REG_CTRL2, 7);
1238
1239 uint8_t buffer[6];
1240 if (readRegBuff(REG_DVX_L, buffer, 6) != 0) {
1241 return false;
1242 }
1243
1244 int16_t dVX = (int16_t)((buffer[1] << 8) | buffer[0]);
1245 int16_t dVY = (int16_t)((buffer[3] << 8) | buffer[2]);
1246 int16_t dVZ = (int16_t)((buffer[5] << 8) | buffer[4]);
1247
1248 float dVX_mg = dVX * 0.5f;
1249 float dVY_mg = dVY * 0.5f;
1250 float dVZ_mg = dVZ * 0.5f;
1251
1252 if (abs(dVX_mg) > 200.0f && abs(dVY_mg) > 200.0f && abs(dVZ_mg) > 200.0f) {
1253 return true;
1254 }
1255
1256 SENSORLIB_LOG_E("Accelerometer self-test failed: dVX=%.1f, dVY=%.1f, dVZ=%.1f", dVX_mg, dVY_mg, dVZ_mg);
1257 return false;
1258}
1259
1261{
1262 if (!hal) return false;
1263
1264 disableAccel();
1265 disableGyro();
1266
1267 if (writeReg(REG_CTRL7, 0x00) != 0) {
1268 return false;
1269 }
1270
1271 setRegBit(REG_CTRL3, 7);
1272
1273 uint8_t retry = 50;
1274 while (retry-- > 0) {
1275 uint8_t status = readReg(REG_STATUS_INT);
1276 if (status != 0xFF && (status & MASK_INT_AVAIL)) {
1277 break;
1278 }
1279 hal->delay(20);
1280 }
1281
1282 if (retry == 0) {
1283 SENSORLIB_LOG_E("Gyroscope self-test timeout");
1284 return false;
1285 }
1286
1287 clrRegBit(REG_CTRL3, 7);
1288
1289 uint8_t buffer[6];
1290 if (readRegBuff(REG_DVX_L, buffer, 6) != 0) {
1291 return false;
1292 }
1293
1294 float dVX = (float)(((int16_t)buffer[0] << 12) | (buffer[1] >> 4));
1295 float dVY = (float)(((int16_t)buffer[2] << 12) | (buffer[3] >> 4));
1296 float dVZ = (float)(((int16_t)buffer[4] << 12) | (buffer[5] >> 4));
1297
1298 dVX *= (1.0f / 16.0f);
1299 dVY *= (1.0f / 16.0f);
1300 dVZ *= (1.0f / 16.0f);
1301
1302 if (abs(dVX) > 300.0f && abs(dVY) > 300.0f && abs(dVZ) > 300.0f) {
1303 return true;
1304 }
1305
1306 SENSORLIB_LOG_E("Gyroscope self-test failed: dVX=%.1f, dVY=%.1f, dVZ=%.1f", dVX, dVY, dVZ);
1307 return false;
1308}
1309
1310bool SensorQMI8658::configMotionDetect(MotionType type, float threshold_x, float threshold_y,
1311 float threshold_z, uint8_t duration)
1312{
1313 uint8_t thr_x = mgToBytes(threshold_x);
1314 uint8_t thr_y = mgToBytes(threshold_y);
1315 uint8_t thr_z = mgToBytes(threshold_z);
1316
1317 switch (type) {
1319 _motion_any_thr[0] = thr_x;
1320 _motion_any_thr[1] = thr_y;
1321 _motion_any_thr[2] = thr_z;
1322 _motion_any_window = duration;
1324 break;
1326 _motion_no_thr[0] = thr_x;
1327 _motion_no_thr[1] = thr_y;
1328 _motion_no_thr[2] = thr_z;
1329 _motion_no_window = duration;
1330 _motion_no_configured = true;
1331 break;
1333 _motion_sig_confirm_window = duration;
1335 break;
1336 default:
1337 return false;
1338 }
1339
1342 _motion_any_thr[0] = mgToBytes(100.0f);
1343 _motion_any_thr[1] = mgToBytes(100.0f);
1344 _motion_any_thr[2] = mgToBytes(100.0f);
1347 }
1348 if (!_motion_no_configured) {
1349 _motion_no_thr[0] = mgToBytes(100.0f);
1350 _motion_no_thr[1] = mgToBytes(100.0f);
1351 _motion_no_thr[2] = mgToBytes(100.0f);
1352 _motion_no_window = 10;
1353 _motion_no_configured = true;
1354 }
1355 }
1356
1357 return applyMotionConfig();
1358}
1359
1361{
1363
1364 bool gyro_en = _gyro_enabled;
1365 bool accel_en = _accel_enabled;
1366 if (gyro_en) disableGyro();
1367 if (accel_en) disableAccel();
1368
1370 _motion_any_thr[0] = mgToBytes(100.0f);
1371 _motion_any_thr[1] = mgToBytes(100.0f);
1372 _motion_any_thr[2] = mgToBytes(100.0f);
1374 }
1375 if (!_motion_no_configured) {
1376 _motion_no_thr[0] = mgToBytes(100.0f);
1377 _motion_no_thr[1] = mgToBytes(100.0f);
1378 _motion_no_thr[2] = mgToBytes(100.0f);
1379 _motion_no_window = 10;
1380 }
1381
1382 bool ok = true;
1383
1384 ok = ok && (writeReg(REG_CAL1_L, _motion_any_thr[0]) == 0);
1385 ok = ok && (writeReg(REG_CAL1_H, _motion_any_thr[1]) == 0);
1386 ok = ok && (writeReg(REG_CAL2_L, _motion_any_thr[2]) == 0);
1387 ok = ok && (writeReg(REG_CAL2_H, _motion_no_thr[0]) == 0);
1388 ok = ok && (writeReg(REG_CAL3_L, _motion_no_thr[1]) == 0);
1389 ok = ok && (writeReg(REG_CAL3_H, _motion_no_thr[2]) == 0);
1390
1391 uint8_t mode_ctrl = 0;
1393 mode_ctrl |= MASK_ANY_MOTION_X_EN | MASK_ANY_MOTION_Y_EN | MASK_ANY_MOTION_Z_EN;
1394 }
1396 mode_ctrl |= MASK_NO_MOTION_X_EN | MASK_NO_MOTION_Y_EN | MASK_NO_MOTION_Z_EN;
1397 // Use stricter No-Motion axis logic to avoid false positives while moving.
1398 // Requiring all enabled axes to satisfy No-Motion keeps bit6 from staying high.
1399 mode_ctrl |= MASK_NO_MOTION_LOGIC;
1400 }
1401
1402 ok = ok && (writeReg(REG_CAL4_L, mode_ctrl) == 0);
1403 ok = ok && (writeReg(REG_CAL4_H, 0x01) == 0);
1404 ok = ok && (writeCommand(CTRL_CMD_CONFIGURE_MOTION) == 0);
1405
1406 ok = ok && (writeReg(REG_CAL1_L, _motion_any_window) == 0);
1407 ok = ok && (writeReg(REG_CAL1_H, _motion_no_window) == 0);
1408 ok = ok && (writeReg(REG_CAL2_L, static_cast<uint8_t>(_motion_sig_wait_window & 0xFF)) == 0);
1409 ok = ok && (writeReg(REG_CAL2_H, static_cast<uint8_t>((_motion_sig_wait_window >> 8) & 0xFF)) == 0);
1410 ok = ok && (writeReg(REG_CAL3_L, static_cast<uint8_t>(_motion_sig_confirm_window & 0xFF)) == 0);
1411 ok = ok && (writeReg(REG_CAL3_H, static_cast<uint8_t>((_motion_sig_confirm_window >> 8) & 0xFF)) == 0);
1412 ok = ok && (writeReg(REG_CAL4_H, 0x02) == 0);
1413 ok = ok && (writeCommand(CTRL_CMD_CONFIGURE_MOTION) == 0);
1414
1415 if (gyro_en) enableGyro();
1416 if (accel_en) enableAccel();
1417
1418 return ok;
1419}
1420
1422{
1423 switch (type) {
1425 return configMotionDetect(type, 100.0f, 100.0f, 100.0f, 4);
1427 return configMotionDetect(type, 100.0f, 100.0f, 100.0f, 10);
1429 return configMotionDetect(type, 200.0f, 200.0f, 200.0f, 8);
1430 default:
1431 break;
1432 }
1433 return false;
1434}
1435
1437{
1438 if (!_accel_enabled) return false;
1439
1440 switch (pin) {
1441 case IntPin::PIN1:
1442 case IntPin::PIN2:
1443 enableInterrupt(pin);
1444 setRegBit(REG_CTRL8, 6);
1445 break;
1446 default:
1447 break;
1448 }
1449
1450 const bool enable_any = _motion_any_configured || _motion_sig_configured;
1451 const bool enable_no = _motion_no_configured || _motion_sig_configured;
1452 const bool enable_sig = _motion_sig_configured;
1453
1454 if (enable_any) {
1455 setRegBit(REG_CTRL8, SHIFT_ANY_MOTION_ENABLE);
1456 } else {
1457 clrRegBit(REG_CTRL8, SHIFT_ANY_MOTION_ENABLE);
1458 }
1459
1460 if (enable_no) {
1461 setRegBit(REG_CTRL8, SHIFT_NO_MOTION_ENABLE);
1462 } else {
1463 clrRegBit(REG_CTRL8, SHIFT_NO_MOTION_ENABLE);
1464 }
1465
1466 if (enable_sig) {
1467 setRegBit(REG_CTRL8, SHIFT_SIGNIFICANT_MOTION_EN);
1468 } else {
1469 clrRegBit(REG_CTRL8, SHIFT_SIGNIFICANT_MOTION_EN);
1470 }
1471
1472 return true;
1473}
1474
1476{
1477 clrRegBit(REG_CTRL8, SHIFT_ANY_MOTION_ENABLE);
1478 clrRegBit(REG_CTRL8, SHIFT_NO_MOTION_ENABLE);
1479 clrRegBit(REG_CTRL8, SHIFT_SIGNIFICANT_MOTION_EN);
1480
1481 return true;
1482}
1483
1484bool SensorQMI8658::configWakeOnMotion(uint8_t threshold, float odr_hz, IntPin pin)
1485{
1486 return configWakeOnMotionAdvanced(threshold, odr_hz, pin, 1, 0x20, AccelFullScaleRange::FS_8G);
1487}
1488
1489bool SensorQMI8658::configWakeOnMotionAdvanced(uint8_t threshold, float odr_hz, IntPin pin,
1490 uint8_t default_pin_value, uint8_t blanking_time,
1491 AccelFullScaleRange acc_range)
1492{
1493 if (!hal) return false;
1494
1495 reset();
1496
1497 clrRegBit(REG_CTRL7, 0);
1498
1499 uint8_t range_val = getAccelRangeRegValue(acc_range);
1500 if (updateBits(REG_CTRL2, 0x70, static_cast<uint8_t>(range_val << 4)) != 0) {
1501 return false;
1502 }
1503
1504 uint8_t odr_val = findClosestAccelLpOdr(odr_hz);
1505 if (updateBits(REG_CTRL2, 0x0F, odr_val) != 0) {
1506 return false;
1507 }
1508
1509 if (writeReg(REG_CAL1_L, threshold) != 0) {
1510 return false;
1511 }
1512
1513 uint8_t cal1_h = 0;
1514 if (pin == IntPin::PIN1) {
1515 cal1_h = default_pin_value ? 0x02 : 0x00;
1516 } else if (pin == IntPin::PIN2) {
1517 cal1_h = default_pin_value ? 0x03 : 0x01;
1518 } else {
1519 return false;
1520 }
1521 cal1_h = static_cast<uint8_t>((cal1_h << 6) | (blanking_time & 0x3F));
1522 if (writeReg(REG_CAL1_H, cal1_h) != 0) {
1523 return false;
1524 }
1525
1527 return false;
1528 }
1529
1530 enableAccel();
1531 enableInterrupt(pin);
1532
1533 return true;
1534}
1535
1537{
1539 switch (range) {
1540 case AccelRange::FS_2G:
1541 common_range = AccelFullScaleRange::FS_2G;
1542 break;
1543 case AccelRange::FS_4G:
1544 common_range = AccelFullScaleRange::FS_4G;
1545 break;
1546 case AccelRange::FS_8G:
1547 common_range = AccelFullScaleRange::FS_8G;
1548 break;
1549 case AccelRange::FS_16G:
1550 common_range = AccelFullScaleRange::FS_16G;
1551 break;
1552 }
1553 return configAccel(common_range, legacyAccelOdrToFloat(odr), lpf);
1554}
1555
1556bool SensorQMI8658::configAccel(AccelRange range, float data_rate_hz, LpfMode lpf)
1557{
1559 switch (range) {
1560 case AccelRange::FS_2G:
1561 common_range = AccelFullScaleRange::FS_2G;
1562 break;
1563 case AccelRange::FS_4G:
1564 common_range = AccelFullScaleRange::FS_4G;
1565 break;
1566 case AccelRange::FS_8G:
1567 common_range = AccelFullScaleRange::FS_8G;
1568 break;
1569 case AccelRange::FS_16G:
1570 common_range = AccelFullScaleRange::FS_16G;
1571 break;
1572 }
1573 return configAccel(common_range, data_rate_hz, lpf);
1574}
1575
1577{
1579 switch (range) {
1581 common_range = GyroFullScaleRange::FS_125_DPS;
1582 break;
1584 common_range = GyroFullScaleRange::FS_250_DPS;
1585 break;
1587 common_range = GyroFullScaleRange::FS_500_DPS;
1588 break;
1590 common_range = GyroFullScaleRange::FS_1000_DPS;
1591 break;
1593 common_range = GyroFullScaleRange::FS_2000_DPS;
1594 break;
1595 default:
1596 common_range = GyroFullScaleRange::FS_1000_DPS;
1597 break;
1598 }
1599 return configGyro(common_range, legacyGyroOdrToFloat(odr), lpf);
1600}
1601
1602bool SensorQMI8658::configGyro(GyroRange range, float data_rate_hz, LpfMode lpf)
1603{
1605 switch (range) {
1607 common_range = GyroFullScaleRange::FS_125_DPS;
1608 break;
1610 common_range = GyroFullScaleRange::FS_250_DPS;
1611 break;
1613 common_range = GyroFullScaleRange::FS_500_DPS;
1614 break;
1616 common_range = GyroFullScaleRange::FS_1000_DPS;
1617 break;
1619 common_range = GyroFullScaleRange::FS_2000_DPS;
1620 break;
1621 default:
1622 common_range = GyroFullScaleRange::FS_1000_DPS;
1623 break;
1624 }
1625 return configGyro(common_range, data_rate_hz, lpf);
1626}
1627
1628bool SensorQMI8658::configWakeOnMotion(uint8_t threshold, AccelODR odr, IntPin pin)
1629{
1630 return configWakeOnMotion(threshold, legacyAccelOdrToFloat(odr), pin);
1631}
1632
1633bool SensorQMI8658::configWakeOnMotion(uint8_t threshold, AccelODR odr, IntPin pin,
1634 uint8_t default_pin_value, uint8_t blanking_time,
1635 AccelRange acc_range)
1636{
1638 switch (acc_range) {
1639 case AccelRange::FS_2G:
1640 common_range = AccelFullScaleRange::FS_2G;
1641 break;
1642 case AccelRange::FS_4G:
1643 common_range = AccelFullScaleRange::FS_4G;
1644 break;
1645 case AccelRange::FS_8G:
1646 common_range = AccelFullScaleRange::FS_8G;
1647 break;
1648 case AccelRange::FS_16G:
1649 common_range = AccelFullScaleRange::FS_16G;
1650 break;
1651 }
1652 return configWakeOnMotionAdvanced(threshold, legacyAccelOdrToFloat(odr), pin,
1653 default_pin_value, blanking_time, common_range);
1654}
1655
1657{
1658 float g = mg / 1000.0f;
1659 int units = static_cast<int>(roundf(g / 0.03125f));
1660 if (units < 0) {
1661 units = 0;
1662 } else if (units > 0xFF) {
1663 units = 0xFF;
1664 }
1665 return static_cast<uint8_t>(units);
1666}
1667
1668bool SensorQMI8658::configTap(TapPriority priority, uint8_t peak_window, uint16_t tap_window,
1669 uint16_t double_tap_window, float peak_mag_threshold, float quiet_threshold)
1670{
1672
1673 bool gyro_en = _gyro_enabled;
1674 bool accel_en = _accel_enabled;
1675 if (gyro_en) disableGyro();
1676 if (accel_en) disableAccel();
1677
1678 writeReg(REG_CAL1_L, peak_window);
1679 writeReg(REG_CAL1_H, toQmiTapPriority(priority));
1680 writeReg(REG_CAL2_L, (uint8_t)(tap_window & 0xFF));
1681 writeReg(REG_CAL2_H, (uint8_t)((tap_window >> 8) & 0xFF));
1682 writeReg(REG_CAL3_L, (uint8_t)(double_tap_window & 0xFF));
1683 writeReg(REG_CAL3_H, (uint8_t)((double_tap_window >> 8) & 0xFF));
1684 writeReg(REG_CAL4_H, 0x01);
1685
1687
1688 const float alpha = 0.0625f;
1689 const float gamma = 0.25f;
1690 uint8_t alpha_hex = static_cast<uint8_t>(alpha * 128.0f);
1691 uint8_t gamma_hex = static_cast<uint8_t>(gamma * 128.0f);
1692 writeReg(REG_CAL1_L, alpha_hex);
1693 writeReg(REG_CAL1_H, gamma_hex);
1694
1695 const float g = QMI8658_CONSTANT_ONE_G;
1696 float resolution = 0.001f * g * g;
1697
1698 uint16_t peak_val = (uint16_t)((peak_mag_threshold * g * g) / resolution);
1699 uint16_t quiet_val = (uint16_t)((quiet_threshold * g * g) / resolution);
1700
1701 writeReg(REG_CAL2_L, (uint8_t)(peak_val & 0xFF));
1702 writeReg(REG_CAL2_H, (uint8_t)((peak_val >> 8) & 0xFF));
1703 writeReg(REG_CAL3_L, (uint8_t)(quiet_val & 0xFF));
1704 writeReg(REG_CAL3_H, (uint8_t)((quiet_val >> 8) & 0xFF));
1705 writeReg(REG_CAL4_H, 0x02);
1706
1708
1709 if (gyro_en) enableGyro();
1710 if (accel_en) enableAccel();
1711
1712 return true;
1713}
1714
1716{
1717 return configTap(priority,
1718 30, // peak_window: 30 samples
1719 100, // tap_window: 100 samples
1720 500, // double_tap_window: 500 samples
1721 1.5f, // peak_mag_threshold: 1.5 g^2
1722 0.5f); // quiet_threshold: 0.5 g^2
1723}
1724
1726{
1727 if (!_accel_enabled) return false;
1728 switch (pin) {
1729 case IntPin::PIN1:
1730 case IntPin::PIN2:
1731 enableInterrupt(pin);
1732 setRegBit(REG_CTRL8, 6);
1733 break;
1734 default:
1735 break;
1736 }
1737 return setRegBit(REG_CTRL8, 0);
1738}
1739
1741{
1742 return clrRegBit(REG_CTRL8, 0);
1743}
1744
1746{
1747 uint8_t status = readReg(REG_TAP_STATUS);
1748 uint8_t tap_type = status & MASK_TAP_TYPE;
1749
1750 switch (tap_type) {
1751 case TAP_TYPE_SINGLE:
1752 return TapEvent::SINGLE;
1753 case TAP_TYPE_DOUBLE:
1754 return TapEvent::DOUBLE;
1755 default:
1756 return TapEvent::INVALID;
1757 }
1758}
1759
1760bool SensorQMI8658::configPedometer(uint16_t sample_count, uint16_t peak_to_peak, uint16_t peak_threshold,
1761 uint16_t time_up, uint8_t time_low, uint8_t entry_count,
1762 uint8_t fix_precision, uint8_t sig_count)
1763{
1765
1766 bool gyro_en = _gyro_enabled;
1767 bool accel_en = _accel_enabled;
1768 if (gyro_en) disableGyro();
1769 if (accel_en) disableAccel();
1770
1771 uint8_t data[2];
1772 data[0] = (uint8_t)(sample_count & 0xFF);
1773 data[1] = (uint8_t)((sample_count >> 8) & 0xFF);
1774 if (writeRegBuff(REG_CAL1_L, data, 2) != 0) {
1775 return false;
1776 }
1777 data[0] = (uint8_t)(peak_to_peak & 0xFF);
1778 data[1] = (uint8_t)((peak_to_peak >> 8) & 0xFF);
1779 if (writeRegBuff(REG_CAL2_L, data, 2) != 0) {
1780 return false;
1781 }
1782 data[0] = (uint8_t)(peak_threshold & 0xFF);
1783 data[1] = (uint8_t)((peak_threshold >> 8) & 0xFF);
1784 if (writeRegBuff(REG_CAL3_L, data, 2) != 0) {
1785 return false;
1786 }
1787 if (writeReg(REG_CAL4_H, 0x01) != 0 || writeReg(REG_CAL4_L, 0x02) != 0) {
1788 return false;
1789 }
1790
1792 return false;
1793 }
1794
1795 data[0] = (uint8_t)(time_up & 0xFF);
1796 data[1] = (uint8_t)((time_up >> 8) & 0xFF);
1797 if (writeRegBuff(REG_CAL1_L, data, 2) != 0) {
1798 return false;
1799 }
1800 if (writeReg(REG_CAL2_L, time_low) != 0 || writeReg(REG_CAL2_H, entry_count) != 0 ||
1801 writeReg(REG_CAL3_L, fix_precision) != 0 || writeReg(REG_CAL3_H, sig_count) != 0 ||
1802 writeReg(REG_CAL4_H, 0x02) != 0 || writeReg(REG_CAL4_L, 0x02) != 0) {
1803 return false;
1804 }
1805
1807 return false;
1808 }
1809
1810 if (gyro_en) enableGyro();
1811 if (accel_en) enableAccel();
1812
1813 return true;
1814}
1815
1817{
1818 float rate = 1000.0f / odr;
1819 uint16_t sample_count = 50;
1820 uint16_t peak_to_peak = 100;
1821 uint16_t peak_threshold = 116;
1822 uint16_t time_up = static_cast<uint16_t>(2000.0f / rate);
1823 uint8_t time_low = static_cast<uint8_t>(300.0f / rate);
1824 uint8_t entry_count = 8;
1825 uint8_t sig_count = 1;
1826
1827 return configPedometer(sample_count, peak_to_peak, peak_threshold,
1828 time_up, time_low, entry_count, 0, sig_count);
1829}
1830
1832{
1833 if (!_accel_enabled) return false;
1834
1836
1838
1839 switch (pin) {
1840 case IntPin::PIN1:
1841 case IntPin::PIN2:
1842 enableInterrupt(pin);
1843 setRegBit(REG_CTRL8, 6);
1844 break;
1845 default:
1846 break;
1847 }
1848
1849 // Datasheet: toggling Pedo_EN from 0->1 resets step counter and restarts engine.
1850 clrRegBit(REG_CTRL8, 4);
1851 bool ok = setRegBit(REG_CTRL8, 4);
1852 if (ok) {
1853 _last_step_count = 0;
1854 enableAccel();
1855 if (_gyro_enabled) {
1856 enableGyro();
1857 }
1858 }
1859 return ok;
1860}
1861
1863{
1864 return clrRegBit(REG_CTRL8, 4);
1865}
1866
1868{
1869 uint8_t buffer[3];
1870 if (readRegBuff(REG_STEP_CNT_L, buffer, 3) == 0) {
1871 return (uint32_t)(buffer[2] << 16) | (uint32_t)(buffer[1] << 8) | buffer[0];
1872 }
1873 return 0;
1874}
1875
1880
1885
1887{
1888 callbacks.onNoMotion = callback;
1889}
1890
1895
1900
1902{
1903 callbacks.onTap = callback;
1904}
1905
1910
1915
1920
1925
1927{
1928 callbacks = cbs;
1929}
1930
1935
1940
1942{
1943 uint16_t result = 0;
1944
1945 uint8_t status[3];
1946 if (readRegBuff(REG_STATUS_INT, status, 3) != 0) {
1947 return 0;
1948 }
1949
1950 if (status[0] & MASK_INT_CTRL9_DONE) {
1951 result |= 0x80;
1952 }
1953
1954 if (status[0] & MASK_INT_AVAIL) {
1955 result |= 0x01;
1956 }
1957
1958 if ((status[0] & 0x03) == 0x03) {
1959 result |= 0x100;
1962 }
1963 }
1964
1965 if (!_sync_mode) {
1966 if (status[1] & MASK_GYRO_DATA_RDY) {
1967 result |= 0x02;
1970 }
1971 }
1972
1973 if (status[1] & MASK_ACCEL_DATA_RDY) {
1974 result |= 0x04;
1977 }
1978 }
1979 }
1980
1981 if (status[2] & STATUS_SIGNIFICANT_MOTION) {
1982 result |= STATUS_SIGNIFICANT_MOTION;
1985 }
1986 }
1987
1988 if (status[2] & STATUS_NO_MOTION_EVENT) {
1989 result |= STATUS_NO_MOTION_EVENT;
1990 if (callbacks.onNoMotion) {
1992 }
1993 }
1994
1995 if (status[2] & STATUS_ANY_MOTION_EVENT) {
1996 result |= STATUS_ANY_MOTION_EVENT;
1997 if (callbacks.onAnyMotion) {
1999 }
2000 }
2001
2002 bool pedometer_event = (status[2] & STATUS_PEDOMETER_EVENT) != 0;
2003 uint32_t step_count = getStepCount();
2004 if (step_count != _last_step_count) {
2005 pedometer_event = true;
2006 _last_step_count = step_count;
2007 }
2008 if (pedometer_event) {
2009 result |= STATUS_PEDOMETER_EVENT;
2010 if (callbacks.onPedometer) {
2012 }
2013 }
2014
2015 if (status[2] & STATUS_WOM_EVENT) {
2016 result |= STATUS_WOM_EVENT;
2019 }
2020 }
2021
2022 bool tap_now = (status[2] & STATUS_TAP_EVENT) != 0;
2023 if (tap_now) {
2024 TapEvent tap_event = getTapStatus();
2025 if (tap_event != TapEvent::INVALID) {
2026 uint32_t now_ms = 0;
2027 now_ms = hal->millis();
2028 bool in_cooldown = (now_ms - _last_tap_event_ms) < 500;
2029 bool duplicate = (tap_event == _last_tap_event) && in_cooldown;
2030 if ((!duplicate && !in_cooldown) || !_tap_event_active) {
2031 result |= STATUS_TAP_EVENT;
2032 if (callbacks.onTap) {
2033 callbacks.onTap(tap_event);
2034 }
2035 _last_tap_event = tap_event;
2036 _last_tap_event_ms = now_ms;
2037 }
2038 }
2039 }
2040 _tap_event_active = tap_now;
2041
2042 return result;
2043}
2044
2046{
2047 uint8_t buffer[9];
2048 if (readRegBuff(REG_CTRL1, buffer, 9) == 0) {
2049 for (int i = 0; i < 9; ++i) {
2050 SENSORLIB_LOG_I("CTRL%d: 0x%02X", i + 1, buffer[i]);
2051 }
2052 }
2053
2054 SENSORLIB_LOG_I("FIFO_CTRL: 0x%02X", readReg(REG_FIFO_CTRL));
2055 SENSORLIB_LOG_I("STATUS_INT: 0x%02X", readReg(REG_STATUS_INT));
2056 SENSORLIB_LOG_I("STATUS0: 0x%02X", readReg(REG_STATUS0));
2057 SENSORLIB_LOG_I("STATUS1: 0x%02X", readReg(REG_STATUS1));
2058}
2059
2060void SensorQMI8658::getChipUsid(uint8_t *buffer, uint8_t length)
2061{
2062 if (length > 6) {
2063 length = 6;
2064 }
2065 memcpy(buffer, _usid, length);
2066}
2067
2069{
2070 return _accel_scale;
2071}
2072
2074{
2075 return _gyro_scale;
2076}
2077
2079{
2080 _axis_layout = layout;
2081}
2082
2087
2088void SensorQMI8658::axisConvert(int16_t accel_data[3], int16_t gyro_data[3])
2089{
2090 int16_t raw_a[3], raw_g[3];
2091
2092 raw_a[0] = accel_data[0];
2093 raw_a[1] = accel_data[1];
2094 raw_a[2] = accel_data[2];
2095 raw_g[0] = gyro_data[0];
2096 raw_g[1] = gyro_data[1];
2097 raw_g[2] = gyro_data[2];
2098
2099 uint8_t layout = static_cast<uint8_t>(_axis_layout);
2100
2101 if (layout >= 4 && layout <= 7) {
2102 accel_data[2] = -accel_data[2];
2103 gyro_data[2] = -gyro_data[2];
2104 }
2105
2106 if (layout & 0x01) {
2107 accel_data[0] = raw_a[1];
2108 accel_data[1] = raw_a[0];
2109 gyro_data[0] = raw_g[1];
2110 gyro_data[1] = raw_g[0];
2111 } else {
2112 accel_data[0] = raw_a[0];
2113 accel_data[1] = raw_a[1];
2114 gyro_data[0] = raw_g[0];
2115 gyro_data[1] = raw_g[1];
2116 }
2117
2118 if (layout == 1 || layout == 2 || layout == 4 || layout == 7) {
2119 accel_data[0] = -accel_data[0];
2120 gyro_data[0] = -gyro_data[0];
2121 }
2122 if (layout == 2 || layout == 3 || layout == 6 || layout == 7) {
2123 accel_data[1] = -accel_data[1];
2124 gyro_data[1] = -gyro_data[1];
2125 }
2126}
2127
2129{
2130 _static_cali_enabled = enable;
2131 if (!enable) {
2133 }
2134}
2135
2140
2145
2146bool SensorQMI8658::processStaticCalibration(int16_t accel_data[3], int16_t gyro_data[3])
2147{
2149 return _static_cali_complete;
2150 }
2151
2152 if (_static_cali_sample_count == 0) {
2153 memset(_accel_cali_sum, 0, sizeof(_accel_cali_sum));
2154 memset(_gyro_cali_sum, 0, sizeof(_gyro_cali_sum));
2155 }
2156
2158 for (int i = 0; i < 3; i++) {
2159 _gyro_cali_sum[i] += gyro_data[i];
2160 if (i == 2) {
2161 _accel_cali_sum[i] += (accel_data[i] - (1 << 14));
2162 } else {
2163 _accel_cali_sum[i] += accel_data[i];
2164 }
2165 }
2167 }
2168
2170 for (int i = 0; i < 3; i++) {
2173 }
2175 _static_cali_complete = true;
2176 return true;
2177 }
2178
2179 return false;
2180}
2181
2182void SensorQMI8658::getStaticCalibrationOffsets(int16_t accel_offset[3], int16_t gyro_offset[3])
2183{
2184 if (accel_offset) {
2185 memcpy(accel_offset, _accel_cali_offset, sizeof(_accel_cali_offset));
2186 }
2187 if (gyro_offset) {
2188 memcpy(gyro_offset, _gyro_cali_offset, sizeof(_gyro_cali_offset));
2189 }
2190}
2191
2193{
2195 _static_cali_complete = false;
2196 memset(_accel_cali_sum, 0, sizeof(_accel_cali_sum));
2197 memset(_gyro_cali_sum, 0, sizeof(_gyro_cali_sum));
2198 memset(_accel_cali_offset, 0, sizeof(_accel_cali_offset));
2199 memset(_gyro_cali_offset, 0, sizeof(_gyro_cali_offset));
2200}
2201
2203{
2204 _dyn_cal_enabled = enable;
2205 if (enable) {
2206 _gyro_static_deviation = 0.005f * 57.29578f * (1 << 15);
2207 _accel_static_deviation = 0.04f / 9.8f * (1 << 14);
2208 } else {
2209 memset(_dyn_gyro_offset, 0, sizeof(_dyn_gyro_offset));
2211 _dyn_static_flag = 0;
2214 }
2215}
2216
2221
2223{
2224 if (gyro_offset) {
2225 memcpy(gyro_offset, _dyn_gyro_offset, sizeof(_dyn_gyro_offset));
2226 }
2227}
2228
2229void SensorQMI8658::processDynamicCalibration(int16_t accel_data[3], int16_t gyro_data[3])
2230{
2231 if (!_dyn_cal_enabled) {
2232 return;
2233 }
2234
2235 float gyro_magnitude = sqrtf(
2236 static_cast<float>(gyro_data[0]) * gyro_data[0] +
2237 static_cast<float>(gyro_data[1]) * gyro_data[1] +
2238 static_cast<float>(gyro_data[2]) * gyro_data[2]
2239 );
2240 float accel_magnitude = sqrtf(
2241 static_cast<float>(accel_data[0]) * accel_data[0] +
2242 static_cast<float>(accel_data[1]) * accel_data[1] +
2243 static_cast<float>(accel_data[2]) * accel_data[2]
2244 );
2245
2246 float gyro_sum = 0;
2247 float accel_sum = 0;
2248
2249 for (int i = 0; i < DYN_CAL_BUFFER_SIZE - 1; i++) {
2251 accel_sum += _accel_speed_buffer[i];
2253 gyro_sum += _gyro_speed_buffer[i];
2254 }
2255
2256 _accel_speed_buffer[DYN_CAL_BUFFER_SIZE - 1] = accel_magnitude;
2257 accel_sum += _accel_speed_buffer[DYN_CAL_BUFFER_SIZE - 1];
2258 _gyro_speed_buffer[DYN_CAL_BUFFER_SIZE - 1] = gyro_magnitude;
2259 gyro_sum += _gyro_speed_buffer[DYN_CAL_BUFFER_SIZE - 1];
2260
2261 float accel_avg = accel_sum / DYN_CAL_BUFFER_SIZE;
2262 float gyro_avg = gyro_sum / DYN_CAL_BUFFER_SIZE;
2263
2264 gyro_sum = 0;
2265 accel_sum = 0;
2266
2267 for (int i = 0; i < DYN_CAL_BUFFER_SIZE; i++) {
2268 float diff = _accel_speed_buffer[i] - accel_avg;
2269 accel_sum += diff * diff;
2270 diff = _gyro_speed_buffer[i] - gyro_avg;
2271 gyro_sum += diff * diff;
2272 }
2273
2274 float accel_std_dev = sqrtf(accel_sum / DYN_CAL_BUFFER_SIZE);
2275 float gyro_std_dev = sqrtf(gyro_sum / DYN_CAL_BUFFER_SIZE);
2276
2277 if (accel_std_dev < _accel_static_deviation && gyro_std_dev < _gyro_static_deviation) {
2278 if (_dyn_static_flag == 0) {
2279 if (_dyn_static_delay < 12) {
2281 } else {
2283 _dyn_static_flag = 1;
2284 }
2285 } else {
2286 if (_dyn_offset_applied != 1) {
2287 if (_dyn_cali_sample_count == 0) {
2288 for (int i = 0; i < 3; i++) {
2289 _dyn_gyro_sum[i] = 0;
2290 }
2293 for (int i = 0; i < 3; i++) {
2294 _dyn_gyro_sum[i] += gyro_data[i];
2295 }
2297 } else if (_dyn_cali_sample_count == DYN_CAL_STATIC_COUNT + 1) {
2298 for (int i = 0; i < 3; i++) {
2300 }
2303 }
2304 }
2305 }
2306 } else {
2308 _dyn_static_flag = 0;
2311 }
2312}
2313
2314bool SensorQMI8658::hardwareSelfTest(bool includeAccel, bool includeGyro)
2315{
2316 if (!hal) return false;
2317
2318 bool gyro_en = _gyro_enabled;
2319 bool accel_en = _accel_enabled;
2320 if (gyro_en) disableGyro();
2321 if (accel_en) disableAccel();
2322
2323 writeReg(REG_CTRL7, 0x00);
2324
2325 if (includeAccel) {
2326 writeReg(REG_CTRL2, static_cast<uint8_t>((ACCEL_RANGE_8G << 4) | ACCEL_ODR_224_2HZ | 0x80));
2327
2328 uint8_t retry = 50;
2329 while (retry-- > 0) {
2330 uint8_t status = readReg(REG_STATUS_INT);
2331 if (status != 0xFF && (status & MASK_INT_AVAIL)) {
2332 break;
2333 }
2334 hal->delay(1);
2335 }
2336
2337 writeReg(REG_CTRL2, static_cast<uint8_t>((ACCEL_RANGE_8G << 4) | ACCEL_ODR_224_2HZ));
2338
2339 retry = 50;
2340 while (retry-- > 0) {
2341 uint8_t status = readReg(REG_STATUS_INT);
2342 if (status != 0xFF && !(status & MASK_INT_AVAIL)) {
2343 break;
2344 }
2345 hal->delay(1);
2346 }
2347
2348 uint8_t reg_data[6];
2349 if (readRegBuff(REG_DVX_L, reg_data, 6) != 0) {
2350 if (accel_en) enableAccel();
2351 if (gyro_en) enableGyro();
2352 return false;
2353 }
2354
2355 int16_t raw_x = (int16_t)((reg_data[1] << 8) | reg_data[0]);
2356 int16_t raw_y = (int16_t)((reg_data[3] << 8) | reg_data[2]);
2357 int16_t raw_z = (int16_t)((reg_data[5] << 8) | reg_data[4]);
2358
2359 _hw_st_accel_result[0] = static_cast<float>(raw_x) * 1000.0f / 2048.0f;
2360 _hw_st_accel_result[1] = static_cast<float>(raw_y) * 1000.0f / 2048.0f;
2361 _hw_st_accel_result[2] = static_cast<float>(raw_z) * 1000.0f / 2048.0f;
2362
2366 SENSORLIB_LOG_E("HW Self-test Accel failed: X=%.1f Y=%.1f Z=%.1f mg",
2368 if (accel_en) enableAccel();
2369 if (gyro_en) enableGyro();
2370 return false;
2371 }
2372 }
2373
2374 if (includeGyro) {
2375 writeReg(REG_CTRL3, static_cast<uint8_t>((GYRO_RANGE_1024DPS << 4) | GYRO_ODR_224_2HZ | 0x80));
2376
2377 uint8_t retry = 50;
2378 while (retry-- > 0) {
2379 uint8_t status = readReg(REG_STATUS_INT);
2380 if (status != 0xFF && (status & MASK_INT_AVAIL)) {
2381 break;
2382 }
2383 hal->delay(1);
2384 }
2385
2386 writeReg(REG_CTRL3, static_cast<uint8_t>((GYRO_RANGE_1024DPS << 4) | GYRO_ODR_224_2HZ));
2387
2388 retry = 50;
2389 while (retry-- > 0) {
2390 uint8_t status = readReg(REG_STATUS_INT);
2391 if (status != 0xFF && !(status & MASK_INT_AVAIL)) {
2392 break;
2393 }
2394 hal->delay(1);
2395 }
2396
2397 uint8_t reg_data[6];
2398 if (readRegBuff(REG_DVX_L, reg_data, 6) != 0) {
2399 if (accel_en) enableAccel();
2400 if (gyro_en) enableGyro();
2401 return false;
2402 }
2403
2404 int16_t raw_x = (int16_t)((reg_data[1] << 8) | reg_data[0]);
2405 int16_t raw_y = (int16_t)((reg_data[3] << 8) | reg_data[2]);
2406 int16_t raw_z = (int16_t)((reg_data[5] << 8) | reg_data[4]);
2407
2408 _hw_st_gyro_result[0] = static_cast<float>(raw_x) / 16.0f;
2409 _hw_st_gyro_result[1] = static_cast<float>(raw_y) / 16.0f;
2410 _hw_st_gyro_result[2] = static_cast<float>(raw_z) / 16.0f;
2411
2415 SENSORLIB_LOG_E("HW Self-test Gyro failed: X=%.1f Y=%.1f Z=%.1f dps",
2417 if (accel_en) enableAccel();
2418 if (gyro_en) enableGyro();
2419 return false;
2420 }
2421 }
2422
2423 if (accel_en) enableAccel();
2424 if (gyro_en) enableGyro();
2425
2426 return true;
2427}
2428
2429void SensorQMI8658::getHardwareSelfTestResults(float accel_result[3], float gyro_result[3])
2430{
2431 if (accel_result) {
2432 memcpy(accel_result, _hw_st_accel_result, sizeof(_hw_st_accel_result));
2433 }
2434 if (gyro_result) {
2435 memcpy(gyro_result, _hw_st_gyro_result, sizeof(_hw_st_gyro_result));
2436 }
2437}
2438
2439
2440bool SensorQMI8658::initImpl(uint8_t param)
2441{
2442 if (_int_pin >= 0 && hal) {
2443 hal->pinMode(_int_pin, INPUT);
2444 }
2445
2446 if (!reset()) {
2447 return false;
2448 }
2449
2450 uint8_t id = getChipID();
2451 if (id != QMI8658_WHO_AM_I_VAL) {
2452 SENSORLIB_LOG_E("QMI8658 ID mismatch: expected 0x%02X, got 0x%02X", QMI8658_WHO_AM_I_VAL, id);
2453 return false;
2454 }
2455
2456 writeReg(REG_CTRL8, 0x80);
2457
2458 if (writeCommand(CTRL_CMD_COPY_USID, 100) != 0) {
2459 SENSORLIB_LOG_E("Failed to copy USID");
2460 return false;
2461 }
2462
2463 uint8_t buffer[3] = {0};
2464 if (readRegBuff(REG_DQW_L, buffer, 3) == 0) {
2465 _firmware_version = buffer[0] | (uint32_t)(buffer[1] << 8) | (uint32_t)(buffer[2] << 16);
2466 }
2467
2468 if (readRegBuff(REG_DVX_L, _usid, 6) != 0) {
2469 SENSORLIB_LOG_E("Failed to read USID");
2470 return false;
2471 }
2472
2473 return true;
2474}
2475
2476#endif
OperationMode
Enumeration of sensor operation modes.
GyroFullScaleRange
Enumeration of gyroscope full-scale range settings.
@ FS_2000_DPS
±2000 °/s
@ FS_1000_DPS
±1000 °/s
@ FS_250_DPS
±250 °/s
@ FS_500_DPS
±500 °/s
@ FS_125_DPS
±125 °/s
AccelFullScaleRange
Enumeration of accelerometer full-scale range settings.
@ FS_16G
±16g range
#define SENSORLIB_LOG_I(...)
#define SENSORLIB_LOG_E(...)
#define SENSORLIB_LOG_D(...)
@license MIT License
std::unique_ptr< SensorHal > hal
bool clrRegBit(uint8_t reg, uint8_t bit)
bool getRegBit(uint8_t reg, uint8_t bit)
int readReg(uint8_t reg) const
int writeRegBuff(uint8_t reg, uint8_t *buf, size_t len)
int writeReg(uint8_t reg, uint8_t val)
int readRegBuff(uint8_t reg, uint8_t *buf, size_t len) const
int updateBits(uint8_t reg, uint8_t mask, uint8_t value_shifted)
bool setRegBit(uint8_t reg, uint8_t bit)
bool configGyro(GyroFullScaleRange range, float data_rate_hz, LpfMode lpf=LpfMode::MODE_0)
Configure the gyroscope with specified parameters.
GyroODR
Gyroscope output data rate enumeration.
static constexpr uint8_t DYN_CAL_BUFFER_SIZE
bool reset() override
Reset the IMU sensor to its default state.
bool resetFifo()
Reset the FIFO.
static constexpr uint8_t STATUS_TAP_EVENT
bool configPedometer(uint16_t sample_count, uint16_t peak_to_peak, uint16_t peak_threshold, uint16_t time_up, uint8_t time_low=20, uint8_t entry_count=10, uint8_t fix_precision=0, uint8_t sig_count=4)
Configure pedometer parameters.
float _accel_speed_buffer[DYN_CAL_BUFFER_SIZE]
bool readGyro(GyroscopeData &out) override
Read gyroscope data.
void setAccelOffset(int16_t x, int16_t y, int16_t z)
Set accelerometer host delta offsets.
bool setOperationMode(OperationMode mode) override
Set IMU operation mode.
bool disableTap()
Disable tap detection.
bool readGyroRaw(int16_t &x, int16_t &y, int16_t &z) override
Read raw gyroscope data without scaling.
float getTemperature() override
Get the temperature reading from the sensor.
uint8_t getInterruptStatus()
Get interrupt status.
static constexpr uint8_t STATUS_WOM_EVENT
float getGyroScaleFromRange(GyroFullScaleRange range)
void getStaticCalibrationOffsets(int16_t accel_offset[3], int16_t gyro_offset[3])
Get static calibration offsets.
void setSignificantMotionCallback(MotionCallback callback)
Set callback for significant motion event.
bool isGyroEnabled() override
Check if gyroscope is enabled.
bool isDataReady(uint8_t mask=static_cast< uint8_t >(ImuBase::DataReadyMask::BOTH)) override
Check if requested IMU data is available.
uint8_t findClosestAccelLpOdr(float hz)
bool configMotionDetectDefault(MotionType type)
Configure motion detection with default parameters.
static constexpr uint16_t MAX_STATIC_CALI_SAMPLES
uint32_t getStepCount()
Get pedometer step count.
bool disableMotionDetect()
Disable motion detection.
int writeCommand(uint8_t cmd, uint32_t timeout_ms=1000)
bool setAccelOutputDataRate(float data_rate_hz) override
Set accelerometer output data rate.
void setDataLockingCallback(DataReadyCallback callback)
Set callback for data locking.
std::function< void(TapEvent event)> TapCallback
Callback function type for tap events.
uint8_t getAccelRangeRegValue(AccelFullScaleRange range)
bool enableAccel() override
Enable the accelerometer.
static constexpr uint8_t STATUS_SIGNIFICANT_MOTION
static constexpr float HW_ST_ACCEL_THRESHOLD_MG
bool configFifo(FifoMode mode, FifoSamples samples=FifoSamples::SAMPLES_16, uint8_t watermark_samples=16)
Configure FIFO with specified mode.
bool isStaticCalibrationComplete() const
Get the current static calibration status.
bool processStaticCalibration(int16_t accel_data[3], int16_t gyro_data[3])
Process static calibration with current sensor data.
float getAccelScale()
Get the accelerometer scale factor.
void resetStaticCalibration()
Reset static calibration.
uint8_t getChipID() override
Get the chip ID / WHO_AM_I value.
bool disableSyncMode() override
Disable synchronous sampling mode.
bool enableSyncMode() override
Enable synchronous sampling mode.
void setIntPin(int pin)
Backward-compatible alias of setPins.
float odrToFloat(float odr)
AccelODR
Accelerometer output data rate enumeration.
@ ODR_LP_21Hz
Low power 21 Hz.
@ ODR_LP_11Hz
Low power 11 Hz.
@ ODR_LP_128Hz
Low power 128 Hz.
@ ODR_LP_3Hz
Low power 3 Hz.
bool disableLockingMechanism()
Disable the data locking mechanism.
bool disableAccel() override
Disable the accelerometer.
bool selfTestGyro()
Perform gyroscope self-test.
void processDynamicCalibration(int16_t accel_data[3], int16_t gyro_data[3])
Process dynamic calibration with current sensor data.
bool enableInterrupt(IntPin pin)
Enable interrupt on specified pin.
void enableStaticCalibration(bool enable)
Start static calibration.
bool readAccel(AccelerometerData &out) override
Read accelerometer data.
void getDynamicGyroCalibrationOffsets(int16_t gyro_offset[3])
Get dynamic gyroscope calibration offsets.
uint8_t findClosestAccelOdr(float hz)
bool enablePedometer(IntPin pin=IntPin::DISABLE)
Enable pedometer.
@ STREAM
Stream mode (overwrites oldest)
@ BYPASS
Bypass mode (FIFO disabled)
@ FIFO
FIFO mode (stops when full)
void setTapCallback(TapCallback callback)
Set callback for tap event.
bool enableGyro() override
Enable the gyroscope.
bool configTap(TapPriority priority, uint8_t peak_window, uint16_t tap_window, uint16_t double_tap_window, float peak_mag_threshold, float quiet_threshold)
Configure tap detection parameters.
bool disableGyro() override
Disable the gyroscope.
~SensorQMI8658() override
Destroy the SensorQMI8658 object.
uint16_t readFromFifo(AccelerometerData *accel_data, uint16_t accel_count, GyroscopeData *gyro_data, uint16_t gyro_count) override
Read data from FIFO buffer.
bool enableDataReadyInterrupt(IntPin pin=IntPin::PIN2)
Enable data ready interrupt.
bool enableLockingMechanism()
Enable the data locking mechanism.
uint32_t getFirmwareVersion() override
Get the firmware version.
void setNoMotionCallback(MotionCallback callback)
Set callback for no-motion event.
static constexpr float HW_ST_GYRO_THRESHOLD_DPS
TapEvent getTapStatus()
Get tap status.
static constexpr uint8_t STATUS_ANY_MOTION_EVENT
bool isPresent() override
Check if the IMU sensor is responding.
uint8_t getGyroRangeRegValue(GyroFullScaleRange range)
GyroRange
Gyroscope full-scale range enumeration.
@ FS_128DPS
±128 degrees per second
@ FS_512DPS
±512 degrees per second
@ FS_2048DPS
±2048 degrees per second
@ FS_1024DPS
±1024 degrees per second
@ FS_256DPS
±256 degrees per second
bool isAccelEnabled() override
Check if accelerometer is enabled.
bool configureFifo(bool enable, uint8_t watermark_samples=16) override
Configure the FIFO buffer.
EventCallbacks & getCallbacks()
Get current callbacks.
void enableDynamicGyroCalibration(bool enable)
Enable dynamic gyroscope calibration.
Layout getAxisLayout() const
Get the current axis layout setting.
float _gyro_speed_buffer[DYN_CAL_BUFFER_SIZE]
SensorQMI8658()
Construct a new SensorQMI8658 object.
bool enableTap(IntPin pin=IntPin::PIN1)
Enable tap detection.
void setGyroOffset(int16_t x, int16_t y, int16_t z)
Set gyroscope host delta offsets.
float getAccelScaleFromRange(AccelFullScaleRange range)
LpfMode
Low-pass filter mode enumeration.
@ MODE_0
2.66% of output data rate
@ OFF
Disable low-pass filter.
@ MODE_3
13.37% of output data rate
@ MODE_2
5.39% of output data rate
@ MODE_1
3.63% of output data rate
AccelRange
Accelerometer full-scale range enumeration.
bool enableMotionDetect(IntPin pin=IntPin::PIN1)
Enable motion detection on specified pin.
uint8_t findClosestGyroOdr(float hz)
bool calibrate(uint16_t *gyro_x_gain=nullptr, uint16_t *gyro_y_gain=nullptr, uint16_t *gyro_z_gain=nullptr)
Perform on-demand calibration.
bool initImpl(uint8_t param) override
bool isDynamicGyroCalibrationEnabled() const
Check if dynamic gyro calibration is enabled.
bool setGyroFullScaleRange(GyroFullScaleRange range) override
Set gyroscope full scale range.
MotionType
Motion detection type enumeration.
@ ANY_MOTION
Any motion detection.
@ NO_MOTION
No motion detection.
@ SIGNIFICANT
Significant motion detection.
bool setGyroOutputDataRate(float data_rate_hz) override
Set gyroscope output data rate.
void setAccelDataReadyCallback(DataReadyCallback callback)
Set callback for accelerometer data ready.
void setPedometerCallback(MotionCallback callback)
Set callback for pedometer event.
std::function< void()> DataReadyCallback
Callback function type for data ready events.
bool selfTest() override
Perform self-test on the IMU sensor.
void setAnyMotionCallback(MotionCallback callback)
Set callback for any-motion event.
void dumpRegisters()
Dump control registers for debugging.
bool resetStepCount()
Reset pedometer counter.
bool configAccel(AccelFullScaleRange range, float data_rate_hz, LpfMode lpf=LpfMode::MODE_0)
Configure the accelerometer with specified parameters.
uint16_t update()
Update sensor status and process events.
float getGyroScale()
Get the gyroscope scale factor.
void getHardwareSelfTestResults(float accel_result[3], float gyro_result[3])
Get hardware self-test results.
static constexpr uint8_t STATUS_PEDOMETER_EVENT
bool configTapDefault(TapPriority priority=TapPriority::X_GT_Y_GT_Z)
Configure tap detection with default parameters.
bool readAccelRaw(int16_t &x, int16_t &y, int16_t &z) override
Read raw accelerometer data without scaling.
uint8_t getFifoStatus()
Get FIFO status.
uint32_t getTimestamp() override
Get the timestamp from the sensor.
std::function< void()> MotionCallback
Callback function type for motion events.
bool setAccelFullScaleRange(AccelFullScaleRange range) override
Set accelerometer full scale range.
void setCallbacks(const EventCallbacks &cbs)
Set all event callbacks at once.
void setPins(int pin)
Set interrupt pin.
bool configWakeOnMotion(uint8_t threshold=200, float odr_hz=128.0f, IntPin pin=IntPin::PIN2)
Configure wake-on-motion (WoM) feature.
bool configPedometerDefault(float odr=125.0f)
Configure pedometer with default parameters.
bool disablePedometer()
Disable pedometer.
void setGyroDataReadyCallback(DataReadyCallback callback)
Set callback for gyroscope data ready.
bool hardwareSelfTest(bool includeAccel=true, bool includeGyro=true)
Perform hardware self-test on both sensors.
TapPriority
Tap detection axis priority enumeration.
void setAxisLayout(Layout layout)
Set the sensor axis layout for coordinate transformation.
bool configWakeOnMotionAdvanced(uint8_t threshold, float odr_hz, IntPin pin, uint8_t default_pin_value, uint8_t blanking_time, AccelFullScaleRange acc_range)
Configure wake-on-motion (WoM) with full vendor parameters.
bool isStaticCalibrationEnabled() const
Check if static calibration is enabled.
void setWakeOnMotionCallback(MotionCallback callback)
Set callback for wake-on-motion event.
void axisConvert(int16_t accel_data[3], int16_t gyro_data[3])
Apply axis conversion to raw sensor data.
bool selfTestAccel()
Perform accelerometer self-test.
static constexpr uint8_t STATUS_NO_MOTION_EVENT
static constexpr uint8_t DYN_CAL_STATIC_COUNT
bool writeCalibration(uint16_t gyro_x_gain, uint16_t gyro_y_gain, uint16_t gyro_z_gain)
Write previously saved gyro calibration gains.
void getChipUsid(uint8_t *buffer, uint8_t length)
Get sensor USID (unique chip ID).
Layout
Sensor axis layout enumeration for coordinate transformation.
FifoSamples
FIFO sample count enumeration.
bool configMotionDetect(MotionType type, float threshold_x, float threshold_y, float threshold_z, uint8_t duration)
Configure motion detection parameters.
bool disableInterrupt(IntPin pin)
Disable interrupt on specified pin.
uint8_t mgToBytes(float mg)
float gToMps2(float g_value)
Convert acceleration from g-force to m/s²
uint8_t i
const uint16_t buffer_size
Structure representing accelerometer data.
SensorVector mps2
Acceleration in meters per second squared (m/s²)
float temperature
Temperature in degrees Celsius (if available)
RawVector raw
Raw sensor values in LSB (least significant bits)
Structure representing gyroscope data.
float temperature
Temperature in °C (if available)
SensorVector dps
Angular velocity in degrees per second (°/s)
RawVector raw
Raw sensor values in LSB.
int16_t x
X-axis raw value.
int16_t y
Y-axis raw value.
int16_t z
Z-axis raw value.
Grouped callbacks for QMI8658 events.
float x
X-axis component.
float y
Y-axis component.
float z
Z-axis component.
int16_t x[5]
int16_t y[5]