Un GPIO es un pin digital configurable como entrada o salida para leer botones, controlar LEDs, activar relés o detectar finales de carrera.
En Arduino, esto era tan fácil como digitalWrite(13, HIGH). En Zephyr, la filosofía es distinta. Zephyr busca que tu código sea agnóstico del hardware. El código que escribas hoy para leer un botón debe funcionar mañana en un ESP32, un Nordic o un STM32 sin cambiar ni una coma del archivo .c.
Para lograr esto, Zephyr utiliza una API de GPIO Unificada. Hoy vamos a dominarla.
El objeto gpio_dt_spec
En la API basada en Devicetree, los pines se representan mediante una estructura gpio_dt_spec (GPIO Devicetree Specification) en lugar de repetir números físicos en el código.
Esta estructura contiene:
- El Puerto: El puntero al controlador (ej.
GPIOA,GPIO0). - El Pin: El número de bit (ej.
13,5). - Las Flags: Configuración por defecto (ej.
GPIO_ACTIVE_LOW,GPIO_PULL_UP) extraída del Devicetree.
Para usar un pin, primero lo definimos en el Devicetree (usando un alias, como vimos en capítulos anteriores) y luego lo capturamos en C:
/* En Devicetree (app.overlay) */
/ {
aliases {
led0 = &my_led;
sw0 = &my_button;
};
};
/* En C (main.c) */
#define LED0_NODE DT_ALIAS(led0)
#define SW0_NODE DT_ALIAS(sw0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);Esta macro GPIO_DT_SPEC_GET lee todas las propiedades del hardware y nos prepara la estructura lista para usar.
Salidas digitales
Una vez tenemos nuestra variable led, usarla es muy intuitivo, siempre usando las funciones con sufijo _dt (DeviceTree).
Configuración
Antes de usar el pin, debemos decirle al driver si es entrada o salida.
if (!gpio_is_ready_dt(&led)) {
return 0; // Error: El driver no está listo
}
/* Configuramos como salida e inicializamos en estado inactivo */
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);Escritura
Podemos poner el pin a 1, a 0, o invertirlo.
gpio_pin_set_dt(&led, 1); // Encender (Activar)
gpio_pin_set_dt(&led, 0); // Apagar (Desactivar)
gpio_pin_toggle_dt(&led); // Invertir estadoFíjate que digo “Activar” y no “Poner a 5V”. Si en el Devicetree definiste el LED como GPIO_ACTIVE_LOW, al poner un 1 lógico, Zephyr pondrá 0V en el pin físico. La API maneja la lógica negativa por nosotros.
Entradas digitales
Para leer un botón, primero lo configuramos. Aquí es muy útil activar las resistencias de Pull-Up o Pull-Down internas si el hardware lo requiere.
/* Configurar como entrada con Pull-Up */
/* GPIO_INPUT es obligatorio. GPIO_PULL_UP es opcional pero recomendado para botones */
gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_PULL_UP);Lectura por sondeo
Podemos leer el estado en un bucle.
int val = gpio_pin_get_dt(&button);
if (val > 0) {
printk("Botón pulsado\n");
}Esto está bien para pruebas, pero gasta CPU inútilmente preguntando constantemente “¿Ya? ¿Ya? ¿Ya?”. La forma profesional es usar Interrupciones.
Interrupciones (Callbacks)
El sistema de callbacks para interrupciones GPIO requiere tres pasos:
Configurar el disparo: ¿Cuándo debe saltar? (Flanco de subida, bajada, ambos, nivel…).
Preparar la estructura Callback: Zephyr necesita una “ficha” donde guardar quién es la función a llamar.
Añadir el Callback: Suscribir nuestra función al puerto GPIO.
Estructura del código
Necesitamos una variable struct gpio_callback que debe vivir durante toda la ejecución (normalmente estática o global).
static struct gpio_callback button_cb_data;
/* Esta es la función ISR que se ejecutará al pulsar */
void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
printk("¡Botón pulsado en ISR!\n");
/* La ISR debe ser breve: no puede dormir ni ejecutar tareas pesadas. */
}Inicialización en el main
/* 1. Configurar la interrupción en el hardware */
/* GPIO_INT_EDGE_TO_ACTIVE: Dispara cuando pasa a estado activo (ej. pulsado) */
gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
/* 2. Inicializar la estructura de callback */
/* Le decimos: "Usa la función 'button_pressed' para el pin de este botón" */
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
/* 3. Añadir el callback al puerto */
gpio_add_callback(button.port, &button_cb_data);Ejemplo completo: interruptor de luz
Vamos a juntar todo. Un botón que, al pulsarlo, cambia el estado de un LED usando interrupciones.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <errno.h>
/* Obtenemos nodos del Devicetree */
#define LED0_NODE DT_ALIAS(led0)
#define SW0_NODE DT_ALIAS(sw0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
/* Estructura para gestionar el callback */
static struct gpio_callback button_cb_data;
/* Función de interrupción (ISR) */
void button_handler(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
/* Cambiamos el LED instantáneamente */
gpio_pin_toggle_dt(&led);
}
int main(void)
{
/* Verificaciones de seguridad */
if (!gpio_is_ready_dt(&led) || !gpio_is_ready_dt(&button)) {
return 0;
}
/* 1. Configurar LED como salida */
if (gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE) < 0) {
return -EIO;
}
/* 2. Configurar Botón como entrada con Pull-Up */
if (gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_PULL_UP) < 0) {
return -EIO;
}
/* 3. Registrar Callback */
gpio_init_callback(&button_cb_data, button_handler, BIT(button.pin));
if (gpio_add_callback(button.port, &button_cb_data) < 0) {
return -EIO;
}
/* 4. Habilitar la interrupción después de registrar el callback */
if (gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_BOTH) < 0) {
return -EIO;
}
/* El hilo principal no tiene nada que hacer, se duerme para siempre */
while (1) {
k_sleep(K_FOREVER);
}
return 0;
}Consideraciones sobre el rebote (debounce)
Los botones mecánicos son ruidosos. Al pulsar, la señal eléctrica “rebota” varias veces, generando múltiples interrupciones en milisegundos.
Zephyr no incluye un “Debounce por software” automático en el driver GPIO básico (algunos controladores de hardware sí lo tienen). Si tu botón dispara 10 veces al pulsar una vez, tienes dos opciones:
- Hardware: Usar una red RC o un circuito de acondicionamiento adecuado a la entrada.
- Software: Usar un Timer o una Workqueue Retardada (como vimos en el artículo anterior) para ignorar pulsaciones sucesivas.