
简介面向STM32F103ZET6开发者、具备一定C语言基础的物联网实战资料演示借助ESP8266无线模块将DHT11温湿度数据上传至阿里云平台。资源基于正点原子ZET6开发板提供实物展示、完整接线方式说明、固件烧录指引以及常见问题汇总适合准备物联网课程设计、电子竞赛或希望快速实现云端数据采集的嵌入式学习者也可直接作为毕业设计或项目原型参考。压缩包内含318个文件整体大小约56.78MB以C语言与H头文件源码、BIN固件、BMP图片及PDF文档为主体便于直接查看源码和烧录固件同时附带了32Mbit与8Mbit两种常见容量的ESP8266固件、中英文演示程序以及Keil工程辅助文件文件目录划分清楚方便按需选型、对比和二次开发。资源已有1042人学习内容覆盖从硬件连接到云端配置的完整链路针对下载程序、烧录固件、模块复位、连接路由器等高频故障点都有说明并对2.4G频段选择、PING报文回复等易错环节给出了排查思路能显著减少入门阶段的试错成本。1. 不接串口屏ZET6靠一颗ESP8266把数据送进阿里云STM32F103ZET6 的板子很常见跑起来之后最缺的不是算力而是一条能联网的路。ESP8266 模块价格低、生态成熟阿里云物联网平台又支持 MQTT 直连三者凑在一起就成了最经典的“单片机 Wi-Fi 云平台”教学组合。这个标题的核心动作是ZET6 通过串口向 ESP8266 发 AT 指令让 ESP8266 以 MQTT 协议连接阿里云并把温湿度、GPIO 状态之类的小数据帧推到云端的物模型里。这套链路并不复杂但卡住过不少初学者。常见的问题是硬件上只接 VCC、GND、TXD、RXD 就以为能通却忽略了 ESP8266 的供电电流和电平逻辑或者固件版本太老AT 指令行为不一致又或者阿里云那边的三元组、Topic、签名规则没搞清楚。下面按“硬件准备 → 云平台配置 → STM32 代码实现 → 排查方法”的顺序把整条链路的每一步讲透。适合手里有 ZET6 核心板、想快速把数据跑上云的人也适合把这套代码移植到其他 STM32 型号时对照使用。2. ESP8266 与 ZET6 硬接线与 AT 固件准备2.1 ZET6 与 ESP8266 的接线先从电平适配开始先明确一件事ESP8266 模块是 3.3V 供电IO 逻辑电平也是 3.3V。ZET6 的普通 GPIO 在大部分板上也是 3.3V 供电所以两者之间不需要电平转换这一点和 Arduino 的 5V 逻辑不一样。但要注意个别开发板上某个引脚被接成了 5V 容忍或者电源部分用了 5V 供电的稳压方案这时就需要确认 ESP8266 模块是不是独立稳压到了 3.3V。常见接线方案是用 STM32 的 USART2 做通信口因为 USART1 经常被占用USART2 的 PA2TX、PA3RX在核心板上都有引出。ESP8266 的 URXD 接 STM32 的 PA2UTXD 接 STM32 的 PA3构成交叉连接。ZET6 引脚ESP8266 引脚说明PA2USART2_TXURXDSTM32 发数据给 ESP8266PA3USART2_RXUTXDESP8266 发数据给 STM323.3V 输出VCC供电正极GNDGND共地3.3V 或 GPIO 高电平CH_PD使能引脚必须拉高悬空GPIO0运行模式悬空或接高CH_PD 是很多人漏掉的一根线它一旦浮空模块可能无法正常工作。GPIO0 在正常运行模式下要悬空或接 3.3V如果接了 GND就进入了烧录模式表现为模块不响应 AT 指令。ESP8266 启动瞬间电流可以到 300mA 以上最好在 VCC 和 GND 之间并联一个 100uF 电容避免供电被拉垮导致 WDT 复位。接好线后先不要接 STM32 程序直接打开串口调试助手看模块是否正常。把 ESP8266 的 TX 接到 USB 转 TTL 的 RX模块 RX 接 USB 转 TTL 的 TX共地然后给模块供 3.3V。2.2 检查 AT 固件版本与基础通信串口必须先通模块上电后串口调试助手发送AT如果收到OK说明最基本的串口链路没问题。未收到时先检查波特率是否匹配推荐 115200 或 9600。部分模块默认波特率是 115200但也有出厂是 9600 的情况逐个试就能确定。# Linux 下用 minicom 快速确认 AT 响应 minicom -b 115200 -D /dev/ttyUSB0发送 AT 指令后模块才会回到OK。如果之前用某个串口助手连过模块并设置了不换行可能导致指令拼接错误。AT 指令必须以回车换行结尾在 minicom 下按回车键即可。确认基础通信后查询固件版本ATGMR能看到类似 1.x 或 2.x 的版本号。建议用支持 MQTT 的 AT 固件早期 0.x 版本的固件对 TCP 长连接支持不好。市面上的 ESP-01 模块大多出厂已经带 AT 固件不需要自己烧录但如果模块被刷过 NodeMCU 固件或 SPI 接口固件就得重新烧 AT 固件。提示ESP8266 只能连接 2.4GHz 的 Wi-Fi 网络不支持 5GHz 频段。手机热点要在“便携式热点设置”里把 AP 频段设为 2.4GHz否则模块扫描不到热点。2.3 AT 指令建 Wi-Fi 连接最小三件套别写错Wi-Fi 配置和连接是最容易抄错的一段指令。不同固件版本的指令格式有差别但以下这套在 1.x 以上版本是通用的。ATCWMODE1 // 1 表示 Station 模式只当客户端 ATCWJAPMy-AP,password // 连接 Wi-FiSSID 和密码用双引号包住 ATCIPSTA? // 查询获得的 IP 地址确认已连上第一条指令设置 Wi-Fi 工作模式第二条连接热点。连接前建议先执行ATCWQAP断开已有连接以免模块还连着上一个热点时状态异常。第三条查询 IP等待返回CIPSTA:ip:192.168.x.x后才说明拿到 DHCP 地址。ATCWJAPMy-AP,password # 成功返回 WIFI GOT IP # 失败返回 WIFI DISCONNECT 或 CWJAP:1/2/3 等错误码连接失败时先检查 SSID 是否带特殊字符。路由器名称里如果有中文或空格AT 指令解析会出错建议临时把热点改成纯英文名。密码里的特殊字符同样存在转义问题最好一开始都用简单字符测试。如果附近可用热点很多信号弱也会导致 GOT IP 反复失败。这套 AT 指令不只是初始化用后面 STM32 程序里也要在 main 函数开始阶段依次发送。把 Wi-Fi 连接逻辑和 MQTT 连接逻辑分开做状态判断避免在没联网时就发 MQTT 数据包。3. 阿里云物联网平台的设备创建与 MQTT 连接参数计算3.1 产品、设备、三元组先把层级建对再谈上传阿里云物联网平台里的结构是“产品 → 设备”两级。产品定义物模型和数据格式设备挂载在产品之下具备唯一身份。创建产品时选择“自定义品类”数据格式建议选“Alink JSON”或“透传”前者方便在控制台直接看到属性上报后者适合自己拼二进制帧。学习阶段直接用 Alink JSON省去编解码脚本的麻烦。进入设备列表后新增一个设备会生成三个关键身份信息参数含义示例ProductKey产品唯一标识a1abcXYZ123DeviceName设备名称esp8266_zet6DeviceSecret设备密钥32 位十六进制字符串这三个参数合称三元组后面计算 MQTT 连接参数时全部使用。设备建立后先不要着急写代码把状态停在“未激活”也没问题连接成功后会自动变更为“在线”。注意ProductKey 和 DeviceName 是明文传输的DeviceSecret 只用来做签名不会在网络上明文传输。不要把 DeviceSecret 硬编码在容易被反编译的移动端但单片机固件里只能如此学习阶段可以接受。3.2 手算 clientId、username、password附可复制脚本阿里云 MQTT 连接需要三个动态参数很多人卡在这一步。实际的算法是clientId{productKey}.{deviceName}|securemode3,signmethodhmacmd5,timestamp{timestamp}|username{deviceName}{productKey}password以 deviceSecret 为密钥对clientId{content}deviceName{deviceName}productKey{productKey}timestamp{timestamp}做 HMAC-MD5 计算其中 clientId 的{content}是securemode3,signmethodhmacmd5,timestamp{timestamp}这段不含竖线。时间戳用毫秒级可以取当前 Unix 时间戳乘以 1000。三种签名方法可选 hmacmd5、hmacsha1、hmacsha256使用前要在 IoT 平台的“产品详情 → Topic 类”里确认支持推荐 hmacmd5计算最简单所有平台都能跑。下面这段 Python 脚本可以本地先算出参数再和单片机实现对比import hmac import hashlib import time product_key a1abcXYZ123 device_name esp8266_zet6 device_secret 0123456789abcdef0123456789abcdef timestamp str(int(time.time() * 1000)) content securemode3,signmethodhmacmd5,timestamp{}.format(timestamp) client_id {}.{}|{}|.format(product_key, device_name, content) username {}{}.format(device_name, product_key) raw clientId{}deviceName{}productKey{}timestamp{}.format( client_id, device_name, product_key, timestamp) password hmac.new(device_secret.encode(), raw.encode(), hashlib.md5).hexdigest() print(clientId:, client_id) print(username:, username) print(password:, password)运行一次得到三行结果再和报错日志对照。常见错误是 content 拼接少了 securemode 字段或者 clientId 末尾漏了竖线导致服务器解析超时。单片机端实现时这三个字符串需要放入 char 数组长度要预先算够后面第 4 章的代码里会直接使用。3.3 设备要操作的 Topic上报属性必须走对前缀阿里云产品模型定义了三类默认 Topic属性上报、属性设置、事件上报。最常用的是属性上报和属性设置。属性上报 Topic 路径固定为/sys/{productKey}/{deviceName}/thing/event/property/post对应的时间戳格式要求是毫秒物模型数据用 JSON 包一层params。平台返回的消息订阅路径为/sys/{productKey}/{deviceName}/thing/event/property/post_reply操作方向Topic 路径权限上报属性/sys/{pk}/{dn}/thing/event/property/post发布平台回复上报/sys/{pk}/{dn}/thing/event/property/post_reply订阅服务调用/sys/{pk}/{dn}/thing/service/开头订阅如果产品创建时没定义物模型属性上报时平台可能不解析但 MQTT 层面一样会收到 PUBACK。为了能看到云端的物模型数据需要在产品里先定义一个“温湿度”属性标识符写temperature或humidity数据类选 float 或 int。上报的 JSON 要和物模型标识符严格一致大小写敏感。4. ZET6 端 STM32 代码串口 AT 指令与 MQTT 报文的逐段实现4.1 串口驱动与 AT 指令发送状态机比阻塞延时更可靠ZET6 和 ESP8266 之间就是普通串口通信先把USART2初始化做好波特率设为 115200开启接收中断。数据接收使用环形缓冲区避免在中断里做复杂处理。发送 AT 指令时等待应答可以简单用HAL_UART_Receive超时轮询但更稳的做法是发送后等待OK或ERROR关键词。uint8_t esp8266_tx_buffer[256]; uint8_t esp8266_rx_buffer[512]; volatile uint8_t esp8266_rx_index 0; void ESP8266_Send_Cmd(const char *cmd, const char *ack, uint16_t timeout) { HAL_UART_Transmit(huart2, (uint8_t *)cmd, strlen(cmd), 200); // 等待串口接收到回应 uint32_t start HAL_GetTick(); memset(esp8266_rx_buffer, 0, sizeof(esp8266_rx_buffer)); esp8266_rx_index 0; while (HAL_GetTick() - start timeout) { if (strstr((char *)esp8266_rx_buffer, ack) ! NULL) { return; } } }发送ATCIPSTART这类指令后模块可能要几百毫秒才返回 CONNECT OK所以超时时间要放到 2000ms。接收缓冲区的strstr依赖完整的一帧数据如果串口中断里一次只收一个字节会出现判断时机过晚的问题。简单做法是在中断服务函数末尾检查\n字符收到换行后再让主循环去匹配关键字。4.2 拼 MQTT CONNECT 报文按字节把握手包写进数组ESP8266 的 AT 固件不能直接发 MQTT 报文需要先进入透传模式再把 MQTT 报文按 TCP 数据流发出去。连接 MQTT 服务器时先用 AT 指令建 TCP 连接再发送 CONNECT 报文。const char *product_key a1abcXYZ123; const char *device_name esp8266_zet6; const char *password 0123456789abcdef0123456789abcdef; uint8_t mqtt_buf[512]; void MQTT_Connect_Packet(void) { char client_id[128], username[128], pass[64]; uint16_t pos 0; sprintf(client_id, %s.%s|securemode3,signmethodhmacmd5,timestamp1750000000000|, product_key, device_name); sprintf(username, %s%s, device_name, product_key); snprintf(pass, sizeof(pass), %s, password); // 固定报头 mqtt_buf[pos] 0x10; // 剩余长度先占位后面回填 uint8_t len_pos pos; // 可变报头协议名 MQTT 和协议级别 mqtt_buf[pos] 0x00; mqtt_buf[pos] 0x04; mqtt_buf[pos] M; mqtt_buf[pos] Q; mqtt_buf[pos] T; mqtt_buf[pos] T; mqtt_buf[pos] 0x04; mqtt_buf[pos] 0x02; // connect flags: clean session mqtt_buf[pos] 0x00; mqtt_buf[pos] 0x3C; // keepalive: 60 秒 // payload: clientId mqtt_buf[pos] strlen(client_id) 8; mqtt_buf[pos] strlen(client_id) 0xFF; memcpy(mqtt_buf[pos], client_id, strlen(client_id)); pos strlen(client_id); // payload: username mqtt_buf[pos] strlen(username) 8; mqtt_buf[pos] strlen(username) 0xFF; memcpy(mqtt_buf[pos], username, strlen(username)); pos strlen(username); // payload: password mqtt_buf[pos] strlen(pass) 8; mqtt_buf[pos] strlen(pass) 0xFF; memcpy(mqtt_buf[pos], pass, strlen(pass)); pos strlen(pass); mqtt_buf[len_pos] pos - len_pos - 1; // 回填剩余长度 }0x10表示 CONNECT 报文第二个字节是剩余字节数。剩余长度在拼完所有字段后回填。注意 clientId、username、password 前都要有两个字节的 UTF-8 字符串长度高位在前。connect flags 用0x02表示 clean session 为 1表示连接时清理会话这样断线重连后不会被旧的会话状态干扰。阿里云要求 CONNECT 报文的 keepalive 必须小于等于 300 秒这里设置成 60 秒也是 ESP8266 保活机制的安全范围。如果设置太长模块在 NAT 超时后收不到心跳响应会误以为连接正常。顺序上clientId、username、password 三段必须严格按此拼接顺序颠倒会导致服务器返回协议错误。4.3 拼 PUBLISH 报文属性数据怎么塞进 JSON 并发布属性上报的 Topic 固定数据内容用 JSON 包一层。发布时固定报头使用0x30QoS 0如果希望平台确认收到可以用0x32QoS 1但需要额外处理报文标识符和 PUBACK 应答。学习阶段用 QoS 0 足够云端物模型照样能解析到属性值。char publish_topic[128]; char json_msg[128]; void MQTT_Publish_Temperature(float temperature) { snprintf(publish_topic, sizeof(publish_topic), /sys/%s/%s/thing/event/property/post, product_key, device_name); snprintf(json_msg, sizeof(json_msg), {\params\:{\temperature\:%.2f},\version\:\1.0.0\}, temperature); uint16_t pos 0; uint8_t *pkt mqtt_buf; pkt[pos] 0x30; uint8_t rem_len_pos pos; pkt[pos] strlen(publish_topic) 8; pkt[pos] strlen(publish_topic) 0xFF; memcpy(pkt[pos], publish_topic, strlen(publish_topic)); pos strlen(publish_topic); memcpy(pkt[pos], json_msg, strlen(json_msg)); pos strlen(json_msg); pkt[rem_len_pos] pos - rem_len_pos - 1; // 进入透传发送 ESP8266_AT_SendData(pkt, pos); }JSON 字符串里的temperature就是物模型里定义过的一个 float 属性标识符。如果物模型没有定义该属性云端日志里能看到字段不存在的报错。发送前要确认 ESP8266 已经进入了透传模式否则这串字节会被当成 AT 指令解析。ESP8266_AT_SendData要拆成三段先ATCIPSENDlen告知模块要发送的字节数收到后再发送实际数据最后等模块返回SEND OK。不要在一开始就把数据包交给模块否则前 20 字节可能会丢失。void ESP8266_AT_SendData(uint8_t *data, uint16_t len) { char cmd[32]; sprintf(cmd, ATCIPSEND%d\r\n, len); ESP8266_Send_Cmd(cmd, , 1000); HAL_UART_Transmit(huart2, data, len, 1000); ESP8266_Send_Cmd((char *)data, SEND OK, 3000); }4.4 初始化到上报的主流程AT 指令逐个过别跳步上电后先初始化串口然后依次执行 Wi-Fi 连接、TCP 连接、MQTT CONNECT最后才进入上报循环。每步之间必须有应答检查没有确认就继续下一步是最大的坑。int main(void) { HAL_Init(); SystemClock_Config(); MX_USART2_UART_Init(); ESP8266_Send_Cmd(AT\r\n, OK, 2000); ESP8266_Send_Cmd(ATCWMODE1\r\n, OK, 2000); ESP8266_Send_Cmd(ATCWJAP\My-AP\,\password\\r\n, WIFI GOT IP, 10000); ESP8266_Send_Cmd( ATCIPSTART\TCP\,\iot-06-00a.aliyuncs.com\,1883\r\n, CONNECT OK, 5000); MQTT_Connect_Packet(); while (1) { MQTT_Publish_Temperature(24.50); HAL_Delay(10000); } }TCP 连接地址填产品所在地的 MQTT 接入地址在 IoT 平台产品详情页的“设备接入”页面能看到。不要填www.aliyun.com之类域名。端口 1883 是加密关闭的明文端口学习够用企业项目建议使用 443 端口加 TLS但 ESP8266 的 AT 固件做 TLS 要额外消耗大量内存且证书管理复杂学习阶段用 1883 即可。主循环里 10 秒发一次数据这个频率对学习和展示足够。如果发太快ESP8266 的 TCP 栈和 AT 指令解析可能来不及导致串口缓冲溢出表现就是数据断续或 AT 指令被截断。5. 上传失败的高频雷区与在线验证方法5.1 优先看阿里云设备日志区分“没连上”和“没数据”设备激活并连接后打开 IoT 平台控制台进入设备详情页的“日志服务”点击“云端运行日志”里面会按时间列出设备的上连接、断开、消息上报记录。这是排查的第一现场。如果只看到handshake成功但没有消息记录问题在 STM32 的 PUBLISH 报文拼装或透传发送如果连 CONNECT 都失败问题在 clientId、username、password 三个参数计算上如果看到topic not exist检查 Topic 前缀是不是完整路径是否少了/thing/event/property/post这一段在线调试也是个好办法。设备详情页的“在线调试”里可以直接选择属性上报向设备发送期望属性看设备是否收到。反过来设备上报后也可以在这个界面看到物模型属性和最近上报的时间戳确认平台侧解析成功。5.2 四个高频雷区时间戳、重连速度、Topic 拼接、AT 回显时间戳是签名参数的一部分如果生成 clientId 时写死了一个过期时间连接会被拒绝。很多人为了方便直接把阿里云示例里的时间戳复制过来结果连接永远是login fail。动态获取时间戳在单片机上成本不高用一个time(NULL)或从 NTP 获取都行最稳妥的是在启动阶段让 ESP8266 通过ATCIPSNTPCFG同步一次网络时间。重连速度太高会触发物联网平台的限流规则。控制台有连接频率限制一般 1 秒内最多一次连接尝试。断线重连逻辑要加延时不能用while(1)无限循环去发 CONNECT。常见做法是失败后延迟 5 秒再重试连续三次失败后延时拉长到 30 秒。AT 指令里的ATCWJAP重连热点后TCP 连接也会断开所以重连顺序必须是 Wi-Fi → TCP → MQTT 三级依次重新建立。Topic 拼接错误多发生在产品标识和设备名拼错时。注意在物联网平台中{productKey}和{deviceName}是全小写字母和数字的组合手工抄写时容易把0写成O。建议从控制台直接复制到代码里不要手打。AT 回显干扰是新手最容易忽视的问题。模块默认打开回显echo发送的每条指令都会原样返回然后才是响应。如果接收缓冲区里既有回显又有响应strstr匹配到的可能是回显里的关键词让状态判断提前通过或提前失败。解决办法是启动时发送ATE0关闭回显这样缓冲里只剩下干净的响应内容。提示连接失败后查看模块的完整输出不要只看是否出现OK。ERROR前面通常有具体错误码比如CLOSED表示 TCP 连接被对端关闭PING TIMEOUT表示心跳超时DNS FAIL表示接入域名解析失败。5.3 用 PC 工具先裸跑 MQTT验证参数比烧录更省时间在 STM32 和 ESP8266 之前先用 PC 端工具验证阿里云配置是否正确。打开一个支持 MQTT 的 PC 客户端常见的有 MQTTX、MQTT Explorer填入上面算出的 clientId、username、password服务器地址填接入域名端口 1883。如果 PC 能连上并收到CONNACK那问题肯定在单片机侧不在云平台配置。这个方法能快速圈定问题归属。如果 PC 端也连不上再回去检查三元组是否正确、时间戳是否过期、产品是否开通 MQTT 接入。PC 端连上后还可以直接发布一个属性上报的 Topic看控制台日志服务里是否出现对应的消息记录。这一步验证的就是 Topic 格式和 JSON 结构不用反复烧录 ZET6 的固件。最后保留一个确定性的验证顺序先看 ESP8266 串口原始输出确认 CONNACK 里的返回码再在云端日志确认设备上下线和上行消息最后在物模型数据里看数值变化。如果云端已显示设备在线但属性没有更新检查上报 JSON 的字段名是否和物模型标识符一致浮点数精度和单位也能在物模型定义里预先约束好避免实际应用里的单位换算错误。本文还有配套的精品资源点击获取