Difference between revisions of "Zephyr drivers"

From Wiki at Neela Nurseries
Jump to navigation Jump to search
m (→‎^ Zephyr device tree macros: - drivers that depend on other devices)
m (Add missed formatting)
 
(48 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
<i><b>Keywords:</b>  sensor interrupts : sensor triggers : Zephyr triggers : sensor interrupt support</i>
  
== [[#top|^]] OVERVIEW ==
+
OVERVIEW
  
2022 Q4 notes on Zephyr RTOS drivers, driver frameworks and driver designCurrent projects include work to extend KX132-1211 Zephyr out-of-tree driver, plus work to craft an IIS2DH driver which supports in Zephyr RTOS context both I2C and SPI bus connections for this sensor.
+
This page a stash point to hold Zephyr driver notesZephyr RTOS project includes a range of types of drivers.  First type referenced by this page will be Zephyr's sensor drivers.
  
<!-- comentario -->
+
== [[#top|^]] Zephyr Sensor Model ==
  
== [[#top|^]] Example Zephyr drivers ==
+
Zephyr 4.3.0 device driver documentation:
  
Example apps here offer varying amounts of insight into Zephyr RTOS' framework(s) for device driver development. Of particular interest 2022 Q4 are the ways in which developers use Zephyr device tree macros to obtain pointers to devices, in their mostly C based code, at compile time.  Though not fully explained in Zephyr's extensive documentation it appears that device pointers which can be correctly passed to and used with API function [https://docs.zephyrproject.org/latest/kernel/drivers/index.html#c.device_is_ready device_is_ready()] and macro [https://docs.zephyrproject.org/latest/kernel/drivers/index.html#c.DEVICE_DT_GET DEVICE_DT_GET()] involve pairings of macros, which must be expanded prior to these calls in order to compile correctly.
+
* https://docs.zephyrproject.org/4.3.0/doxygen/html/group__io__interfaces.html
  
*  https://github.com/circuitdojo/pcf85063a
+
Zephyr 4.3.0 sensor model:
  
*  https://docs.zephyrproject.org/latest/services/smf/index.html
+
*  https://docs.zephyrproject.org/4.3.0/doxygen/html/group__sensor__interface.html
  
* https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/drivers/spi_bitbang
+
"Sensor attribute get" function prototype. This prototype reveals Zephyr's enumerations for sensor yyy . . .
  
*  https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/drivers/uart/stm32/single_wire
+
*  https://docs.zephyrproject.org/4.3.0/doxygen/html/group__sensor__interface.html#gaedfdfc71dce702dc1fb1c6e60ff0f73a
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr init_res ./* | grep -v reset | grep init_res
 
./include/zephyr/device.h:416: unsigned int init_res : 8;
 
./include/zephyr/net/dns_resolve.h:459:void dns_init_resolver(void);
 
./include/zephyr/net/dns_resolve.h:462:#define dns_init_resolver(...)
 
./kernel/device.c:84: dev->state->init_res = rc;
 
./kernel/device.c:161: return dev->state->initialized && (dev->state->init_res == 0U);
 
./subsys/net/ip/net_core.c:476: dns_init_resolver();
 
./subsys/net/lib/dns/resolve.c:1428:void dns_init_resolver(void)
 
What's going on in LM77 temperature sensor Kconfig file, on line 10?  Interesting Kconfig stanza contains $(dt_compat_enabled,lm77):
 
  
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/lm77/Kconfig#L10
+
Zephyr's sensor model plays a big role in the design of sensor drivers for this RTOS. Zephyr's [... sensor.h] header defines many things.  Two important enums from this header file are:
  
 +
_ Sensor channel enum _
  
Zephyr sensor API routine `device_is_ready()` is prototyped in [https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/device.h#L710 zephyr/include/zephyr/device.h].  A search for `device_is_ready()` implementation gives:
+
https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/sensor.h#L65
  
<pre>
+
_ Sensor attribute enum _
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr z_device_is_ready ./*
 
./include/zephyr/device.h:782:bool z_device_is_ready(const struct device *dev);
 
./include/zephyr/device.h:803: return z_device_is_ready(dev);
 
./kernel/device.c:108: if (z_device_is_ready(dev) && (dev->name == name)) {
 
./kernel/device.c:114: if (z_device_is_ready(dev) && (strcmp(name, dev->name) == 0)) {
 
./kernel/device.c:151:bool z_device_is_ready(const struct device *dev)
 
</pre>
 
  
The body of this API routine is:
+
*  https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/sensor.h#L342
<pre>
 
bool z_device_is_ready(const struct device *dev)
 
{
 
        /*
 
        * if an invalid device pointer is passed as argument, this call
 
        * reports the `device` as not ready for usage.
 
        */
 
        if (dev == NULL) {
 
                return false;
 
        }
 
  
        return dev->state->initialized && (dev->state->init_res == 0U);
+
Sensor channels generally correspond to readings of a quantity in specific units, like degrees centigrade and acceleration in the x-axis.  Sensor attributes generally corrrespond to configuration settings for a sensor, where these settings modify the way in which readings are taken and reported.
}
 
</pre>
 
  
Having some trouble, however, finding places where `init_res` and `initialized` data members of device state structure get initialized.  Looked recursively in both modules and zephyr source tree.  Looks like there may be one place where this assignment takes place, in `zephyr/kernel/device.c`:
+
For a Zephyr driver to support configuration of a sensor's attributes, it must implement a function of the form:
  
 
<pre>
 
<pre>
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr init_res ./* | grep -v reset | grep init_res
+
sensor_attr_set()
./include/zephyr/device.h:416: unsigned int init_res : 8;
+
int sensor_attr_set(const struct device* dev,
./include/zephyr/net/dns_resolve.h:459:void dns_init_resolver(void);
+
                enum sensor_channel chan,
./include/zephyr/net/dns_resolve.h:462:#define dns_init_resolver(...)
+
                enum sensor_attribute attr,
./kernel/device.c:84: dev->state->init_res = rc;
+
                const struct sensor_value* val)
./kernel/device.c:161: return dev->state->initialized && (dev->state->init_res == 0U);
 
./subsys/net/ip/net_core.c:476: dns_init_resolver();
 
./subsys/net/lib/dns/resolve.c:1428:void dns_init_resolver(void)
 
 
</pre>
 
</pre>
  
Routine which iterates over and initializes all devices needing initialization prior to app code starting:
+
. . . whose typedef is at https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/sensor.h#L431.
  
<pre>
+
== [[#top|^]] Bosch BMI323 Driver Example ==
54 void z_sys_init_run_level(int32_t level)
 
55 {
 
56        static const struct init_entry *levels[] = {
 
57                __init_PRE_KERNEL_1_start,
 
58                __init_PRE_KERNEL_2_start,
 
59                __init_POST_KERNEL_start,
 
60                __init_APPLICATION_start,
 
61 #ifdef CONFIG_SMP
 
62                __init_SMP_start,
 
63 #endif
 
64                /* End marker */
 
65                __init_end,
 
66        };
 
67        const struct init_entry *entry;
 
68
 
69        for (entry = levels[level]; entry < levels[level+1]; entry++) {
 
70                const struct device *dev = entry->dev;
 
71                int rc = entry->init(dev);
 
72
 
73                if (dev != NULL) {
 
74                        /* Mark device initialized.  If initialization
 
75                          * failed, record the error condition.
 
76                          */
 
77                        if (rc != 0) {
 
78                                if (rc < 0) {
 
79                                        rc = -rc;
 
80                                }
 
81                                if (rc > UINT8_MAX) {
 
82                                        rc = UINT8_MAX;
 
83                                }
 
84                                dev->state->init_res = rc;
 
85                        }
 
86                        dev->state->initialized = true;
 
87                }
 
88        }
 
89 }
 
</pre>
 
  
Sensor init priority:
+
The Bosch BMI323 accelerometer magnetometer combined sensor has a driver in Zephyr 4.3.0.  The driver has just a few source files with nearly all driver functions defined in bmi323.c.  The driver follows Zephyr's sensor model, and entails a notion of "set" and "get" functions for sensor attributes.  In this context attributes are most often sensor configuration registers.  To configure the sensor for particular operating modes is to "set its attributes" in Zephyr sensor model terminology.  This driver also implements "get" and "fetch" functions for Zephyr supported sensor "channels".  Each channel maps to a type of reading the sensor can take, for example x-axis acceleration, y-axis magnetometer reading, die temperature and similar.
  
<pre>
+
=== [[#top|^]] Attribute Setters ===
ted@localhost1:~/projects-sandbox/workspace-for-nexus/zephyr$ grep -nr SENSOR_INIT_PRIORITY ./* | grep -v CONFIG_SENSOR | grep SENSOR_INIT_PRIORITY
 
./boards/arm/thingy52_nrf52832/board.c:33:#error BOARD_CCS_VDD_PWR_CTRL_INIT_PRIORITY must be lower than SENSOR_INIT_PRIORITY
 
./boards/arm/thingy52_nrf52832/Kconfig:15:   BOARD_VDD_PWR_CTRL_INIT_PRIORITY, but smaller than SENSOR_INIT_PRIORITY.
 
./drivers/sensor/Kconfig:17:config SENSOR_INIT_PRIORITY
 
</pre>
 
  
 +
This driver implements its sensor attribute setter function in bmi323.c in two places.  The first place is a sensor API struct which the driver declares.  This API struct organizes the public API function of the driver.  These APIs implement and honor each a function signature that's type defined in Zephyr's sensor.h header file.
  
<!-- comentario -->
+
The structure is defined starting at about line 384, bmi323.c:384.  Technically the reference to the attribute setter function is not part of this function's definition, but the reference is needed for the driver to function in Zephyr's "sensor model".
  
== [[#top|^]] Zephyr Kernel Level Device Support ==
+
To "set an attribute" equates to writing one or more registers of the given sensor.  This assumes of course a sensor with a digital interface.
  
Interesting code excerpt from `zephy/kernel/device.c`, this following routine is the implementation behind a wrapper like function `device_is_read()`.  But this routine, and sensibly so, doesn't say anything about how a given device is initialized.  It only looks at two data members of a device instance data structure:
+
<i>Excerpt 101 - BMI323 device API definition</i>
  
 
<pre>
 
<pre>
150
+
static DEVICE_API(sensor, bosch_bmi323_api) = {
151 bool z_device_is_ready(const struct device *dev)
+
         .attr_set = bosch_bmi323_driver_api_attr_set,       <-- attribute setter
152 {
+
        .attr_get = bosch_bmi323_driver_api_attr_get,
153         /*
+
         .trigger_set = bosch_bmi323_driver_api_trigger_set,
154          * if an invalid device pointer is passed as argument, this call
+
         .sample_fetch = bosch_bmi323_driver_api_sample_fetch,
155          * reports the `device` as not ready for usage.
+
         .channel_get = bosch_bmi323_driver_api_channel_get,
156          */
+
};
157         if (dev == NULL) {
 
158                return false;
 
159         }
 
160
 
161         return dev->state->initialized && (dev->state->init_res == 0U);
 
162 }
 
163
 
 
</pre>
 
</pre>
  
On a somewhat unrelated note, a couple of important Zephyr documents, including Zephyr device tree macros used to obtain node identifiers:
+
The attribute setter function is defined at about line 1101, bmi323.c:1101:
  
* https://docs.zephyrproject.org/latest/build/dts/api-usage.html#node-identifiers
+
<pre>
 +
static int bosch_bmi323_driver_api_attr_set(const struct device *dev,
 +
    enum sensor_channel chan,
 +
    enum sensor_attribute attr,
 +
    const struct sensor_value *val)
 +
{
 +
struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
 +
int ret;
  
*  https://docs.zephyrproject.org/latest/build/dts/dt-vs-kconfig.html  Device Tree versus Kconfig, a fairly short read
+
k_mutex_lock(&data->lock, K_FOREVER);
  
*  https://docs.zephyrproject.org/1.14.0/guides/kconfig/index.html
+
switch (chan) {
 +
case SENSOR_CHAN_ACCEL_XYZ:
 +
switch (attr) {
 +
case SENSOR_ATTR_SAMPLING_FREQUENCY:
 +
ret = bosch_bmi323_driver_api_set_acc_odr(dev, val);
  
 +
break;
  
<!-- comentario -->
+
case SENSOR_ATTR_FULL_SCALE:
 +
ret = bosch_bmi323_driver_api_set_acc_full_scale(dev, val);
  
== [[#top|^]] Understanding Zephyr SPI API ==
+
break;
  
To make use of Zephyr API for SPI bus, we must in part understand how each of the three parameters passed to [https://docs.zephyrproject.org/latest/hardware/peripherals/spi.html#c.spi_read spi_read()] and [https://docs.zephyrproject.org/latest/hardware/peripherals/spi.html#c.spi_write spi_write()] are defined.  Further, when we have a sensor with which we communicate in our Zephyr based app we need to understand how parts of this sensor's data structures in the firmware relate to the SPI bus to which the sensor is connected.  Our sensor driver's code which talks with the sensor via SPI must pass certain SPI bus related pointers to Zephyr's SPI Application Programmers' Interface.
+
case SENSOR_ATTR_FEATURE_MASK:
 +
ret = bosch_bmi323_driver_api_set_acc_feature_mask(dev, val);
  
Another important SPI defining resource in Zephyr RTOS is the header file [https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/spi.h#L379 spi.h#L379].  This file defines the small data structure named `spi_dt_spec`.  It gives us the data member names for a SPI bus peripheral, as Zephyr sees them.
+
break;
  
2022-11-25
+
default:
 +
ret = -ENODEV;
  
The best Zephyr SPI instructions we find so far are in the sample app `spi_bitbang`.  The source file main.c there, sets up RX and TX buffers, and some Zephyr driver structures about these.  There is an interesting design pattern in Zephyr's SPI API:  receive and transmit buffer pairs can be made multiple, in two arrays of these same buffers.  The SPI interface function `spi_tranceive()`, about which spi_write() and spi_read() wrap, steps through the count of write buffers and receive buffers it is passed from calling code.  In this way, a careful choosing of sets of buffers permits app developers to express more complex SPI communications by defining the buffers with needed lengths, and then call the SPI API of Zephyr RTOS just once.
+
break;
 +
}
  
<!-- comentario -->
+
break;
  
== [[#top|^]] Zephyr logging ==
+
case SENSOR_CHAN_GYRO_XYZ:
 +
switch (attr) {
 +
case SENSOR_ATTR_SAMPLING_FREQUENCY:
 +
ret = bosch_bmi323_driver_api_set_gyro_odr(dev, val);
  
Note, in a Zephyr project or an out-of-tree driver, what we have learned here is that LOG_MODULE_REGISTER(project_name) must be called in one -- possibly the first -- of the project's source files.  Any other dot c files from which developers need logging capability must include the function like macro LOG_MODULE_DECLARE().
+
break;
  
*  https://docs.zephyrproject.org/3.0.0/reference/logging/index.html#logging-in-a-module
+
case SENSOR_ATTR_FULL_SCALE:
*  https://docs.zephyrproject.org/3.0.0/reference/logging/index.html#c.LOG_MODULE_DECLARE
+
ret = bosch_bmi323_driver_api_set_gyro_full_scale(dev, val);
  
 +
break;
  
<!-- comentario -->
+
case SENSOR_ATTR_FEATURE_MASK:
 +
ret = bosch_bmi323_driver_api_set_gyro_feature_mask(dev, val);
  
== [[#top|^]] LPC55S69 flexcomm2 and hs_lspi pins conflict ==
+
break;
  
zephyr/boards/arm/lpcxpresso55s69/lpcxpresso55s69-pinctrl.dtsi lines 21 - 28
+
default:
 +
ret = -ENODEV;
  
Note that in spite of the comment above in the dot dtsi file from NXP, SPI communications with an IIS2DH sensor work while UART2 is also in use and working - TMH
+
break;
 +
}
  
<!-- comentario -->
+
break;
  
== [[#top|^]] STMicro device context data structure ==
+
default:
 +
ret = -ENODEV;
  
Symbol `stmdev_ctx_t` is defined in file <b>modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c</b>.  On github this file as of 2022-11-18 located at https://github.com/zephyrproject-rtos/hal_st/tree/master/sensor/stmemsc/iis2dh_STdC/driver'
+
break;
 +
}
  
Data structure `stmdev_ctx_t` defined as follows:
+
k_mutex_unlock(&data->lock);
  
<pre>
+
return ret;
104 /** @addtogroup  Interfaces_Functions
+
}
105  * @brief      This section provide a set of functions used to read and
 
106  *              write a generic register of the device.
 
107  *              MANDATORY: return 0 -> no Error.
 
108  * @{
 
109  *
 
110  */
 
111
 
112 typedef int32_t (*stmdev_write_ptr)(void *, uint8_t, const uint8_t *, uint16_t);
 
113 typedef int32_t (*stmdev_read_ptr)(void *, uint8_t, uint8_t *, uint16_t);
 
114
 
115 typedef struct
 
116 {
 
117  /** Component mandatory fields **/
 
118  stmdev_write_ptr  write_reg;
 
119  stmdev_read_ptr  read_reg;
 
120  /** Customizable optional pointer **/
 
121  void *handle;
 
122 } stmdev_ctx_t;
 
 
</pre>
 
</pre>
  
Parameter lists for the register write and register read functions are nearly the same, with exception of the third parameter.  In the register write function this parameter is qualified with C `const`, as it is only expected to be read by the function using it.
+
=== [[#top|^]] Mutex Use in BMI323 Driver ===
  
 +
BMI323 driver code locks a mutex in seven of the driver routines of bmi323.c.  It's not yet clear why these particular routines are mutex protected but if any of them may be called by interrupt driven code, or two or more threads of execution then this mutual exclusion protection will be critical to avoiding race conditions:
  
 +
1.  line 384: static int bosch_bmi323_driver_api_attr_set(...)
  
With the above definition, an instance of stmdev_cts_t is created for each device with device tree compatible property of 'st_iis2dh', and is assigned in `zephyr/drivers/sensor/iis2dh/iis2dh-i2c.c` as follows:
+
2. line 727: static int bosch_bmi323_driver_api_attr_get(...)
  
<pre>
+
3line 843: static int bosch_bmi323_driver_api_trigger_set(...)
38 stmdev_ctx_t iis2dh_i2c_ctx = {
 
39        .read_reg = (stmdev_read_ptr) iis2dh_i2c_read,
 
40        .write_reg = (stmdev_write_ptr) iis2dh_i2c_write,
 
  41 };
 
42
 
43 int iis2dh_i2c_init(const struct device *dev)
 
44 {
 
45        struct iis2dh_data *data = dev->data;
 
46        const struct iis2dh_device_config *config = dev->config;
 
47
 
48        if (!device_is_ready(config->i2c.bus)) {
 
49                LOG_ERR("Bus device is not ready");
 
50                return -ENODEV;
 
51        }
 
52
 
53        data->ctx = &iis2dh_i2c_ctx;
 
54        data->ctx->handle = (void *)dev;
 
55
 
56        return 0;
 
57 }
 
58 #endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
 
</pre>
 
  
The above init function is called from iis2dh.c:
+
4. line 996: static int bosch_bmi323_driver_api_sample_fetch(const struct device *dev, enum sensor_channel chan)
  
<pre>
+
5. line 1047: static int bosch_bmi323_driver_api_channel_get(...)
232 static int iis2dh_init_interface(const struct device *dev)
 
233 {
 
234        int res;
 
235
 
236 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi)
 
237        res = iis2dh_spi_init(dev);
 
238        if (res) {
 
239                return res;
 
240        }
 
241 #elif DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
 
242        res = iis2dh_i2c_init(dev);
 
243        if (res) {
 
244                return res;
 
245        }
 
246 #else
 
247 #error "BUS MACRO NOT DEFINED IN DTS"
 
248 #endif
 
249
 
250        return 0;
 
251 }
 
252
 
253 static int iis2dh_init(const struct device *dev)
 
254 {
 
255        struct iis2dh_data *iis2dh = dev->data;
 
256        const struct iis2dh_device_config *cfg = dev->config;
 
257        uint8_t wai;
 
258
 
259        if (iis2dh_init_interface(dev)) {
 
260                return -EINVAL;
 
261        }
 
  
</pre>
+
6. line 1150: static void bosch_bmi323_irq_callback_handler(...)
 +
       
 +
7. line 1241: static int bosch_bmi323_pm_action(...)
  
 +
=== [[#top|^]] BMI323 Driver Routines ===
  
 +
All BMI323 driver functions appear to be statically qualified.  A simple text search over bmi323.c gives the list of these functions in the next code excerpt.  Some annotations are added in columns left of the functions to highlight (1) driver functions related to interrupt support and (2) functions which are mutex protected.
  
Pointer *dev has a member named 'data', which in turn has a member named 'ctx', which in turn gets assigned the address of a not named instance of `iis2dh_i2c_ctx`.  This means that we have `dev->data->ctx->read_reg()` and `dev->data->ctx->write_reg` on successful completion of routine iis2dh_i2c_init().  This is reflected in <b>modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c</b>, in the two generalized register read and write functions:
+
<i>Excerpt 102 - BMI323 driver functions</i>
  
 
<pre>
 
<pre>
  39 /**
+
              59:static int bosch_bmi323_bus_init(const struct device *dev)
  40  * @brief  Read generic device register
+
              68:static int bosch_bmi323_bus_read_words(const struct device *dev, uint8_t offset, uint16_t *words,
  41  *
+
              78:static int bosch_bmi323_bus_write_words(const struct device *dev, uint8_t offset, uint16_t *words,
  42  * @param  ctx  read / write interface definitions(ptr)
+
              88:static int32_t bosch_bmi323_lsb_from_fullscale(int64_t fullscale)
  43  * @param  reg  register to read
+
              94:static int64_t bosch_bmi323_value_to_micro(int16_t value, int32_t lsb)
  44  * @param  data  pointer to buffer that store the data read(ptr)
+
            100:static void bosch_bmi323_value_to_sensor_value(struct sensor_value *result, int16_t value,
  45  * @param  len  number of consecutive register to read
+
            111:static bool bosch_bmi323_value_is_valid(int16_t value)
  46  * @retval          interface status (MANDATORY: return 0 -> no Error)
+
            116:static int bosch_bmi323_validate_chip_id(const struct device *dev)
  47  *
+
            134:static int bosch_bmi323_soft_reset(const struct device *dev)
  48  */
+
            152:static int bosch_bmi323_enable_feature_engine(const struct device *dev)
  49 int32_t iis2dh_read_reg(stmdev_ctx_t *ctx, uint8_t reg, uint8_t *data,
+
            178:static int bosch_bmi323_driver_api_set_acc_odr(const struct device *dev,
  50                        uint16_t len)
+
            226:static int bosch_bmi323_driver_api_set_acc_full_scale(const struct device *dev,
  51 {
+
            257:static int bosch_bmi323_driver_api_set_acc_feature_mask(const struct device *dev,
  52  int32_t ret;
+
            280:static int bosch_bmi323_driver_api_set_gyro_odr(const struct device *dev,
  53
+
            328:static int bosch_bmi323_driver_api_set_gyro_full_scale(const struct device *dev,
  54  ret = ctx->read_reg(ctx->handle, reg, data, len);
+
            361:static int bosch_bmi323_driver_api_set_gyro_feature_mask(const struct device *dev,
  55
+
      [mtx]  384:static int bosch_bmi323_driver_api_attr_set(const struct device *dev, enum sensor_channel chan,
  56  return ret;
+
            455:static int bosch_bmi323_driver_api_get_acc_odr(const struct device *dev, struct sensor_value *val)
  57 }
+
            530:static int bosch_bmi323_driver_api_get_acc_full_scale(const struct device *dev,
  58
+
            566:static int bosch_bmi323_driver_api_get_acc_feature_mask(const struct device *dev,
  59 /**
+
            589:static int bosch_bmi323_driver_api_get_gyro_odr(const struct device *dev, struct sensor_value *val)
  60  * @brief Write generic device register
+
            664:static int bosch_bmi323_driver_api_get_gyro_full_scale(const struct device *dev,
  61  *
+
            704:static int bosch_bmi323_driver_api_get_gyro_feature_mask(const struct device *dev,
  62  * @param ctx  read / write interface definitions(ptr)
+
      [mtx] 727:static int bosch_bmi323_driver_api_attr_get(const struct device *dev, enum sensor_channel chan,
  63  * @param  reg  register to write
+
[irq]        796:static int bosch_bmi323_driver_api_trigger_set_acc_drdy(const struct device *dev)
  64  * @param data  pointer to data to write in register reg(ptr)
+
[irq]        806:static int bosch_bmi323_driver_api_trigger_set_acc_motion(const struct device *dev)
  65  * @param  len  number of consecutive register to write
+
[irq] [mtx] 843:static int bosch_bmi323_driver_api_trigger_set(const struct device *dev,
  66  * @retval          interface status (MANDATORY: return 0 -> no Error)
+
            883:static int bosch_bmi323_driver_api_fetch_acc_samples(const struct device *dev)
  67  *
+
            925:static int bosch_bmi323_driver_api_fetch_gyro_samples(const struct device *dev)
  68  */
+
            968:static int bosch_bmi323_driver_api_fetch_temperature(const struct device *dev)
  69 int32_t iis2dh_write_reg(stmdev_ctx_t *ctx, uint8_t reg,
+
      [mtx] 996:static int bosch_bmi323_driver_api_sample_fetch(const struct device *dev, enum sensor_channel chan)
  70                          uint8_t *data,
+
      [mtx] 1047:static int bosch_bmi323_driver_api_channel_get(const struct device *dev, enum sensor_channel chan,
  71                          uint16_t len)
+
            1100:static DEVICE_API(sensor, bosch_bmi323_api) = {
  72 {
+
[irq]      1108:static void bosch_bmi323_irq_callback(const struct device *dev)
  73  int32_t ret;
+
[irq]      1115:static int bosch_bmi323_init_irq(const struct device *dev)
  74
+
[irq]      1139:static int bosch_bmi323_init_int1(const struct device *dev)
  75  ret = ctx->write_reg(ctx->handle, reg, data, len);
+
[irq] [mtx] 1150:static void bosch_bmi323_irq_callback_handler(struct k_work *item)
  76
+
            1164:static int bosch_bmi323_pm_resume(const struct device *dev)
  77  return ret;
+
            1225:static int bosch_bmi323_pm_suspend(const struct device *dev)
  78 }
+
      [mtx] 1241:static int bosch_bmi323_pm_action(const struct device *dev, enum pm_device_action action)
 +
            1271:static int bosch_bmi323_init(const struct device *dev)
 +
            1318:      static struct bosch_bmi323_data bosch_bmi323_data_##inst;                                 \
 +
[irq]      1322:      static void bosch_bmi323_irq_callback##inst(const struct device *dev,                     \
 +
            1328:      static const struct bosch_bmi323_config bosch_bmi323_config_##inst = {                    \
 
</pre>
 
</pre>
  
 +
=== [[#top|^]] Sensor Bus API and Sensor Bus ===
  
. . .
+
The BMI323 sensor provides I2C and SPI digital interfaces. These are in fact the only ways to the talk with this sensor. This is common however for MEMs type accelerometers.  There are code comments in the driver to the effect that I2C is not yet supported but will be added.
  
 +
One strange feature is that the driver bus read() and write() functions implement 16-bit words rather than bytes, even though both I2C and SPI generally use bytes as their granular unit of data.
  
 +
<pre>
 +
178 struct bosch_bmi323_bus_api {
 +
179        /* Read up to multiple words from the BMI323 */
 +
180        int (*read_words)(const void *context, uint8_t offset, uint16_t *words,
 +
181                          uint16_t words_count);
 +
182
 +
183        /* Write up to multiple words to the BLI323 */
 +
184        int (*write_words)(const void *context, uint8_t offset, uint16_t *words,
 +
185                            uint16_t words_count);
 +
186
 +
187        /* Initialize the bus */
 +
188        int (*init)(const void *context);
 +
189 };
 +
190
 +
191 struct bosch_bmi323_bus {
 +
192        const void *context;
 +
193        const struct bosch_bmi323_bus_api *api;
 +
194 };
 +
</pre>
  
https://docs.zephyrproject.org/latest/hardware/peripherals/i2c.html#c.i2c_burst_read_dt
+
=== [[#top|^]] Trigger a.k.a. Interrupt Support ===
 
 
 
 
 
 
<!-- comentario -->
 
 
 
== [[#top|^]] Zephyr interrupt set up code ==
 
 
 
In Zephyr RTOS context, triggers are interrupts which are generated by sensors.  Zephyr constructs a notion of "trigger types" and "trigger channels". Some pages from Zephyr 3.2.0 documentation:
 
  
*  https://docs.zephyrproject.org/latest/hardware/peripherals/sensor.html#triggers
+
BMI323 sensor driver provides interrupt handling and support through the following code constructsThe first routine calls helper functions to write config registers. This and the two helper functions are set up functions. They are not interrupt service handlers nor routines submitted as work at the detection of an interrupt.
* https://docs.zephyrproject.org/latest/hardware/peripherals/sensor.html#c.sensor_trigger_type
 
  
 
+
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/bosch/bmi323/bmi323.c#L843
This section created to help address a challenge we have, setting up a sensor interrupt or Zephyr "trigger" in KX132 driver. While unsure, there is suspicion that the `int_gpio` member of the sensor's `config` data structure may not be getting assigned a GPIO port at all, in an early Zephyr init level at boot time.  In a diagnostic, `cfg->int_gpio.port->name` shows as "=&", which does not look like a valid port name.
 
 
 
KX132's config data structure includes this snippet of code:
 
  
 
<pre>
 
<pre>
  58         uint8_t pm;
+
  843 static int bosch_bmi323_driver_api_trigger_set(const struct device *dev,
  59 #ifdef CONFIG_KX132_TRIGGER
+
844                                                const struct sensor_trigger *trig,
  60 #warning "KX132 1211 driver - compiling gpio_dt_spec instance in struct 'kx132_device_config'"
+
845                                                sensor_trigger_handler_t handler)
  61 // # REF https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/gpio.h#L271
+
846 {
  62         struct gpio_dt_spec int_gpio;
+
847        struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
  63 #endif /* CONFIG_KX132_TRIGGER */
+
848         int ret = -ENODEV;
  64 };
+
  849
 +
  850        k_mutex_lock(&data->lock, K_FOREVER);
 +
851
 +
852        data->trigger = trig;
 +
853        data->trigger_handler = handler;
 +
854
 +
855        switch (trig->chan) {
 +
  856        case SENSOR_CHAN_ACCEL_XYZ:
 +
857                switch (trig->type) {
 +
858                case SENSOR_TRIG_DATA_READY:
 +
859                        ret = bosch_bmi323_driver_api_trigger_set_acc_drdy(dev);
 +
860
 +
861                        break;
 +
862
 +
863                case SENSOR_TRIG_MOTION:
 +
864                        ret = bosch_bmi323_driver_api_trigger_set_acc_motion(dev);
 +
865
 +
866                        break;
 +
867
 +
868                default:
 +
869                        break;
 +
  870                }
 +
871
 +
872                break;
 +
873
 +
874        default:
 +
875                break;
 +
876         }
 +
877
 +
878        k_mutex_unlock(&data->lock);
 +
  879
 +
880        return ret;
 +
  881 }
 
</pre>
 
</pre>
  
In a different sample app, iis2dh-driver-demo, we were able to set up a trigger per the code from Zephyr's `samples/basic/button` app.  The early declarative elements to prepare to use a GPIO pin on the MCU, as an interrupt input line connected to a sensor, are in this sample:
+
The following code excerpts are a structure to organize the BMI323 sensor driver API, following by the driver's callback function.  The callback function is, maybe no here but in a general sense called shortly or as soon as possible after a sensor interrupt such as "data ready", "motion detected" occurs.  The callback is executed according to the relative priority of the thread in which it runs, among threads in the given application.
  
<pre>
+
There is some indirection to unwrap in order for the program to "reach" the address of the callback. In routine `bosch_bmi323_irq_callback()` the callback handler, a routine, exists in *dev->data->callback_work. There seems to be a pattern where Zephyr driver functions declare a local pointer to the `data` member of the passed device structure.
127 #include <zephyr/drivers/gpio.h>  // needed to provide GPIO_DT_SPEC_GET_OR Zephyr device tree macro
 
128
 
129 #define SENSOR_INT1_NODE        DT_ALIAS(sensorinterrupt1)
 
130 #if !DT_NODE_HAS_STATUS(SENSOR_INT1_NODE, okay)
 
131 #error "- DEV 1108 - Could not find in device tree source any sensor interrupt type node!"
 
  132 #endif
 
133 //static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
 
134 static const struct gpio_dt_spec interrupt_line_1 = GPIO_DT_SPEC_GET_OR(SENSOR_INT1_NODE, gpios, {0});
 
  135
 
136 static struct gpio_callback sensor_interrupt_1_cb_data;
 
</pre>
 
  
Device tree overlay expresses the above referenced DTS node alias:
+
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/bosch/bmi323/bmi323.c#L1100
  
 
<pre>
 
<pre>
  5 / {
+
1100 static DEVICE_API(sensor, bosch_bmi323_api) = {
  6    aliases {
+
1101        .attr_set = bosch_bmi323_driver_api_attr_set,
  9         sensorinterrupt1 = &iis2dhint1;
+
1102        .attr_get = bosch_bmi323_driver_api_attr_get,
11    };
+
1103        .trigger_set = bosch_bmi323_driver_api_trigger_set,
12 };
+
1104        .sample_fetch = bosch_bmi323_driver_api_sample_fetch,
 
+
1105         .channel_get = bosch_bmi323_driver_api_channel_get,
65 / {
+
1106 };
66         sensor_interrupts {
+
1107
67                compatible = "gpio-keys";
+
1108 static void bosch_bmi323_irq_callback(const struct device *dev)
68                iis2dhint1: iis2dh_int1 {
+
1109 {
69                        gpios = < &gpio1 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
+
1110         struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
70                };
+
1111
71                /* ... other buttons ... */
+
1112         k_work_submit(&data->callback_work);
72         };
+
1113 }
73 };
 
 
</pre>
 
</pre>
 
In a driver use case, the interrupt line(s) or pins themselves likely won't require a DTS node alias.  These GPIO pins will be expressed as values in the sensor's device tree node.  The property will be one of `irq-gpios`, `drdy-gpios` and similar, depending on the given sensor's device tree bindings file.  Of course, the port controlling the given pins must somewhere in device tree or DTS overlay files be enabled.
 
 
 
  
  
Zephyr interrupt set up code from button sample app . . .
+
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/bosch/bmi323/bmi323.c#L1115
  
 
<pre>
 
<pre>
      MODULE_ID__THREAD_IIS2DH, sensor->state->init_res);
+
1115 static int bosch_bmi323_init_irq(const struct device *dev)
 
+
1116 {
 
+
1117         struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
 
+
1118         struct bosch_bmi323_config *config = (struct bosch_bmi323_config *)dev->config;
// 'button' from samples/basic/button becomes 'interrupt_line_1':
+
1119        int ret;
 
+
1120
// STEP config step
+
1121         ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
         if (!device_is_ready(interrupt_line_1.port)) {
+
1122
                printk("Error: sensor interrupt 1 port '%s' detected as not ready\n",
+
1123         if (ret < 0) {
                      interrupt_line_1.port->name);
+
1124                 return ret;
//                return;
+
1125         }
         }
+
1126
 
+
1127        gpio_init_callback(&data->gpio_callback, config->int_gpio_callback,
// STEP config step
+
1128                            BIT(config->int_gpio.pin));
        rstatus = gpio_pin_configure_dt(&interrupt_line_1, GPIO_INPUT);
+
1129
        if (rstatus != 0) {
+
1130        ret = gpio_add_callback(config->int_gpio.port, &data->gpio_callback);
                printk("Error %d: failed to configure %s pin %d\n",
+
1131
                      rstatus, interrupt_line_1.port->name, interrupt_line_1.pin);
+
1132         if (ret < 0) {
//                return;
+
1133                return ret;
        }
+
1134         }
 
+
1135
// STEP config step
+
1136         return gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
         rstatus = gpio_pin_interrupt_configure_dt(&interrupt_line_1,
+
1137 }
                                              GPIO_INT_EDGE_TO_ACTIVE);
 
         if (rstatus != 0) {  
 
                 printk("Error %d: failed to configure interrupt on %s pin %d\n",
 
                        rstatus, interrupt_line_1.port->name, interrupt_line_1.pin);
 
//                return;
 
         }
 
 
 
// STEP config step
 
//        gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
 
//        gpio_add_callback(button.port, &button_cb_data);
 
//        printk("Set up button at %s pin %d\n", button.port->name, button.pin);
 
 
 
         gpio_init_callback(&sensor_interrupt_1_cb_data, sensor_interrupt_1_cb, BIT(interrupt_line_1.pin));
 
         gpio_add_callback(interrupt_line_1.port, &sensor_interrupt_1_cb_data);
 
         printk("Set up button at %s pin %d\n", interrupt_line_1.port->name, interrupt_line_1.pin);
 
 
 
 
</pre>
 
</pre>
  
Interestingly this demo code gives the following port name after the GPIO based interrupt is successfully configured:
+
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/bosch/bmi323/bmi323.c#L1139
  
 
<pre>
 
<pre>
*** Booting Zephyr OS build zephyr-v3.2.0  ***
+
1139 static int bosch_bmi323_init_int1(const struct device *dev)
- scoreboard - in setter function test val holds 0,
+
1140 {
- scoreboard - setting test val to  5,
+
1141        uint16_t buf;
Success in init of KX132!
+
1142
- DEV 1028 - symbol ST_IIS2DH got assigned 'DT_N_S_soc_S_peripheral_50000000_S_spi_9f000_S_iis2dh_0'
+
1143        buf = IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_LVL, ACT_HIGH) |
- kd_thread_iis2dh - Success finding iis2dh device,
+
1144              IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_OD, PUSH_PULL) |
- kd_thread_iis2dh - sensor device 'iis2dh@0' is ready,
+
1145              IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_OUTPUT_EN, EN);
- kd_thread_iis2dh - INFO:  device name (Zephyr compile time setting) found to be 'iis2dh@0'
+
1146
- kd_thread_iis2dh - INFO:  boot time Zephyr init result value holds 0,
+
1147        return bosch_bmi323_bus_write_words(dev, IMU_BOSCH_BMI323_REG_IO_INT_CTRL, &buf, 1);
INFO: sensor interrupt 1 port 'gpio@1' detected as ready!
+
1148 }
Set up button at gpio@1 pin 9
 
 
</pre>
 
</pre>
  
Name `gpio@1` is definitely not equivalent or like `=&`.  So QUESTION:  where in the run time process, and in the source code of a given out-of-tree driver, is a valid port controller assigned to `const struct device *dev->cfg->int_gpio.port`?
 
 
In Jared Wolff's sgp40 driver there's explicit code to assign values to sensor data structure members which support an enable line.  Line of interest is [https://github.com/circuitdojo/air-quality-wing-zephyr-drivers/blob/main/drivers/sgp40/sgp40.c#L153 sgp40.c line 153].  In this case the line communication is an enable line, not an interrupt, but the needed set up should be equivalent.
 
 
QUESTION:  where and how is STMicro IIS2DH driver achieving the same set up for its "data ready", drdy gpio?
 
 
 
<!-- comentario -->
 
 
== [[#top|^]] C Preprocessor macros ==
 
 
*  https://stackoverflow.com/questions/1489932/how-can-i-concatenate-twice-with-the-c-preprocessor-and-expand-a-macro-as-in-ar
 
 
<!-- comentario -->
 
 
== [[#top|^]] Zephyr device tree macros ==
 
 
Section on Zephyr device tree macros, many defined in devicetree.h.  Also worth noting is [https://docs.zephyrproject.org/3.2.0/build/dts/howtos.html#device-drivers-that-depend-on-other-devices Zephyr 3.2.0 notes on device drivers that depend on other devices].
 
  
<i>Definition of DT_DRV_INST</i>
+
* https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/sensor/bosch/bmi323/bmi323.c#L1150
  
 
<pre>
 
<pre>
2903 /**
+
1150 static void bosch_bmi323_irq_callback_handler(struct k_work *item)
2904  * @defgroup devicetree-inst Instance-based devicetree APIs
+
1151 {
2905  * @ingroup devicetree
+
1152        struct bosch_bmi323_data *data =
2906  * @{
+
1153                CONTAINER_OF(item, struct bosch_bmi323_data, callback_work);
2907  */
+
1154
2908
+
1155        k_mutex_lock(&data->lock, K_FOREVER);
2909 /**
+
1156
2910  * @brief Node identifier for an instance of a `DT_DRV_COMPAT` compatible
+
1157        if (data->trigger_handler != NULL) {
2911  * @param inst instance number
+
1158                data->trigger_handler(data->dev, data->trigger);
2912  * @return a node identifier for the node with `DT_DRV_COMPAT` compatible and
+
1159         }
2913  *         instance number @p inst
+
1160
2914  */
+
1161        k_mutex_unlock(&data->lock);
2915 #define DT_DRV_INST(inst) DT_INST(inst, DT_DRV_COMPAT)
+
1162 }
 
</pre>
 
</pre>
  
 +
== [[#top|^]] References ==
  
<i>Definition of DT_INST</i>
+
<span id="ref--zephyr-interrupts--zephyr-s-issues-s-30133"></span>
 +
1.  https://github.com/zephyrproject-rtos/zephyr/issues/30133
  
<pre>
+
<!-- comentario -->
232 /**
 
233  * @brief Get a node identifier for an instance of a compatible
 
234  *
 
235  * All nodes with a particular compatible property value are assigned
 
236  * instance numbers, which are zero-based indexes specific to that
 
237  * compatible. You can get a node identifier for these nodes by
 
238  * passing DT_INST() an instance number, @p inst, along with the
 
239  * lowercase-and-underscores version of the compatible, @p compat.
 
240  *
 
241  * Instance numbers have the following properties:
 
242  *
 
243  * - for each compatible, instance numbers start at 0 and are contiguous
 
244  * - exactly one instance number is assigned for each node with a compatible,
 
245  *  **including disabled nodes**
 
246  * - enabled nodes (status property is `okay` or missing) are assigned the
 
247  *  instance numbers starting from 0, and disabled nodes have instance
 
248  *  numbers which are greater than those of any enabled node
 
249  *
 
250  * No other guarantees are made. In particular:
 
251  *
 
252  * - instance numbers **in no way reflect** any numbering scheme that
 
253  *  might exist in SoC documentation, node labels or unit addresses,
 
254  *  or properties of the /aliases node (use DT_NODELABEL() or DT_ALIAS()
 
255  *  for those)
 
256  * - there **is no general guarantee** that the same node will have
 
257  *  the same instance number between builds, even if you are building
 
258  *  the same application again in the same build directory
 
259  *
 
260  * Example devicetree fragment:
 
261  *
 
262  * @code{.dts}
 
263  *    serial1: serial@40001000 {
 
264  *            compatible = "vnd,soc-serial";
 
265  *            status = "disabled";
 
266  *            current-speed = <9600>;
 
267  *            ...
 
268  *    };
 
269  *
 
270  *    serial2: serial@40002000 {
 
271  *            compatible = "vnd,soc-serial";
 
272  *            status = "okay";
 
273  *            current-speed = <57600>;
 
274  *            ...
 
275  *    };
 
276  *
 
277  *    serial3: serial@40003000 {
 
278  *            compatible = "vnd,soc-serial";
 
279  *            current-speed = <115200>;
 
280  *            ...
 
281  *    };
 
 
 
282  * @endcode
 
283  *
 
284  * Assuming no other nodes in the devicetree have compatible
 
285  * `"vnd,soc-serial"`, that compatible has nodes with instance numbers
 
286  * 0, 1, and 2.
 
287  *
 
288  * The nodes `serial@40002000` and `serial@40003000` are both enabled, so
 
289  * their instance numbers are 0 and 1, but no guarantees are made
 
290  * regarding which node has which instance number.
 
291  *
 
292  * Since `serial@40001000` is the only disabled node, it has instance
 
293  * number 2, since disabled nodes are assigned the largest instance
 
294  * numbers. Therefore:
 
295  *
 
296  * @code{.c}
 
297  *    // Could be 57600 or 115200. There is no way to be sure:
 
298  *    // either serial@40002000 or serial@40003000 could
 
299  *    // have instance number 0, so this could be the current-speed
 
300  *    // property of either of those nodes.
 
301  *    DT_PROP(DT_INST(0, vnd_soc_serial), current_speed)
 
302  *
 
303  *    // Could be 57600 or 115200, for the same reason.
 
304  *    // If the above expression expands to 57600, then
 
305  *    // this expands to 115200, and vice-versa.
 
306  *    DT_PROP(DT_INST(1, vnd_soc_serial), current_speed)
 
307  *
 
308  *    // 9600, because there is only one disabled node, and
 
309  *    // disabled nodes are "at the the end" of the instance
 
310  *    // number "list".
 
311  *    DT_PROP(DT_INST(2, vnd_soc_serial), current_speed)
 
312  * @endcode
 
313  *
 
314  * Notice how `"vnd,soc-serial"` in the devicetree becomes `vnd_soc_serial`
 
315  * (without quotes) in the DT_INST() arguments. (As usual, `current-speed`
 
316  * in the devicetree becomes `current_speed` as well.)
 
317  *
 
318  * Nodes whose `compatible` property has multiple values are assigned
 
319  * independent instance numbers for each compatible.
 
320  *
 
321  * @param inst instance number for compatible @p compat
 
322  * @param compat lowercase-and-underscores compatible, without quotes
 
323  * @return node identifier for the node with that instance number and
 
324  *        compatible
 
325  */
 
326 #define DT_INST(inst, compat) UTIL_CAT(DT_N_INST, DT_DASH(inst, compat))
 
</pre>
 
  
<!-- comentario -->
 
  
== [[#top|^]] Sensors to seek for Zephyr demo exercising ==
+
<center>
 +
[[#top|- - - top of page - - -]]
 +
</center>
  
[ ]  [https://www.adafruit.com/product/1782 MCP9808] I2C based temperature sensor, [https://www.mouser.com/datasheet/2/268/25095A-15487.pdf datasheet], long lead times at Mouser
 
  
 
<!-- comentario -->
 
<!-- comentario -->

Latest revision as of 04:46, 26 February 2026

Keywords: sensor interrupts : sensor triggers : Zephyr triggers : sensor interrupt support

OVERVIEW

This page a stash point to hold Zephyr driver notes. Zephyr RTOS project includes a range of types of drivers. First type referenced by this page will be Zephyr's sensor drivers.

^ Zephyr Sensor Model

Zephyr 4.3.0 device driver documentation:

Zephyr 4.3.0 sensor model:

"Sensor attribute get" function prototype. This prototype reveals Zephyr's enumerations for sensor yyy . . .

Zephyr's sensor model plays a big role in the design of sensor drivers for this RTOS. Zephyr's [... sensor.h] header defines many things. Two important enums from this header file are:

_ Sensor channel enum _

_ Sensor attribute enum _

Sensor channels generally correspond to readings of a quantity in specific units, like degrees centigrade and acceleration in the x-axis. Sensor attributes generally corrrespond to configuration settings for a sensor, where these settings modify the way in which readings are taken and reported.

For a Zephyr driver to support configuration of a sensor's attributes, it must implement a function of the form:

sensor_attr_set()
int sensor_attr_set(const struct device* dev,
                enum sensor_channel chan,
                enum sensor_attribute attr,
                const struct sensor_value* val)

. . . whose typedef is at https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/sensor.h#L431.

^ Bosch BMI323 Driver Example

The Bosch BMI323 accelerometer magnetometer combined sensor has a driver in Zephyr 4.3.0. The driver has just a few source files with nearly all driver functions defined in bmi323.c. The driver follows Zephyr's sensor model, and entails a notion of "set" and "get" functions for sensor attributes. In this context attributes are most often sensor configuration registers. To configure the sensor for particular operating modes is to "set its attributes" in Zephyr sensor model terminology. This driver also implements "get" and "fetch" functions for Zephyr supported sensor "channels". Each channel maps to a type of reading the sensor can take, for example x-axis acceleration, y-axis magnetometer reading, die temperature and similar.

^ Attribute Setters

This driver implements its sensor attribute setter function in bmi323.c in two places. The first place is a sensor API struct which the driver declares. This API struct organizes the public API function of the driver. These APIs implement and honor each a function signature that's type defined in Zephyr's sensor.h header file.

The structure is defined starting at about line 384, bmi323.c:384. Technically the reference to the attribute setter function is not part of this function's definition, but the reference is needed for the driver to function in Zephyr's "sensor model".

To "set an attribute" equates to writing one or more registers of the given sensor. This assumes of course a sensor with a digital interface.

Excerpt 101 - BMI323 device API definition

static DEVICE_API(sensor, bosch_bmi323_api) = {
        .attr_set = bosch_bmi323_driver_api_attr_set,        <-- attribute setter
        .attr_get = bosch_bmi323_driver_api_attr_get,
        .trigger_set = bosch_bmi323_driver_api_trigger_set,
        .sample_fetch = bosch_bmi323_driver_api_sample_fetch,
        .channel_get = bosch_bmi323_driver_api_channel_get,
};

The attribute setter function is defined at about line 1101, bmi323.c:1101:

static int bosch_bmi323_driver_api_attr_set(const struct device *dev,
					    enum sensor_channel chan,
					    enum sensor_attribute attr,
					    const struct sensor_value *val)
{
	struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
	int ret;

	k_mutex_lock(&data->lock, K_FOREVER);

	switch (chan) {
	case SENSOR_CHAN_ACCEL_XYZ:
		switch (attr) {
		case SENSOR_ATTR_SAMPLING_FREQUENCY:
			ret = bosch_bmi323_driver_api_set_acc_odr(dev, val);

			break;

		case SENSOR_ATTR_FULL_SCALE:
			ret = bosch_bmi323_driver_api_set_acc_full_scale(dev, val);

			break;

		case SENSOR_ATTR_FEATURE_MASK:
			ret = bosch_bmi323_driver_api_set_acc_feature_mask(dev, val);

			break;

		default:
			ret = -ENODEV;

			break;
		}

		break;

	case SENSOR_CHAN_GYRO_XYZ:
		switch (attr) {
		case SENSOR_ATTR_SAMPLING_FREQUENCY:
			ret = bosch_bmi323_driver_api_set_gyro_odr(dev, val);

			break;

		case SENSOR_ATTR_FULL_SCALE:
			ret = bosch_bmi323_driver_api_set_gyro_full_scale(dev, val);

			break;

		case SENSOR_ATTR_FEATURE_MASK:
			ret = bosch_bmi323_driver_api_set_gyro_feature_mask(dev, val);

			break;

		default:
			ret = -ENODEV;

			break;
		}

		break;

	default:
		ret = -ENODEV;

		break;
	}

	k_mutex_unlock(&data->lock);

	return ret;
}

^ Mutex Use in BMI323 Driver

BMI323 driver code locks a mutex in seven of the driver routines of bmi323.c. It's not yet clear why these particular routines are mutex protected but if any of them may be called by interrupt driven code, or two or more threads of execution then this mutual exclusion protection will be critical to avoiding race conditions:

1. line 384: static int bosch_bmi323_driver_api_attr_set(...)

2. line 727: static int bosch_bmi323_driver_api_attr_get(...)

3. line 843: static int bosch_bmi323_driver_api_trigger_set(...)

4. line 996: static int bosch_bmi323_driver_api_sample_fetch(const struct device *dev, enum sensor_channel chan)

5. line 1047: static int bosch_bmi323_driver_api_channel_get(...)

6. line 1150: static void bosch_bmi323_irq_callback_handler(...)

7. line 1241: static int bosch_bmi323_pm_action(...)

^ BMI323 Driver Routines

All BMI323 driver functions appear to be statically qualified. A simple text search over bmi323.c gives the list of these functions in the next code excerpt. Some annotations are added in columns left of the functions to highlight (1) driver functions related to interrupt support and (2) functions which are mutex protected.

Excerpt 102 - BMI323 driver functions

              59:static int bosch_bmi323_bus_init(const struct device *dev)
              68:static int bosch_bmi323_bus_read_words(const struct device *dev, uint8_t offset, uint16_t *words,
              78:static int bosch_bmi323_bus_write_words(const struct device *dev, uint8_t offset, uint16_t *words,
              88:static int32_t bosch_bmi323_lsb_from_fullscale(int64_t fullscale)
              94:static int64_t bosch_bmi323_value_to_micro(int16_t value, int32_t lsb)
             100:static void bosch_bmi323_value_to_sensor_value(struct sensor_value *result, int16_t value,
             111:static bool bosch_bmi323_value_is_valid(int16_t value)
             116:static int bosch_bmi323_validate_chip_id(const struct device *dev)
             134:static int bosch_bmi323_soft_reset(const struct device *dev)
             152:static int bosch_bmi323_enable_feature_engine(const struct device *dev)
             178:static int bosch_bmi323_driver_api_set_acc_odr(const struct device *dev,
             226:static int bosch_bmi323_driver_api_set_acc_full_scale(const struct device *dev,
             257:static int bosch_bmi323_driver_api_set_acc_feature_mask(const struct device *dev,
             280:static int bosch_bmi323_driver_api_set_gyro_odr(const struct device *dev,
             328:static int bosch_bmi323_driver_api_set_gyro_full_scale(const struct device *dev,
             361:static int bosch_bmi323_driver_api_set_gyro_feature_mask(const struct device *dev,
      [mtx]  384:static int bosch_bmi323_driver_api_attr_set(const struct device *dev, enum sensor_channel chan,
             455:static int bosch_bmi323_driver_api_get_acc_odr(const struct device *dev, struct sensor_value *val)
             530:static int bosch_bmi323_driver_api_get_acc_full_scale(const struct device *dev,
             566:static int bosch_bmi323_driver_api_get_acc_feature_mask(const struct device *dev,
             589:static int bosch_bmi323_driver_api_get_gyro_odr(const struct device *dev, struct sensor_value *val)
             664:static int bosch_bmi323_driver_api_get_gyro_full_scale(const struct device *dev,
             704:static int bosch_bmi323_driver_api_get_gyro_feature_mask(const struct device *dev,
      [mtx]  727:static int bosch_bmi323_driver_api_attr_get(const struct device *dev, enum sensor_channel chan,
[irq]        796:static int bosch_bmi323_driver_api_trigger_set_acc_drdy(const struct device *dev)
[irq]        806:static int bosch_bmi323_driver_api_trigger_set_acc_motion(const struct device *dev)
[irq] [mtx]  843:static int bosch_bmi323_driver_api_trigger_set(const struct device *dev,
             883:static int bosch_bmi323_driver_api_fetch_acc_samples(const struct device *dev)
             925:static int bosch_bmi323_driver_api_fetch_gyro_samples(const struct device *dev)
             968:static int bosch_bmi323_driver_api_fetch_temperature(const struct device *dev)
      [mtx]  996:static int bosch_bmi323_driver_api_sample_fetch(const struct device *dev, enum sensor_channel chan)
      [mtx] 1047:static int bosch_bmi323_driver_api_channel_get(const struct device *dev, enum sensor_channel chan,
            1100:static DEVICE_API(sensor, bosch_bmi323_api) = { 
[irq]       1108:static void bosch_bmi323_irq_callback(const struct device *dev)
[irq]       1115:static int bosch_bmi323_init_irq(const struct device *dev)
[irq]       1139:static int bosch_bmi323_init_int1(const struct device *dev)
[irq] [mtx] 1150:static void bosch_bmi323_irq_callback_handler(struct k_work *item)
            1164:static int bosch_bmi323_pm_resume(const struct device *dev)
            1225:static int bosch_bmi323_pm_suspend(const struct device *dev)
      [mtx] 1241:static int bosch_bmi323_pm_action(const struct device *dev, enum pm_device_action action)
            1271:static int bosch_bmi323_init(const struct device *dev)
            1318:       static struct bosch_bmi323_data bosch_bmi323_data_##inst;                                  \
[irq]       1322:       static void bosch_bmi323_irq_callback##inst(const struct device *dev,                      \
            1328:       static const struct bosch_bmi323_config bosch_bmi323_config_##inst = {                     \

^ Sensor Bus API and Sensor Bus

The BMI323 sensor provides I2C and SPI digital interfaces. These are in fact the only ways to the talk with this sensor. This is common however for MEMs type accelerometers. There are code comments in the driver to the effect that I2C is not yet supported but will be added.

One strange feature is that the driver bus read() and write() functions implement 16-bit words rather than bytes, even though both I2C and SPI generally use bytes as their granular unit of data.

178 struct bosch_bmi323_bus_api {
179         /* Read up to multiple words from the BMI323 */
180         int (*read_words)(const void *context, uint8_t offset, uint16_t *words,
181                           uint16_t words_count);
182 
183         /* Write up to multiple words to the BLI323 */
184         int (*write_words)(const void *context, uint8_t offset, uint16_t *words,
185                            uint16_t words_count);
186 
187         /* Initialize the bus */
188         int (*init)(const void *context);
189 };
190 
191 struct bosch_bmi323_bus {
192         const void *context;
193         const struct bosch_bmi323_bus_api *api;
194 };

^ Trigger a.k.a. Interrupt Support

BMI323 sensor driver provides interrupt handling and support through the following code constructs. The first routine calls helper functions to write config registers. This and the two helper functions are set up functions. They are not interrupt service handlers nor routines submitted as work at the detection of an interrupt.

 843 static int bosch_bmi323_driver_api_trigger_set(const struct device *dev,
 844                                                const struct sensor_trigger *trig,
 845                                                sensor_trigger_handler_t handler)
 846 {
 847         struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
 848         int ret = -ENODEV;
 849 
 850         k_mutex_lock(&data->lock, K_FOREVER);
 851 
 852         data->trigger = trig;
 853         data->trigger_handler = handler;
 854 
 855         switch (trig->chan) {
 856         case SENSOR_CHAN_ACCEL_XYZ:
 857                 switch (trig->type) {
 858                 case SENSOR_TRIG_DATA_READY:
 859                         ret = bosch_bmi323_driver_api_trigger_set_acc_drdy(dev);
 860 
 861                         break;
 862 
 863                 case SENSOR_TRIG_MOTION:
 864                         ret = bosch_bmi323_driver_api_trigger_set_acc_motion(dev);
 865 
 866                         break;
 867 
 868                 default:
 869                         break;
 870                 }
 871 
 872                 break;
 873 
 874         default:
 875                 break;
 876         }
 877 
 878         k_mutex_unlock(&data->lock);
 879 
 880         return ret;
 881 }

The following code excerpts are a structure to organize the BMI323 sensor driver API, following by the driver's callback function. The callback function is, maybe no here but in a general sense called shortly or as soon as possible after a sensor interrupt such as "data ready", "motion detected" occurs. The callback is executed according to the relative priority of the thread in which it runs, among threads in the given application.

There is some indirection to unwrap in order for the program to "reach" the address of the callback. In routine `bosch_bmi323_irq_callback()` the callback handler, a routine, exists in *dev->data->callback_work. There seems to be a pattern where Zephyr driver functions declare a local pointer to the `data` member of the passed device structure.

1100 static DEVICE_API(sensor, bosch_bmi323_api) = {
1101         .attr_set = bosch_bmi323_driver_api_attr_set,
1102         .attr_get = bosch_bmi323_driver_api_attr_get,
1103         .trigger_set = bosch_bmi323_driver_api_trigger_set,
1104         .sample_fetch = bosch_bmi323_driver_api_sample_fetch,
1105         .channel_get = bosch_bmi323_driver_api_channel_get,
1106 };
1107 
1108 static void bosch_bmi323_irq_callback(const struct device *dev)
1109 {
1110         struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
1111 
1112         k_work_submit(&data->callback_work);
1113 }


1115 static int bosch_bmi323_init_irq(const struct device *dev)
1116 {
1117         struct bosch_bmi323_data *data = (struct bosch_bmi323_data *)dev->data;
1118         struct bosch_bmi323_config *config = (struct bosch_bmi323_config *)dev->config;
1119         int ret;
1120 
1121         ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
1122 
1123         if (ret < 0) {
1124                 return ret;
1125         }
1126 
1127         gpio_init_callback(&data->gpio_callback, config->int_gpio_callback,
1128                            BIT(config->int_gpio.pin));
1129 
1130         ret = gpio_add_callback(config->int_gpio.port, &data->gpio_callback);
1131 
1132         if (ret < 0) {
1133                 return ret;
1134         }
1135 
1136         return gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLE);
1137 }
1139 static int bosch_bmi323_init_int1(const struct device *dev)
1140 {
1141         uint16_t buf;
1142 
1143         buf = IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_LVL, ACT_HIGH) |
1144               IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_OD, PUSH_PULL) |
1145               IMU_BOSCH_BMI323_REG_VALUE(IO_INT_CTRL, INT1_OUTPUT_EN, EN);
1146 
1147         return bosch_bmi323_bus_write_words(dev, IMU_BOSCH_BMI323_REG_IO_INT_CTRL, &buf, 1);
1148 }


1150 static void bosch_bmi323_irq_callback_handler(struct k_work *item)
1151 {
1152         struct bosch_bmi323_data *data =
1153                 CONTAINER_OF(item, struct bosch_bmi323_data, callback_work);
1154 
1155         k_mutex_lock(&data->lock, K_FOREVER);
1156 
1157         if (data->trigger_handler != NULL) {
1158                 data->trigger_handler(data->dev, data->trigger);
1159         }
1160 
1161         k_mutex_unlock(&data->lock);
1162 }

^ References

1. https://github.com/zephyrproject-rtos/zephyr/issues/30133


- - - top of page - - -