Caso práctico: Logger cadena de frío Arduino MKR GSM 1400

Caso práctico: Logger cadena de frío Arduino MKR GSM 1400 — hero

Objetivo y caso de uso

Qué construirás: Un registrador de datos de cadena de frío utilizando Arduino MKR GSM 1400, DS3231, MicroSD SPI y DS18B20 para monitoreo de temperatura.

Para qué sirve

  • Monitoreo de temperatura en transporte de productos farmacéuticos.
  • Registro de condiciones ambientales en el almacenamiento de alimentos perecederos.
  • Control de temperatura en laboratorios de investigación.
  • Alertas en tiempo real para condiciones fuera de rango.

Resultado esperado

  • Registro de temperatura cada 5 minutos con precisión de ±0.5°C.
  • Envío de datos a la nube con una frecuencia de 1 paquete cada 10 minutos.
  • Latencia de respuesta de alertas de temperatura superior a 2 segundos.
  • Capacidad de almacenar hasta 1,000 registros en la tarjeta MicroSD.

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

Arquitectura/flujo: Arduino MKR GSM 1400 <-> DS18B20 (sensor) <-> DS3231 (reloj) <-> MicroSD (almacenamiento) <-> MQTT (comunicación)

Nivel: Avanzado

Prerrequisitos

  • Sistemas operativos probados:
  • Windows 11 23H2 (64-bit) o Windows 10 22H2 (64-bit).
  • Ubuntu 22.04 LTS (x86_64).
  • macOS 13.6 Ventura o macOS 14 Sonoma (Apple Silicon o Intel).

  • Toolchain exacta (línea de comandos, sin IDE gráfico):

  • Python 3.11.6
  • PlatformIO Core 6.1.15
  • Plataforma de compilación SAMD para PlatformIO:
    • platform = atmelsam@8.1.0
    • framework = arduino (ArduinoCore-samd 1.8.13)
  • Librerías Arduino (versionadas, a instalar vía PlatformIO):

    • arduino-libraries/MKRGSM@1.5.1
    • adafruit/RTClib@2.1.4
    • paulstoffregen/OneWire@2.3.7
    • milesburton/DallasTemperature@3.11.0
    • arduino-libraries/SD@1.2.4
  • Requisitos de hardware/driver:

  • Arduino MKR GSM 1400 usa USB CDC nativo. No requiere drivers en macOS y Linux modernos.
  • Windows 10/11: el controlador CDC-ACM se instala automáticamente. Si no aparece el puerto, actualizar Windows Update o usar el driver del paquete Arduino SAMD (instalado al usar Arduino IDE, opcional).
  • Permisos en Linux: agregar el usuario al grupo dialout para poder abrir el puerto serie (ver sección de compilación/ejecución).

  • Conectividad:

  • SIM nano con datos activos y APN conocido de tu operador (PIN opcional). Se usará GSM/GPRS para publicar los registros.
  • Cobertura GSM suficiente en el lugar de pruebas (2G/3G dependiendo del módem/operador).

Materiales

  • Arduino MKR GSM 1400 (modelo exacto ABX00018).
  • Módulo RTC DS3231 (compatible 3.3 V, con batería CR2032 para respaldo).
  • MicroSD SPI (soporte de tarjeta microSD a 3.3 V) + nivelador/buffer CD74HC4050.
  • CD74HC4050 como buffer unidireccional para líneas de salida del MKR hacia la MicroSD.
  • Nota: el CD74HC4050 se alimenta a 3.3 V; MISO no pasa por el 4050 (es entrada a la MCU).
  • Tarjeta microSD clase 10 (formateada FAT32).
  • Sensor de temperatura DS18B20 (cápsula TO-92 o sonda impermeable) a 3.3 V.
  • Resistencia 4.7 kΩ (pull-up del bus 1-Wire del DS18B20).
  • Antena para el MKR GSM 1400 (obligatoria para radio).
  • SIM con datos (APN/usuario/contraseña, PIN si aplica).
  • Cables Dupont/M-F/M-M según módulos.
  • Fuente de alimentación:
  • USB 5 V estable para desarrollo.
  • Recomendado: batería LiPo 3.7 V conectada al conector JST del MKR para evitar caídas de tensión durante ráfagas GSM (opcional pero recomendable).
  • Consumibles:
  • Batería CR2032 para el DS3231.
  • Cinta térmica o abrazaderas para fijar la sonda DS18B20 a paquetes fríos.

