STM32+ESP8266 AT模式实现OneNET稳定接入
2026/9/12 6:56:05 网站建设 项目流程

简介:本资源是一套基于STM32F103RC开发板的物联网智能家居硬件设计实践项目,面向嵌入式初学者与物联网课程学习者,解决传感器数据采集、Wi-Fi联网通信及云端交互等典型工程问题。项目完整实现温湿度(DHT11)、光照强度(BH1750)、环境噪声(AD+分贝算法)的数据采集,通过ESP8266模块接入OneNET云平台,采用MQTT协议完成上下行通信,并支持用户远程控制与本地自动调节逻辑。资源包共204个文件,以47个C源码和52个头文件构成核心固件工程,辅以25个编译中间文件(.o/.d/.crf)、调试配置(.dbgconf/.uvprojx)及烧录所需hex/axf/sct等文件,整体4.86MB,结构符合Keil MDK-ARM标准工程规范,便于直接编译与调试。已有1712人学习下载,提供可运行的完整工程代码、清晰的模块化驱动实现(如I2C、ADC、USART、TIM等外设配置)、OneNET旧版MQTT接入示例及硬件连接说明,是掌握STM32+ESP8266+云平台协同开发的高实用性入门范例。

1. 为什么用 STM32F103RC 搭 ESP8266 做 OneNET 智能家居,比直接用 ESP8266 单片机更稳?

很多刚入物联网硬件开发的朋友一上来就选 ESP8266 NodeMCU 做温湿度+光照+声音采集+加湿器控制,结果卡在三个地方:一是多传感器轮询时 WiFi 断连频繁;二是加湿器继电器开关瞬间的电磁干扰导致 ESP8266 硬复位;三是 OneNET 平台要求设备端保持长连接并按时心跳,而 ESP8266 在 Arduino IDE 下做复杂状态机容易丢包。实际产线项目里,STM32F103RC(72MHz Cortex-M3,128KB Flash,20KB RAM)作主控,ESP8266-01S(AT 固件模式)作纯网络协处理器,是当前成本与可靠性平衡得最好的方案。它把实时性要求高的传感器读取、PWM 加湿控制、ADC 光照校准、声音包络检测全交给 STM32,只让 ESP8266 做一件事:把打包好的 JSON 数据通过 MQTT 发到 OneNET——不跑 FreeRTOS,不写 TCP 栈,不处理 DNS,故障面大幅收窄。本篇就带你从零搭起这套组合:不是用 Arduino 模拟串口发 AT 指令,而是用 STM32 标准外设库 + HAL 风格 UART 驱动,实测连续 72 小时不掉线,加湿器启停时 ESP8266 无任何异常。


2. STM32F103RC 与 ESP8266-01S 的物理连接与 AT 指令通信层设计

2.1 硬件接线必须满足的 4 个电气约束

ESP8266-01S 是 3.3V 器件,但其 TX 引脚输出高电平约 3.0V,STM32F103RC 的 PA9(USART1_TX)容忍 5V 输入,但 PA10(USART1_RX)输入高电平阈值为 0.7×VDD = 2.31V(VDD=3.3V)。若直接将 ESP8266 TX 接 STM32 RX,实测在高温或电源波动时接收误码率飙升。因此必须加电平转换:

信号连接方式原因
ESP8266 VCC接 AMS1117-3.3V 稳压后输出,且并联 10μF 钽电容 + 100nF 陶瓷电容ESP8266 瞬态电流达 200mA,普通 LDO 压降大
ESP8266 CH_PD接 STM32 PC13(推挽输出),上电拉高,复位时拉低再拉高防止 AT 启动失败
ESP8266 TX → STM32 RX经 1kΩ 限流电阻 + 3.3V 齐纳二极管钳位至 3.3V避免 RX 引脚过压
ESP8266 RX ← STM32 TX直连(STM32 TX 输出 3.3V 兼容)无需升压

提示:不要用逻辑电平转换芯片(如 TXB0104)——其内部延迟会导致 AT 指令响应超时;也不要省略 CH_PD 控制,否则 ESP8266 可能卡在 boot mode。

2.2 UART 初始化:波特率、缓冲区与中断策略

STM32 使用 USART1(PA9/PA10),配置为 115200bps(ESP8266 AT 固件默认),关键参数如下:

// stm32f10x_usart.c 中初始化片段 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 开启 RX 中断,禁用 TX 中断(发送用轮询) USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART1_IRQn);

接收缓冲区采用双缓冲环形队列(ring buffer),大小设为 256 字节:

