Skip to content

Commit 39480f1

Browse files
emul: Introduce emulator backend API and generic sensor test
This PR introduces a backend API to be implemented by sensor emulators that creates a standardized mechanism for setting expected sensor readings in tests. This unlocks the ability to create a generic sensor test that can automatically set expected values in supported sensor emulators and verify them through the existing sensor API. An implementation of this API is provided for the AKM09918C magnetometer. A generic sensor test is also created to exercise this implementation. Observe that this test knows nothing about the AKM09918C; info about supported channels and sample ranges is discovered through the backend API. The test iterates over all devices attached to the virtual I2C and SPI buses in the test binary's device tree, which (theoretically) covers all sensors. Sensors whose emulator does not exist yet or does not support the backend API are skipped. Signed-off-by: Tristan Honscheid <[email protected]>
1 parent 1ae83c7 commit 39480f1

File tree

7 files changed

+468
-3
lines changed

7 files changed

+468
-3
lines changed

doc/hardware/peripherals/sensor.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,4 @@ API Reference
228228
**************
229229

230230
.. doxygengroup:: sensor_interface
231+
.. doxygengroup:: sensor_emulator_backend

drivers/sensor/akm09918c/akm09918c_emul.c

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
#include <zephyr/device.h>
99
#include <zephyr/drivers/emul.h>
10+
#include <zephyr/drivers/emul_sensor.h>
1011
#include <zephyr/drivers/i2c.h>
1112
#include <zephyr/drivers/i2c_emul.h>
1213
#include <zephyr/logging/log.h>
14+
#include <zephyr/sys/util.h>
1315

16+
#include "akm09918c.h"
1417
#include "akm09918c_emul.h"
1518
#include "akm09918c_reg.h"
1619

@@ -130,14 +133,90 @@ static int akm09918c_emul_init(const struct emul *target, const struct device *p
130133
return 0;
131134
}
132135

136+
static int akm09918c_emul_backend_set_channel(const struct emul *target, enum sensor_channel ch,
137+
q31_t value, int8_t shift)
138+
{
139+
if (!target || !target->data) {
140+
return -EINVAL;
141+
}
142+
143+
struct akm09918c_emul_data *data = target->data;
144+
uint8_t reg;
145+
146+
switch (ch) {
147+
case SENSOR_CHAN_MAGN_X:
148+
reg = AKM09918C_REG_HXL;
149+
break;
150+
case SENSOR_CHAN_MAGN_Y:
151+
reg = AKM09918C_REG_HYL;
152+
break;
153+
case SENSOR_CHAN_MAGN_Z:
154+
reg = AKM09918C_REG_HZL;
155+
break;
156+
/* This function only supports setting single channels, so skip MAGN_XYZ */
157+
default:
158+
return -ENOTSUP;
159+
}
160+
161+
/* Set the ST1 register to show we have data */
162+
data->reg[AKM09918C_REG_ST1] |= AKM09918C_ST1_DRDY;
163+
164+
/* Convert fixed-point Gauss values into microgauss and then into its bit representation */
165+
int32_t microgauss = (shift < 0 ? ((int64_t)value >> -shift) : ((int64_t)value << shift)) *
166+
1000000 / ((int64_t)INT32_MAX + 1);
167+
168+
int16_t reg_val =
169+
CLAMP(microgauss, AKM09918C_MAGN_MIN_MICRO_GAUSS, AKM09918C_MAGN_MAX_MICRO_GAUSS) /
170+
AKM09918C_MICRO_GAUSS_PER_BIT;
171+
172+
/* Insert reading into registers */
173+
data->reg[reg] = reg_val & 0xFF;
174+
data->reg[reg + 1] = (reg_val >> 8) & 0xFF;
175+
176+
return 0;
177+
}
178+
179+
static int akm09918c_emul_backend_get_sample_range(const struct emul *target,
180+
enum sensor_channel ch, q31_t *lower,
181+
q31_t *upper, q31_t *epsilon, int8_t *shift)
182+
{
183+
ARG_UNUSED(target);
184+
185+
if (!lower || !upper || !epsilon || !shift) {
186+
return -EINVAL;
187+
}
188+
189+
switch (ch) {
190+
case SENSOR_CHAN_MAGN_X:
191+
case SENSOR_CHAN_MAGN_Y:
192+
case SENSOR_CHAN_MAGN_Z:
193+
/* +/- 49.12 Gs is the measurement range. 0.0015 Gs is the granularity */
194+
*shift = 6;
195+
*upper = (int64_t)(49.12 * ((int64_t)INT32_MAX + 1)) >> *shift;
196+
*lower = -*upper;
197+
*epsilon = (int64_t)(0.0015 * ((int64_t)INT32_MAX + 1)) >> *shift;
198+
break;
199+
default:
200+
return -ENOTSUP;
201+
}
202+
203+
return 0;
204+
}
205+
133206
static const struct i2c_emul_api akm09918c_emul_api_i2c = {
134207
.transfer = akm09918c_emul_transfer_i2c,
135208
};
136209

