Instrumentos y Mediciones 2 · UNPSJB · Instructivo Técnico v1.0

Instrumentos Virtuales
Arduino UNO + HMI Web

Material de desarrollo para la cátedra Instrumentos y Mediciones 2 — Facultad de Ingeniería, UNPSJB. Guía para implementar instrumentos de medición y control con firmware C++ en Arduino UNO y panel de visualización HTML/JS/CSS conectado vía USB serial.

// 01

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.

⚙ Arduino UNO ←→ ⚡ USB Serial ←→ 🖥 Navegador (HMI)
Pines analógicos (A0–A5) ──→ ADC 10-bit/8-bit ──→ UART TX ──→ USB CDC ──→ Web Serial API ──→ Canvas/SVG
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.

ℹ️
Compatibilidad de navegadores Web Serial API funciona en Google Chrome 89+ y Microsoft Edge 89+. No es compatible con Firefox ni Safari actualmente.
// 02

Requisitos y herramientas

ComponenteDescripciónNecesario
Arduino UNO R3Microcontrolador ATmega328P con USB integrado✅ Sí
Arduino IDEv2.x recomendado — arduino.cc/en/software✅ Sí
Google Chrome / Edge89+ para Web Serial API✅ Sí
Editor de códigoVS Code, Sublime Text u otroRecomendado
Cable USB-A a USB-BComunicación y alimentación del Arduino✅ Sí
MultímetroPara verificar señales de entradaRecomendado

Instalación rápida

  1. Instalar Arduino IDE desde arduino.cc/en/software y verificar que reconoce el Arduino UNO al conectarlo.
  2. En el IDE, ir a Herramientas → Puerto y seleccionar el puerto COM asignado (ej: COM3 en Windows, /dev/ttyACM0 en Linux).
  3. Crear un archivo HTML local para la interfaz HMI. No se requieren dependencias npm.
  4. Abrir el archivo HTML en Chrome. Al conectar el Arduino, el navegador pedirá permiso para acceder al puerto serial.
⚠️
Conflicto de puertos El Arduino IDE y la HMI web no pueden usar el puerto serial simultáneamente. Cerrar el Monitor Serie del IDE antes de abrir la conexión desde el navegador.
// 03

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

Arduino → HMI CLAVE:VALOR\n  —  ejemplo: V0:128\n
HMI → Arduino CMD:PARAM\n  —  ejemplo: LED:1\n
Multi-valor DATA:128,210,95\n  —  separador coma
JSON (avanzado) {"V0":128,"V1":64,"T":25.3}\n
💡
Recomendación de baud rate Usar 115200 bps para instrumentos que envían datos rápido (ej: osciloscopio). Para sensores lentos (temperatura, humedad) 9600 bps es suficiente y más robusto.

Tabla de comandos estándar

Comando (HMI→ARD)DescripciónEjemplo
STARTInicia envío continuo de datosSTART\n
STOPPausa el envío de datosSTOP\n
RATE:NEstablece intervalo de muestreo en msRATE:100\n
LED:0|1Controla LED de salidaLED:1\n
PWM:NAjusta duty cycle PWM (0–255)PWM:128\n
MODE:NCambia modo de operación del instrumentoMODE:2\n
PIN?:NSolicita lectura puntual de pin analógicoPIN?:A0\n
Dato (ARD→HMI)DescripciónEjemplo
V0:NValor ADC de 8 o 10 bitsV0:255\n
V1:NValor ADC canal 1V1:120\n
TEMP:N.NTemperatura con decimalTEMP:25.4\n
OKConfirmación de comando recibidoOK\n
ERR:MSGError con descripciónERR:CMD_UNKNOWN\n
HELLO:v1.0Identificación al conectarHELLO:v1.0\n
// 04

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()

C++ / Arduino — sketch_instrumento.ino
// =============================================
// 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

C++ / Arduino — función sendSamples()
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

C++ / Arduino — readCommands() + interpretCommand()
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);
  }
}
// 05

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.

Clic "Conectar" navigator.serial.requestPort() port.open() TextDecoderStream \n Interprete DATA (8-bit) Actualizar UI
Desconectado
000
Último ADC (8-bits)
0.00 V
Voltaje Calculado
💡
Lógica del Buffer de Barrido El script de abajo inyecta las muestras en bloque en un 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.
// 06

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.

C++ / Arduino — voltimetro.ino
// 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).

C++ / Arduino — osciloscopio.ino
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();
}
📐
Optimización del canal por 8-bits Al bajar la resolución a 8 bits e implementar justificación a la izquierda (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.
// 07

Debug y resolución de errores

SíntomaCausa probableSolució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:

Monitor Serie — salida esperada tras enviar "START"
← (escribir en Monitor Serie):  START
→ (respuesta Arduino):          HELLO:scope-8bit
                                DATA:127,130,135,142...
← (escribir):                    STOP
                                OK
🛠️
Consejo de desarrollo iterativo Desarrollar y probar el firmware primero con el Monitor Serie, sin involucrar la HMI. Una vez confirmado que el protocolo funciona correctamente, conectar la interfaz web. Esto aísla problemas y acelera el desarrollo.