Preparación y conexión

Consideraciones generales de señal y alimentación

  • El MKR GSM 1400 trabaja a 3.3 V. No aplicar 5 V a sus entradas.
  • El módulo microSD y el DS3231 deben alimentarse a 3.3 V. Muchos módulos de mercado incluyen regulador a 3.3 V; si tu módulo microSD está diseñado para 5 V, es preferible usar uno nativo 3.3 V. En este caso incluimos el CD74HC4050 como buffer/aislador unidireccional en SCK, MOSI y CS.
  • La línea MISO (desde la MicroSD al MKR) no debe atravesar el CD74HC4050 (el 4050 es unidireccional: de entrada a salida); se conecta directamente a la entrada MISO del MKR (tensión 3.3 V compatible).
  • El DS18B20 necesita un pull-up de 4.7 kΩ entre su línea de datos y 3.3 V (si tu cable es largo, podrías necesitar ajustar el valor o la topología para integridad de señal).

Tabla de conexiones

La SPI del MKR GSM 1400 está expuesta en el header SPI (MOSI/MISO/SCK). No uses pines digitales numerados para MOSI/MISO/SCK; usa los pines etiquetados del conector SPI. El pin CS sí puede ser cualquier GPIO (usaremos D4).

Función Componente Pin en MKR GSM 1400 A través del CD74HC4050 Pin del módulo Notas
Alimentación 3.3 V Todos 3V3 N/A VCC 3.3 V a DS3231, MicroSD y CD74HC4050.
Tierra Todos GND N/A GND Masa común para todos los módulos.
SPI MOSI MicroSD MOSI (cabecera SPI) Sí (buffer) DI (MOSI) Conectar MOSI del MKR a entrada del 4050; salida del 4050 al DI.
SPI MISO MicroSD MISO (cabecera SPI) No DO (MISO) Directo MicroSD→MKR (3.3 V).
SPI SCK MicroSD SCK (cabecera SPI) Sí (buffer) SCK MKR SCK→4050→SCK de MicroSD.
SPI CS (GPIO) MicroSD D4 Sí (buffer) CS Selección de chip; define D4 en el firmware.
I2C SDA DS3231 SDA No SDA I2C a 3.3 V.
I2C SCL DS3231 SCL No SCL I2C a 3.3 V.
1-Wire DAT DS18B20 D5 No DQ Añadir pull-up 4.7 kΩ entre D5 y 3.3 V.
Alimentación DS18B20 DS18B20 3V3 N/A VDD Modo alimentación normal (no parasitario).
Tierra DS18B20 DS18B20 GND N/A GND
VCC CD74HC4050 3V3 N/A VCC Alimentar el 4050 a 3.3 V para nivel lógico correcto.
Entradas 4050 Desde MKR (MOSI, SCK, D4) MOSI, SCK, D4 N/A 4050-IN Entradas del buffer desde el MKR.
Salidas 4050 Hacia MicroSD (DI, SCK, CS) N/A N/A 4050-OUT Salidas del buffer hacia el módulo MicroSD.
Antena GSM MKR GSM 1400 Conector u.FL N/A Antena Conecta la antena antes de encender el módem.
SIM MKR GSM 1400 Ranura SIM N/A SIM Inserta la nanoSIM con datos activos.

Preparación previa a energizar

  1. Formatea la microSD a FAT32 (tamaño de clúster por defecto).
  2. Inserta la CR2032 en el DS3231 (asegura polaridad).
  3. Inserta la nanoSIM y conecta la antena GSM al MKR.
  4. Alimenta todo por USB. Para pruebas con GSM se recomienda además conectar una batería LiPo al conector JST del MKR para evitar resets por picos de corriente (el módem puede requerir >1.5 A en ráfagas muy cortas).
  5. Ten a mano los datos del APN de tu operador (APN, usuario, contraseña) y el PIN de la SIM si estuviera activo.

Código completo (C++ Arduino, PlatformIO)

