Difference between revisions of "Zephyr device driver model"

From Wiki at Neela Nurseries
Jump to: navigation, search
m
m (^ LIS2DH driver files)
Line 291: Line 291:
 
== [[#top|^]] LIS2DH driver files ==
 
== [[#top|^]] LIS2DH driver files ==
  
Beginning with lis2dh_i2c.c, here we examine where Zephyr driver files for this accelerometer are located in Zephyr's source file project space.  We'll also look at how routines and pound defines are factored across these files.
+
Beginning with lis2dh_i2c.c, here we examine where Zephyr driver files for this accelerometer are located in Zephyr's source file project space.  We'll also look at how routines and pound defines are factored across these files. Beginning with <code>lis2dh_i2c.c</code> file and its final twenty or so lines, we have a specific sensor API routine, a structure which entails function pointers to all the LIS2DH driver routines in this source file, and finally an initializing function for this driver:
  
 +
<pre>
 +
static int lis2dh_i2c_update_reg(const struct device *dev, uint8_t reg_addr,
 +
                                  uint8_t mask, uint8_t value)
 +
{
 +
        struct lis2dh_data *data = dev->data;
 +
        const struct lis2dh_config *cfg = dev->config;
 +
 +
        return i2c_reg_update_byte(data->bus,
 +
                                  cfg->bus_cfg.i2c_slv_addr,
 +
                                  reg_addr, mask, value);
 +
}
 +
 +
static const struct lis2dh_transfer_function lis2dh_i2c_transfer_fn = {
 +
        .read_data = lis2dh_i2c_read_data,
 +
        .write_data = lis2dh_i2c_write_data,
 +
        .read_reg  = lis2dh_i2c_read_reg,
 +
        .write_reg  = lis2dh_i2c_write_reg,
 +
        .update_reg = lis2dh_i2c_update_reg,
 +
};
 +
 +
int lis2dh_i2c_init(const struct device *dev)
 +
{
 +
        struct lis2dh_data *data = dev->data;
 +
 +
        data->hw_tf = &lis2dh_i2c_transfer_fn;
 +
 +
        return 0;
 +
}
 +
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
 +
</pre>
 +
 +
We also can see the closing pre-processor conditional directive, which pairs with a test at compile time which only compiles this driver API when at least one instance of the sensor is indicated in developer code:
 +
 +
<pre>
 +
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
 +
 +
LOG_MODULE_DECLARE(lis2dh, CONFIG_SENSOR_LOG_LEVEL);
 +
 +
static int lis2dh_i2c_read_data(const struct device *dev, uint8_t reg_addr,
 +
                                uint8_t *value, uint8_t len)
 +
{
 +
        struct lis2dh_data *data = dev->data;
 +
        const struct lis2dh_config *cfg = dev->config;
 +
 +
  .
 +
  .
 +
  .
 +
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
 +
</pre>
 +
 +
Ok, so already a fair amount going on here.  An important point to note and remember is that routines in <code>lis2dh_i2c.c</code> . . .
  
 
<!-- comment -->
 
<!-- comment -->

Revision as of 13:24, 15 August 2021

Keywords: zephyr device drivers Zephyr device driver model Zephyr device drivers getting started LIS2DH driver study


- OVERVIEW - Challenged by Zephyr's way of writing device drivers, capturing some notes here on a study of a Zephyr example project which talks to LIS2DH accelerometer on a Sparkfun Thing Plus board.


^ Early Research and SDK Used

A couple of Zephyr driver tutorials and posts worth noting. Casting net wider for Zephyr device driver "How To" articles, find Jared Wolff's blog post:

Here is a link to a tutorial, part three of a series, which may be helpful:

And before getting too far into Zephyr drivers, important to note the SDK we're using, obtained by git cloning Nordic's ncs v1.6.1 SDK and then executing `west init && west zephyr-export` commands. Turns out west like git supports a status query command:

ted@localhost:~/projects/embedded/ncs$ west status

=== status of manifest (nrf):
HEAD detached at v1.6.1
nothing to commit, working tree clean
=== status of zephyr (zephyr):
Refresh index: 100% (16309/16309), done.
HEAD detached at refs/heads/manifest-rev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   samples/basic/blinky/prj.conf
	modified:   samples/sensor/lis2dh/prj.conf

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	samples/basic/blinky/z--build-messages.txt
	samples/z--sandbox/

no changes added to commit (use "git add" and/or "git commit -a")
=== status of openthread (modules/lib/openthread):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mcuboot (bootloader/mcuboot):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nrfxlib (nrfxlib):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of trusted-firmware-m (modules/tee/tfm):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tfm-mcuboot (modules/tee/tfm-mcuboot):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of matter (modules/lib/matter):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cjson (modules/lib/cjson):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cmock (test/cmock):
HEAD detached at refs/heads/manifest-rev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   vendor/unity (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
=== status of unity (test/cmock/vendor/unity):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbedtls-nrf (mbedtls):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nanopb (modules/lib/nanopb):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of Alexa-Gadgets-Embedded-Sample-Code (modules/alexa-embedded):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbed-cloud-client (modules/lib/pelion-dm):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cddl-gen (modules/lib/cddl-gen):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of memfault-firmware-sdk (modules/lib/memfault-firmware-sdk):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cmsis (modules/hal/cmsis):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of canopennode (modules/lib/canopennode):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of civetweb (modules/lib/civetweb):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of fatfs (modules/fs/fatfs):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of hal_nordic (modules/hal/nordic):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of hal_st (modules/hal/st):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of libmetal (modules/hal/libmetal):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of lvgl (modules/lib/gui/lvgl):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbedtls (modules/crypto/mbedtls):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mcumgr (modules/lib/mcumgr):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of net-tools (tools/net-tools):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of open-amp (modules/lib/open-amp):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of loramac-node (modules/lib/loramac-node):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of segger (modules/debug/segger):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tinycbor (modules/lib/tinycbor):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tinycrypt (modules/crypto/tinycrypt):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of littlefs (modules/fs/littlefs):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mipi-sys-t (modules/debug/mipi-sys-t):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nrf_hw_models (modules/bsim_hw_models/nrf_hw_models):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of TraceRecorderSource (modules/debug/TraceRecorder):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of edtt (tools/edtt):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean

ted@localhost:~/projects/embedded/ncs


Excerpt from Zephyr project drivers documentation:

    Driver Data Structures The device initialization macros populate some data structures at build time which are split into read-only and runtime-mutable parts. At a high level we have:
     struct device {
          const char *name;
          const void *config;
          const void *api;
          void * const data;
     };

Excerpt from file ~/ncs/zephyr/include/drivers/sensor.h:

382 /**
383  * @brief Set an attribute for a sensor
384  *
385  * @param dev Pointer to the sensor device
386  * @param chan The channel the attribute belongs to, if any.  Some
387  * attributes may only be set for all channels of a device, depending on
388  * device capabilities.
389  * @param attr The attribute to set
390  * @param val The value to set the attribute to
391  *
392  * @return 0 if successful, negative errno code if failure.
393  */
394 __syscall int sensor_attr_set(const struct device *dev,
395                               enum sensor_channel chan,
396                               enum sensor_attribute attr,
397                               const struct sensor_value *val);
398 
399 static inline int z_impl_sensor_attr_set(const struct device *dev,
400                                          enum sensor_channel chan,
401                                          enum sensor_attribute attr,
402                                          const struct sensor_value *val)
403 {
404         const struct sensor_driver_api *api =
405                 (const struct sensor_driver_api *)dev->api;
406 
407         if (api->attr_set == NULL) {
408                 return -ENOSYS;
409         }
410 
411         return api->attr_set(dev, chan, attr, val);
412 }
413 

From these two code excerpts the big question is, how is dev->api assigned in early configuring code for an I2C based sensor?


./include/drivers/sensor.h:61: SENSOR_CHAN_ACCEL_XYZ, . . . hmm, this is part of a large enumeration:

 53 enum sensor_channel {
 54         /** Acceleration on the X axis, in m/s^2. */
 55         SENSOR_CHAN_ACCEL_X,
 56         /** Acceleration on the Y axis, in m/s^2. */
 57         SENSOR_CHAN_ACCEL_Y,
 58         /** Acceleration on the Z axis, in m/s^2. */
 59         SENSOR_CHAN_ACCEL_Z,
 60         /** Acceleration on the X, Y and Z axes. */
 61         SENSOR_CHAN_ACCEL_XYZ,
 62         /** Angular velocity around the X axis, in radians/s. */
 63         SENSOR_CHAN_GYRO_X,
 64         /** Angular velocity around the Y axis, in radians/s. */
 65         SENSOR_CHAN_GYRO_Y,
 66         /** Angular velocity around the Z axis, in radians/s. */
 67         SENSOR_CHAN_GYRO_Z,
 68         /** Angular velocity around the X, Y and Z axes. */
 69         SENSOR_CHAN_GYRO_XYZ,
   .
   .
   .


edit point

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/boards/arm/sparkfun_thing_plus_nrf9160$ nm -s libboards__arm__sparkfun_thing_plus_nrf9160.a 

board.c.obj:
0000002c t $d
00000000 r $d
00000000 t $t
00000001 t board_sparkfun_thing_plus_nrf9160_init
00000000 r __init_sys_init_board_sparkfun_thing_plus_nrf9160_init0
         U z_impl_device_get_binding
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/boards/arm/sparkfun_thing_plus_nrf9160$


File noted:

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/dev_handles.c
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls *.c
dev_handles.c  isr_tables.c
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls *.h
ls: cannot access '*.h': No such file or directory

Example project build directory noted, this dir auto-gen'd:

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls include/generated/
app_data_alignment.ld  autoconf.h            driver-validation.h  otype-to-size.h     snippets-ram-sections.ld  snippets-sections.ld  version.h
app_smem_aligned.ld    device_extern.h       kobj-types-enum.h    otype-to-str.h      snippets-rodata.ld        syscall_dispatch.c
app_smem.ld            devicetree_fixups.h   ncs_version.h        pm_config.h         snippets-rom-start.ld     syscall_list.h
app_smem_unaligned.ld  devicetree_unfixed.h  offsets.h            snippets-noinit.ld  snippets-rwdata.ld        syscalls


^ Kionix KX132

Useful datasheets and documents for Kionix accelerometer KX132-1211:


^ LIS2DH driver files

Beginning with lis2dh_i2c.c, here we examine where Zephyr driver files for this accelerometer are located in Zephyr's source file project space. We'll also look at how routines and pound defines are factored across these files. Beginning with lis2dh_i2c.c file and its final twenty or so lines, we have a specific sensor API routine, a structure which entails function pointers to all the LIS2DH driver routines in this source file, and finally an initializing function for this driver:

static int lis2dh_i2c_update_reg(const struct device *dev, uint8_t reg_addr,
                                  uint8_t mask, uint8_t value)
{
        struct lis2dh_data *data = dev->data;
        const struct lis2dh_config *cfg = dev->config;

        return i2c_reg_update_byte(data->bus,
                                   cfg->bus_cfg.i2c_slv_addr,
                                   reg_addr, mask, value);
}

static const struct lis2dh_transfer_function lis2dh_i2c_transfer_fn = { 
        .read_data = lis2dh_i2c_read_data,
        .write_data = lis2dh_i2c_write_data,
        .read_reg  = lis2dh_i2c_read_reg,
        .write_reg  = lis2dh_i2c_write_reg,
        .update_reg = lis2dh_i2c_update_reg,
};

int lis2dh_i2c_init(const struct device *dev)
{
        struct lis2dh_data *data = dev->data;

        data->hw_tf = &lis2dh_i2c_transfer_fn;

        return 0;
}
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */

We also can see the closing pre-processor conditional directive, which pairs with a test at compile time which only compiles this driver API when at least one instance of the sensor is indicated in developer code:

 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)

LOG_MODULE_DECLARE(lis2dh, CONFIG_SENSOR_LOG_LEVEL);

static int lis2dh_i2c_read_data(const struct device *dev, uint8_t reg_addr,
                                 uint8_t *value, uint8_t len)
{
        struct lis2dh_data *data = dev->data;
        const struct lis2dh_config *cfg = dev->config;

   .
   .
   .
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */

Ok, so already a fair amount going on here. An important point to note and remember is that routines in lis2dh_i2c.c . . .