☰
温度控制风扇完整指南:NTC测温、软件滤波与PID/PWM调速
2026/10/15 8:17:48 网站建设 项目流程

简介:这是一份基于51单片机设计的智能电风扇控制系统完整设计文档,面向单片机学习者、电子设计竞赛参赛者及嵌入式入门开发人员。方案针对传统电风扇无法随温度自动调速、机械定时噪音大、缺乏遥控等痛点,给出了从总体硬件设计到系统软件流程的完整实现思路。内容覆盖总体架构、DS18B20数字温度传感器模块、电机调速与控制模块、温度显示与控制模块,以及传感器程序设计与调速程序流程等关键环节;功能上包含5档风速自动切换、睡眠控制、12小时定时开关机、红外遥控、数码管显示与RS232串口控制,温度每升降3℃自动调整一档,可直接用于课程设计或毕业设计的方案参考。压缩包共1个doc文件,约167KB,全文结构清晰、章节完整。目前已有7255人学习下载,适合需要搭建智能风扇系统框架并撰写设计文档的读者。

1. 温度控制风扇:真正难的不是风扇,是测温与控制策略

很多人第一次做温度控制风扇,都在风扇驱动上花了大把时间:接个MOS管、写个PWM、让转速跟着温度走,看起来不到半天就能跑通。但真正把这套东西用起来、用得住,难点全在另外两处——温度测不准,和控制策略不合理。测温点放错位置、采样值没有滤波、临界温度反复启停、上电瞬间风扇猛转,这些问题才是温度控制风扇「翻车」的重灾区。

这个标题里的「完整」,不是指把线接完、代码写完就算完整,而是指从传感器选型、信号采集、软件滤波,到控制算法、PWM驱动、异常排查的整个闭环。适合谁看?想给电脑机箱、DIY小机柜、3D打印机舱室做温控散热的人,以及正在做单片机课程设计但只抄到半套代码、想搞清楚每行逻辑的初学者。下面这套方案不依赖特定开发板,任何带ADC和定时器的单片机都能落地。

2. 温度控制风扇的整体架构:传感器、控制器与执行器的选型逻辑

2.1 三种测温方案的取舍:NTC、DS18B20与热电偶

温度控制风扇的测温环节,常见方案就三种,选型时从精度、响应速度、成本三个维度卡就行。

NTC热敏电阻是最常见的入门选择,成本极低,一颗也就几毛钱,配合一个分压电阻直接进ADC采样。它的响应速度中等,热容量小、测温头做得很细的情况下,能感知到秒级的温度变化。但NTC有两个天生缺点:一是阻值和温度的关系是非线性的,25℃标称10K的NTC在50℃可能只有4K左右,必须做查表或者公式换算;二是离散性大,同型号的NTC在相同温度下阻值可能差5%以上,做精密温控必须每颗单独校准。

DS18B20是数字传感器,单总线协议,直接输出温度值,精度标称±0.5℃,不需要ADC,也不存在非线性换算问题。它的麻烦在于时序敏感,单总线读写时序要求严格,代码写得不好容易读回85℃这种固定错误值。而且DS18B20的测温头封装热阻偏大,贴在散热片上测的是「散热片温度」而非「空气温度」,做环境温控时响应会慢半拍。

热电偶适合高温场景,比如3D打印机热端风冷、加热腔体散热,测温范围宽,但需要冷端补偿和仪表放大器,电路复杂度上一个台阶。做风扇温控扛着热电偶属于杀鸡用牛刀,除非目标是200℃以上的场景,否则我的建议是:室温到80℃区间,优先NTC;要省事、要精度、不在乎时序调试成本,选DS18B20。

2.2 执行机构选型:普通直流风扇与四线PWM风扇的驱动差异

风扇这边,市面上的温控项目用两类:普通两线/三线直流风扇,和四线PWM风扇。两线风扇只有电源正负,三线多一根转速信号(FG开漏输出),调速只能靠改变供电电压,典型做法是MOS管配合PWM斩波,也就是通过调节平均电压来改变转速,这是最常见的低成本方案。

四线PWM风扇是电脑机箱里的那种,电源、地、转速信号、PWM控制线四根线。它的调速不是靠斩波供电,而是风扇内部的控制芯片根据PWM占空比直接调节转速,外部只需要给一个3.3V或5V的逻辑电平PWM信号。这类风扇的好处是低速线性好,最低转速能压到很低且不丢转,缺点是贵,而且必须选对PWM频率——很多四线风扇要求25kHz左右的控制信号,频率不对风扇会啸叫或者不转。