A continuación se muestra un firmware con:
– Registro periódico en microSD (CSV).
– Timestamps desde DS3231.
– Lectura de temperatura DS18B20.
– Umbrales configurables de cadena de frío (p. ej. 2 °C a 8 °C).
– Publicación periódica por GSM vía HTTP POST.
– Retardo no bloqueante con millis().

Archivo: platformio.ini (en la raíz del proyecto):

[env:mkrgsm1400]
platform = atmelsam@8.1.0
board = mkrgsm1400
framework = arduino
monitor_speed = 115200
lib_deps =
  arduino-libraries/MKRGSM@1.5.1
  adafruit/RTClib@2.1.4
  paulstoffregen/OneWire@2.3.7
  milesburton/DallasTemperature@3.11.0
  arduino-libraries/SD@1.2.4
build_flags =
  -DARDUINOJSON_USE_LONG_LONG=1

Archivo: src/main.cpp

#include <Arduino.h>
#include <MKRGSM.h>
#include <Wire.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <SD.h>

// =========================
// Configuración del proyecto
// =========================

// SIM/APN
static const char SIM_PIN[] = "";              // PIN de la SIM o "" si no tiene
static const char APN[] = "YOUR_APN";          // Cambiar por el APN de tu operador
static const char APN_USER[] = "";             // Usuario APN si aplica
static const char APN_PASS[] = "";             // Contraseña APN si aplica

// Publicación HTTP
static const char HTTP_HOST[] = "httpbin.org"; // Servidor de pruebas
static const int  HTTP_PORT = 80;
static const char HTTP_PATH[] = "/post";       // Ruta POST

// Umbrales de cadena de frío
static const float TEMP_MIN_C = 2.0f;          // °C
static const float TEMP_MAX_C = 8.0f;          // °C
static const uint32_t BREACH_HOLDOFF_MS = 10000; // Persistencia (10 s) antes de disparar alarma

// Logging y temporización
static const uint32_t SAMPLE_INTERVAL_MS = 60000; // 60 s
static const uint32_t SEND_INTERVAL_MS   = 300000; // 5 min
static const char LOG_FILENAME[] = "/coldchain.csv";

// Pines
static const uint8_t PIN_SD_CS   = 4;          // CS de microSD (D4), vía CD74HC4050
static const uint8_t PIN_1WIRE   = 5;          // D5 para DS18B20 con pull-up 4.7k a 3.3V

// =========================
// Objetos globales
// =========================
RTC_DS3231 rtc;

OneWire oneWire(PIN_1WIRE);
DallasTemperature dallas(&oneWire);

GSM gsmAccess;
GPRS gprs;
GSMClient netClient; // No-SSL. Para HTTPS usar GSMSSLClient (requiere certificados).

File logFile;

DeviceAddress dsAddr;
bool dsFound = false;

bool sdReady  = false;
bool gsmReady = false;

// Timers no bloqueantes
uint32_t t_lastSample = 0;
uint32_t t_lastSend   = 0;

// Estado de alarma
bool inBreach = false;
uint32_t breachSince = 0;

// =========================
// Utilidades
// =========================

String iso8601(const DateTime& dt) {
  char buf[25];
  snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02dZ",
           dt.year(), dt.month(), dt.day(),
           dt.hour(), dt.minute(), dt.second());
  return String(buf);
}

