
简介基于STM32F103的完整空气净化器项目集成Altium Designer 09原理图、PCB布局与配套软件源码面向嵌入式初学者、电子竞赛选手及产品开发者提供从硬件设计到程序实现的全过程参考。压缩包共160个文件约4.61MB主要包含C语言与头文件源码、Keil工程配置、Altium Designer原理图与PCB文件、元件库以及hex、axf等编译输出文件便于直接查看与烧录调试。已有909人学习下载。工程围绕空气净化器实际需求展开硬件部分覆盖ADC采集空气质量传感器与温湿度数据、电源稳压滤波、电机与风扇控制、UART/I2C通信接口、过流过热保护电路以及双面PCB布局优化软件部分包含了外设驱动编写、传感器数据处理算法、自动与手动模式切换逻辑、用户交互界面及异常保护机制。通过学习该项目可掌握STM32外设驱动、模数转换、PWM调速、嵌入式C编程等关键技能也可作为课程设计或产品初型的参考蓝本。1. 基于STM32F103的空气净化器这个压缩包里到底有什么可用的东西把空气净化器拆成电路最核心的不是风机而是“粉尘浓度高不高、风量该多大”这一条闭环。基于STM32F103空气净化器AD设计硬件原理图PCB软件程序源码.zip 这个名字里其实已经说清了项目包的组成Altium Designer 绘制的原理图、PCB 文件以及能直接打开编译的 STM32F103 固件源码。对正在做毕业设计或想快速起一个家电控制板的人来说它的价值不是某一个电路单元而是从传感器采样、电机 PWM 调速到 PCB 布局的完整链路。很多人拿到这包资料先看板子能不能跑我的建议是先看最小系统和电源因为空气净化器这种负载变化大的板子最容易在电源和地线上翻车。后面会按“原理图 → PCB → 源码 → 验证”的顺序把每一段该关注的参数和能落地的操作讲清楚。2. 用AD把空气净化器原理图拆开画最小系统、传感器、电机驱动2.1 STM32F103最小系统晶振、复位、BOOT和SWD这四件事绕不开打开原理图先找 STM32F103 最小系统。市面上的 STM32F103C8T6 最小系统板原理图看起来都差不多但用在空气净化器这种带电机和继电器的板子上必须额外注意电源和复位否则后面每到电机启动就复位一次。最小系统要包含这些部分主电源VDD、VDDA、VSSA、VBAT 都要接到对应网络。VDDA 必须单独用 1μF 和 0.1μF 电容并联去耦模拟电源与数字电源在引脚附近单点连接ADC 采样抖动会明显变小。8MHz 晶振接 PD0/PD1OSC_IN/OSC_OUT两个 20pF 负载电容到地晶振两端并联 1MΩ 电阻。晶振要靠近 MCU走线底下不要穿过其他数字信号。复位电路NRST 接 10kΩ 上拉到 3.3V再接 100nF 电容到地形成低电平复位。BOOT 设置BOOT0 和 BOOT1 各自通过 10kΩ 下拉到地。BOOT0 拉到 GND 时从主 Flash 启动这样才能正常跑用户程序。SWD 下载口PA13/PA14 引出 SWDIO/SWCLK如果空间允许再留出 NRST 和 GND四线 SWD 足够调试。引脚分配可以直接对照下面的表格最小系统单元器件/引脚典型参数作用主晶振PD0/PD18MHz 晶振20pF×2 电容提供系统时钟源RTC 晶振PC14/PC1532.768kHz12.5pF×2可选用作低功耗时钟复位NRST10kΩ 上拉 100nF 到地低电平复位BOOT0BOOT010kΩ 下拉选择 Flash 启动BOOT1PB210kΩ 下拉配合 BOOT0 设置模式SWDPA13/PA14直连下载器SWDIO/SWCLK有些原理图会把 VREF 通过磁珠或 0Ω 电阻接到 VDDA再在 VREF 引脚放一个 0.1μF 电容这可以进一步降低 ADC 基准噪声。对粉尘传感器这种模拟信号采集来说两个去耦电容的位置比芯片摆放更关键它们要尽量靠近 VREF 或 VDDA 引脚不能隔着过孔很远跑去打孔。2.2 传感器与执行器粉尘浓度ADC采样、PWM调速、NMOS驱动空气净化器至少要有三路外设粉尘传感器、温湿度传感器、风机电机。常见做法是选 Sharp GP2Y1010AU0F 作为 PM2.5 检测模块它输出模拟电压和 STM32F103 内部 ADC 很好配合。这个传感器有 6 个引脚其中 LED 驱动需要 MCU 输出一个脉冲信号典型驱动波形是周期 10ms、脉宽 0.32ms输出采样点在 LED 脉冲开始后约 0.28ms 处。因为脉冲时序固定所以用定时器输出比较或者普通 GPIO 延时都行只要保证 10ms 周期稳定。DHT11 用于检测温湿度单总线协议数据脚需要一个 4.7kΩ 上拉到 3.3V。它的时序比较吃延时精度建议在代码里用微秒级延时函数标准库里可以用Delay_us()实现尽量不要用HAL_Delay()这种毫秒级函数去做 DHT11 的 bit 读取。电机驱动按风机的类别来区分如果是小功率有刷直流风机或普通风扇直接用逻辑电平 NMOS 比较便宜AO3400、IRLZ44N 都可以。电路如下MCU_PWM -- 100Ω电阻 -- NMOS栅极 -- 10kΩ下拉到地 电机 --- 电源12V/5V 电机- --- NMOS漏极 NMOS源极 --- GND 电机两端并联1N5819或SS34二极管方向接在电机到电机-栅极串电阻是为了限制驱动电流10kΩ 下拉保证上电瞬间不误导通。续流二极管要靠近电机端子走线尽量短否则电机反电动势会把 PWM 波形打花。如果风机是无刷的项目里通常会用驱动板MCU 只需要输出 PWM 调速信号和方向信号这时注意共地和电平匹配最好不要直接把驱动板的高压电源和 MCU 电源连在一起。外设引脚可以这样安排外设信号类型STM32F103 引脚说明GP2Y1010 LED数字输出PB0输出 10ms 周期脉冲GP2Y1010 模拟量ADC 输入PA0 (ADC1_IN0)0~3.3V 电压DHT11 数据单总线PB54.7kΩ 上拉风机 PWMPWM 输出PA7 (TIM1_CH2)需要重映射时确认 AFIO蜂鸣器GPIO/PWMPB8无源蜂鸣器用定时器驱动2.3 AD原理图绘制与检查从建库到编译ERC重点看网络是否真连通Altium Designer 里画这种控制板流程是“建工程 → 画原理图 → 编译检查 → 导出网表 → 画 PCB”。原理图阶段不需要把每一个元件都建得和实物照片一样重点是封装、引脚编号和网络标号对得上。AD 自带的 Miscellaneous Devices.IntLib 里可以找到电容、电阻、二极管、按键等常见基本元件STM32F103C8T6 可能需要自己新建原理图库或者去 AD 官网的厂商库下载。这里建议把库文件单独保存不要直接放在临时工程里。常用快捷键可以先记住这几个操作快捷键说明放置元件PD打开元件库选择窗口放置网络标号PN在不同页之间连线时用放置电源符号PO选 VCC/VDD/GND连线CtrlW快速走线编译工程右键工程名选择 Compile PCB Project画完原理图最关键的一步是检查有没有连上不能只肉眼看线。在 AD20 中右键工程名选择 Compile PCB Project编译后打开 Messages 面板重点看Floating Net、Net Has No Driving Source这类错误。如果某一根线看起来连接了但节点上没有显示小圆点多数情况是引脚没有和线接触成功。也可以是检查引脚名与网络标签是否一致比如 STM32 的 PA9 要接到 USB 头的 D如果标成 PA10 就会在 DRC 阶段找不到连接。在导出 PDF 或生成 PCB 之前先导出一份 BOM 表来核对料号。CSV 文件的内容类似这样Designator,Comment,Footprint,Quantity C1,100nF/50V,0603,10 C2,10uF/16V,0805,4 C3,22pF/50V,0603,2 R1,10kΩ/0.1W,0603,6 U1,STM32F103C8T6,LQFP48,1 U2,AMS1117-3.3,SOT223,1 Q1,AO3400,SOT23,1 D2,SS34,SMA,1 J1,DC-002,DC-002,1 J2,SWD-4,Header4,1这里的 Quantity 表示同封装类型数量后续在 AD 的 BOM 编辑器里可以合并同类项。核对 BOM 时尤其要注意封装里电阻电容的 Footprint 是否与实物尺寸一致板子打样后才发现 0603 买成 0805焊接会很痛苦。导出 BOM 后把原理图重新编译一遍确定无错误再进入 PCB 设计。3. 从原理图到PCB层叠设计、布局分区和敷铜走线的关键参数3.1 双层板层叠与走线宽度选择空气净化器为什么用两层就够STM32F103 的工作主频 72MHz空气净化器里的信号主要是极低频的传感器模拟量、PWM 和 UART双层板完全够用。常见做法是整板使用双层板顶层走信号和部分电源底层尽量保留完整地平面。底层被地平面对的越多ADC 参考电压越稳定粉尘传感器读数越不容易跳变。PCB 走线要求里线宽要跟着电流和电压降走。12V 到 LM2596 的输入线、电机供电线至少要 1mm 甚至更宽零散 0.5mm 的细线送 1A 电流会发热。常规规则表可以参考这个网络类别最小线宽推荐线宽原因地线/GND0.5mm1.0mm 或敷铜降低地电位差电源线 (5V/3.3V)0.3mm0.5mm~0.8mm压降与去耦模拟信号 (粉尘输出)0.2mm0.3mm短线、少过孔电机驱动线0.5mm1.0mm 以上大电流脉冲晶振走线0.2mm0.2mm短而直两侧包地底层敷铜建议设置成网状地或者全铜铺铜之后再用 GND 过孔把顶层和底层缝合在一起。模拟地和数字地在原理图上是同一个 GND在 PCB 上一般不用分割而是把高电流回路和低电流回路在地平面上的路径分开让电机回流电流不要经过 MCU 的 GND 过孔附近。如果项目里用了继电器继电器线圈的驱动部分要单独拉一条回地不要和 MCU 的 GND 共用同一段窄铜皮。3.2 布局分区强电弱电分开、继电器远离晶振、传感器放风道侧布局决定了后面布线顺不顺利。空气净化器常见的拓扑是电源入口在左侧风机和传感器靠近进风口。按照功能分区块主板可以分成电源区、主控区、传感器区和执行器区顺序尽量直线排列避免输入输出线相交。电源入口放共模电感、保险丝、滤波电容然后是 DC-DC 或 LDO主控区放 STM32F103 最小系统、晶振、复位、SWD 口传感器区放 GP2Y1010 和 DHT11其中 GP2Y1010 本身是一个光学模块输出引脚要尽量靠近 MCU 的 PA0避免模拟信号走长线执行器区放电机驱动 NMOS、续流二极管、继电器和风扇端子。晶振要远离电机驱动线和继电器因为继电器线圈断电瞬间会产生反电动势即使有续流二极管也可能在天线上耦合出噪声。NMOS 的栅极驱动线应从 MCU 短直引出不在晶振下方路过。GP2Y1010 的采样线也不要和 PWM 线平行长距离走必要时在模拟走线两侧放 GND 过孔做隔离。如果 PCB 尺寸允许在电机端子、继电器触点等高压部分下面用 AD20 的 Polygon Pour Cutout 把铜皮挖空防止爬电距离不够。放置方式是在 Place 菜单下选 Polygon Pour Cutout在禁止铺铜区域画一个包围线圈然后 Tools→Polygons→Repour All 重新灌铜挖空的地方就不会有铜箔。这样可以留出绝缘槽尤其是 220V 继电器触点之间至少按 2mm 爬电距离设计。3.3 布线规则与DRC线宽、间距、过孔、敷铜和钻孔文件布线前先在 AD 里配置 Design Rules不要等画完再检查。常见规则项包括间距Clearance、线宽Width、过孔尺寸Vias、铜皮到板边距离Board Outline。例如设置信号间距 6mil电源间距 12mil电源线宽最小 20milGND 线宽最小 30mil。如果板厂工艺是 0.3mm 最小线宽就不能在规则里写 0.2mm 的线否则打样出来可能断线。设置好规则后布线最后执行 DRC 检查的快捷键是 TD或者菜单 Tools→Design Rule Check。检查结果里常见的错误有Silk To Solder Mask、Hole Size Constraint、Un-Routed Net Constraint。新手最容易忽略的是 Un-Routed Net看起来两个焊盘之间有飞线但其实网络没有连通这种必须清零。敷铜时不要每次都默认全板铺铜先看底层是否需要分割。如果板子里有 12V、5V、GND 三种电源网络底层整片铺 GND 再把 12V 和 5V 布成粗线会比“三层电源铜”更容易控制回流路径。AD20 的敷铜设置可以在 Properties 面板里把 Connect To 改为 GNDNet Options 选 Pour Over Same Net。AD 导出的钻孔文件名称通常是xxx.drl如果使用 Gerber 输出会看到xxx.TXT或xxx-1-1.drl这是独立于xxx.GTL、xxx.GBL的孔位文件。发给板厂时要把所有.gbr/.drl/.txt一起打包不能只发 PCB 文件。有些板厂可以直接上传 AD 工程但这里建议还是导出 Gerber 检查一遍因为这样可以提前发现钻孔层缺失。4. 看懂并修改源码ADC采集、PWM控制和档位切换逻辑4.1 源码结构标准库 V3.5 怎么从 main 函数一路追到外设市面上很多 STM32F103 项目源码是基于标准外设库 V3.5 写的因为 Keil 里选择 STM32F10x 标准库的工程模板最容易找。拿到源码后不需要每个文件都看先看工程目录下有没有FWLIB或Libraries目录里面是标准库的 C 文件再看User目录下的main.c、stm32f10x_it.c。main.c里的SystemStm32f10x.c通过SystemInit()完成时钟初始化默认会配置成 72MHz。定位主循环时记住一个方法先搜索while (1)再找TIM_SetCompare和ADC_SoftwareStartConvCmd这几个标志性函数。空气净化器的控制逻辑很简单主循环大致是读一次 ADC、换算 PM2.5、通过 PID 或分段策略改 PWM 占空比、串口打印一轮调试信息。代码看起来像这样#include stm32f10x.h #include delay.h #include adc.h #include pwm.h #include dht11.h #include usart1.h int main(void) { SystemInit(); Delay_Init(); GPIO_Config(); TIM2_PWM_Init(999, 71); // 72MHz / (711) / (9991) 1kHz ADC1_Init(); USART1_Init(115200); DHT11_Init(); while (1) { uint16_t adcVal ADC1_GetAverage(8); uint16_t density ADC_to_PM25(adcVal); uint16_t duty Control_Density_To_Duty(density); TIM_SetCompare1(TIM2, duty); uint16_t temperatura, humi; DHT11_ReadData(temperatura, humi); printf(adc:%d pm25:%d duty:%d temp:%d hum:%d\r\n, adcVal, density, duty, temperatura, humi); Delay_Ms(500); } }这里TIM2_PWM_Init(999, 71)的参数含义是定时器预分频 71自动重装载值 999。APB1 的时钟进入定时器后先被 72 分频711得到 1MHz 计数频率再计 1000 个周期最终 PWM 频率是 1kHz。PWM 频率不能选太低太低电机噪声大还会让电流波动也不能选太高太高 NMOS 开关损耗会上去24kHz 以上还可能让后级驱动板响应不过来。4.2 粉尘传感器ADC读取多通道采样、数字滤波和电压转浓度GP2Y1010 输出电压线性对应粉尘浓度但它的瞬时值波动很大同一个浓度下相邻两次采样可能差 50mV 以上。所以代码里不能直接拿单次转换值去算浓度先做软件滤波。常见做法是连续转换 8 次取平均值再把平均值换算成电压和 PM2.5。ADC 初始化和平均值采样可以这样写void ADC1_Init(void) { ADC_InitTypeDef adc; GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 12MHz ADC时钟 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 模拟输入 GPIO_Init(GPIOA, GPIO_InitStructure); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); } uint16_t ADC1_GetAverage(uint8_t times) { uint32_t sum 0; uint8_t i; for (i 0; i times; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); sum ADC_GetConversionValue(ADC1); } return (uint16_t)(sum / times); }ADC_SampleTime_55Cycles5是采样周期12MHz ADC 时钟下大约 4.6μs 一次足够 GP2Y1010 输出端的小电容充电稳定。换算 PM2.5 时需要知道参考电压默认 VDDA 是 3.3V12 位 ADC 满量程 4095。GP2Y1010 在干净空气中的输出电压约 0.5V以后每升高 0.1V浓度大致增加 100μg/m³这个关系式只适合粗略标定float ADC_to_PM25(uint16_t adc) { float volt (float)adc * 3.3f / 4095.0f; if (volt 0.5f) { return 0.0f; } return (volt - 0.5f) * 500.0f; }这里的系数 500 是经验值正式产品要在风机出风口和使用环境里做两点标定把 0μg/m³ 的零点电压和标称烟尘浓度下的电压代入线性公式求解斜率。4.3 PWM驱动电机和档位策略绑定变速、软启动和手动保留PWM 初始化代码需要使能定时器时钟和复用 GPIO 时钟。PA7 是 TIM1_CH2也可以重映射到 PB0 做 TIM3_CH3源码里用哪一组就要改对应的 GPIO 配置。下面是标准库下用 TIM2_CH1 在 PA0 输出的反例很容易混实际上 PA0 是 ADC 通道不能同时做 PWM。因此一定要确认好引脚分配在代码里写清注释void TIM2_PWM_Init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_1; // PA1 作为 TIM2_CH2 gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); tim.TIM_Prescaler psc; tim.TIM_CounterMode TIM_CounterMode_Up; tim.TIM_Period arr; tim.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tim); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse 0; oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC2Init(TIM2, oc); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); }注释里写的是 PA1/TIM2_CH2具体与原理图引脚保持一致。初始化之后TIM_SetCompare2(TIM2, duty)就能更新占空比。占空比值范围是 0~arr也就是 0~999。档位策略最简单的做法是分段线性映射PM2.5 浓度目标占空比运行状态0~5010%静音低速50~15040%自动中速150~30070%高速净化300100%全速强净为了防止电机瞬间全速启动拉低电源电压每档切换时在代码里循环递增占空比步进 5延时 20ms实现软启动。如果是无刷风机驱动板软启动还要在硬件上确认是否有缓启动电容代码侧可以继续保留。4.4 串口打印与状态显示别让延时函数卡住控制时序空气净化器运行中需要把 ADC、PPM2.5、湿度、温度打出来方便调试。调试串口一般用 USART1引脚 PA9/PA10如果项目里 USART1 被复用去接串口屏或者需要多路串口通信可以用 USART3引脚在 PB10/PB11。要注意串口 1 和串口 3 的时钟源不同使能 RCC_APB2Periph_USART1 与 RCC_APB1Periph_USART3重映射时还要开 AFIO 时钟这个差异容易让代码卡在发送等待。使用printf()之前先做 fputc 重定向代码里要包含stdio.h并在fputc函数内往串口写数据。打印频率不能太高建议 500ms 打印一次。DHT11 读取本身有最大 5ms 的阻塞延时如果放在主循环里不影响 PWM 输出因为 PWM 由定时器硬件持续产生不会因为 MCU 卡在延时里而停。 这里要强调这个好处新人常有个误解以为 PWM 由软件持续刷新其实配置好以后只要不关闭定时器输出就会一直保持。5. 打样回来后的板级验证上电顺序、串口联调和粉尘传感器标定技巧5.1 先量短路再上电重点测电源与地之间阻抗板子打样回来不能直接插电。先用万用表二极管挡测 12V 与 GND、5V 与 GND、3.3V 与 GND 之间是否有短路正常读数会有一定电容充电过程不会直接响。如果直接蜂鸣先用放大镜看有没有焊锡桥连尤其是 STM32F103 的 LQFP48 引脚间距很小第四脚到第五脚的桥连很常见。上电后用手摸一下 LDO如果温度快速上升说明后级有过流立刻断电。5.2 串口打印与PWM波形用示波器看两处关键信号串口连接 ST-Link 的虚拟串口打开 115200 波特率正常能看到adc:xxx pm25:xxx duty:xxx的循环打印。如果打印显示adc值一直在 4095 附近说明 PA0 的电压可能超过量程或者引脚没有配置成模拟输入。如果 adc 值在某个数上下剧烈跳动用示波器测 PA0 引脚确认是外部信号噪声还是 MCU 基准不稳。再用探头测 NMOS 栅极波形占空比应该随串口里的 density 值变化并且上升沿干净、没有反复振荡。如果示波器看到 PWM 在切换档位瞬间出现一个小凹坑那是占空比更新时机造成的可以改用定时器预装载功能确保TIM_SetCompare在下一个周期边缘才生效而不是立即写入。5.3 粉尘传感器的标定零点和标准浓度两点校正传感器出厂的电压-浓度关系一致性并不好简单用虎钳或卫生纸来标定非常不准确。常见做法是把设备放到密封箱里先通纯净空气测零点电压再点一根香烟或用标准尘埃源做高浓度点记录对应电压。用两个点算出斜率和截距写回源码。之后再用真实净化器在工作条件下测试确认读数随环境变化趋势正确。如果标定后发现读数整体偏高先检查 GP2Y1010 的 LED 脉冲时序是否正确采样点是否真的落在 LED 点亮后的 0.28ms 附近。这个时序误差会导致输出电压偏大或偏小不是加一个比例系数能简单修回来的。把标定后的两个系数以 float 类型存在 STM32F103 内部 Flash 的某个页里上电时读取并覆盖固定系数以后重新标定就不需要改源码重新烧录了。本文还有配套的精品资源点击获取