ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32+GP2Y1010+ESP8266 PM2.5采集上传OneNet实战

STM32+GP2Y1010+ESP8266 PM2.5采集上传OneNet实战 简介以STM32F103RCT为核心的PM2.5实时监测与云端上传工程面向物联网入门者、嵌入式开发者和电子竞赛备赛者解决环境数据采集与远程上云问题。项目整合传感器驱动、OLED显示、ESP8266联网和OneNet云平台接入覆盖从底层硬件操作到上层协议对接的完整开发流程并可在开发板上直接运行验证。压缩包共163个文件大小4.28MB以C源码(.c/.h)为主包含Keil工程文件、编译中间文件与可直接烧录的hex/axf固件其中头文件与C文件覆盖外设驱动、显示模块及主逻辑map文件有助于分析内存占用便于按模块查阅和二次开发。目前已有1071人学习下载。借助该工程可快速掌握STM32多外设协同编程、PM2.5传感器数据解析、OLED屏幕驱动、ESP8266 AT指令联网以及OneNet平台数据上传等关键技能可作为课设、毕设或竞赛项目的实战框架。1. 先把“STM32采集PM2.5并上传云端”这条链路拆开看把一个 PM2.5 监测点从“能采集”做到“能上云”卡住大家的往往不是传感器本身而是三处衔接GP2Y1010 的 LED 脉冲要在正确的时间点采样、SSD1306 的页寻址、ESP8266 的 HTTP POST 报文构造。这套基于 STM32F103RCT 的工程本质是一条“传感器电压 → ADC → OLED 显示 → 串口转发 → OneNet”的数据管道只要时序和帧结构对了剩下都是体力活。文章按这条链路往下拆先讲外设分配与采样时序再落到 OLED 驱动和 esp8266 入网最后给出联调排错和校准技巧适合手上有板子、想从例程改造成完整体验物联网上报的工程师。目录里能看出这是拿 DS18B20 工程改造的后面会讲到怎么把外设尽可能复用到新任务上。2. STM32F103RCT 外设分配与 GP2Y1010 采样时序2.1 为什么这个工程选 ADC 型 PM2.5 传感器标题里的 PM2.5 传感器有两条主流路线一类是 GP2Y1010 这种模拟电压输出型光学散射原理红外 LED 照射气样光敏管接收散射光输出与浓度近似成正比的电压直接进 ADC另一类是 PMS5003 这种数字串口型内置风扇和激光器数据通过 UART 输出自带湿度校准。工程文件里有stm32f10x_adc.c没有看到串口解析 PM2.5 协议的代码所以主传感器基本可以锁定为模拟输出方案。选模拟型的好处是便宜、结构简单、响应快缺点是采样时序必须自己控制而且容易受到环境光和电路噪声干扰。GP2Y1010 的数据手册典型工作时序是 10ms 一个周期、LED 脉冲宽度 0.32ms、在脉冲开始后 0.28ms 附近取 OUT 引脚电压。很多移植不成功的案例问题都出在 ADC 采样的时间点不对LED 还没亮到峰值就去读电压读数自然偏低且跳动大。2.2 外设资源盘点与外设分配STM32F103RCT 有 256KB Flash、48KB RAM资源对这个项目绰绰有余。这个工程源头是 DS18B20 例程keilkilll.bat清理中间文件后剩下stm32f10x_tim.c、stm32f10x_adc.c、lcd.c、stm32f10x_i2c.c等基本就是物联网节点需要的外设骨架。我拿到手先做了一次外设分配避免功能模块之间抢引脚。外设功能引脚说明ADC1 通道 10GP2Y1010 AOUTPC012 位采样软件触发TIM210ms 采样周期无更新中断驱动 LED 脉冲GPIOA Pin 0GP2Y1010 LED 脉冲PA0控制红外 LED 通断I2C1SSD1306 OLEDPB6/PB7硬件 I2C速率 400kHzUSART2ESP8266 AT 通信PA2/PA3115200 8N1USART1调试日志PA9/PA10115200 8N1USART1 留作调试输出ESP8266 放在 USART2是因为调试信息优先级高项目现场排查问题时不能断日志。GP2Y1010 的 V-LED 引脚如果模块上自带三极管驱动就直接用 PA0 控制如果是从传感器裸片引出LED 需要串联电阻接到 5VPA0 通过三极管或 MOS 管去拉电路不能用 GPIO 直接驱动大电流。2.3 GP2Y1010 的采样时序与 ADC 触发GP2Y1010 的散射光信号很弱LED 必须脉冲驱动才能区分环境光。完整采样时序是LED 点亮 → 等待 0.28ms → 采样 OUT 电压 → LED 熄灭 → 等待 9.68ms 进入下一周期。我把这个时序放在 TIM2 的 10ms 更新中断里做状态切换static uint8_t pm25_phase 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) RESET) { return; } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (pm25_phase 0) { GPIO_SetBits(GPIOA, GPIO_Pin_0); // LED 脉冲开始 delay_us(280); // 峰值采样点 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 触发一次 ADC 转换 } else { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // LED 关闭 } pm25_phase !pm25_phase; }280us 是 GP2Y1010 数据手册给出的典型峰值采样点早了信号没起来晚了 LED 快灭了。中断里用delay_us在示例工程里可以跑通因为 TIM2 优先级高于所有可能打断这段延时的外设中断追求严谨的做法是把时序拆成两段状态机进入中断立刻置位 LED再用另一个定时器通道或 Systick 在 280us 后触发 ADC这样主循环和串口中断都不会影响采样点。2.4 从 ADC 原始值换算到浓度GP2Y1010 的零粉尘输出电压约 0.9V灵敏度典型值 0.5V/(0.1mg/m³)。换算公式可以按线性外推来做float vout (float)adc_value * 3.3f / 4095.0f; float pm25 (vout - 0.9f) * 200.0f; // 0.5V 对应 100ug/m3所以乘 200 if (pm25 0.0f) { pm25 0.0f; }adc_value来自 ADC1 的转换结果寄存器3.3f / 4095.0f是把 12 位 ADC 原始值映射到电压。系数 200 的推导过程是0.1mg/m³ 100ug/m³对应 0.5V那么1ug/m³ 0.005V用电压差除以 0.005 就是浓度也就是乘以 200。如果传感器的 VCC 不是 5V或者输出经过电阻分压这个斜率必须重新标定。工程里stm32f10x_flash.c可以用来保存标定系数上电直接读掉后面第 5 章会讲具体做法。3. SSD1306 OLED 驱动初始化、坐标与数值刷新3.1 为什么 OLED 适合这类桌面级监测屏0.96 寸 SSD1306 是 128x64 分辨率的自发光屏I2C 接口只用两根线功耗低、对比度高放在桌面空气质量监测仪上很合适。这个工程里已经有lcd.c说明原作者把屏幕驱动独立封装了。习惯用 HAL 库的人移植时I2C_WriteByte换成HAL_I2C_Mem_Write即可SSD1306 的命令序列和页寻址逻辑完全不用改。3.2 I2C 初始化与 SSD1306 命令序列SSD1306 上电后默认处于关闭状态需要先初始化内部稳压器和充电泵再打开显示。关键命令如下命令含义说明0xAE / 0xAF关显示 / 开显示初始化最后必须开显示0x8D 0x14开充电泵不开这个屏幕不亮0x20 0x02页寻址模式适合逐页写入字符0xDA 0x12COM 引脚设置0.96 寸屏常用配置0xD5 0x80时钟分频0x80 是标准值I2C 地址一般是 0x3C也有极少数屏是 0x3D。写入函数最核心的是控制字节0x00 表示后续字节是命令0x40 表示是显存数据static void OLED_WriteCmd(uint8_t cmd) { I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x3C 1, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, 0x00); // 控制字节命令 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, cmd); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); }每次通信都从 START 开始发送 7 位设备地址左移后的 8 位地址字节然后连续发控制字节和数据。这里有个常见坑STM32 硬件 I2C 在总线上有设备未应答时会卡在事件等待循环里我一般会给I2C_CheckEvent加一个超时变量超过 100ms 直接复位 I2C 外设避免整机死等。3.3 数字显示与局部刷新SSD1306 内部有 1KB GDDRAM页寻址模式下把屏幕分成 8 页每页 128 列。显示一个 ASCII 字符就是往对应页的列地址写 8 字节字模。显示 PM2.5 数值时工程里OLED_ShowNum的函数结构如下void OLED_ShowNum(uint8_t x, uint8_t y, uint16_t num, uint8_t len) { for (uint8_t i 0; i len; i) { uint8_t digit num % 10; OLED_ShowChar(x (len - 1 - i) * 6, y, 0 digit); num / 10; } } void OLED_ShowPM25Value(uint16_t pm25) { OLED_ShowNum(48, 3, pm25, 4); // 固定刷新 48,3 起始的 4 位数字区域 }OLED_ShowChar按 6x8 像素字体从font.h里取模字符宽 6 像素所以坐标偏移是(len - 1 - i) * 6。从个位开始取余数写最右边的坐标这样显示结果符合大家从左到右读数的习惯。显示之前最好用空白字符把旧值覆盖掉否则数值从三位变成四位再变回三位时屏幕上会残留尾巴。3.4 刷新频率与汉字取模思路PM2.5 采集是 10ms 一次但屏幕不需要跟着刷新1Hz 就足够人眼稳定读数。我习惯在主循环里做计数每 10 次采样计算一次平均浓度再刷新 OLED这样显示更平稳。如果要在屏幕上显示“颗粒物浓度”这类汉字取模软件按 16x16 取模每行 32 字节按页寻址写入时注意一页只能写 128 列写满后必须切到下一页再继续。0.91 寸 128x32 的屏同理只是页数减半初始化时 MULTIPLEX 参数要从 0x3F 改成 0x1F。4. ESP8266 AT 指令链路与 OneNet HTTP 上传4.1 ESP-01 接线与启动时序ESP8266 和 STM32 通信最省事的方式是走 AT 指令。工程里 ESP8266 接在 USART2PA2 发指令、PA3 收响应。ESP-01 模块的 CH_PD 必须拉高才能工作VCC 峰值电流能到 300mA不能直接从 STM32 板载 3.3V 稳压器取电我一般会单独用 AMS1117-3.3 从 5V 转一路给它供电。STM32 引脚ESP-01 引脚说明PA2RXSTM32 发送 AT 指令PA3TXSTM32 接收响应3V3VCC独立供电GNDGND共地3V3CH_PD使能必须拉高上电后 ESP8266 至少要等 500ms 才能接收第一条指令我一般先发一个不带参数的AT\r\n收到OK再继续后面的网络配置。有些模块默认波特率是 9600和工程里 115200 对不上串口回显出来是乱码先用ATUART_DEF115200,8,1,0,0校准一次。4.2 串口发送与响应状态机AT 指令链路的核心是一个能同时发送指令和等待关键词的串口函数void ESP8266_SendCmd(const char *cmd, const char *expect, uint16_t timeout_ms) { uint32_t start GetTick(); USART_SendString(USART2, cmd); while ((GetTick() - start) timeout_ms) { if (strstr(rx_buf, expect) ! NULL) { break; } } }rx_buf是 USART2 接收中断不断追加的环形缓冲区expect是期望出现的字符串比如OK、WIFI GOT IP或CONNECT OK。超时必须给足ATCWJAP连路由器有时需要几秒钟。这个函数的关键是不要在中断里做strstr匹配只做字节入队主循环里调用ESP8266_SendCmd再去扫描缓冲区否则 USART2 中断被拖住会导致丢字节。4.3 入网与 OneNet 数据点上报下面这套流程用ATCIPSTARTATCIPSEND实现 HTTP POST官方 AT 固件都支持比ATHTTPCLIENT通用性更好。先看指令序列ATCWMODE1 OK ATCWJAP你的SSID,密码 WIFI GOT IP ATCIPSTARTTCP,api.heclouds.com,80 CONNECT OK ATCIPSEND234 POST /devices/123456/datapoints?type3 HTTP/1.1 Host: api.heclouds.com api-key: xxxxxx Content-Type: application/json Content-Length: 46 {datastreams:[{id:pm25,datapoints:[{value:88}]}]}STM32 侧构造请求报文时重点是把 JSON 转义处理好代码如下char body[64]; char request[256]; sprintf(body, {\datastreams\:[{\id\:\pm25\,\datapoints\:[{\value\:%d}]}]}, pm25_value); sprintf(request, POST /devices/%s/datapoints?type3 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, strlen(body), body); ESP8266_SendCmd(ATCIPSTART\TCP\,\api.heclouds.com\,80\r\n, CONNECT OK, 5000); char cipsend_cmd[32]; sprintf(cipsend_cmd, ATCIPSEND%d\r\n, strlen(request)); ESP8266_SendCmd(cipsend_cmd, , 2000); USART_SendString(USART2, request);Content-Length必须是 body 的字节数不是整个请求的字节数这个数算错了 HTTP 服务器会一直等后续数据直到超时。ATCIPSEND的参数也要和实际发送长度严格一致多一个字节或少一个字节都会导致发送失败。收到SEND OK后再等待 OneNet 返回{errno:0,error:succ}这个响应会出现在IPD段里。顺带说一句OneNet 老平台的这个api-key鉴权接口现在主要服务存量设备新注册的 OneNET Studio 已经改用 token 签名设备 ID、URL 路径和鉴权头都不同了。如果是自建服务器把POST /devices/...换成你自己的路由header 里的api-key换成自定义字段整个链路不用改。4.4 AT 固件差异与常见返回处理不同厂家出厂的 ESP8266 AT 固件版本差异很大ATCIPSTART是标准指令但ATCIPSTARTTCP,host,port中间如果少了引号返回就是ERROR。遇到这种情况先发ATGMR看固件版本并确认是不是经过了电平转换芯片。ESP8266 的 TX 是 3.3V 电平和 STM32 的 RX 可以直接相连但很多开发板上的 ESP8266 串口经过了板载 RS232 电平转换接法就不一样了。另外ATCIPSEND一次最大发送长度受CIPSEND退出的提示符限制超过 512 字节的请求要拆包本场景一条数据点报文不超过 300 字节不需要分片。5. 联调排错、校准斜率与云端之外的扩展5.1 三个高频故障定位第一OLED 不亮先查0x8D 0x14充电泵有没有发再看地址是 0x3C 还是 0x3D最后把 OLED 的 SDA/SCL 上拉电阻补上STM32 硬件 I2C 开漏输出必须有外部上拉到 3.3V。第二ESP8266 不回 OK引脚没错的话就是供电问题串口助手单独接模块跑一遍 AT 指令是最快的排查方式。第三云端没有数据先在串口日志里确认是否收到CONNECT OK和SEND OK再看 OneNet 返回的errno多数是api-key复制多了空格或者 JSON 里 value 带引号导致类型不匹配。5.2 GP2Y1010 的两点校准GP2Y1010 的 0.9V 零点和灵敏度都只是典型值批次差异很大。我一般做两点校准先在密封干净的盒子里测零粉尘电压 V0再用点燃的香靠近传感器到某个体积浓度测 Vm用参考设备读出数值 Cm标定公式就变成pm25 (vout - V0) / (Vm - V0) * Cm。把 V0 和 Cm 存进stm32f10x_flash.c提供的存储区上电先读标定参数再进入采样循环。ADC 原始值在采样后还会叠加约 20mV 的高频噪声滑动平均窗口取 10 组数据就比较稳static uint16_t buf[10]; static uint8_t idx 0; static float pm25_avg 0.0f; buf[idx] pm25_value; if (idx 10) idx 0; uint32_t sum 0; for (uint8_t i 0; i 10; i) { sum buf[i]; } pm25_avg sum / 10.0f;buf只存原始浓度每 10ms 覆盖一次旧值这样显示和上传用的都是最近 100ms 内 10 次采样平均而不是单次跳变读数。校准完成后把pm25_avg喂给OLED_ShowPM25Value和 HTTP 报文构造函数即可。5.3 扩展成多参数环境节点工程里那批 DS18B20 文件不是多余的温度正好作为 PM2.5 校正参数之一。GP2Y1010 受湿度影响明显湿度大时同浓度粉尘输出电压会偏高采集温度做湿度补偿是低成本方案。把温度拼进 JSON 的datastreams数组里OneNet 侧会自动生成另一个数据流。低功耗扩展时数据上报完发送ATCWDHCP断开连接STM32 进入 STOP 模式由 RTC 定时唤醒下一次采样整套链路不需要改 HTTP 报文结构只需要把上报频率从 1S 改成 10S电池寿命就能拉长一个量级。校准参数的 Flash 读写、HTTP 请求的 Content-Length 计算、ATCIPSEND的等待这三个点是这套工程里最容易复用到其他物联网项目的通用模块。本文还有配套的精品资源点击获取
返回列表