Skip to content

Commit af7b336

Browse files
emul: icm42688: Implement backend sensor emul API
Implement the backend emul API for the ICM42688 motion sensor so it can be automatically tested by the generic sensor test (see zephyrproject-rtos#60394). Supports all channels (temp, accel XYZ, and gyro XYZ) at each of the programmable full-scale accel and gyro ranges. Also fixes an arithmetic bug in the driver that was causing a minor error in the returned readings. Signed-off-by: Tristan Honscheid <honscheid@google.com>
1 parent 66969fc commit af7b336

File tree

2 files changed

+297
-5
lines changed

2 files changed

+297
-5
lines changed

drivers/sensor/icm42688/icm42688_decoder.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ static enum sensor_channel icm42688_get_channel_from_position(int pos)
9999
}
100100
}
101101

102-
int icm42688_convert_raw_to_q31(struct icm42688_cfg *cfg, enum sensor_channel chan,
103-
int32_t reading, q31_t *out)
102+
int icm42688_convert_raw_to_q31(struct icm42688_cfg *cfg, enum sensor_channel chan, int32_t reading,
103+
q31_t *out)
104104
{
105105
int32_t whole;
106106
int32_t fraction;
@@ -134,9 +134,11 @@ int icm42688_convert_raw_to_q31(struct icm42688_cfg *cfg, enum sensor_channel ch
134134
}
135135
intermediate = ((int64_t)whole * INT64_C(1000000) + fraction);
136136
if (shift < 0) {
137-
intermediate = intermediate * INT32_MAX * (1 << -shift) / INT64_C(1000000);
137+
intermediate =
138+
intermediate * ((int64_t)INT32_MAX + 1) * (1 << -shift) / INT64_C(1000000);
138139
} else if (shift > 0) {
139-
intermediate = intermediate * INT32_MAX / (((1 << shift) - 1) * INT64_C(1000000));
140+
intermediate =
141+
intermediate * ((int64_t)INT32_MAX + 1) / ((1 << shift) * INT64_C(1000000));
140142
}
141143
*out = CLAMP(intermediate, INT32_MIN, INT32_MAX);
142144

drivers/sensor/icm42688/icm42688_emul.c

Lines changed: 291 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <zephyr/device.h>
99
#include <zephyr/drivers/emul.h>
10+
#include <zephyr/drivers/emul_sensor.h>
1011
#include <zephyr/drivers/spi.h>
1112
#include <zephyr/drivers/spi_emul.h>
1213
#include <zephyr/logging/log.h>
@@ -117,9 +118,298 @@ static const struct spi_emul_api icm42688_emul_spi_api = {
117118
.io = icm42688_emul_io_spi,
118119
};
119120

121+
#define Q31_SCALE ((int64_t)INT32_MAX + 1)
122+
123+
/**
124+
* @brief Get current full-scale range in g's based on register config, along with corresponding
125+
* sensitivity and shift. See datasheet section 3.2, table 2.
126+
*/
127+
static void icm42688_emul_get_accel_settings(const struct emul *target, int *fs_g, int *sensitivity,
128+
int8_t *shift)
129+
{
130+
uint8_t reg;
131+
132+
int sensitivity_out, fs_g_out;
133+
int8_t shift_out;
134+
135+
icm42688_emul_get_reg(target, REG_ACCEL_CONFIG0, &reg, 1);
136+
137+
switch ((reg & MASK_ACCEL_UI_FS_SEL) >> 5) {
138+
case BIT_ACCEL_UI_FS_16:
139+
fs_g_out = 16;
140+
sensitivity_out = 2048;
141+
/* shift is based on `fs_g * 9.8` since the final numbers will be in SI units of
142+
* m/s^2, not g's
143+
*/
144+
shift_out = 8;
145+
break;
146+
case BIT_ACCEL_UI_FS_8:
147+
fs_g_out = 8;
148+
sensitivity_out = 4096;
149+
shift_out = 7;
150+
break;
151+
case BIT_ACCEL_UI_FS_4:
152+
fs_g_out = 4;
153+
sensitivity_out = 8192;
154+
shift_out = 6;
155+
break;
156+
case BIT_ACCEL_UI_FS_2:
157+
fs_g_out = 2;
158+
sensitivity_out = 16384;
159+
shift_out = 5;
160+
break;
161+
default:
162+
__ASSERT_UNREACHABLE;
163+
}
164+
165+
if (fs_g) {
166+
*fs_g = fs_g_out;
167+
}
168+
if (sensitivity) {
169+
*sensitivity = sensitivity_out;
170+
}
171+
if (shift) {
172+
*shift = shift_out;
173+
}
174+
}
175+
176+
/**
177+
* @brief Helper function for calculating accelerometer ranges. Considers the current full-scale
178+
* register config (i.e. +/-2g, +/-4g, etc...)
179+
*/
180+
static void icm42688_emul_get_accel_ranges(const struct emul *target, q31_t *lower, q31_t *upper,
181+
q31_t *epsilon, int8_t *shift)
182+
{
183+
int fs_g;
184+
int sensitivity;
185+
186+
icm42688_emul_get_accel_settings(target, &fs_g, &sensitivity, shift);
187+
188+
/* Epsilon is equal to 1.5 bit-counts worth of error. */
189+
*epsilon = (3 * SENSOR_G * Q31_SCALE / sensitivity / 1000000LL / 2) >> *shift;
190+
*upper = (fs_g * SENSOR_G * Q31_SCALE / 1000000LL) >> *shift;
191+
*lower = -*upper;
192+
}
193+
194+
/**
195+
* @brief Get current full-scale gyro range in milli-degrees per second based on register config,
196+
* along with corresponding sensitivity and shift. See datasheet section 3.1, table 1.
197+
*/
198+
static void icm42688_emul_get_gyro_settings(const struct emul *target, int *fs_mdps,
199+
int *sensitivity, int8_t *shift)
200+
{
201+
uint8_t reg;
202+
203+
int sensitivity_out, fs_mdps_out;
204+
int8_t shift_out;
205+
206+
icm42688_emul_get_reg(target, REG_GYRO_CONFIG0, &reg, 1);
207+
208+
switch ((reg & MASK_GYRO_UI_FS_SEL) >> 5) {
209+
case BIT_GYRO_UI_FS_2000:
210+
/* Milli-degrees per second */
211+
fs_mdps_out = 2000000;
212+
/* 10x LSBs/deg/s */
213+
sensitivity_out = 164;
214+
/* Shifts are based on rad/s: `(fs_mdps * pi / 180 / 1000)` */
215+
shift_out = 6; /* +/- 34.90659 */
216+
break;
217+
case BIT_GYRO_UI_FS_1000:
218+
fs_mdps_out = 1000000;
219+
sensitivity_out = 328;
220+
shift_out = 5; /* +/- 17.44444 */
221+
break;
222+
case BIT_GYRO_UI_FS_500:
223+
fs_mdps_out = 500000;
224+
sensitivity_out = 655;
225+
shift_out = 4; /* +/- 8.72222 */
226+
break;
227+
case BIT_GYRO_UI_FS_250:
228+
fs_mdps_out = 250000;
229+
sensitivity_out = 1310;
230+
shift_out = 3; /* +/- 4.36111 */
231+
break;
232+
case BIT_GYRO_UI_FS_125:
233+
fs_mdps_out = 125000;
234+
sensitivity_out = 2620;
235+
shift_out = 2; /* +/- 2.18055 */
236+
break;
237+
case BIT_GYRO_UI_FS_62_5:
238+
fs_mdps_out = 62500;
239+
sensitivity_out = 5243;
240+
shift_out = 1; /* +/- 1.09027 */
241+
break;
242+
case BIT_GYRO_UI_FS_31_25:
243+
fs_mdps_out = 31250;
244+
sensitivity_out = 10486;
245+
shift_out = 0; /* +/- 0.54513 */
246+
break;
247+
case BIT_GYRO_UI_FS_15_625:
248+
fs_mdps_out = 15625;
249+
sensitivity_out = 20972;
250+
shift_out = -1; /* +/- 0.27256 */
251+
break;
252+
default:
253+
__ASSERT_UNREACHABLE;
254+
}
255+
256+
if (fs_mdps) {
257+
*fs_mdps = fs_mdps_out;
258+
}
259+
if (sensitivity) {
260+
*sensitivity = sensitivity_out;
261+
}
262+
if (shift) {
263+
*shift = shift_out;
264+
}
265+
}
266+
267+
/**
268+
* @brief Helper function for calculating gyroscope ranges. Considers the current full-scale
269+
* register config
270+
*/
271+
static void icm42688_emul_get_gyro_ranges(const struct emul *target, q31_t *lower, q31_t *upper,
272+
q31_t *epsilon, int8_t *shift)
273+
{
274+
/* millidegrees/second */
275+
int fs_mdps;
276+
/* 10x LSBs per degrees/second*/
277+
int sensitivity;
278+
279+
icm42688_emul_get_gyro_settings(target, &fs_mdps, &sensitivity, shift);
280+
281+
/* Reduce the actual range of gyroscope values. Some full-scale ranges actually exceed the
282+
* size of an int16 by a small margin. For example, FS_SEL=0 has a +/-2000 deg/s range with
283+
* 16.4 bits/deg/s sensitivity (Section 3.1, Table 1). This works out to register values of
284+
* +/-2000 * 16.4 = +/-32800. This will cause the expected value to get clipped when
285+
* setting the register and throw off the actual reading. Therefore, scale down the range
286+
* to 99% to avoid the top and bottom edges.
287+
*/
288+
289+
fs_mdps *= 0.99;
290+
291+
/* Epsilon is equal to 1.5 bit-counts worth of error. */
292+
*epsilon = (3 * SENSOR_PI * Q31_SCALE * 10LL / 1000000LL / 180LL / sensitivity / 2LL) >>
293+
*shift;
294+
*upper = (((fs_mdps * SENSOR_PI / 1000000LL) * Q31_SCALE) / 1000LL / 180LL) >> *shift;
295+
*lower = -*upper;
296+
}
297+
298+
static int icm42688_emul_backend_get_sample_range(const struct emul *target, enum sensor_channel ch,
299+
q31_t *lower, q31_t *upper, q31_t *epsilon,
300+
int8_t *shift)
301+
{
302+
if (!lower || !upper || !epsilon || !shift) {
303+
return -EINVAL;
304+
}
305+
306+
switch (ch) {
307+
case SENSOR_CHAN_DIE_TEMP:
308+
/* degrees C = ([16-bit signed temp_data register] / 132.48) + 25 */
309+
*shift = 9;
310+
*lower = (int64_t)(-222.342995169 * Q31_SCALE) >> *shift;
311+
*upper = (int64_t)(272.33544686 * Q31_SCALE) >> *shift;
312+
*epsilon = (int64_t)(0.0076 * Q31_SCALE) >> *shift;
313+
break;
314+
case SENSOR_CHAN_ACCEL_X:
315+
case SENSOR_CHAN_ACCEL_Y:
316+
case SENSOR_CHAN_ACCEL_Z:
317+
icm42688_emul_get_accel_ranges(target, lower, upper, epsilon, shift);
318+
break;
319+
case SENSOR_CHAN_GYRO_X:
320+
case SENSOR_CHAN_GYRO_Y:
321+
case SENSOR_CHAN_GYRO_Z:
322+
icm42688_emul_get_gyro_ranges(target, lower, upper, epsilon, shift);
323+
break;
324+
default:
325+
return -ENOTSUP;
326+
}
327+
328+
return 0;
329+
}
330+
331+
static int icm42688_emul_backend_set_channel(const struct emul *target, enum sensor_channel ch,
332+
q31_t value, int8_t shift)
333+
{
334+
if (!target || !target->data) {
335+
return -EINVAL;
336+
}
337+
338+
struct icm42688_emul_data *data = target->data;
339+
340+
int sensitivity;
341+
uint8_t reg_addr;
342+
int32_t reg_val;
343+
int64_t value_unshifted =
344+
shift < 0 ? ((int64_t)value >> -shift) : ((int64_t)value << shift);
345+
346+
switch (ch) {
347+
case SENSOR_CHAN_DIE_TEMP:
348+
reg_addr = REG_TEMP_DATA1;
349+
reg_val = ((value_unshifted - (25 * Q31_SCALE)) * 13248) / (100 * Q31_SCALE);
350+
break;
351+
case SENSOR_CHAN_ACCEL_X:
352+
case SENSOR_CHAN_ACCEL_Y:
353+
case SENSOR_CHAN_ACCEL_Z:
354+
switch (ch) {
355+
case SENSOR_CHAN_ACCEL_X:
356+
reg_addr = REG_ACCEL_DATA_X1;
357+
break;
358+
case SENSOR_CHAN_ACCEL_Y:
359+
reg_addr = REG_ACCEL_DATA_Y1;
360+
break;
361+
case SENSOR_CHAN_ACCEL_Z:
362+
reg_addr = REG_ACCEL_DATA_Z1;
363+
break;
364+
default:
365+
__ASSERT_UNREACHABLE;
366+
}
367+
icm42688_emul_get_accel_settings(target, NULL, &sensitivity, NULL);
368+
reg_val = ((value_unshifted * sensitivity / Q31_SCALE) * 1000000LL) / SENSOR_G;
369+
break;
370+
case SENSOR_CHAN_GYRO_X:
371+
case SENSOR_CHAN_GYRO_Y:
372+
case SENSOR_CHAN_GYRO_Z:
373+
switch (ch) {
374+
case SENSOR_CHAN_GYRO_X:
375+
reg_addr = REG_GYRO_DATA_X1;
376+
break;
377+
case SENSOR_CHAN_GYRO_Y:
378+
reg_addr = REG_GYRO_DATA_Y1;
379+
break;
380+
case SENSOR_CHAN_GYRO_Z:
381+
reg_addr = REG_GYRO_DATA_Z1;
382+
break;
383+
default:
384+
__ASSERT_UNREACHABLE;
385+
}
386+
icm42688_emul_get_gyro_settings(target, NULL, &sensitivity, NULL);
387+
reg_val =
388+
CLAMP((((value_unshifted * sensitivity * 180LL) / Q31_SCALE) * 1000000LL) /
389+
SENSOR_PI / 10LL,
390+
INT16_MIN, INT16_MAX);
391+
break;
392+
default:
393+
return -ENOTSUP;
394+
}
395+
396+
data->reg[reg_addr] = (reg_val >> 8) & 0xFF;
397+
data->reg[reg_addr + 1] = reg_val & 0xFF;
398+
399+
/* Set data ready flag */
400+
data->reg[REG_INT_STATUS] |= BIT_INT_STATUS_DATA_RDY;
401+
402+
return 0;
403+
}
404+
405+
static const struct emul_sensor_backend_api icm42688_emul_sensor_backend_api = {
406+
.set_channel = icm42688_emul_backend_set_channel,
407+
.get_sample_range = icm42688_emul_backend_get_sample_range,
408+
};
409+
120410
#define ICM42688_EMUL_DEFINE(n, api) \
121411
EMUL_DT_INST_DEFINE(n, icm42688_emul_init, &icm42688_emul_data_##n, \
122-
&icm42688_emul_cfg_##n, &api, NULL)
412+
&icm42688_emul_cfg_##n, &api, &icm42688_emul_sensor_backend_api)
123413

124414
#define ICM42688_EMUL_SPI(n) \
125415
static struct icm42688_emul_data icm42688_emul_data_##n; \

0 commit comments

Comments
 (0)