Developing IoT terminal devices is essentially systems engineering practice in resource‑constrained environments. Mastering MCU peripheral drivers, multi‑mode communication protocols (Wi‑Fi/MQTT), low‑power sensor acquisition, and device‑cloud collaboration forms the four cornerstones of building reliable embedded IoT systems. Its core principles lie in standardized hardware abstraction layers, decoupled communication protocol stacks, and hybrid scheduling of real‑time and background tasks. This technical approach significantly improves product maintainability, cross‑platform reusability, and industrial robustness, widely used in smart fire protection, monitoreo ambiental, edge alerting, y otros escenarios. This practice uses a smoke alarm as a typical carrier.
1. IoT Smoke Alarm System: 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. All code is collaboratively designed based on STM32 HAL Library and ESP‑IDF dual platforms, covering three core layers: perception (sensor drivers), network (Wi‑Fi/Bluetooth/NB‑IoT multi‑mode adaptation), and application (local logic + mando a distancia).
1.1 Teaching Positioning and Engineering Value
This tutorial targets three clear technical groups:
- Students majoring in electronics/information/IoT: Need to complete course or graduation projects requiring demonstrability, defensibility, and reproducibility;
- Junior embedded engineers: Master basic GPIO/UART operations but lack experience in scheduling multiple peripherals under RTOS;
- IoT entrepreneurs/makers: Need to verify prototypes within 3 semanas, highly sensitive to power, costo, and development cycle.
All technical selections follow three hard constraints:
- Hardware availability: 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 + 0x0D0A terminator |
This layering is not an ideal textbook model but a survival strategy learned from pitfalls. Early versions placed MQTT reconnection logic on STM32, causing UART buffer overflow when Wi‑Fi dropped. Finally, network exception handling was fully moved to ESP32; STM32 only receives structured JSON status packets (p.ej., {"wifi":"connected","mqtt":"ready"}), completely decoupling the two.
2. Hardware Platform Selection and Critical Circuit Design
The STM32F103C8T6 is chosen not for high‑end features but for precise clock tree and peripheral matching. A smoke alarm requires no floating‑point or high‑speed ADC but demands:
- Accurate 1ms time base (for smoke concentration moving‑average filtering);
- Independent UART channels (UART1 for debug logs, UART2 dedicated to ESP32);
- Sufficient GPIO to drive LEDs, buzzers, and sensor enable pins;
- On‑chip SRAM meeting FreeRTOS minimum stack (≥512 bytes per task).
The 72MHz clock of F103C8T6 comes from HSI via PLL. APB1 (PCLK1) runs at 36MHz, perfectly meeting the 1ms interrupt accuracy of TIM2:
plaintext
// 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
The Prescaler is not arbitrary: setting it to 35999 gives a 1kHz timer clock. For a 1ms interrupt, set ARR to 0 and use the Update Event as the 1ms tick. Such details determine stability for all time‑critical operations.
2.1 Sensor Interface Circuit Design
The MP‑2.5 outputs 0.1–4.0V analog corresponding to 0–10000ppm smoke. Critical design pitfalls:
- Power noise suppression: The sensor is highly sensitive to ripple. When VCC ripple >50mV, ADC readings jump ±15%. Solución: place 10μF tantalum + 100nF ceramic capacitors near the sensor VCC; power this rail from STM32 VREF+ (not main 3.3V);
- ADC reference stability: 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:
plaintext
// 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:
plaintext
// 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:
- Private cloud: EMQX cluster (v5.0) on enterprise LAN for device access, rule engine, alert push;
- Public cloud: WeChat Mini Program APP via HTTPS API to private cloud.
This avoids public cloud vendor lock‑in while retaining WeChat reach. Key design: state synchronization protocol:
- STM32 does not connect directly to cloud; all data passes through ESP32;
- ESP32 ↔ EMQX uses MQTT over TLS:
device/{product_key}/{device_id}/up(uplink)device/{product_key}/{device_id}/down(downlink); - Mini Program fetches status via EMQX REST API, no long connection.
Standard uplink JSON:
plaintext
{
"ts": 1712345678901,
"smoke_ppm": 1250,
"battery_mv": 3280,
"wifi_rssi": -62,
"event": "alarm_high"
}
event values:
normal: <300ppm;warn: 300–800ppm (slow LED blink);alarm_high: ≥800ppm (zumbador + fast blink);alarm_clear: <300ppm for 10s.
APP logic is simplified: monitor event to drive UI, no need to interpret raw ppm. Reusing as formaldehyde detector only requires changing the threshold on STM32—APP unchanged. This is the value of standard protocols.
5.1 Reliable OTA Upgrade
OTA is critical for IoT devices, 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? → Mirror router port, 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:
plaintext
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..