对于从零开始做的温度控制风扇,我更推荐普通直流风扇加外部MOS驱动。原因很直接:不管风扇规格怎么变,MOS管方案都能通过改占空比上限来适配,而四线风扇一旦PWM频率适配不好,排查起来很玄学。下面是两种方案的对比:

对比项普通直流风扇 + MOSFET斩波四线PWM风扇
驱动电路需外接MOS管、续流二极管只需逻辑电平PWM
控制频率10kHz~30kHz均可,宽容度高通常在25kHz附近,偏离会啸叫
低速线性较差,占空比低于20%可能停转好,能实现很低转速
成本风扇便宜,多几毛钱电路风扇贵2~3倍
适用场景DIY自制、成本敏感机箱改造、追求静音

2.3 控制器的角色划分:单片机选型与资源分配

控制器选型不追求性能,追求「够用且顺手」。带10位以上ADC、一个定时器能输出PWM、有足够GPIO驱动OLED或数码管做显示,这三个条件满足就够。8位MCU完全跑得动,不用上32位。

资源分配上,ADC通道给NTC分压点,定时器拆成两路:一路输出PWM给风扇,另一路做1ms时基用于采样节拍和软件计时。串口留一个,调试的时候打印实时温度、占空比和PID输出,这个习惯能帮你省掉大量肉眼排查的时间。传感器用DS18B20的话,还需要一个GPIO模拟单总线时序,注意选带外部上拉能力的引脚。

3. 温度采集与数据处理:从ADC原始值到稳定温度的完整链路

3.1 NTC热敏电阻的分压电路设计与采样计算

NTC测温的标准接法是分压电路。以10K NTC、25℃标称值举例,串联一个10K精密电阻,接到参考电压和地之间,中间抽头进ADC。选10K做分压电阻是为了让25℃附近的输出接近参考电压的一半,使ADC在常用测温区间获得最大的电压分辨率。

#define NTC_SERIES_RESISTOR 10000.0f #define NTC_NOMINAL_VALUE 10000.0f #define NTC_NOMINAL_TEMP 25.0f #define NTC_B_VALUE 3950.0f #define ADC_FULL_SCALE 4095.0f #define ADC_REF_VOLTAGE 3.3f // 读取NTC分压点的温度,采用B值公式换算 float ntc_read_temperature(void) { uint32_t adc_val = adc_read_single(CH_NTC); // 单次采样 float v_out = (float)adc_val * ADC_REF_VOLTAGE / ADC_FULL_SCALE; float r_ntc = NTC_SERIES_RESISTOR * v_out / (ADC_REF_VOLTAGE - v_out); // B值公式: 1/T = 1/T0 + (1/B) * ln(R/R0) float temp_k = 1.0f / (1.0f / (NTC_NOMINAL_TEMP + 273.15f) \ + (1.0f / NTC_B_VALUE) * logf(r_ntc / NTC_NOMINAL_VALUE)); return temp_k - 273.15f; }

这段代码只在25℃附近做了B值公式换算,没有做查表。B值公式的误差在0~70℃区间内大约±1~2℃,对风扇温控来说完全够用。如果追求更准,可以改用厂商提供的R-T分度表,在代码里做成const数组配合线性插值,误差能压到±0.5℃以内,代价是占几百字节Flash,8位MCU也负担得起。

这里有个参数要特别说明:NTC_SERIES_RESISTOR不等于NTC_NOMINAL_VALUE。有人图省事让两者都用10K,在25℃确实输出中间电压,但如果你把分压电阻换成其他阻值,比如为了扩大高温区分辨率用4.7K,那么分压公式里的NTC_SERIES_RESISTOR必须跟着改,否则测出来每个温度点都系统性偏高。

3.2 软件滤波:中值滤波与滑动平均的组合策略

NTC采样值最大的坑不是ADC分辨率不够,而是干扰。PWM驱动风扇的电流脉冲会通过地线耦合进ADC参考电压,导致采样值在真实值附近上下跳,跳动幅度可能达到3~5℃的等效误差。只做一次采样就拿来算温度,风扇转速会跟着抖,噪音听着就像在「喘气」。

我常用的组合是:每50ms采5次,先做中值滤波剔除野值,再对中值结果做滑动平均。中值滤波能干掉尖峰脉冲干扰,滑动平均能平滑掉缓慢的波动,两者职责不同、缺一不可。

