Módulo 2.6

Modelo de Drivers

Arquitectura de drivers y cómo extender el sistema operativo

Santi Scagliusi, PhD

Comenzar arrow_downward
layers
Abstracción
API genérica
data_object
struct device
Estructura central
code
Macros
DEVICE_DT_DEFINE
repeat
Instancias
DT_INST_FOREACH

¿Por qué un modelo de drivers?

La abstracción permite escribir código portable y reutilizable.

warning Sin abstracción

Si cada aplicación accede directamente al hardware, el código se vuelve:

  • close No portable: Cambiar de chip requiere reescribir
  • close Difícil de mantener: Código duplicado en cada proyecto
  • close Propenso a errores: Cada desarrollador reinventa la rueda

check_circle Con modelo de drivers

El modelo de drivers de Zephyr proporciona:

  • check API unificada: gpio_pin_set() funciona en cualquier chip
  • check Portabilidad: Cambia el hardware, no el código
  • check Reutilización: Drivers probados por la comunidad
lightbulb
Analogía: Es como usar un enchufe estándar. No importa qué central eléctrica genere la energía, el enchufe siempre funciona igual. El modelo de drivers es el "estándar" entre tu aplicación y el hardware.

Arquitectura del modelo de drivers

Capas de abstracción desde la aplicación hasta el hardware.

apps Aplicación Tu código
keyboard_arrow_down Llama funciones genéricas
api Driver API gpio_pin_set(), sensor_sample_fetch()
keyboard_arrow_down Referencia mediante puntero
data_object Device Instance const struct device *
keyboard_arrow_down Contiene punteros a implementación
code Driver Implementation Código específico del chip
keyboard_arrow_down Accede a registros
memory Hardware Periféricos del microcontrolador
api

Driver API

Funciones de alto nivel independientes del hardware. El mismo código funciona en nRF, STM32 o ESP32.

data_object

Device Instance

Representación en runtime del dispositivo. Contiene configuración, datos y punteros a la API.

code

Implementation

Código de bajo nivel que accede a los registros específicos del microcontrolador.

struct device

El corazón del modelo de drivers en Zephyr.

Definición de la estructura

zephyr/device.h
struct device {
const char *name;
/* Nombre del dispositivo (del DT) */
const void *config;
/* Configuración read-only */
void * const data;
/* Datos de runtime (mutables) */
const void *api;
/* Puntero a funciones de la API */
};

Campos explicados

name
Identificador del dispositivo, típicamente derivado del label del devicetree. Ejemplo: "gpio@50000000"
config
Puntero a estructura con configuración estática (pines, direcciones, etc.). Se define en tiempo de compilación desde el devicetree.
data
Puntero a estructura con estado en runtime (contadores, flags, buffers). Puede modificarse durante ejecución.
api
Puntero a estructura con funciones que implementan la API genérica del driver.
lightbulb
const struct device *: Siempre se usa como puntero constante. La estructura se crea en tiempo de compilación y reside en memoria flash (read-only).

Config vs Data

Dos estructuras complementarias con propósitos diferentes.

settings

Config (Configuración)

const void *config
struct mysensor_config {
struct i2c_dt_spec i2c;
struct gpio_dt_spec int_gpio;
uint32_t sample_rate;
};
  • memory Reside en flash (read-only)
  • lock Valores del devicetree
  • schedule Definida en compilación
database

Data (Estado)

void * const data
struct mysensor_data {
int32_t last_reading;
bool initialized;
struct k_sem data_ready;
};
  • memory Reside en RAM (mutable)
  • sync Valores que cambian en runtime
  • play_arrow Modificada durante ejecución
warning
El puntero data es constante, no el contenido: void * const data significa que no puedes cambiar a dónde apunta, pero sí puedes modificar el contenido de la estructura a la que apunta.

Macros de definición de drivers

Zephyr proporciona macros para crear instancias de dispositivos.

DEVICE_DEFINE Básico

Para dispositivos que NO provienen del devicetree.

- Dispositivos virtuales
- Hardware sin binding DT
- Uso muy específico
DEVICE_DT_DEFINE Devicetree

Para dispositivos definidos en devicetree con node_id.

- Usa identificador de nodo
- Acceso a propiedades DT
- Flexibilidad total
DEVICE_DT_INST_DEFINE Más común

Igual que anterior pero usando número de instancia.

- Usa índice de instancia (0, 1, 2...)
- Simplifica múltiples instancias
- Recomendado para drivers
Ejemplo de DEVICE_DT_INST_DEFINE
DEVICE_DT_INST_DEFINE(
inst, /* Índice de instancia (0, 1, 2...) */
mysensor_init, /* Función de inicialización */
NULL, /* PM (power management) o NULL */
&mysensor_data_##inst, /* Puntero a datos de runtime */
&mysensor_config_##inst,/* Puntero a configuración */
POST_KERNEL, /* Nivel de inicialización */
CONFIG_SENSOR_INIT_PRIORITY, /* Prioridad */
&mysensor_api /* Puntero a API del driver */
);

DT_DRV_COMPAT

