Domina el Multihilo en Arduino con Protothreading: Tutorial Paso a Paso
Componentes y materiales
| Arduino UNO | 1 |
| Pantalla LCD 16x2 Azul Sunfounder | 1 |
| Protoboard (genérica) | 1 |
| Potenciómetro rotativo (genérico, ~1 kΩ) | 1 |
Sobre este proyecto
Este tutorial muestra cómo lograr que un Arduino de un solo núcleo realice tres tareas simultáneamente:
- Pulsar la retroiluminación del LCD a ritmo constante sin interrupciones.
- Incrementar un contador cada segundo y mostrarlo en la pantalla.
- Rotar varios mensajes de texto cada pocos segundos en la pantalla.
¿Qué es el protothreading?
El protothreading es una técnica para simular multitarea (ejecutar dos o más tareas a la vez o en intervalos diferentes) en un Arduino. Aunque suena a "multihilo", el Arduino tiene un solo núcleo y código procedural, por lo que el multihilo real es imposible.
Multihilo real vs. protothreading
Para entender el protothreading, primero hay que comprender por qué no es multihilo real.
Los microcontroladores y CPUs trabajan en "ciclos". La frecuencia de reloj (16 MHz en el Arduino Uno) indica cuántos ciclos por segundo puede ejecutar el procesador. Cada ciclo es una instrucción de bajo nivel (mover datos entre registros), no una línea de código.
En un PC, el multihilo real usa varios núcleos: un hilo principal gestiona hilos trabajadores que se ejecutan en paralelo. En un Arduino solo hay un núcleo, así que el multihilo tradicional es imposible. Aquí entra el protothreading.
¿En qué se diferencia el protothreading?
El protothreading se parece al hyperthreading de Intel: en lugar de dividir ciclos de CPU, divide el trabajo por "bucles" o líneas de código del sketch. Cada vez que el bucle principal no tiene trabajo urgente, verifica si es momento de ejecutar otras tareas y salta a ellas.
Punto clave: las tareas "bloqueantes" (lectura de tarjeta SD, delays largos) seguirán bloqueando otros protothreads. Pero para acciones rápidas (cambio de variables, salidas PWM) funciona excelentemente.
Manos a la obra
Primero definimos las tareas y las ordenamos de menos a más bloqueantes:
- Pulsar retroiluminación (menos bloqueante): intervalo rápido, perpetuo, trabajo mínimo. Será el hilo principal.
- Incrementar contador: cada 700 ms, trabajo rápido.
- Rotar mensajes: cada 3000 ms, trabajo rápido.
El hilo principal (parpadeo de la retroiluminación) verifica en cada iteración —y dentro de sus propios bucles— si los otros hilos deben ejecutarse, llamando a numberThread.check() y textThread.check().
Se usa la librería TimedAction para programar las tareas con intervalos independientes. Nota: esta librería está desactualizada; en versiones modernas de Arduino IDE hay que editar TimedAction.h y reemplazar WProgram.h por Arduino.h.
Código
/* Arduino Protothreading Example v1.1 by Drew Alden (@ReanimationXP)
* 1/12/2016 - Actualización v1.1 - 8/18/17: correcciones para Arduino 1.6.6+
* Nota: TimedAction está obsoleta. Ver notas sobre errores WProgram.h / Arduino.h.
*/
// COMPONENTES
// LCD azul Sunfounder del kit de inicio (disponible en Amazon)
// LIBRERÍAS DE TERCEROS (instalación manual)
// TimedAction: permite acciones en intervalos separados
// https://playground.arduino.cc/Code/TimedAction
#include <TimedAction.h>
// LIBRERÍAS NATIVAS
#include <LiquidCrystal.h>
/* Conexiones LCD (Hitachi HD44780):
* RS -> pin 12
* EN -> pin 11
* D4 -> pin 5
* D5 -> pin 4
* D6 -> pin 3
* D7 -> pin 2
* R/W -> GND
* VSS -> GND
* VCC -> 5V
* Potenciómetro 10K: extremos a 5V y GND, cursor a VO (pin 3 LCD)
* Retroiluminación: K a GND, A a 220Ω y luego a pin 9 (PWM)
*/
// VARIABLES GLOBALES
int backlightPin = 9;
int timerCounter = 0;
int stringNo = 0;
char* stringArray[] = {"Mira esto... ", "Tengo 3 hilos", "funcionando a la vez...", "Genial, ¿eh?! :D "};
// INICIALIZACIÓN
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// FUNCIONES DE LAS TAREAS SECUNDARIAS
void incrementNumber() {
lcd.setCursor(0, 1);
timerCounter++;
lcd.print(timerCounter);
}
void changeText() {
lcd.setCursor(0, 0);
lcd.print(stringArray[stringNo]);
if (stringNo >= sizeof(stringArray)/sizeof(char *)) {
stringNo = 0;
changeText();
} else {
stringNo++;
}
}
// TIMERS (hilos secundarios)
// Deben declararse DESPUÉS de las funciones que usan
TimedAction numberThread = TimedAction(700, incrementNumber);
TimedAction textThread = TimedAction(3000, changeText);
// La tercera tarea es el propio loop() (parpadeo backlight)
void setup() {
lcd.begin(16, 2);
changeText(); // muestra mensaje inicial
}
void loop() {
// Verificar hilos secundarios
numberThread.check();
textThread.check();
// TAREA PRINCIPAL: fundido de retroiluminación (fade in/out)
digitalWrite(13, HIGH); // LED indicador opcional
// Fade in: 0 a 255
for (int fadeValue = 0; fadeValue <= 255; fadeValue += 10) {
numberThread.check(); // ¡Importante: verificar hilos DENTRO de bucles!
textThread.check();
analogWrite(backlightPin, fadeValue);
delay(20); // delays cortos para no bloquear otros hilos
}
digitalWrite(13, LOW);
// Fade out: 255 a 0
for (int fadeValue = 255; fadeValue >= 0; fadeValue -= 10) {
numberThread.check();
textThread.check();
analogWrite(backlightPin, fadeValue);
delay(20);
}
}
Este es un ejemplo educativo simple. Para proyectos más complejos con tareas bloqueantes, considere un RTOS (FreeRTOS, Zephyr) en placas más potentes (ESP32, STM32).
Proceso de manufactura
- Medición de la Calidad del Aire con OpenSensors: Guía para Arduino y Python
- Crea un Cerrojo RFID Seguro con Arduino – Guía Paso a Paso
- Construya un mando a distancia IR universal con Arduino: Guía paso a paso
- Construye un estadiómetro digital: Controla tu altura con Arduino y sensor láser
- Uso Fácil de Termistor: Guía Paso a Paso con Arduino
- Genera Tonos Musicales con Arduino: Guía Paso a Paso
- Conexión de Arduino a NMEA‑0183: Guía Paso a Paso
- Proyecto Sensor de Huella Dactilar con Arduino Nano – Tutorial Paso a Paso
- Domina Modbus en Arduino: Guía Paso a Paso
- Construye un Mini Piano con Arduino UNO – Tutorial Paso a Paso
- Tutorial Arduino para Principiantes 01: Inicia tu Primer Proyecto