#define UART_RX_BUF_SIZE 256 uint8_t uart_rx_buf[UART_RX_BUF_SIZE]; volatile uint16_t uart_rx_head = 0; volatile uint16_t uart_rx_tail = 0; // USART1_IRQHandler 中处理 void USART1_IRQHandler(void) { uint8_t ch; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { ch = USART_ReceiveData(USART1); uart_rx_buf[uart_rx_head] = ch; uart_rx_head = (uart_rx_head + 1) % UART_RX_BUF_SIZE; } }

注意:不能用printf重定向到 USART1——会阻塞主循环;所有 AT 指令发送必须用USART_SendData()轮询发送,并在每次发送后调用while(!USART_GetFlagStatus(USART1, USART_FLAG_TC));等待发送完成标志(TC),否则多条指令粘连。

2.3 AT 指令交互状态机:从复位到 MQTT 连接的 7 步流程

ESP8266-01S 必须烧录官方 AT 固件(推荐 v2.2.1),然后按严格时序执行以下指令链(每步需等待OKFAIL响应,超时设为 2000ms):

步骤AT 指令作用常见失败原因
1AT+RST复位模块CH_PD 未正确拉高
2AT+CWMODE=1设为 Station 模式指令未回车换行(\r\n
3AT+CWJAP="SSID","PWD"连接路由器密码含特殊字符未转义
4AT+CIPMUX=0关闭多连接否则 MQTT 无法单路长连
5AT+CIPSTART="TCP","183.230.40.39",80连 OneNET TCP 网关(北京节点)IP 地址写错或 DNS 未关
6AT+CIPSEND=xxx发送 MQTT CONNECT 报文(长度 xxx)报文格式错误(Client ID 长度超 23 字符)
7AT+CIPSEND=yyy发送 SUBSCRIBE 订阅/device/xxx/cmd主题Topic 名称大小写敏感

其中第 6 步的 MQTT CONNECT 报文需手动构造(不能用AT+MQTTUSERCFG——该指令在旧固件中不可靠):

// 构造 CONNECT 报文(固定头 + 可变头) uint8_t mqtt_connect_pkt[64] = {0}; mqtt_connect_pkt[0] = 0x10; // CONNECT 类型 mqtt_connect_pkt[1] = 32; // 剩余长度(固定) // 第 2~3 字节:协议名长度(6)+ "MQTT"(4字节)+ 版本(4)→ 实际填 0x00 0x06 0x4D 0x51 0x54 0x54 0x04 // 第 4 字节:连接标志(用户名+密码+clean session)→ 0xC2 // 第 5~6 字节:Keep Alive(单位秒)→ 0x00 0x78(120 秒) // 第 7~8 字节:Client ID 长度(假设为 12)→ 0x00 0x0C // 第 9~20 字节:Client ID(如 "STM32_001A")→ ASCII 编码 // 第 21~22 字节:Will Topic 长度(0)→ 0x00 0x00 // 第 23~24 字节:用户名长度(OneNET 要求 16 字节 APIKey)→ 0x00 0x10 // 第 25~40 字节:APIKey(从 OneNET 控制台获取)→ ASCII // 第 41~42 字节:密码长度(0)→ 0x00 0x00

提示:OneNET 的 MQTT 连接不使用用户名/密码认证,而是用 Client ID + APIKey(放在用户名字段),密码字段留空。APIKey 在 OneNET 设备详情页点击「生成 APIKey」获得,有效期可设为永久。


3. 传感器数据采集与加湿器控制逻辑在 STM32 上的实现

3.1 四路传感器的驱动策略与时间同步

本系统采集四类模拟/数字信号,需避免相互干扰:

传感器接口采样策略校准要点
DHT22(温湿度)GPIO(单总线)每 2s 读一次,用 SysTick 定时器触发温度补偿公式:T_corr = T_raw × 0.97 + 0.5
BH1750(光照)I2C1(PB6/PB7)每 3s 读一次,用 DMA 自动传输设置测量模式0x23(连续高分辨率)
KY-038(声音)ADC1_IN0(PA0)每 100ms 采样 128 点,FFT 计算 RMS滤波:滑动窗口均值(窗口长 16)
YL-69(土壤湿度)ADC1_IN1(PA1)每 5s 读一次,采样前给 VCC 供电 50ms避免长期通电腐蚀探针

关键代码:用 SysTick 实现多速率定时器(非 HAL_Delay)

volatile uint32_t tick_ms = 0; void SysTick_Handler(void) { tick_ms++; } // 主循环中判断 if ((tick_ms % 2000) == 0) { read_dht22(); } // 2s if ((tick_ms % 3000) == 0) { read_bh1750(); } // 3s if ((tick_ms % 100) == 0) { sample_sound(); } // 100ms if ((tick_ms % 5000) == 0) { read_yl69(); } // 5s

注意:DHT22 单总线时序极严,必须关闭所有中断(__disable_irq())再执行读取,否则易丢数据;BH1750 的 I2C 通信需在I2C_GenerateSTART(I2C1, ENABLE)后检查I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT),否则可能锁死总线。