Vincula el driver con los nodos del devicetree.

Definición obligatoria

Al inicio del archivo .c del driver
#define DT_DRV_COMPAT vendor_mysensor
/* Esto habilita las macros DT_INST_* */
/* para acceder a nodos con ese compatible */
info
El valor debe coincidir con la propiedad compatible del devicetree, reemplazando comas y guiones por guiones bajos.

Correspondencia en devicetree

app.overlay
&i2c0 {
mysensor@48 {
compatible = "vendor,mysensor";
reg = <0x48>;
status = "okay";
};
};
Transformación
"vendor,mysensor" arrow_forward vendor_mysensor

Comas y guiones se convierten en guiones bajos.

Anatomía de un driver

Componentes esenciales de un driver de Zephyr.

drivers/sensor/mysensor/mysensor.c
/* 1. Definir DT_DRV_COMPAT */
#define DT_DRV_COMPAT vendor_mysensor
/* 2. Estructura de configuración (read-only) */
struct mysensor_config { struct i2c_dt_spec i2c; };
/* 3. Estructura de datos (runtime) */
struct mysensor_data { int32_t value; };
/* 4. Función de inicialización */
static int mysensor_init(const struct device *dev) { ... }
/* 5. Estructura de API */
static const struct sensor_driver_api mysensor_api = { ... };
/* 6. Macro para instanciar (ver siguiente slide) */

Los 6 componentes de un driver

Cada driver sigue esta estructura consistente.

1
DT_DRV_COMPAT
Vincula con devicetree
2
Config struct
Parámetros del DT
3
Data struct
Estado en runtime
4
Init function
Se ejecuta al boot
5
API struct
Funciones del driver
6
DEVICE macro
Crea la instancia
lightbulb
Consistencia: Todos los drivers de Zephyr siguen este patrón. Una vez que lo entiendes, puedes leer y escribir cualquier driver.

DT_INST_FOREACH_STATUS_OKAY

Crea automáticamente instancias para todos los nodos habilitados.

Patrón de instanciación

Al final del archivo del driver
/* Macro que define una instancia */
#define MYSENSOR_DEFINE(inst) \
static struct mysensor_data \
mysensor_data_##inst; \
static const struct mysensor_config \
mysensor_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
}; \
DEVICE_DT_INST_DEFINE(inst, ...); \
/* Itera por cada nodo con status="okay" */
DT_INST_FOREACH_STATUS_OKAY(MYSENSOR_DEFINE)

Ejemplo de expansión

Si el devicetree tiene 3 nodos con status = "okay":

inst = 0 mysensor_data_0, mysensor_config_0
inst = 1 mysensor_data_1, mysensor_config_1
inst = 2 mysensor_data_2, mysensor_config_2
auto_awesome
Automático: El preprocesador genera código para cada instancia. No necesitas saber cuántas hay.
lightbulb
¿Por qué STATUS_OKAY? Solo instancia nodos con status = "okay". Los nodos con status = "disabled" se ignoran, permitiendo deshabilitar hardware sin modificar el driver.

Ejercicio: Explorar un driver existente

Analiza el driver BME280 para entender la estructura real.

1
Localiza el driver
zephyr/drivers/sensor/bosch/bme280/
2
Identifica DT_DRV_COMPAT
Busca la definición al inicio del .c
3
Encuentra las estructuras
bme280_config y bme280_data
4
Analiza la función init
bme280_init() - qué hace en el boot
5
Revisa la API
sensor_driver_api
6
Observa DT_INST_FOREACH
Instanciación múltiple

Comandos para explorar

Terminal
# Ver el código fuente
cat $ZEPHYR_BASE/drivers/sensor/bosch/bme280/bme280.c
# Buscar DT_DRV_COMPAT
grep -n "DT_DRV_COMPAT" \
$ZEPHYR_BASE/drivers/sensor/bosch/bme280/*.c
# Ver el binding del devicetree
cat $ZEPHYR_BASE/dts/bindings/sensor/bosch,bme280*.yaml
search ¿Qué buscar?
  • check ¿Qué campos tiene bme280_config?
  • check ¿Qué datos guarda bme280_data?
  • check ¿Cómo accede a I2C o SPI?
  • check ¿Qué macros DT_INST_* utiliza?

Resumen del módulo

Conceptos clave del modelo de drivers en Zephyr.

layers

Arquitectura en capas

App, API, Instance, Implementation, Hardware. Cada capa tiene un propósito definido.

data_object

struct device

Estructura central con name, config, data y api. Siempre se usa como const struct device *.

code

Macros DT_INST

DEVICE_DT_INST_DEFINE y DT_INST_FOREACH simplifican crear múltiples instancias.

tips_and_updates

"Entender el modelo de drivers es clave para extender Zephyr y escribir código verdaderamente portable."

Referencia rápida

Componentes del driver
Binding DT_DRV_COMPAT
Configuración struct xxx_config
Estado struct xxx_data
Macros importantes
Instanciar DEVICE_DT_INST_DEFINE
Iterar DT_INST_FOREACH_STATUS_OKAY
Obtener device DEVICE_DT_GET