Caso práctico: epaper-air-quality-logger con Arduino Nano

Caso práctico: epaper-air-quality-logger con Arduino Nano — hero

Objetivo y caso de uso

Qué construirás: Un registrador de calidad del aire de bajo consumo utilizando Arduino Nano 33 IoT, un display e-Paper de Waveshare y un sensor BME680.

Para qué sirve

  • Monitoreo continuo de la calidad del aire en entornos urbanos.
  • Visualización de datos de calidad del aire en tiempo real en un display e-Paper.
  • Registro de datos históricos para análisis de tendencias de contaminación.
  • Integración con sistemas de alerta mediante MQTT para notificaciones en tiempo real.

Resultado esperado

  • Datos de calidad del aire actualizados cada 10 segundos con latencia mínima.
  • Visualización de niveles de CO2, temperatura y humedad en el display e-Paper.
  • Envío de datos a un servidor MQTT con una frecuencia de 1 paquete cada 30 segundos.
  • Capacidad de operar con una duración de batería de más de 6 meses en modo de bajo consumo.

Público objetivo: Ingenieros y entusiastas de IoT; Nivel: Avanzado

Arquitectura/flujo: Arduino Nano 33 IoT -> BME680 -> e-Paper -> MQTT.

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas

  • Sistemas operativos soportados:
  • Linux: Ubuntu 22.04 LTS x86_64
  • macOS: 13 Ventura o 14 Sonoma (Intel/Apple Silicon)
  • Windows 11 Pro/Enterprise (x64)

  • Toolchain exacta:

  • Arduino CLI 0.35.3
  • Core de placa: Arduino SAMD Boards 1.8.14
  • Bibliotecas Arduino (vía Library Manager):
  • GxEPD2 1.5.9
  • Adafruit GFX Library 1.11.9
  • Adafruit BME680 Library 2.0.3
  • Adafruit Unified Sensor 1.1.14
  • Adafruit BusIO 1.14.1
  • ArduinoLowPower 1.2.2
  • FlashStorage_SAMD 1.3.2

Notas:
– El Arduino Nano 33 IoT usa USB nativo. No requiere drivers en macOS ni Linux. En Windows 10/11 se instala como “USB Serial Device (COMx)”; no se necesitan drivers externos.
– Se usa Arduino CLI (no el IDE GUI) para todo el flujo: instalación del core, dependencias, compilación y subida.

Verificación del hardware y entorno

  • Confirmar el puerto serie:
  • Linux/macOS: típico /dev/ttyACM0 o /dev/tty.usbmodemXXXX
  • Windows: COM3, COM4, etc. (ver en “Administrador de dispositivos”)
  • Conexión a Internet para descargar cores y bibliotecas.
  • Cable micro‑USB de datos (no solo carga).

Materiales

  • 1x Arduino Nano 33 IoT (modelo exacto)
  • 1x Módulo Waveshare 2.9″ e‑Paper monocromo con controlador SSD1680 (modelo exacto; versión b/w V2 con SSD1680)
  • 1x Sensor ambiental BME680 (I2C)
  • Cables Dupont macho‑hembra
  • Protoboard (opcional, para ordenar cableado)

Observación sobre alimentación y niveles:
– El Nano 33 IoT funciona a 3.3 V lógicos, compatibles con la pantalla e‑Paper SSD1680 y con el BME680. No usar 5 V en señales.

Preparación y conexión

Disposición de pines y cableado

Para la pantalla Waveshare 2.9″ e‑Paper (SSD1680) se usará SPI. El módulo típico expone: VCC, GND, DIN (MOSI), CLK (SCK), CS, DC, RST, BUSY. No se usa MISO en el panel b/w.

Para el BME680 se usará I2C con alimentación a 3.3 V. La mayoría de breakout boards vienen con regulador y pull‑ups integradas; verificar el serigrafiado de su módulo.

Tabla de conexiones (Nano 33 IoT ↔ periféricos):

Función Nano 33 IoT e‑Paper (SSD1680) BME680 (I2C)
Alimentación 3V3 VCC VIN/3V3
Tierra GND GND GND
SPI MOSI D11 (MOSI) DIN
SPI SCK D13 (SCK) CLK
SPI CS panel D10 CS
SPI DC (data/command) D9 DC
Reset panel D8 RST
Busy panel D7 BUSY
I2C SDA SDA SDA
I2C SCL SCL SCL

Indicaciones:
– Conecte el BME680 a los pines etiquetados “SDA” y “SCL” del Nano 33 IoT (no confundir con A4/A5 propios de placas AVR).
– La pantalla e‑Paper debe alimentarse con 3.3 V. No usar 5 V en VCC ni en señales.
– Mantenga cortos los cables SPI de la e‑Paper para minimizar ruido y artefactos de actualización.

Preparación del entorno de compilación

1) Descargar e instalar Arduino CLI 0.35.3:
– Linux:
– curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
– Verifique versión: arduino-cli version (debe mostrar 0.35.3)
– macOS:
– brew update && brew install arduino-cli
– Verifique: arduino-cli version
– Windows:
– Descargue el binario .exe de Arduino CLI 0.35.3 y añádalo al PATH.
– Verifique: arduino-cli version

2) Instalar el core de la placa SAMD (exacto):
– arduino-cli core update-index
– arduino-cli core install arduino:samd@1.8.14

3) Instalar bibliotecas exactas:
– arduino-cli lib install «GxEPD2@1.5.9»
– arduino-cli lib install «Adafruit GFX Library@1.11.9»
– arduino-cli lib install «Adafruit BME680 Library@2.0.3»
– arduino-cli lib install «Adafruit Unified Sensor@1.1.14»
– arduino-cli lib install «Adafruit BusIO@1.14.1»
– arduino-cli lib install «ArduinoLowPower@1.2.2»
– arduino-cli lib install «FlashStorage_SAMD@1.3.2»

4) Verificar que el FQBN esté disponible:
– arduino-cli board listall | grep -i «Nano 33 IoT»
– Debe listar: arduino:samd:nano_33_iot

Código completo

A continuación se entrega el sketch “epaper-air-quality-logger.ino”. El objetivo:
– Leer cada minuto el BME680 (T, H, P, gas).
– Calibrar un valor de baseline de gas durante los primeros 5 minutos.
– Calcular un índice simple de calidad de aire (IAQ%) basado en gas y humedad.
– Mostrar en e‑Paper: valores actuales y una minigráfica histórica.
– Registrar datos en memoria flash del SAMD21 con un buffer circular persistente.
– Permitir volcado de registros por Serial en CSV cuando se envía el comando “DUMP”.

Notas importantes para el display:
– Para Waveshare 2.9″ b/w V2 (SSD1680) usar la clase GxEPD2_290_T5 (128×296).
– Configuramos pines CS/DC/RST/BUSY según la tabla de conexión.

// epaper-air-quality-logger.ino
// Dispositivo: Arduino Nano 33 IoT + Waveshare 2.9" e-Paper (SSD1680) + BME680
// Toolchain: Arduino CLI 0.35.3, Core SAMD 1.8.14
// Bibliotecas: ver versiones en la sección de prerrequisitos

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
#include <Adafruit_GFX.h>
#include <GxEPD2_BW.h>
#include <Fonts/FreeSans9pt7b.h>
#include <ArduinoLowPower.h>
#include <FlashStorage_SAMD.h>

// Pines e-Paper
#define EPD_CS   10
#define EPD_DC    9
#define EPD_RST   8
#define EPD_BUSY  7

// Instancia display: Waveshare 2.9" V2 (SSD1680) -> GxEPD2_290_T5 128x296
// Nota: GxEPD2 usa Adafruit_GFX como backend gráfico
#include <GxEPD2_3C.h>   // no se usará color; se incluye por compatibilidad
#include <GxEPD2_290.h>  // headers base
// Para SSD1680 (GDEW029T5 o equivalente):
class GxEPD2_290_T5; // forward decl. (incluido en librería)
GxEPD2_BW<GxEPD2_290_T5, GxEPD2_290_T5::HEIGHT> display(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);

// BME680 por I2C
Adafruit_BME680 bme(&Wire);

// Configuración de logging y almacenamiento persistente
#define LOG_CAPACITY 288  // 288 muestras ~ 24 h si muestreamos cada 5 min (ajustable)
#define SAMPLE_PERIOD_MS (60UL * 1000UL)  // 60 s
#define CALIBRATION_TIME_MS (5UL * 60UL * 1000UL)  // 5 min de baseline

