STM32F103+ESP8266 AT指令快速连接阿里云物联网平台
简介基于STM32F1与ESP8266的阿里云物联网接入源码面向嵌入式学习者和物联网开发人员演示通过AT指令方式完成设备上云与数据交互可运用于毕业设计、竞赛项目及产品原型开发。压缩包共291个文件体积仅6.38MB除C语言源文件与头文件外还包含工程文件、启动文件、十六进制烧录文件、批处理辅助脚本及编译中间产物工程配置、源码编写、编译烧录等环节文件一应俱全目录结构便于按模块查看。该工程已有1241人学习下载。工程基于Keil MDK构建源码可见系统时钟配置、定时器、模数转换采集、I2C通信、OLED显示等STM32F10x系列外设驱动并结合ESP8266模块的AT指令交互与阿里云设备接入流程完整呈现嵌入式终端上云的实现路径对快速打通硬件端到云端链路具有直接参考价值。1. 为什么用 STM32F1ESP8266 AT 指令直连阿里云而不是移植 SDK在做一个嵌入式上云原型时第一反应是在 STM32 上移植 C 版 MQTT 协议栈再通过 ESP8266 的 TCP 透传去连阿里云。结果发现一旦涉及加密、重连、心跳代码量和调试成本立刻上来了。而手头这份工程用了另一条路STM32F103 标准外设库写业务逻辑ESP8266 跑乐鑫的 MQTT AT 固件STM32 只发 AT 指令就能完成 Wi-Fi 连接、MQTT 鉴权和发布订阅。这样把协议栈差距到 AT 指令层上云的链路变得可观测、可复现。适合做设备接入层的快速验证也适合毕业生拿着逻辑图直接改功能。2. 硬件接线与 AT 固件选型STM32 串口驱动 ESP8266 的边界条件2.1 从工程文件识别硬件资源打开这包代码能看到 stm32f10x_rcc.c、stm32f10x_tim.c、stm32f10x_flash.c、stm32f10x_adc.c、stm32f10x_i2c.c 和 fy_oledfont.c。这基本能确定设计者当初规划的硬件资源RCC 做时钟使能TIM 做延时或 PWMFlash 用于参数存储ADC 采集传感器I2C 或许是接入外部 RTC 或 IO 扩展OLED 显示状态ESP8266 必然走串口。在动手改代码之前最好先确认你的开发板上这些外设是否都在因为 AT 连接阿里云本身只需要串口但属性上报需要实际数据源所以 ADC/TIM 在这里绑定了业务数据采集。2.2 STM32F103 与 ESP8266 的引脚连接ESP8266 的默认 AT 固件一般占用 UART0TXD0/RXD0而 STM32 需要使用一个剩余的 USART 作为数据链路。使用 USART2PA2/PA3比较常见因为很多板子把 USART1PA9/PA10留给了调试串口。信号STM32F103ESP8266 模块UART TXPA2 / USART2_TXRXDUART RXPA3 / USART2_RXTXDGNDGNDGND3.3VVCC外部 LDO 提供VCCCH_PD / EN上拉 3.3V使能引脚电平兼容方面ESP8266 的 GPIO 逻辑电平是 3.3VSTM32 的推挽输出高电平也是 3.3V可以直接驱动。但部分模块串口引脚没有电平转换注意别接到 5V 上。比较容易忽略的是 ESP8266 发射瞬间电流可达 300mA如果用 STM32 板载 LDO 给它供电会出现模块反复重启、AT 无响应这时候先查电源而不是查程序。我习惯在 ESP8266 RXD 串一个 100Ω 电阻用于抑制 STM32 TX 边沿的振铃。2.3 AT 固件版本与 MQTT 指令支持范围只有带 MQTT 功能的 AT 固件才能直接使用ATMQTTCONN这类指令。乐鑫官方发布的 AT 固件通常包含ATMQTTUSERCFG、ATMQTTCONN、ATMQTTSUB、ATMQTTPUB等但注意有些第三方精简固件只保留了 TCP/UDP需要先发ATMQTTCONN测试是否返回 ERROR否则就得重新刷入官方 AT 固件。初始化串口时我一般把波特率设为 115200使用 8N1并关闭流控。代码段使用 ST 标准外设库void USART2_Init(u32 baud) { GPIO_InitTypeDef gpio; USART_InitTypeDef usart; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); gpio.GPIO_Mode GPIO_Mode_AF_PP; // TX 复用推挽 gpio.GPIO_Pin GPIO_Pin_2; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; // RX 浮空输入 gpio.GPIO_Pin GPIO_Pin_3; GPIO_Init(GPIOA, gpio); usart.USART_BaudRate baud; usart.USART_WordLength USART_WordLength_8b; usart.USART_StopBits USART_StopBits_1; usart.USART_Parity USART_Parity_No; usart.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART2, usart); USART_Cmd(USART2, ENABLE); }这段代码的关键是 GPIOA2 配置为复用推挽输出GPIOA3 为浮空输入并用USART_Mode_RX | USART_Mode_TX同时打开收发。ESP8266 的 RXD 需要 TX 推挽才能稳定被拉高RX 用浮空输入通常没问题。如果发现接收乱码可以把 RX 改成GPIO_Mode_IPU开启内部上拉再测试。注意不要用 PA9/PA10 同时做调试串口和 ESP8266 串口否则 Keil 的 printf 和 AT 指令会互相干扰。2.4 直接使用 MQTT AT 而不是 AT 透传常见做法是让 ESP8266 进入透传模式ATCIPMODE1在 STM32 端用软件实现 MQTT 协议。另一种做法是使用 MQTT AT让固件内部直接处理 MQTT 报文。选择题数据量小、网络不稳定、需要快速上线的选 MQTT AT 更省心。因为 MQTT 报文中的发送、重发、心跳都由固件处理STM32 只要解析 AT 返回状态。要注意使用 MQTT AT 后不能再同时使用ATCIPSEND直接往 TCP socket 写数据因为 MQTT 指令有自己的连接 ID需要先查看ATMQTTCONN?返回当前连接状态确认连接 ID 为 0 后再操作。如果混用会出现 ERROR 或卡死在发送状态。3. 阿里云物联网平台配置与 MQTT 鉴权参数生成3.1 平台侧创建产品与设备阿里云物联网平台要做的核心事情是拿到三元组。ProductKey 是产品唯一标识DeviceName 在平台中唯一DeviceSecret 是设备密钥。创建产品时节点类型选直连设备联网方式选 Wi-Fi数据格式建议 JSON。每个产品可以添加功能定义比如温度、湿度属性标识符会对应到上报 JSON 的 key。设备添加成功后三元组在设备详情页的“MQTT 连接参数”里后面计算签名时要用。3.2 用脚本计算 clientId、username、password阿里云 IoT 平台 MQTT broker 使用的不是明文密码而是将 DeviceSecret 作为密钥对 clientId、deviceName、productKey、timestamp 拼接内容做 HMAC-SHA1 哈希。这里给出一个独立于设备的 Python 脚本方便在任何电脑上生成参数import hmac import hashlib product_key a1EF9xxx device_name stm32_demo device_secret 3ede7...xxx timestamp 974000000000 # 可自定义建议用当前毫秒时间戳 client_id f{product_key}.{device_name}|securemode3,signmethodhmacsha1,timestamp{timestamp}| username f{device_name}{product_key} content fclientId{client_id}deviceName{device_name}productKey{product_key}timestamp{timestamp} password hmac.new( device_secret.encode(), content.encode(), hashlib.sha1 ).hexdigest() print(client_id) print(username) print(password)运行后得到的三个字符串就是 AT 指令中ATMQTTUSERCFG用的参数。注意 content 里的 clientId 是带竖线和 securemode 的完整字符串而不仅仅是product_key.device_name。很多人在这里直接把 clientId 填成设备名导致 MQTT 连接被服务器拒绝。timestamp 可以自己生成只要保证一次性有效即可。3.3 AT 指令序列与 Topic 规划拿到三元组和签名后按顺序发送以下 AT 指令// 复位模块 ATRST // 等待 ready 后设置为 Station 模式 ATCWMODE1 // 连接家庭路由器避免 5G 频段 ATCWJAPyour_ssid,your_password // 配置 MQTT 连接参数注意前两个参数是 linkID 和协议使能 ATMQTTUSERCFG0,1,client_id,username,password // 连接阿里云 MQTT broker ATMQTTCONN0,iot-as-mqtt.cn-shanghai.aliyuncs.com,1883,1 // 订阅属性设置命令 ATMQTTSUB0,/sys/a1EF9xxx/stm32_demo/thing/service/property/set,0这里ATMQTTUSERCFG第 1 个参数 0 表示使用连接 ID 0第 2 个参数 1 表示使能 MQTT 协议如果填 0 则相当于裸 TCP。ATMQTTCONN第 4 个参数 1 表示开启 keep-alive 保活固件会自动发送 ping。Topic 与平台标准 Topic 保持一致建议把接收的下行 Topic 写成宏方便在 STM32 代码中替换。3.4 指令参数对照与常见误用AT 指令关键参数失败时常见原因ATCWJAPSSID,密码密码错误、路由器开了 5GATMQTTUSERCFGclientId/username/passwordclientId 漏了竖线ATMQTTCONNbroker 域名,端口,keepalive域名写错、设备未激活ATMQTTSUBtopic,qostopic 中 productKey/deviceName 写错不同阿里云 regionMQTT broker 域名后缀不一致。例如上海可用iot-as-mqtt.cn-shanghai.aliyuncs.com新加坡区用iot-as-mqtt.ap-southeast-1.aliyuncs.com。不要直接复制网上的域名去平台设备详情页的“MQTT 连接参数”里找实际地址。4. STM32 端 AT 状态机与 OLED 状态显示实现4.1 串口中断与 AT 应答解析ESP8266 的 AT 响应是按行返回的STM32 端通常用串口接收中断把字符拼成一个缓冲区再在行尾查找OK、ERROR、MQTTSUBRECV等标志。这里给出一个简单框架char at_rx_buf[256]; uint8_t at_rx_index 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { char c USART_ReceiveData(USART2); if (c \n) { at_rx_buf[at_rx_index] \0; process_at_line(at_rx_buf); at_rx_index 0; } else if (at_rx_index 255) { at_rx_buf[at_rx_index] c; } } }process_at_line里用strstr(line, OK)或者置位at_cmd_done 1。注意 AT 行有时会带\r前缀比较之前先做 trim。如果ATCWJAP返回WIFI DISCONNECT后再返回OK不能只等第一个OK就认为连接成功应该等收到WIFI GOT IP再往下走。OLED 在这里派上用场可以把当前收到的协议行直接滚动显示排错时非常直观。4.2 连接阿里云的状态机AT 链路是串行的且ATMQTTCONN从发送到返回 OK 可能耗时数百毫秒不能在一个循环里阻塞等待。常见做法是把连接流程拆成状态机每次定时器轮询推进状态。典型状态包括状态触发命令成功标志超时重试STATE_WIFI_CONNATCWJAPWIFI GOT IP2000ms 重试STATE_MQTT_CFGATMQTTUSERCFGOK1000ms 重试STATE_MQTT_CONNATMQTTCONNOK5000ms 重试STATE_MQTT_SUBATMQTTSUBOK2000ms 重试STATE_ONLINE周期上报心跳由固件处理——状态机伪代码如下static uint8_t at_state STATE_WIFI_CONN; static uint32_t at_tick 0; void at_state_machine(void) { switch (at_state) { case STATE_WIFI_CONN: at_send_cmd(ATCWJAP\ssid\,\password\); at_state STATE_WAIT_WIFI; at_tick HAL_GetTick(); break; case STATE_WAIT_WIFI: if (at_event AT_EVENT_GOT_IP) { at_send_cmd(ATMQTTUSERCFG...); at_state STATE_WAIT_MQTT_CFG; } else if (HAL_GetTick() - at_tick 3000) { at_state STATE_WIFI_CONN; // 重新发起连接 } break; // 后续状态... } }这里需要强调AT 指令收发之间不能并发所有at_send_cmd函数只入队不立即发送避免在中断里嵌套发送。此外收到MQTTDISCONNECTED事件时要把状态重置回STATE_WIFI_CONN或STATE_MQTT_CONN保证自动恢复。如果你用的不是 HAL 库HAL_GetTick需要替换成自己的 ms 时钟函数。4.3 属性上报与命令下发处理上报数据用ATMQTTPUBpayload 要加引号且逗号需要转义。常见做法是在 STM32 端用sprintf拼一个 JSON 字符串再填充到ATMQTTPUB中。代码示例char topic[] /sys/a1EF9xxx/stm32_demo/thing/event/property/post; char payload[64]; sprintf(payload, {\params\:{\temperature\:%.2f,\humidity\:%.2f}}, temp, hum); at_send_cmd_format(ATMQTTPUB0,\%s\,%d,\%s\,1,0, topic, strlen(payload), payload);这条命令最后的参数 1 表示 qos10 表示不移除 topic固件收到发布成功会返回MQTTPUB: 0。如果返回 ERROR 先检查 JSON 引号是否被转义。下行命令通过MQTTSUBRECV事件进入在process_at_line中捕获后直接做字符串匹配或 cJSON 解析然后修改全局变量控制执行器。OLED 显示连接状态和本地属性值可以用fy_oledfont.c内的字模数组直接更新。5. 用串口工具复现连接过程与掉线重连实战5.1 先用串口工具验证 MQTT 链路不启动 STM32 程序直接将 ESP8266 的串口接到 USB 转 TTL用串口工具逐条发同样的 AT 指令很快就能定位是平台参数问题还是硬件问题。特别时返回ATMQTTCONN超时后用ATMQTTCONN?查看错误码。常见错误码是 1 表示主机名解析失败2 表示 TCP 连接失败3 表示认证失败。这一步能缩小排查范围。5.2 STM32 里的自动重连与异常恢复实际工程中Wi-Fi 断开后串口提示WIFI DISCONNECTMQTT 断线提示MQTTDISCONNECTED: 0。在状态机里不能只依赖这些通知还要定期发送ATMQTTPING探测链路。建议在 STM32 端用 30 秒超时判断链路活性一旦超时直接发送ATRST然后从头进入状态机。另外把收到的下行MQTTSUBRECV的 timestamp 记录下来若连续 60 秒没有收到任何下行报文触发重连。注意在发送ATMQTTCONN之前必须确认上一次连接已经彻底断开否则会返回 ERROR这时先发ATMQTTCLEAN0清理 MQTT 连接状态再重新走配置流程。本文还有配套的精品资源点击获取