#define FILTER_SAMPLE_COUNT 5 #define SLIDING_AVERAGE_SIZE 8 static uint16_t raw_buffer[FILTER_SAMPLE_COUNT]; static float temp_history[SLIDING_AVERAGE_SIZE]; static uint8_t history_index = 0; // 冒泡排序后取中值,只排5个数开销极小 static uint16_t median_filter(uint16_t *buf, uint8_t len) { uint16_t tmp; for (uint8_t i = 0; i < len - 1; i++) { for (uint8_t j = i + 1; j < len; j++) { if (buf[i] > buf[j]) { tmp = buf[i]; buf[i] = buf[j]; buf[j] = tmp; } } } return buf[len / 2]; } float temperature_filtered_read(void) { for (uint8_t i = 0; i < FILTER_SAMPLE_COUNT; i++) { raw_buffer[i] = adc_read_single(CH_NTC); // 连续读5次 } uint16_t med = median_filter(raw_buffer, FILTER_SAMPLE_COUNT); float temp_c = adc_to_temperature(med); // 转成温度 temp_history[history_index] = temp_c; history_index = (history_index + 1) % SLIDING_AVERAGE_SIZE; float sum = 0.0f; for (uint8_t i = 0; i < SLIDING_AVERAGE_SIZE; i++) { sum += temp_history[i]; } return sum / SLIDING_AVERAGE_SIZE; }

注意滑动平均的数组长度。8个历史值意味着对温度的响应延迟了8个采样周期,每个周期50ms,总延迟400ms,这个延迟对温控系统来说是可接受的——热系统本身是大惯性对象,温度变化本来就不快。如果你把历史数组加到32个,延迟变成1.6秒,风扇对温度变化的响应会明显迟钝,升温时转速跟不上,这就是滤波过度。

3.3 温度标定:两点校准与查表法

NTC的B值公式误差主要来自料本身的离散性,同一批次的NTC阻值可能差3%~5%,算出来的温度可能差2℃。如果你的温控项目对绝对温度有要求,比如「38℃开始加速散热」,那必须做标定。

标定不需要恒温槽这种专业设备。找一杯冰水混合物(0℃)和一杯热水(用手感温度计或另一路DS18B20做参考),把NTC探头分别浸入,记录ADC换算结果与参考温度的偏差,然后对B值公式做偏移修正。常见做法是给换算结果加一个固定偏移量,这个偏移量在30℃~60℃区间内基本恒定,一个偏移搞定整个工作区间的校准。

标定完成后,把修正值写死在Flash里。如果追求更高精度,可以在0℃、25℃、50℃、75℃四个点记录实际阻值,生成一张R-T表,运行时查表插值。查表法的好处是彻底摆脱B值公式的非线性误差,坏处是每个传感器要单独标定,生产流程麻烦。对单件DIY来说,偏移修正已经足够,查表法留给批量做的温控产品。

4. 控制算法与PWM驱动:让风扇转速跟随温度平滑变化

4.1 迟滞比较:最简单的无抖动温控逻辑

温度控制风扇最基础的控制算法是阈值比较:超过某个温度就全速转,低于某个温度就停。但直接用单阈值会有一个让人崩溃的问题——当温度在阈值附近徘徊时,风扇会反复启停,嗡嗡声一阵一阵的,听着就像电路在「抽搐」。

解决办法是加迟滞:设两个阈值,比如开启温度35℃、关闭温度30℃,升温到35℃才打开风扇,之后温度要回落到30℃以下才会关停。中间这5℃的区间就是缓冲带,风扇不会因为0.1℃的波动来回切换。

#define FAN_ON_TEMP_C 35.0f #define FAN_OFF_TEMP_C 30.0f // 必须低于FAN_ON_TEMP_C static uint8_t fan_running = 0; void fan_switch_control(float temp_c) { if (!fan_running && temp_c >= FAN_ON_TEMP_C) { fan_running = 1; fan_set_duty(80); // 启动直接给80%,避免低速启动困难 } else if (fan_running && temp_c <= FAN_OFF_TEMP_C) { fan_running = 0; fan_set_duty(0); } // 区间内保持原状态,不更新输出 }

迟滞窗口的宽度取决于被控对象的温度波动幅度。如果你的环境温度波动本身就超过迟滞范围,风扇照样会频繁启停,这时候应该先加强测温滤波,或者增大迟滞。但迟滞也不是越大越好——做散热时风扇启动晚了,设备温度会冲得更高。