struct Measurement {
  uint32_t t_ms;   // tiempo desde arranque (ms)
  float temp;      // °C
  float hum;       // %RH
  float pres;      // hPa
  float gas;       // ohmios
  float iaq;       // 0..100 índice simple (no BSEC)
};

struct LogStore {
  uint32_t magic;     // firma para validar
  uint16_t head;      // próxima posición de escritura
  uint16_t count;     // nº de muestras válidas (<= LOG_CAPACITY)
  float gas_baseline; // baseline persistente del gas
  Measurement data[LOG_CAPACITY];
};

FlashStorage(log_store, LogStore);

static const uint32_t MAGIC = 0xA1Q1E0FF;

LogStore store;
uint32_t last_sample_ms = 0;
bool baseline_locked = false;

// Utilidades de mapeo/clamp
static inline float clampf(float v, float lo, float hi) {
  if (v < lo) return lo;
  if (v > hi) return hi;
  return v;
}

// Cálculo de IAQ simple (0-100) no equivalente a BSEC
float compute_iaq_percent(float gas, float gas_baseline, float humidity) {
  if (gas_baseline <= 0) return 0;
  // Puntuación de gas: mayor resistencia -> mejor aire (menos VOC)
  float gas_score = (gas / gas_baseline) * 100.0f;
  gas_score = clampf(gas_score, 0.0f, 100.0f);

  // Humedad ideal ~ 40% (penaliza desviaciones)
  float hum_score = 100.0f - fabsf(humidity - 40.0f) * 2.5f; // ±40% -> 0
  hum_score = clampf(hum_score, 0.0f, 100.0f);

  // Fusión ponderada (más peso a gas)
  float iaq = 0.75f * gas_score + 0.25f * hum_score;
  return clampf(iaq, 0.0f, 100.0f);
}

void drawHeader() {
  display.setFont(&FreeSans9pt7b);
  display.setTextColor(GxEPD_BLACK);
  display.setCursor(4, 16);
  display.print("epaper-air-quality-logger");
  display.setFont(); // volver a font por defecto para cuerpo
}

void drawReadings(const Measurement& m) {
  char line[48];

  snprintf(line, sizeof(line), "T: %.2f C  H: %.1f %%", m.temp, m.hum);
  display.setCursor(4, 36);
  display.print(line);

  snprintf(line, sizeof(line), "P: %.1f hPa  Gas: %.0f ohm", m.pres, m.gas);
  display.setCursor(4, 52);
  display.print(line);

  snprintf(line, sizeof(line), "IAQ*: %.1f /100  (baseline: %.0f)", m.iaq, store.gas_baseline);
  display.setCursor(4, 68);
  display.print(line);

  display.setCursor(4, 84);
  display.print("*Indice simplificado (no BSEC)");
}

void drawSparkline() {
  // Área de la minigráfica: x=4..292, y=90..120 (altura ~30 px)
  const int x0 = 4, y0 = 120, w = 288, h = 28;
  display.drawRect(x0-1, y0-h-1, w+2, h+2, GxEPD_BLACK);

  if (store.count == 0) {
    display.setCursor(x0, y0 - 8);
    display.print("Sin datos suficientes para graficar.");
    return;
  }

  // Graficar IAQ en 0..100 mapeado a altura
  int points = min((int)store.count, w);
  // Recorremos el log desde el más reciente hacia atrás
  int idx = (int)store.head - 1;
  if (idx < 0) idx = LOG_CAPACITY - 1;

  for (int i = 0; i < points; i++) {
    const Measurement& m = store.data[idx];
    float iaq = clampf(m.iaq, 0.0f, 100.0f);
    int y = y0 - (int)((iaq / 100.0f) * (float)h);
    int x = x0 + (w - 1 - i);
    display.drawPixel(x, y, GxEPD_BLACK);
    if (--idx < 0) idx = LOG_CAPACITY - 1;
  }
}

void epaperFullRefresh(const Measurement& last) {
  display.setFullWindow();
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    drawHeader();
    drawReadings(last);
    drawSparkline();
  } while (display.nextPage());
}

void initStorage() {
  store = log_store.read();
  if (store.magic != MAGIC) {
    memset(&store, 0, sizeof(store));
    store.magic = MAGIC;
    store.head = 0;
    store.count = 0;
    store.gas_baseline = 0.0f;
    log_store.write(store);
  }
}

