Zephyr Drivers

Device Initialization

Each device are initialized at boot time by their initialization functions with different initialization levels and priorities. The initialization functions with the same level and priorities are called in their dependency order. And devices that depend on other devices cannot be initialized before the devices they depend on are initialized.

Device Dependency

The dependency of each node in the device tree is determined by:

  • The child is dependent on the parent node.

  • The node that references another node (using phandle) is dependent on the referenced node.

And the final order is listed in zephyr/include/generated/zephyr/devicetree_generated.h of the build directory [1].

References

Clock control

STM32 domain clocks

However, for peripherals that support domain clocks, clock source macros STM32_SRC_* and clock selection macros *_SEL(X) are used to determine the clock source for the domain.

For example, to configure FDCAN1 for STM32G474RE to use PLLQ as the clock source, the following code snippet is used:

&fdcan1 {
    clocks = <&rcc STM32_CLOCK_BUS_APB1 0x02000000>,
             <&rcc STM32_SRC_PLL_Q FDCAN_SEL(1)>;
};

STM32_SRC_* is easy to determine, but *_SEL(X) is not. To determine it, you have to refer to the clock configuration register (CCIPR) of the reset and clock control (RCC) section in the reference manual, where the value of X is listed in the table.

Note

The default configurations in stm32*.dtsi may only define bus clock source, but you still have to copy it to your own device tree and add the domain clock of your choice.

Direct Memory Access (DMA)

Direct memory access (DMA) sees very limited support in Zephyr, especially in documentation and samples. Currently only UART and SPI drivers has wide support for DMA, throuth UART async API and SPI vendor specific Kconfig options CONFIG_SPI_.*_DMA (“wider” support of eight vendors), and limited two vendors support for I2C through CONFIG_I2C_.*_DMA.

Note

Support for UART async API can be checked by Kconfig option CONFIG_SERIAL_SUPPORT_ASYNC.

Since unlike UART has native API support for DMA, SPI and I2C drivers may have some creative ways to utilize DMA. For example, for STM32 SPI, DMA is used in sychronous API spi_transceive() (but not in async ones) to context switch out current thread and let DMA handle the data transfer [2].

DMA in STM32

Since STM32 series come with 4 iterations of DMA controllers, their configurations will vary quite a lot. To use DMA, first we have to figure out which DMA channel is connected to the peripheral that we want to enable DAM in the DMA sections in the reference manual. Or for newer STM32 series that has DMA multiplexers, please refer to the DMAMUX section such as table 91 for STM32G4 series.

Note

The device tree configurations for DMA can be referenced from zephyr/tests/drivers/uart/uart_async_api/boards.

DAM with data cache

High performance microcontrollers that have cache such as Cortex-M7 may cause data inconsistency when using DMA since the CPU may read the data from the cache whereas the DMA writes to the memory directly. This is typically resolved by setting the memory region used for DMA buffer to non-cacheable in MPU or simply turning the data cache off (setting CONFIG_DCACHE=n).

In Zephyr, you can get global non-cacheable memory using __nocache macro defined in zephyr/include/zephyr/linker/section_tags.h after enabling the CONFIG_NOCACHE_MEMORY Kconfig option. Or allocate non-cacheable memory using mem_attr_heap_alloc() as described in Zephyr Memory Attributes

In STM32 driver implementation, memory buffer used to store data to transmit/receive is directly used as DMA buffer [3], it requires changing the application to ensure the memory buffer is non-cacheable. If the performance is not a concern, turning the data cache off is a simpler solution.

The use of DMA is advised, but care must be taken to ensure the espected behaviors.

Reference

Battery Backed RAM (BBRAM)

Zephyr provides a battery backed RAM (BBRAM) driver that allows you to store data across system resets through BBRAM API. Depending on the hardware, the data may be persisted even if the main power is lost, being kept by the dedicated battery, hence the name.

However, not all STM32 serise device tree include st,stm32-bbram device that corrsepond to BBRAM. To use it, add it to st,stm32-rtc device in the device tree overlay like so:

&rtc {
    bbram: backup_regs {
        compatible = "st,stm32-bbram";
        st,backup-regs = <32>;
        status = "okay";
    };
};

Where st,backup-regs is the number of backup register of the STM32 and the exact values should refer to the reference manuals.

General Purpose Input/Output (GPIO)

Zephyr provides basic GPIO driver using the GPIO API that can perform basic operations such as digital read, write, and interrupt trigger. However, for more advanced features such as LED effects and button debouncing, you have to rely on higher-level drivers and subsystems. Below are two drivers and subsystems that just do that:

Light Emitting Diode (LED)

Zephyr provides special LED API that controls various kinds of LEDs such as RGB LEDs and LED strips. Through gpio-leds device binding, you can control LEDs connected to GPIOs using the LED API.

Note

Since there may be multiple LEDs defined under the same gpio-leds device, the LED API requires LED number to specify which LED to control. And the LED number of a specific LED is the order it is defined in the gpio-leds device, no matter if the LED is disabled or not [4].

Input

Zephyr provides special input subsystem designed for various kinds of inputs such as key triggers, movement, etc through Input API. It can also be used for debouncing buttons through gpio-keys device binding. However, currently it only supports callbacks APIs with no polling support.

Note

Every children of gpio-keys devices must have a unique zephyr,code property to identify the key. Available options start from INPUT_KEY_RESERVED.

EXIT in STM32

The extended interrupt and event controller (EXIT) in STM32 is used for handling interrupt events from GPIOs. Since every pin number is connected to a specific EXIT line, only one GPIO with the same pin numbers can be used for external interrupt triggers at a time [5]. For example, since PA0 and PB0 share the same pin number, only one of them can be used for external interrupt triggers.

Note

Though EXIT input 0~15 for GPIOs does not map to NVIC IRQ numbers one-to-one (whcih means that they may share the same ISR), when the driver handlers the interrupt, it will check registers of EXIT to determine which pin triggered the interrupt and handle them accordingly [6].

Reference

Pulse Width Modulation (PWM)

For STM32 timers that support PWM generation using complementary output pins (CHxN), STM32_PWM_COMPLEMENTARY flag must be set for that PWM channel in the device tree. For example, to enable complementary output for TIM1 channel 1 in STM32G4 series, the following code snippet is used:

#include <dt-bindings/pwm/stm32_pwm.h>

&tim1 {
    ...

    pwm1: pwm1 {
        pinctrl-0 = <&tim1_ch1n_pa7>;
        pinctrl-names = "default";
        status = "okay";
    };
};
...

&pwmleds {
    compatible = "pwm-leds";
    ...

    pwmled {
        pwms = <&pwm1 1 PWM_MSEC(1) STM32_PWM_COMPLEMENTARY>;
    };
};

Universal Asynchronous Receiver/Transmitter (UART)

STM32 UART provides hardware flow control for both RS232 and RS485 transceivers (using CTS, RTS pins for RS232 and DE pins for RS485). Since the activation / deactivation time of the transceiver takes time, STM32 UART driver provides a feature to delay the transmission of the first bit after toggling the pins. For RS458 transceiver MAX487E that we used, it takes up to 3000ns to finish the transaction [7]. So for a baud rate of 115200, it will take 0.35 bit time. With over sampling of 16 times per bit, it’s 5.5 or minimum 6 sample time, which cooresponds to de-assert-time and de-deassert-time in the device tree.

Reference

CAN Bus

The driver for controller area network (CAN) driver provides a nice feature of figuring out the sync jump width and other parameters for the bus automatically, you only need to provide the baud rate and the sampling point.

Weirdly, maximum baud rate for CAN bus is set to 800kbps in Zephyr [8].

Reference

Secure Digital Input Output (SDIO)

Typically, microcontrollers provide SDIO bus controllers to connect SD cards or MultiMedia cards such as espressif,esp32-sdhc-slot native SDIO controller or SDIO in SPI mode zephyr,sdhc-spi-slot device bindings and they are marked as sd bus in Zephyr and implements the SDHC API. Such API can then be used to connect to SD card using zephyr,sdmmc-disk or MMC using zephyr,mmc-disk device bindings that implements disk access API for file system.

However, currently STM32 drivers for SDIO does not expose the SDHC API, but rather directly defines st,stm32-sdmmc device binging that directly implements the disk access API. This means that STM32 microcontrollers are not able to connect other devices such as WiFi modules that uses SDIO and cannot be tested by tests for SDHC controllers such as tests/drivers/sdhc or tests/subsys/sd/sdmmc which requires generic zephyr,sdmmc-disk binding.

Real Time I/O (RTIO)

RTIO is a set of interfaces inspired by Linux’s io_uring that facilitates multiplexed asynchronous I/O operations. After its adoption in 3.4.0, it has quickly become the norm for defining new APIs for asynchronous I/O operations in Zephyr but currently only includes I2C, SPI, and sensor drivers. Today still very few drivers natively support RTIO (i.e. use DMA or other coprocessors for true asynchronous transaction), threre are fallbacks that wraps the synchronous API to RTIO API for the above three drivers [9] [10] [11].

The official documentation does not provide much information about the use of RTIO, but you can refer to the code and comments in I2C lookpack sample for a sample usage of RTIO and RTIO reference for API documentation.

Work Request

Aside from relaying on interrupts to achieve non-blocking operations, RTIO also provides work request API to dispatch work that requires blocking operations such as the aforementioned fallbacks.

Note

The work request API is neither documented in RTIO documentation nor in a doxygen group that can be referenced from the RTIO group. It’s only available in its file reference.

References

Sensors

Asynchronous API

Sensor asynchronous driver API is built on top of RTIO, and its usage can be referenced from the sensor read and decode documentation.

To create a new sensor driver that support asynchronous API, both the decoder API and the async read initialization (sensor_driver_api.submit) can be referenced from default_rtio_sensor.c. And since default implementation for submit does not support streaming [12], the implementation of it can be referenced from existing drivers such as adxl345_stream.c.

References