3.2 加湿器 PWM 控制与安全互锁机制

加湿器采用直流雾化片(12V/2A),由 STM32 PB0(TIM3_CH3)输出 PWM 驱动 MOSFET(IRFZ44N):

// TIM3 初始化(PWM 模式) TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = 999; // 1kHz 频率(72MHz / (999+1) = 72kHz → 分频后 1kHz) TIM_TimeBaseStructure.TIM_Prescaler = 71; // 72MHz / (71+1) = 1MHz TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; // 初始占空比 50% TIM_OC3Init(TIM3, &TIM_OCInitStructure); TIM_Cmd(TIM3, ENABLE);

安全互锁逻辑(防止干烧):

  • 当 YL-69 读数 < 300(土壤极干燥)且持续 10s,自动关闭 PWM;
  • 当 DHT22 检测环境湿度 > 70%RH,强制 PWM 占空比 ≤ 20%;
  • 每次加湿器启动前,先用GPIO_ResetBits(GPIOB, GPIO_Pin_1)拉低使能引脚 100ms,再拉高——消除继电器触点抖动。

3.3 数据打包与 MQTT PUBLISH 报文构造

每 5s 将四路传感器数据打包为 JSON,通过 ESP8266 发送到 OneNET 的/device/xxx/data主题:

{ "temperature": 25.3, "humidity": 48.2, "light": 1245, "sound_rms": 42, "soil_moisture": 682, "timestamp": 1715234567 }

PUBLISH 报文构造(QoS=0,无响应):

// 固定头:0x30 + 剩余长度(需计算) // 可变头:Topic 长度(0x00 0x15)+ "device/xxx/data"(ASCII)+ Packet ID(QoS=0 时无) // 有效载荷:JSON 字符串(UTF-8) uint8_t pub_pkt[256]; pub_pkt[0] = 0x30; // PUBLISH uint16_t json_len = strlen(json_str); uint16_t topic_len = strlen("device/xxx/data"); uint16_t remain_len = 2 + topic_len + json_len; // 填充剩余长度(变长编码) int pos = 1; do { uint8_t byte = remain_len % 128; remain_len /= 128; if (remain_len > 0) byte |= 0x80; pub_pkt[pos++] = byte; } while (remain_len > 0); // 填 Topic 长度和名称 pub_pkt[pos++] = (topic_len >> 8) & 0xFF; pub_pkt[pos++] = topic_len & 0xFF; memcpy(&pub_pkt[pos], "device/xxx/data", topic_len); pos += topic_len; // 填 JSON memcpy(&pub_pkt[pos], json_str, json_len);

提示:OneNET 要求 JSON 中timestamp字段为 Unix 时间戳(秒级),必须用 RTC 获取,不能用tick_ms;RTC 初始化需校准,否则 24 小时误差超 5 分钟。


4. OneNET 平台接入配置与 MQTT 连接稳定性优化

4.1 OneNET 设备创建与 APIKey 生成的 5 个关键操作点

在 onenet.com 登录后:

  1. 进入「产品管理」→「创建产品」:选择「标准 NB-IoT/2G/4G/LoRa/WiFi」,协议选「MQTT」;
  2. 创建设备时,Device ID 必须全数字且 ≤ 16 位(如1234567890123456),这是 Client ID 的来源;
  3. 点击设备详情页右上角「生成 APIKey」→ 选择「永久有效」→ 复制生成的 32 位十六进制字符串(如a1b2c3d4e5f678901234567890abcdef);
  4. 在「数据流管理」中,手动添加 5 个数据流:temperaturehumiditylightsound_rmssoil_moisture,类型均为float
  5. 不要开启「数据校验」——OneNET 对 MQTT 的 JSON 校验极严,字段缺失即拒收,建议初期关闭。

注意:OneNET 的 MQTT Broker 地址为183.230.40.39(北京)、183.230.40.40(杭州)、183.230.40.41(广州),端口统一为6002(TLS 关闭)或6001(TLS 开启)。本方案用6002,避免证书管理开销。

4.2 ESP8266 连接 OneNET 的 3 个必调参数与心跳机制

AT 指令链中,以下三处参数直接影响连接寿命:

