El desarrollo de dispositivos terminales de IoT es esencialmente una práctica de ingeniería de sistemas en entornos con recursos limitados.. Dominar los controladores periféricos de MCU, protocolos de comunicación multimodo (Wi‑Fi/MQTT), adquisición de sensores de baja potencia, y la colaboración entre dispositivos y la nube constituye los cuatro pilares de la construcción de sistemas IoT integrados confiables. Sus principios básicos residen en capas de abstracción de hardware estandarizadas., pilas de protocolos de comunicación desacoplados, y programación híbrida de tareas en tiempo real y en segundo plano. Este enfoque técnico mejora significativamente la mantenibilidad del producto., reutilización multiplataforma, y robustez industrial, Ampliamente utilizado en protección inteligente contra incendios., monitoreo ambiental, alerta de borde, y otros escenarios. Esta práctica utiliza una alarma de humo como portador típico..
1. Sistema de alarma de humo IoT: Una ruta completa de práctica de ingeniería para ingenieros integrados
Los principiantes suelen considerar el desarrollo de sistemas de IoT como "apilamiento de protocolos" o "empalme de módulos". Sin embargo, El núcleo de los proyectos reales de grado industrial nunca es una lista de funciones., pero el pensamiento de ingeniería a nivel de sistema: cómo construir un sistema mantenible., escalable, Enlace de datos de extremo a extremo diagnosticable en una MCU con recursos limitados.
Esta serie de tutoriales utiliza una alarma de humo como portador., evitando conceptos abstractos y presentando solo el proceso de implementación de enlace completo de un ingeniero integrado: del análisis de requisitos, selección de hardware, configuración periférica, integración de la pila de protocolos de comunicación, acoplamiento en la nube, a la interacción de la aplicación. Todo el código está diseñado de forma colaborativa en base a la biblioteca STM32 HAL y las plataformas duales ESP-IDF., cubriendo tres capas centrales: percepción (controladores de sensores), red (Adaptación multimodo Wi‑Fi/Bluetooth/NB‑IoT), y aplicación (lógica local + mando a distancia).
1.1 Posicionamiento docente y valor de ingeniería
Este tutorial se dirige a tres grupos técnicos claros.:
- Estudiantes con especialización en electrónica/información/IoT: Necesidad de completar cursos o proyectos de graduación que requieran demostrabilidad., defensa, y reproducibilidad;
- Ingenieros integrados junior: Domina las operaciones GPIO/UART básicas pero carece de experiencia en la programación de múltiples periféricos bajo RTOS;
- Emprendedores/creadores de IoT: Necesidad de verificar prototipos dentro 3 semanas, muy sensible al poder, costo, y ciclo de desarrollo.
Todas las selecciones técnicas siguen tres estrictas limitaciones.:
- Disponibilidad de hardware: Todos los módulos disponibles en el mercado de LCSC (No PCB personalizado), Costo de la lista de materiales ≤ ¥85 (excluyendo el recinto);
- Programabilidad del firmware: Ningún depurador dedicado como J‑Link; solo se necesita USB a TTL para la actualización del firmware y la captura de registros;
- Reemplazabilidad del protocolo: Pila de comunicación subyacente desacoplada de la lógica empresarial; cambiar MQTT a CoAP/LwM2M requiere solo dos cambios de puntero de función.
Estas limitaciones no son compromisos sino fronteras industriales reales.. Como consultor técnico para un fabricante de equipos contra incendios., Una vez encontré una desconexión masiva a -20°C debido a la falta de tolerancia a fallas de sincronización SPI en un controlador de sensor. La solución final no fue reemplazar el chip sino reescribir la lógica de retardo del CS.. La verdadera capacidad de ingeniería siempre crece en las lagunas de las limitaciones..
1.2 Arquitectura del sistema: Modelo de desacoplamiento de tres capas
La alarma de humo tiene una forma física mínima.: STM32F103C8T6 (64Flash de KB / 20KB de RAM), Sensor de humo MP‑2.5, indicador LED, zumbador, Módulo Wi‑Fi ESP32‑WROOM‑32. Su arquitectura de software debe respaldar la futura expansión hacia nodos de agricultura inteligente. (sensores de humedad/luz del suelo) o pasarelas de iluminación inteligentes (Coordinador Zigbee). Se adopta un estricto diseño en capas.:
hoja
| Capa | Componente | Responsabilidades | Restricciones clave |
|---|---|---|---|
| Capa de percepción | STM32F103 | Adquisición de datos del sensor, lógica de alarma local, gestión de bajo consumo | Todos los controladores de sensores proporcionan estándar init() / read() / deinit(); ADC admite disparador de software/temporizador |
| Capa de red | ESP32‑WROOM‑32 | Gestión de wifi, Cifrado TLS, Cliente MQTT, OTA | Bucle de eventos nativo ESP‑IDF; sin bloqueo en devoluciones de llamadas Wi-Fi; Latido del corazón MQTT = 60 s |
| Capa de aplicación | STM32+ESP32 | Motor de reglas de negocio, análisis de comandos, sincronización de estado | UART2 (STM32) ↔ UART0 (ESP32); marco con CRC16 + 0terminador x0D0A |
Esta estratificación no es un modelo de libro de texto ideal, sino una estrategia de supervivencia aprendida de los obstáculos.. Las primeras versiones colocaron la lógica de reconexión MQTT en STM32, provocando un desbordamiento del búfer UART cuando se cae el Wi-Fi. Finalmente, El manejo de excepciones de red se trasladó completamente a ESP32.; STM32 solo recibe paquetes de estado JSON estructurados (p.ej., {"wifi":"connected","mqtt":"ready"}), desacoplando completamente los dos.
2. Selección de plataforma de hardware y diseño de circuitos críticos
El STM32F103C8T6 no se elige por sus características de alta gama, sino por su árbol de reloj preciso y combinación de periféricos.. Una alarma de humo no requiere un ADC de punto flotante o de alta velocidad, pero exige:
- Base de tiempo precisa de 1 ms (para filtrado de media móvil de concentración de humo);
- Canales UART independientes (UART1 para registros de depuración, UART2 dedicado a ESP32);
- GPIO suficiente para controlar los LED, zumbadores, y pines de habilitación del sensor;
- SRAM en chip que cumple con la pila mínima de FreeRTOS (≥512 bytes por tarea).
El reloj de 72 MHz del F103C8T6 proviene de HSI a través de PLL. APB1 (PCLK1) funciona a 36MHz, Cumpliendo perfectamente con la precisión de interrupción de 1 ms de TIM2.:
texto plano
// Key TIM2 initialization (HAL Library)
htim2.Instance = TIM2;
htim2.Init.Prescaler = 36000 - 1; // 36MHz / 36000 = 1kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1000 - 1; // 1s overflow
El Prescaler no es arbitrario: configurándolo en 35999 da un reloj temporizador de 1kHz. Para una interrupción de 1 ms, establezca ARR en 0 y use el evento de actualización como marca de 1 ms. Estos detalles determinan la estabilidad de todas las operaciones en las que el tiempo es crítico..
2.1 Diseño del circuito de interfaz del sensor
El MP‑2.5 emite 0,1–4,0 V analógicos correspondientes a 0–10000 ppm de humo.. Errores críticos de diseño:
- Supresión de ruido de potencia: El sensor es muy sensible a las ondulaciones.. Cuando VCC se ondula >50mV, Las lecturas del ADC saltan ±15%. Solución: coloque tantalio 10μF + 100Condensadores cerámicos nF cerca del sensor VCC; alimenta este riel desde STM32 VREF+ (no principal 3.3V);
- Estabilidad de referencia del ADC: VREF+ necesita un desacoplamiento de 100 nF; Período de muestreo del ADC ≥1,5 μs (Sección RM0008 12.4.3) para evitar la distorsión;
- Acondicionamiento de señal: La interferencia de alta frecuencia requiere un filtro de paso bajo RC (R=1kΩ, C=100nF, corte ≈1.6kHz) para suprimir armónicos de red de 50 Hz.
En verdad diseño de PCB, compartir GND entre MP‑2.5 y el MOSFET del zumbador provocó un 30% Caída del ADC cuando sonó el timbre. arreglo final: sensor GND conectado por separado a STM32 AGND, aterrizado en estrellas en AVSS. Esto es obligatorio para la adquisición analógica de alta precisión..
2.2 Diseño de interfaz de comunicación STM32 – ESP32
UART2 (STM32) ↔ UART0 (ESP32) Parece simple pero esconde cuatro riesgos principales.:
- Compatibilidad de niveles: STM32 GPIO = 3,3 VTTL; ESP32 UART0 RX máx. 3,6 V, TX = 3,3 V (conexión directa segura);
- Sin control de flujo: Sin RTS/CTS; protocolo de software necesario para evitar la pérdida de paquetes;
- Error de velocidad de baudios: F103 HSI (±1%), ESP32 OSCURO (±2%), error total máximo 3%; elegir velocidad de baudios tolerante;
- Desbordamiento del búfer: Búfer UART RX predeterminado de ESP32 = 128 bytes; STM32 puede explotar JSON de 200 bytes.
Soluciones de ingeniería:
- Velocidad de baudios: 921600bps (no estándar). UBRR=3 en F103 (error 0.15%), <0.5% en ESP32, mucho mejor que 115200bps (2.3% error);
- Formato de marco:
[0xAA][LEN_H][LEN_L][CMD][PAYLOAD...][CRC_H][CRC_L][0x0D][0x0A]LEN = longitud de la carga útil; CRC16‑CCITT cubre CMD a CRC_L; - Expansión del búfer ESP32: Colocar
rx_buffer_size = 1024enuart_driver_install(); - Seguridad de la transmisión STM32: Controlar
huart2.gState == HAL_UART_STATE_READYantesHAL_UART_Transmit(); reintente después de 10 ms si está ocupado.
Esta solución pasó pruebas de producción en masa de -40 °C a 85 °C con tasa de error de bits <10⁻⁹ (GB/T 17626.3 Estándar CEM).
3. Arquitectura de firmware STM32: Metal desnudo + Programación híbrida RTOS
La alarma de humo tiene conflictos inherentes en tiempo real:
- Tiempo real difícil: El timbre debe activarse dentro de 200 ms si el humo excede el umbral (límite audible humano);
- Suave en tiempo real: indicación LED, rebote clave, la salida del registro tolera un retraso de 50 ms;
- Tiempo no real: Subir a la nube, La verificación OTA se ejecuta de forma asincrónica.
Forzar causas de programación unificada de FreeRTOS:
- La tarea del zumbador agota la CPU, Dañando la reconexión Wi-Fi;
- Tareas de baja prioridad retrasadas, El LED parpadea fuera de sincronización.
Modelo de programación híbrido:
- Contexto de interrupción: La interrupción de actualización de 1 ms de TIM2 ejecuta muestreo ADC y filtrado de promedio móvil;
- Bucle principal de metal desnudo:
while(1)maneja la máquina de estado LED, escaneo de claves, Registros UART; - Tareas RTOS: Sólo dos tareas FreeRTOS:
wifi_task(comunicación ESP32) ycloud_task(Mensajería MQTT).
Innovación clave: Aísle tareas difíciles en tiempo real de RTOS y manejelas directamente en interrupciones. TIM2 ISR debe:
- Ejecutar en ≤5μs (~360 ciclos a 72MHz);
- No llamar a funciones de biblioteca HAL (No
HAL_Delay()); - Acceda a variables globales solo con
volatiley secciones críticas.
Implementación:
texto plano
// TIM2 ISR (simplified)
void TIM2_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
// Non‑blocking ADC start
HAL_ADC_Start(&hadc1);
// 1ms tick for LED blink
ms_tick++;
// Moving average filter (window = 8)
static uint16_t smoke_buf[8] = {0};
static uint8_t buf_idx = 0;
uint16_t adc_val;
if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
adc_val = HAL_ADC_GetValue(&hadc1);
smoke_buf[buf_idx] = adc_val;
buf_idx = (buf_idx + 1) & 0x07;
uint32_t sum = 0;
for(uint8_t i=0; i<8; i++) sum += smoke_buf[i];
current_smoke_ppm = sum >> 3; // divide by 8
}
}
}
ADC inicia y regresa inmediatamente; resultados leídos en la siguiente interrupción. Este canal mantiene la ejecución de ISR en 3,2 μs (medido por osciloscopio), muy por debajo del límite de seguridad de 5μs.
3.1 Partición de tareas y optimización de memoria de FreeRTOS
La gestión de la memoria es fundamental en F103 con sólo 20 KB de RAM. Asignación precisa de la pila:
wifi_task: Analiza las respuestas AT, JSON máximo ~ 150 bytes → pila 512 bytes;cloud_task: Publicación/suscripción MQTT, almacena tema/carga útil → pila 768 bytes;- Deshabilitar la asignación dinámica: Colocar
configUSE_MALLOC_FAILED_HOOK = 1; reemplazarpvPortMalloc()con estáticaxTaskCreateStatic(); - Agrupación de prioridad de interrupción:
NVIC_PriorityGroup_2(2 con derecho preferente + 2 sub); TIM2 (con derecho preferente 0) > todas las tareas RTOS (con derecho preferente 1).
Comunicación entre tareas: cola + grupo de eventos:
smoke_queue: Mantiene los valores de humo filtrado. (uint16_t), escrito por TIM2 ISR víaxQueueSendFromISR();wifi_event_group: Bits para el estado de la conexión Wi‑Fi/IP/MQTT.
El juicio de alarma se mueve a cloud_task: activar sólo si 3 lecturas consecutivas >800ppm. Equilibra la velocidad y la prevención de falsas alarmas.
4. Capa de red ESP32: Firmware AT personalizado profundo + Aceleración TLS
ESP32‑WROOM‑32 ejecuta ESP‑IDF v4.4, pero El componente oficial MQTT no se utiliza. por tres razones:
- MQTT oficial depende de lwIP, que no cabe en la RAM limitada del F103;
- Los comandos AT ofrecen un control más preciso (p.ej., DNS estático);
- Los sitios industriales a menudo necesitan IP estática (no hay DHCP en algunas fábricas).
De este modo, ESP32 usa modo AT puro; STM32 controla todo Wi-Fi/MQTT a través de UART. Firmware AT estándar (v2.2.0.0) tiene defectos fatales:
- Se corrigió el tiempo de espera de TLS de 10 segundos (Los servidores públicos MQTT como EMQX a menudo necesitan 15);
- La SUSCRIPCIÓN MQTT carece de QoS2 (requerido para alertas de incendio sin pérdidas);
- Sin interfaz AES de hardware; Usos del protocolo de enlace TLS 95% UPC.
Correcciones de firmware AT personalizadas profundas:
- Modificar
components/at/src/at_port/at_port_uart.cpara parametrizar el tiempo de espera de TLS (AT+MQTTTLS=1,15000); - Agregue el campo QoS2 a
AT+MQTTSUBencomponents/at/src/at_cmd_src/at_cmd_mqtt.c; - Habilite el hardware ESP32 AES en
idf.py menuconfig; llave de precarga conaes_encrypt_init()antes del protocolo de enlace TLS.
Mejoras:
- Apretón de manos TLS de 15,2 s → 3,8 s;
- Entrega de QoS2 desde 82% → 99.99%;
- Carga máxima de CPU desde 95% → 45%.
Máquina de estado de comando STM32 AT:
texto plano
// AT state machine (pseudo code)
typedef enum {
AT_STATE_IDLE,
AT_STATE_WAITING_OK,
AT_STATE_WAITING_IP,
AT_STATE_MQTT_CONNECTED
} at_state_t;
at_state_t at_state = AT_STATE_IDLE;
uint8_t at_retry_cnt = 0;
void at_send_command(const char* cmd) {
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 100);
at_state = AT_STATE_WAITING_OK;
at_retry_cnt = 0;
}
// UART2 RX interrupt parsing
void USART2_IRQHandler(void) {
uint8_t rx_byte;
HAL_UART_Receive(&huart2, &rx_byte, 1, 1);
switch(at_state) {
case AT_STATE_WAITING_OK:
if(strstr(rx_buffer, "OK")) {
at_state = AT_STATE_IDLE;
} else if(strstr(rx_buffer, "ERROR")) {
if(++at_retry_cnt < 3)
at_send_command(last_cmd);
else
at_switch_apn();
}
break;
// other states...
}
}
5. Nube & Colaboración de aplicaciones: Protocolo ligero & Sincronización de estado
El sistema utiliza un híbrido privado + arquitectura de nube pública:
- Nube privada: Clúster EMQX (v5.0) en LAN empresarial para acceso a dispositivos, motor de reglas, empuje de alerta;
- Nube pública: Aplicación WeChat Mini Program a través de API HTTPS para nube privada.
Esto evita la dependencia del proveedor de la nube pública y al mismo tiempo mantiene el alcance de WeChat.. Diseño clave: protocolo de sincronización de estado:
- STM32 no se conecta directamente a la nube; todos los datos pasan a través de ESP32;
- ESP32 ↔ EMQX usa MQTT sobre TLS:
device/{product_key}/{device_id}/up(enlace ascendente)device/{product_key}/{device_id}/down(enlace descendente); - El mini programa obtiene el estado a través de la API REST de EMQX, no hay conexión larga.
JSON de enlace ascendente estándar:
texto plano
{
"ts": 1712345678901,
"smoke_ppm": 1250,
"battery_mv": 3280,
"wifi_rssi": -62,
"event": "alarm_high"
}
event valores:
normal: <300ppm;warn: 300–800 ppm (parpadeo lento del LED);alarm_high: ≥800 ppm (zumbador + parpadeo rápido);alarm_clear: <300ppm durante 10 segundos.
La lógica de la aplicación se simplifica: monitor event para conducir la interfaz de usuario, no es necesario interpretar las ppm sin procesar. La reutilización como detector de formaldehído solo requiere cambiar el umbral en STM32—APP sin cambios. Este es el valor de los protocolos estándar.
5.1 Actualización OTA confiable
OTA es fundamental para los dispositivos IoT, pero el Flash de 64 KB del F103 no puede contener bancos duales. Actualización diferencial + verificar & revertir:
- Paquete de actualización = parche bsdiff (12% de tamaño completo);
- Parche almacenado en SPI Flash externo (W25Q32);
- Después de la verificación, desbloquear flash, borrar aplicación, escribe parche;
- Regresar al sector de respaldo si falla la verificación.
Puntos de control clave:
- Descargar: CRC32 por 1 KB contra la lista de servidores;
- Escribir: Vuelva a leer inmediatamente después de la programación.;
- Bota: Validar el puntero de la pila en
0x08000000; revertir un valor no válido. Implementado en 2000 dispositivos para 18 meses con 0 fallas de actualización.
6. Depuración práctica: Seguimiento de enlace completo desde el osciloscopio hasta Wireshark
El desafío final en la depuración de IoT integrada es que los problemas pueden ocurrir en cualquier capa.:
- ADC STM32 anormal? → Verifique la forma de onda PA0 para detectar ruido de energía;
- ESP32 no se puede conectar a Wi‑Fi? → Capture UART2 para confirmar los comandos AT enviados;
- MQTT no llega a la nube? → Puerto del enrutador espejo, filter MQTT with Wireshark;
- APP data delayed? → Check EMQX rule engine SQL for Cartesian product.
Debug toolchain:
- Hardware: DS1054Z oscilloscope (with protocol decoding):
- PA0 peak‑peak noise <20mV;
- USART2 TX idle level high (else ESP32 misdetects start bit);
- firmware: SEGGER RTT (replaces printf):
SEGGER_RTT_printf(0, "SMOKE:%d BATT:%dmV\r\n", current_smoke_ppm, battery_mv);Zero latency, no UART, multi‑channel; - Network: Wireshark + ESP32 Sniffer firmware capture 802.11 frames;
- Nube: EMQX Dashboard
Client Listfor real‑time status.
Most overlooked tip: timestamp alignment. Sync STM32, ESP32, EMQX, Mini Program via NTP (ESP32 as client) to ±500ms. Otherwise, “alarm 5 minutes ago” vs log timestamp “2024‑04‑05T10:23:45Z” confuses operations.
7. Producción en masa: ESD Protection & Long‑Term Aging Tests
The gap between student projects and mass production is environmental adaptability. The smoke alarm must pass industrial tests:
- ESD: Contact discharge ±8kV (CEI 61000‑4‑2):
- Agregar diodos TVS (SMAJ3.3A) a interfaces USB/sensor;
- Tierra de cobre del borde de PCB, agujeros ≤20 mm;
- Envejecimiento a alta temperatura: 85°C durante 72h:
- Deriva MP‑2.5 <±5% fondo de escala;
- Sensor de temperatura interno STM32 frente a termómetro infrarrojo;
- Vibración: 5–Barrido de 500 Hz, 3g aceleración, 2h; comprobar las uniones de soldadura.
En pruebas de preproducción, El éxito de Wi‑Fi disminuyó de 99.9% a 82% después de 48h a 45°C. Causa principal: condensador de carga de cristal ESP32 incorrecto (12pF → 10pF cambio de frecuencia fija a alta temperatura). Estos detalles sólo salen a la superficie en pruebas de envejecimiento reales..
Lección sangrienta: Mantenga siempre una puerta trasera de depuración en el firmware de producción en masa.. Añadir en main() comenzar:
texto plano
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) {
debug_mode = 1; // enable full logs
}
Mantenga presionado restablecer 3 segundos para exportar registros en el sitio. Nos salvó tres grandes fracasos por sólo 200 bytes flash.
El proyecto de alarma de humo parece simple pero condensa la capacidad de ingeniería de IoT integrada. Evita la ingeniería excesiva y se centra en el equilibrio entre los recursos., costo, fiabilidad, y mantenibilidad. Cuando sueldas la última resistencia, firmware flash, y vea la curva de humo en tiempo real en la APLICACIÓN, La sensación única de logro como ingeniero supera con creces cualquier ilusión de tutorial rápido..














