Zephyr drivers

From Wiki at Neela Nurseries
Revision as of 21:02, 23 November 2022 by Ted (talk | contribs) (Zephyr sensor interrupts are called "triggers")
Jump to: navigation, search

^ OVERVIEW

2022 Q4 notes on Zephyr RTOS drivers, driver frameworks and driver design. Current 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.


^ Example Zephyr drivers

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 device_is_ready() and macro DEVICE_DT_GET() involve pairings of macros, which must be expanded prior to these calls in order to compile correctly.

What's going on in LM77 temperature sensor Kconfig file, on line 10? Interesting Kconfig stanza contains $(dt_compat_enabled,lm77):


^ Zephyr Kernel Level Device Support

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:

150 
151 bool z_device_is_ready(const struct device *dev)
152 {
153         /*
154          * if an invalid device pointer is passed as argument, this call
155          * reports the `device` as not ready for usage.
156          */
157         if (dev == NULL) {
158                 return false;
159         }
160 
161         return dev->state->initialized && (dev->state->init_res == 0U);
162 }
163 

On a somewhat unrelated note, a couple of important Zephyr documents, including Zephyr device tree macros used to obtain node identifiers:


^ Understanding Zephyr SPI API

To make use of Zephyr API for SPI bus, we must in part understand how each of the three parameters passed to spi_read() and 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.

Another important SPI defining resource in Zephyr RTOS is the header file 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.


^ Zephyr logging


^ LPC55S69 flexcomm2 and hs_lspi pins conflict

zephyr/boards/arm/lpcxpresso55s69/lpcxpresso55s69-pinctrl.dtsi lines 21 - 28

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


^ STMicro device context data structure

Symbol `stmdev_ctx_t` is defined in file modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c. 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'

Data structure `stmdev_ctx_t` defined as follows:

 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;

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.


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:

 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) */

The above init function is called from iis2dh.c:

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         }


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 modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c, in the two generalized register read and write functions:

  39 /**
  40   * @brief  Read generic device register
  41   *
  42   * @param  ctx   read / write interface definitions(ptr)
  43   * @param  reg   register to read
  44   * @param  data  pointer to buffer that store the data read(ptr)
  45   * @param  len   number of consecutive register to read
  46   * @retval          interface status (MANDATORY: return 0 -> no Error)
  47   *
  48   */
  49 int32_t iis2dh_read_reg(stmdev_ctx_t *ctx, uint8_t reg, uint8_t *data,
  50                         uint16_t len)
  51 {
  52   int32_t ret;
  53 
  54   ret = ctx->read_reg(ctx->handle, reg, data, len);
  55 
  56   return ret;
  57 }
  58 
  59 /**
  60   * @brief  Write generic device register
  61   *
  62   * @param  ctx   read / write interface definitions(ptr)
  63   * @param  reg   register to write
  64   * @param  data  pointer to data to write in register reg(ptr)
  65   * @param  len   number of consecutive register to write
  66   * @retval          interface status (MANDATORY: return 0 -> no Error)
  67   *
  68   */
  69 int32_t iis2dh_write_reg(stmdev_ctx_t *ctx, uint8_t reg,
  70                          uint8_t *data,
  71                          uint16_t len)
  72 {
  73   int32_t ret;
  74 
  75   ret = ctx->write_reg(ctx->handle, reg, data, len);
  76 
  77   return ret;
  78 }


. . .


https://docs.zephyrproject.org/latest/hardware/peripherals/i2c.html#c.i2c_burst_read_dt



^ Zephyr interrupt set up code

Some pages from Zephyr 3.2.0 documentation . . .

In Zephyr RTOS context, triggers are interrupts which are generated by sensors. Zephyr constructs a notion of "trigger types" and "trigger channels".


Zephyr interrupt set up code from button sample app . . .

      MODULE_ID__THREAD_IIS2DH, sensor->state->init_res);



// 'button' from samples/basic/button becomes 'interrupt_line_1':

// STEP config step
        if (!device_is_ready(interrupt_line_1.port)) {
                printk("Error: sensor interrupt 1 port '%s' detected as not ready\n",
                       interrupt_line_1.port->name);
//                return;
        }

// STEP config step
        rstatus = gpio_pin_configure_dt(&interrupt_line_1, GPIO_INPUT);
        if (rstatus != 0) { 
                printk("Error %d: failed to configure %s pin %d\n",
                       rstatus, interrupt_line_1.port->name, interrupt_line_1.pin);
//                return;
        }

// STEP config step
        rstatus = gpio_pin_interrupt_configure_dt(&interrupt_line_1,
                                              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);


^ C Preprocessor macros