Ejercicio 03 — Mini-CLI
Dificultad: amarillo · Módulo 05 (Comunicacion serie)
Enunciado
Mini-CLI. Leer una línea completa del serie (hasta ‘\n’) y ejecutar comandos: “led on”, “led off”, “blink”, “help”.
Montaje (Arduino UNO)
LED + 220 ohm en el pin 8 (a GND)
Cómo se resuelve
Un carácter suelto da para dos órdenes. Aquí se pasa a palabras: led on, led off, blink y help. El montaje es el mismo del ejercicio anterior, un LED con su resistencia de 220 Ω en el pin 8, y todo lo nuevo está en cómo se recibe el texto.
El cambio de fondo es que ya no se lee un carácter, se lee una línea:
String linea = Serial.readStringUntil('\n');
linea.trim();readStringUntil va sacando caracteres del buffer y los va juntando en un String hasta encontrar el delimitador que le has pedido, aquí el salto de línea '\n'. Ese delimitador lo consume pero no lo mete en el resultado, así que lo que te queda es el comando limpio. El '\n' es el “Enter”: es la señal de que el comando ha terminado y ya se puede interpretar.
De ahí sale la trampa clásica de este ejercicio, y está avisada en la propia cabecera del sketch: el monitor serie tiene que enviar ese '\n'. En el IDE de Arduino, el desplegable de al lado del campo de envío tiene las opciones “Sin ajuste de línea”, “Nueva línea”, “Retorno de carro” y “Ambos NL & CR”. Si está en “Sin ajuste de línea”, al pulsar Enviar solo viaja tu texto y el delimitador no llega nunca. Ponlo en “Nueva línea”. Si tu comando parece correcto y no pasa nada de nada, mira ese desplegable antes de dudar del código.
La segunda línea, linea.trim(), parece cosmética y no lo es. Si el monitor está en “Ambos NL & CR”, justo antes del '\n' llega un retorno de carro '\r' que sí acaba dentro del String. Tu comando pasa a ser "led on\r", que no es igual a "led on", y la comparación falla por un carácter que no se ve. Lo mismo con un espacio de más al final si se te escapa al teclear. trim() elimina los espacios y caracteres de control de los dos extremos del texto y hace que el programa funcione en las cuatro configuraciones del monitor. Es una línea que compra mucha tranquilidad.
Con la línea ya limpia, el resto es una cadena de comparaciones. Un String se compara con == directamente contra el texto entre comillas dobles, sin funciones raras:
if (linea == "led on") { digitalWrite(PIN_LED, HIGH); Serial.println("OK: LED encendido"); }
else if (linea == "led off") { digitalWrite(PIN_LED, LOW); Serial.println("OK: LED apagado"); }Cada rama responde con un OK: por serie. Eso es la mitad de lo que hace que esto se sienta como una consola de verdad: escribes una orden y el sistema te contesta que la ha ejecutado.
El comando blink es distinto de los otros dos porque no fija un estado, ejecuta una acción con duración:
for (int i = 0; i < 3; i++) {
digitalWrite(PIN_LED, HIGH);
delay(200);
digitalWrite(PIN_LED, LOW);
delay(200);
}
Serial.println("OK: blink x3");Tres parpadeos de 200 ms encendido y 200 ms apagado, es decir, 1200 ms en total en los que la placa está congelada dentro del bucle. Durante esos 1,2 segundos no se atiende al serie: si escribes otro comando mientras parpadea, no se pierde (se queda en el buffer, que para eso está) pero no se ejecuta hasta que termine. Es la misma limitación de delay que arrastras desde el módulo 01, y ahora se nota más porque hay alguien esperando respuesta al otro lado. Fíjate en que el OK: blink x3 se imprime después del bucle, no antes: se confirma lo hecho, no lo que se va a hacer.
Las dos últimas ramas son las que separan un programa de juguete de uno usable. help llama a ayuda(), la función que también se llama desde setup() para que la lista de comandos aparezca nada más arrancar. Tenerla como función y no como un println suelto significa que la lista está escrita en un solo sitio: el día que añadas un comando, actualizas esa línea y se actualizan a la vez el saludo inicial, la respuesta a help y el mensaje de error.
Y el error:
else if (linea.length() > 0) {
Serial.print("Comando desconocido: ");
Serial.println(linea);
ayuda();
}La condición linea.length() > 0 está ahí por una razón muy concreta: si pulsas Enviar con el campo vacío, o si el monitor manda un '\n' suelto de más, llega una línea vacía que no es un comando desconocido, es simplemente nada. Sin esa comprobación el programa te escupiría un “Comando desconocido:” seguido de nada cada vez que se cuela un salto de línea perdido. Cuando el comando sí tiene texto y no coincide con ninguno, el programa hace lo correcto: te dice qué no ha entendido (repitiendo tu texto, que sirve además para ver si llegó lo que creías) y te vuelve a enseñar la ayuda.
Eso es una CLI en miniatura, y el patrón se generaliza tal cual: leer una línea, limpiarla, compararla contra una lista de comandos conocidos y responder algo siempre, incluso cuando no se entiende.
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 05: Comunicación serie
* Ejercicio 03 — PRÁCTICA (rellena los TODO)
* Enunciado: Mini-CLI. Leer una línea completa del serie (hasta '\n') y
* ejecutar comandos: "led on", "led off", "blink", "help".
* Dificultad: amarillo
* Placa: Arduino UNO Montaje: LED + 220 ohm en el pin 8 (a GND)
* Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README)
* Pon el monitor serie en modo "Nueva línea" (envía '\n' al pulsar Enviar).
*/
const int PIN_LED = 8;
void ayuda() {
Serial.println("Comandos: led on | led off | blink | help");
}
void setup() {
// TODO: configura PIN_LED como salida
// TODO: abre el puerto serie a 9600 baudios
// TODO: muestra la ayuda llamando a ayuda()
}
void loop() {
// TODO: if (Serial.available() > 0) { ... }
// TODO: lee una línea -> String linea = Serial.readStringUntil('\n');
// TODO: limpia la línea -> linea.trim();
// TODO: si linea == "led on" -> enciende el LED e informa "OK: LED encendido"
// TODO: si linea == "led off" -> apaga el LED e informa "OK: LED apagado"
// TODO: si linea == "blink" -> parpadea 3 veces con un for y delay()
// TODO: si linea == "help" -> llama a ayuda()
// TODO: si no está vacía y no coincide -> "Comando desconocido" + ayuda()
}Solución — cópiala y ejecútala
/*
* Curso de Arduino — Módulo 05: Comunicación serie
* Ejercicio 03 — MODELO (resuelto)
* Enunciado: Mini-CLI. Leer una línea completa del serie (hasta '\n') y
* ejecutar comandos: "led on", "led off", "blink", "help".
* Dificultad: amarillo
* Placa: Arduino UNO Montaje: LED + 220 ohm en el pin 8 (a GND)
* Correr: Arduino IDE / Wokwi (wokwi/ej03) / arduino-cli (ver README)
* Pon el monitor serie en modo "Nueva línea" (envía '\n' al pulsar Enviar).
*/
const int PIN_LED = 8;
void ayuda() {
Serial.println("Comandos: led on | led off | blink | help");
}
void setup() {
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600);
ayuda();
}
void loop() {
if (Serial.available() > 0) { // ¿hay algo en el buffer?
String linea = Serial.readStringUntil('\n'); // lee hasta el salto de línea
linea.trim(); // quita espacios y '\r' sobrantes
if (linea == "led on") {
digitalWrite(PIN_LED, HIGH);
Serial.println("OK: LED encendido");
} else if (linea == "led off") {
digitalWrite(PIN_LED, LOW);
Serial.println("OK: LED apagado");
} else if (linea == "blink") {
for (int i = 0; i < 3; i++) { // parpadea 3 veces
digitalWrite(PIN_LED, HIGH);
delay(200);
digitalWrite(PIN_LED, LOW);
delay(200);
}
Serial.println("OK: blink x3");
} else if (linea == "help") {
ayuda();
} else if (linea.length() > 0) { // texto no vacío y desconocido
Serial.print("Comando desconocido: ");
Serial.println(linea);
ayuda();
}
}
}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-led", "id": "led1", "top": -110, "left": 130, "attrs": { "color": "red" } },
{ "type": "wokwi-resistor", "id": "r1", "top": -40, "left": 120, "attrs": { "value": "220" } }
],
"connections": [
[ "uno:8", "r1:1", "green", [ "v0" ] ],
[ "r1:2", "led1:A", "green", [ "v0" ] ],
[ "led1:C", "uno:GND.1", "black", [ "v0" ] ]
],
"dependencies": {}
}En VS Code, con el repo del curso
El paquete de simulación de este ejercicio está en practica/05-comunicacion-serie/wokwi/ej03/. Con la extensión Wokwi instalada y la licencia activada (lo cuenta el onboarding del curso):
make sim EJ=05-comunicacion-serie/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.