210+
static const struct emul_sensor_backend_api akm09918c_emul_sensor_backend_api = {
211+
.set_channel = akm09918c_emul_backend_set_channel,
212+
.get_sample_range = akm09918c_emul_backend_get_sample_range,
213+
};
214+
137215
#define AKM09918C_EMUL(n) \
138216
const struct akm09918c_emul_cfg akm09918c_emul_cfg_##n; \
139217
struct akm09918c_emul_data akm09918c_emul_data_##n; \
140218
EMUL_DT_INST_DEFINE(n, akm09918c_emul_init, &akm09918c_emul_data_##n, \
141-
&akm09918c_emul_cfg_##n, &akm09918c_emul_api_i2c, NULL)
219+
&akm09918c_emul_cfg_##n, &akm09918c_emul_api_i2c, \
220+
&akm09918c_emul_sensor_backend_api)
142221

143222
DT_INST_FOREACH_STATUS_OKAY(AKM09918C_EMUL)

include/zephyr/drivers/emul_sensor.h

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2023 Google LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <zephyr/drivers/emul.h>
7+
#include <zephyr/drivers/sensor.h>
8+
9+
#include <stdint.h>
10+
11+
/**
12+
* @brief Sensor emulator backend API
13+
* @defgroup sensor_emulator_backend Sensor emulator backend API
14+
* @ingroup io_interfaces
15+
* @{
16+
*/
17+
18+
/**
19+
* @cond INTERNAL_HIDDEN
20+
*
21+
* These are for internal use only, so skip these in public documentation.
22+
*/
23+
24+
/**
25+
* @brief Collection of function pointers implementing a common backend API for sensor emulators
26+
*/
27+
__subsystem struct emul_sensor_backend_api {
28+
/** Sets a given fractional value for a given sensor channel. */
29+
int (*set_channel)(const struct emul *target, enum sensor_channel ch, q31_t value,
30+
int8_t shift);
31+
/** Retrieve a range of sensor values to use with test. */
32+
int (*get_sample_range)(const struct emul *target, enum sensor_channel ch, q31_t *lower,
33+
q31_t *upper, q31_t *epsilon, int8_t *shift);
34+
};
35+
/**
36+
* @endcond
37+
*/
38+
39+
/**
40+
* @brief Check if a given sensor emulator supports the backend API
41+
*
42+
* @param target Pointer to emulator instance to query
43+
*
44+
* @return True if supported, false if unsupported or if \p target is NULL.
45+
*/
46+
static inline bool emul_sensor_backend_is_supported(const struct emul *target)
47+
{
48+
return target && target->backend_api;
49+
}
50+
51+
/**
52+
* @brief Set an expected value for a given channel on a given sensor emulator
53+
*
54+
* @param target Pointer to emulator instance to operate on
55+
* @param ch Sensor channel to set expected value for
56+
* @param value Expected value in fixed-point format using standard SI unit for sensor type
57+
* @param shift Shift value (scaling factor) applied to \p value
58+
*
59+
* @return 0 if successful
60+
* @return -ENOTSUP if no backend API or if channel not supported by emul
61+
* @return -ERANGE if provided value is not in the sensor's supported range
62+
*/
63+
static inline int emul_sensor_backend_set_channel(const struct emul *target, enum sensor_channel ch,
64+
q31_t value, int8_t shift)
65+
{
66+
if (!target || !target->backend_api) {
67+
return -ENOTSUP;
68+
}
69+
70+
struct emul_sensor_backend_api *api = (struct emul_sensor_backend_api *)target->backend_api;
71+
72+
if (api->set_channel) {
73+
return api->set_channel(target, ch, value, shift);
74+
}
75+
return -ENOTSUP;
76+
}
77+
78+
/**
79+
* @brief Query an emulator for a channel's supported sample value range and tolerance
80+
*
81+
* @param target Pointer to emulator instance to operate on
82+
* @param ch The channel to request info for. If \p ch is unsupported, return `-ENOTSUP`
83+
* @param[out] lower Minimum supported sample value in SI units, fixed-point format
84+
* @param[out] upper Maximum supported sample value in SI units, fixed-point format
85+
* @param[out] epsilon Tolerance to use comparing expected and actual values to account for rounding
86+
* and sensor precision issues. This can usually be set to the minimum sample value step
87+
* size. Uses SI units and fixed-point format.
88+
* @param[out] shift The shift value (scaling factor) associated with \p lower, \p upper, and
89+
* \p epsilon.
90+
*
91+
* @return 0 if successful
92+
* @return -ENOTSUP if no backend API or if channel not supported by emul
93+
*
94+
*/
95+
static inline int emul_sensor_backend_get_sample_range(const struct emul *target,
96+
enum sensor_channel ch, q31_t *lower,
97+
q31_t *upper, q31_t *epsilon, int8_t *shift)
98+
{
99+
if (!target || !target->backend_api) {
100+
return -ENOTSUP;
101+
}
102+
103+
struct emul_sensor_backend_api *api = (struct emul_sensor_backend_api *)target->backend_api;
104+
105+
if (api->get_sample_range) {
106+
return api->get_sample_range(target, ch, lower, upper, epsilon, shift);
107+
}
108+
return -ENOTSUP;
109+
}
110+
111+
/**
112+
* @}
113+
*/