void ensureRTCInit() {
  if (!rtc.begin()) {
    Serial.println(F("[RTC] Error: no se detecta DS3231 en I2C (0x68)."));
    return;
  }
  if (rtc.lostPower()) {
    Serial.println(F("[RTC] Se detectó pérdida de energía. Ajustando a tiempo de compilación."));
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  Serial.println(F("[RTC] OK"));
}

void ensureDallasInit() {
  dallas.begin();
  if (dallas.getDeviceCount() < 1) {
    Serial.println(F("[DS18B20] No se detectan sensores. Verifica cableado y pull-up."));
    dsFound = false;
    return;
  }
  dsFound = dallas.getAddress(dsAddr, 0);
  if (!dsFound) {
    Serial.println(F("[DS18B20] No se obtuvo dirección del primer sensor."));
    return;
  }
  dallas.setResolution(dsAddr, 12); // Máxima resolución
  Serial.print(F("[DS18B20] Sensor 0: "));
  for (uint8_t i = 0; i < 8; i++) {
    Serial.print(dsAddr[i], HEX); if (i < 7) Serial.print(":");
  }
  Serial.println();
}

bool ensureSDInit() {
  if (sdReady) return true;
  if (!SD.begin(PIN_SD_CS)) {
    Serial.println(F("[SD] Falló SD.begin(). Verifica CS, 4050 y formato FAT32."));
    sdReady = false;
    return false;
  }
  // Crear encabezado si no existe
  if (!SD.exists(LOG_FILENAME)) {
    File f = SD.open(LOG_FILENAME, FILE_WRITE);
    if (f) {
      f.println(F("timestamp,temp_c,status,breach"));
      f.close();
      Serial.println(F("[SD] Archivo creado con encabezado."));
    } else {
      Serial.println(F("[SD] No se pudo crear el archivo de log."));
      sdReady = false;
      return false;
    }
  }
  sdReady = true;
  Serial.println(F("[SD] OK"));
  return true;
}

float readTemperatureC() {
  if (!dsFound) return NAN;
  dallas.requestTemperatures();
  float c = dallas.getTempC(dsAddr);
  return c;
}

String statusFromTemp(float c) {
  if (isnan(c)) return "NA";
  if (c < TEMP_MIN_C) return "LOW";
  if (c > TEMP_MAX_C) return "HIGH";
  return "OK";
}

void appendLog(const String& line) {
  if (!ensureSDInit()) return;
  File f = SD.open(LOG_FILENAME, FILE_WRITE);
  if (f) {
    f.println(line);
    f.close();
  } else {
    Serial.println(F("[SD] Error al abrir el log para escritura."));
  }
}

bool ensureGSM() {
  if (gsmReady) return true;
  Serial.print(F("[GSM] Inicializando módem... "));
  bool connected = false;
  for (int i = 0; i < 3 && !connected; i++) {
    if (gsmAccess.begin(SIM_PIN) == GSM_READY) {
      connected = true;
      break;
    }
    delay(2000);
  }
  if (!connected) {
    Serial.println(F("Fallo."));
    return false;
  }
  Serial.println(F("OK"));

  Serial.print(F("[GPRS] Adjuntando a APN... "));
  if (gprs.attachGPRS(APN, APN_USER, APN_PASS)) {
    Serial.println(F("OK"));
    gsmReady = true;
    return true;
  } else {
    Serial.println(F("Fallo. Verifica APN/credenciales/cobertura."));
    gsmReady = false;
    return false;
  }
}

bool httpPostJSON(const String& host, int port, const String& path, const String& json) {
  if (!ensureGSM()) return false;

  Serial.print(F("[HTTP] Conectando a ")); Serial.print(host); Serial.print(F(":")); Serial.println(port);
  if (!netClient.connect(host.c_str(), port)) {
    Serial.println(F("[HTTP] No se pudo conectar."));
    return false;
  }

  String req = String("POST ") + path + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Content-Type: application/json\r\n" +
               "Connection: close\r\n" +
               "Content-Length: " + String(json.length()) + "\r\n\r\n" +
               json;

  netClient.print(req);

  // Leer respuesta simple
  uint32_t start = millis();
  bool ok = false;
  while (millis() - start < 10000) {
    while (netClient.available()) {
      String line = netClient.readStringUntil('\n');
      line.trim();
      if (line.startsWith("HTTP/1.1 200")) ok = true;
      // parar si fin de headers
      if (line.length() == 0) {
        // cuerpo siguiente (omitimos)
        break;
      }
    }
    if (!netClient.connected()) break;
  }
  netClient.stop();
  Serial.println(ok ? F("[HTTP] Respuesta 200 OK.") : F("[HTTP] Respuesta no OK."));
  return ok;
}

void publishLastSample(float c, const String& status, const String& ts, bool breach) {
  // JSON mínimo
  String json = String("{\"device\":\"mkrgsm1400\",\"ts\":\"") + ts +
                "\",\"temp_c\":" + String(c, 3) +
                ",\"status\":\"" + status +
                "\",\"breach\":" + (breach ? "true" : "false") + "}";

  httpPostJSON(HTTP_HOST, HTTP_PORT, HTTP_PATH, json);
}

void printBanner() {
  Serial.println();
  Serial.println(F("== cellular-cold-chain-sd-logger =="));
  Serial.println(F("HW: Arduino MKR GSM 1400 + DS3231 + MicroSD SPI (CD74HC4050) + DS18B20"));
  Serial.println(F("Toolchain: PlatformIO Core 6.1.15, atmelsam@8.1.0, ArduinoCore-samd 1.8.13"));
  Serial.println(F("Log: /coldchain.csv | Intervalo muestreo: 60 s | Envios: 5 min"));
  Serial.println();
}

// =========================
// Setup y loop
// =========================

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) { /* esperar CDC */ }
  printBanner();

  ensureRTCInit();
  ensureDallasInit();
  ensureSDInit();

  // Confirmar fecha/hora inicial
  DateTime now = rtc.now();
  Serial.print(F("[RTC] Tiempo actual: ")); Serial.println(iso8601(now));

  // Intentar arrancar GSM/GPRS temprano (no bloquear)
  ensureGSM();

  t_lastSample = millis();
  t_lastSend   = millis();
}

void loop() {
  uint32_t nowMs = millis();

  // Muestreo periódico
  if (nowMs - t_lastSample >= SAMPLE_INTERVAL_MS || t_lastSample == 0) {
    t_lastSample = nowMs;

    // Leer tiempo y temperatura
    DateTime now = rtc.now();
    String ts = iso8601(now);

    float tempC = readTemperatureC();
    String stat = statusFromTemp(tempC);

    // Lógica de brecha
    bool breachEvent = false;
    if (stat == "LOW" || stat == "HIGH") {
      if (!inBreach) {
        // inicia periodo de confirmación
        if (breachSince == 0) breachSince = nowMs;
        if (nowMs - breachSince >= BREACH_HOLDOFF_MS) {
          inBreach = true;
          breachEvent = true;
          Serial.println(F("[ALERTA] Temperatura fuera de rango persistente."));
        }
      }
    } else {
      inBreach = false;
      breachSince = 0;
    }

    // Línea CSV
    String line = ts;
    line += ",";
    if (isnan(tempC)) line += "NaN"; else line += String(tempC, 3);
    line += ",";
    line += stat;
    line += ",";
    line += (inBreach ? "1" : "0");

    Serial.print(F("[LOG] ")); Serial.println(line);
    appendLog(line);

    // Opcional: escribir un archivo de estado rápido
    File f = SD.open("/last.txt", FILE_WRITE);
    if (f) {
      f.seek(0);
      f.print("ts=");    f.println(ts);
      f.print("temp=");  f.println(isnan(tempC) ? String("NaN") : String(tempC, 3));
      f.print("status=");f.println(stat);
      f.print("breach=");f.println(inBreach ? "1" : "0");
      f.close();
    }
  }

  // Envío periódico por GSM
  if (nowMs - t_lastSend >= SEND_INTERVAL_MS || t_lastSend == 0) {
    t_lastSend = nowMs;

    DateTime now = rtc.now();
    String ts = iso8601(now);
    float tempC = readTemperatureC();
    String stat = statusFromTemp(tempC);

    if (!isnan(tempC)) {
      publishLastSample(tempC, stat, ts, inBreach);
    } else {
      Serial.println(F("[GSM] Se omite envío: temperatura NaN."));
    }
  }

  // Trabajo de fondo mínimo
  delay(10);
}

Puntos clave del código:
– Usa DS3231 como fuente de tiempo; si detecta pérdida de energía del RTC, fija el reloj a tiempo de compilación.
– Inicializa la SD y crea el archivo CSV con encabezado si no existe.
– Lee el DS18B20 a 12 bits y registra cada minuto: timestamp ISO8601, temperatura, estado y si hay brecha activa.
– Implementa retardo de confirmación antes de declarar una “brecha” (para evitar falsos positivos por transitorios).
– Publica cada 5 minutos un JSON pequeño al endpoint HTTP (no TLS) usando MKRGSM. Para producción, usa TLS con GSMSSLClient y certificados.

Compilación, flash y ejecución (comandos exactos)

