Arquitectura del sistema
El sistema se compone de tres capas: el hardware de adquisición (Arduino UNO con sus periféricos), la capa de comunicación (protocolo serial sobre USB), y la interfaz HMI (aplicación web en el navegador). Esta arquitectura es el punto de partida para los trabajos prácticos de la cátedra.
HMI ──→ Comando texto ──→ USB CDC ──→ UART RX ──→ Interprete ──→ digitalWrite / PWM
Arduino UNO
ATmega328P a 16 MHz. ADC configurable por registros, UART a 9600–115200 bps, pines PWM y GPIO.
USB Serial
El chip CH340/ATmega16U2 convierte UART a USB CDC. No requiere driver adicional en Linux/macOS.
Web Serial API
API nativa de Chrome/Edge que permite acceso al puerto COM desde JavaScript sin instalación.
HMI Web
HTML + CSS + Canvas/SVG para visualización. Sin frameworks externos requeridos.
Requisitos y herramientas
| Componente | Descripción | Necesario |
|---|---|---|
| Arduino UNO R3 | Microcontrolador ATmega328P con USB integrado | ✅ Sí |
| Arduino IDE | v2.x recomendado — arduino.cc/en/software | ✅ Sí |
| Google Chrome / Edge | 89+ para Web Serial API | ✅ Sí |
| Editor de código | VS Code, Sublime Text u otro | Recomendado |
| Cable USB-A a USB-B | Comunicación y alimentación del Arduino | ✅ Sí |
| Multímetro | Para verificar señales de entrada | Recomendado |
Instalación rápida
- Instalar Arduino IDE desde
arduino.cc/en/softwarey verificar que reconoce el Arduino UNO al conectarlo. - En el IDE, ir a Herramientas → Puerto y seleccionar el puerto COM asignado (ej:
COM3en Windows,/dev/ttyACM0en Linux). - Crear un archivo HTML local para la interfaz HMI. No se requieren dependencias npm.
- Abrir el archivo HTML en Chrome. Al conectar el Arduino, el navegador pedirá permiso para acceder al puerto serial.
Protocolo de comunicación serial
Se utiliza un protocolo de texto simple, legible y fácil de depurar. Cada mensaje es una línea terminada en \n. El diseño prioriza la simplicidad sobre la eficiencia, lo que facilita el aprendizaje y la verificación de señales — criterio clave en el contexto de la cátedra.
Formato de trama
CLAVE:VALOR\n — ejemplo: V0:128\n
CMD:PARAM\n — ejemplo: LED:1\n
DATA:128,210,95\n — separador coma
{"V0":128,"V1":64,"T":25.3}\n
Tabla de comandos estándar
| Comando (HMI→ARD) | Descripción | Ejemplo |
|---|---|---|
START | Inicia envío continuo de datos | START\n |
STOP | Pausa el envío de datos | STOP\n |
RATE:N | Establece intervalo de muestreo en ms | RATE:100\n |
LED:0|1 | Controla LED de salida | LED:1\n |
PWM:N | Ajusta duty cycle PWM (0–255) | PWM:128\n |
MODE:N | Cambia modo de operación del instrumento | MODE:2\n |
PIN?:N | Solicita lectura puntual de pin analógico | PIN?:A0\n |
| Dato (ARD→HMI) | Descripción | Ejemplo |
|---|---|---|
V0:N | Valor ADC de 8 o 10 bits | V0:255\n |
V1:N | Valor ADC canal 1 | V1:120\n |
TEMP:N.N | Temperatura con decimal | TEMP:25.4\n |
OK | Confirmación de comando recibido | OK\n |
ERR:MSG | Error con descripción | ERR:CMD_UNKNOWN\n |
HELLO:v1.0 | Identificación al conectar | HELLO:v1.0\n |
Firmware Arduino (C++)
La estructura del sketch sigue un patrón no bloqueante basado en temporización con millis(), permitiendo leer sensores, enviar datos y responder comandos de forma concurrente sin usar delay(). Este enfoque es fundamental en instrumentación ya que garantiza la integridad temporal de las mediciones.
Estructura principal — setup() y loop()
// ============================================= // Instrumento Virtual — Firmware base // Arduino UNO + HMI Web via USB Serial // ============================================= const int BAUD_RATE = 115200; const int SAMPLE_PIN = A0; // Pin analógico de entrada const int LED_PIN = 13; // LED integrado unsigned long lastSample = 0; unsigned long sampleRate = 100; // ms entre muestras bool streaming = false; String inputBuffer = ""; // Buffer para comandos entrantes void setup() { Serial.begin(BAUD_RATE); pinMode(LED_PIN, OUTPUT); // Esperar a que el puerto serie esté listo while (!Serial) { delay(10); } // Mensaje de identificación al conectar Serial.println("HELLO:v1.0"); } void loop() { readCommands(); // Procesar comandos entrantes sendSamples(); // Enviar datos si streaming activo }
Envío de datos de sensores
void sendSamples() { if (!streaming) return; unsigned long now = millis(); if (now - lastSample < sampleRate) return; lastSample = now; // Leer canal analógico (0 a 1023) int raw = analogRead(SAMPLE_PIN); // Convertir a tensión (0.0 – 5.0 V) float voltage = raw * (5.0 / 1023.0); // Enviar en formato CLAVE:VALOR Serial.print("V0:"); Serial.println(raw); Serial.print("VV:"); Serial.println(voltage, 3); }
Intérprete de comandos desde la HMI
void readCommands() { while (Serial.available() > 0) { char c = (char) Serial.read(); if (c == '\n') { inputBuffer.trim(); // Eliminar \r y espacios if (inputBuffer.length() > 0) { interpretCommand(inputBuffer); } inputBuffer = ""; // Limpiar buffer } else { inputBuffer += c; // Acumular caracteres } } } void interpretCommand(String cmd) { int sep = cmd.indexOf(':'); String key = (sep > 0) ? cmd.substring(0, sep) : cmd; String param = (sep > 0) ? cmd.substring(sep + 1) : ""; if (key == "START") { streaming = true; Serial.println("OK"); } else if (key == "STOP") { streaming = false; Serial.println("OK"); } else if (key == "RATE") { sampleRate = param.toInt(); Serial.println("OK"); } else if (key == "LED") { digitalWrite(LED_PIN, param.toInt() ? HIGH : LOW); Serial.println("OK"); } else if (key == "PWM") { analogWrite(9, param.toInt()); Serial.println("OK"); } else { Serial.print("ERR:CMD_UNKNOWN:"); Serial.println(key); } }
Interfaz HMI en HTML/JS (Modo 8-bits)
La Web Serial API permite al navegador comunicarse con dispositivos serie sin instalar intermediarios. El flujo implementado decodifica el stream binario entrante, lo agrupa mediante un transformador por saltos de línea (\n), analiza la presencia de la cabecera DATA: y vuelca las muestras de 8 bits separadas por comas directamente al Canvas y los visualizadores digitales.
Float32Array y desplaza un puntero vertical (cursor rojo). Al operar a 8 bits, las matemáticas dividen sobre un máximo de 255 para el eje Y y el cálculo de la tensión.
Ejemplos de instrumentos completos
Ejemplo 1 — Voltímetro digital
Mide la tensión en el pin A0 (0–5 V), calcula promedio de 16 muestras para reducir ruido, y envía el resultado cada 200 ms.
// Voltímetro con promediado de muestras const int N_SAMPLES = 16; unsigned long lastMs = 0; bool streaming = false; String cmdBuf = ""; void setup() { Serial.begin(115200); while (!Serial); Serial.println("HELLO:voltimetro-v1"); } float readVoltage() { long sum = 0; for (int i = 0; i < N_SAMPLES; i++) { sum += analogRead(A0); delayMicroseconds(200); } return (sum / N_SAMPLES) * (5.0 / 1023.0); } void loop() { while (Serial.available()) { char c = Serial.read(); if (c == '\n') { cmdBuf.trim(); if (cmdBuf == "START") { streaming = true; Serial.println("OK"); } if (cmdBuf == "STOP") { streaming = false; Serial.println("OK"); } cmdBuf = ""; } else { cmdBuf += c; } } if (streaming && millis() - lastMs >= 200) { lastMs = millis(); float v = readVoltage(); Serial.print("VOLT:"); Serial.println(v, 4); } }
Ejemplo 2 — Osciloscopio rápido de 8 bits
Captura bloques de 50 muestras del pin A0 en formato de 8 bits a través del registro ADCH con el prescaler forzado a 16 (~76.9 kS/s por hardware).
const int BLOCK = 50; // Muestras por paquete bool run = false; String buf = ""; void setup() { Serial.begin(115200); while (!Serial); // Desactivar buffer digital en A0 para reducir ruido de conmutación DIDR0 |= (1 << ADC0D); // Configurar AVCC como referencia y activar ADLAR para justificación a la izquierda (8 bits en ADCH) ADMUX = (1 << REFS0) | (1 << ADLAR); // Cambiar el Prescaler a 16 (Reloj ADC a 1 MHz, conversión en 13 µs) ADCSRA &= ~( (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) ); ADCSRA |= (1 << ADPS2); // Activar el hardware del ADC ADCSRA |= (1 << ADEN); Serial.println("HELLO:scope-8bit"); } void loop() { while (Serial.available()) { char c = Serial.read(); if (c == '\n') { buf.trim(); if (buf == "START") run = true; if (buf == "STOP") run = false; buf = ""; } else { buf += c; } } if (!run) return; // Buffer optimizado de un byte por elemento uint8_t samples[BLOCK]; for (int i = 0; i < BLOCK; i++) { ADCSRA |= (1 << ADSC); // Iniciar conversión por registros while (ADCSRA & (1 << ADSC)); // Esperar bandera de hardware samples[i] = ADCH; // Captura directa en un ciclo de reloj } // Enviar ráfaga de datos Serial.print("DATA:"); for (int i = 0; i < BLOCK; i++) { Serial.print(samples[i]); if (i < BLOCK - 1) Serial.print(','); } Serial.println(); }
ADLAR), se prescinde del registro bajo de adquisición (ADCL). El tiempo de conversión física cae a 13 µs y las tramas transmitidas por cable se acortan significativamente en tamaño textual, optimizando el ancho de banda del enlace serie de forma crítica.
Debug y resolución de errores
| Síntoma | Causa probable | Solución |
|---|---|---|
| El botón Conectar no aparece o no abre diálogo | Navegador no soporta Web Serial | Usar Chrome 89+ o Edge 89+. Verificar con navigator.serial en consola. |
| La HMI no recibe datos aunque el Arduino envía | Monitor Serie del IDE abierto | Cerrar el Monitor Serie antes de conectar desde el navegador. |
| Datos corruptos o líneas mezcladas | Baud rate incorrecto | Verificar que el baud rate en Serial.begin() y en port.open() sean iguales. |
| Comando enviado desde HMI no responde | Buffer de entrada sin procesar | Asegurarse de que el loop() llama a readCommands() frecuentemente. |
| Lecturas ADC muy ruidosas | Sin promediado o referencia flotante | Promediar N muestras. Conectar AREF a 3.3 V o usar analogReference(INTERNAL). |
| Puerto COM no aparece en Windows | Driver CH340 faltante | Instalar driver CH340 desde el fabricante o usar Arduino UNO original (ATmega16U2). |
Verificación rápida del protocolo
Antes de abrir la HMI, usar el Monitor Serie del Arduino IDE para verificar que el firmware envía los datos correctamente:
← (escribir en Monitor Serie): START → (respuesta Arduino): HELLO:scope-8bit → DATA:127,130,135,142... ← (escribir): STOP → OK