void appendMeasurement(const Measurement& m) {
  store.data[store.head] = m;
  store.head = (store.head + 1) % LOG_CAPACITY;
  if (store.count < LOG_CAPACITY) store.count++;
  // Escribimos bloque completo (flash) de forma conservadora (1/min)
  log_store.write(store);
}

bool setupBME680() {
  if (!bme.begin(0x76)) {         // la mayoría de BME680 usan 0x76
    if (!bme.begin(0x77)) {       // fallback si el jumper de su módulo selecciona 0x77
      return false;
    }
  }
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320 C durante 150 ms
  return true;
}

bool readBME680(Measurement& m) {
  if (!bme.performReading()) return false;
  m.t_ms = millis();
  m.temp = bme.temperature;          // °C
  m.hum  = bme.humidity;             // %RH
  m.pres = bme.pressure / 100.0f;    // Pa -> hPa
  m.gas  = bme.gas_resistance;       // ohmios
  // Base line: primeras muestras (5 min) para calibrar gas
  if (!baseline_locked && m.t_ms < CALIBRATION_TIME_MS) {
    // Promedio incremental simple
    if (store.gas_baseline <= 0.0f) {
      store.gas_baseline = m.gas;
    } else {
      store.gas_baseline = (store.gas_baseline * 0.99f) + (m.gas * 0.01f);
    }
  } else if (!baseline_locked) {
    baseline_locked = true;
    // Persistir baseline tras calibración
    log_store.write(store);
  }

  float base = (store.gas_baseline > 0.0f) ? store.gas_baseline : m.gas;
  m.iaq = compute_iaq_percent(m.gas, base, m.hum);
  return true;
}

void dumpCSV() {
  Serial.println(F("#epaper-air-quality-logger CSV"));
  Serial.println(F("#t_ms,temp_c,hum_pct,pres_hpa,gas_ohm,iaq_pct"));
  int idx = store.head - store.count;
  if (idx < 0) idx += LOG_CAPACITY;
  for (int i = 0; i < store.count; i++) {
    const Measurement& m = store.data[idx];
    Serial.print(m.t_ms); Serial.print(',');
    Serial.print(m.temp, 2); Serial.print(',');
    Serial.print(m.hum, 1); Serial.print(',');
    Serial.print(m.pres, 1); Serial.print(',');
    Serial.print(m.gas, 0); Serial.print(',');
    Serial.println(m.iaq, 1);
    if (++idx >= LOG_CAPACITY) idx = 0;
  }
  Serial.println(F("#END"));
}

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 3000) { } // breve ventana para consola

  initStorage();

  Wire.begin();
  if (!setupBME680()) {
    Serial.println(F("Error: BME680 no encontrado en 0x76/0x77"));
    // Seguimos para mostrar error en pantalla
  }

  // Inicialización del e-Paper
  display.init(115200); // SPI a 115200kHz internamente optimiza; se usa para debug
  display.setRotation(1); // apaisado (ancho 296, alto 128)
  // Pantalla inicial
  display.setFullWindow();
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    drawHeader();
    display.setCursor(4, 40);
    display.print("Inicializando sensores...");
  } while (display.nextPage());

  last_sample_ms = 0;
}

void loop() {
  // Comandos por Serial
  if (Serial.available()) {
    int c = Serial.read();
    if (c == 'D' || c == 'd') {
      dumpCSV();
    }
  }

  uint32_t now = millis();
  if (now - last_sample_ms >= SAMPLE_PERIOD_MS || last_sample_ms == 0) {
    last_sample_ms = now;

    Measurement m{};
    if (readBME680(m)) {
      appendMeasurement(m);
      epaperFullRefresh(m);
      Serial.print(F("OK: T=")); Serial.print(m.temp, 2);
      Serial.print(F("C H=")); Serial.print(m.hum, 1);
      Serial.print(F("% P=")); Serial.print(m.pres, 1);
      Serial.print(F("hPa GAS=")); Serial.print(m.gas, 0);
      Serial.print(F("ohm IAQ=")); Serial.print(m.iaq, 1);
      Serial.println(F("%"));
    } else {
      // Reporte de error y mostrar en e-Paper
      Serial.println(F("Error: performReading() BME680"));
      display.setFullWindow();
      display.firstPage();
      do {
        display.fillScreen(GxEPD_WHITE);
        drawHeader();
        display.setCursor(4, 48);
        display.print("Error lectura BME680");
      } while (display.nextPage());
    }
  }

  // Bajo consumo entre muestras
  LowPower.sleep(1000); // Sleep ligero 1s, repite hasta llegar al minuto
}

