Enfocados en el desarrollo de soluciones ESP32.

STM32+ESP32 Smoke Alarm IoT Development Practice

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.

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:

  1. 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);
  2. 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;
  3. 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

CapaComponenteResponsabilidadesRestricciones clave
Capa de percepciónSTM32F103Adquisición de datos del sensor, lógica de alarma local, gestión de bajo consumoTodos los controladores de sensores proporcionan estándar init() / read() / deinit(); ADC admite disparador de software/temporizador
Capa de redESP32‑WROOM‑32Gestión de wifi, Cifrado TLS, Cliente MQTT, OTABucle de eventos nativo ESP‑IDF; sin bloqueo en devoluciones de llamadas Wi-Fi; Latido del corazón MQTT = 60 s
Capa de aplicaciónSTM32+ESP32Motor de reglas de negocio, análisis de comandos, sincronización de estadoUART2 (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.

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.:

  1. Compatibilidad de niveles: STM32 GPIO = 3,3 VTTL; ESP32 UART0 RX máx. 3,6 V, TX = 3,3 V (conexión directa segura);
  2. Sin control de flujo: Sin RTS/CTS; protocolo de software necesario para evitar la pérdida de paquetes;
  3. Error de velocidad de baudios: F103 HSI (±1%), ESP32 OSCURO (±2%), error total máximo 3%; elegir velocidad de baudios tolerante;
  4. 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 = 1024 en uart_driver_install();
  • Seguridad de la transmisión STM32: Controlar huart2.gState == HAL_UART_STATE_READY antes HAL_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).

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) y cloud_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 volatile y 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; reemplazar pvPortMalloc() con estática xTaskCreateStatic();
  • 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ía xQueueSendFromISR();
  • 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.

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:

  1. Modificar components/at/src/at_port/at_port_uart.c para parametrizar el tiempo de espera de TLS (AT+MQTTTLS=1,15000);
  2. Agregue el campo QoS2 a AT+MQTTSUB en components/at/src/at_cmd_src/at_cmd_mqtt.c;
  3. Habilite el hardware ESP32 AES en idf.py menuconfig; llave de precarga con aes_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...
 }
}

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.

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 List for 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.

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..

Imagen de Berg Zhou

Berg Zhou

Berg Zhou se centra en el diseño esquemático de ESP32, diseño de PCB, desarrollo de firmware y producción en masa de PCBA. Competente en diseño de circuitos., selección de componentes, Pruebas de prototipos y soluciones OEM/ODM integrales.. Proporcionar estabilidad, Módulos funcionales y tableros de control ESP32 confiables y rentables para clientes globales, Apoyar el desarrollo personalizado y la fabricación en volumen..

Publicaciones recientes

Traducción
Establecer como idioma predeterminado
Whatsapp
Whatsapp
Correo electrónico
Correo electrónico
chatear
chatear
chatear

Obtenga una cotización

Nuestros expertos en productos y técnicos responderán sus preguntas dentro de 24 horas.

Utilizamos cookies para asegurarnos de brindarle la mejor experiencia en nuestro sitio web..