1) Instalar PlatformIO Core 6.1.15:

  • Windows/macOS/Linux (recomendado con pipx para aislar):
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install "platformio==6.1.15"
platformio --version

Salida esperada (similar):
– PlatformIO Core, version 6.1.15

2) Inicializar el proyecto para MKR GSM 1400:

mkdir -p cellular-cold-chain-sd-logger
cd cellular-cold-chain-sd-logger
pio project init --board mkrgsm1400 --project-option "platform=atmelsam@8.1.0" --project-option "framework=arduino"

3) Sustituir platformio.ini con el contenido proporcionado antes y crear src/main.cpp:

mkdir -p src
# Copia y pega el platformio.ini y el src/main.cpp según el tutorial

4) Instalar dependencias (se instalarán automáticamente en el primer build, pero puedes forzarlo):

pio pkg install
pio lib --global install "arduino-libraries/MKRGSM@1.5.1" "adafruit/RTClib@2.1.4" "paulstoffregen/OneWire@2.3.7" "milesburton/DallasTemperature@3.11.0" "arduino-libraries/SD@1.2.4"

5) Compilar:

pio run

6) Conectar el MKR GSM 1400 por USB. En Linux, si no tienes permisos de puerto serie:

sudo usermod -aG dialout $USER
# cierra sesión y vuelve a entrar, o reinicia la sesión

7) Localizar el puerto serie:

pio device list
  • Windows: COMx (p. ej., COM5)
  • macOS: /dev/cu.usbmodemXXXX
  • Linux: /dev/ttyACM0

8) Subir firmware:

# Si PlatformIO detecta el puerto automáticamente:
pio run -t upload

# O especificando puerto:
pio run -t upload --upload-port /dev/ttyACM0

9) Abrir monitor serie a 115200 baudios:

pio device monitor -b 115200

Notas:
– Si el módem necesita varios segundos para adjuntarse a la red, verás reintentos en la consola.
– Conecta primero la antena antes de energizar el MKR.

Validación paso a paso

1) Arranque y diagnóstico inicial
– Abre el monitor serie. Debes ver:
– Banner con descripciones de HW/toolchain.
– [RTC] OK y hora actual en ISO8601. Si aparece “pérdida de energía”, el RTC se ajustó a la hora de compilación; configura el reloj manual si quieres exactitud total (puedes cambiar el ajuste en código a un método propio).
– [SD] OK y/o creación del archivo con encabezado.
– Detección del DS18B20 (imprime la dirección de 8 bytes).

2) Verificación del DS18B20
– Observa líneas [LOG] cada 60 s con el valor temp_c. Coloca el sensor en:
– Ambiente (~20–25 °C) para ver “OK” si los umbrales están 2–8 °C, verás “HIGH”.
– Mezcla de hielo y agua (~0 °C) para ver “LOW”.
– Confirma que el estado (“status”) coincide con tus expectativas.

3) Verificación del DS3231
– Desconecta USB, mantén la CR2032 y espera 2–3 min.
– Reconecta. La hora debe continuar avanzando correctamente (no reiniciarse).
– Si la hora es errónea, reemplaza la CR2032 y vuelve a compilar o añade una rutina para ajustar el RTC (e.g., desde host vía un comando serie).

4) Verificación de escritura en SD
– Tras varios ciclos, extrae la microSD y abre coldchain.csv:
– Debe tener encabezado y filas similares a:
– 2025-01-12T18:25:00Z,5.312,OK,0
– 2025-01-12T18:26:00Z,8.954,HIGH,1
– Alternativamente, deja la SD y revisa /last.txt con un lector si tu módulo lo permite (o quita la SD y léela en PC).

5) Verificación GSM/GPRS y HTTP
– En la consola, busca:
– [GSM] Inicializando módem… OK
– [GPRS] Adjuntando a APN… OK
– [HTTP] Conectando a httpbin.org:80
– [HTTP] Respuesta 200 OK.
– Las publicaciones se realizan cada 5 min (puedes bajar SEND_INTERVAL_MS para testear).
– Si no obtienes 200 OK:
– Verifica APN, cobertura, saldo de datos, y que no haya firewall bloqueando puertos outbound 80.

