STM32+Y01-3IN1空气质量模块OLED显示实战教程
发布时间:2026/9/7 5:30:07来源:尧图网络
最近帮朋友搭了一个小型环境监测的demo核心就是用STM32驱动Y01-3IN1空气质量模块把测到的PM2.5、温湿度、CO2这些数据实时显示在一块0.96寸OLED上。这个组合在空气质量监测、智能家居、教室改造这类项目里很常见也是很多毕业设计会选的方向。整个过程从选型、接线、写代码到调串口前前后后踩了不少坑这里完整记录下来给正准备做类似项目的人一份可以直接抄作业的参考。这篇教程适合刚学完STM32基础、想上手一个综合小项目的开发者也适合正在构思空气监测类毕业设计的同学。你不需要太多前置知识只要会用Keil MDK、能跑通一个最简单的LED流水灯工程剩下的跟着做就行。我会把硬件接线、CubeMX配置、HAL库驱动、串口数据解析、OLED显示这几个核心环节全部拆开讲同时把调试过程中遇到的典型问题和排查思路也一并整理出来。1. 项目整体设计与方案选型1.1 Y01-3IN1模块到底是个什么东西Y01-3IN1这个型号市面上有好几个厂家在做核心特点是“一个模块集成多路空气质量传感器”。我手上这块集成了PM2.5激光传感器、温湿度传感器和CO2传感器模块上直接引出串口TTL接口所有测量结果都通过UART协议输出。有些版本是三合一里换成甲醛或者TVOC协议解析思路完全一样只是数据字段的含义不同。模块内部其实已经帮你把多个传感器数据融合好了。它内部有一颗单片机负责轮询各个传感器整理成固定格式的数据帧然后通过串口发出来。对STM32来说你不需要分别驱动激光传感器、温湿度芯片、CO2芯片只需要接两根线把数据收进来再按照协议手册把帧解析出来就行。这比用三个独立传感器再自己写校准、补偿逻辑要省事太多。选择这个模块还有一个现实原因三合一模块的价格通常比单独买三个传感器再加转接板便宜而且占板面积小。对于空气质量监测这种需要“多点测量”的场景方案越简单越稳定。1.2 为什么用UART通信 STM32 OLED这个组合先说STM32。STM32F103C8T6是入门首选淘宝上一块最小系统板十几块钱包含了晶振、复位电路、USB转串口、稳压电路焊上排针就能用。它有两个USART、两个I2C、足够的GPIO驱动一个串口模块加一块OLED绰绰有余完全不会出现资源紧张的问题。Y01-3IN1输出的是串口数据所以通信方式没得选必须走UART。STM32的USART1接收模块数据这是整个项目的数据入口。OLED选择0.96寸I2C接口的SSD1306屏是另一个层面的“省事”。I2C总线只需要SDA和SCL两根线两个引脚就能搞定显示剩下的引脚全部空出来做其它事情。SPI接口的OLED虽然刷新速度快但要占用4根线甚至更多在这个显示内容只有几行数字的应用场景下根本没有必要。SSD1306驱动库非常成熟HAL库环境下几行代码就能点亮。整个系统的数据流就是传感器模块采集空气数据 → 串口发给STM32 → STM32解析数据帧 → I2C发给OLED → OLED显示在屏幕上。一条线串下来单向流动逻辑非常清晰。1.3 核心资源分配与后续扩展性在动手之前我把引脚资源做了一个大致的规划。USART1固定在PA9TX和PA10RX用于和Y01-3IN1通信I2C1使用PB6SCL和PB7SDA连接OLED。剩下的PA1到PA5、PB0、PB1等引脚都空着后续如果要做按键切换页面、控制继电器、外接风扇这些功能都有地方接。选择PA9/PA10还有一个好处USART1的这部分引脚默认就能用作串口烧录如果项目中途要加SWD调试接口之外的调试手段会比较灵活。不过在这个项目里我直接用ST-LINK通过SWD口下载和调试后面会提到一个很常见的连接错误。2. 硬件准备与接线要点2.1 物料清单准备材料的时候不要图省事尤其电源部分。以下是这个项目用到的全部物料物料型号/规格数量备注STM32最小系统板STM32F103C8T61蓝色板或者黑色板都行空气质量模块Y01-3IN1串口版1确认输出方式为TTL串口OLED显示屏0.96寸I2C接口SSD130614针或6针版本均可ST-LINK调试器ST-LINK V21用于下载和调试杜邦线母对母若干建议多备几根USB-TTL模块CH340或CP21021用于单独看模块原始输出可选电源5V/1A以上USB电源1给整个系统供电2.2 接线表与电气连接接线是整个项目中最容易出错、却又是最基础的一步。我画了一张接线表照着接基本不会出问题Y01-3IN1STM32F103C8T6说明VCC5V模块供电多数型号需要5V供电GNDGND必须共地TXPA10USART1_RX模块发送STM32接收交叉连接RXPA9USART1_TXSTM32发送模块接收OLED SDAPB7I2C数据线OLED SCLPB6I2C时钟线OLED VCC3.3V或5V根据OLED模块丝印选择OLED GNDGND共地这里提醒一句接线前一定要先看自己手上模块的指标。Y01-3IN1如果是5V供电它的串口TX引脚输出电平一般是3.3V TTL可以直接接STM32的PA10。但如果模块手册写明是5V TTL电平理论上需要加电平转换电路。实际做样机时如果两个板子供电都稳定不少人也直接连但我建议还是按手册来别赌这一步。2.3 供电细节中的一个隐藏大坑这个坑我当年踩过一次必须单独拿出来说。空气质量模块内部有激光粉尘传感器那个激光二极管和风扇部分型号有在启动瞬间会有比较大的电流。如果STM32从同一个USB口取电而且线材质量一般模块启动瞬间会把板上电压拉低导致STM32复位。我的解决方案是模块供电和MCU供电从源头分开用两个USB口或者用一个5V/2A的适配器供电后分流。ST-LINK只负责下载和调试不给目标板供电。如果只有一路供电也要确保线材足够粗尽量缩短供电线路。另外一个经验是把模块的VCC接在STM32板的5V输出上然后STM32板子用Micro-USB供电绝大多数情况下也是稳定的。如果你发现模块工作一会就数据异常或者OLED闪烁优先检查是不是供电不够。3. 软件驱动实现从CubeMX到HAL库3.1 CubeMX工程配置软件部分我选择了HAL库加STM32CubeMX的组合。和标准库相比HAL库层次更清晰尤其是处理中断、DMA、I2C这些外设时HAL库已经帮你把底层寄存器操作封装好了不用自己去抠寄存器位。打开STM32CubeMX新建工程芯片选STM32F103C8Tx。在Pinout Configuration页面做以下配置SYSDebug模式选Serial Wire否则SWD接口不工作后面没法下载程序。RCCHSE选择Crystal/Ceramic Resonator。USART1模式选Asynchronous波特率1152008位数据1位停止位无校验。I2C1I2C模式选择Standard Mode速度100kHz。时钟树让HCLK跑到72MHz。我直接让CubeMX自动设置HSI作为PLL源也可以这个项目对时钟精度不敏感。工程设置Toolchain选MDK-ARM代码生成选“Copy only the necessary library files”。工程生成之后先在main.c里写一个简单的GPIO翻转测试确认板子能正常下载和运行然后再进行下一步。3.2 串口接收方案选型中断接收还是DMA接收Y01-3IN1是主动上报型模块。上电之后它会按照某个固定周期一般是1秒到几秒不等往外发数据帧不需要STM32发送任何命令。因此STM32这边要做的就是“被动收”。串口接收有两种常见姿势逐字节中断接收和DMA接收。逐字节中断最简单每收到一个字节触发一次中断把字节存进数组主循环里判断帧是否完整。这个方式在波特率不高、数据量不大的场景够用但缺点是每收一个字节耗一次中断CPU开销大。DMA接收的好处是数据到了自动搬进内存缓冲区完全由硬件完成CPU不用管。接收完成后通过中断通知CPU去处理。我最终选了DMA加串口空闲中断IDLE的方案。所谓空闲中断就是串口在一段时间内没有收到新数据时触发一次中断表示一帧数据结束了。这个方案在不定长帧场景特别好用数据到了就在中断里处理不需要主循环轮询。3.3 CubeMX里的DMA配置在CubeMX的USART1配置页里进入DMA Settings添加一个USART1_RX的DMA通道。模式选Circular循环模式数据宽度都是Byte优先级可以选Medium。USART1全局中断和DMA中断都要在NVIC Settings里勾选。USART1全局中断我们必须开因为空闲中断要靠它触发。DMA中断开不开取决于你想在DMA传输完成中断里做事情我们这个场景可以不开DMA传输完成中断只靠空闲中断就够了。这里的关键在于理解DMA循环模式和空闲中断是怎么配合的DMA在Circular模式下会一直监听串口收到数据就往缓冲区写写满后自动回到缓冲区开头继续写每收到一帧数据串口检测到总线上空闲了就触发一次IDLE中断在IDLE中断回调里通过读取DMA当前计数器的值算出本轮数据量然后去缓冲区里取数据。4. 核心代码实现与参数解析4.1 串口DMA接收的实现代码工程生成后我在usart.c里增加了DMA接收的启动函数。核心思路是启动DMA在Circular模式下接收然后等待IDLE中断到来。uint8_t uart_rx_buffer[128]; void UART_RX_Start(void) { HAL_UART_Receive_DMA(huart1, uart_rx_buffer, sizeof(uart_rx_buffer)); }注意这里传给HAL_UART_Receive_DMA的缓冲区大小一定不要写死成1要写缓冲区的完整长度。因为在Circular模式下DMA会持续往这个缓冲区写数据直到缓冲区写满再从头开始。如果长度写错了数据会把缓冲区外面也写穿严重时会直接硬件错误。在main.c的主循环之前先调用UART_RX_Start()让串口进入接收状态。HAL_UART_Receive_DMA这个函数不会阻塞调用一次之后就一直生效除非中途调用HAL_UART_DMAStop停止它。4.2 空闲中断回调里的数据提取HAL库在串口空闲中断的处理上不同版本函数名有些区别。我用的HAL库版本里串口空闲中断统一走HAL_UART_IRQHandler之后通过HAL_UARTEx_RxEventCallback回调通知应用层。如果你的HAL库没有这个回调可以自己在HAL_UART_IRQHandler里加一段对UART_FLAG_IDLE的判断。void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { uint16_t len 0; if (huart-Instance USART1) { len sizeof(uart_rx_buffer) - __HAL_DMA_GET_COUNTER(hdma_usart1_rx); if (len 0) { Process_Air_Data(uart_rx_buffer, len); memset(uart_rx_buffer, 0, sizeof(uart_rx_buffer)); } UART_RX_Start(); } }这里有一个很关键的细节在进入空闲中断那一刻DMA已经停止了也就是说DMA计数器不再变化。这个时候用缓冲区总长度减去DMA当前计数器剩余值得到的就是这一轮实际收到的数据字节数。处理完数据之后必须重新调用UART_RX_Start()让DMA重新开始接收否则系统只工作一次就再也不收数据了。Process_Air_Data就是解析函数把原始字节流转换成我们需要的数据结构。不同厂家、不同批次的Y01-3IN1协议格式可能不同解析函数必须以你模块附带的协议手册为准。我这里以最常见的格式为例帧头0xAA 0x55之后依次是PM2.5高字节、PM2.5低字节、温度高字节、温度低字节、湿度字节、CO2高字节、CO2低字节最后是校验和。4.3 帧校验与错误处理串口通信有可能受到干扰尤其是传感器模块和STM32之间的杜邦线稍微长一点的时候。所以数据帧解析一定要加校验。常见的校验方式有和校验、CRC校验。Y01-3IN1这类模块大多采用和校验就是把一帧数据里除了校验字节之外的所有字节求和取低8位和最后一个校验字节比较相等才认为这帧数据有效。uint8_t calculate_checksum(uint8_t *buf, uint16_t len) { uint8_t sum 0; for (uint16_t i 0; i len; i) { sum buf[i]; } return sum; } void Process_Air_Data(uint8_t *buf, uint16_t len) { if (len 9) { return; } if (buf[0] ! 0xAA || buf[1] ! 0x55) { return; } uint8_t checksum calculate_checksum(buf, len - 1); if (checksum ! buf[len - 1]) { return; } pm25 (buf[2] 8) | buf[3]; temperature (int16_t)((buf[4] 8) | buf[5]) / 10.0f; humidity buf[6]; co2 (buf[7] 8) | buf[8]; }温度除以10是因为模块内部用一位小数来表示温度比如0x00FA是十进制250实际就是25.0摄氏度。PM2.5和CO2直接用两个字节组合成16位整数单位是ug每立方米和ppm。不同模块的数据格式会有差异有些是每个数据都带符号位有些湿度也有小数位这一块必须靠自己实测验证。还有一个容易忽略的点数据帧错位。如果你在模块上电的中途才开始监听串口收到的第一个数据可能不是帧头往后的数据都会错位。解决办法是在解析函数里做滑动窗口查找如果没有找到帧头就直接丢弃。我这里只是最简单的情况如果要做得更稳可以维护一个环形缓冲区把所有收到的字节都缓存下来再逐字节滑动匹配帧头。4.4 OLED显示驱动代码OLED部分我直接用SSD1306库I2C方式驱动。核心就两件事初始化SSD1306、显示字符串。uint8_t oled_address 0x78; // 7位地址0x3C左移一位注意SSD1306的I2C地址有两种常见情况0x3C和0x3D对应写地址寄存器时的0x78和0x7A。如果你的模块没显示任何内容尝试改一下这个地址。我用的是0x78。初始化部分直接调用库函数OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, Air Quality Monitor, 12);显示更新逻辑放在主循环里。因为PM2.5、温度这些数据是不断变化的我每一轮解析完成后刷新一次屏幕。但要注意刷新频率不能太夸张OLED本身有刷新次数上限而且人眼对几毫秒的变化不敏感。我这个模块每秒上报一次数据所以我让屏幕也跟着每秒刷新一次就够了。显示整数和浮点数时要注意格式化问题。HAL库的printf重定向需要额外处理我在项目里直接用sprintf把数值转换成字符串再显示char str_buf[20]; sprintf(str_buf, PM2.5: %d ug/m3, pm25); OLED_ShowString(0, 2, str_buf, 12); sprintf(str_buf, Temp: %.1f C, temperature); OLED_ShowString(0, 4, str_buf, 12);OLED屏幕上的坐标单位是行不是像素。0.96寸OLED一般是128x64像素在显示12号字体时一行高度12个像素一屏大概能显示5到6行。如果你要显示更多内容就得用更小的字体或者翻页。4.5 汉字显示与字模处理OLED本身只能显示点阵任何字符、汉字都需要先做成点阵字模。英文字符和数字大家常用库里有现成的字库直接调用就行但中文就需要自己去取模。常用做法是使用PCtoLCD2002这个软件输入你要显示的文字选择一个合适的字模格式阴码、逐列式然后生成一个数组放到代码里。比如我要显示“空气”两个字用16x16点阵生成一个32字节的数组然后在显示函数里逐个像素点画出来。这里不需要自己写复杂的UI只是在原有OLED库里加一个显示汉字的函数即可。生成字模时有一个比较常见的坑取模软件里默认的“自左向右”和OLED驱动IC实际扫描方向如果不一致显示出来的汉字会看起来乱糟糟的。解决方法是每次取模后先在屏幕上测试一下如果汉字左右颠倒或者上下颠倒就去取模软件里调整取模方向重新生成。我在项目里最后选的是“逐列式、低位在前”的格式和SSD1306内部显存排列完全匹配。4.6 完整主循环代码主循环的逻辑很简单OLED先显示基础信息然后等待串口数据到来更新数据再刷新显示。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_DMA_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, Air Quality Monitor, 12); UART_RX_Start(); while (1) { if (data_updated) { data_updated 0; OLED_Clear(); sprintf(str_buf, PM2.5: %d ug/m3, pm25); OLED_ShowString(0, 0, str_buf, 12); sprintf(str_buf, Temp: %.1f C, temperature); OLED_ShowString(0, 2, str_buf, 12); sprintf(str_buf, Humi: %d %%, humidity); OLED_ShowString(0, 4, str_buf, 12); sprintf(str_buf, CO2: %d ppm, co2); OLED_ShowString(0, 5, str_buf, 12); } } }data_updated是一个全局标志位在串口回调函数里置1主循环检测到标志位后才刷新屏幕。这种方式避免了主循环频繁刷屏、而串口数据又没更新造成的浪费。温度显示用了%%来表示字符串里的百分号sprintf的转义规则不要记错了。5. 常见问题排查与调试经验5.1 串口收不到任何数据这是最让人头疼的问题。按照可能性从高到低排第一TX和RX接反了。模块的TX对应STM32的RX模块的RX对应STM32的TX。很多人在这栽跟头尤其在模块丝印不清晰的时候。最好用万用表量一下模块的TX引脚有没有电平跳变或者把模块单独用USB-TTL工具接电脑在串口助手里看有没有原始数据进来。第二共地问题。模块和STM32必须共地如果不共地串口电平根本没有参考地数据就是乱的。检查一下GND线是不是松脱了。第三波特率不对。模块手册写的是115200但有些批次默认是9600甚至有38400的。逐一试试串口助手能收到数就说明模块这边没问题问题出在STM32配置。第四DMA和空闲中断配置有遗漏。比如CubeMX里忘了勾选USART1全局中断或者忘了在NVIC里打开USART1中断回调函数永远不会被调用。5.2 解析出来的数据明显不正常如果你在串口助手能收到数据但STM32解析出来的数据全是大烂值先检查两个地方。一是检查和校验逻辑。有些人在处理一帧数据时把校验字节也算进去了导致校验永远失败。还有人是帧头判断错误0xAA 0x55是对的但实际模块是0xAA 0x00开头这就需要对协议手册逐字节核对。二是字节序问题。模块一般按大端发送即高字节在前低字节在后。但个别模块用低字节在前的格式。这个没法凭空猜只能先从串口助手记录的原始帧里人工比对一下数据。比如让模块在干净环境待一会PM2.5应该是个比较小的值如果解析出来几百上千大概率是字节组合顺序错了或者没用温湿度校正公式。还有一个细节容易被忽略温度是带符号的整数。北方的冬天室温可能是-10度模块返回的负数是补码表示直接用uint16_t去接收再转换成float会解出错误值。我把温度字段先转成int16_t再做除法就是专门处理这种情况。5.3 ST-LINK下载时报“Error: no STM32 target found”连接开发板时最常见的报错就是“Error: no STM32 target found! If your product embeds debug authentication, please...”这个。很多人第一次遇到就以为是板子坏了其实大部分原因是以下几点。Debug模式没打开。CubeMX里SYS设置中的Debug没有选Serial Wire生成的代码就没初始化SWD引脚ST-LINK自然找不到芯片。解决方法是重新在CubeMX里打开Serial Wire重新生成代码再烧录一次。接线问题。ST-LINK的SWDIO、SWCLK、GND三根线必须分别接到目标板的SWDIO、SWCLK、GND。有些ST-LINK的3.3V输出也要接板子提供参考电压。另外SWDIO和SWCLK不要接反两口握手失败就会报这个错误。BOOT0引脚影响。STM32F103的BOOT0如果被拉高芯片会进入系统存储器启动模式核心Cortex-M3仍然能识别但SWD不一定能正常连接。排查时可以确认一下BOOT0跳线帽是否在默认的Ground位置如果拔掉了先接回去。还有一种情况是目标板供电不足或者完全没有供电。ST-LINK的3.3V输出电流有限如果目标板本身接了空气模块和OLED总电流可能远超ST-LINK能提供的量导致芯片电压不稳SWD握手失败。我推荐的方式是目标板单独用USB供电ST-LINK只接SWDIO、SWCLK、GND不接3.3V。5.4 OLED不亮或花屏OLED不亮先查电源和I2C地址。SSD1306的地址0x3C对应写地址0x780x3D对应写地址0x7A。如果模块背面有电阻选焊位置地址可能是可配置的仔细看丝印。如果地址不对初始化后屏幕没有任何反应改一改再试。花屏的原因大多是I2C速率太高或者时序有问题。把CubeMX里I2C速度从400kHz改回100kHz排除信号质量因素。另外I2C总线上拉电阻也很关键。标准做法是SDA和SCL各接一个4.7k上拉电阻到3.3V。很多便宜的OLED模块板上已经自带上拉电阻但如果你用的是那种极简版的模块板上没有上拉就需要自己焊接。还有一种花屏是屏幕初始化顺序不对。SSD1306的初始化命令序列有严格要求不同厂家的屏对初始化指令的宽容度不同。如果你用的库初始化命令不完整就可能出现亮但花屏的情况。此时换一个成熟的开源SSD1306库或者对比官方数据手册的初始化序列一条一条核对。5.5 数据延迟和虚假数据Y01-3IN1内部的激光传感器在校准阶段会输出一些不稳定数据。模块上电后的前10秒PM2.5数值跳动得厉害甚至可能出现峰值很大的假数据。我的做法是在代码里做一个“启动软启动”模块上电后前10秒的PM2.5值不用于显示过了这段时间再正常更新。另外数据显示周期和传感器上报周期要匹配。模块如果每2秒上报一次程序也应该是每2秒刷新一次屏幕。如果你的OLED每秒刷新一次中间那次就是重复显示旧数据看起来数据就不够“实时”。这里建议用一个简单的时间戳记录上次处理数据的时间超过模块上报周期才处理新帧。6. 项目扩展与个人经验补充这套系统跑通之后可玩性还是很高的。最常见的扩展是把数据传到云平台。用ESP8266或者ESP32通过串口从STM32拿数据再走WiFi上传到物联网平台手机端就能实时查看空气数据。这个扩展只需要在现有代码里增加一个串口输出逻辑把解析好的数据按特定格式发出去就行改动不大。另一个实用扩展是数据本地存储。通过SPI接口挂载一张MicroSD卡在数据解析成功之后以CSV格式把带时间戳的数据写入文件。这样就能回看一天甚至一周的空气变化曲线对分析室内环境质量很有用。如果你对显示界面不满意可以从SSD1306的单色显示升级到1.3寸或者更大尺寸的TFT彩屏。界面UI自由度更高还能画趋势图。代价是占用更多引脚和处理时间需要根据实际需求权衡。最后分享一个我实际项目里的体会空气质量监测类项目数据准确性永远是第一位的。硬件上要确保供电稳定传感器周边不要有热源和气流干扰软件上帧校验和数据过滤不能省。做好这些项目基本就成功了一大半。还有一个很实用的调试技巧把原始串口数据直接通过STM32的另一个串口转发到电脑串口助手上加上时间戳打印出来。这样在排障的时候你能看到完整的原始数据流判断到底是模块发错、解析错还是显示错效率能提高很多。我在这个项目里就用PA2/PA3做了调试串口全程观察原始帧和解析结果很多问题一眼就能定位。
网站建设高端定制企业官网