El sistema operativo Zephyr que esta basado en el Nordic nRF Connect Software Development Kit (SDK), tiene un modelo de diseño de driver bien desacoplado del Application Programming Interface (API), lo que permite que los diseñadores puedan cambiar la implementación de bajo nivel sin modificar la aplicación principal. Esto es una de las características deseables que tiene el sistema operativo de tiempo real Zephyr. Antes de proceder con este demo, por favor siga el previo artículo Pasos a seguir para instalar el Nordic nRF54L15-DK Zephyr Linux
Este demo ilustra, como crear un Application Programming Interface (API) adaptable, configurando el Zephyr DeviceTree con los parámetros apropiados, y finalmente, usar estos en dentro del driver y la aplicación en el IoT kit de Nordic nRF54L15-DK,
El propósito de este demo es crear un device driver en Zephyr específico usado para periódicamante parpadear un LED en la plataforma. Este demo demuestra como un LED (vía el módulo General Purpose Input/Output port) parpadea periódicamente. También este periodo es configurable desde el DeviceTree.
El DeviceTree tiene los siguientes 2 parámetros relevantes a este demo:
LED’s GPIO pin
The period of the blinking mechanism
También se puede cambiar el periodo de parpadeo desde el código main.c. El device driver provee las siguientes 2 funciones en el API’s:
blink_set_period_ms – To establish the blinking period.
blink_off – To deactivate the LED entirely.
Primero se comienza de una marco de referencia de archivos de Zephyr RTOS ilustrado a continuación,
template/
├─── app/
| ├─── boards/
│ ├─── src/
│ │ └─── main.c
│ ├──prj.conf
| └──CMakeLists.txt
|
└─── custom_driver_module/
├─── drivers/
│ ├── blink/
│ │ ├──gpio_led.c
│ │ ├──CMakeLists.txt
│ │ └───Kconfig
│ ├──CMakeLists.txt
│ └───Kconfig
├─── dts/
├─── include/
│ └───blink.h
├─── zephyr/
│ └───module.yml
├──CMakeLists.txt
└──Kconfig
Siga los próximos pasos,
1. Crea el enlaze del driver.
Se necesita un archivo que provee el enlace al driver para definir los parámetros del device driver.
1.1 Se crea el archivo de enlace llamado blink-gpio-leds.yaml.
Crea el archivo llamado blink-gpio-leds.yaml en el directorio dts/bindings, y añada las siguientes lineas para declarar en el DeviceTree el nombre y la base del enlace,
compatible: "blink-gpio-led"
include: base.yaml
1.2 Incluye la propiedad del LED GPIO al enlace.
Incluye la propiedad led-gpios creando las siguientes lineas en el archivo como se ilustra a continuación,
properties:
led-gpios:
type: phandle-array
required: true
description: GPIO-controlled LED.
la propiedad the led-gpios se va usar para determinar el pin GPIO pin usado para conectar al LED apropiado.
1.3 Incluye una propiedad para establecer el periodo de parpadeo con el enlance deseado,
Incluye la propiedad llamada blink-period-ms para el blink period con las lineas como se muestra a continuación.
blink-period-ms:
type: int
description: Initial blinking period in milliseconds.
Este es el parámetro importante utilizado para controlar el comportamiento del driver en el sistema operativo Zephyr. En los próximos pasos, vamos actualizar el API para poder cambiar su valor cuando se necesite,
2. Establece el API’s para la clase driver de “blink”
En este momento, se procede a crear una clase para el driver. Una clase especializada se añade para proveer un API común con una selección de device drivers. Esto se hace usando el archivo blink.h que se encuentra en el directorio custom_driver_module/include.
2.1 Se crea una estructura del API en la clase del driver
Modifica el archivo include/blink.h en el módulo del driver especializado y crea una estructura del API llamada blink_driver_api. En este instante, se incluye un “pointer” a la función que cambia el periodo de parpadeo. Proceda a comunicarle al toolchain que esta estructura es un device driver API usando el apropiado r __subsystem prefijo.
__subsystem struct blink_driver_api {
/**
* @brief Configure the LED blink period.
*
* @param dev Blink device instance.
* @param period_ms Period of the LED blink in milliseconds, 0 to
* disable blinking.
*
* @retval 0 if successful.
* @retval -EINVAL if @p period_ms can not be set.
* @retval -errno Other negative errno code on failure.
*/
int (*set_period_ms)(const struct device *dev, unsigned int period_ms);
};
2.2 Implementa una función pública del API para la clase del driver.
El código main.c ahora podrá usar la función de una instancia de cualquier driver que pertenece a esa clase. A pesar de que comparten la misma función API, el comportamiento será especifico una implementación especifica. La función del API debe seguir la estructura de definición de nombramiento z_impl_<function_name>.
Ahora se usa un macro que ayuda a este propósito:
DEVICE_API_IS(class, device)– Esto verifica si el artefacto es de una clase en particular. En este caso, se verifca si la función fue llamada desde el artefacto cuyo driver es la clase denominadablink.
DEVICE_API_GET(class, device)– Obtiene el pointer a la instancia del API para una instacia de una clase del artefacto. Aquí, se le da aceso a una instanciablink_driver_apidefinido por el driver como se muestra a continuación,
static inline int z_impl_blink_set_period_ms(const struct device *dev,
unsigned int period_ms)
{
__ASSERT_NO_MSG(DEVICE_API_IS(blink, dev));
return DEVICE_API_GET(blink, dev)->set_period_ms(dev, period_ms);
}
2.3 Provee al espacio del usuario una envoltura con un prefijo __syscall ante la declaración de la función de API
__syscall int blink_set_period_ms(const struct device *dev,
unsigned int period_ms);
2.4 Crea una función de API blink_off() para desactivar el proceso de parpadeo
Usando la función previamente definida con el prefijo,
static inline int blink_off(const struct device *dev)
{
return blink_set_period_ms(dev, 0);
}
2.5 Se añade el syscall header al final del archivo header
Cualquier archivo del header que declara llamadas al sistema debe incluir como requisito un header especial generado al final del archivo header.
#include <syscalls/blink.h>
2.6 Comunicarle al sistema de construcción donde encontrar las declaraciones syscalls
Se añada el header de la clase del driver y la correspondiente lista de syscalls cambiando el archivo CMakeLists.txt en la raiz del módulo especializado:
zephyr_syscall_include_directories(include)
3. Define el driver de gpio_led driver que pertenece a la clase especializada `blink’
Como esta clase especializada llamada blink fue previamente definida. El device driver fue implementado en la de clase en el API. Esto será incorporado en el archivo gpio_led.c encontrado en el custom_driver_module/drivers/blink.
3.1 Ahora se define la estructura del driver
El parpadeo del LED se realiza con el callback del timer,
struct blink_gpio_led_data {
struct k_timer timer;
};
3.2 Ahora, se define la configuración de la estructura del driver en el archivo drivers/blink/gpio_led.c
struct blink_gpio_led_config {
struct gpio_dt_spec led;
unsigned int period_ms;
};
3.3 Integra la función blink_gpio_led_set_period_ms a la función correspondiente del driver en el API
Como las funciones del driver están preparadas en este demo, en este momento se configuran las estructuras del API y se usan apropiadamente en la aplicación main.c. En este momento, se usa el macro llamado DEVICE_API(class, function), que asigna la función function selecionada a un device driver en particular de una clase demoninada class.
En este demo, ahora se crea una instancia de la estructura de la clase (sub-sistema) blink y se conecta a la función blink_gpio_led_set_period_ms para establecer el periodo vía .set_period_ms como parte del API del driver,
static DEVICE_API(blink, blink_gpio_led_api) = {
.set_period_ms = &blink_gpio_led_set_period_ms,
};
[wrap="justify"]
[/wrap]
4. Define el artefacto.
Procede a definir el device especializado. Asigna el API y la configuración de las estructuras en la campos apropiados de el artefacto en la definición de la estructura:
4.1 Define la estructura de la data en una referencia de instancia
Ponga el siguiente código en el macro llamado BLINK_GPIO_LED_DEFINE
static struct blink_gpio_led_data data##inst;
4.2 Crea la configuracin de la estructura de referencia de la instancia
En el previo paso 1, el enlace que contiene los campos led_gpios y el blink_period_ms fueron creados. Se usan estos campos para obtener los parámetros de configuración del DeviceTree.
- Como el parámetro
ledes del tipogpio_dt_specse puede esperar tener la correspondiente propiedad de los (led-gpios) en el nodo del DeviceTree. El macro llamadoGPIO_DT_SPEC_INST_GET()va a procesar y convertir el parámetro de este demo
- El parámetro
period_ms, va a buscar el parámetro por su nombre en el nodo DeviceTree usandoDT_INST_PROP_OR(). Si nada se encuentra, se obtiene el valor de 0.
static const struct blink_gpio_led_config config##inst = { \
.led = GPIO_DT_SPEC_INST_GET(inst, led_gpios), \
.period_ms = DT_INST_PROP_OR(inst, blink_period_ms, 0U), \
};
4.3 Declare la definición de referencia del artefacto
DEVICE_DT_INST_DEFINE(inst, blink_gpio_led_init, NULL, &data##inst, \
&config##inst, POST_KERNEL, \
CONFIG_BLINK_INIT_PRIORITY, \
&blink_gpio_led_api);
4.4 Define el nivel de prioridad del driver’s init
Establece el Kconfig BLINK_INIT_PRIORITY en drivers/blink/Kconfig
Define el valor default al KERNEL_INIT_PRIORITY_DEVICE
config BLINK_INIT_PRIORITY
int "Blink device drivers init priority"
default KERNEL_INIT_PRIORITY_DEVICE
help
Blink device drivers init priority.
5. Use el driver especializado en la applicación.
Añade un nodo al DeviceTree con el enlace previamente definido en este demo.
5.1 Establece un nodo del artefacto a blink_gpio_leds en el DeviceTree.
Inluya un overlay file <board_target>.overlay en app/boards, con el nombre correspondiente a el IoT Nordic kit nRF54L15-DK,
En el archivo overlay, se define un nodo llamado blink_led, y añada los parámetros led-gpios y el blink-period-ms. Para el led-gpios use uno de los LEDs incluidos en la plataforma (en este demo pin 2.9 para el nRF54L15 DK). Para el blink-period-ms, establesca un periodo de blink, como ejemplo de 1 segundo (1000 ms).
Finalmente, se establece la compatibilidad del nodo blink-gpio-led.
El archivo de overlay debe ser como sigue a continuación,
/ {
blink_led: blink-led {
compatible = "blink-gpio-led";
led-gpios = <&gpio2 9 GPIO_ACTIVE_HIGH>;
blink-period-ms = <1000>;
};
};
5.2 Ahora active el driver de blink driver dentro de la applicación
Active el driver de blink driver en el código main.c añadiendo la siguiente linea en el archivo prj.conf
CONFIG_BLINK=y
**5.3 Use el API especializado blink del driver para modificar el periodo de parpadeo desde la aplicación main.c
Añada el siguiente código en la aplicación main.c,
/* Use custom API to turn LED off */
int ret = blink_off(blink);
if (ret < 0) {
LOG_ERR("Could not turn off LED (%d)", ret);
return 0;
}
while (1) {
/* When LED is constantly enabled - start over with high blinking period*/
if (period_ms == 0U) {
period_ms = BLINK_PERIOD_MS_MAX;
} else {
period_ms -= BLINK_PERIOD_MS_STEP;
}
printk("Setting LED period to %u ms\n",
period_ms);
/* Use custom API to change LED blinking period*/
blink_set_period_ms(blink, period_ms);
k_sleep(K_MSEC(1000));
}
En este momento, el archivo de la aplicación main.c debe ser como se ilustra,
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <blink.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(DigiKey Coffee Cup, LOG_LEVEL_INF);
#define BLINK_PERIOD_MS_STEP 100U
#define BLINK_PERIOD_MS_MAX 1000U
int main(void)
{
/* Start blinking - slow*/
unsigned int period_ms = BLINK_PERIOD_MS_MAX;
LOG_INF("Zephyr Example Application");
const struct device * blink = DEVICE_DT_GET(DT_NODELABEL(blink_led));
if (!device_is_ready(blink)) {
LOG_ERR("Blink LED not ready");
return 0;
}
/* STEP 5.3 Use the custom blink API from the driver to change the blinking period */
/* Use custom API to turn LED off */
int ret = blink_off(blink);
if (ret < 0) {
LOG_ERR("Could not turn off LED (%d)", ret);
return 0;
}
while (1) {
/* When LED is constantly enabled - start over with high blinking period*/
if (period_ms == 0U) {
period_ms = BLINK_PERIOD_MS_MAX;
} else {
period_ms -= BLINK_PERIOD_MS_STEP;
}
LOG_INF("Setting LED period to %u ms",
period_ms);
/* Use custom API to change LED blinking period*/
blink_set_period_ms(blink, period_ms);
k_sleep(K_MSEC(1000));
}
return 0;
}
Ahora construya la aplicación de Zephyr como se muestra a continuación, desde el tope del projecto. Después de muchos argumentos presentados en el terminal durante el proceso de construcción, aquí se muestran los últimos si el proceso se completa apropiadamente,
digikey_coffee_cup # west build app -b nrf54l15dk/nrf54l15/cpuapp
....
....
....
-- Configuring done (8.8s)
-- Generating done (0.1s)
-- Build files have been written to: /digikey_coffee_cup/app/build
-- west build: building application
[1/159] Preparing syscall dependency handling
[3/159] Generating include/generated/zephyr/version.h
-- Zephyr version: 4.2.99 , build: v4.2.0-5624-gfb7a74ebbd0a
[159/159] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 39476 B 1428 KB 2.70%
RAM: 6728 B 188 KB 3.49%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from /digikey_coffee_cup/app/zephyr/zephyr.elf for board: nrf54l15dk
Finalmente, programe la aplicación despues de conectar el Nordic nRF54L15-DK kit vía la interfaz USB a la computadora como se muestra,
digikey_coffee_cup # west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner nrfutil
-- runners.nrfutil: reset after flashing requested
Using board 001057777221
-- runners.nrfutil: Flashing file: /digikey_coffee_cup/zephyr/zephyrproject/zephyr/driver/appl/build/zephyr/zephyr.hex
-- runners.nrfutil: Connecting to probe
-- runners.nrfutil: Programming image
-- runners.nrfutil: Verifying image
-- runners.nrfutil: Reset
-- runners.nrfutil: Board(s) with serial number(s) 1057777221 flashed successfully.
Después de programar el Device Driver Zephyr RTOS demo a el kit Nordic nRF54L15-DK, el periodo del LED0 va a cambiar una vez cada segundo, como se puede observar en el siguiente video,
Este demo muestra como crear un API especializado, configurando el DeviceTree de Zephyr con parámetros especializados, usando estos en el driver, y la aplicación utilizando el IoT kit de Nordic nRF54L15-DK,
El IoT kit de Nordic nRF54L15-DK es una excelente plataforma para desarrollar aplicaciones IoT donde los requisitos de consumo de potencia son restringidos. Este plataforma IoT está disponible en DigiKey. Que tenga en excelente dia.
Este artículo está disponible en inglés aquí.
This article is also available in english here.