6) Verificación de detección de brecha
– Coloca el sensor fuera del rango (p. ej., agua tibia ~30 °C).
– Tras el tiempo de persistencia (10 s por BREACH_HOLDOFF_MS), debe mostrarse:
– [ALERTA] Temperatura fuera de rango persistente.
– En el CSV, la columna breach pasa a 1 mientras dure la brecha.

7) Estabilidad de alimentación (opcional pero recomendado)
– Si notas reinicios durante el adjunto GPRS o POST:
– Conecta una LiPo al MKR y repite.
– Observa que el sistema no reinicia durante picos de transmisión.

Troubleshooting (errores típicos y soluciones)

1) SD.begin() falla o el CSV no se crea
– Síntomas: [SD] Falló SD.begin().
– Causas probables:
– CS incorrecto: verifica que el firmware use D4 y que el cable vaya por la salida del 4050 al pin CS del módulo SD.
– Orden del CD74HC4050: recuerda que es unidireccional. Usa 4050 para MOSI, SCK y CS desde MCU→SD. No inserte el 4050 en MISO.
– MicroSD no formateada FAT32 o defectuosa.
– Alimentación inestable a 3.3 V.
– Solución:
– Revisa cableado, pinout y reemplaza la microSD o formatea (FAT32).
– Confirma continuidad de señales con multímetro.

2) No se detecta el DS18B20
– Síntomas: “[DS18B20] No se detectan sensores.”
– Causas:
– Falta resistencia de 4.7 kΩ entre D5 y 3.3 V.
– Sensor alimentado en modo parasitario sin configurar el firmware.
– Cableado invertido (GND/VDD/DQ).
– Solución:
– Añade pull-up de 4.7 kΩ.
– Verifica el pinout del encapsulado o sonda.
– Usa alimentación normal (VDD a 3.3V).

3) El RTC DS3231 marca hora errónea o constante
– Causas:
– Falta CR2032 o agotada.
– Módulo DS3231 a 5 V incompatible con 3.3 V (I2C pull-ups a 5 V).
– Solución:
– Cambia CR2032.
– Asegura que las resistencias de pull-up I2C vayan a 3.3 V (en algunos módulos, hay jumpers o resistencias que debes modificar).

4) GSM no registra o GPRS no adjunta
– Síntomas:
– [GSM] fallo, [GPRS] fallo.
– Causas:
– SIM con PIN no configurado (SIM_PIN vacío).
– APN incorrecto o credenciales faltantes.
– Cobertura insuficiente o antena desconectada.
– Solución:
– Ajusta SIM_PIN, APN, usuario, contraseña.
– Mueve el dispositivo a zona con mejor cobertura.
– Conecta correctamente la antena.

5) Reinicios aleatorios al enviar por GSM
– Causas:
– Picos de corriente del módem (hasta >1 A en burst).
– Solución:
– Usa una LiPo en el conector JST del MKR.
– Añade un condensador de reserva (e.g., 470–1000 µF) en la línea de 3.3/5 V según diseño total.

6) No aparece el puerto serie
– Windows:
– Prueba otro cable USB (datos, no solo carga).
– Reinstala el driver CDC (vía Arduino IDE si es necesario).
– macOS/Linux:
– Comprueba permisos (Linux: grupo dialout).
– Verifica con pio device list.
– Todos:
– Pulsa el botón de reset doble-rapido para forzar el bootloader (el puerto puede cambiar temporalmente).

7) HTTP responde distinto a 200 OK
– Causas:
– Servidor indisponible, cortafuegos operatorio, NAT peculiar.
– Solución:
– Prueba otro host (temporalmente un servidor propio) o un APN diferente.
– Considera activar TLS con GSMSSLClient y un endpoint HTTPS confiable (necesitarás cargar certificados).

8) Archivo CSV corrupto tras apagones
– Causa:
– Extracción de la SD mientras el archivo está abierto.
– Solución:
– El código abre/cierra por escritura atómica por línea. Minimiza riesgo. Aun así, evita desconectar durante escritura y usa alimentación estable.