Explicación breve de partes clave:
– Baseline del gas: se promedia durante 5 minutos al arranque para normalizar la resistencia del sensor (que varía entre unidades y con el entorno). Luego se “congela” y se persiste.
– IAQ simplificado: no usa BSEC (Bosch), pero ofrece una métrica cualitativa de 0 a 100 que combina gas y humedad.
– e‑Paper: se usa un refresco completo por ciclo para simplificar. En mejoras proponemos pasar a parciales.
– Persistencia: usamos FlashStorage_SAMD para almacenar un buffer circular. Es un ejemplo didáctico: escribir flash con frecuencia conlleva desgaste; más abajo damos recomendaciones para mitigar.

Compilación, flasheo y ejecución

Se asume que el sketch está en un directorio llamado “epaper-air-quality-logger”.

Estructura sugerida:
– epaper-air-quality-logger/
– epaper-air-quality-logger.ino

Pasos:

1) Preparar el core y libs (si no lo hizo antes):

arduino-cli core update-index
arduino-cli core install arduino:samd@1.8.14
arduino-cli lib install "GxEPD2@1.5.9"
arduino-cli lib install "Adafruit GFX Library@1.11.9"
arduino-cli lib install "Adafruit BME680 Library@2.0.3"
arduino-cli lib install "Adafruit Unified Sensor@1.1.14"
arduino-cli lib install "Adafruit BusIO@1.14.1"
arduino-cli lib install "ArduinoLowPower@1.2.2"
arduino-cli lib install "FlashStorage_SAMD@1.3.2"

2) Compilar para Nano 33 IoT:

arduino-cli compile --fqbn arduino:samd:nano_33_iot epaper-air-quality-logger

3) Identificar el puerto (ejemplos):
– Linux: ls /dev/ttyACM
– macOS: ls /dev/tty.usbmodem

– Windows: mode | findstr COM (o ver en el Administrador de dispositivos)

4) Subir el binario:
– Linux/macOS:

arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:samd:nano_33_iot epaper-air-quality-logger
  • Windows (ajuste COMx):
arduino-cli upload -p COM4 --fqbn arduino:samd:nano_33_iot epaper-air-quality-logger

5) Abrir monitor serie a 115200 baudios:

arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200

En Windows:

arduino-cli monitor -p COM4 -c baudrate=115200

Validación paso a paso

1) Validación de arranque:
– La pantalla e‑Paper debe mostrar “epaper-air-quality-logger” y el texto “Inicializando sensores…” durante el primer ciclo.
– En el monitor serie, verá un mensaje de estado o un error del BME680 si no está detectado.

2) Detección del BME680:
– Si todo va bien, al cabo de ~1 minuto, se imprimirá por serie una línea con T, H, P, GAS e IAQ.
– La pantalla hará un refresco completo mostrando:
– Temperatura, humedad, presión
– Resistencia de gas
– IAQ (%) y el valor de baseline
– Un recuadro en la parte inferior para la minigráfica (al principio con pocos puntos)

3) Calibración de baseline:
– Durante los primeros 5 minutos, el baseline de gas se ajusta (valor fluctúa hacia un promedio).
– El campo “baseline” en la pantalla se estabilizará.
– baseline_locked pasa a true internamente y el valor queda persistido.

4) Registro en flash:
– Tras varios minutos, envíe “d” o “D” por el monitor serie.
– Debe aparecer un CSV con encabezado y filas: t_ms,temp_c,hum_pct,pres_hpa,gas_ohm,iaq_pct.
– Reinicie la placa (botón reset) y vuelva a pedir “DUMP”: la data debe persistir (no se pierde tras reset).

5) Minigráfica:
– A partir de ~10 muestras, la línea en el recuadro inferior debe mostrar una tendencia de IAQ (0..100) desplazándose hacia la izquierda con el tiempo.

