Vas a cambiar el sentido de un motor de corriente continua con el puente H L298N. Combinarás las dos entradas lógicas para avanzar, parar y retroceder, y separarás la alimentación del motor de la lógica de la placa. El motor va de 5 a 12 V en corriente continua. No conmutas la red de casa: este puente no debe ver la instalación eléctrica doméstica.
Vas a regular la velocidad del mismo motor con una señal PWM en el enable del L298N, en el pin D9 del Uno, que admite analogWrite. Verás que el sentido sigue dependiendo de IN1 e IN2, y que el ciclo de trabajo solo cambia cuánta energía media llega al motor. La potencia sigue siendo de 5 a 12 V en continua. No conmutas la red.
Vas a dar exactamente una vuelta con el motor paso a paso 28BYJ-48 y su driver ULN2003. En paso completo, la reductora interna deja 2048 pasos por revolución: el sketch cuenta esos pasos y se detiene. El motor se alimenta a 5 V. No lo pongas a 12 V ni lo relaciones con la red; es un actuador de prácticas, pequeño y lento.
Vas a convertir un ángulo en pasos del 28BYJ-48. Con 2048 pasos por vuelta, los grados que escribas por el monitor se traducen a un número entero de pasos, con signo para el sentido. Así dejas de contar vueltas a ojo y pasas a una posición que puedes repetir. El motor sigue a 5 V, sin relación con la red.
Vas a encender y apagar un LED desde un teléfono emparejado por Bluetooth clásico con un HC-05, al lado de la placa. No hay cuenta, no hay servidor y no sale nada de tu mesa: el teléfono habla solo con el módulo. En modo datos el HC-05 usa 9600 baudios, que es lo que abre este sketch. El modo de comandos AT es otro ritmo, casi siempre 38400, y aquí no lo necesitas porque no vas a cambiar el nombre ni la clave.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-05 HC-05 mando local de un LED
// Pines: D2 RX del enlace, D3 TX del enlace, D8 LED
// Datos a 9600 baudios. El modo AT del HC-05, si lo usaras, iria a 38400.
#include <SoftwareSerial.h>
const int PIN_BT_RX = 2;
const int PIN_BT_TX = 3;
const int PIN_LED = 8;
SoftwareSerial bt(PIN_BT_RX, PIN_BT_TX);
void setup() {
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, LOW);
Serial.begin(9600);
bt.begin(9600);
Serial.println(F("HC-05 en datos a 9600. Enlace local, sin cuenta."));
Serial.println(F("Envia 1 para encender y 0 para apagar."));
}
void loop() {
if (bt.available()) {
char c = bt.read();
Serial.print(F("recibido "));
Serial.println(c);
if (c == '1') digitalWrite(PIN_LED, HIGH);
else if (c == '0') digitalWrite(PIN_LED, LOW);
}
}
Pasos
Alimenta el HC-05 a 5 V en su VCC de breakout y une las masas. La patilla RX del módulo se queda detrás del level shifter.
Conecta TX del HC-05 a D2 y D3 al lado de alta del shifter, y el lado de baja del shifter al RX del HC-05.
Pon el LED con su resistencia en D8. No uses D13 si te confunde el LED de la placa.
Sube el sketch con el módulo ya cableado, pero no enlazado todavía al monitor por D0 y D1.
En el teléfono, empareja el HC-05 como cualquier Bluetooth clásico. La clave de fábrica suele ser 1234 o 0000, y es una clave del propio módulo, no una cuenta en internet.
Abre un terminal Bluetooth serie en el teléfono, a 9600 baudios, y manda el carácter 1 y luego el 0.
En el monitor USB, también a 9600, debes ver el carácter que llegó. Si abres AT por error, recuerda que esa velocidad es 38400 y aquí no la usamos.
Variantes
Acepta también las letras E y A, además de 1 y 0, para un mando más claro.
Haz que un carácter desconocido deje el LED como está y escriba un aviso por el USB.
Añade un segundo LED en otro pin y conmuta con los caracteres 2 y 3, manteniendo 9600.
Depuración
Si el monitor USB muestra basura, estás a otra velocidad. Datos del HC-05: 9600. No mezcles con 38400, que es la del modo AT.
Si el teléfono empareja pero no llega nada, TX y RX están cruzados al revés o el shifter no tiene referencia de 3,3 V.
Si el módulo no aparece, KEY está a alto y arrancó en AT. Déjalo a bajo y vuelve a alimentar.
Si el LED no cambia y el carácter sí llega al USB, el fallo está en D8 o en la resistencia, no en el radio.
Vas a leer un mando infrarrojo doméstico con un receptor TSOP de 38 kHz y a quedarte con un código concreto grabado en el sketch. La primera vez el monitor te enseña el número que llega; luego lo copias a la constante y el LED solo obedece a esa tecla. El decodificado es local, en la propia placa, sin enviar nada a ningún sitio. El sensor deja la línea en reposo a nivel alto y la baja mientras recibe la portadora.
Vas a pasar un byte de una placa a otra por luz infrarroja, sin cables de datos entre ellas. La emisora modula a 38 kHz con tone en el pin del LED infrarrojo, y la receptora, con un TSOP, mide los pulsos y reconstruye el byte. Es un enlace de mesa, óptico y local. Cada placa lleva su propio sketch: no sirven intercambiados.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-07 Enlace IR entre dos placas
// Hay dos sketches. Graba cada uno en su placa.
// ===== Placa emisora. Pines: D3 LED infrarrojo =====
const int PIN_IR = 3;
const byte MENSAJE = 0xA5;
void marca(int us) {
tone(PIN_IR, 38000);
delayMicroseconds(us);
noTone(PIN_IR);
}
void espacio(int us) { delayMicroseconds(us); }
void enviarByte(byte dato) {
marca(2400);
espacio(1200);
for (int b = 0; b < 8; b++) {
marca(600);
espacio((dato & 1) ? 1600 : 600);
dato >>= 1;
}
marca(600);
}
void setup() { pinMode(PIN_IR, OUTPUT); Serial.begin(9600); }
void loop() {
enviarByte(MENSAJE);
Serial.println(F("enviado A5"));
delay(400);
}
// ===== Placa receptora. Pines: D2 TSOP, D8 LED =====
/*
const int PIN_IR = 2;
const int PIN_LED = 8;
bool pulsoAlto(unsigned long &ancho) {
unsigned long t0 = micros();
while (digitalRead(PIN_IR) == HIGH) {
if (micros() - t0 > 6000UL) return false;
}
ancho = micros() - t0;
return true;
}
bool leerByte(byte &dato) {
if (digitalRead(PIN_IR) != LOW) return false;
unsigned long t0 = micros();
while (digitalRead(PIN_IR) == LOW) {
if (micros() - t0 > 5000UL) return false;
}
if (micros() - t0 < 1800UL) return false;
unsigned long espacioCab = 0;
if (!pulsoAlto(espacioCab)) return false;
if (espacioCab < 800UL) return false;
byte v = 0;
for (int b = 0; b < 8; b++) {
t0 = micros();
while (digitalRead(PIN_IR) == LOW) {
if (micros() - t0 > 2000UL) return false;
}
unsigned long espacio = 0;
if (!pulsoAlto(espacio)) return false;
if (espacio > 1000UL) v |= (1 << b);
}
dato = v;
return true;
}
void setup() {
pinMode(PIN_IR, INPUT);
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600);
Serial.println(F("Esperando byte IR"));
}
void loop() {
byte dato = 0;
if (!leerByte(dato)) return;
Serial.println(dato, HEX);
digitalWrite(PIN_LED, dato == 0xA5 ? HIGH : LOW);
delay(50);
digitalWrite(PIN_LED, LOW);
}
*/
Pasos
Monta la emisora: LED infrarrojo y resistencia desde D3 hacia GND. D3 es el pin donde tone genera 38 kHz.
Monta la receptora en la otra placa: TSOP a 5 V, GND y salida a D2, LED visible en D8.
Graba en la primera placa solo el sketch emisor. En la segunda, copia el bloque receptor, quita los comentarios de bloque y no dejes las dos lógicas a la vez.
Enfrenta el LED infrarrojo a la cúpula, a unos cinco centímetros, en la misma mesa.
Abre el monitor de la receptora a 9600 baudios. Debe aparecer A5 varias veces por segundo y el LED debe parpadear.
Tapa el haz con la mano. El monitor tiene que callarse. Si sigue imprimiendo, está leyendo ruido de una lámpara.
Separa las placas hasta que falle y anota la distancia. Este enlace no atraviesa una pared.
Variantes
Cambia MENSAJE a otro byte y comprueba que el LED de la receptora se queda apagado si no actualizas la comparación.
Envía dos bytes seguidos, primero un inicio fijo y luego el dato, para descartar falsos.
Baja el ritmo del delay del emisor a 100 ms y mira si la receptora pierde tramas.
Depuración
Si la receptora no ve nada, el LED emisor es visible y no infrarrojo, o está al revés. Míralo con la cámara del teléfono: el infrarrojo se ve morado.
Si tone no modula, no estás en D3 o el pin quedó como entrada.
Si llegan bytes que no son A5, hay poca señal o demasiado ángulo. Acércalos y alinéalos.
Si grabaste el mismo sketch en las dos placas, una de las dos no está haciendo su papel. Revisa el comentario de cabecera.
Vas a enviar un texto corto de un Uno a otro con dos módulos nRF24L01 y la librería RF24. Cada placa lleva su sketch, emisor o receptor, y comparten canal y dirección. El módulo se alimenta solo a 3,3 V y quiere un condensador pegado entre su alimentación y masa, porque el pico de emisión tira la tensión abajo. Los datos pueden pasar por un level shifter desde el Uno de 5 V. No hay red Wi-Fi ni ningún servicio fuera de las dos placas.
Uno en cada placa, en las líneas que salen del Uno hacia el módulo.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-08 NRF24L01 un mensaje entre dos Unos
// Pines en las dos placas: D9 CE, D10 CSN, D11 MOSI, D12 MISO, D13 SCK
// Alimentacion del radio: 3,3 V y condensador de 10 microfaradios.
// Libreria RF24. Dos sketches, uno por placa.
// ===== Placa emisora =====
#include <SPI.h>
#include <RF24.h>
const int PIN_CE = 9;
const int PIN_CSN = 10;
const int PIN_MOSI = 11;
const int PIN_MISO = 12;
const int PIN_SCK = 13;
RF24 radio(PIN_CE, PIN_CSN);
const byte DIRECCION[6] = "EAG08";
void setup() {
Serial.begin(9600);
Serial.print(F("SPI D11 D12 D13, CE D9, CSN D10, VCC 3,3 V. MOSI "));
Serial.println(PIN_MOSI);
if (!radio.begin()) {
Serial.println(F("Radio no responde. Revisa 3,3 V y el condensador."));
while (true) {}
}
radio.setChannel(76);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);
radio.openWritingPipe(DIRECCION);
radio.stopListening();
}
void loop() {
char texto[8] = "hola";
bool ok = radio.write(texto, sizeof(texto));
Serial.println(ok ? F("enviado") : F("sin ack"));
delay(500);
}
// ===== Placa receptora: mismo cableado, otro sketch =====
/*
#include <SPI.h>
#include <RF24.h>
const int PIN_CE = 9;
const int PIN_CSN = 10;
const int PIN_MOSI = 11;
const int PIN_MISO = 12;
const int PIN_SCK = 13;
RF24 radio(PIN_CE, PIN_CSN);
const byte DIRECCION[6] = "EAG08";
void setup() {
Serial.begin(9600);
if (!radio.begin()) {
Serial.println(F("Radio no responde"));
while (true) {}
}
radio.setChannel(76);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_LOW);
radio.openReadingPipe(1, DIRECCION);
radio.startListening();
Serial.println(F("Escuchando en canal 76"));
}
void loop() {
if (!radio.available()) return;
char texto[8] = {0};
radio.read(texto, sizeof(texto));
Serial.print(F("recibido "));
Serial.println(texto);
}
*/
Pasos
Instala la librería RF24 desde el gestor de librerías del IDE. No busques un servicio en internet dentro del sketch: la librería se queda en tu ordenador.
Alimenta cada nRF24 desde el pin de 3,3 V, nunca desde 5 V. Suelda o pincha el condensador de 10 microfaradios junto al módulo, del lado correcto.
Pasa CE, CSN, MOSI y SCK por el level shifter. MISO puede ir directo a D12.
Graba el sketch emisor en la primera placa y el receptor, descomentando su bloque y quitando el emisor, en la segunda.
Abre los dos monitores a 9600 baudios. Una debe decir enviado y la otra recibido hola.
Si el emisor dice que el radio no responde, para y mide 3,3 V en el módulo antes de seguir probando canales.
Separa las placas un metro, sin paredes, y confirma que el acuse sigue llegando con la potencia baja.
Variantes
Cambia el canal 76 en las dos placas a la vez. Si solo lo cambias en una, dejan de oírse.
Manda un contador en vez del texto fijo y comprueba que no se saltan números.
Sube a RF24_PA_HIGH solo si con baja potencia ya es estable y necesitas más distancia en la misma habitación.
Depuración
Si begin devuelve falso, el módulo no tiene 3,3 V, el condensador está al revés o CSN no está en D10.
Si begin va bien y no hay acuse, las dos placas no comparten dirección o canal, o una sigue en modo escucha.
Si solo falla al emitir, la tensión de 3,3 V se hunde. El condensador falta o el pin de 3,3 V del Uno no da la corriente.
Si al conectar el radio el USB se cae, metiste 5 V en VCC o hay un corto. Desconecta y revisa antes de reprogramar.
Vas a dibujar en un OLED SSD1306 la lectura de un sensor Hall analógico, como una traza que avanza de izquierda a derecha. Usarás las librerías Adafruit_SSD1306 y Adafruit_GFX, y la dirección I2C 0x3C. El gráfico sale de una medida local: el imán acerca o aleja el valor y la curva se mueve. No se envía a ningún sitio. Aprenderás a escalar una lectura de 10 bits a la altura de 64 píxeles.
Vas a encender una tira WS2812 de un solo color con la librería Adafruit_NeoPixel. Todos los LED muestran el mismo rojo suave, sin animación de arcoíris ni mezcla de colores: así ves claro el protocolo de un hilo de datos. En ese hilo pones una resistencia en serie, y entre la alimentación de la tira y masa un condensador grande, cerca del primer LED. La tira va a 5 V. No la alimentes desde un pin de datos ni desde la red.
330 ohmios en serie en el cable de datos, junto al primer LED.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-10 Tira de LEDs direccionables, un color
// Pin: D6 datos, con resistencia de 330 ohmios en serie.
// Libreria Adafruit_NeoPixel. Un solo color en toda la tira.
#include <Adafruit_NeoPixel.h>
const int PIN_DATOS = 6;
const int NUM_LEDS = 8;
Adafruit_NeoPixel tira(NUM_LEDS, PIN_DATOS, NEO_GRB + NEO_KHZ800);
const unsigned long PASO_MS = 40;
unsigned long anterior = 0;
int brillo = 10;
int dir = 1;
void setup() {
tira.begin();
tira.setBrightness(40);
tira.clear();
tira.show();
Serial.begin(9600);
Serial.println(F("Un solo color rojo en D6."));
}
void loop() {
if (millis() - anterior < PASO_MS) return;
anterior = millis();
brillo += dir;
if (brillo >= 80 || brillo <= 5) dir = -dir;
uint32_t color = tira.Color(brillo, 0, 0);
for (int i = 0; i < NUM_LEDS; i++) tira.setPixelColor(i, color);
tira.show();
}
Pasos
Instala Adafruit_NeoPixel desde el gestor de librerías.
Pon la resistencia de 330 ohmios entre D6 y DIN del primer LED. No la olvides: sin ella es fácil estropear esa entrada.
Coloca el condensador grande entre 5 V y GND de la tira, con la raya del electrolítico a masa, cerca del conector.
Une las masas de la placa y de la tira antes de dar alimentación.
Cuenta los LED y ajusta NUM_LEDS. Sube el sketch: todos deben verse del mismo rojo, respirando despacio.
Si ves verde o azul, tu tira no es GRB. Cambia el flag a NEO_RGB y deja un solo color igualmente.
No pidas blanco fuerte a una tira larga desde el USB. Baja el brillo o pasa a una fuente de 5 V.
Variantes
Fija el brillo en 20 y quita la respiración, para dejar un rojo constante.
Prueba el mismo nivel en verde, con Color(0, brillo, 0), y compara consumo a ojo con el calentamiento del cable.
Apaga la mitad de los LED poniendo Color(0, 0, 0) y deja el rojo solo en los pares.
Depuración
Si el primer LED va bien y el resto no, NUM_LEDS es corto o la flecha de la tira está al revés.
Si parpadean colores locos, falta la masa común o el condensador, o el cable de datos es demasiado largo.
Si no luce ninguno, DIN y DOUT están cambiados o la resistencia quedó en el cable de 5 V.
Si el Uno se reinicia al hacer show, la tira chupa del USB. Alimenta la tira aparte y baja el brillo.
Vas a moverte por un menú de tres líneas en el OLED SSD1306 con tres pulsadores, usando la dirección 0x3C y las librerías Adafruit_SSD1306 y Adafruit_GFX. El programa no lee el botón en crudo: espera a que lleve unos milisegundos estable, y distingue el estado de estar en la lista del estado de estar editando un valor. Todo ocurre en la placa, sin salir a ningún servicio externo.
Vas a leer una celda de carga con el HX711 y a convertir las cuentas en gramos con una constante que vive dentro del sketch. Primero taras en vacío, luego apoyas un peso que ya conoces y calculas la escala a mano: cuentas netas partido por los gramos de ese peso. Ese número lo escribes en el programa y vuelves a grabar. No se guarda en ningún servicio externo ni en una cuenta: si cambias de celda, repites la medida y actualizas la constante.
Vas a medir la corriente continua de una carga de baja tensión con el ACS712, leyendo su salida analógica. A mitad de la alimentación el sensor marca cero amperios, y cada amperio se aparta de esa mitad según la versión del chip: 185 mV por amperio en el de 5 A. El ensayo se hace con una carga de pocos voltios, en serie con el sensor. No pongas este módulo en la red de casa: aquí no se mide ni se corta la instalación eléctrica.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-13 Corriente con ACS712
// Pin: A0 salida del sensor
// Version de 5 A: 0,185 V por amperio. Carga de baja tension, sin red.
const int PIN_SENSOR = A0;
const float VREF = 5.0;
const float V_POR_A = 0.185;
float ceroV = 2.5;
void setup() {
Serial.begin(9600);
Serial.println(F("Sin carga, calculo el cero durante un segundo."));
long acum = 0;
for (int i = 0; i < 50; i++) {
acum += analogRead(PIN_SENSOR);
delay(20);
}
ceroV = (acum / 50.0) * (VREF / 1023.0);
Serial.print(F("cero V "));
Serial.println(ceroV, 3);
}
void loop() {
long acum = 0;
for (int i = 0; i < 10; i++) acum += analogRead(PIN_SENSOR);
float v = (acum / 10.0) * (VREF / 1023.0);
float amperios = (v - ceroV) / V_POR_A;
Serial.print(F("V "));
Serial.print(v, 3);
Serial.print(F(" A "));
Serial.println(amperios, 3);
delay(300);
}
Pasos
Deja la carga desconectada. Alimenta el ACS712 a 5 V desde la placa y lleva su salida a A0.
Sube el sketch y mira el cero que imprime. Debe quedar cerca de 2,5 V.
Corta la alimentación de la carga. Inserta el sensor en serie: fuente, borne IP+, borne IP-, carga y vuelta a masa.
Usa una fuente de continua de baja tensión, 5 o 12 V. No uses la red.
Enciende la carga. La corriente debe subir en el sentido que hayas cableado. Si sale negativa, la estás midiendo al revés y basta con leer el valor absoluto o girar los bornes.
Compara con una idea previa: un LED con 220 ohmios a 5 V anda por unos 15 mA, cerca del ruido de este sensor. Una lámpara pequeña de 12 V se ve mucho mejor.
Si tu módulo es de 20 A, cambia V_POR_A a 0,100. Si es de 30 A, a 0,066. Anótalo en el sketch.
Variantes
Resta un offset fijo si tras calibrar el cero aún te quedan 30 mA fantasma.
Muestra miliamperios enteros para no sugerir una precisión que el de 5 A no tiene.
Avisa por el monitor cuando pases de 1 A, como alarma de práctica.
Depuración
Si la tensión se queda en 0, A0 no ve la salida o el módulo no tiene 5 V.
Si marca amperios sin carga, el cero no se tomó bien. Reinicia con la carga apagada.
Si el número se dispara al conectar, el sensor está en paralelo con la fuente. Páralo: es un corto.
Si no distingues el LED, es normal en el de 20 A o 30 A. Pasa a una carga mayor o al módulo de 5 A.
Vas a cerrar un relé de baja tensión de forma periódica y a integrar la corriente que mide el ACS712 mientras pasa el tiempo. El contacto del relé solo corta una carga de 5 a 12 V en continua, nunca la red. Verás la diferencia entre el consumo de la bobina, pequeño, y el de la carga, que es el que de verdad suma amperios por segundo. El acumulado se calcula en la placa con millis y se enseña por el monitor.
Vas a leer la hora de un DS3231 por I2C y, cuando coincida con la hora y el minuto escritos en el sketch, cerrar un relé durante un rato corto. El reloj mantiene la hora con su pila aunque apagues la placa. El relé solo puede manejar una carga de 5 a 12 V en continua, por ejemplo un LED o un timbre de baja tensión. No conmutas la red: una alarma de práctica no es un programador de enchufes de casa.
Vas a mostrar temperatura y presión de un BMP280 en el OLED SSD1306, las dos cosas en la misma mesa y en el mismo bus I2C. El BMP280 trabaja a 3,3 V, así que sus datos pasan por un level shifter desde el Uno de 5 V. La pantalla se inicia en la dirección 0x3C con Adafruit_SSD1306 y Adafruit_GFX, y el sensor en la suya, normalmente 0x76. No hay cuenta ni envío: si desconectas el USB, la medida desaparece con la placa. Es meteo de escritorio, no una estación publicada.
Vas a mover un motor pequeño de persiana de prácticas, en continua y de 5 a 12 V, con dos finales de carrera que marcan arriba y abajo. Si el final de arriba ya está pulsado, el motor no sigue subiendo aunque mantengas el botón. Lo mismo hacia abajo. Si los dos finales están pulsados a la vez, se considera un fallo de cable y el motor se queda quieto. No conmutas la red: no es el motor tubular de una persiana de verdad.
Vas a hacer la maqueta de una barrera de garaje: el ultrasonido mide si hay algo cerca, el servo levanta o baja la barrera y tres LED hacen de semáforo. Rojo con la barrera baja, ámbar mientras se mueve y verde cuando está abierta. El servo se alimenta a 5 V, no a 12 V, y no hay motor de puerta de comunidad ni red de por medio. La distancia se calcula con el tiempo del eco, en la propia placa.
Vas a contar vueltas de un eje con un sensor Hall y un imán, usando la interrupción del pin D2, y a pasar esas vueltas a revoluciones por minuto. El eje lo mueve el motor de continua a través del L298N, con alimentación de 5 a 12 V. No conmutas la red. Cada paso del imán delante del sensor suma una vuelta, así que el imán tiene que ir uno solo y bien sujeto. El cálculo se hace cada segundo, en la placa.
Vas a armar el perro guardián del Uno con avr/wdt.h para que, si el programa se cuelga, la placa se reinicie sola en menos de dos segundos. El rearranque no es ciego: al salir de un reset del watchdog el relé se queda apagado hasta que pulses una confirmación. Así una tarea que se quedó a medias no vuelve a excitar la salida por su cuenta. El relé, si lo usas, solo puede ver una carga de 5 a 12 V. No conmutas la red. El colgado de prueba lo provocas tú manteniendo un botón, para ver el ciclo completo.
Útil si dejas el ensayo fijo, con los dos pulsadores accesibles.
La franja sale del catálogo de Hardware. No es un precio de tienda.
Cableado
Código
// AP-EAG-20 Rearranque seguro con watchdog
// Pines: D8 rele, D7 LED, D2 cuelgue, D4 confirmar
// Libreria de nucleo avr/wdt.h. Carga del rele de 5 a 12 V, sin red.
#include <avr/wdt.h>
const int PIN_RELE = 8;
const int PIN_LED = 7;
const int PIN_CUELGUE = 2;
const int PIN_OK = 4;
const int RELE_ON = LOW;
const int RELE_OFF = HIGH;
uint8_t causaReset __attribute__((section(".noinit")));
void capturaCausa() __attribute__((naked, used, section(".init3")));
void capturaCausa() {
causaReset = MCUSR;
MCUSR = 0;
wdt_disable();
}
bool vinoDelPerro = false;
bool confirmado = false;
unsigned long ultimoLatido = 0;
bool ledAlto = false;
void setup() {
pinMode(PIN_RELE, OUTPUT);
digitalWrite(PIN_RELE, RELE_OFF);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_CUELGUE, INPUT_PULLUP);
pinMode(PIN_OK, INPUT_PULLUP);
vinoDelPerro = causaReset & _BV(WDRF);
Serial.begin(9600);
if (vinoDelPerro) {
Serial.println(F("Reinicio por watchdog. Rele apagado hasta confirmar."));
} else {
Serial.println(F("Arranque normal. Watchdog a 2 s."));
confirmado = true;
}
wdt_enable(WDTO_2S);
}
void loop() {
if (digitalRead(PIN_CUELGUE) == LOW) {
Serial.println(F("Cuelgue voluntario. Suelta el boton."));
while (digitalRead(PIN_CUELGUE) == LOW) {}
}
wdt_reset();
if (vinoDelPerro && !confirmado && digitalRead(PIN_OK) == LOW) {
confirmado = true;
Serial.println(F("Confirmado. Ya puedes armar el rele."));
}
if (confirmado && !vinoDelPerro) {
digitalWrite(PIN_RELE, RELE_OFF);
}
if (millis() - ultimoLatido > 200) {
ultimoLatido = millis();
ledAlto = !ledAlto;
digitalWrite(PIN_LED, ledAlto ? HIGH : LOW);
}
}
Pasos
Conecta el relé a D8, el LED a D7 y los pulsadores de cuelgue y confirmación a D2 y D4, contra masa.
Deja el contacto del relé en una carga de 5 a 12 V, o sin carga la primera vez. No lo lleves a la red.
Sube el sketch. El monitor debe decir arranque normal y el LED tiene que latir.
Mantén el pulsador de cuelgue. El latido se para porque el programa se queda en el while y no llama a wdt_reset.
En menos de dos segundos la placa reinicia. El mensaje tiene que decir que el reinicio viene del watchdog y que el relé sigue apagado.
Pulsa confirmar. Solo entonces el programa da por bueno el rearranque. Suelta y comprueba que no se activa solo al volver de un cuelgue.
Si un clon se queda reiniciando en bucle al grabar, el perro quedó armado durante la subida. Vuelve a grabar justo tras un reset manual y no armes el watchdog hasta que el programa ya esté estable.
Variantes
Pasa el tiempo del perro a WDTO_500MS para ver el reinicio más rápido, y no uses esa ventana si tu bucle tarda más.
Guarda en una variable cuántos rearranques llevas y muéstralo, sabiendo que una variable normal se borra y por eso la causa va en noinit.
Tras confirmar, exige otros cinco segundos de latido sano antes de permitir cerrar el relé.
Depuración
Si nunca reinicia, wdt_reset se está llamando dentro del supuesto cuelgue o el perro no llegó a armarse.
Si reinicia en bucle sin pulsar, el bucle tarda más de dos segundos o el init3 no desarma el perro a tiempo.
Si tras el reinicio el relé pega un golpe, digitalWrite de reposo no es la primera escritura del pin. No pongas nada antes.
Si el monitor no distingue la causa, MCUSR se leyó tarde y el bootloader ya lo había borrado. La función en init3 tiene que seguir ahí.