ARTICLE DETAIL

资讯详情

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

基于STM32与Y01-3IN1的空气质量监测OLED显示实战

基于STM32与Y01-3IN1的空气质量监测OLED显示实战 干了这些年嵌入式发现不少朋友一提到空气质量检测第一反应就是去淘宝买一堆传感器自己拼PM2.5一个模块温湿度一个模块甲醛再来一个模块光是布线就把人绕晕了更别提调试时一个个排查问题。其实市面上早就有三合一模块比如这次要说的Y01-3IN1把PM2.5/PM10、温湿度、甲醛集成在一个板子上走一个串口就能读出所有数据。配合一块0.96寸OLED屏做一个桌面空气质量监测小终端整个项目的核心工作就落在串口协议解析和OLED显示这两块。这篇文章就把我调通这套方案的完整过程记录下来。从硬件接线、CubeMX配置到串口帧解析、OLED驱动、汉字显示再到调试中踩过的坑一次性说清楚。代码基于STM32 HAL库主控用最经典的STM32F103C8T6这篇文章适合刚学完STM32基础但想做个完整小项目的朋友也适合想快速把传感器模块跑通然后移植到自己板子上的同学。1. 项目整体设计与硬件选型思路1.1 为什么选Y01-3IN1而不是单传感器自己拼先把话说在前面如果你想做的是学习每个传感器原理这种目的那自己分开买PM2.5传感器、SHT30温湿度模块、甲醛模块一个个驱动起来这个过程本身很有价值。但如果你跟我当时一样核心诉求是快速做一个能摆在桌面、实时显示空气质量的小设备那三合一模块的省心程度是碾压级的。从硬件设计角度看Y01-3IN1这类模块把三种传感器信号统一做进一个MCU然后通过串口按固定帧格式输出。这意味着我只需要占用主控的一组UART引脚就能拿到全部数据。要知道PM2.5激光传感器本身是串口输出的温湿度传感器通常是I2C接口的甲醛传感器则常见为模拟量或I2C。如果自己拼主控得同时开UART、I2C、ADC三路外设代码复杂度和排错难度立刻翻倍。从稳定性看一体化模块的电源调理、信号滤波、校准逻辑都在出厂前调好了。特别是甲醛传感器这种电化学类型的对供电纹波和上电时序非常敏感自己拼的时候很容易因为电源不干净导致数据漂移。买三合一模块厂家已经把这些坑填掉了我们在应用层只管解析数据帧。1.2 硬件清单与接线方式这次用到的硬件非常简单全部列出来也就四样STM32F103C8T6最小系统板蓝色Pill那种某宝十几块Y01-3IN1空气质量三合一模块串口输出0.96寸OLED屏I2C接口主控是SSD1306或SSD1315USB转TTL模块一个我用的CH340用来给STM32烧录程序接线方式如下Y01-3IN1模块STM32F103C8T6说明VCC5V模块必须5V供电GNDGND共地必须接TXPA10 (USART1_RX)模块发送主控接收RXPA9 (USART1_TX)主控发送模块接收OLDE VCC3.3VOLED屏供电OLED SCLPB6 (I2C1_SCL)时钟线OLED SDAPB7 (I2C1_SDA)数据线OLED GNDGND共地这里有个特别值得注意的地方Y01-3IN1模块建议用5V供电而不是3.3V。我一开始图省事直接把模块的VCC接到了板子的3.3V上结果PM2.5数据完全读不出来。查了半天发现激光粉尘传感器内部是有个小风扇的启动瞬间电流能达到几百毫安3.3V引脚根本扛不住电压被拉低后传感器工作就不正常了。后来改成5V供电一切恢复正常。OLED供电则是3.3V这个千万别接5V虽然很多SSD1306屏幕标称支持5V长期使用还是容易发热甚至烧坏。最稳妥的做法就是严格按照模块手册接。1.3 模块通信协议解析读懂数据帧才是关键很多新手卡在传感器模块上不是因为不会写代码而是根本看不懂模块手册里的协议表。Y01-3IN1这类模块走的是主动上报模式模块上电稳定后以固定的频率不断往外吐数据帧不需要主机发任何指令。我手上这块模块的帧结构是15字节一帧具体如下字节偏移内容说明00x42帧头10x4D帧头20x00数据区长度高字节30x0C数据区长度低字节即后面还有12字节数据4PM2.5高字节单位 μg/m³5PM2.5低字节6PM10高字节单位 μg/m³7PM10低字节8温度高字节单位 0.01℃9温度低字节有符号数10湿度高字节单位 0.01%RH11湿度低字节12甲醛高字节单位 0.001 mg/m³13甲醛低字节14校验和高字节前面所有字节累加和15校验和低字节这里要跟各位强调一下不同品牌、不同批次的Y01-3IN1模块帧格式可能不一样。有的厂家做的是9字节固定帧有的把PM2.5放前边有的把甲醛放前边。拿到模块第一件事是找手册看协议表千万别直接拿着网上别人的代码硬套。我手头这块是15字节帧下面所有解析代码都基于这个帧结构移植到别的模块时只需要改偏移量和数据区长度就行。校验和用的是累加和校验计算方式是把从帧头到数据区最后一位也就是偏移0到13的14个字节全部累加得到的结果应该等于偏移14和15组成的16位整数。这个检查必须做因为串口传输在恶劣环境下是有可能出错的不做校验等于拿错误数据算空气质量。2. 开发环境准备与CubeMX工程配置2.1 工具链准备少走弯路的关键一步先说开发环境这一步看着基础但真有不少人卡在这。我在带新人的时候发现很多问题不是代码写的不好而是工具链没配好导致连下载调试都过不去。用到的工具清单Keil MDK我用的是5.x版本STM32CubeMX用于图形化配置引脚和外设并生成初始化代码ST-Link驱动或J-Link驱动取决于你用哪个调试器CH340串口驱动如果你用的是USB转TTL模块烧录或串口调试这里有个安装顺序的问题先装驱动再装Keil最后装CubeMX的芯片支持包。如果顺序反了容易出现Keil识别不到调试器或者CubeMX生成代码时找不到芯片包。调试器我建议用ST-Link V2便宜又稳定和STM32同门兼容性最好。用J-Link也能跑但J-Link正版授权的问题偶尔会折腾人。安装完ST-Link驱动后插上调试器打开设备管理器能看到两个设备。如果驱动装完还是显示黄色叹号多半是驱动版本太老卸载干净后重新装最新版即可。2.2 CubeMX关键配置串口和I2C一个都不能少打开STM32CubeMX选择芯片STM32F103C8T6创建新工程。下面这些关键配置项我逐个说明。SYS选项里Debug选Serial Wire。这一步千万别省。如果你不配置SWD引脚程序下载一次后调试器就再也连不上板子了因为默认状态下PA13和PA14被当作普通GPIO用掉了烧录口等于被封印。这是新人最容易踩的大坑之一我真的见过有人因为这个直接换板子。RCC选项里HSE选Crystal/Ceramic Resonator。前提是你的最小系统板上真的有一颗8MHz晶振。时钟树配置HSE输入8MHzPLL倍频到72MHz作为系统主频。STM32F103的最高主频就是72MHz跑这个项目绰绰有余。配置方法是点击HCLK后面的输入框直接输入72回车CubeMX会自动算好PLL倍频值和分频值。然后配置USART1Mode选Asynchronous波特率设9600数据位8停止位1无校验打开USART1全局中断波特率为什么选9600因为很多空气质量模块默认出厂波特率就是9600。有的模块支持通过指令切换波特率但我建议就用默认值稳定可靠。Y01-3IN1每秒主动上报一帧数据15字节的帧在9600波特率下传输耗时不到16毫秒完全够用。再配置I2C1I2C Speed Mode选Standard Mode速率100kHz其余默认I2C的速率用100kHz就够了OLED屏幕刷新率要求不高100kHz甚至能跑得比400kHz更稳。如果系统里还有别的I2C设备比如EEPROM之类标准模式兼容性也最好。GPIO方面如果板载LED可以顺手配置一个GPIO输出口用来做系统运行指示灯。这个不是必须但对调试帮助挺大——至少你看一眼LED就知道程序有没有卡死。2.3 生成工程前的几个细节设置Project Manager界面里Project Name起个有意义的名字Toolchain选MDK-ARM V5。高级设置里有个HAL库配置选项建议保持默认不需要精简。生成代码之前还有个容易被忽略的地方代码生成模式。在Project Manager - Code Generator里勾选Generate peripheral initialization as a pair of .c/.h files per peripheral。这样每个外设的初始化代码单独生成一个文件和头文件比如usart.c、i2c.c结构清晰改起来也方便。不勾选的话所有外设初始化代码全挤在main.c里后期维护会想哭。另外勾选Copy only the necessary library files这样生成的工程只包含用到的HAL库源文件编译速度快不少。生成代码后用Keil打开工程第一次编译大概率会有几个警告一般不影响使用。如果想清爽一点可以在魔术棒选项卡里把C语言标准改成GNU11警告会少很多。3. 串口数据接收与Y01-3IN1协议解析实现3.1 串口中断接收与状态机设计串口接收这块我采用的是最实用的方案单字节中断接收配合一个简单的状态机做帧同步。为什么不直接上DMA因为DMA配合空闲中断确实效率高但对新手来说理解门槛更高而且这个场景每秒才一帧15字节的数据用中断方式接收完全够系统的负担几乎没有。核心思路是这样每次串口收到一个字节就把这个字节喂给状态机。状态机的状态有四个等待帧头第一个字节0x42等待帧头第二个字节0x4D接收数据区内容接收校验和用一个帧索引变量记录当前已经收到第几个字节数据区长度根据帧头后面的长度字段动态调整而不是写死。这样即使模块固件版本不同、帧长度略有差异程序也能自适应提高移植性。代码组织上我建议把帧接收和解析的逻辑单独写一个文件比如ys_sensor.c和ys_sensor.h不要在main.c里堆一大坨。main.c里只需要暴露一个类似Y01_GetData(pm25, temp, humi, hcho)的函数接口。这样做的好处是以后换其他传感器模块只需要替换这一个文件主程序基本不用动。先看串口接收的HAL库回调函数uint8_t rx_byte; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { Y01_UartRxProcess(rx_byte); HAL_UART_Receive_IT(huart1, rx_byte, 1); } }注意最后那行HAL_UART_Receive_IT必须每次都重新调用一次否则串口只会收到一个字节就罢工了。这是HAL库的机制用IT方式接收一次只能指定收一个字节收完之后中断就停了必须在回调里重新挂上下一次的接收请求。这个细节忘了写的话程序的表现就是串口数据只更新一次以后再也不变了很多人在这卡好久。3.2 帧校验与数据提取实战代码下面给出完整的帧处理函数。这个函数放在ys_sensor.c里逻辑清晰直接贴到工程里就能跑#define FRAME_HEADER_LEN 2 #define FRAME_CHECK_LEN 2 #define FRAME_DATA_MAX 14 static uint8_t rx_state 0; static uint8_t rx_index 0; static uint8_t frame_data_len 0; static uint8_t frame_buf[FRAME_HEADER_LEN FRAME_DATA_MAX FRAME_CHECK_LEN]; // 解析结果 volatile uint16_t g_pm25; volatile uint16_t g_pm10; volatile int16_t g_temperature; volatile uint16_t g_humidity; volatile uint16_t g_hcho; void Y01_UartRxProcess(uint8_t *pData) { switch (rx_state) { case 0: if (*pData 0x42) { frame_buf[0] *pData; rx_index 1; rx_state 1; } break; case 1: frame_buf[1] *pData; if (*pData 0x4D) { rx_state 2; rx_index 2; } else { rx_state 0; // 重新同步 } break; case 2: // 解析数据区长度 if (rx_index 2) { frame_data_len (*pData) 8; } else if (rx_index 3) { frame_data_len | *pData; if (frame_data_len FRAME_DATA_MAX) { rx_state 0; break; } } frame_buf[rx_index] *pData; rx_index; if (rx_index (FRAME_HEADER_LEN frame_data_len FRAME_CHECK_LEN)) { Y01_ParseFrame(frame_buf, FRAME_HEADER_LEN frame_data_len FRAME_CHECK_LEN); rx_state 0; } break; default: rx_state 0; break; } } static void Y01_ParseFrame(uint8_t *buf, uint8_t len) { uint16_t sum 0; uint8_t i; // 累加和校验 for (i 0; i len - 2; i) { sum buf[i]; } uint16_t check (buf[len - 2] 8) | buf[len - 1]; if (sum ! check) { return; // 校验失败丢弃这一帧 } // 提取数据注意大小端高字节在前 g_pm25 (buf[4] 8) | buf[5]; g_pm10 (buf[6] 8) | buf[7]; g_temperature (int16_t)((buf[8] 8) | buf[9]); g_humidity (buf[10] 8) | buf[11]; g_hcho (buf[12] 8) | buf[13]; }代码里用了大小端转换。这类模块的数据基本都是高字节在前也就是大端模式。你在HAL库的I2C、SPI里习惯了高字节先写很容易忽视这个但串口帧里数据的高低位顺序一定要核对手册否则读出来的数值会差256倍甚至完全不对。温湿度数据需要再转换一下温度单位是0.01℃比如读到的值是2536实际温度就是25.36℃。湿度同理单位是0.01%RH读值4521表示45.21%RH。甲醛读值单位是0.001 mg/m³读值125表示0.125 mg/m³。显示的时候记得做除法并保留两位小数。3.3 数据处理的几个思考为什么不轮询而是中断可能有朋友会问除了中断能不能用查询方式项目里如果在主循环里直接调HAL_UART_Receive查询接收一帧数据也不是不行。但我强烈建议用中断或DMA。原因有两个第一主循环里做串口等待接收代码会阻塞在接收函数里白白消耗CPU时间。虽然这个项目就一块OLED一个传感器看起来无所谓但一旦以后要加按键处理、还要做动画刷新阻塞接收会让你整个系统响应变慢。第二中断方式把收到数据这个事件和处理数据这个逻辑解耦了。传感器数据到了就立刻存到全局变量里主循环随时去读全局变量就行设计上更清爽。以后换操作系统或者加RTOS这段代码也能直接复用。再来个建议全局变量定义成volatile包括我在ys_sensor.c里定义的g_pm25这些。编绎器优化开启后不声明volatile的话变量可能被优化掉主循环读到的永远是旧值。这个坑我踩过一次排查到最后才想起来是关键字丢了。4. OLED显示驱动与界面布局4.1 SSD1306驱动的I2C写入细节OLED屏的驱动方式是I2C地址一般是0x3C这个是7位地址。很多朋友写OLED驱动时把地址写成了0x78这是因为0x3C左移一位加上读写位得到的8位地址正好是0x78。HAL库的I2C接口比如HAL_I2C_Mem_Write需要的是7位地址所以写0x3C。写0x78的话I2C通信会一直NAK屏幕怎么都不亮。SSD1306的数据线是单工的写命令和写数据都在同一条I2C总线上靠控制字节区分。每次I2C传输先发控制字节0x00表示后面是命令0x40表示后面是显示数据。很多人卡死在屏幕不亮或者乱码就是这个控制字节没处理对。以HAL库为例写命令的代码长这样void OLED_WrCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, 0x3C 1, buf, 2, 100); }写数据的代码把控制字节改为0x40void OLED_WrDat(uint8_t dat) { uint8_t buf[2] {0x40, dat}; HAL_I2C_Master_Transmit(hi2c1, 0x3C 1, buf, 2, 100); }注意HAL_I2C_Master_Transmit的地址参数要左移一位8位地址 7位地址 1。这个操作非常容易出错。如果你用的是HAL_I2C_Mem_Write接口那么传入的地址参数不需要左移直接写0x3C。两个接口的地址处理方式不一样一定看清楚。屏幕初始化序列直接用SSD1306标准的初始化流程即可static void OLED_Init(void) { OLED_WrCmd(0xAE); // 关闭显示 OLED_WrCmd(0xD5); OLED_WrCmd(0x80); // 设置时钟分频因子 OLED_WrCmd(0xA8); OLED_WrCmd(0x3F); // 设置驱动路数 OLED_WrCmd(0xD3); OLED_WrCmd(0x00); // 设置显示偏移 OLED_WrCmd(0x40); // 设置起始行 OLED_WrCmd(0x8D); OLED_WrCmd(0x14); // 电荷泵开启 OLED_WrCmd(0x20); OLED_WrCmd(0x02); // 页寻址模式 OLED_WrCmd(0xA1); // 段重映射左右方向调整 OLED_WrCmd(0xC8); // 扫描方向 OLED_WrCmd(0xDA); OLED_WrCmd(0x12); // 设置COM引脚硬件配置 OLED_WrCmd(0x81); OLED_WrCmd(0xCF); // 对比度设置 OLED_WrCmd(0xD9); OLED_WrCmd(0xF1); // 预充电时间 OLED_WrCmd(0xDB); OLED_WrCmd(0x40); // VCOMH稳压 OLED_WrCmd(0xA4); // 显示从RAM内容 OLED_WrCmd(0xA6); // 正常显示 OLED_WrCmd(0xAF); // 开启显示 }初始化序列里尤其要注意0x81后面跟的对比度值。如果这个值设成0x00屏幕会非常暗看起来就像没点亮一样。默认0xCF是稳妥选择觉得太亮可以调小。4.2 汉字取模与显示方法OLED显示汉字核心问题是字模怎么来。我用的是PCtoLCD2002这个取模软件网上随便搜就有一堆绿色免安装直接解压运行。取模设置有讲究设置不对显示出来就是乱码或镜像字。我把参数列在下面字模选项阴码取模走向逐列式每行显示数16取模走向逆向低位在前以16x16点阵的汉字为例一个汉字占32个字节。显示的时候需要把OLED分成8页每页8个像素行。16x16汉字跨两页先显示上半部分8行再显示下半部分8行。我自己封装了三个显示函数从底层到上层分别是// 设置坐标x为列坐标y为页坐标 void OLED_SetPos(uint8_t x, uint8_t y) { OLED_WrCmd(0xB0 y); OLED_WrCmd(((x 0xF0) 4) | 0x10); OLED_WrCmd(x 0x0F); }// 显示单个16x16汉字 void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t ch[]) { uint8_t i, j; for (j 0; j 2; j) { OLED_SetPos(x, y j); for (i 0; i 16; i) { OLED_WrDat(ch[j * 16 i]); } } }// 显示字符串 void OLED_ShowStr(uint8_t x, uint8_t y, char *str) { while (*str ! 0) { OLED_ShowChar(x, y, *str); x 8; if (x 112) break; // 128宽度减去8防止溢出 } }ASCII字符字模是8x16点阵一个字符占16字节。汉字是16x16点阵占32字节。这两种在取模软件里分别处理ASCII字符用8x16的字体模板汉字用16x16的字体模板。如果某个汉字显示出来是左右镜像或者颠倒的就是取模软件的设置跟代码预期不匹配。最常出问题的是逐列式/逐行式和顺向/逆向这两个选项。如果你发现字左右颠倒就把取模走向从逆向改成顺向重新取模。如果发现字上下颠倒就把逐列式改成逐行式。这个对应关系我踩了一次坑就记住了建议你也记一下。4.3 界面布局与刷新策略OLED只有128x64个像素空间不富裕布局要合理。我做的是四行显示第一行左侧显示PM2.5右侧显示数值比如043单位固定放在最后第二行左侧显示PM10右侧显示数值第三行左侧显示温度右侧显示25.3C湿度放在下一行第四行左侧显示湿度和甲醛各占一半或者全部显示甲醛具体怎么排看个人喜好我的建议是数值和单位尽量分离单位固定的部分可以放在布局里不需要跟着数字一起刷新。比如PM2.5那一行显示PM2.5后面跟三位整数再看显示ug/m3这样每次刷新时只需要刷新数字部分能减少I2C写入量屏幕闪烁也会更少。刷新策略上我采用的是全量刷新和局部刷新结合的方式屏幕初始化和切换页面时用OLED_Clear()清屏数据更新时只重写数值区域不清屏原因是OLED响应速度快但刷新时如果整屏重写能看到明显的闪烁感。局部刷新只有某几个数字变化人眼几乎察觉不到体感舒服很多。数据更新的时机也有讲究。串口每秒钟来一帧数据主循环如果持续不断刷新屏幕每秒可能刷几十上百次这是完全没有必要的。我用了两种策略简单粗暴版用HAL_GetTick()计算时间戳每隔500ms刷新一次屏幕。数据变化周期是1秒500ms刷新其实有点浪费。干净利落版加一个数据更新标志位串口解析完一帧数据后置1主循环检测到标志位后刷新屏幕再清零。这样保证每次刷新都是有效刷新完全无冗余。第二种方案代码量也没多少我却一直建议用它。因为这代表了一种事件驱动的思维当你的项目变复杂了这种处理方式能自然过渡到RTOS的消息队列模式。5. 常见问题与排查方法这部分是我最想写的因为是实际调试中一条一条试出来的经验。新手遇到问题时最容易手足无措我整理一个速查表把能遇到的问题尽量列全后面再挑几个典型场景展开讲。5.1 典型故障速查表现象可能原因排查方法OLED完全不亮I2C地址错误确认是0x3C还是0x3D用I2C扫描程序打印OLED不亮但I2C地址对初始化序列被跳过确认初始化函数被执行断点检查OLED有背光但无内容对比度寄存器设成0检查0x81后面的参数OLED显示花屏取模方式不匹配检查逐列/逐行、顺向/逆向设置OLED显示乱码汉字取模尺寸不对确认是16x16取模别的尺寸会偏移串口收不到数据供电不足换5V供电用万用表量模块VCC串口收不到数据TX/RX没交叉STM32的RX接模块TX反过来就收不到数据全是0模块还在预热上电后等10秒以上再看数据偶尔跳变接触不良或电源纹波检查杜邦线换短粗线加0.1uF去耦电容调试器连不上芯片SWD引脚被复用按住复位键下载或设置Boot0为1进入ISP模式编译报错找不多头文件CubeMX生成不全重新生成代码确认外设头文件已加入工程5.2 开发环境与烧录问题新手最痛的一关先说说下载调试的坑。用ST-Link下载程序时如果报错提示找不到ST-Link设备先不要怀疑硬件坏了。检查三样东西驱动是否已安装、调试器是否被电脑识别、Keil的Debug选项里是否选择了ST-Link并选对了接口SW。如果连上了但下载时报RDDI-DAP Error或No target found大概率是上一次烧录的程序把SWD引脚复用掉了。解决办法是把BOOT0跳线跳到1上电后芯片进入ISP模式此时内核不跑用户程序SWD引脚被释放再用ST-Link连接下载就能成功。下载完把BOOT0跳回0断电重启即可。用CH340串口下载的话还有一个常见问题设备管理器里CH340显示正常工作但串口工具打开后一直乱码。这个大概率是波特率设置不对。Y01-3IN1模块默认9600但很多串口调试助手默认是115200打开的瞬间就是满屏乱码。确认波特率匹配是第一步。另外CH340有个老毛病插上后电脑提示无法识别USB设备。常见原因是USB线是纯充电线没有数据线芯。换一根能传数据的USB线基本能解决。5.3 数据解析的几个隐性坑代码逻辑看着没问题但数据就是不对这类问题最让人抓狂。我遇到的几个典型情况第一个是温度显示为负值。Y01-3IN1的温度数据是uint16_t格式还是int16_t格式一定要看手册。如果是int16_t定义变量一定要用有符号类型否则负数会被解析成65535之类的巨大正数。我代码里用的是(int16_t)强制转换就是堵这个坑。第二个是PM2.5数值突然从三位数变成四位数。这通常是串口收串帧了导致某两位字节错位。帧同步状态机的同步逻辑必须严格发现帧头不匹配马上回到初始状态重找不能进入卡死状态。我把frame_data_len FRAME_DATA_MAX的情况直接丢弃就是为了防呆。如果没这个判断一个错误的数据长度会让状态机永远等一个不存在的字节那整个串口接收就瘫痪了。第三个问题是关于看门狗的。如果开独立看门狗IWDG一定要在主循环里及时喂狗。否则因为OLED的I2C写入是阻塞式万一某个I2C设备应答超时HAL_I2C_Master_Transmit会卡在超时等待函数里看门狗就会把芯片不断复位。从现象上看就是程序反复重启。在这种小项目里我建议先把看门狗关掉等所有功能都调试稳定后再考虑开。第四个坑是OLED显示刷新时闪烁这个跟取模方式无关纯粹是刷新逻辑问题。整屏全量刷新时先清屏再重绘屏幕中间会有几百毫秒的空白期肉眼看起来就是闪。改成局部刷新后问题自然消失。记住一个原则数据不变的区域永远不要重复写。5.4 我踩过的坑和几个小建议这块最后说三个建议都是折腾了不少时间总结出来的。第一杜邦线质量决定调试心情。Y01-3IN1模块的激光粉尘传感器带风扇工作时会有微小振动如果杜邦线接触不良串口数据就会间歇性中断。我建议模块部分用短一点的杜邦线最好把线剪短到5厘米以内或者直接焊接。OLED和主控之间线长了没关系I2C本身抗干扰不错。第二模块上电后需要预热。特别是甲醛传感器电化学原理需要几分钟才能稳定。刚上电那会儿数据偏高是正常现象不要误以为是程序BUG。我一般让设备跑5分钟以后再看数值。第三用串口调试助手先验证模块是否正常。在接STM32之前先把模块的TX接到USB转TTL的RX上打开串口助手波特率9600看能不能看到一帧帧十六进制数据。如果能稳定收到42 4D 00 0C...这种数据说明模块没问题再去排查STM32这端。这个习惯能帮你把问题快速隔离到模块坏了还是代码写错了省去大量盲目调错的时间。6. 后续扩展与个人体会Y01-3IN1这套方案跑通以后扩展空间非常大。往上走可以加个ESP8266或ESP32模块把数据上传到云端做远程监控往下走可以把显示换成1.3寸或2.4寸大屏显示内容更丰富。如果对采集的数据有更高要求还可以加一个微动开关切换到不同的显示页面。从我的角度看这个项目的核心收获不在于成功点亮了一块屏、读到了几个传感器数据而在于你理解了一条完整的数据流通链路传感器物理量采集、模块内部数据处理、串口协议封装、主控接收解析、外设显示呈现。这条链路是很多物联网终端设备的基础架构做透一次以后再做其他传感器项目都能触类旁通。至于我个人的体会有一句话特别想送给正在折腾的朋友嵌入式这东西九成的奇怪问题都是最基础的常识问题比如供电不足、接线错误、波特率不对。每次卡住的时候先别急着怀疑代码拿起万用表测一测电压用串口助手看看原始数据问题往往就在这一步自己暴露了。调通一个模块之后的爽快感是真的强烈尤其是你看着OLED上温度、湿度、PM2.5数字交替跳动会觉得自己搭起了一个微小但完整的系统这种感觉做久了是真上瘾。
返回列表