ESP32 AT固件+STM32构建物联网网关:从串口通信到MQTT上云全链路实战

做物联网项目这几年,我反复遇到一个场景:客户要求设备既要有STM32的实时控制能力,又要能稳定联网上云。最初我用ESP32直接跑Arduino框架,把业务逻辑和通信全塞进一颗芯片,结果代码越写越臃肿,维护成本直线上升。后来转向"主控+通信模组"的分离式架构,用STM32做主控,ESP32烧入AT固件做纯通信模块,系统复杂度大幅降低,通信稳定性反而更好。

这篇文章把我这套方案从硬件选型、AT指令通信、MQTT上云到调试排障的完整链路拆开讲,适合正在做物联网网关开发或准备从单体架构迁移到分离架构的工程师参考。

为什么不用Arduino单体方案

先说痛点。ESP32跑Arduino或ESP-IDF确实能一个人干完所有事,但在实际产品中有三个问题:

第一,业务逻辑和通信栈耦合在一起。WiFi断连时你的传感器采集任务也会被阻塞,尤其是用了阻塞式HTTP请求后,整个主循环卡死几秒钟是常事。

第二,ESP32的GPIO驱动能力和ADC精度不如STM32。工业场景下你要驱动继电器、读高精度传感器,ESP32的硬件底子确实不够看。

第三,团队协作成本高。做嵌入式的人不熟WiFi协议栈,搞网络的人不会写寄存器,一颗芯片把两拨人的工作全搅在一起,出问题互相甩锅。

分离式架构的核心思路是:STM32专注实时控制和传感器采集,ESP32只负责WiFi连接和MQTT通信,两者通过UART串口用AT指令交互。职责清晰,各司其职。

硬件连接设计

硬件层面,STM32和ESP32之间最简单的连接方式就是UART。以STM32F103C8T6和ESP32-WROOM-32为例:

连接项STM32 引脚ESP32 引脚说明
串口TXPA9 (USART1_TX)GPIO16 (RX2)STM32发,ESP32收
串口RXPA10 (USART1_RX)GPIO17 (TX2)ESP32发,STM32收
电源3.3V3.3V共电源
GNDGNDGND共地
EN-使能脚可接GPIO控制复位

波特率建议设115200,实测在921600下也能稳定通信,但留余量更保险。注意ESP32的IO是3.3V逻辑电平,和STM32的USART电平兼容,不需要电平转换。

AT固件烧录与验证

ESP32出厂自带AT固件,但版本可能较旧。建议从乐鑫官网下载最新AT固件,用ESP32 Flash Tool烧录。烧完后用串口工具验证:

# 用esptool烧录AT固件
esptool.py --port /dev/ttyUSB0 write_flash \
  0x10000 ESP32_AT_firmware.bin

# 验证AT响应
echo "AT" > /dev/ttyUSB0
# 正常返回: OK

烧完固件后,ESP32就是一个纯粹的通信模组了,你通过串口发AT指令,它执行并返回结果。所有WiFi连接、MQTT通信都通过AT指令完成。

STM32端AT指令通信框架

STM32这边,核心是写一个可靠的AT指令收发框架。我踩过最大的坑是串口接收不完整——AT响应可能分多个数据包到达,你不能发完指令就立刻读,必须等接收完成再解析。

// AT指令发送与响应等待
typedef struct {
    uint8_t rx_buf[1024];
    volatile uint16_t rx_len;
    volatile uint8_t rx_done;
} at_ctx_t;

at_ctx_t at_ctx = {0};

// 串口中断接收
void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t ch = USART_ReceiveData(USART1);
        if (at_ctx.rx_len < sizeof(at_ctx.rx_buf)) {
            at_ctx.rx_buf[at_ctx.rx_len++] = ch;
        }
        at_ctx.rx_done = 0; // 标记正在接收
    }
}

// 发送AT指令并等待响应
int at_send_cmd(const char *cmd, const char *expect, uint32_t timeout_ms) {
    at_ctx.rx_len = 0;
    at_ctx.rx_done = 0;
    uart_send_string(cmd);
    uart_send_string("\r\n");

    uint32_t start = HAL_GetTick();
    while ((HAL_GetTick() - start) < timeout_ms) {
        if (strstr((char*)at_ctx.rx_buf, expect) != NULL) {
            return 0; // 匹配成功
        }
        HAL_Delay(10);
    }
    return -1; // 超时
}

关键点:接收缓冲区要够大(MQTT消息可能很长),超时时间要根据指令类型差异化设置。WiFi连接类指令给10秒,普通查询指令给2秒。

MQTT上云流程

AT固件支持MQTT指令集,整个上云流程分三步:

// 1. 连接WiFi
at_send_cmd("AT+CWJAP=\"SSID\",\"password\"", "WIFI GOT IP", 15000);

// 2. 配置MQTT参数 (以阿里云为例)
at_send_cmd("AT+MQTTUSERCFG=0,1,\"client_id\",\"username\",\"password\",0,0,\"\"", "OK", 5000);

// 3. 连接MQTT Broker
at_send_cmd("AT+MQTTCONN=0,\"broker.address\",1883,1", "OK", 10000);

// 发布消息
at_send_cmd("AT+MQTTPUB=0,\"/device/data\",\"{\\\"temp\\\":25.6}\",1,0", "OK", 5000);

// 订阅主题
at_send_cmd("AT+MQTTSUB=0,\"/device/cmd\",1", "OK", 5000);

实测在稳定WiFi环境下,MQTT心跳保持和消息收发都能正常工作。但要注意AT固件的MQTT实现不支持QoS2,如果对端要求QoS2会握手失败。

调试排障实战经验

这套架构上线后,我遇到过几个典型问题:

串口数据粘包。ESP32的AT响应有时会和MQTT订阅消息混在一个数据包里。解决方案是在中断接收里做帧分隔——遇到+MQTTSUBRECV:前缀就单独提取一条消息。

WiFi断连后不自动重连。AT固件的CWJAP指令连上后如果AP掉线,不会自动重连。需要在主循环里定期检查WIFI GOT IP状态,发现掉线就重新发CWJAP。

MQTT连接假死。TCP连接底层断了但AT固件没及时感知,MQTT心跳也不报错。解决方案是加应用层心跳——定时发布一条ping消息,连续3次无响应就主动断开重连。

调试这些串口问题时,一个好用的工具是虎王科技开源的随身WiFi硬件调试工具(gitee.com/zesso/hardware_tool),它支持中兴微、ASR、展锐等多种通信芯片的串口AT指令调试,界面直观,可以直接发送AT指令看响应,比用minicom裸调高效得多。我在调试ESP32的AT指令序列时,先用这个工具验证每条指令的响应格式,再写进STM32代码里,少走了不少弯路。

性能数据与总结

实测数据:STM32F103@72MHz + ESP32 AT固件,MQTT消息端到端延迟在200-400ms(含WiFi传输),传感器采集到云端可见的完整链路在1秒以内。系统连续运行72小时无掉线(含1次WiFi自动重连恢复)。

这套架构的核心价值不在于性能多强,而在于职责分离带来的工程可控性。STM32的固件可以独立迭代,ESP32的AT固件升级也不影响业务逻辑。团队分工明确,出问题定位快。

如果你正在做类似项目,建议先把AT指令序列在串口调试工具上跑通,再写进MCU代码。调试阶段多花一小时,编码阶段能省一整天。觉得有用点个赞收藏,实测踩坑的经验会持续更新,关注不错过下一期物联网网关进阶内容。

更多推荐