6) Comprobación visual y numérica:
– Compare temperatura y humedad con otro termohigrómetro para validar orden de magnitud.
– Alterar el ambiente:
– Exponer brevemente a alcohol isopropílico o aliento cerca del sensor (sin tocar): la resistencia de gas bajará y el IAQ tenderá a disminuir (peor “calidad”).
– Volver a aire limpio: el IAQ se recupera gradualmente.

Troubleshooting

1) La pantalla e‑Paper no muestra nada / se queda en blanco
– Causas probables:
– Pines mal conectados (DC/CS/RST/BUSY invertidos).
– Falta de 3.3 V o masa común.
– Clase de panel incorrecta en GxEPD2.
– Solución:
– Verifique la tabla de pines. Asegure EPD_CS=10, EPD_DC=9, EPD_RST=8, EPD_BUSY=7.
– Confirme que su Waveshare es 2.9″ b/w V2 (SSD1680). La clase GxEPD2_290_T5 es la adecuada para SSD1680.
– Pruebe display.setRotation(0/1/2/3) por si el mapeo afecta coordenadas visibles.

2) BME680 no detectado en 0x76/0x77
– Causas:
– Cableado SDA/SCL invertido o en pines incorrectos.
– Alimentación a 5 V en lugar de 3.3 V o GND suelta.
– Dirección I2C configurada por puente a otra distinta.
– Solución:
– Conectar a los pines etiquetados SDA/SCL del Nano 33 IoT (no A4/A5).
– Usar un escáner I2C para confirmar dirección.
– Revisar soldaduras o jumpers en el módulo BME680.

3) Ghosting o artefactos en e‑Paper
– Causas:
– Actualizaciones muy frecuentes sin refresco completo.
– Cables SPI largos o ruidosos.
– Solución:
– Mantener el refresco completo cada cierto número de ciclos.
– Reducir longitud de cables y retorcer MOSI/SCK con GND cercano para minimizar ruido.

4) Subida falla: “No device found on…”
– Causas:
– Puerto incorrecto.
– El bootloader solo está activo unos segundos tras reset.
– Solución:
– Identificar el puerto correcto con arduino-cli board list.
– Pulsar reset doblemente para entrar en “modo bootloader” y reintentar upload.

5) Mensajes “Error lectura BME680”
– Causas:
– Tiempos de calentamiento del gas no satisfechos.
– Interferencias I2C o alimentación inestable.
– Solución:
– Verificar alimentación 3.3 V estable.
– Aumentar delay entre lecturas o revisar setGasHeater(320, 150).

6) Desgaste de flash evidente / errores de escritura
– Causas:
– Frecuencia de escritura muy alta.
– Solución:
– Incrementar intervalo de muestreo (p. ej., 5 minutos).
– Escribir en flash cada N muestras en lugar de cada vez (buffer RAM + commit).

7) e‑Paper parpadea demasiado
– Causa:
– Refresco completo en cada ciclo.
– Solución:
– Migrar a actualizaciones parciales para datos pequeños (ver mejoras).
– Usar full refresh cada X ciclos para “limpiar”.

8) IAQ no parece realista
– Causa:
– El IAQ simplificado no es equivalente a BSEC.
– Solución:
– Aplique BSEC2 de Bosch para IAQ calibrado y métricas como eCO2/VOC Index (ver mejoras).

Mejoras y variantes

  • IAQ profesional con BSEC2:
  • Sustituir el cálculo simple por Bosch BSEC2 para obtener IAQ, eCO2, bVOC con calibración robusta.
  • Asegurarse de la compatibilidad de BSEC2 con SAMD21 y uso de licencias.

  • Reducción de desgaste de flash:

  • Implementar un buffer en RAM para N muestras (p. ej., 12) y escribir en flash en bloques.
  • Reducir el muestreo a cada 5 minutos y LOG_CAPACITY = 288 para ~24 h.

  • Actualización parcial de e‑Paper:

  • Usar “setPartialWindow” y dibujar solo las áreas cambiadas (número IAQ/última barra del sparkline).
  • Hacer un full-refresh cada 10 parciales para evitar ghosting.

  • Exportación de datos:

  • Implementar un comando “DUMPJSON” con ArduinoJson para exportar en JSON.
  • Guardar en archivo CSV en microSD (si se añade un módulo microSD por SPI con su propio CS).

  • Integración IoT:

  • Publicar mediciones por WiFi (Nano 33 IoT) a MQTT/InfluxDB y mantener la e‑Paper como tablero local.
  • Sincronizar hora por NTP y almacenar timestamps UNIX en el log.

  • Energía:

  • Usar “LowPower.deepSleep” con alarma RTC para ciclos de muestreo largos.
  • Apagar periféricos entre muestras (p. ej., desalimentar BME680 con transistor p‑MOS si el diseño lo permite).

  • Visual:

  • Cambiar la tipografía por fuentes GFX más grandes/bold para legibilidad.
  • Añadir iconos según rangos de IAQ con bitmap monocromo.

