Ejercicio 03 — Contar pulsaciones del botón e imprimir el total por Serial
Dificultad: amarillo · Módulo 02 (Entradas digitales)
Enunciado
Contar pulsaciones del botón e imprimir el total por Serial. Contar UNA vez por pulsación detectando el flanco de bajada (paso de suelto=HIGH a pulsado=LOW).
Montaje (Arduino UNO)
Botón entre pin 2 y GND (LED opcional)
Cómo se resuelve
Aquí es donde el antirrebote del ejercicio anterior deja de ser una precaución teórica y se vuelve imprescindible. El programa cuenta cuántas veces has pulsado el botón y escribe el total por el puerto serie. El montaje se simplifica: basta el botón entre el pin 2 y GND; el LED es opcional porque ya no se usa.
Para ver la salida hay que abrir el Monitor Serie a 9600 baudios, la misma velocidad que declara el programa. Si lo abres a otra velocidad verás caracteres basura, que es el fallo más habitual del ejercicio junto con olvidar abrirlo.
En setup() hay dos novedades:
pinMode(PIN_BOTON, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Pulsaciones: 0");Serial.begin(9600) arranca la comunicación por USB con el ordenador; sin esa línea el programa funciona pero no dice nada. Va en setup() porque es una preparación que se hace una vez. Y ese Serial.println inicial imprime el estado de partida nada más arrancar, que es un detalle pequeño pero útil: te confirma que la placa ha reseteado y que el monitor está bien configurado, antes incluso de tocar el botón.
Las variables de estado son casi las del ejercicio 02, con una añadida:
int estadoEstable = HIGH;
int lecturaAnterior = HIGH;
unsigned long tUltimoCambio = 0;
unsigned long contador = 0;contador es unsigned long por coherencia y por holgura: nunca va a ser negativo y así aguanta más de cuatro mil millones de pulsaciones sin desbordarse. Y, como las otras, vive fuera del loop() porque su razón de existir es sobrevivir entre vueltas.
El loop() empieza exactamente igual que el del ejercicio 02: lectura cruda, cronómetro que se reinicia con cada cambio, y filtro de 50 ms. Ese esqueleto ya no se comenta, se reutiliza. Lo nuevo está en lo que ocurre dentro del filtro:
if (lectura != estadoEstable) {
estadoEstable = lectura;
// Flanco de bajada: el estado filtrado pasó a LOW (botón recién pulsado).
if (estadoEstable == LOW) {
contador++;
Serial.print("Pulsaciones: ");
Serial.println(contador);
}
}Ahí dentro está la idea central del ejercicio: la diferencia entre nivel y flanco.
El nivel responde a “¿el botón está pulsado?”, y es cierto durante todo el tiempo que mantengas el dedo encima. El flanco responde a “¿el botón se acaba de pulsar?”, y es cierto durante un único instante. Es la diferencia entre “la puerta está abierta” y “la puerta se acaba de abrir”.
Si contaras el nivel, escribiendo if (digitalRead(PIN_BOTON) == LOW) contador++;, el loop() daría miles de vueltas por segundo mientras aguantas el botón y te sumaría miles de pulsaciones por cada dedo apoyado. Es un error que parece absurdo escrito así, y que sin embargo comete casi todo el mundo la primera vez, porque leyendo el código en voz alta suena razonable: “si el botón está pulsado, suma uno”. El programa lo hace, literalmente, miles de veces por segundo.
La detección de flanco no necesita nada nuevo, solo aprovechar lo que ya hay. El if (lectura != estadoEstable) significa “el estado filtrado ha cambiado respecto a lo que yo creía”, y eso ocurre una sola vez por cada cambio real, justo en la vuelta en la que se acepta. Dentro de ese bloque ya estás, por construcción, en el instante del cambio. Lo único que queda es elegir qué cambio te interesa, y para eso está el if (estadoEstable == LOW): como con INPUT_PULLUP la lógica está invertida, pasar a LOW es el momento de pulsar. Eso es un flanco de bajada.
Si quitaras esa comprobación contarías el doble: sumarías uno al pulsar y otro al soltar, porque soltar también es un cambio de estado (un flanco de subida). Puede que en algún proyecto eso sea justo lo que quieres, pero aquí no: una pulsación es un incremento.
Fíjate en la disposición de los cuatro if anidados, porque cada uno descarta un tipo de falso positivo distinto. El primero apunta cuándo se movió la señal. El segundo descarta los rebotes, dejando pasar solo lo que lleva 50 ms quieto. El tercero descarta las vueltas en las que no ha cambiado nada, dejando pasar solo el instante del cambio. El cuarto descarta el flanco de soltar. Quítale cualquiera de los cuatro y el contador se dispara o se duplica; entender qué se rompe al quitar cada uno vale más que memorizar el bloque entero.
La impresión usa dos funciones distintas a propósito:
Serial.print("Pulsaciones: ");
Serial.println(contador);Serial.print escribe sin saltar de línea y Serial.println escribe y salta. Así el texto y el número salen en la misma línea y cada pulsación ocupa una línea nueva. Si usaras println en los dos, el número aparecería debajo de la etiqueta.
La prueba de fuego es sencilla: pulsa diez veces despacio y comprueba que el monitor marca exactamente diez. Luego pulsa manteniendo el dedo tres segundos y comprueba que sigue sumando uno. Si sube mientras aguantas, estás contando nivel; si sube de dos en dos, te falta filtrar el flanco de subida; si sube a saltos irregulares de tres o cuatro, el antirrebote no está haciendo su trabajo.
Para practicar — cópialo y complétalo
Pega este esqueleto y completa los TODO. Es la mejor forma de aprender: inténtalo antes de mirar la solución.
/*
* Curso de Arduino — Módulo 02: Entradas digitales
* Ejercicio 03 — PRÁCTICA (rellena los TODO)
* Enunciado: Contar pulsaciones del botón e imprimir el total por Serial. Contar UNA vez por
* pulsación detectando el flanco de bajada (paso de suelto=HIGH a pulsado=LOW).
* Dificultad: amarillo
* Placa: Arduino UNO Montaje: botón entre pin 2 y GND (LED opcional)
* Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README) — abre el Monitor Serie a 9600
*
* Idea: "flanco" = el instante del cambio, no el nivel mantenido. Si contáramos mientras el pin
* está LOW, sumaríamos miles de veces por pulsación. Comparamos lectura actual vs anterior.
*/
const int PIN_BOTON = 2;
const unsigned long ANTIRREBOTE = 50;
int estadoEstable = HIGH;
int lecturaAnterior = HIGH;
unsigned long tUltimoCambio = 0;
unsigned long contador = 0;
void setup() {
// TODO: configura PIN_BOTON como INPUT_PULLUP
// TODO: inicia el puerto serie -> Serial.begin(9600)
}
void loop() {
// TODO: lee el pin -> int lectura = digitalRead(PIN_BOTON)
// TODO: antirrebote igual que en ej02: si lectura != lecturaAnterior reinicia tUltimoCambio
// TODO: cuando (millis() - tUltimoCambio) > ANTIRREBOTE y lectura != estadoEstable:
// actualiza estadoEstable = lectura
// si estadoEstable == LOW (flanco de bajada): contador++ e imprime el total por Serial
}Solución — cópiala y ejecútala
/*
* Curso de Arduino — Módulo 02: Entradas digitales
* Ejercicio 03 — MODELO (resuelto)
* Enunciado: Contar pulsaciones del botón e imprimir el total por Serial. Contar UNA vez por
* pulsación detectando el flanco de bajada (paso de suelto=HIGH a pulsado=LOW).
* Dificultad: amarillo
* Placa: Arduino UNO Montaje: botón entre pin 2 y GND (LED opcional)
* Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README) — abre el Monitor Serie a 9600
*
* Idea: "flanco" = el instante del cambio, no el nivel mantenido. Si contáramos mientras el pin
* está LOW, sumaríamos miles de veces por pulsación. Comparamos lectura actual vs anterior.
*/
const int PIN_BOTON = 2;
const unsigned long ANTIRREBOTE = 50;
int estadoEstable = HIGH;
int lecturaAnterior = HIGH;
unsigned long tUltimoCambio = 0;
unsigned long contador = 0;
void setup() {
pinMode(PIN_BOTON, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Pulsaciones: 0");
}
void loop() {
int lectura = digitalRead(PIN_BOTON);
if (lectura != lecturaAnterior) {
tUltimoCambio = millis();
lecturaAnterior = lectura;
}
if ((millis() - tUltimoCambio) > ANTIRREBOTE) {
if (lectura != estadoEstable) {
estadoEstable = lectura;
// Flanco de bajada: el estado filtrado pasó a LOW (botón recién pulsado).
if (estadoEstable == LOW) {
contador++;
Serial.print("Pulsaciones: ");
Serial.println(contador);
}
}
}
}Cómo ejecutar la simulación
Sin instalar nada, en el navegador
- Abre wokwi.com y crea un proyecto nuevo de la placa que pide el ejercicio.
- Pega la solución (o tu intento) en la pestaña
sketch.ino. - Abre la pestaña
diagram.json, borra lo que haya y pega el circuito de aquí abajo. - Dale al play. Verás el circuito funcionando: LEDs, pantallas y monitor serie.
Circuito del ejercicio (diagram.json)
{
"version": 1,
"author": "ESIBot",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-arduino-uno", "id": "uno", "top": 0, "left": 0, "attrs": {} },
{ "type": "wokwi-pushbutton", "id": "btn", "top": -100, "left": 200, "attrs": { "color": "green" } }
],
"connections": [
[ "uno:2", "btn:1.l", "green", [] ],
[ "btn:2.l", "uno:GND.1", "black", [] ]
],
"dependencies": {}
}En VS Code, con el repo del curso
El paquete de simulación de este ejercicio está en practica/02-entradas-digitales/wokwi/ej03/. Con la extensión Wokwi instalada y la licencia activada (lo cuenta el onboarding del curso):
make sim EJ=02-entradas-digitales/ej03y después, en VS Code, Ctrl+Shift+P → Wokwi: Start Simulator. La extensión no compila: simula un firmware ya compilado, y de compilarlo se encarga ese make.
Cómo usarlo
Con placa: abre el .ino en el Arduino IDE, elige placa y puerto y pulsa Subir. Sin placa: simulalo en Wokwi con cualquiera de las dos vias de arriba. Antes de mirar la solucion, intenta completar tu el esqueleto: es la mejor forma de aprender.