zephyr-devicetree-introduccion

Introducción a Devicetree en Zephyr (.dts)

  • 5 min

Devicetree es una descripción jerárquica del hardware que Zephyr procesa al compilar para generar la configuración que utilizarán los drivers y la aplicación.

Si vienes de Arduino, PIC o de programar directamente en C para microcontroladores, quizá estés acostumbrado a esto:

#define LED_PIN  13
#define BTN_PIN  5

int main(void) {
   pinMode(LED_PIN, OUTPUT);
   // ...
   return 0;
}
Copied!

Parece inofensivo, ¿verdad? Pues bien, en Zephyr esto es una práctica prohibida. O al menos, es una práctica que va en contra de toda la filosofía del sistema.

En Zephyr, el código fuente C no debe saber en qué pin físico está conectado un componente. Nunca veréis un número de pin “hardcodeado” en una aplicación profesional de Zephyr.

Para lograr esta abstracción total, Zephyr toma prestada una tecnología del Kernel de Linux: el Devicetree.

¿Qué es el Devicetree?

El Devicetree (DTS) es una estructura de datos jerárquica que describe el hardware de una placa. Es un archivo de texto (con extensión .dts o .dtsi) que lista todos los componentes físicos: la CPU, la memoria, los buses (I2C, SPI), los controladores GPIO y los periféricos conectados a ellos (sensores, LEDs, botones).

Piensa en Devicetree como el plano arquitectónico de la placa.

  • El Código C define la lógica (qué hacer).
  • El Devicetree define el contexto (dónde están las cosas).

Gracias a Devicetree, puedes compilar el mismo código C para distintas placas sin cambiar el archivo .c, siempre que todas proporcionen los alias, periféricos y capacidades que espera la aplicación.

¿Por qué no usar #define PIN 13?

Supongamos que escribes un driver para un sensor de temperatura conectado por I2C.

A la antigua usanza: Escribes en tu código I2C_SDA_PIN 21. Si mañana cambias el diseño de la PCB y el sensor se mueve al pin 15, tienes que entrar en el código C, buscar esa línea, cambiarla y recompilar. Si tienes 20 sensores, es un caos.

Al estilo Zephyr: Tu código C dice: “Sistema, dame el puntero al dispositivo llamado ‘sensor_temp’”. El sistema consulta Devicetree, encuentra sensor_temp en el controlador I2C0 con dirección 0x40 y devuelve el dispositivo configurado.

Si cambias la PCB, solo actualizas el archivo .dts. El código C ni se entera. Desacoplamiento total.

Anatomía de un archivo .dts

El lenguaje del Devicetree parece una mezcla entre JSON y C. Se organiza en Nodos y Propiedades.

Veamos un ejemplo simplificado de cómo se ve un LED en un Devicetree:

/ {
    model = "Mi Placa IoT";
    compatible = "mi-empresa,mi-placa";

    /* Nodo de alias para facilitar el acceso */
    aliases {
        led0 = &my_led;
    };

    leds {
        compatible = "gpio-leds";
        
        /* Definición del LED */
        my_led: led_0 {
            gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
            label = "LED Rojo de Estado";
        };
    };
};
Copied!

Analicemos esto, porque contiene la esencia de Zephyr:

  1. Raíz (/): Todo empieza en la raíz.
  2. Nodos (leds, led_0): Representan dispositivos o agrupaciones.
  3. Etiquetas (my_led:): Son “variables” internas del DTS. Sirven para referenciar este nodo desde otros sitios.
  4. Propiedades (gpios = ...): Aquí se describe la conexión real.
  • &gpio0: Referencia al controlador del puerto 0.
  • 13: El número de pin.
  • GPIO_ACTIVE_LOW: Una “flag” que indica que el LED se enciende con lógica negativa (0V).

Fíjate en GPIO_ACTIVE_LOW. En Arduino tendrías que gestionar en tu código C si escribir un HIGH o un LOW enciende el LED. En Zephyr, el driver lee esta propiedad y se encarga él solo. Tu código solo dice “Enciéndete”, y el driver sabe si eso significa poner un 1 o un 0.

El flujo de compilación: macros generadas

Es fundamental entender que el archivo .dts no se lee en tiempo de ejecución. El microcontrolador no parsea texto.

Durante la compilación (cuando haces west build):

El preprocesador coge todos los archivos .dts y .dtsi (de la placa, del procesador y los tus) y los fusiona.

El compilador de Devicetree (dtc) verifica que todo sea correcto.

Script de generación: Zephyr convierte ese árbol en un archivo de cabecera C gigante llamado devicetree_generated.h.

Este archivo generado contiene miles de macros ininteligibles que traducen la información del árbol a constantes C.

Por ejemplo, nuestro LED de antes se convierte en algo así (simplificado):

#define DT_N_S_leds_S_led_0_GPIO_PIN 13
#define DT_N_S_leds_S_led_0_GPIO_FLAGS (GPIO_ACTIVE_LOW)
Copied!

Y por eso en tu código C usas macros como DT_ALIAS(led0) o GPIO_DT_SPEC_GET, que por debajo resuelven estas definiciones automáticas.

Nodos, propiedades, etiquetas y aliases

Para trabajar con Devicetree necesitas distinguir estos cuatro términos:

  • Node (Nodo): Un bloque { ... } en el archivo DTS. Representa un dispositivo (ej. un controlador UART).
  • Property (Propiedad): Pares clave-valor dentro del nodo (ej. current-speed = <115200>;).
  • Label (Etiqueta): El nombre seguido de dos puntos (ej. my_uart:) antes del nodo. Es un puntero único dentro del DTS.
  • Alias: Un nombre “amigable” (como led0 o uart-console) que mapea a un nodo complejo. Los usamos para no volvernos locos buscando nodos anidados.

Devicetree permite que buena parte del hardware sea configuración en lugar de lógica C. Para añadir un sensor o cambiar una propiedad sin modificar la definición original de la placa utilizaremos overlays, que veremos en el siguiente artículo.