No ACL setup yet! Denying access to everyone.
en:edu:zmitac:s2:aih:lab:nrf52gpio
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| en:edu:zmitac:s2:aih:lab:nrf52gpio [2023/11/22 11:07] – [Concept of the Device Tree] ktokarz | en:edu:zmitac:s2:aih:lab:nrf52gpio [2025/11/14 10:59] (current) – [Tasks for the laboratory on GPIO] ktokarz | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Laboratory on GPIO use in Zephyr OS ====== | ====== Laboratory on GPIO use in Zephyr OS ====== | ||
| GPIOs in Nordic Semiconductor nRF52840 can be handled by software with polling, with interrupts or using direct hardware connection with PPI (Programmable Peripheral Interconnect). In the first part of the laboratory, we will handle GPIO events with the software. | GPIOs in Nordic Semiconductor nRF52840 can be handled by software with polling, with interrupts or using direct hardware connection with PPI (Programmable Peripheral Interconnect). In the first part of the laboratory, we will handle GPIO events with the software. | ||
| - | The first example named “blinky” ((https:// | + | The first example named “blinky” ((https:// |
| ===== Concept of the Device Tree ===== | ===== Concept of the Device Tree ===== | ||
| Line 80: | Line 80: | ||
| - flags | - flags | ||
| </ | </ | ||
| - | ===== Use of the device defined in the device tree ===== | + | ===== Blinky - how to use the LED ===== |
| - | To use the hardware device in the program first there is a need to check if this device is defined in the device tree, and if so, set its main parameters. | + | To use the hardware device in the program first there is a need to check if this device is defined in the device tree, and if so, set its main parameters. |
| + | < | ||
| + | /* The devicetree node identifier for the " | ||
| + | #define LED0_NODE DT_ALIAS(led0) | ||
| + | #if DT_NODE_HAS_STATUS(LED0_NODE, | ||
| + | #define LED0 DT_GPIO_LABEL(LED0_NODE, | ||
| + | #define PIN DT_GPIO_PIN(LED0_NODE, | ||
| + | #define FLAGS DT_GPIO_FLAGS(LED0_NODE, | ||
| + | #else | ||
| + | /* A build error here means your board isn't set up to blink an LED. */ | ||
| + | #error " | ||
| + | #define LED0 "" | ||
| + | #define PIN 0 | ||
| + | #define FLAGS 0 | ||
| + | #endif | ||
| + | </ | ||
| + | * **DT_ALIAS** macro takes the identifier and converts it to c compatible string (special characters are replaced with underscore symbols and all characters are converted to lowercase). | ||
| + | * **DT_NODE_HAS_STATUS** checks if the status of the node is OK. | ||
| + | If the status is OK the label, the number of the pin, and flags are set as defined in the device tree. If not, all these elements are cleared. This may look like the overhead, but it is useful while we work with different hardware boards to prevent the compilation of the software for incompatible boards. | ||
| + | The initial part of the code creates the structure which holds the parameters of the device and binds it with the LED0 identifier. At the end, the device is configured according to parameters taken from the device tree. | ||
| + | After the configuration, | ||
| + | < | ||
| + | /* ... */ | ||
| + | |||
| + | /* 1000 msec = 1 sec */ | ||
| + | #define SLEEP_TIME_MS | ||
| + | |||
| + | /* ... */ | ||
| + | |||
| + | void main(void) | ||
| + | { | ||
| + | const struct device *dev; | ||
| + | bool led_is_on = true; | ||
| + | int ret; | ||
| + | |||
| + | dev = device_get_binding(LED0); | ||
| + | if (dev == NULL) { | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | ret = gpio_pin_configure(dev, | ||
| + | if (ret < 0) { | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | while (1) { | ||
| + | gpio_pin_set(dev, | ||
| + | led_is_on = !led_is_on; | ||
| + | k_msleep(SLEEP_TIME_MS); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | ===== Direct register use ===== | ||
| + | Using a device tree is universal but can be found somehow complex. To obtain the same results, certainly, with losing the portability of the program between different microcontrollers and hardware platforms, there is a possibility to write directly to registers of the microcontroller. We need to know what is the base address of the P0 port and offsets of the registers responsible for pin configuration and state. Also, the pin number where the LED is connected. The values are presented in the table 1. | ||
| + | < | ||
| + | < | ||
| + | ^ base address ^ direction register ^ port register ^ pin number ^ | ||
| + | | 0x50000000 | 0x514 | 0x504 | 14 | | ||
| + | </ | ||
| + | The program can look like this. | ||
| + | < | ||
| + | #include < | ||
| + | |||
| + | #define SLEEP_TIME_MS | ||
| + | #define P0_BASE_ADDR | ||
| + | #define GPIO_PORT_REG | ||
| + | #define GPIO_DIRECTION_REG 0x514 //offset of the register for defining pin function | ||
| + | #define PIN_NUMBER | ||
| + | |||
| + | int main(void) | ||
| + | { | ||
| + | volatile uint32_t *p0_dir_reg = (volatile uint32_t *) (P0_BASE_ADDR + GPIO_DIRECTION_REG); | ||
| + | *p0_dir_reg |= (1 << PIN_NUMBER); | ||
| + | |||
| + | volatile uint32_t *p0_port_reg = (volatile uint32_t *) (P0_BASE_ADDR + GPIO_PORT_REG); | ||
| + | while (1) { | ||
| + | *p0_port_reg |= (1 << PIN_NUMBER); | ||
| + | k_msleep(SLEEP_TIME_MS); | ||
| + | *p0_port_reg &= ~(1 << PIN_NUMBER); | ||
| + | k_msleep(SLEEP_TIME_MS); | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
| + | Some explanation of the " | ||
| + | * Shared global variable modified by an interrupt service routine, used also in the main program | ||
| + | * Shared global variable within a multi-threaded application used by more than one thread | ||
| + | * Memory-mapped peripheral registers | ||
| + | In our example, we have that third situation. | ||
| + | |||
| + | ===== Button example ===== | ||
| + | A similar example named “button” [5] uses one of the onboard buttons to generate interrupts and, at the same time, to control the onboard LED. The button example is not a part of the distribution of the Zephyr package, but can be downloaded from GitHub: https:// | ||
| + | The button is included in the program similar way as LED. | ||
| + | < | ||
| + | #define SW0_NODE DT_ALIAS(sw0) | ||
| + | #if DT_NODE_HAS_STATUS(SW0_NODE, | ||
| + | #define SW0_GPIO_LABEL DT_GPIO_LABEL(SW0_NODE, | ||
| + | #define SW0_GPIO_PIN DT_GPIO_PIN(SW0_NODE, | ||
| + | #define SW0_GPIO_FLAGS (GPIO_INPUT | DT_GPIO_FLAGS(SW0_NODE, | ||
| + | #endif | ||
| + | </ | ||
| + | Configuration of the button is done with the gpio_pin_configure() function: | ||
| + | < | ||
| + | gpio_pin_configure(button, | ||
| + | </ | ||
| + | The button is configured to signal the interrupt. | ||
| + | < | ||
| + | gpio_pin_interrupt_configure(button, | ||
| + | </ | ||
| + | As the cause of the press or release the callback function is started. The definition of this function can be like in the following code: | ||
| + | < | ||
| + | static struct gpio_callback button_cb_data; | ||
| + | void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) | ||
| + | { | ||
| + | | ||
| + | } | ||
| + | </ | ||
| + | The callback function must be initialised and configured in the main: | ||
| + | < | ||
| + | gpio_init_callback(& | ||
| + | gpio_add_callback(button, | ||
| + | </ | ||
| + | Note that to be able to observe the UART port there is a need to enable the serial port in the file prj.conf: | ||
| + | < | ||
| + | CONFIG_SERIAL=y | ||
| + | </ | ||
| + | and set the speed of transmission in platformio.ini: | ||
| + | < | ||
| + | monitor_speed = 115200 | ||
| + | </ | ||
| + | ===== Tasks for the laboratory on GPIO ===== | ||
| + | - Import the Blinky (zephyr_blink) example in the VS Code, as shown in the introduction to PlatformIO document. | ||
| + | - Find a zephyr-button example. It's not importable as the project, so the simplest solution is to replace files main.c and prj.conf in the already imported project (e.g. Blinky). Files can be found in the folder < | ||
| + | - Merge two projects “blink”, | ||
| + | |||
| + | |||
| + | ===== The code for the laboratory on GPIO ===== | ||
| + | ==== The Zephyr-button code example ==== | ||
| + | <code c> | ||
| + | /* | ||
| + | * Copyright (c) 2016 Open-RnD Sp. z o.o. | ||
| + | * Copyright (c) 2020 Nordic Semiconductor ASA | ||
| + | * | ||
| + | * SPDX-License-Identifier: | ||
| + | */ | ||
| + | |||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | #define SLEEP_TIME_MS 1 | ||
| + | |||
| + | /* | ||
| + | * Get button configuration from the devicetree sw0 alias. This is mandatory. | ||
| + | */ | ||
| + | #define SW0_NODE DT_ALIAS(sw0) | ||
| + | #if !DT_NODE_HAS_STATUS(SW0_NODE, | ||
| + | #error " | ||
| + | #endif | ||
| + | static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, | ||
| + | {0}); | ||
| + | static struct gpio_callback button_cb_data; | ||
| + | |||
| + | /* | ||
| + | * The led0 devicetree alias is optional. If present, we'll use it | ||
| + | * to turn on the LED whenever the button is pressed. | ||
| + | */ | ||
| + | static struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), | ||
| + | {0}); | ||
| + | |||
| + | void button_pressed(const struct device *dev, struct gpio_callback *cb, | ||
| + | uint32_t pins) | ||
| + | { | ||
| + | printk(" | ||
| + | } | ||
| + | |||
| + | int main(void) | ||
| + | { | ||
| + | int ret; | ||
| + | |||
| + | if (!gpio_is_ready_dt(& | ||
| + | printk(" | ||
| + | | ||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | ret = gpio_pin_configure_dt(& | ||
| + | if (ret != 0) { | ||
| + | printk(" | ||
| + | ret, button.port-> | ||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | ret = gpio_pin_interrupt_configure_dt(& | ||
| + | GPIO_INT_EDGE_TO_ACTIVE); | ||
| + | if (ret != 0) { | ||
| + | printk(" | ||
| + | ret, button.port-> | ||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | gpio_init_callback(& | ||
| + | gpio_add_callback(button.port, | ||
| + | printk(" | ||
| + | |||
| + | if (led.port && !gpio_is_ready_dt(& | ||
| + | printk(" | ||
| + | ret, led.port-> | ||
| + | led.port = NULL; | ||
| + | } | ||
| + | if (led.port) { | ||
| + | ret = gpio_pin_configure_dt(& | ||
| + | if (ret != 0) { | ||
| + | printk(" | ||
| + | ret, led.port-> | ||
| + | led.port = NULL; | ||
| + | } else { | ||
| + | printk(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | printk(" | ||
| + | if (led.port) { | ||
| + | while (1) { | ||
| + | /* If we have an LED, match its state to the button' | ||
| + | int val = gpio_pin_get_dt(& | ||
| + | |||
| + | if (val >= 0) { | ||
| + | gpio_pin_set_dt(& | ||
| + | } | ||
| + | k_msleep(SLEEP_TIME_MS); | ||
| + | } | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
en/edu/zmitac/s2/aih/lab/nrf52gpio.1700647635.txt.gz · Last modified: by ktokarz