tests/drivers/build_all/sensor/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ cmake_minimum_required(VERSION 3.20.0)
44
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
55
project(build_all)
66

7-
FILE(GLOB app_sources src/*.c)
8-
target_sources(app PRIVATE ${app_sources})
7+
# Exclude main when running the generic test because ztest supplies its own
8+
target_sources_ifndef(CONFIG_GENERIC_SENSOR_TEST app PRIVATE src/main.c)
9+
target_sources_ifdef(CONFIG_GENERIC_SENSOR_TEST app PRIVATE src/generic_test.c)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) 2023 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config GENERIC_SENSOR_TEST
5+
bool "Compile and run the generic sensor tests"
6+
depends on ZTEST && ZTEST_NEW_API
7+
help
8+
Enables building and running the generic sensor test suite that will
9+
iterate through the device tree and run sample path tests on any
10+
sensor that supports the backend sensor emulator API.
11+
12+
config GENERIC_SENSOR_TEST_NUM_EXPECTED_VALS
13+
int "Number of expected values to use in test"
14+
default 5
15+
depends on GENERIC_SENSOR_TEST
16+
help
17+
Controls the number of expected values to use in the generic sensor
18+
test, interpolated from the sensor's reported lower and upper sample
19+
range boundaries. The test will run one iteration for each expected
20+
value. For example, if GENERIC_TEST_NUM_EXPECTED_VALS == 5, and the
21+
sensor range is 0..100, the test will run 5 times with expected values
22+
0, 25, 50, 75, and 100. These iterations are run in parallel.
23+
24+
source "Kconfig.zephyr"

0 commit comments

Comments
 (0)