4.2 PID调速:为什么温控风扇用PD就够了

阈值开关控制解决的是「要不要转」,PID解决的是「转多快」。温控风扇的完整PID流程是:以目标温度减去当前温度得到误差,PID输出对应占空比。目标温度可以是你设定的期望工作温度,也可以设成35℃之类的恒定值。如果只想要「温度越高转越快」的映射关系,不追求精确控温,那么用P(比例)就够了。

实际做的时候发现,纯P控制在温度快速变化时会有振荡——风扇转速跟不上温度的瞬间变化,过冲明显。所以我会加D项,也就是微分项,让输出对温度变化率做出提前响应。I项积分在温控风扇里其实很少用,因为风扇本身就是个执行器,不具备自整定能力,积分积分过头容易造成转速饱和,反而让风扇长时间满转。

#define TARGET_TEMP 35.0f #define KP 12.0f // 增益越大,温度每超1℃增加的占空比越大 #define KD 3.0f // 抑制温度突变,防止过冲 #define DUTY_MIN 20.0f // 最低转速占空比,低于此值风扇可能不转 #define DUTY_MAX 100.0f static float last_error = 0.0f; float pid_compute_duty(float temp_c) { float error = TARGET_TEMP - temp_c; float derivative = error - last_error; last_error = error; float duty = KP * error + KD * derivative; if (duty < DUTY_MIN) duty = DUTY_MIN; if (duty > DUTY_MAX) duty = DUTY_MAX; return duty; }

这段代码的输出直接就是占空比百分比,省掉了先算0~100%再映射的中间步骤。注意DUTY_MIN设了20%,这是一个「血泪经验」——普通直流风扇占空比低于某个阈值(具体值因电机而异,一般在15%~25%之间)会直接停转,停转后要继续加大占空比才能重新转起来,你如果直接输出很低的占空比,会看到风扇一会儿停一会儿转,噪音反而更大。

4.3 用定时器输出可调PWM:频率选择与占空比映射

PWM频率的选择直接影响风扇噪音。低于10kHz时,PWM的开关频率进入人耳可听范围,风扇会发出尖锐的啸叫;高于30kHz时,MOS管的开关损耗增大,对小散热片来说发热可观。所以普遍的选择是15kHz~25kHz之间,我习惯用20kHz,人耳基本听不到,开关损耗也可接受。

定时器配置上,以某款常见8位MCU为例,把定时器2配置为PWM模式,时钟源选择内部时钟不分频,重载值决定了PWM频率,比较寄存器值决定占空比。频率和占空比的计算关系是:频率等于时钟频率除以重载值,占空比等于比较值除以重载值。

// 以20kHz PWM为例:定时器时钟20MHz,重载值1000 void fan_pwm_init(void) { // 设置定时器时钟源,不分频,周期设为1000 // 输出比较通道使能,选择PWM模式1 // 初始占空比0%,比较值寄存器清0 // 使能定时器,启动输出 } void fan_set_duty(uint8_t duty_percent) { uint16_t compare_value = (uint16_t)duty_percent * 10; // 1000/100 = 10 // 写入比较寄存器,占空比越界检查交给调用方 }

有人喜欢用延时加GPIO翻转来做PWM,这在10Hz的LED呼吸灯上没问题,用在风扇上就是灾难——延时函数会被其他代码打断,PWM频率飘忽不定,风扇转速一会儿快一会儿慢。定时器硬件PWM的好处是输出不占用CPU,你可以在主循环里放心做滤波、显示、按键扫描,PWM波形不受影响。

5. 温度控制风扇的高频踩坑:从测温漂移到上电抖动的完整排查

5.1 温度显示跳变:NTC引脚悬空与采样时序

现象:温度显示在正常值和明显偏高(比如70℃以上)之间来回跳,频率不稳定。

原因:NTC引线过长且没有屏蔽,PWM驱动电流在引线上感应出尖峰电压;另一种可能是ADC采样刚好落在PWM开关瞬间,参考电压被拉偏。

解决:硬件上,NTC分压点到MCU引脚的走线尽量短,靠近MCU放置;软件上,采样时机避开PWM周期中点——在定时器溢出中断里触发采样,确保采样发生在PWM电平最稳定的时刻。改成定时器同步采样后,跳变问题基本消失。

5.2 风扇在临界温度反复启停