Mejoras/variantes

  • Seguridad y transporte:
  • Migrar de HTTP a HTTPS con GSMSSLClient. Cargar certificados raíz al módem (proceso específico para MKRGSM; usar herramienta de certificados del core SAMD o scripts AT). Cambiar GSMClient por GSMSSLClient y ajustar puerto 443.
  • Usar MQTT sobre TLS para publicar a un broker (p. ej., AWS IoT Core) con autenticación por certificados.
  • Robustez de almacenamiento:
  • Rotación de logs (daily rolling): coldchain-YYYYMMDD.csv y purga automática según espacio libre.
  • Cálculo de checksum por línea (CRC32) para detección de corrupción.
  • Multi-sensor:
  • Gestionar múltiples DS18B20 en el mismo bus 1-Wire (identificación por dirección y columnas separadas).
  • Gestión de energía:
  • Dormir entre muestras (modo standby del SAMD21, apagar módem entre envíos). Despertar por RTC (alarma DS3231) si se desea.
  • Alertas activas:
  • Enviar SMS cuando se detecte brecha persistente (usar MKRGSM SMS).
  • Añadir buzzer y LED de estado (verde OK, rojo brecha).
  • Geolocalización aproximada:
  • Consultar Cell-ID a la red y adjuntar MCC/MNC/LAC/CI al payload para trazabilidad.
  • Configuración remota:
  • Leer umbrales, intervalos y APN desde un archivo config.ini en SD, o por comandos simples vía serie.
  • Integridad temporal:
  • Sincronización NTP por GPRS al arranque si el RTC perdió energía (UDP NTP en puerto 123) y actualización del DS3231.

Checklist de verificación

  • [ ] He instalado PlatformIO Core 6.1.15 y puedo ejecutar platformio --version.
  • [ ] He inicializado el proyecto con mkrgsm1400 y la plataforma atmelsam@8.1.0.
  • [ ] He copiado platformio.ini y src/main.cpp exactamente como en el tutorial.
  • [ ] He cableado la MicroSD por SPI usando el conector SPI del MKR y el CD74HC4050 en MOSI/SCK/CS, dejando MISO directo.
  • [ ] He conectado el DS3231 a SDA/SCL (3.3 V) y le he colocado una CR2032 funcional.
  • [ ] He cableado el DS18B20 al pin D5 con resistencia de 4.7 kΩ a 3.3 V.
  • [ ] He insertado la antena y la SIM en el MKR GSM 1400.
  • [ ] He configurado APN/usuario/contraseña y PIN en el código.
  • [ ] El monitor serie muestra [RTC] OK, [SD] OK y el DS18B20 detectado.
  • [ ] Veo líneas [LOG] cada minuto con timestamp y temperatura.
  • [ ] Se crea/actualiza coldchain.csv en la microSD con datos válidos.
  • [ ] Cada 5 min obtengo [HTTP] Respuesta 200 OK (o he ajustado APN/host hasta conseguirlo).
  • [ ] He probado condiciones de baja y alta temperatura y el estado cambia a LOW/HIGH; se marca breach cuando persiste.
  • [ ] El sistema no se reinicia durante transmisiones GSM (uso LiPo si fue necesario).

Con este caso práctico has construido un registrador de temperatura para cadena de frío con sello temporal por RTC, almacenamiento en microSD mediante SPI y publicación celular periódica, todo sobre Arduino MKR GSM 1400 y respetando el camino de señal con CD74HC4050. El proyecto es una base sólida para despliegues de campo y escalabilidad hacia comunicaciones seguras, configuración remota y optimización energética.

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 la versión mínima de Windows requerida para el sistema operativo?




Pregunta 2: ¿Qué versión de Python se necesita para la toolchain?




Pregunta 3: ¿Cuál es la librería de Arduino que se debe instalar para el módulo RTC?




Pregunta 4: ¿Qué tipo de SIM se requiere para la conectividad?




Pregunta 5: ¿Cuál es el modelo exacto del Arduino requerido?




Pregunta 6: ¿Qué controlador se instala automáticamente en Windows 10/11?




Pregunta 7: ¿Cuál es la versión de la plataforma de compilación SAMD para PlatformIO?




Pregunta 8: ¿Qué tipo de batería se necesita para el módulo RTC DS3231?




Pregunta 9: ¿Qué permisos se deben configurar en Linux para abrir el puerto serie?




Pregunta 10: ¿Cuál es la versión de la librería DallasTemperature que se debe instalar?




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:
Scroll to Top