参数AT 指令推荐值说明
TCP 保活AT+CIPKEEPALIVE=120120 秒告诉 ESP8266 每 120s 发 TCP keepalive 包
MQTT 心跳CONNECT 报文中 Keep Alive 字段120 秒OneNET 要求 ≥60s,设 120s 防网络抖动
重连间隔AT+CIUPDATE=1,3000,53000ms,最多 5 次连接失败后,3 秒后重试,共 5 次

心跳报文必须由 STM32 主动发送(不能依赖 ESP8266 自动):

// 每 110s 发送一次 MQTT PINGREQ if ((tick_ms % 110000) == 0) { uint8_t ping_pkt[2] = {0xC0, 0x00}; // PINGREQ 固定头 send_to_esp8266(ping_pkt, 2); }

若收到+IPD,2:...且内容为0xD0 0x00(PINGRESP),则连接正常;否则需触发 AT+CIPCLOSE → AT+CIPSTART 重连流程。

4.3 OneNET 数据可视化配置:折线图与阈值告警

在 OneNET 控制台「数据可视化」中:

  • 新建仪表盘 → 添加「折线图」组件;
  • 数据源选择设备 → 选择数据流temperaturehumiditylight
  • 时间范围设为「最近 24 小时」,刷新间隔「30 秒」;
  • 点击「高级设置」→ 开启「阈值告警」:
    • temperature> 35℃ → 触发红色告警(加湿器强制满功率);
    • soil_moisture< 200 → 触发黄色告警(推送微信通知);
    • sound_rms> 80 → 触发橙色告警(记录事件日志)。

提示:OneNET 的折线图默认只显示整点数据,需在「数据聚合」中选择「原始数据」,否则传感器每 5s 上报的数据会被自动降采样为每分钟一个点。


5. 实战排错:从串口日志定位 90% 的连接失败问题

5.1 串口抓包分析法:用 USB-TTL 捕获完整 AT 交互流

将 STM32 的 USART2(PB3/PB4)配置为 115200bps 透传模式,接电脑串口助手,打印所有发给 ESP8266 的指令及返回:

// 在 send_at_cmd() 函数中加入 printf(">> %s", cmd); // 打印发送指令 printf("<< %s", response); // 打印收到响应

典型失败场景与日志特征:

现象串口日志特征解决方法
连不上 WiFiAT+CWJAP?返回+CWJAP:"",""检查 SSID/PWD 是否含空格,改用AT+CWJAP="SSID","PWD"显式发送
TCP 连接超时AT+CIPSTART后无响应,2s 后返回ERROR检查183.230.40.39是否能 ping 通,或换183.230.40.40
MQTT 连接被拒+IPD,xx:...中含0x04 0x00(CONNACK 返回码 0x04)Client ID 重复或 APIKey 错误,重新生成 APIKey 并核对长度
数据上报失败AT+CIPSEND后返回SEND OK,但 OneNET 无数据检查 JSON 中字段名是否与数据流名完全一致(大小写、下划线)

5.2 STM32 硬件级调试:用 LED 指示灯快速判断故障域

在 PCB 上预留 4 个 LED,分别指示:

LED亮起条件故障定位意义
D1(红)tick_ms % 500 == 0闪烁STM32 主循环运行正常
D2(绿)ESP8266_OK_FLAG == 1ESP8266 已成功响应 AT 指令
D3(蓝)ONE_NET_CONNECTED == 1MQTT 连接已建立
D4(黄)DATA_UPLOAD_SUCCESS == 1最近一次 PUBLISH 成功

若 D1 亮但 D2 不亮 → ESP8266 未上电或 CH_PD 异常;
若 D2 亮但 D3 不亮 → AT+CIPSTART 成功但 MQTT CONNECT 失败;
若 D3 亮但 D4 不亮 → JSON 格式错误或 OneNET 数据流未创建。

5.3 OneNET 平台侧验证:用 MQTTX 工具反向测试连接

下载 MQTTX (跨平台 MQTT 客户端),配置:

  • Name:onenet_test
  • Host:183.230.40.39
  • Port:6002
  • Client ID:1234567890123456(与设备一致)
  • Username:a1b2c3d4e5f678901234567890abcdef(APIKey)
  • Password: 留空

连接成功后,订阅主题device/1234567890123456/data,再用另一实例发布 JSON 测试数据。若 MQTTX 能收到,证明 OneNET 配置无误;若 STM32 设备收不到,问题一定在硬件或 AT 指令层。

提示:OneNET 的 MQTT 主题权限是单向的——设备只能向/device/xxx/data发布,向/device/xxx/cmd订阅;云平台向/device/xxx/cmd发布,设备订阅后解析执行。切勿在 STM32 中尝试向cmd主题发布,会触发连接断开。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询