现象:温度在设定阈值附近波动,风扇以秒级周期反复开、关,噪音非常烦人。

原因:控制逻辑用的单阈值,没有迟滞区间。温度升到阈值就开风扇,开风扇之后温度立刻降下来,降到阈值又关,形成震荡。

解决:把单阈值改成双阈值迟滞控制,也就是第4章的fan_switch_control逻辑。如果已经加了迟滞还是抖,检查滤波时间常数——滤波太浅,温度毛刺依然会穿越阈值区间,此时加大滑动平均窗口。

5.3 PWM低速时风扇嗡嗡响

现象:占空比在20%~30%区间时,风扇发出明显的周期性嗡嗡声,转速不稳。

原因:普通直流风扇低速状态下,电机的反电动势减小,换向力矩不足,进入了「要转不转」的临界区。PWM的导通脉宽对这个位置的风扇来说,每一脉冲都在「推一下然后停一下」。

解决:两个方向。一个是把最低占空比下限从20%提到30%,牺牲最低转速换稳定;另一个是换四线PWM风扇,它的内部控制芯片能保证低占空比下有足够的启动力矩。优先尝试前者,成本为零,改一个宏定义就行。

5.4 上电瞬间风扇猛转一下再停

现象:系统上电时风扇会全速转约0.3~0.5秒,然后才回到正常控制状态。

原因:MCU的GPIO在上电复位瞬间处于输入态或默认高电平状态,此时MOS管栅极被拉高,风扇全速通电。等到固件初始化完成、GPIO被配置为PWM输出并输出低占空比,风扇才停下来。

解决:硬件上,在MOS管栅极到地之间加一个10K下拉电阻,确保MCU复位期间栅极一直为低电平。软件上,在main函数的开头立即关闭PWM输出并置输出低电平,再做其他初始化。栅极下拉电阻是治本方案,软件处理只能缩短吓人一跳的时间。

5.5 温度测出来比实际环境高好几度

现象:紧闭机箱内,环境温度30℃,NTC却显示34℃,风扇提前进入高速状态。

原因:NTC探头贴在大功率元件附近,被测点的温度本身就高于环境;或者NTC自身自热——通过NTC的电流在2.5V分压点上大约有0.25mA,虽然不高,但在空气中散热不良时,自热温升也能达到1~3℃。

解决:测温点位置调整到风扇进风口的中心气流区域,远离发热元件;分压电阻的阻值从10K提高到47K,把通过NTC的电流降到原来的五分之一左右,自热影响基本可以忽略。代价是ADC的输入阻抗匹配变差,需要确认MCU的ADC输入阻抗足够高,否则采样值会带系统偏差。

6. 进阶验证:把「能转」变成「转得好」的三个实测方法

6.1 用热风枪与温度记录仪测响应曲线

温度控制风扇的调试不能靠手感。我会用一个热风枪设定固定的出风温度(比如45℃)对着测温点吹,同时用串口以1Hz的速率记录温度和占空比。然后查看数据:升温阶段占空比是否平滑上升,有没有突然跳满的过冲;降温阶段是否平稳回落,有没有在某一区间震荡超过三个周期。如果占空比输出画出来是锯齿状的,说明滤波或PID参数还需要调。

6.2 听噪音测转速:占空比-转速曲线的实际校准

做一套「占空比↔风量」对照其实不用昂贵的转速表。手动设定固定占空比,听风扇声音,在20%、40%、60%、80%四个点分别记录主观噪音和风感,再结合手头的红外测温枪测散热片温度,就能摸清这台风扇的「性格」——有的风扇30%以下根本不出风,有的风扇70%以上噪音急剧增加。把校准结果写进注释里,下次换风扇时不用重新试。

6.3 功耗测量与长期稳定性观察

最后我会做一次持续72小时的运行验证:测量整个系统的静态功耗和满转速功耗,记录温度控制精度是否在设定目标附近波动。如果发现温度长期缓慢爬升,排除控制问题后检查风扇积灰;如果发现夜间温度下降后风扇依然在高速运转,检查温度探头是否被热源近距离烤着。

这套验证流程的价值在于把「主观感觉良好」变成「数据支撑的结论」。做习惯了以后,我每次拿到新风扇第一件事就是跑一遍6.2的转速曲线,而不是直接改参数——风扇换了,控制参数重调是必然的,但有了校准数据,重调从「玄学调参」变成「按数据插值」,省的时间够你多试好几组参数。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询