拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32与ESP-01S手写AT指令和MQTT报文接入云平台

STM32 和 ESP-01S 这套组合做物联网课设、毕业设计、设备联网改造都绕不开。很多人拿到 ESP-01S 后第一反应是找现成云平台 SDK或者直接套 MQTT 库结果一换芯片、一换平台就抓瞎。我这里记录的是手打代码的方式STM32 只负责串口收发和状态机ESP-01S 跑 AT 固件所有 AT 指令、MQTT 报文、心跳、订阅、发布都自己拼。云平台可以选 OneNET也可以换成任意支持 MQTT 的物联网平台参数按平台文档填。适合已经能点亮 LED、会串口收发、想搞懂“设备到底怎么连上云平台”的人。整套流程不依赖冷门库核心代码都能自己敲出来调试过程也能看清每一层到底在干什么。1. 方案选型与整体链路设计1.1 为什么不用现成 SDK 直接开搞很多教程一上来就让人移植 ESP8266 的 SDK或者在 STM32 上跑 MQTT 库。这样做的确能跑通但对初学者不友好。ESP-01S 本身资源小默认 AT 固件已经把 WiFi 协议栈、TCP/IP 都做好了STM32 只需要通过串口发命令就能建立 TCP 连接。既然目标只是连接云平台没必要把整个网络协议栈再塞进 STM32。手打代码的价值在于你能清楚看到设备联网的每一步先设置 WiFi 模式再连路由器再建立 TCP再发 MQTT 连接报文最后等平台返回 CONNACK。中间任何一步失败你都能通过串口日志定位。用现成库时报错往往是一句“连接失败”排查范围太大。自己写一遍之后换云平台、换模组、换芯片思路都是通的。还有一个现实原因ESP-01S 的 AT 固件版本很多有的支持 MQTT AT 指令有的只支持 TCP。如果不确定手上模组固件最稳妥的办法就是把它当纯 TCP 透传模块用MQTT 报文自己在 STM32 里拼。这样不管模组是哪个版本只要支持基础 AT 指令就能用。1.2 STM32 和 ESP-01S 各自负责什么这套方案里STM32 是主控负责三件事第一初始化串口和 ESP-01S 通信第二维护一个接收缓冲区处理 ESP-01S 返回的响应和云平台下发的数据第三拼装 MQTT 报文按业务流程发布和订阅。ESP-01S 只负责联网和 TCP 传输它不关心你发的是 MQTT 还是 HTTP只负责把字节流送到云平台。这样分工有个好处STM32 的业务代码和网络代码可以分开写。网络层只提供esp_at_send、esp_tcp_send、esp_tcp_read这类接口业务层负责调用。以后要换成 4G 模组或者以太网只需要替换网络层MQTT 层和业务层不用大改。硬件连接上我习惯用两个串口USART1 接 USB-TTL作为调试口用来打印日志USART2 接 ESP-01S作为通信口。调试口非常重要因为 ESP-01S 返回的数据有时是二进制直接看很难受把原始字节打印成 HEX 会方便很多。1.3 云平台协议选择MQTT 比 HTTP 更合适云平台常见的接入协议有 HTTP、MQTT、TCP 透传。HTTP 适合低频上报每次请求都要重新建立连接开销大TCP 透传最灵活但平台侧通常要写解析脚本通用性差。MQTT 是物联网平台最常用的协议特点是长连接、发布订阅、心跳保活、支持 QoS适合设备持续在线。对 STM32 来说MQTT 报文虽然需要自己拼但结构并不复杂。固定头只有两个字节起步剩余长度用变长编码可变头和载荷按字段拼。只要把 CONNECT、SUBSCRIBE、PUBLISH、PINGREQ 这几个报文写出来就能完成大部分业务。相比 HTTP 每次拼 JSON 请求MQTT 的长连接反而更省资源。选云平台时优先看它是否提供 MQTT 接入。OneNET、阿里云 IoT、腾讯云 IoT、EMQX 自建服务都可以。不同平台的连接参数不同但 MQTT 协议本身一样。这篇记录以通用 MQTT 参数为主OneNET 的接入信息放在配置章节里说明。2. 硬件接线与开发环境搭建2.1 物料清单与供电坑先列一下我实际用到的东西STM32F103C8T6 最小系统板一块ESP-01S 模组一个ESP-01S 转接板一个USB-TTL 调试器一个3.3V 电源模块一个杜邦线若干。如果手头是 ESP-01S 裸模组最好配一个转接板因为它的引脚间距小直接接线容易短路。供电是第一个大坑。ESP-01S 在发射 WiFi 信号时峰值电流能到 200mA 以上有些甚至接近 300mA。STM32 最小系统板上的 3.3V LDO 通常只适合给 MCU 和少量外设供电再带一个 WiFi 模组很容易掉电重启。我一开始用板载 3.3V模组每次连 WiFi 就重启串口打印一堆乱码。后来单独用一个 3.3V 电源模块给 ESP-01S 供电GND 和 STM32 共地问题立刻消失。还有一点ESP-01S 的 EN 脚和 GPIO0 脚要处理。正常运行时 EN 接 3.3VGPIO0 悬空。如果 GPIO0 拉低上电会进入下载模式。转接板一般已经处理好裸模组要自己注意。电源正负极不要接反ESP-01S 没有反接保护接反很容易损坏。2.2 串口接线和电平匹配STM32 和 ESP-01S 之间只连四根线STM32 的 USART2_TX 接 ESP-01S 的 RXSTM32 的 USART2_RX 接 ESP-01S 的 TXVCC 接 3.3VGND 接 GND。注意 TX 和 RX 要交叉很多人接成 TX 对 TX结果收不到任何响应。电平方面STM32F103 的 IO 是 3.3VESP-01S 也是 3.3V可以直接连不需要电平转换。如果用的是 5V 单片机比如某些 Arduino就要加电平转换否则 ESP-01S 的 RX 可能被 5V 损坏。串口波特率初始化先设 115200ESP-01S 默认通常是 115200。如果没响应可以尝试 9600 或者 74880有些老固件默认波特率不同。调试串口 USART1 也接 USB-TTL波特率同样设 115200。PC 端用串口助手打开能看到 STM32 打印的日志。最好把 ESP-01S 的原始返回也通过调试串口转发出来格式化成字符串或者 HEX这样调试时不用来回插拔。2.3 Keil 工程与串口收发框架开发环境我用 Keil MDK5芯片包装 STM32F1 系列。新建工程时选好芯片型号勾选 CMSIS 的 Core 和 Device Startup然后手动添加main.c、usart.c、esp01s.c、mqtt.c这些文件。不用勾选复杂中间件这个方案不需要 RTOS也不需要网络库。串口初始化用 HAL 库或者标准库都行。我为了代码直观HAL 库和标准库都用过。HAL 库的HAL_UART_Receive_IT适合做单字节中断接收标准库直接写中断服务函数也可以。关键是接收不能丢字节。ESP-01S 返回的数据可能很快如果主循环来不及读就会丢包。我建议用一个环形缓冲区中断里只做一件事把收到的字节放进缓冲区然后重新开启接收中断。主循环或者状态机再去解析缓冲区。下面是一个简化的环形缓冲区结构#define ESP_RX_BUF_SIZE 512 typedef struct { uint8_t buf[ESP_RX_BUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; } RingBuf_t; RingBuf_t esp_rx; void ring_push(RingBuf_t *rb, uint8_t data) { uint16_t next (rb-head 1) % ESP_RX_BUF_SIZE; if (next ! rb-tail) { rb-buf[rb-head] data; rb-head next; } } int ring_pop(RingBuf_t *rb, uint8_t *data) { if (rb-head rb-tail) return 0; *data rb-buf[rb-tail]; rb-tail (rb-tail 1) % ESP_RX_BUF_SIZE; return 1; }中断回调里这样写uint8_t esp_rx_byte; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart2) { ring_push(esp_rx, esp_rx_byte); HAL_UART_Receive_IT(huart2, esp_rx_byte, 1); } }初始化时调用一次HAL_UART_Receive_IT(huart2, esp_rx_byte, 1);让中断先跑起来。调试串口的打印函数最好也重定向一下方便用printf输出日志。注意printf可能阻塞不要在中断里调用。3. ESP-01S AT 指令驱动层手写3.1 串口中断接收与超时等待AT 指令驱动层最核心的两个函数一个是发送命令一个是等待响应。发送命令时先把接收缓冲区清空避免旧数据干扰然后通过串口发送命令字符串末尾加\r\n接着在超时时间内不断从环形缓冲区取数据拼成字符串查找期望的响应关键字。等待响应不能死等要有超时。ESP-01S 有时候不回复如果代码里写while(!found)整个程序就卡死了。我一般设 2 秒到 5 秒的超时具体看命令。连接 WiFi 可能耗时较长可以设 10 秒。超时后返回失败业务层决定是否重试。下面是一个简化实现uint8_t esp_wait_ack(const char *ack, uint32_t timeout_ms) { char resp[256]; uint16_t idx 0; uint32_t start HAL_GetTick(); memset(resp, 0, sizeof(resp)); while ((HAL_GetTick() - start) timeout_ms) { uint8_t ch; while (ring_pop(esp_rx, ch)) { if (idx sizeof(resp) - 1) { resp[idx] ch; resp[idx] \0; } if (strstr(resp, ack) ! NULL) { return 0; } } } return 1; }这个实现没有处理二进制数据只适合 AT 阶段的文本响应。MQTT 阶段返回的 CONNACK 是二进制不能用strstr判断要单独解析。3.2 AT 发送、重试与状态机发送函数可以这样封装uint8_t esp_send_cmd(const char *cmd, const char *ack, uint32_t timeout_ms) { while (ring_pop(esp_rx, (uint8_t){0})); // 清空缓冲区实际写法要循环弹出 uart_send_string(huart2, cmd); uart_send_string(huart2, \r\n); return esp_wait_ack(ack, timeout_ms); }实际代码里不要用复合字面量取地址那种写法老老实实用一个临时变量循环弹出。发送后等待OK或者指定响应。如果失败可以重试两次。但重试不是万能的比如 WiFi 密码错了重试一百次也连不上。所以重试次数不要太多失败后要打印错误日志。状态机适合管理联网流程。比如定义状态ESP_STATE_IDLE、ESP_STATE_WIFI_CONNECTING、ESP_STATE_TCP_CONNECTING、ESP_STATE_MQTT_CONNECTING、ESP_STATE_ONLINE。主循环里根据状态调用对应函数成功就切换下一个状态失败就回到空闲并延时重试。这样不会因为某一步卡死导致整个程序无响应。3.3 连接 WiFi 和 TCP 的完整时序上电后先发AT确认模组有响应。如果返回OK说明串口和波特率正确。接着发ATCWMODE1设置为 Station 模式。然后发ATCWJAPWiFi名称,WiFi密码等待WIFI CONNECTED和WIFI GOT IP最后返回OK。这一步耗时较长超时要设大一点。连上 WiFi 后如果只需要一条 TCP 连接发ATCIPMUX0设置单连接模式。然后发ATCIPSTARTTCP,mqtt.example.com,1883把域名和端口换成云平台提供的地址。如果模组不支持域名解析就先用电脑解析出 IP再填 IP。连接成功后返回CONNECT和OK。发送数据时先发ATCIPSEND长度等模组返回提示符再发送指定长度的原始数据。注意长度是字节数不是字符数。MQTT 报文里可能有0x00串口发送函数不能遇到0x00就停止要按长度发送。void esp_tcp_send(uint8_t *data, uint16_t len) { char cmd[32]; sprintf(cmd, ATCIPSEND%d, len); esp_send_cmd(cmd, , 2000); uart_send_bytes(huart2, data, len); esp_wait_ack(SEND OK, 5000); }接收云平台数据时ESP-01S 会返回IPD,长度:数据。需要解析这个格式把数据部分取出来再交给 MQTT 层处理。如果数据里包含二进制不能用字符串函数找冒号要按字节解析。先找到IPD,读长度跳过冒号再读指定长度的数据。4. MQTT 报文手写与云平台连接4.1 MQTT 报文结构快速拆解MQTT 报文分三部分固定头、可变头、载荷。固定头第一个字节高 4 位是报文类型低 4 位是标志位。比如 CONNECT 是0x10CONNACK 是0x20PUBLISH 是0x30SUBSCRIBE 是0x80PINGREQ 是0xC0。固定头后面是剩余长度表示可变头加载荷一共多少字节。剩余长度是变长编码最多四个字节。每个字节低 7 位是数据最高位表示是否还有后续字节。比如剩余长度 64编码就是一个字节0x40剩余长度 200编码是两个字节0xC8 0x01。计算方法是先取长度除以 128 的余数如果商大于 0最高位置 1继续直到商为 0。CONNECT 报文的可变头包括协议名MQTT、协议级别0x04、连接标志、Keep Alive 时间。载荷包括 Client ID、Will Topic、Will Message、Username、Password。连接标志决定这些字段是否存在。比如设置 Clean Session 位表示不保存会话状态。4.2 CONNECT 报文拼装与剩余长度计算先写一个剩余长度编码函数uint8_t mqtt_encode_len(uint8_t *buf, uint32_t len) { uint8_t i 0; do { uint8_t byte len % 128; len / 128; if (len 0) byte | 0x80; buf[i] byte; } while (len 0); return i; }然后拼 CONNECT 报文。为了代码清晰我用一个临时缓冲区先写固定头预留剩余长度位置写完可变头和载荷后再回填剩余长度。注意剩余长度不包括固定头第一个字节和剩余长度本身只包括后面的字节。uint16_t mqtt_build_connect(uint8_t *buf, const char *client_id, const char *username, const char *password, uint16_t keepalive) { uint16_t pos 0; buf[pos] 0x10; // CONNECT uint8_t rl_pos pos; // 协议名 buf[pos] 0x00; buf[pos] 0x04; buf[pos] M; buf[pos] Q; buf[pos] T; buf[pos] T; buf[pos] 0x04; uint8_t flags 0x02; // Clean Session if (username) flags | 0x80; if (password) flags | 0x40; buf[pos] flags; buf[pos] keepalive 8; buf[pos] keepalive 0xFF; // Client ID uint16_t len strlen(client_id); buf[pos] len 8; buf[pos] len 0xFF; memcpy(buf[pos], client_id, len); pos len; // Username if (username) { len strlen(username); buf[pos] len 8; buf[pos] len 0xFF; memcpy(buf[pos], username, len); pos len; } // Password if (password) { len strlen(password); buf[pos] len 8; buf[pos] len 0xFF; memcpy(buf[pos], password, len); pos len; } // 回填剩余长度 uint32_t remain pos - rl_pos - 1; uint8_t rl[4]; uint8_t rl_len mqtt_encode_len(rl, remain); memmove(buf[rl_pos rl_len], buf[rl_pos 1], remain); memcpy(buf[rl_pos], rl, rl_len); return pos rl_len - 1; }发送 CONNECT 后云平台会返回 CONNACK。CONNACK 报文是0x20 0x02 0x00 0x00最后一位返回码为0x00表示连接成功。如果返回0x01到0x05分别表示协议版本不对、Client ID 被拒绝、服务不可用、用户名密码错误、未授权。解析时从IPD数据里取前几个字节判断即可。4.3 订阅、发布、心跳的实现订阅报文类型是0x82固定头后面是剩余长度、包标识符、主题过滤器、QoS。包标识符每发一次订阅要递增避免重复。主题可以订阅平台下发的命令主题QoS 一般用 0 或 1。手写时注意主题长度是两个字节。发布报文类型是0x30QoS 0 时没有包标识符直接跟主题长度、主题、载荷。如果 QoS 1标志位要改成0x32并加包标识符。大多数传感器上报用 QoS 0 就够了掉一两条数据可以接受。如果控制指令要求可靠可以用 QoS 1。uint16_t mqtt_build_publish(uint8_t *buf, const char *topic, const uint8_t *payload, uint16_t plen) { uint16_t pos 0; buf[pos] 0x30; // PUBLISH, QoS 0 uint16_t topic_len strlen(topic); uint32_t remain 2 topic_len plen; pos mqtt_encode_len(buf[pos], remain); buf[pos] topic_len 8; buf[pos] topic_len 0xFF; memcpy(buf[pos], topic, topic_len); pos topic_len; memcpy(buf[pos], payload, plen); pos plen; return pos; }心跳报文PINGREQ最简单两个字节0xC0 0x00。云平台一般要求 Keep Alive 时间内至少有一次报文交互每隔半个 Keep Alive 时间发一次心跳。比如 Keep Alive 设 60 秒就每 30 秒发一次PINGREQ。如果超过 1.5 倍 Keep Alive 没收到任何报文平台会断开连接。心跳最好放在定时器中断或者主循环的时间判断里不要用HAL_Delay阻塞。主循环里记录last_ping_tick每次循环判断HAL_GetTick() - last_ping_tick 30000满足就发心跳并更新。这样不影响其他任务。5. 云平台侧配置与联调5.1 创建产品与设备以 OneNET 为例登录平台后创建产品选择 MQTT 接入协议。产品创建后平台会生成产品 ID、鉴权信息等。然后在产品下创建设备设备名称可以自定义比如stm32_esp01s_01。平台会生成设备 ID、设备密钥或者让你自己填鉴权信息。不同版本的 OneNET 接入参数略有差异。老版多协议接入里MQTT 服务器地址可能是mqtt.heclouds.com端口6002Client ID 填设备 IDUsername 填产品 IDPassword 填鉴权 token。新版 OneNET Studio 的接入地址、端口、主题格式又不一样具体以平台文档为准。如果不想折腾平台鉴权可以先用公共 MQTT 服务器测试比如broker.emqx.io端口1883不需要用户名密码。等 MQTT 报文跑通后再把参数换成 OneNET 的。参数项公共 MQTT 测试OneNET 示例说明服务器地址broker.emqx.iomqtt.heclouds.com以平台文档为准端口18836002明文 MQTT 端口Client ID任意唯一字符串设备 ID不能重复Username可空产品 ID按平台要求Password可空鉴权 token可能需工具生成发布主题自定义平台定义注意主题权限创建完设备和参数后记下这些信息服务器地址、端口、Client ID、Username、Password、发布主题、订阅主题。把这些填到 STM32 代码的宏定义里编译烧录。5.2 参数映射与在线调试STM32 代码里不要写死太多字符串最好用宏定义集中管理#define MQTT_BROKER broker.emqx.io #define MQTT_PORT 1883 #define MQTT_CLIENT_ID stm32_esp01s_01 #define MQTT_USERNAME #define MQTT_PASSWORD #define MQTT_PUB_TOPIC stm32/esp01s/data #define MQTT_SUB_TOPIC stm32/esp01s/cmd #define MQTT_KEEPALIVE 60连接流程按顺序执行先发AT再ATCWMODE1再ATCWJAP再ATCIPMUX0再ATCIPSTART然后发 CONNECT 报文。每步成功打印一条日志失败就重试。调试串口上能看到类似AT - OK CWMODE - OK CWJAP - WIFI CONNECTED CWJAP - WIFI GOT IP CWJAP - OK CIPSTART - CONNECT CIPSTART - OK MQTT CONNECT - CONNACK 0x00看到CONNACK 0x00时说明设备已经连上云平台。如果平台侧显示设备在线但在线状态一会就掉通常是心跳没发或者 Keep Alive 设置太短。先检查PINGREQ是否按时间发出。5.3 数据上报与命令下发数据上报就是拼 PUBLISH 报文把传感器数据转成字符串或者 JSON作为载荷发出去。比如 DHT11 温湿度数据可以拼成{temp:25,humi:60}。注意 JSON 字符串长度要算准PUBLISH 剩余长度要包含主题长度和载荷长度。命令下发时云平台会向订阅主题发 PUBLISH 报文。STM32 收到IPD数据后先判断是不是 MQTT PUBLISH。固定头第一个字节高 4 位是0x30说明是 PUBLISH。然后解析剩余长度、主题长度、主题、载荷。如果主题匹配订阅主题就把载荷取出来执行。比如收到{led:1}就点亮 LED。解析 PUBLISH 时要注意主题长度和包标识符的位置。QoS 0 没有包标识符主题长度直接跟在剩余长度后面。QoS 1 会多两个字节的包标识符。如果订阅时用了 QoS 1解析时就要跳过这两个字节。建议先统一用 QoS 0 跑通再改 QoS 1。报文类型固定头首字节用途注意点CONNECT0x10连接平台剩余长度回填容易错CONNACK0x20连接确认返回码 0x00 才算成功PUBLISH0x30上报/下发数据QoS 决定有无包标识符SUBSCRIBE0x82订阅主题包标识符要递增PINGREQ0xC0心跳两个字节别拼错DISCONNECT0xE0断开连接主动断开时发6. 常见问题与排查实录6.1 ESP-01S 不响应或者返回乱码最常见的原因是波特率不对。ESP-01S 默认 115200但有些固件是 9600 或 74880。可以先在 115200 下发AT没反应就换 9600。如果返回乱码说明波特率不匹配多试几个常见值。还有可能是供电不足模组启动时电压跌落复位后打印启动信息看起来像乱码。用独立 3.3V 电源供电并在电源脚附近加一个 100uF 电解电容和一个 0.1uF 陶瓷电容能缓解瞬间掉压。接线错误也会导致不响应。TX 和 RX 必须交叉GND 必须共地。如果只连了 TX、RX 和 VCC没连 GND串口电平没有参考通信肯定失败。另外检查 EN 脚是否拉高GPIO0 是否悬空。如果 GPIO0 被拉低模组进入下载模式AT 固件不运行。6.2 TCP 连上但 MQTT 被拒绝TCP 连接成功只说明网络通了MQTT 连接失败通常是参数问题。CONNACK 返回码能直接告诉你原因0x01协议版本不对检查 CONNECT 报文里协议级别是不是0x040x02Client ID 被拒绝检查 Client ID 是否唯一、是否超过平台限制0x04用户名密码错误检查 Username 和 Password0x05未授权检查设备是否被禁用或者 token 过期。OneNET 的鉴权 token 有时效性过期后需要重新生成。如果设备之前在线突然连不上先看 token 是否过期。公共 MQTT 服务器如果不需要用户名密码CONNECT 标志位不要设置用户名和密码位否则服务器可能拒绝。还有一点MQTT 报文里的字符串长度字段是两个字节低字节在前高字节在后。比如 Client ID 长度 13要写成0x00 0x0D。如果写成0x0D 0x00平台解析长度会变成一个很大的数直接拒绝连接。6.3 数据发不出去或者频繁掉线数据发不出去先看ATCIPSEND是否返回。如果没有返回可能是 TCP 连接已经断开或者模组忙。可以先发ATCIPSTATUS查看连接状态。如果返回CLOSED需要重新走ATCIPSTART和 MQTT CONNECT 流程。频繁掉线通常是心跳问题。Keep Alive 设 60 秒但代码里没有按时发PINGREQ平台在 90 秒左右会断开。检查心跳定时逻辑确保主循环没有长时间阻塞。如果用了HAL_Delay(1000)这类阻塞延时心跳可能被推迟。把延时改成非阻塞的时间判断或者把心跳放在定时器中断里设置标志位。缓冲区溢出也会导致数据错乱。ESP-01S 返回的IPD数据可能比较长如果接收缓冲区只有 128 字节而云平台下发 200 字节的 JSON后面的数据就被丢了。把缓冲区设大一点至少 512 字节。解析时先判断长度不够就丢弃或者分片处理。现象可能原因排查方法AT 无响应波特率、接线、供电换波特率查 TX/RX单独供电返回乱码波特率不匹配、掉电试 9600/74880加电容TCP 连接失败WiFi 未连、地址端口错发 ATCIPSTATUS检查 IPCONNACK 0x04用户名密码错核对三元组重新生成 token上报无数据主题无权限、长度算错平台查日志抓包看报文设备频繁离线心跳未发、Keep Alive 短检查 PINGREQ 周期加大 Keep Alive收到数据乱码解析越界、QoS 不符按长度解析统一 QoS 07. 实战心得与后续扩展7.1 长时间运行的几个处理设备联网不能只跑几分钟要连续跑几天才算过关。我一般会加三个处理第一看门狗。STM32 的独立看门狗或者窗口看门狗在主循环里喂狗。如果程序跑飞看门狗复位后重新联网。第二断线重连。MQTT 连接断开后状态机回到 TCP 连接之前重新走一遍流程。重连不要太频繁每次失败后延时 5 秒到 10 秒。第三日志分级。调试阶段打印所有 AT 交互正式运行时只打印关键状态减少串口占用。ESP-01S 长时间运行偶尔会死机表现为不回复 AT。这种情况可以在代码里加一个“AT 探活”每隔几分钟发一次AT连续三次没响应就控制 ESP-01S 的复位脚或者用继电器断电重启。转接板上如果有 RST 脚可以接 STM32 的一个 GPIO低电平复位。注意复位后要重新初始化 WiFi 和 MQTT。7.2 后续可以扩展的方向跑通基础连接后可以往几个方向扩展。第一接传感器。DHT11、DS18B20、光敏电阻、土壤湿度传感器都可以把数据拼成 JSON 上报。第二接执行器。继电器、LED、蜂鸣器通过订阅命令主题实现远程控制。第三加 OLED 显示。本地显示网络状态、IP 地址、温湿度调试时不用一直看电脑。第四用 FreeRTOS。把网络任务、传感器任务、显示任务分开心跳和重连放在独立任务里主循环更清晰。如果产品要量产建议把 ESP-01S 换成支持 MQTT AT 固件的模组或者换成 ESP32直接跑 MQTT 库。但作为学习过程手打一遍 MQTT 报文非常有价值。以后看任何物联网平台的接入文档都能快速理解那些参数到底对应协议里的哪个字段。7.3 个人调试体会我在实际调试时最大的时间不是花在写代码上而是花在确认 ESP-01S 到底有没有收到命令。后来我养成了一个习惯所有发给 ESP-01S 的 AT 指令和收到的原始数据都通过调试串口打印出来二进制数据用 HEX 格式显示。这样一眼就能看出是没发出去还是发出去了但响应被解析错。另一个体会是不要一上来就接云平台。先用公共 MQTT 服务器把 CONNECT、SUBSCRIBE、PUBLISH 跑通确认 MQTT 报文拼装没问题。然后再换 OneNET把 Client ID、Username、Password 换成平台参数。这样出问题时你能确定是 MQTT 报文的问题还是平台鉴权的问题。每次只改一个变量排查效率会高很多。最后再分享一个小技巧剩余长度计算和回填是手写 MQTT 最容易出错的地方。可以在拼完报文后把整个报文按字节打印出来对照 MQTT 协议文档手工核对一遍。特别是 CONNECT 报文剩余长度、协议名长度、Client ID 长度、Username 长度、Password 长度任何一个长度字段写错平台都会直接拒绝。核对几次之后后面再写就顺手了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门