尧图精选

STM32驱动Y01-3IN1空气质量传感器:串口DMA+OLED显示实战

🕒 发布时间:2026/9/8 14:48:24 📁 来源:尧图网络
我最早接触Y01-3IN1这个模块是在给工作室做一个室内空气质量监测小系统的时候。当时市面上常见的传感器方案要么只测PM2.5要么只测温湿度想同时拿到颗粒物浓度和环境温湿度就得接两个模块代码得写两套协议OLED上还得来回切换页面麻烦得要命。后来换到Y01-3IN1发现一个模块全搞定串口直接出数据配合STM32的串口DMA接收和一块0.96寸OLED整个系统代码量比想象中少很多。这篇教程我会把整个项目的关键节点全部过一遍从Y01-3IN1的接口定义、STM32CubeMX的初始化配置到串口数据帧的解析、OLED驱动移植和界面布局最后再分享几个我实际调板时踩过的坑。适合正在做环境监测类课设、DIY空气质量检测仪或者第一次用STM32驱动串口传感器的朋友参考。就算你之前没用过DMA和空闲中断照着做也能跑起来。1. Y01-3IN1是个什么来头模块内部构成与STM32的接线要点先说结论Y01-3IN1本质上是一个把激光颗粒物传感器和温湿度传感器打包在一起的串口输出模块它的核心价值在于把所有模拟信号处理、标定、温度补偿都做进了模块内部对外只通过UART输出已经换算好的数字量。你做驱动层的开发完全不用关心激光散射怎么转成电压也不用做复杂的曲线拟合。1.1 “三合一”到底合了哪些参数这个模块输出的数据通常分成两个部分颗粒物浓度和温湿度。颗粒物浓度部分有的批次型号输出的是PM1.0、PM2.5、PM10三通道浓度有的批次还会额外带出温度、湿度。我手头这块就是标准版本一帧数据里同时包含PM1.0、PM2.5、PM10和温度、湿度所以严格来说是“颗粒物温度湿度”三合一。这种集成方式对单片机侧非常友好因为你不需要像DHT11那样自己写时序读温湿度也不需要像GP2Y1010那样拿ADC采样再按公式换算粉尘浓度。模块自己把脏活累活干完了STM32要做的只是把串口收到的字节按协议拆出来然后显示到屏幕上。这也是我推荐入门项目用这类模块的原因——它能让你把注意力集中在“串口驱动、DMA接收、OLED显示”这些通用技能上而不是浪费在传感器的信号调理上。1.2 串口输出还是模拟量Y01-3IN1的接口逻辑市面上空气质量模块大致分三类纯模拟量输出、PWM输出、UART串口输出。Y01-3IN1采用的是UART TTL串口输出默认波特率常见配置是9600bps8个数据位、1个停止位、无校验。对于STM32F103系列来说任何一个USART外设都可以直接用不需要额外的电平转换芯片。需要注意的是模块的VCC一般建议接5V供电因为内部的激光发射器和风扇如果有需要更高的电压才能稳定工作。而它的串口TX、RX引脚输出的是TTL电平和STM32的3.3V逻辑电平能否直接兼容取决于具体硬件设计。我实测下来多数版本的Y01-3IN1串口引脚是可以直接连STM32引脚的因为模块内部的电平转换和限流做得比较保守3.3V的单片机能正常识别高电平。如果你用的是特别老的批次或者模块说明书里明确写了“串口电平5V”那稳妥起见在RX线路上串一个1kΩ电阻做限流或者用一两块钱的TTL电平转换模块避免长期高压冲击损坏单片机引脚。1.3 接线之前必须想清楚的两个电平问题第一个问题是模块的TX和单片机的RX必须交叉连接。也就是说模块的TX引脚要接到STM32的USART1_RX引脚PA10模块的RX引脚接到STM32的USART1_TX引脚PA9。这个交叉是最容易被新手忽略的我见过好几个同学拿着串口助手能收到数据一接到单片机上就死活收不到最后发现是TX对TX、RX对RX直连了。第二个问题是共地。模块和STM32开发板必须共用一个GND否则串口电平没有参考点收到的数据全是乱码或者干脆没反应。这两条是串口通信的基本功但说真的哪怕是有经验的人换了一套新硬件也容易在这上面翻车。完整的接线表如下Y01-3IN1引脚STM32F103C8T6引脚说明VCC5V模块供电按手册要求选择GNDGND必须共地TXPA10USART1_RX模块发送到单片机RXPA9USART1_TX单片机发送到模块OLED那边的接线我放在下一节专门说因为这里面涉及的I2C地址和上拉电阻问题比串口那边更容易让人头大。2. 走I2C还是软件模拟OLED显示端选型与CubeMX初始化显示端我用的是最常见的0.96寸OLED驱动芯片SSD1306接口是I2C分辨率128x64。选择这个屏幕的原因很直接它便宜、成熟、驱动代码满地都是而且I2C只占用两根线把PA9和PA10留给串口之后整个系统的引脚压力很小。2.1 0.96寸SSD1306为什么是首选项你可能想用TFT彩屏显示效果确实更好看但驱动一个TFT需要SPI接口加一堆初始化命令代码量至少是OLED的两三倍。对于空气质量监测这种以数字为主、变化频率不高的界面OLED的黑底白字反而更清晰直观。而且SSD1306内部自带显存你用I2C写完一屏数据后屏幕自己会持续刷新显示不占用MCU的时间。另一个考虑是I2C接口的OLED可以在“硬件I2C”和“软件模拟I2C”之间自由切换。这个灵活性在后面调板的时候特别重要。如果硬件I2C莫名其妙卡死或者总线被拉死你可以直接把驱动换到软件模拟上几十行代码就解决了完全不用改硬件。2.2 CubeMX里把串口、I2C、DMA一次配好我用的是STM32F103C8T6最小系统板开发环境是STM32CubeMX Keil MDKHAL库版本比较新。在CubeMX里的配置顺序我习惯是先配时钟再配外设最后配DMA因为DMA请求需要挂在具体外设上外设没初始化的时候DMA选项是灰的。具体步骤如下RCC里打开HSE外部高速晶振SYS里Debug选项选Serial Wire这样ST-Link还能继续下载调试。USART1选择Asynchronous异步模式波特率填9600字长8位无校验1位停止位。然后勾选USART1全局中断。在DMA Settings选项卡里添加USART1_RX的DMA请求Direction选Peripheral To MemoryMode选NormalPeripheral Increment关掉Memory Increment打开Peripheral和Memory的数据宽度都选Byte。I2C1启用I2C模式速度选Standard Mode 100kHz其他默认就行。PB6和PB7会自动分配为I2C1的SCL和SDAOLED按这个接。DMA Mode选Normal而不选Circular是个值得说一嘴的细节。Normal模式下一轮DMA传输完成后会停在那里等你在中断回调里重新启动下一次接收。Circular模式虽然也能用但它会在缓冲区满后自动绕回开头代码逻辑稍微复杂一点容易把帧边界搞混。对于串口传感器这种不定长数据帧的场景Normal模式配合空闲中断是最好理解的方案。2.3 硬件I2C偶尔翻车预留软件I2C的退路F103的硬件I2C其实没有传说中那么难用我用HAL库的I2C驱动SSD1306跑得很稳。但有一点必须承认当I2C总线上出现异常电平毛刺时硬件I2C的状态机可能会卡在一个奇怪的状态表现为程序死等、总线锁死必须复位外设甚至掉电重启才能恢复。这个问题在杜邦线连接的环境下更容易触发。所以我做这个项目时的经验是优先用硬件I2C但如果你的代码莫名其妙卡在HAL_I2C_Mem_Write这个函数里别硬磕直接把驱动改成软件模拟I2C。软件模拟I2C随便用两个GPIO就能实现唯一的代价是每次写显存时需要手动翻转IO但OLED刷新率本来就不高根本感觉不到性能损失。我的工程里两种驱动都留了一份用一个宏开关切换调试时哪个好用哪个。3. 数据帧拆解Y01-3IN1串口协议与稳定接收的完整写法这一部分是整个项目的核心。传感器数据就在串口线上躺着但如果你不会按协议解析收到的就是一堆看起来毫无规律的字节。Y01-3IN1的数据帧协议其实很简单典型结构是“帧头数据段校验和”但不同批次、不同厂家的模块在字节排列上可能存在差异所以拿到模块后第一件事是查你手里这本手册的协议页确认数据段顺序。3.1 一帧数据长什么样帧头、数据段、校验我手头这块Y01-3IN1的帧格式大致是这样的每帧13个字节字节偏移内容说明00xAA帧头110x55帧头22PM1.0高字节单位ug/m33PM1.0低字节4PM2.5高字节单位ug/m35PM2.5低字节6PM10高字节单位ug/m37PM10低字节8温度高字节有符号数实际值/109温度低字节10湿度高字节实际值/10单位%RH11湿度低字节12SUM校验和第0字节到第11字节累加取低8位这里有一个很常见的坑是温度的有符号处理。温度可能是负的所以高字节不能当成无符号数要按照int16_t来看待然后用原始值除以10得到实际的带一位小数的温度。湿度则用uint16_t读取就行。PM系列浓度同理用uint16_t拼接高低字节。如果你的模块手册里的帧格式跟这个不完全一样别慌解析框架是一样的你只需要调整数据段的字节偏移即可。校验方式如果手册写的是异或校验那把你代码里的累加求和改成按位异或就行。3.2 用DMA空闲中断接收而不是在中断里一个一个读这里我想强调一个观点串口传感器数据接收永远优先考虑DMA空闲中断而不是每收到一个字节就进一次串口中断。为什么呢因为空气质量模块通常每隔一秒甚至几百毫秒就会发一帧数据如果每次都在中断里把字节拷贝到数组CPU会被频繁打断而且一旦主循环在处理别的任务比如刷新OLED字节处理不及时就会丢帧。DMA空闲中断的思路是这样的DMA外设在后台自动把串口收到的字节搬运到内存缓冲区完全不用CPU干预。当串口总线上出现一个“空闲状态”时说明一帧数据接收完毕这时硬件会产生空闲中断告诉CPU“你可以过来处理数据了”。你只需要在空闲中断回调里记录一下本次DMA实际接收了多少字节然后把缓冲区里的数据丢给解析函数。在较新版本的HAL库里这个逻辑已经封装成HAL_UARTEx_ReceiveToIdle_DMA函数使用方式很简单uint8_t rx_buffer[64]; volatile uint16_t rx_len 0; volatile uint8_t rx_complete 0; // 在main函数里开启第一次接收 HAL_UARTEx_ReceiveToIdle_DMA(huart1, rx_buffer, 64); // 回调函数 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart huart1) { rx_len Size; rx_complete 1; // 重新启动下一次接收 HAL_UARTEx_ReceiveToIdle_DMA(huart1, rx_buffer, 64); } }这个函数的作用是把串口接收和空闲检测绑定在一起当DMA收到至少一个字节并且总线上出现空闲时就会触发RxEventCallback。回调里传入的Size就是本次接收到的字节数非常直观。3.3 校验通过再更新防止脏数据上屏解析函数不能拿到一个帧头就急着更新显示必须把校验做完整。因为串口通信偶尔会出现字节错位或者干扰如果你不校验就把数据塞给UI屏幕上PM2.5瞬间跳个几千那体验就太糟了。我的处理思路是先按偏移找帧头找到后从帧头开始累加校验累加结果和最后一字节的校验值比较相等才算有效帧。uint8_t ParseAirData(uint8_t *buf, uint16_t len) { for (uint16_t i 0; i 12 len; i) { if (buf[i] 0xAA buf[i 1] 0x55) { uint8_t sum 0; for (uint8_t j 0; j 12; j) sum buf[i j]; if (sum ! buf[i 12]) continue; pm1_0 (buf[i 2] 8) | buf[i 3]; pm2_5 (buf[i 4] 8) | buf[i 5]; pm10 (buf[i 6] 8) | buf[i 7]; temp (int16_t)((buf[i 8] 8) | buf[i 9]) / 10.0f; humi (uint16_t)((buf[i 10] 8) | buf[i 11]) / 10.0f; return 1; } } return 0; }这里用了一个循环从头开始找帧头而不是假定缓冲区第一字节就是帧头。为什么因为DMA接收可能从帧中间开始缓冲区里可能是半截帧只有通过查找帧头并做完整校验才能确保每次解析的都是一条完整帧。这个“先找帧头再校验”的思路是所有串口协议解析的基本功。4. OLED驱动移植从显示一个“Hello”到完整界面OLED驱动部分最省事的方式是找现成的SSD1306驱动文件直接移植网上流传的中景园版驱动已经很成熟了我在这个项目里也是基于它改的。整个移植过程就三步把源文件加进工程改掉I2C地址和I2C句柄然后调通显示。4.1 驱动文件从哪来、怎么塞进工程你可以在GitHub上搜“ssd1306 hal library”能找到封装得很好的库或者用中景园电子官方提供的SSD1306例程里带的oled.c和oled.h。把oled.c、oled.h、oledfont.h三个文件复制到你的工程目录下然后在Keil里把oled.c添加进Source Group在包含路径里加上对应的头文件目录。移植过程中必须要改的地方是I2C地址。这里我要特别提醒一下SSD1306的I2C地址有两种表示法。数据手册上写的是7位地址0x3C但很多驱动代码里用的是8位写地址0x78。你用HAL库的HAL_I2C_Mem_Write函数时传入的是7位左移后的地址吗并不是HAL库内部会自己处理读写位所以你只需要填7位地址0x3C。如果你直接沿用网上某些51单片机例程里的0x78I2C通信会一直返回错误。我习惯在驱动头文件里定义一个OLED_ADDR宏改成0x3C然后在所有调用I2C写函数的地方使用这个宏保证整个工程只有一处需要改地址的地方。OLED驱动里最基本的函数一个是OLED_Init一个是OLED_Clear还有一个是OLED_ShowString。OLED_Init里面有一大串SSD1306的配置命令正常情况下你不需要去动它除非你想调整显示方向、对比度或者电荷泵设置。OLED_Clear把显存清零。OLED_ShowString在指定位置打印字符串。4.2 屏幕布局一眼看清PM2.5、温度、湿度128x64的屏幕如果用16x16的汉字和16x8的ASCII字符一行最多显示8个ASCII字符或者4个汉字总共可以显示4行。我这个项目的界面是这样安排的第一行显示PM1.0第二行显示PM2.5第三行显示PM10第四行显示温度和湿度。用8x16字体的话每行高度16像素4行刚好占满64像素。为了让数据显示更稳定我给每个数值都设置了固定宽度。比如PM2.5的值我用“%4d”格式化不足四位前面补空格这样数字变化时不会因为位数不同导致屏幕后面残留旧字符。如果你直接用“%d”去格式化数值从999变成1000时后面那个多出来的数字会占掉原本属于单位“ug/m3”的位置看起来就像乱码。显示刷新的策略我采用的是“数据更新时整行刷新”而不是每一帧都全屏清空重绘。全屏清空重绘会带来闪烁问题刷新频率高时尤其明显。我的做法是每次调用OLED_ShowString前先用OLED_FillArea把一个矩形区域填充为背景色再打印新字符串。这样相当于局部刷屏视觉上没有任何闪烁。4.3 汉字字模提取与显示界面上肯定不能全是英文缩写至少要显示“温”“湿”这样的中文让整个界面看起来更正式。SSD1306驱动默认只带ASCII字模中文需要你自己取模。取模工具我用的是PCtoLCD2002设置很关键。取模方式选“逐行式”每行8个点字节方向选“高位在前”还是“低位在前”要看你的显示函数是按什么方式解析的。最稳妥的办法是先用工具自带的预览功能生成一个“你”字再写个小测试函数把输出和预览对比确认字节序对上后再批量取模。16x16的汉字每个字需要32个字节的字模数据定义成const uint8_t数组放在头文件里就行。显示中文的函数其实和显示ASCII的原理一样只是每个字符的宽度从8像素变成了16像素逐字节把字模数据写入显存。如果你用的是现成的中景园驱动它的OLED_ShowChinese函数就是这个逻辑你只需要把字模数组填进去。5. 主程序整合一秒钟一刷的完整运行逻辑到这里传感器数据解析和OLED显示都通了最后就差把它们串到主循环里。这一节我讲讲主程序的整合思路。5.1 主循环怎么写才不乱很多第一次用DMA接收的人容易犯一个错误在while(1)里不断调用HAL_UARTEx_ReceiveToIdle_DMA却发现回调里的数据总是对的但主循环里读到的全局变量却是旧的。原因是主循环跑得比DMA回调快你可能在回调还没触发时就去读数据了。正确做法是用一个标志位只有标志置1时才去解析和刷新。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(8, 0, Air Monitor, 16); HAL_Delay(1000); HAL_UARTEx_ReceiveToIdle_DMA(huart1, rx_buffer, 64); while (1) { if (rx_complete) { rx_complete 0; if (ParseAirData(rx_buffer, rx_len)) { UpdateDisplay(); } } HAL_Delay(10); } }主循环里加一个HAL_Delay(10)主要是降低无谓的循环空转同时给OLED的一些时序操作留口气。10毫秒的延迟对这个项目来说影响为零因为传感器数据最快也就是一秒一帧OLED刷新根本不需要这么高的频率。5.2 从串口原始数据到屏幕字串的流转路径整个数据流是单向的Y01-3IN1模块通过串口TX发出数据帧STM32的USART1_DMA自动接收进rx_buffer硬件空闲中断触发RxEventCallback回调置rx_complete标志主循环检测到标志后调用ParseAirData解析出来的全局变量被传给UpdateDisplay最后UpdateDisplay里通过snprintf把变量格式化成字符串再调用OLED_ShowString刷新屏幕。这个流程里最关键的是“回调只置标志、主循环做处理”这个设计。串口空闲中断属于中断上下文里面不应该做太多工作尤其不能调用OLED刷新之类的耗时函数因为OLED通过I2C写显存需要时间在中断里做会拖慢整个系统的实时性。很多人的程序卡死、现象怪异都是因为在中断回调里干了太多不该干的事。更新显示的函数可以写成这样void UpdateDisplay(void) { char buf[20]; snprintf(buf, sizeof(buf), PM1.0:%4d, pm1_0); OLED_ShowString(0, 0, buf, 16); snprintf(buf, sizeof(buf), PM2.5:%4d, pm2_5); OLED_ShowString(0, 16, buf, 16); snprintf(buf, sizeof(buf), PM10:%4d, pm10); OLED_ShowString(0, 32, buf, 16); snprintf(buf, sizeof(buf), T:%4.1f H:%2.1f, temp, humi); OLED_ShowString(0, 48, buf, 16); }如果你的Keil工程用的还是MicroLIB记得在Options for Target里勾选Use MicroLIB否则snprintf这类浮点格式化函数会占用非常大的代码空间甚至链接报错。5.3 实测效果与数据验证程序烧录后串口模块要预热几秒钟才能输出稳定数据。你可以在串口助手上先验证模块输出再启动单片机接收。模块预热结束后OLED上应该能看到PM数值在合理范围内波动温度、湿度也能正常显示。为了验证解析是否正确我习惯把同一帧数据用串口助手发到单片机里让代码自己解析再对比串口助手解析出的数值。如果数值不一致说明帧格式理解有误需要回头调整字节偏移。我实测时在室内安静环境下PM2.5大约在20到40ug/m3左右PM1.0会比PM2.5低一些PM10会高一些。如果在模块旁边点燃一根线香几秒钟内PM2.5数值就会飙升到几百移开后又会慢慢回落到正常值。注意做这个测试时保持通风别把传感器搞太脏。6. 踩坑记录下载失败、串口乱码、OLED花屏的排查链路调硬件项目光会写代码还不够排查问题的思路往往更值钱。这一节我把这个项目里最容易遇到的一类问题集中拿出来按“现象→排查顺序→根因→解决”的链路讲一遍照顺序排查能省很多时间。6.1 ST-LINK连不上目标板先别急着怀疑芯片很多人拿到板子第一件事就是下载代码结果Keil报错“error: no stm32 target found! if your product embeds debug authentication...”。这个提示看起来非常吓人很容易让人以为是芯片锁死了或者ST-LINK坏了。但我遇到过的情况里九成都是硬件连接问题。排查顺序是这样的首先确认ST-LINK的四根线SWDIO、SWCLK、GND、3V3是否接对SWDIO对应PA13SWCLK对应PA14别接反然后确认目标板已经被供电有些最小系统板不通过ST-LINK供电时你得单独插一根USB线接着把Keil里Utilities设置里ST-LINK的下载速度从默认的4MHz降到1MHz试试有时候杜邦线太长导致信号质量差降低频率能救回来如果还不行按住目标板的复位按键点击Download后马上松开有时候能绕过芯片启动阶段的异常状态。如果以上都不行再考虑芯片是否被某种方式锁住。F103这种老芯片很少涉及Debug Authentication但确实存在意外把SWD引脚配置成普通GPIO导致下载口失效的情况。这时可以把BOOT0拉高重新上电让芯片进入ISP模式用串口ISP工具把芯片擦除一遍然后再把BOOT0拉回低电平重新用ST-LINK下载。这个方法可以解决绝大多数“连不上”问题。6.2 串口“收到但乱码”和“完全收不到”是两回事这两个问题在Y01-3IN1项目里都容易遇到。先说“完全收不到”。先用USB转TTL工具直接连模块的TX和GND在电脑串口助手上看有没有数据。这一步能快速区分是模块本身没工作还是单片机侧的问题。如果串口助手也没有数据优先检查模块供电是否正常VCC和GND有没有接反模块上有没有指示灯在亮。空气质量模块一般上电后需要预热1到3秒才往外发数据别接好线马上就说没反应。如果串口助手有数据而单片机收不到查两件事杜邦线TX到PA10的连续性以及单片机侧DMA配置是否正确。我犯过一次很蠢的错误CubeMX里DMA请求选成了USART1_TX而不是USART1_RX结果DMA一直在往串口发送缓冲区搬数据接收路径完全没通。再说“收到但乱码”。乱码的根源基本就是波特率不匹配。Y01-3IN1默认波特率常见的是9600但有些批次可能默认115200。如果模块手册丢了可以试着用串口助手的“自动识别波特率”功能扫一遍或者直接试几个常见波特率看哪一组能出AA 55开头的规律数据。此外还要确认模块和单片机的数据位、停止位、校验位配置一致。6.3 OLED不亮或花屏的常见根源OLED不亮的排查思路和串口接收不同因为I2C总线上没有“数据输出”这种主动事件你需要主动去读或者写来判断。如果你的OLED上电后完全不亮先看模块供电是不是3.3V或者5V看你买的模块版本再看SCL和SDA有没有接反。这两个引脚接反了不会烧硬件但屏幕怎么也不会亮。如果背光亮了但屏幕上全是乱码点多半是I2C地址不对。SSD1306模块的I2C地址由SA0引脚的电平决定高电平时7位地址是0x3D低电平时是0x3C。大多数模块默认是0x3C但如果你是从淘宝随便买的最好在代码里直接做个I2C地址扫描。自己写一个for循环从0x03一直试到0x77每试一个地址就调一次HAL_I2C_IsDeviceReady能找到哪些地址有设备应答一分钟就定位了。花屏还有一个常见根源是上拉电阻缺失。如果OLED模块上没有板载4.7k或10k上拉电阻你的I2C总线就缺少输出高电平的驱动能力现象可能就是时好时坏、花屏、闪烁。解决办法是在SCL和SDA上各接一个4.7k电阻到3.3V电源。绝大多数成品OLED模块都已经带了这个电阻但如果你用的是从屏幕排线上直接引线的裸屏或者转接板大概率需要自己补上。硬件I2C还有一个专属现象程序运行一段时间后卡死在I2C写函数里叫做“总线锁死”。解决办法是把SCL和SDA引脚临时配成普通GPIO输出模式在SDA上连续输出9个时钟脉冲来释放被锁住的状态然后再恢复I2C外设模式。我在驱动里单独写了一个I2C_Unlock函数每次OLED初始化之前调用一次从此再也没有遇到过I2C卡死的问题。6.4 数据会跳变、偶尔卡住怎么办如果OLED上显示的数据整体趋势正常但偶尔冒出一个明显离谱的数值比如PM2.5突然跳到几千大概率是你的解析代码把不完整的帧当成了有效帧。你可能会觉得“我明明做了校验啊”但细想一下如果模块一帧发到一半DMA在中间某处触发了空闲中断那你拿到的rx_buffer里很可能前半部分是上一帧的尾巴后半部分是下一帧的帧头而校验和却恰好对上了——这种概率不高但DMA缓冲区长度越短犯错的概率越大。解决办法有两个方向。第一个方向是加大DMA缓冲区长度尽量容纳多帧数据然后在解析时从头扫描寻找帧头。第二个方向是加入“连续N帧有效才算数”的滤波逻辑比如连续两帧解析通过的数值相差不大才更新显示。空气质量数据本来就是缓慢变化的这种滤波不会让你错过什么快速变化的事件还能让显示更稳定。另外模块自身的原因也要考虑。有些空气质量模块内部带一个微型风扇或者气泵刚上电那阵子工况不稳定数据波动较大。建议代码里做一个初始化丢弃策略开机后前5帧只解析不显示从第6帧开始更新屏幕。这个小改动能让开机阶段屏幕数据稳定很多。数据偶尔“卡住”不更新多半是DMA接收没有重新启动。检查一下你的RxEventCallback里有没有再次调用HAL_UARTEx_ReceiveToIdle_DMA。如果忘了重启第一次接收完成后DMA就停摆了后续数据再也不会进缓冲区屏幕上自然就永远停在旧数值上。这个问题我至少见过三次每次都是回调里少了一行重启代码。如果你用的是比较老版本的HAL库没有HAL_UARTEx_ReceiveToIdle_DMA这个函数就需要手动处理UART的IDLE中断。做法是在USART1_IRQHandler里判断IDLE标志然后读取SR和DR寄存器来清除标志最后用__HAL_DMA_GET_COUNTER计算当前DMA剩余字节数从而推导出本次接收长度。逻辑是一样的只是代码需要自己多写几行。最后再分享一个我实际操作中的小习惯每接到一个新传感器模块我不会急着写STM32代码而是先在电脑上用USB转TTL把它接到串口助手里用“十六进制显示”模式观察原始数据流把帧格式彻底看明白再动手。有时候模块手册写得含糊但实际数据一看就清楚了。说实话这个习惯帮我省下的时间绝对比我写这篇教程的时间还多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →