ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32+ESP8266接入OneNET:智能环境监测系统开发实战

STM32+ESP8266接入OneNET:智能环境监测系统开发实战 简介一套覆盖STM32、ESP8266与OneNET云平台的物联网综合实训资料面向物联网工程、嵌入式开发方向的初学者和竞赛选手。系统以STM32F103C8T6为主控通过ESP8266连接OneNET实时采集DHT11温湿度和MQ-2烟雾浓度并在OLED屏显示温湿度或烟雾超标时联动蜂鸣器、风扇与LED模拟控制同时支持云端下发指令远程控制设备。资料共3436个文件以C/H源码、Java程序、原理图与PCB设计、PDF文档及hex/bin固件为主总大小354.46MB还包含大量工程备份与图片资料适合对照学习物联网设备端、云端接入和上位机开发。已有3267人学习下载对于希望快速复现智能家居环境监测与远程控制项目的开发者具有较高参考价值无论是毕业设计、课程设计还是物联网入门练手都能获得从硬件到云端的完整参考。1. F103C8T6 接 ESP8266 上 OneNET这个家用环境监测盒子到底卡在哪先给结论这套组合里最难的不是 DHT11 和 MQ-2 的驱动而是 STM32F103C8T6 和 ESP8266 之间的 UART 串口对决。ESP8266 模块的 RX 引脚电平是 3.3V而很多人的 USB-TTL 已经调到 5V一接上去模块要么不发数据要么直接烧引脚STM32 的串口默认时钟是 PCLK1不同倍频配置下 BaudRate 的误差能到 3%时间一长 OneNET 侧收到的就是乱码。这个项目想跑通本质上是把传感器采集、AT 指令状态机、云平台数据点上报和本地 LED 告警四条链路串在一起其中任何一条不闭环都会让整个盒子看起来“点上电但不动”。下面按从传感器到云端的顺序把方案过一遍硬件选型就用最常见的F103C8T6 最小系统板、ESP8266-01S、DHT11 模块、MQ-2 模块以及一个 GPIO 控制的 LED 模拟室内灯。2. DHT11 和 MQ-2 在 F103C8T6 上的采集HAL 库时序和 ADC 阈值的真实边界2.1 HAL 库驱动 DHT11 的单总线时序怎么不踩坑DHT11 是单总线协议数据线只靠一根线完成主机控制和从机应答。常见错误是把 DHT11 当 I2C 或者直接拉高就读。实际上主机要先把总线拉低至少 18ms 启动释放后等待从机拉低 80us 再拉高 80us 应答然后从机连续发送 40 位数据。每一位的区分靠高电平持续时长高电平持续约 26~28us 表示“0”70us 左右表示“1”。用 STM32 HAL 库做关键在于 GPIO 方向切换和延时精度。// DHT11 数据线接 F103C8T6 的 PB0开漏输出模式外部加上拉电阻 #define DHT11_PORT GPIOB #define DHT11_PIN GPIO_PIN_0 void DHT11_Delay_us(uint16_t us) { // 使用 DWT 延时避免 HAL_Delay 只支持 ms 精度 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; uint32_t start DWT-CYCCNT; while (DWT-CYCCNT - start us * (SystemCoreClock / 1000000)); } uint8_t DHT11_ReadByte(void) { uint8_t value 0; for (int i 0; i 8; i) { while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); // 等低电平结束 DHT11_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { value | (0x80 i); // 40us 后仍为高判定为“1” } while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); // 等高电平结束 } return value; }这段代码的关键是DHT11_Delay_us不能直接用HAL_Delay后者在 DWT 配置不当的情况下最小单位只能到毫秒每一位的判断都会偏掉。while (HAL_GPIO_ReadPin(...) GPIO_PIN_RESET)这个等待循环必须放在第一位判断之前因为从机在发送每一位之前有一个 50us 的低电平起始位如果不等它结束后续位时序会整体漂移。2.2 DHT11 数据校验和湿度整数位的处理DHT11 一次返回 5 个字节湿度整数、湿度小数、温度整数、温度小数、校验和。校验和是前四个字节相加取低 8 位HAL 驱动里必须在读完 40 位后立刻校验否则一次错位会让后续所有数据失真。uint8_t dht11_data[5]; uint8_t checksum (dht11_data[0] dht11_data[1] dht11_data[2] dht11_data[3]) 0xFF; if (checksum ! dht11_data[4]) { // 返回错误码上位逻辑直接丢弃本次采样不能缓存旧数据 return HAL_ERROR; }注意 DHT11 的湿度整数位和小数位不是十进制关系通常模块返回的湿度小数位一直为 0。如果你需要更高的湿度分辨率要么换 DHT22AM2302要么在应用层把湿度整数位直接作为最终值。F103C8T6 内部有硬件 CRC 外设但 DHT11 的校验算法就是简单的字节累加用硬件 CRC 反而要额外配置多项式得不偿失。2.3 MQ-2 测烟雾浓度不是读 ADC 就完事MQ-2 的模拟量输出 AO 引脚接到 F103C8T6 的 PA0使用 ADC1 的通道 0。模块上电后需要预热很多人的数值一直在漂就是因为加热电阻还没到工作温度。MQ-2 的敏感体在冷态和热态下电阻差很大常见做法是上电后先等 60 秒再开始采集。下面是 ADC 配置和采样代码// PA0 配置为模拟输入ADC1 采样序列里只有一个通道 ADC_HandleTypeDef hadc1; uint32_t mq2_value 0; HAL_ADC_Start(hadc1); for (int i 0; i 10; i) { HAL_ADC_PollForConversion(hadc1, 100); mq2_value HAL_ADC_GetValue(hadc1); HAL_Delay(10); } mq2_value / 10; // 中值滤波消除瞬时抖动 // 换算电压3.3V 参考电压12 位 ADC float voltage (mq2_value * 3.3f) / 4095.0f;10 次采集中值滤波是必要的因为 MQ-2 的 AO 输出在气体浓度稳定后仍有微小的波动尤其烟雾刚飘到传感器附近时会出现尖峰。voltage并不是烟雾浓度的线性映射MQ-2 在手册里给出的是灵敏度曲线不同可燃气体斜率不同。实际工程里常见方案是模块在干净的空气中会输出一个基础电压通常在 0.3V 左右你把当前电压减去这个基础电压再除以基础电压得到一个相对变化率。烟雾超标阈值设定在这个相对变化率超过 0.5 时触发而不是直接用绝对电压。2.3.1 MQ-2 数字量输出 DO 能不能用MQ-2 模块上还有一个 DO 引脚它通过板载电位器调节比较器阈值输出 0/1 数字量。如果上位机只做一个粗略的烟雾报警直接用 DO 接 F103C8T6 的 GPIO 更省事但要在线调节阈值时必须拧电位器没法在代码里改。我的建议是 AO 接 ADC用代码里的voltage_threshold常量做阈值这样云平台升级时可以直接下发阈值参数不用拆盖子去动螺丝刀。3. ESP8266 与 F103C8T6 串口通信从 AT 指令到 OneNET 数据点上报3.1 F103C8T6 和 ESP8266 的硬件连接与电平匹配法则这一节是整篇文章里最容易烧板子的一步。ESP8266-01S 的 VCC 是 3.3VCH_PDEN要拉高GPIO0 在运行状态下必须悬空或接高电平GPIO2 保持悬空即可。STM32F103C8T6 的串口 TX 是 PA9RX 是 PA10要接 ESP8266 的 RX 和 TX而不是同名互连。很多人的印象是“TX 接 RX”在板载 CH340 的 USB-TTL 上成立但到了两块开发板之间就糊涂了。这里明确F103C8T6 的 PA9USART1_TX接 ESP8266 的 RXDF103C8T6 的 PA10USART1_RX接 ESP8266 的 TXD。// 使用 USART1对应引脚 PA9 和 PA10 // 注意不要额外给 ESP8266 的 RX 引脚接上拉否则拉高电平可能让模块误判为命令输入 GPIO_InitStruct.Pin GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);另外如果你的 F103C8T6 最小系统板是 5V 供电ESP8266 绝不能从板子的 5V 引脚取电必须用独立的 3.3V LDO。ESP8266 在 Wi-Fi 发射时峰值电流可以到 300mAF103C8T6 板载的 AMS1117-3.3 通常只能稳定输出 500mA勉强够用但如果同时给 DHT11 和 MQ-2 供电建议直接用外部 3.3V 电源共地即可。3.2 ESP8266 的 AT 指令配置静态 IP 还是 DHCPESP8266-01S 出厂固件一般是 AT 指令版本连接 OneNET 之前先要让它联网。常见做法是让 ESP8266 进入 Station 模式连接家里的路由器。下面是一套稳定的 AT 指令序列建议在串口助手里面先手工跑一遍再写进 STM32 代码ATRST // 复位等待 Ready大约 2 秒 ATCWMODE1 // 1 表示 Station 模式ESP8266 作为终端连接外部 AP ATCWJAP你的WiFi名,你的密码 // 连接 Wi-Fi等待 WIFI GOT IP 返回 ATCIFSR // 查询 ESP8266 获取到的 IP确认联网成功注意ATCWJAP这条指令在路由器信号弱的情况下可能返回WIFI DISCONNECTSTM32 侧要设计重试机制一般做法是每 5 秒重试一次最多 3 次后让 LED 以 500ms 周期闪烁提示网络错误。不要用ATCWJAP的返回内容作为判断联网成功的唯一标准以ATCIFSR能返回非 0.0.0.0 的 IP 为准。3.3 OneNET 生成 APIKey 与创建设备HTTP 数据点接口的完整流程OneNET 平台接入分两步创建产品、给设备添加数据流。旧版控制台里你需要登录平台后选择“多协议接入”创建一个 HTTP 协议的产品然后在产品下添加设备。设备创建成功后页面会显示设备 ID 和 APIKey。APIKey 是设备上报数据的身份凭证本质上是一个 HTTP 请求头里的字段。新版 OneNET Studio 的接入方式和旧版略有差异但这个项目的核心逻辑不变上报数据点要通过http://api.heclouds.com/devices/{device_id}/datapoints这个接口。HTTP 上报数据点的请求格式用串口助手或者 STM32 直接发 POST /devices/123456/datapoints HTTP/1.1 Host: api.heclouds.com api-key: 你的APIKey Content-Length: 69 Content-Type: application/json {datastreams:[{id:temp,datapoints:[{value:27.5}]}]}datastreams里嵌套的数组结构容易出错。每一个id对应 OneNET 控制台里的一个数据流名称比如温度流的 id 设为temp湿度流设为humi烟雾流设为smoke。上报时可以把三个数据流放在同一个请求里JSON 里的datapoints是个数组value 类型可以是浮点数也可以是整数。OneNET 的 HTTP 接口返回的 JSON 里errno为 0 表示成功非 0 时通常是api-key头部拼错或者设备 ID 不匹配。3.3.1 ESP8266 通过 AT 指令直接 POST 数据到 OneNET如果 F103C8T6 先读传感器再把数据通过串口透传给 ESP8266效率很低常见做法是STM32 负责拼好完整的 HTTP 请求字符串和 JSON 数据然后通过 AT 指令让 ESP8266 建立 TCP 连接到api.heclouds.com的 80 端口再透传发送整个 HTTP 报文。串口波特率建议 115200因为 ESP8266 固件默认在这个速率下工作F103C8T6 的 USART1 也配成 115200 才能避免乱码。// STM32 侧用 sprintf 拼 HTTP 请求 char request[512]; int len sprintf(request, POST /devices/%s/datapoints HTTP/1.1\r\n Host: api.heclouds.com\r\n api-key: %s\r\n Content-Type: application/json\r\n Content-Length: %d\r\n\r\n %s, device_id, api_key, (int)strlen(json_body), json_body); // 先发 AT 指令建立 TCP 连接 send_at(ATCIPSTART\TCP\,\api.heclouds.com\,80, CONNECT, 5000); // 通知 ESP8266 接下来要发送的数据长度 char cipSendCmd[32]; sprintf(cipSendCmd, ATCIPSEND%d, len); send_at(cipSendCmd, , 3000); // 发送 HTTP 请求正文 send_raw((uint8_t *)request, len); // 等待 OneNET 返回 HTTP 响应最后发送 ATCIPCLOSE 关闭连接 send_at(ATCIPCLOSE, OK, 2000);ATCIPSEND这里不写长度会让 ESP8266 进入无限等待状态必须传入待发送字节数。HTTP 请求里的Content-Length要精确等于 JSON 字符串长度不能多一个换行符否则 OneNET 的 HTTP 解析会报 400。我在调试时一般把request通过串口打印出来直接复制到 PC 上用curl测试确认 OneNET 接口本身没问题再回头查 STM32 的发送逻辑。4. 温湿度和烟雾超标联动 LED 室内灯云平台触发器与本地 GPIO 双保险4.1 LED 模拟室内灯的 GPIO 控制逻辑F103C8T6 板载 LED 通常接在 PC13 引脚但“室内灯”语义是模拟场景建议外接一个 LED 到 PB1用三极管驱动不限流这样逻辑和实际照明灯更接近。LED 的开关状态由两个条件决定用户手动开关标志manual_light_on和超限自动开启标志alarm_triggered。这两个标志位用按位或合成最终控制信号当alarm_triggered为 1 时不管手动开关状态如何LED 都必须点亮这是安全逻辑优先级最高。#define LAMP_PIN GPIO_PIN_1 #define LAMP_PORT GPIOB // 超限标志由 DHT11 和 MQ-2 的阈值判断共同驱动 if ((temp 30.0f) || (humi 70.0f) || (voltage 1.2f)) { alarm_triggered 1; } else { alarm_triggered 0; } if (alarm_triggered || manual_light_on) { HAL_GPIO_WritePin(LAMP_PORT, LAMP_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(LAMP_PORT, LAMP_PIN, GPIO_PIN_RESET); }需要注意的是“温度大于 30 度触发灯亮”只是模拟真实室内灯不会因为温度高就自动开但这个项目的课程设计场景里煤气管道的联动灯就是个常见展示逻辑。实际部署时阈值应该放在常量区方便后期通过 OneNET 的下发指令远程修改。4.2 OneNET 端触发器的配置和 HTTPS 回调除了本地 STM32 的告警逻辑OneNET 平台侧还提供了一个触发器功能。在 OneNET 控制台的“触发器”菜单里选择目标设备的数据流设定条件类型大于或小于某个阈值。当满足条件时平台会向预设的 URL 发送一个 POST 请求。这个 URL 可以是服务器的接口也可以对接第三方消息推送。常见做法是让触发器把超限信息用 POST 方式推给一个轻量级服务服务端收到后再往微信公众号或者钉钉群发消息。OneNET 触发器配置路径选择产品 → 设备 → 数据流标签页 → 触发器管理 → 添加触发器。触发条件里有一个“数据点值”的选项当上报的值超过阈值时触发。注意 OneNET 触发的判断周期取决于你的上报频率如果 STM32 每 10 秒上报一次数据那么触发器的判断也是最多 10 秒一次不是秒级实时。另外触发器回调 URL 需要公网可以访问如果你在内网测试可以用内网穿透工具把本地端口暴露到公网但注意不要把回调 URL 写成 PC 的局域网地址。4.3 超限告警上报时的数据格式设计OneNET 的数据点 value 只支持基础类型所以你不能直接把“超标”这个布尔值塞进去。常见做法是定义一个新的数据流alarm每次上报时把警告状态编码为整数0 表示正常1 表示温度超标2 表示湿度超标3 表示烟雾超标4 表示多项超标。这样在 OneNET 的折线图里可以很直观地看到告警变化也能在触发器里只对alarm数据流做阈值判断避免为温湿度和烟雾各写一条触发规则。// JSON body 示例 { datastreams: [ {id: temp, datapoints: [{value: 31.2}]}, {id: humi, datapoints: [{value: 68.5}]}, {id: smoke, datapoints: [{value: 0.92}]}, {id: alarm, datapoints: [{value: 4}]} ] }这里alarm值为 4 表示温度超温且烟雾超标同时发生。要注意浮点数在 JSON 序列化时不要用%f直接拼因为不同编译器的浮点格式差异会让 OneNET 收到nan或inf字符串导致接口报错。STM32 的sprintf对浮点支持依赖新库建议用整数运算放缩温度乘以 10 存整数上报时以浮点数解析或者用dtostrf函数转字符串。5. OneNET 折线图调参和上报踩坑APIKey 失效与设备 ID 不一致5.1 上报后用 curl 自查 OneNET 数据点状态在 STM32 烧录之前先用 PC 的 curl 命令模拟上报一次把问题隔离在云平台配置这一侧。OneNET 的 HTTP 接口返回体里有个关键的errno字段0 代表成功其他值各有含义20表示 APIKey 无效21表示设备不存在或者设备 ID 不对22表示数据流不存在23表示 JSON 格式非法。你在串口助手或者 curl 输出里看到这些编号不需要猜。curl -X POST \ http://api.heclouds.com/devices/123456/datapoints \ -H api-key: 你的APIKey \ -H Content-Type: application/json \ -d {datastreams:[{id:temp,datapoints:[{value:27.5}]}]}返回{errno:0,error:succ}时平台侧数据流已经多了一个点。如果返回错误优先检查device_id是不是控制台里设备列表页的数字 ID不是你自己命名的字符串APIKey 需要从设备详情页里复制产品级的 APIKey 有时和设备级不通用。5.2 F103C8T6 串口乱码和 ESP8266 连不上 OneNET 的排查顺序一套常见的排查顺序先确认 STM32 的串口输出是不是乱码。乱码根源通常是 USART1 的波特率配置和 ESP8266 固件不一致还有系统时钟源切换导致的波特率误差。F103C8T6 的 HSI 是 8MHz如果你用 HSI 作为系统时钟USART1 挂在 APB2 上波特率计算会有较大偏差所以一定要用外部晶振 HSE 倍频到 72MHz然后单独配置 USART1 的时钟源为 PCLK2。如果串口数据正常、ESP8266 能连 Wi-Fi 但连不上 api.heclouds.com就要用ATPINGapi.heclouds.com测试域名解析和 ICMP 连通性。ESP8266-01S 内部 DNS 解析有时候会失败这时可以在 AT 指令里把要连接的域名显式写成 IP但 OneNET 的 IP 不稳定不建议写死。更多时候问题出在ATCIPSTART没等到 CONNECT 就发送数据可以用一个超时状态机来管理发送 AT 指令后等待响应的时间不能少于 5 秒因为 TCP 拨号建立连接期间 ESP8266 内部会切换模式响应变慢。5.3 上报失败时 LED 闪烁频率表达的故障码本地 LED 不只用来模拟室内灯也可以复用作为故障指示。常见做法是用闪烁次数表示错误类别LED 闪 1 次停 2 秒表示 DHT11 读取失败闪 2 次表示 MQ-2 预热未完成闪 3 次表示 ESP8266 连接 Wi-Fi 超时闪 4 次表示 OneNET HTTP 上报返回非零 errno。注意这些一闪一停的状态会与室内灯逻辑冲突所以要单独用一个 GPIO 接一个调试 LED不要把两者混在同一个引脚上。// 故障码展示伪代码blink_count 表示第几种故障间隔 2s 重复 void show_error_code(uint8_t code) { for (int i 0; i code; i) { HAL_GPIO_WritePin(ERROR_LED_PORT, ERROR_LED_PIN, GPIO_PIN_SET); HAL_Delay(200); HAL_GPIO_WritePin(ERROR_LED_PORT, ERROR_LED_PIN, GPIO_PIN_RESET); HAL_Delay(200); } HAL_Delay(1800); }如果你在 OneNET 云端已经能看到数据点但就是没有触发告警大概率是触发条件和数据流 id 对不上。OneNET 触发器监控的是数据流级维度你配置了temp 30但上报用的是新数据流temperature那永远触发不了。多数据流时建议上报前打印完整 JSON 到串口人工确认每个 id 和控制台上的名字完全一致包括大小写。LED 故障码只能走到这一层最终的数据匹配还得靠日志。本文还有配套的精品资源点击获取
返回列表