Checklist de verificación

  • [ ] He instalado Arduino CLI 0.35.3 y el core arduino:samd@1.8.14.
  • [ ] He instalado las bibliotecas exactas: GxEPD2 1.5.9, Adafruit GFX 1.11.9, Adafruit BME680 2.0.3, Adafruit Unified Sensor 1.1.14, Adafruit BusIO 1.14.1, ArduinoLowPower 1.2.2, FlashStorage_SAMD 1.3.2.
  • [ ] He cableado correctamente e‑Paper: CS=10, DC=9, RST=8, BUSY=7; MOSI=D11, SCK=D13; 3V3 y GND comunes.
  • [ ] He cableado correctamente BME680 por I2C: SDA y SCL a los pines SDA/SCL del Nano 33 IoT; 3V3 y GND.
  • [ ] El sketch compila con: arduino-cli compile –fqbn arduino:samd:nano_33_iot epaper-air-quality-logger.
  • [ ] El sketch sube con: arduino-cli upload -p –fqbn arduino:samd:nano_33_iot epaper-air-quality-logger.
  • [ ] Veo en la e‑Paper el título y, tras ~1 min, los valores de T/H/P/GAS/IAQ y la minigráfica.
  • [ ] Tras 5 min, el baseline de gas se estabiliza y el IAQ varía con el ambiente.
  • [ ] Al enviar “D” por el monitor serie, recibo el CSV con registros.
  • [ ] Tras un reset, el log sigue presente (persistencia OK).

Apéndice: comandos de referencia compactos

  • Instalar core SAMD y libs:
arduino-cli core update-index
arduino-cli core install arduino:samd@1.8.14
arduino-cli lib install "GxEPD2@1.5.9" "Adafruit GFX Library@1.11.9" "Adafruit BME680 Library@2.0.3" "Adafruit Unified Sensor@1.1.14" "Adafruit BusIO@1.14.1" "ArduinoLowPower@1.2.2" "FlashStorage_SAMD@1.3.2"
  • Compilar y subir:
arduino-cli compile --fqbn arduino:samd:nano_33_iot epaper-air-quality-logger
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:samd:nano_33_iot epaper-air-quality-logger
arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200

Nota final

Este caso práctico está específicamente enfocado al modelo “Arduino Nano 33 IoT + Waveshare 2.9in e‑Paper (SSD1680) + BME680” con la toolchain y versiones indicadas. Todo el cableado, el código y los comandos han sido diseñados para esta combinación concreta a fin de lograr un “epaper-air-quality-logger” reproducible y validable.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es el sistema operativo soportado para el Arduino Nano 33 IoT?




Pregunta 2: ¿Qué herramienta se utiliza para la instalación y subida en lugar del IDE GUI?




Pregunta 3: ¿Cuál es la versión de la biblioteca GxEPD2 mencionada en el artículo?




Pregunta 4: ¿Qué tipo de cable se necesita para la conexión del Arduino Nano 33 IoT?




Pregunta 5: ¿Qué puerto serie es típico en Linux para el Arduino Nano 33 IoT?




Pregunta 6: ¿Qué voltaje lógico utiliza el Arduino Nano 33 IoT?




Pregunta 7: ¿Cuál es el modelo exacto del módulo e-Paper mencionado?




Pregunta 8: ¿Qué tipo de sensor es el BME680?




Pregunta 9: ¿Qué versión de Arduino SAMD Boards se menciona en el artículo?




Pregunta 10: ¿Qué es necesario confirmar antes de comenzar la instalación?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
error: Contenido Protegido / Content is protected !!
Scroll to Top