简介:本资源是一套基于STM32F103C8T6的嵌入式智能温控风扇完整工程,面向单片机初学者与嵌入式硬件实践者,解决温度感知、实时显示、电机调速与人机交互等典型控制问题,适用于课程设计、电子竞赛及毕业设计等场景。压缩包含210个文件,总大小757KB,涵盖34个C源文件(含外设驱动如usart、adc、tim、rcc等)、37个头文件(h)、38个编译中间文件(o/d),以及Keil工程配置(uvprojx/uvoptx)、烧录镜像(hex)、链接脚本(sct)和批处理工具(keilkill.bat)等,结构规范,便于理解STM32标准外设库开发流程。已有8861人学习下载,配套B站演示视频直观展示OLED界面、DS18B20测温响应、L298N驱动风扇启停与PWM调速效果。读者可直接部署运行,深入掌握传感器采集、多任务逻辑调度、硬件驱动整合及低功耗温控策略实现。 做了不少单片机项目之后,我发现最能串起传感器采集、功率控制和交互逻辑的练手题目,就是智能温控风扇。它看起来简单——无非是测个温度然后控制风扇转不转、转多快。但真做下来,从传感器选型到驱动电路、从PWM频率选择到控制策略,每一步都有值得深挖的细节。
这个项目特别适合两类人:一类是刚学完STM32基础外设(GPIO、定时器、ADC、串口)想做一个完整小项目的同学,另一类是手头有闲置风扇想改造成温控版本的电子爱好者。做完之后你手里就多了一个能根据环境温度自动调节风速的小设备,放在放大器散热、机箱辅助散热、寝室床头都很实用。
本文从方案选型、电路设计、代码实现到调试踩坑,完整记录我搭建这台基于STM32的智能温控风扇的全过程,尽量把能直接抄作业的细节都写出来。
1. 项目概览与整体方案设计
1.1 这个项目解决了什么问题
传统的机械风扇只有固定档位,要么开要么关,晚上睡觉时温度降下来了还得爬起来关,温度升高了又热醒。智能温控风扇要解决的核心问题,就是让风扇根据环境温度自动调节转速:温度低时低速运转或者完全停止,温度升高时平滑提速,让风量始终匹配散热需求。
这个项目表面上是做一台风扇,实际上是在训练一套完整的传感-决策-执行链路。我在这个项目里投入最多时间的其实不是风扇本身,而是控制策略的设计:温度阈值怎么设才不至于让风扇在临界点附近频繁开关,转速变化怎么过渡才不会让人觉得突兀,温度采样怎么滤波才能稳定。
从学习角度看,这个项目覆盖了STM32的四个核心外设:GPIO和外部中断处理按键输入,定时器输出PWM控制转速,单总线或ADC读取温度传感器,I2C驱动OLED显示屏,再加上UART串口打印调试信息。一套代码把F103系列的主力外设基本用遍了,比单独跑摄像头、跑屏幕这些模块要扎实得多。
1.2 硬件选型与搭配理由
这里先说结论,我最终选型如下:
| 模块 | 型号 | 选型理由 |
|---|---|---|
| 主控 | STM32F103C8T6 | 价格便宜、资料最多,板子损坏成本低 |
| 温度传感器 | DS18B20 | 数字输出无需校准,单总线省引脚 |
| 风扇 | 12V 3线电脑散热风扇 | 风量大、自带测速线、易获取 |
| 电机驱动 | AO3400 N-MOS管 | 导通电阻小,PWM开关损耗低 |
| 显示屏 | 0.96寸 OLED SSD1306 | I2C接口只需两根线,显示效果好 |
| 供电 | 12V电源适配器 + AMS1117-3.3 | 直接驱动风扇,同时稳压给MCU |
STM32F103C8T6这颗芯片就不多说了,Cortex-M3内核72MHz主频,20KB RAM,64KB Flash,做这类轻量级控制绰绰有余。选它的另一个原因是生态成熟,出问题一搜就有答案,新手友好度极高。
DS18B20是个很有性格的传感器,数字输出免校准,精度正负0.5度,而且每个芯片都有一个唯一的64位序列号,总线理论上可以挂多个传感器。但它的单总线时序要求比较严格,微秒级的延时控制做不好就会读不到数据,这也是很多人卡住的地方。后面我会详细讲这里怎么处理。
风扇我强烈建议用三线的电脑散热风扇,比两线的多一根测速线。两线只能控制转不转、快不快,没有反馈;三线风扇的第三根线会输出与转速相关的脉冲信号,接一个定时器输入捕获就能实时测量风扇实际转速,这样后续可以升级成闭环转速控制,可玩性高很多。
1.3 系统架构与信号流转路径
整个系统的数据流我画在脑子里是这样的:DS18B20把温度通过单总线送给STM32,STM32运行控制算法算出一个0到100的转速百分比,然后把这个百分比映射成定时器比较寄存器的值,通过PWM输出控制MOS管的导通时间,从而控制风扇两端平均电压实现调速。与此同时,OLED实时显示当前温度和转速,按键用来调节目标温度,串口把你的调试数据丢给电脑端监控。
这个架构最核心的设计决策是"控制与执行分离":MCU只输出逻辑控制信号,功率通路由MOS管负责,这样即使驱动部分烧坏,也不会殃及主控芯片。另外,温度采集、控制计算、显示刷新这三个任务在软件上是分模块写的,各自维护独立的数据缓存,主循环里顺序调度,结构非常清晰。
2. 电路搭建与硬件调试细节
2.1 供电系统设计:从12V到每一路电压
供电是整个项目的安全线,最先设计。12V风扇由12V电源直接驱动,功耗大概在2-3W,电流200mA出头。STM32F103的供电需要3.3V,AMS1117-3.3做一个低压差稳压,12V输入对它来说压差较大,所以我在AMS1117前面串了一个10欧电阻限流,再配一个100uF电解电容和一个100nF陶瓷电容做输入滤波。这么做是因为AMS1117在较大压差下工作时功耗等于压差乘以电流,虽然MCU功耗很低,但稳妥一点总没坏处。
这里有个容易忽略的点:如果要用5V给OLED或者其它模块供电,建议用MP1584这种降压模块而不是7805。7805在12V输入时压差大、发热厉害,而且接近12V输入的时候线性稳压器功耗会很难看。ST-LINK有时也会通过调试接口给板子供3.3V,如果同时接了外部电源,注意两个电源的GND要共地,否则通信或者烧录会有各种诡异问题。
2.2 温度采集电路:DS18B20的上拉电阻是关键
DS18B20虽然叫"单总线",但它在数据线上的上拉电阻上很挑剔。数据引脚DQ需要接一个4.7K(也有说2.2K-10K都行)的上拉电阻到3.3V。如果没有这个上拉电阻,总线默认电平就是一个不确定状态,时序根本建立不起来。这个问题最坑的点在于:有时候某些开发板上已经板载了这个上拉电阻,你直接插上能工作,结果自己搭最小系统时怎么都读不到,查了半天怀疑是代码问题,其实就是一个上拉电阻的事。
DS18B20的供电方式有两种:外部供电和寄生供电。寄生供电只需要两根线(DQ和GND),但使用寄生模式时,传感器在进行温度转换时需要从数据线上"偷"电流,如果总线驱动能力不够,转换会失败。我在项目里统一采用外部供电:VCC接3.3V,GND接地,只有DQ接数据线,虽然多了一根线,但稳定性和容错性都大幅提升。
实际接线时注意把DS18B20的数据线尽量短地引出,如果导线超过20厘米就有必要用屏蔽线或者双绞线,并且数据线不要跟PWM驱动线、电源线捆绑走在一起,否则PWM切换瞬间的电流跳变很容易耦合到传感器数据线上。
2.3 风扇驱动电路:MOS管比三极管可靠得多
风扇驱动我一开始用的是S8050三极管,实测能转,但有两个问题:一是压降大,12V电源在风扇上实际只有10V多,转速上不去;二是三极管在PWM开关切换时开关损耗大,发热明显。后来换成AO3400这颗N沟道MOS管之后,效果立竿见影——导通内阻只有几十毫欧,基本不发热,开关速度也快得多。
电路接法:MOS管的栅极串一个100欧电阻接STM32的PA0引脚,栅极对地再接一个10K下拉电阻,漏极接风扇的负极,源极接地,风扇正极接12V。续流二极管是必须的,用1N5819肖特基二极管跨接在风扇两端,阴极接12V,阳极接风扇负极,作用是给风扇在PWM关断瞬间产生的反向电动势一个泄放回路。没有这颗二极管,MOS管很容易被尖峰电压击穿,而且系统重启的概率极高。
说到PWM频率,这是一个在调试时非常容易踩坑的参数。低频PWM(比如1KHz)控制风扇时,人耳能听到明显的电磁啸叫声,非常烦人。我把频率定在20KHz以上,人耳基本分辨不出来了。但要注意频率也不是越高越好,如果MOS管栅极驱动电阻过大、开关速度跟不上,高频率下反而会增大开关损耗,所以20-25KHz是一个比较合理的区间。关于这个选择的详细计算,可以看第3节。
2.4 人机交互部分:OLED与按键的正确接法
OLED用SSD1306驱动芯片,市场上有I2C和SPI两种版本,我选的是I2C接口版本,只接SCL、SDA、VCC、GND四个引脚。STM32F103的I2C1默认引脚是PB6(SCL)和PB7(SDA),如果只是驱动OLED,用硬件I2C即可;但如果OLED初始化失败率高,我建议直接用软件模拟I2C(就是GPIO口手动翻转),稳定性反而更好,代码也不复杂。OLED的地址一般是0x3C,也有少数是0x3D,初始化失败时先确认一下地址。
按键我认为用普通轻触开关就够了,关键是消抖处理。硬件上,按键一端接地,另一端接MCU引脚,MCU侧启用内部上拉电阻,这样按键按下时引脚变成低电平。软件上,检测到电平变化后延时20ms再确认一次,如果状态一致才算有效按键,这就是经典的"软件消抖"方案。我踩过的一个坑是按键引脚选到了PA0,而PA0正好是TIM2的PWM输出通道1引脚,结果一按按键PWM输出就被干扰,后来换了PB0和PB1才解决,所以引脚分配一定要提前规划好。
3. 软件实现与控制逻辑解析
3.1 工程搭建与微秒延时的坑
我用STM32CubeMX生成HAL库工程,型号选STM32F103C8T6,时钟配置为72MHz。需要启用的外设包括:PB0/PB1按键输入、PA0定时器PWM输出、PB0单总线读取DS18B20(这里注意按键和传感器引脚要错开)、I2C1驱动OLED,以及UART1串口打印。
这里要重点说一个几乎所有新手都会碰到的问题:HAL_Delay只能毫秒级延时,而DS18B20的时序要求微秒级延时。比如写时序里要求一个写"0"的时间片总共要有60-120us,读时序要求在15us内采样数据线电平,这些用HAL_Delay根本做不到。
解决办法有两个:一是用定时器做微秒级延时函数,比如用TIM2挂一个系统时钟,写一个delay_us函数查计数器差值等待;二是用DWT(Data Watchpoint and Trace)模块实现微秒延时。DWT是Cortex-M3内核自带的调试组件,有一个CYCCNT计数器,每个时钟周期加1,只要使能DWT->CTRL相应位,读CYCCNT的差值就能知道过去了多少个周期,换算后就是微秒。代码很简单,用起来比定时器翻转还快。
void DWT_Delay_us(uint32_t us) { DWT->CYCCNT = 0; uint32_t target = us * (SystemCoreClock / 1000000); DWT->CYCCNT = 0; while(DWT->CYCCNT < target); }必须在主程序开头做一次初始化:
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;这里有个容易出错的地方:SystemCoreClock只有在系统时钟正确配置后才等于72MHz,如果你的时钟配置没生效,目标值算错,延时就会全部错乱,DS18B20当然就读不到数据了。所以我建议调试时先用串口打印SystemCoreClock确认一下。
3.2 温度数据读取与软件滤波
DS18B20的读取流程分三步:复位脉冲、发送ROM命令、执行功能命令。温度转换的命令序列是:复位 -> 跳过ROM(0xCC)-> 启动转换(0x44),然后等待750ms(12位分辨率下典型转换时间);读取温度的序列是:复位 -> 跳过ROM -> 读取暂存器(0xBE),然后连续读两个字节,合成16位温度数据。
温度数据格式是补码:高字节的高5位是符号位,低12位是温度数据,LSB代表0.0625度。负温度时符号位全1,处理时需要判断正负然后取反加一。做风扇控温项目一般室温不会低于零度,但如果你的设备可能在冬天室外使用,这个边界要处理。
原始读取值抖动比较大,我做了两层滤波:第一层是滑动平均——维护一个长度为8的环形缓冲区,每次读取新温度后计算平均值;第二层是一阶低通滤波——new_filtered = 0.7 * old_filtered + 0.3 * new_raw,让显示数值变化平滑。两层叠加后温度显示非常稳定,不会一闪一闪的。
另一个容易踩的坑是转换间隔:DS18B20在正常供电模式下,每次温度转换需要约750ms,如果你在主循环里每次都发转换命令然后等待750ms再读,整个系统等于被卡死了。更好的做法是:主循环里发启动转换命令后先不等待,直接去刷OLED、处理按键,下一次循环经过的时间差不多就够转换完成,再发读取命令读到上一次转换结果。这样系统不会被传感器拖慢,显示和按键都保持流畅。
3.3 控制策略:滞回、线性映射与PID
控制策略是整个项目的灵魂。我写了三版控制逻辑,强烈建议你也从简单到复杂依次试一遍,感受一下差别。
第一版是滞回控制,也是最直观的:设定目标温度T_set,当温度低于T_set-1度时风扇停止,当温度高于T_set+1度时风扇全速,中间1度范围内保持原状态不变。这个上下1度就是"滞回区间",目的是防止温度在目标点附近波动时风扇反复开关。这个策略代码只有几行,但体验一般:风扇要么停要么全力转,温度一升高就"轰"一下,很吵。
第二版是线性映射:设置一个温度区间,比如26度到35度,温度在这个区间内,PWM占空比从0平滑增大到100%。低于26度停转,高于35度全速。这个方案体验比滞回好很多,风扇转速随温度连续变化,不再有突然全速的突兀感。但它的缺点是转速和温度是固定线性关系,如果风扇对温度的响应偏慢,就会出现过冲或不够的情况。
第三版是PID控制。目标温度设为28度,把当前温度与目标温度的误差作为PID输入,PID输出就是PWM占空比。我用的参数是Kp=12,Ki=0.1,Kd=2,这是通过串口打印温度曲线一点一点调出来的。PID的优点是动态响应好:温度偏离目标值时会先全力拉回来,接近目标值后会平滑收敛,风扇不会有明显的阶梯感。缺点是调参花时间,而且没有基础的经验值的话,刚开始往往越调越乱。
我的建议:如果是纯粹去做一个产品,线性映射加一段合理回差通常已经够用,代码简单、行为可预期、不需要调参。如果是学习目的,一定要亲手调一次PID参数、串口观察曲线,你才能真正理解P、I、D三个项分别管什么问题。
3.4 PWM初始化与OLED刷新实现
PWM输出使用TIM2的通道1,复用功能映射到PA0引脚。频率计算公式是:PWM频率 = 时钟频率 /(预分频值+1)/(自动重装载值+1)。72MHz时钟下,预分频设为3,自动重装载值设为899,得到频率 = 72MHz / 4 / 900 = 20kHz,周期约50us。
代码里设置比较值:
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, speed);其中speed取值0到899,对应占空比0%到100%。如果你想要一个"最小启动占空比",比如10%,那么speed最小取89——很多风扇在占空比过低时启动不了,有个初速度才能转起来,这一点在代码里写个限制就行。
OLED刷新我放在主循环里,大约每秒刷新两次。不要把温度转换等待、PID计算、OLED刷新全部串行放到一个大循环里,最好用简单的状态机或者时间片轮询:每100ms读一次温度、每200ms算一次控制输出、每500ms刷一次屏幕,这样整个系统的实时性和流畅度都好得多。
按键消抖这段代码比较固定:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) { HAL_Delay(20); if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) { target_temp++; } }用两个按键分别调节设定温度的加减,温度范围限制在20到35度之间,并且在OLED上显示当前目标值,这个交互就完整了。
4. 实测调试:翻车现场与排查方法
4.1 温度读数跳变的完整排查过程
我第一版程序下载进去后,OLED上显示的初始温度是85度,后来又跳成-5度,完全没法用。当时我怀疑代码写错了,用了整整一个晚上排查。
排查路径按优先级排列:先确认硬件接线,DS18B20的三个引脚有没有接反,VCC接的是3.3V而不是5V(DS18B20最高耐压5.5V,但3.3V更稳);再确认上拉电阻是否存在且阻值合适,用万用表测DQ引脚电压,正常情况下空闲应该是接近3.3V的高电平;如果硬件没问题,再用逻辑分析仪或者示波器看单总线波形,确认复位脉冲、时序宽度是否正常。
我这次的问题最后定位在DWT延时初始化上——我的SystemCoreClock在时钟配置后是72MHz没错,但DWT的时钟源和CPU时钟不一定完全同步,延时目标值需要换算成DWT计数器的实际频率。把所有DWT_Delay_us都换成基于SysTick校准后,温度读取就完全稳定了。
另外还有一个很隐蔽的问题:OLED和DS18B20共用I2C数据线附近的空间布局。OLED刷新时I2C时钟线会产生高频噪声,如果这个线紧贴着DS18B20的数据线走,噪声会串进传感器总线导致偶尔读到0xFF。把传感器线单独走到板子另一侧,问题消失。
4.2 风扇不转与转速异常的定位思路
如果温度读数正常、OLED正常,但风扇不转,优先查三处:测量MOS管栅极电压,如果没有电压说明PWM配置不对或者引脚复用没设置好;测量漏极和源极之间是否有电压,如果风扇正极和漏极之间电压正常但风扇不转,测一下风扇正极和GND之间的电压——如果很低,说明MOS管没有完全导通或线路压降太大;检查续流二极管有没有接反,反接会导致风扇直接短路保护。
转速异常的常见原因有两个。一是PWM最小占空比设置过低,风扇在低占空比下无法启动,表现为开机的瞬间转了一下就停,或者始终不转。解决方法是设置最小占空比为10%-15%,一旦低于这个值直接输出0而不是输出一个微弱脉冲。二是PWM频率过高导致风扇电机跟不上,三线风扇内部霍尔驱动电路对PWM频率有要求,有些型号在25kHz以上会直接失效,表现为电机卡住不动。可以先用固定直流电压测一下风扇是否正常转动,再逐步提高PWM频率找到失效边界。
4.3 临界点风扇频繁启停的处理
线性映射方案做了之后,出现一个有点尴尬的现象:当环境温度正好落在映射区间的中间(比如31度附近),温度小范围波动几个0.1度,风扇转速就在50%上下浮动,能听出风扇在轻微"喘气"。虽然不影响功能,但体验很差。
解决方法是引入滞回思想,把映射曲线做成两条边:一条是升温度曲线,一条是降温度曲线。具体做法是,当前温度上升时,使用映射关系A计算转速;当前温度下降时,使用映射关系B计算转速;两条曲线的阈值相差2-3度。这样温度在同一个点上下波动时,转速会保持在一个相对稳定的值,不会跟着抖。
PID方案就没有这个问题,因为PID本身积分作用会平滑输出,温度小幅波动时输出变化很小。所以如果追求极致体验,PID反而比线性映射更省心,前提是你愿意花时间把PID参数调好。
4.4 调试工具与常用问题速查表
这个项目我强烈建议接上串口调试。在代码里把温度、目标温度、PWM占空比、PID误差全部打包成一行打印出去,波特率115200,电脑端用任意串口助手就能实时观察系统状态。调PID参数时,用串口曲线工具打印温度变化曲线,你能直观看到超调、振荡和稳定时间,比盲调快得多。
下面是我整理的常见问题排查速查表,基本覆盖这个项目能遇到的大部分坑:
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 温度显示85度或乱跳 | DS18B20数据线悬空、上拉电阻缺失、DWT延时不准 | 先测DQ空闲电平,再检查时序波形 |
| 温度稳定但偏高 | DS18B20靠MOS管或电源发热源太近 | 用热成像仪或手摸确认热源位置,传感器移开 |
| 风扇完全不转 | MOS管没导通、PWM引脚没配置、风扇损坏 | 测栅极电压、测MOS管DS压降 |
| 风扇转一下停一下 | 最小占空比设置过低 | 把最小PWM提高到10%以上 |
| 风扇啸叫 | PWM频率在可听范围 | 提高PWM频率到20kHz以上 |
| OLED白屏 | I2C地址错误、引脚接反、供电不足 | 扫描I2C地址,确认SCL/SDA,测电压 |
| 按键没反应 | 引脚内部上拉未使能、引脚冲突 | 换成独立引脚,软件消抖确认电平变化 |
| 烧录失败 | BOOT0引脚状态、接线问题 | 确认BOOT0接地、SWDIO/SWCLK接对、供电正常 |
最后说一个调PID参数时很有用的实操技巧:用电脑风扇自带测速线。三线风扇的测速线输出的是脉冲信号,接到STM32的定时器输入捕获引脚,就可以实测当前风扇转速。然后你可以在OLED上同时显示"目标转速"和"实际转速",如果不一致,说明PID参数需要再调整。我当时为了做这个功能,在代码里加了定时器输入捕获频率测量,也算顺手把STM32的输入捕获外设一起练了。这也是智能温控风扇后续最值得扩展的一个方向:从开环PWM调速升级成闭环转速控制,不管风扇负载怎么变,转速都能牢牢锁定在目标值上。
本文还有配套的精品资源,点击获取