拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于STM32的智能空气净化器控制系统:传感器采集与PWM调速实战

简介面向嵌入式初学者的STM32智能空气净化器完整工程方案源自大二嵌入式开发进阶期末大作业。内容涵盖传感器数据采集、控制算法、电路设计、无线远程监控等关键环节适合正在学习C语言与STM32开发的在校生作为课程设计或综合实践参考。压缩包共1063个文件包含574个C源码、258个头文件、51个汇编文件以及大量工程配置文件并集成标准外设库、CMSIS DSP库与Keil/IAR工程框架便于直接编译和二次开发另有目标文件、链接脚本、日志等辅助理解编译与调试流程。资源包大小约28.51MB已有126人学习下载。整套资料结构清晰、模块覆盖完整既可在原工程基础上快速还原项目也能借鉴其传感器驱动、电机控制与UI交互的代码思路为后续嵌入式系统开发提供扎实的参考。1. 智能空气净化器的控制系统STM32 课程设计的“传感器执行器状态机”的进阶题大二学期的嵌入式进阶大作业选空气净化器很多人一开始以为是做“硬件组装”等真正拆完需求才发现它其实是把模电采样、定时器 PWM、串口协议、状态机设计全部串在一起的综合题。这篇文章讨论的是一套基于 STM32 单片机的智能空气净化器控制系统覆盖从传感器选型到 PWM 风机调速再到自动档位决策的完整落地路径。这套系统对正在准备基于 STM32 的毕业设计、毕设方向要选嵌入式开发的同学或者刚开始规划嵌入式开发学习路线的人来说是一个性价比极高的进阶练习——因为它不要求画 PCB不涉及复杂算法却能让 STM32 的多数外设真正转起来。下文按照数据采集、执行驱动、软件控制、联调标定四个阶段展开侧重每个环节里课程设计中最常栽跟头的地方。2. 传感器数据采集粉尘、温湿度与气体传感器的选型和 STM32 读取2.1 三种检测模块怎么选GP2Y1010、PMS5003 与 MQ-135智能空气净化器的“智能”首先体现在感知层。课程设计里最常见的三种传感器是夏普 GP2Y1010AU0F 红外粉尘传感器、攀藤 PMS5003 激光颗粒物传感器和 MQ-135 半导体气体传感器。三者输出方式和精度差异很大选型直接决定后续代码的写法。模块型号输出方式量程与精度成本适合场景GP2Y1010AU0F模拟电压0~3.6V粉尘浓度精度有限很低低成本课设、模拟信号链练习PMS5003UART 数字帧0~1000μg/m³分辨率 1μg/m³中等需要真实读数、串口解析练习MQ-135模拟电压 / TTL 电平定性判断燃气、气味很低辅助“空气变差”判断不宜定量选型逻辑上GP2Y1010 更贴近嵌入式底层——运放调理、ADC 采样、线性回归换算从硬件到代码都能训练到PMS5003 则是一包一包的串口数据考察的是 DMA 接收、缓冲管理和帧校验能力。课程设计如果只做一个“能亮能转”的演示GP2Y1010 就够如果想让答辩时有真实数据支撑PMS5003 更可靠。两者在 STM32 上的读取方式完全不同建议先确认题目侧重再做决定。MQ-135 只需要一路 ADC适合放在辅助通道上判断异味不作为主控制依据。2.2 GP2Y1010 的脉冲采样时序与 ADC 采样GP2Y1010 的驱动时序比较特殊每隔 10ms 需要把 LED 引脚拉高 0.28ms并在脉冲开始后的 0.14ms 处采样输出电压。直接在 while(1) 主循环里用 delay_us 控制这段时序会遇到两个问题一是主循环里其它任务如按键扫描、OLED 刷新会打断 0.28ms 的脉冲窗口二是延时函数在开启不同编译优化等级后时间抖动变大ADC 采样值毛刺明显。稳妥做法是放在定时器中断里执行课程设计里的常见实现如下// TIM4 每 10ms 进入一次中断控制 GP2Y1010 的 LED 脉冲与采样时刻 void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); GPIO_SetBits(GPIOC, GPIO_Pin_6); // LED 引脚拉高 delay_us(140); // 等待到脉冲中点 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动一次 ADC 转换 delay_us(140); // 维持完整 0.28ms GPIO_ResetBits(GPIOC, GPIO_Pin_6); // LED 引脚拉低 } }这段代码在中断里用延时函数换取时序精准优点是逻辑直白适合课程设计快速调通。但其代价是中断服务函数被占用了接近 0.3ms系统里其它中断的实时性会被拉低。如果想做得更工程化可以用定时器 PWM 直接生成 LED 脉冲波形再把 ADC 配置为注入触发模式由 PWM 输出的中点触发采样全程不需要 CPU 参与延时。这种方案会让代码复杂度上升一个台阶但恰好是答辩时能拿出来讲的亮点。无论选哪种方式GP2Y1010 的模拟输出引脚都要注意电平匹配传感器 5V 供电时输出电压可能超过 STM32 的 3.3V ADC 参考电压需要用电阻分压后送入 PA1。2.3 PMS5003 的串口帧接收与校验换用 PMS5003 后代码侧重点从脉冲时序转移到数据帧解析。PMS5003 以连续 32 字节为一帧帧头固定为 0x42 0x4DPM2.5 浓度值位于第 5、6 字节。帧尾两字节是校验和等于前 30 字节的累加。串口接收建议用 DMA 配合空闲中断但课程设计里环形缓冲区已经够用。帧解析核心如下uint8_t buf[32]; uint16_t pm25 0; // 收到完整 32 字节后调用返回 1 表示校验通过 uint8_t pms_parse_frame(uint8_t *buf) { uint16_t sum 0; if ((buf[0] ! 0x42) || (buf[1] ! 0x4D)) return 0; // 帧头不对 for (int i 0; i 30; i) { sum buf[i]; } if (sum ! ((buf[30] 8) | buf[31])) return 0; // 校验和错误帧被截断或干扰 pm25 (buf[4] 8) | buf[5]; // PM2.5 浓度单位 μg/m³ return 1; }帧头判断和累加校验缺一不可。只判断帧头不做校验串口一个字节错位就可能导致整帧数据解析错乱。PMS5003 的校验和方式与常见的 modbus 帧接收数据程序里的 CRC 校验不同PMS5003 用的是简单一字节累加和实现代价小但应对连续噪声的能力弱。接收逻辑上注意拆帧问题串口中断里收到的第一个字节不一定是帧头所以解析流程需要带上帧状态判断收到半帧数据时要暂存等待剩余部分。2.4 DHT11 单总线读取与常见失败原因温湿度模块一般选 DHT11单总线通信只需要一个 GPIO 口但时序要求严格。读取过程是先拉低总线 18ms 唤醒传感器然后释放总线等待传感器拉低响应再按位读取 40 位数据。课程设计里 DHT11 读取失败的案例多数不是时序算错而是 GPIO 模式切换遗漏读数据期间引脚必须配置为浮空输入发出起始信号时又得切回推挽输出。另一个高频错误是忘记接 4.7kΩ 上拉电阻或者外部上拉和 STM32 内部上拉同时工作导致电平识别异常。DHT11 的温湿度数据只在低速控制里有用比如湿度超过 80% 时强制提高风机转速不要把它的实时性要求和粉尘传感器混为一谈。3. 执行机构驱动与 PWM 调速风机、继电器与负离子模块的嵌入式控制3.1 执行机构选型与驱动方式对比净化器的执行层通常由三部分组成风机、负离子发生器或 UV 灯、以及指示灯或蜂鸣器。风机负责风道循环负离子发生器负责主动沉降颗粒物UV 灯负责杀菌。从 STM32 的控制角度看它们分别对应 PWM 输出和普通 GPIO 输出但驱动能力和安全要求差别很大执行器电气特性STM32 侧控制方式关键注意点直流无刷风机24V / 0.2~0.5ATIM PWM MOS 管或驱动芯片PWM 频率决定了是否产生啸叫交流离心风机220VGPIO 控制继电器 / 可控硅必须光电隔离软件上严禁误触发负离子发生器高压GPIO 控制继电器高压线远离传感器信号线UV 灯220VGPIO 控制继电器继电器线圈需要续流二极管课程设计大多选用直流无刷风机原因很简单用 N-MOS 管就能直接驱动STM32 输出 PWM 信号到 MOS 栅极风机两端接电源和 MOS 漏极即可。交流风机则需要额外处理过零检测、可控硅触发和市电隔离风险和工作量都不在同一个量级。做毕业设计想体现“设计与实现”深度可以在直流风机方案外加一个继电器控制的负离子模块安全风险低控制逻辑也容易说清楚。3.2 TIM PWM 参数计算如何确定风机频率与占空比PWM 调速的核心是三个寄存器参数预分频系数 PSC、自动重装载值 ARR 和比较值 CCR。以 STM32F103C8T6 为例系统时钟 72MHzTIM2 挂在 APB1 总线上要使能时钟后配置。下面是 20kHz 风机 PWM 的初始化代码// TIM2_CH1 输出 PWM频率 20kHz初始占空比 50% void Fan_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 71; // 72MHz / (711) 1MHz TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 49; // 1MHz / (491) 20kHz TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_Pulse 25; // 占空比 25/50 50% TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); }PSC 设 71 得到 1MHz 的计数时钟ARR 设 49 意味着计数器从 0 数到 49实际周期是 50 个计数单位所以 PWM 频率是 1MHz / 50 20kHz。Pulse 值决定占空比改起来只需要喂一个变量。修改占空比时用TIM_SetCompare1(TIM2, ccr)ccr 取值范围是 0 到 ARR即 0 到 49对应 0% 到 100%。这套参数组合不是唯一的关键是先确定目标频率再反推 PSC 和 ARR。不同场景下的常用 PWM 频率如下PWM 频率适用场景优缺点1kHz普通直流电机、舵机低端驱动兼容性好但可听到电流噪声20kHz直流无刷风机超过人耳听觉上限静音MOS 开关损耗略增100kHz 以上精密电机驱动、部分 DC-DC对栅极驱动要求高课程设计不建议我一般会把风机 PWM 直接设为 20kHz因为 1kHz 驱动下风机线圈会发出明显的滋滋声放进净化器外壳后会有共振听起来像产品故障。选 20kHz 时要注意 MOS 管栅极电阻不能太大否则开关边沿变缓MOS 会长时间工作在放大区导致发热。3.3 负离子与 UV 灯的继电器控制顺序负离子发生器和 UV 灯用继电器控制控制端就是一个 GPIO但开关顺序很有讲究。上电初始化阶段所有继电器输出引脚都要先置低确保系统启动瞬间负离子不工作。开机流程建议按下面顺序执行先启动风机 PWM延时 500ms 待风机稳定运转后再吸合负离子继电器关机时先断开负离子继电器再关闭风机。这个顺序的原因是负离子发生器工作时会产生高压电场如果风机没有转动高压离子会在净化器内部积聚干扰粉尘传感器的读数甚至通过电源线耦合进 MCU。做继电器控制时GPIO 输出端接三极管或达林顿管驱动继电器线圈线圈两端必须反向并联续流二极管否则继电器断开瞬间的感应电动势有可能打坏 STM32 引脚。4. 控制系统状态机自动模式切换、按键防抖与串口帧解析4.1 主循环 任务切分的状态机结构净化器的软件层不复杂但代码组织方式直接决定了后期排错难度。课程设计里最常见的坏味道是在 while(1) 里把传感器读取、OLED 刷新、按键检测全写成一坨。比较清爽的结构是把每个功能拆成独立的任务函数由一个固定周期的主循环按顺序调度int main(void) { SystemInit(); Sensor_Init(); // ADC、UART、DHT11 初始化 Fan_PWM_Init(); // TIM2 PWM 初始化 Relay_Init(); // 继电器 GPIO 初始化 Key_Init(); // 按键 GPIO 初始化 OLED_Init(); while (1) { Sensor_Update(); // 读取粉尘、温湿度更新时间戳 Auto_Control_Task(); // 根据模式计算目标动作 Fan_SetLevel(auto_level); Key_Scan_Task(); // 10ms 周期扫描按键 OLED_Show_Data(); delay_ms(10); // 整个循环周期约 10ms } }主循环的周期不是严格固定因为各任务函数执行时间不同但整体节奏基本稳定。Sensor_Update 里读取 PMS5003 或 GP2Y1010 的 ADC 值ADC 转换和帧接收是通过中断完成的任务函数只从缓冲区拿结果不在任务内部等待传感器响应。这样设计后按键扫描即使晚几毫秒也不影响净化器的核心功能传感器数据也不会因为显示刷新而被阻塞。4.2 自动模式阈值决策与防抖算法自动模式的档位决策看起来是“PM2.5 高了就提速”但直接拿瞬时值做判断会有两个问题传感器读数在风机高速运转时波动很大尤其是 GP2Y1010同时颗粒物浓度在阈值边界反复跳动时电机会频繁换挡听起来像抽风。常见做法是在决策前加一层滤波和防抖代码如下// 自动模式EMA 滤波 防抖切换 #define LEVEL_1_THRESHOLD 35 #define LEVEL_2_THRESHOLD 75 #define LEVEL_3_THRESHOLD 150 static uint16_t smooth_pm25 0; static uint8_t change_count 0; uint8_t current_level FAN_LEVEL_1; void Auto_Control_Task(void) { uint16_t raw Get_PM25(); // 从传感器缓冲区取原始浓度 uint8_t target_level; if (smooth_pm25 0) smooth_pm25 raw; // 首值直接进入滤波器 // 指数移动平均alpha 取 0.2越新数据权重越低 smooth_pm25 (uint16_t)(0.8f * smooth_pm25 0.2f * raw); if (smooth_pm25 LEVEL_1_THRESHOLD) target_level FAN_LEVEL_1; else if (smooth_pm25 LEVEL_2_THRESHOLD) target_level FAN_LEVEL_2; else if (smooth_pm25 LEVEL_3_THRESHOLD) target_level FAN_LEVEL_3; else target_level FAN_LEVEL_4; if (target_level ! current_level) { if (change_count 5) { current_level target_level; Fan_SetLevel(current_level); change_count 0; } } else { change_count 0; } }EMA 滤波器的作用是把瞬态尖峰平滑掉系数 0.2 表示新采样值对结果的贡献占两成更早的数据占八成系数越小曲线越平滑但响应越慢。change_count 是切换防抖连续 5 次判断保持同一目标档位才真正执行切换这能避免传感器值在阈值边缘抖动时引起风机频繁变速。阈值 35、75、150 是基于常见室内颗粒物浓度评估区间给出的经验值实际使用要根据应用场景调整有人吸烟的客厅建议把第二档阈值往下调出风口附近的传感器需要把采样位置避开风机直吹。4.3 按键扫描与长短按判定按键模块的常见需求是短按切换手动/自动模式自动模式下长按关机。课程设计的按键消抖代码往往写成“检测到按下就延时 10ms 再读一次”这种阻塞式消抖在带 OLED 和多传感器的系统里会让主循环卡顿。更合理的做法是 10ms 周期扫描将按键状态做成类似状态机的方式typedef struct { uint8_t current; // 当前 GPIO 电平 uint8_t prev; // 上一次确认电平 uint8_t press_cnt; // 按下持续计数 } Key_t; // 每 10ms 调用一次 void Key_Scan_Task(void) { key.current GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN); if (key.current ! key.prev) { key.press_cnt 0; key.prev key.current; return; } if (key.current 0) // 低电平有效 { key.press_cnt; if (key.press_cnt 50) // 500ms 判定为长按 { key.press_cnt 255; // 防止重复触发 System_PowerOff(); } } }如果当前读到的 GPIO 电平和上次确认值不一致说明电平正在变化大概率是抖动此时只复位计数。只有连续多次扫描都读到稳定低电平press_cnt 才会累计到位。按键抬起后可以通过 press_cnt 的范围来判断是短按还是长按计数小于 50 是短按等于 255 是长按已处理。这种非阻塞扫描不占用主循环时间10ms 周期下 50 次计数对应 500ms阈值可调。4.4 UART 帧接收状态机自定义协议与 Modbus 风格帧的移植点空气净化器经常需要向上位机发送实时数据课程设计里一般是接串口助手查看。直接按字节处理接收会面临“数据半包”“丢字节”的问题推荐用一个状态机维护接收流程。下面这段代码可以处理任意以帧头 长度 数据组成的自定义协议也是学习 modbus 单片机帧接收数据程序时最容易上手的框架typedef enum { FRAME_HEAD1 0, FRAME_HEAD2, FRAME_LEN, FRAME_DATA } FrameState; static FrameState rx_state FRAME_HEAD1; static uint8_t rx_buf[32]; static uint8_t rx_len 0; static uint8_t rx_index 0; // 串口中断里逐字节调用 void UART_Rx_Parse(uint8_t dat) { switch (rx_state) { case FRAME_HEAD1: if (dat 0xAA) rx_state FRAME_HEAD2; break; case FRAME_HEAD2: if (dat 0x55) rx_state FRAME_LEN; else rx_state FRAME_HEAD1; // 帧头错误回到初始 break; case FRAME_LEN: rx_len dat; rx_index 0; rx_state (rx_len 32) ? FRAME_DATA : FRAME_HEAD1; break; case FRAME_DATA: rx_buf[rx_index] dat; if (rx_index rx_len) { Process_Frame(rx_buf, rx_len); rx_state FRAME_HEAD1; } break; default: rx_state FRAME_HEAD1; break; } }这个状态机的核心好处是一帧数据可以分多次到达不会因为字节间隔被拆开而丢失帧头匹配失败时自动回到初始状态不会卡死在错误帧里。帧格式是AA 55 len datalen 表示数据区长度最大不超过 32 字节。收到完整帧后统一交给处理函数如解析出命令码和数据再决定是切换模式还是回复当前 PM2.5 值。Modbus 风格的帧接收原理与此一致区别只是在帧头、长度计算和 CRC16 校验上更严格把这个状态机改成 Modbus RTU 的从机接收时也只需要在收到完整帧后加一步 CRC 校验。5. 系统联调与标定传感器零点校准、干扰解耦与答辩验证方法5.1 粉尘传感器两点标定与数据记录GP2Y1010 这类模拟粉尘传感器的输出电压和颗粒物浓度呈近似线性关系直接用原始电压值显示会让人看不懂需要换算成浓度。标定分两步先测零点电压再测一个已知高浓度点最终得到一条直线关系浓度 (Vout - V0) / K其中 V0 是洁净环境下的输出电压K 是灵敏度系数。课程设计没有专业设备做多点标定常见的做法是在净化器无尘室内密闭运行 10 分钟后记录 V0然后点燃一小段烟头待 PM2.5 明显升高后用 PMS5003 作为参考仪器记录电压和浓度对应点。两点确定一条直线把 V0 和灵敏度系数写死在配置区。这样处理后的数据至少具备参考价值比直接显示 ADC 原始值要专业很多。注意断电重启后要让传感器预热 2 分钟再开始标定读数光路积灰会导致零点漂移长时间运行后需要重新标定。5.2 联调阶段反复出现的干扰问题故障现象可能原因排查与处理ADC 读数整体偏高且跳动GP2Y1010 和风机共用一个电源传感器电源独立或加 LC 滤波隔离负离子开启后 PM2.5 数值飙升高压电场干扰粉尘传感器调整传感器位置或打开负离子时暂停采样PWM 输出正常但风机不转MOS 管栅极缺少下拉电阻栅极对地接 10kΩ避免上电瞬间误导通继电器吸合但净化器不工作继电器触点容量不够或压降过大测量触点两端电压更换更大容量继电器STM32 上电后程序偶尔跑飞外部晶振不起振或负载电容不匹配检查两个 20pF 负载电容确认晶振焊接粉尘传感器采样最容易踩的坑是电源耦合。风机是感性负载启动电流是稳态的好几倍如果传感器和风机共用同一条 5V 供电线PWM 导通瞬间的电流变化会直接反映在 ADC 采样值上。我给这类系统的建议是风机电源单独一路传感器电源用 LC 滤波或至少串一个磁珠ADC 采样引脚加 100nF 电容到地。5.3 用一组实验数据证明系统有效答辩时讲“我的系统能自动调速”不如放一组真实实验数据。一个容易复现的验证方法在密闭房间内关闭门窗点燃一小段烟头让 PM2.5 浓度冲高到 200μg/m³ 以上等浓度稳定后开启净化器自动模式每 10 秒记录一次显示浓度和多档风速切换时间点。记录 5 分钟内 PM2.5 下降曲线和档位切换顺序整理成表格时间(s)PM2.5(μg/m³)风机档位事件02104档启动自动模式301624档持续高速运行601204档浓度低于150后可降档但防抖中90883档连续5次低于150降为3档120642档连续5次低于75降为2档这组数据能同时证明三件事传感器采集稳定、档位切换逻辑按预设阈值执行、系统确实把颗粒物浓度降了下来。串口数据导出后可以直接用串口助手或 python 脚本绘图曲线图放进答辩 PPT 的可信度远高于“系统运行正常”一句话。如果还想往纵深扩展下一步可以把这套逻辑移植到 freeRTOS 上做任务调度或者接入 Wi-Fi 模块上报数据。嵌入式开发的价值在于先把底层数据调准再谈上层的控制策略。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门