nRF Connect SDK Fundamentals – [Lesson 2] – Device driver model – v2.9.0-v2.7.0

In order to interact with a hardware peripheral or a system block, we need to use a device driver (or driver for short), which is software that deals with the low-level details of configuring the hardware the way we want. In the nRF Connect SDK, the driver implementation is highly decoupled from its API. This basically means that we are able to switch out the low-level driver implementation without modifying the application because we can use the same generic API.

This decoupling has many benefits, including a high level of portability, as it makes it possible to use the same code on different boards without needing to manually modify the underlying driver implementation.

The application interacts with the hardware through the generic API by obtaining a device pointer for the hardware in question, using the macro DEVICE_DT_GET() or related macros.

Note

It is also possible to get the device pointer through device_get_binding(), though this is no longer recommended.

As opposed to the previous practice of using device_get_binding() to retrieve the device pointer, using DEVICE_DT_GET() has the benefit of failing at build time if the device was not allocated by the driver, for instance, if it does not exist in the devicetree or has the status disabled.

In addition, unlike device_get_binding(), it does not perform a run-time string comparison, which could impact performance in some situations.

The Zephyr device model is responsible for the association between generic APIs and device driver implementations.

Zephyr device model

The macro DEVICE_DT_GET() has the signature shown below: 

To get the device pointer, you need to pass the devicetree node identifier. As mentioned in the Devicetree section, there are many ways to get the node identifier. Two common ways are by the node label through the macro DT_NODELABEL() and by an alias through the macro DT_ALIAS().

Before using the device pointer, it should be checked using device_is_ready(), which has the following signature:

device_is_ready() will check if the device is ready for use, for instance, if it is properly initialized.

The following code snippet will take the devicetree node identifier returned by DT_NODELABEL() and return a pointer to the device object. Then device_is_ready() verifies that the device is ready for use, i.e. in a state so that it can be used with its standard API.

const struct device *dev;
dev = DEVICE_DT_GET(DT_NODELABEL(uart0));

if (!device_is_ready(dev)) {
    return;
}
C

To use a device driver generic API, you must have a pointer of type const struct device to point to its implementation. You need to do this per peripheral instance. For example, if you have two UART peripherals (&uart0 and &uart1) and you want to use them both, you must have two separate pointers of type const struct device.

In other words, you need to have two different calls to DEVICE_DT_GET().

Important

Most peripheral APIs will have an equivalent to DEVICE_DT_GET() and device_is_ready() that are specific to the peripheral. For example for the GPIO peripheral, there is GPIO_DT_SPEC_GET() and gpio_is_ready_dt().

These are the recommended ways to use the peripheral as they collect more information about the peripheral from the devicetree structure and reduce the need to add peripheral configurations in the application code.

Switch language?

Progress is tracked separately for each language. Switching will continue from your progress in that language or start fresh if you haven't begun.

Your current progress is saved, and you can switch back anytime.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.