Now that we have examined the devicetree, device driver model, and the GPIO generic API, let’s dissect the blinky
sample program to understand how it works.
In the following paragraphs, we will examine the blinky
sample line by line to understand how this program is working. The blinky
sample comes with the nRF Connect SDK and can be found at this location: <install_path>\zephyr\samples\basic\blinky
.
1. Include modules
The blinky
sample uses the following modules in the nRF Connect SDK:
<zephyr/
kernel.h>
for the sleep function k_msleep()
. This header is called the Public kernel API header.<zephyr/drivers/gpio.h>
for the structure gpio_dt_spec, the macros GPIO_DT_SPEC_GET(), and the functions, gpio_is_ready_dt(), gpio_pin_configure_dt()
and gpio_pin_toggle_dt()
.#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
C2. Define the node identifier
The line below uses the devicetree macro DT_ALIAS()
to get the node identifier symbol LED0_NODE
, which will represent LED1 (node led_0
). Remember from the Devicetree section that led_0
node is defined in the devicetree of the DK. LED0_NODE
is now the source code symbol that represents the hardware for LED1.
The DT_ALIAS()
macro gets the node identifier from the node’s alias, which as we saw in the Devicetree section, is led0
.
#define LED0_NODE DT_ALIAS(led0) // LED0_NODE = led0 defined in the .dts file
CThere are many ways to retrieve the node identifier. The macros DT_PATH()
, DT_NODELABEL()
, DT_ALIAS()
, and DT_INST()
all return the node identifier, based on various parameters.
3. Retrieve the device pointer, pin number, and configuration flags.
The macro call GPIO_DT_SPEC_GET()
returns the structure gpio_dt_spec led
, which contains the device pointer for node led_0
as well as the pin number and associated configuration flags. The node identifier LED0_NODE
, defined in the previous step, has this information embedded inside its gpios
property. Note the second parameter gpios
, the name of the property containing all this information.
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
CNow let’s examine main()
.
4. Verify that the device is ready for use
As we mentioned before, we must pass the device pointer of the gpio_dt_spec , in this case led
, to gpio_is_ready_dt()
, to verify that the device is ready for use.
if (!gpio_is_ready_dt(&led)) {
return 0;
}
C5. Configure the GPIO pin
The generic GPIO API function gpio_pin_configure_dt()
is used to configure the GPIO pin associated with led
as an output (active low) and initializes it to a logic 1, as explained in the GPIO Generic API section.
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return;
}
C6. Continuously toggle the GPIO pin
Finally, the blinky
main function will enter an infinite loop where we continuously toggle the GPIO pin using gpio_pin_toggle_dt()
. Note that in every iteration, we are also calling the kernel service function k_msleep()
, which puts the main function to sleep for 1 second, resulting in the blinking behavior at 1-second intervals.
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return;
}
k_msleep(SLEEP_TIME_MS);
}
CNow that we have examined the devicetree, device driver model, and the GPIO generic API, let’s dissect the blinky
sample program to understand how it works.
In the following paragraphs, we will examine the blinky
sample line by line to understand how this program is working. The blinky
sample comes with the nRF Connect SDK and can be found at this location: <install_path>\zephyr\samples\basic\blinky
.
1. Include modules
The blinky
sample uses the following modules in the nRF Connect SDK:
<zephyr/
kernel.h>
for the sleep function k_msleep()
. This header is called the Public kernel API header.<zephyr/drivers/gpio.h>
for the structure gpio_dt_spec, the macros GPIO_DT_SPEC_GET(), and the functions, gpio_is_ready_dt(), gpio_pin_configure_dt()
and gpio_pin_toggle_dt()
.#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
C2. Define the node identifier
The line below uses the devicetree macro DT_ALIAS()
to get the node identifier symbol LED0_NODE
, which will represent LED1 (node led_0
). Remember from the Devicetree section that led_0
node is defined in the devicetree of the DK. LED0_NODE
is now the source code symbol that represents the hardware for LED1.
The DT_ALIAS()
macro gets the node identifier from the node’s alias, which as we saw in the Devicetree section, is led0
.
#define LED0_NODE DT_ALIAS(led0) // LED0_NODE = led0 defined in the .dts file
CThere are many ways to retrieve the node identifier. The macros DT_PATH()
, DT_NODELABEL()
, DT_ALIAS()
, and DT_INST()
all return the node identifier, based on various parameters.
3. Retrieve the device pointer, pin number, and configuration flags.
The macro call GPIO_DT_SPEC_GET()
returns the structure gpio_dt_spec led
, which contains the device pointer for node led_0
as well as the pin number and associated configuration flags. The node identifier LED0_NODE
, defined in the previous step, has this information embedded inside its gpios
property. Note the second parameter gpios
, the name of the property containing all this information.
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
CNow let’s examine main()
.
4. Verify that the device is ready for use
As we mentioned before, we must pass the device pointer of the gpio_dt_spec , in this case led
, to gpio_is_ready_dt()
, to verify that the device is ready for use.
if (!gpio_is_ready_dt(&led)) {
return 0;
}
C5. Configure the GPIO pin
The generic GPIO API function gpio_pin_configure_dt()
is used to configure the GPIO pin associated with led
as an output (active low) and initializes it to a logic 1, as explained in the GPIO Generic API section.
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return;
}
C6. Continuously toggle the GPIO pin
Finally, the blinky
main function will enter an infinite loop where we continuously toggle the GPIO pin using gpio_pin_toggle_dt()
. Note that in every iteration, we are also calling the kernel service function k_msleep()
, which puts the main function to sleep for 1 second, resulting in the blinking behavior at 1-second intervals.
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return;
}
k_msleep(SLEEP_TIME_MS);
}
CNow that we have examined the devicetree, device driver model, and the GPIO generic API, let’s dissect the blinky
sample program to understand how it works.
In the following paragraphs, we will examine the blinky
sample line by line to understand how this program is working. The blinky
sample comes with the nRF Connect SDK and can be downloaded below:
1. Include modules
The blinky
sample uses the following modules in the nRF Connect SDK:
<zephyr.h>
for the sleep function k_msleep()
.<device.h>
for the function device_get_binding()
and the device structure.<devicetree.h>
for the macros DT_ALIAS()
, DT_NODE_HAS_STATUS()
, DT_GPIO_LABEL()
, DT_GPIO_PIN()
, DT_GPIO_FLAGS()
.<drivers/gpio.h>
for the functions gpio_pin_configure()
and gpio_pin_set()
.The header files of these modules are included in main.c
through the following include lines.
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
2. Define the node identifier
The line below uses the devicetree macro DT_ALIAS()
to get the node identifier symbol LED0_NODE
, which will represent LED1 (node led_0
). Remember from the Devicetree section that led_0
node is defined in the devicetree of the DK. LED0_NODE
is now the source code symbol that represents the hardware for LED1.
The DT_ALIAS()
macro gets the node identifier from the node’s alias, which as we saw in the Devicetree section, is led0
.
#define LED0_NODE DT_ALIAS(led0) // LED0_NODE = led0 defined in the .dts file
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Euismod in pellentesque massa placerat duis. Consectetur purus ut faucibus pulvinar elementum integer. Tempor nec feugiat nisl pretium fusce id velit. Sed sed risus pretium quam vulputate. Ultrices vitae auctor eu augue ut lectus arcu bibendum.
3. Extract information from the node identifier
The symbol LED0_NODE
contains information about the GPIO port, PIN, flags, and a label embedded inside its gpios
property. The macro calls below extract this information from the LED0_NODE
. Note the second parameter for these macros gpios
. This is the name of the property containing all this information.
//get the port - in the case of the nRF52833 DK, LED0 = "GPIO_0"
#define LED0 DT_GPIO_LABEL(LED0_NODE, gpios)
//get the pin - in the case of the nRF52833 DK, PIN = 13
#define PIN DT_GPIO_PIN(LED0_NODE, gpios)
//get the flags - in the case of the nRF52833 DK, FLAGS = GPIO_ACTIVE_LOW
#define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios)
4. Catch statement for error handling
The following lines will only be executed if the devicetree file of the selected board does not contain an alias node named led0
, which is not the case for the nRF52833 DK or any other Nordic Semiconductor official development kit, so they are simply skipped.
#error "Unsupported board: led0 devicetree alias is not defined"
#define LED0 ""
#define PIN 0
#define FLAGS 0
Now let’s examine main()
.
5. Define a pointer to the device structure
As we mentioned before, for each hardware instance (i.e peripheral or system block) you plan to access in your application, you need to have a device driver associated with it. In the blinky
example, we only have one peripheral to interact with, which is the GPIO peripheral. Therefore, we need to declare one pointer of type const struct device
to point to its driver implementation as shown below:
const struct device *dev;
6. Extract the device driver implementation
We will use the function device_get_binding()
to get the device driver implementation associated with the GPIO peripheral on the board.
dev = device_get_binding(LED0);
if (dev == NULL) {
return;
}
The device_get_binding()
function expects a string as a parameter to look up the driver implementation associated with the hardware, as explained in the Device Driver Model section.
7. Configure the GPIO pin
The generic GPIO API function gpio_pin_configure()
is used to configure GPIO pin 13
as an output (active low) and initializes it to a logic 1, as explained in the GPIO Generic API section.
ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS);
if (ret < 0) {
return;
}
8. Continuously set GPIO pin value to variable led_is_on
Finally, the blinky
main function will enter an infinite loop where we continuously set GPIO pin 13
to the value of the boolean variable led_is_on
. Note that in every iteration, we are inverting this variable through the logical negation operator (!)
. We are also calling the kernel service function k_msleep()
, which puts the main function to sleep for 1 second, resulting in the blinking behavior at 1-second intervals.
while (1) {
gpio_pin_set(dev, PIN, (int)led_is_on);
led_is_on = !led_is_on;
k_msleep(SLEEP_TIME_MS);
}