☰
STM32实战:智能温控风扇的NTC温度采集与PWM驱动全解析
2026/10/16 5:17:03 网站建设 项目流程

简介:本资源是一套基于STM32F103C8T6的嵌入式温控系统完整工程,面向嵌入式初学者与课程设计实践者,解决环境温度实时监测与风扇智能调速控制问题,适用于单片机原理、ARM嵌入式系统等课程实训及毕业设计参考。压缩包共210个文件,含34个C源文件(如stm32f10x_usart.c、stm32f10x_adc.c等外设驱动)、37个头文件(h)、38个编译中间文件(o/d)及Keil工程核心文件(uvprojx、hex、axf、sct等),涵盖OLED显示、DS18B20单总线测温、L298N电机PWM调速、按键交互等完整功能模块,包体仅757KB,结构清晰、注释充分。已有8861人学习下载,配套B站演示视频直观呈现硬件接线、界面操作与温控响应全过程,读者可直接烧录运行、调试理解各模块协同逻辑,并基于现有框架快速扩展多点测温或WiFi远程监控功能。 最近后台收到不少朋友私信,问我能不能写一个完整的STM32实战项目,不要那种跑马灯和点LED的教程,而是要能真正常态运行、带点控制逻辑、做完能放桌面上用的东西。我琢磨了一下,基于STM32的智能温控风扇这个题目挺合适:元件便宜、逻辑不复杂、但该踩的坑一个不少,从传感器读数到PWM驱动再到显示交互全都能练到。这篇文章就把我实际做这一套东西的完整过程写出来,含选型理由、原理分析、代码结构和调参记录,照着做基本能一次跑通。

1. 设计目标与方案取舍:先想清楚要做一个什么样的风扇

1.1 需求拆解:风扇不是"温度高就转"这么简单

很多人一想到温控风扇,第一反应是拿个热敏电阻接个比较器,温度超过阈值就开风扇。这个思路没有错,但它只解决了"转不转"的问题,没解决"转多快"的问题,也没有任何反馈和交互,调试的时候全靠肉眼判断。

我把需求拆成几条:

  • 实时采集环境温度,并且温度显示有小数点后一位,不能只显示整数。
  • 风扇转速随温度线性变化,而不是简单的开/关,避免温度在阈值附近的时候风扇一会转一会停。
  • 需要自动和手动两种模式。自动模式下按温度调速;手动模式下用户直接指定转速百分比,并且能随时切回自动。
  • 用一块OLED显示温度、转速百分比、当前模式,UI要稳定不闪烁。
  • 调试时通过串口把温度、PWM占空比、模式信息打出来,方便看曲线。

如果你只是想要一个"用热敏电阻控制风扇"的物理实验,那确实不用上单片机。但凡是涉及到模式切换、线性输出、人机交互、运行状态可视化,就值得用MCU来做,而且后续想加转速反馈、PID温控、远程控制都有升级空间。

1.2 主控选型:为什么是STM32而不是51或者Arduino

现在市面上可选的方案很多,比如Arduino Nano、ESP32、STM32F103,甚至国产的GD32、CH32系列。我选择STM32F103C8T6(也就是俗称的"蓝丸"核心板),原因有三点:

第一,资料足够多。这个芯片是整个嵌入式领域资料最泛滥的之一,不管是用标准库还是HAL库,遇到问题搜一下基本都能找到解决方案。对入门者来说,"搜得到答案"本身就是巨大的学习效率优势。

第二,外设配置灵活。这个项目需要ADC多通道采集(温度传感和手动调速电位器)、高级定时器PWM输出(驱动风扇)、I2C(OLED)、串口(调试),F103C8T6的外设资源绰绰有余,而且引脚分配不用太纠结。

第三,DMA可用。F103系列虽然老,但DMA、定时器输入捕获这些功能一个不少,正好用这个项目把"ADC多通道扫描循环采样DMA"这种进阶姿势练一遍,而不是停留在"单通道阻塞式轮询"的水平。

1.3 整体架构:从传感器到执行器的完整数据链路

整个系统可以分成四条链路:

  • 感知链路:NTC热敏电阻(或DS18B20)把温度信号送进ADC,ADC用DMA循环扫描采集两个通道(温度 + 手动调速电位器)。
  • 计算链路:MCU把ADC原始值换算成温度,再根据控制策略计算出目标占空比。
  • 执行链路:定时器输出PWM,经MOS管驱动风扇电机。
  • 交互链路:OLED显示状态,按键切换模式,串口输出调试信息。

这种分层的思路非常重要。不管是做这个小型项目还是以后做复杂的工业控制,把感知、决策、执行、交互分开来看,代码就好写得多,出问题也好定位。

2. 温度采集链路:ADC多通道DMA与NTC换算

2.1 传感器选型:NTC、DS18B20、LM35我为什么推荐NTC

温度传感器方案很多,我列个表给大家对比:

传感器类型输出信号精度读取方式特点
DS18B20数字单总线±0.5°C单总线协议接线简单,但读取时序时序要求严格,容易卡延时
NTC热敏电阻模拟电阻经过标定可达±0.5~1°CADC采样便宜,响应快,需要换算公式
LM35模拟电压±0.5°CADC采样10mV/°C,线性好,但要比NTC多买一颗
DHT11数字单总线±2°C单总线时序测温慢,精度一般,更擅长测湿度

我个人推荐NTC热敏电阻做这个项目。原因很实际:一颗10k NTC不到几毛钱,加上一颗10k普通电阻做分压就能用,而且通过这个方案能把ADC的采样、滤波、工程量换算全套流程练下来。相反,DS18B20虽然直接用换算好的数字值,但你少了一个非常重要的技能点——把ADC原始值变成物理量。这一关是永远逃不掉的。

当然,如果你手边只有DS18B20,也能做,只需要把温度读取那段换成单总线驱动的HAL实现,其余控制逻辑完全不用变。

2.2 电路接法:NTC分压电路怎么接才准确

NTC的接法很简单,一颗10k NTC和一颗10k普通电阻串联,中点接STM32的PA0(ADC1通道0),NTC另一端接地,普通电阻另一端接3.3V。这样做的原因是:

  • 3.3V作为AD参考电压,量程匹配。
  • 温度升高时NTC阻值下降,分压中点电压降低,ADC值变小。反过来温度降低时ADC值变大。这个方向关系在代码里要注意,别把公式写反了。

另外一个通道PA1接一个10k电位器,中间抽头直接进ADC,用来做手动模式下的转速设定。电位器接法很简单:固定端分别接3.3V和GND,抽头接PA1。

这里有一个非常重要的提醒:如果温度探头到MCU的线比较长,要在ADC引脚和GND之间加一个100nF的滤波电容,否则采出来的数据跳动非常大,后面再怎么软件滤波都费劲。我的做法是在靠近MCU的一侧并了一个100nF电容,实测ADC采样值跳动了从±10左右降到了±2左右。

2.3 ADC多通道扫描循环采样DMA:不要让采样卡住主循环

很多人写多通道ADC是一路一路轮询,每次HAL_ADC_Start + HAL_ADC_PollForConversion + HAL_ADC_GetValue,两个通道轮流读,中间还要切换通道、配置Rank。这种写法在采样频率要求不高的时候能用,但它把MCU的空闲时间浪费在等待转换上,而且代码啰嗦,可扩展性差。

正确姿势是开启ADC的扫描模式(ScanConvMode)、连续转换模式(ContinuousConvMode),配合DMA环形模式。一次配置好后,DMA会自动把两个通道的转换结果搬运到缓冲区里,CPU完全不用管。我用的配置大致如下(HAL库):

hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.NbrOfConversion = 2; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, 2);

这里有几个细节:

  • 采样时间设为55.5周期左右,对于从NTC这种内阻比较高的信号源来说,采样时间太短会导致采样电容充不满,ADC读数偏低且不稳定。
  • DMA模式选循环模式(Circular),这样数组会被不断刷新,主循环里随时可以读到最新的两组数据。
  • 数组大小正好是2,对应两个通道。如果要扩展更多通道,数组长度跟着变就行。

2.4 NTC温度换算:B值公式与软件滤波

拿到ADC原始值后,需要换算成电阻值,再用热敏电阻的B值公式换算温度。假设用的是10k B=3950的NTC,分压电阻也是10k,电路是NTC在下方接地:

  • 先算NTC当前阻值:R_ntc = R_series * (adc_max / adc_val - 1)。这里的R_series就是分压的10k电阻。
  • 再用B值公式算温度:T = 1 / (1/T0 + (1/B) * ln(R_ntc / R0)) - 273.15。其中T0是基准温度298.15K(25°C),R0是10k。

C语言里用math.h的log函数就行:

float ntc_to_temp(uint16_t adc_val) { if (adc_val == 0) adc_val = 1; float r_ntc = R_SERIES * ((float)ADC_FULL / adc_val - 1.0f); float temp_kelvin = 1.0f / (1.0f / 298.15f + (1.0f / 3950.0f) * logf(r_ntc / 10000.0f)); return temp_kelvin - 273.15f; }

不过ADC即使是加滤波电容后,也还是会有几个码的抖动,直接用单个值换算出来的温度会在整数位上下跳。我的处理办法是滑动平均滤波:开一个长度为16的环形数组,存最近16次的ADC值,每次更新后取平均。16次采样在DMA循环扫描下大概也就几毫秒就完成一次,实时性完全够。

注意:如果主循环里没有做上电稳定等待,刚开机那几百毫秒ADC会读到一些奇奇怪怪的值。建议上电后先延时几百毫秒,再启动DMA采集,或者把前20个采样点丢弃。

3. 风扇驱动电路:为什么GPIO直接带不动电机,以及MOS管的正确用法

3.1 从引脚电流说起:GPIO的电流上限有多小

STM32的GPIO推挽模式最大能输出的电流大概是25mA,但实际的参考手册里通常建议不要把引脚当电源用,持续输出电流不要超过±10mA左右才安全。而一个普通12V直流风扇,就算是最小的4020型号,额定电流也有100mA上下,稍微大一点的6020、8010风扇能达到200~300mA。

所以,直接用PA7接风扇正极、风扇负极接GND这种接法,结果往往是:要么风扇转不起来,要么STM32芯片过热甚至烧引脚。这种错误特别多,因为很多教程的LED实验给人造成了"引脚能输出功率"的错觉。

3.2 MOS管开关电路:AO3400 + 续流二极管

正确的做法是用一个N沟道MOS管做低压侧开关,风扇接在电源正极和MOS管漏极之间,PWM信号控制栅极:

  • MOS管型号:AO3400,30V/5.8A,RDS(on)大约30mΩ,逻辑电平就能完全导通,非常适合3.3V单片机直驱。
  • 栅极串一个100Ω电阻,抑制振铃。
  • 栅源之间加一个10k下拉电阻,防止MCU复位时引脚悬空导致MOS管误导通。
  • 风扇电机两端反向并联一个1N5819肖特基二极管,吸收关断瞬间的感性反电动势。

这个电路原理上就是一个典型的低边开关。风扇的正极接电源,负极接MOS管的D极,S极接地,PWM从单片机输出到G极。PWM高电平时MOS管导通,风扇得电;PWM低电平时MOS管截止,风扇断电。通过调节高电平占空比控制风扇的平均电压,从而控制转速。

这里一定要提续流二极管。风扇是感性负载,MOS管关断瞬间电流突变,电感会产生一个反向电动势,如果没地方泄放,电压尖峰能冲到几十伏,轻则干扰旁边电路,重则击穿MOS管。肖特基二极管的导通压降低,续流效果比普通整流二极管好,而且开关速度快。

3.3 PWM频率怎么选:25kHz的玄学与实际效果

PWM频率的选择对风扇来说是个非常重要又被忽略的细节。很多人图方便直接把PWM频率设成1kHz甚至100Hz,结果风扇嗡嗡响,声音不是电机本身的风噪,而是PWM开关产生的电磁噪声叠加在绕组上引起的啸叫。

对于直流无刷风扇这种电机,我的经验是PWM频率至少要在20kHz以上,也就是超出人耳可听范围。这样你听到的只是风声和电机转动的噪声,而不是电子啸叫。

在STM32上设置20kHz其实很简单。比如TIM3挂载在APB1上,时钟通常配置为72MHz,如果预分频PSC设为1,那么计数时钟是36MHz,要让频率为20kHz,周期就是36MHz / 20000 = 1800,所以ARR设为1799:

htim3.Init.Prescaler = 1; htim3.Init.Period = 1799; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim3);

分辨率算一下:占空比步长是1/1800,大约0.056%,控制风扇转速足够用了。

但这个频率也不是越高越好。频率太高时MOS管的开关损耗会变大,如果风扇和MOS管之间的走线又长又没有处理好,还可能因为寄生参数产生振铃。所以我自己实测后选择了25kHz左右,在F103上把PSC=1、ARR=1439,频率大约是72MHz / (2 * 1440) = 25kHz,声音和发热都比较合适。你如果用的风扇不同,可以从20kHz试到30kHz,找出最安静的点。

3.4 软启动:为什么占空比小于30%风扇根本不转

这是本项目里最典型的坑之一:程序里明明设了20%占空比,风扇却纹丝不动,检查电路、代码半天,最后发现是扇叶太重,启动转矩不够。

原因在于直流无刷风扇内部的电路有个最低启动电压,大概是额定电压的30%~50%左右。20%占空比对应平均电压只有2.4V(按12V风扇算),远达不到启动门槛,所以风扇要么不转,要么是微微颤动。

解决方法是软件软启动:当温度达到启动阈值后,先把占空比拉到60%以上,保证风扇转起来,然后再按目标占空比控制。具体在代码上就是:

if (first_start) { __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, PWM_PERIOD * 0.6); HAL_Delay(300); first_start = 0; }

实测下来,300ms左右的强制高占空比,足够让风扇建立转动惯量,之后再降下来就非常顺滑。这个细节决定了你的风扇是"开机猛转一下然后降速"还是"卡住不动像坏了一样"。

4. 调速控制逻辑:阈值、死区与线性映射

4.1 最基本的开/闭环思路:阈值控制为什么会产生风扇抖动

最粗糙的温度控制就是设定一个阈值,比如温度超过30°C就开满速,低于30°C就关停。这种控制方式有一个非常讨厌的问题:环境温度不可能稳定在一个点上,它会在29.9°C到30.1°C之间反复波动,于是风扇会在一瞬间开一下关一下,发出"咔咔咔"的抖动声,电机频繁启停也非常伤。

解决方案是引入"回差"(hysteresis),也就是开和关使用两个不同的阈值。比如:

  • 当温度升高到32°C以上时启动风扇。
  • 当温度降低到28°C以下时才关闭风扇。
  • 在28°C到32°C之间,保持上次状态不变。

这样形成一个滞回区间,温度小幅波动时风扇状态不会频繁翻转,控制系统的稳定性大幅提升。

4.2 线性映射:让转速随着温度平滑变化

光有开关控制还是不够,因为风扇一旦启动就是满速,温度刚过阈值的时候风扇呜呜的满速转,根本没有必要。实际体验中,温度30°C的时候需要20%的微风,35°C的时候40%风速,40°C以上才考虑满速。

这就需要一个线性映射函数:把温度区间 [T_START, T_FULL] 映射到占空比区间 [DUTY_MIN, 100]。这里T_START是启动温度,比如30°C,T_FULL是满速温度,比如42°C,DUTY_MIN是最小占空比,比如30%(再低风扇就转不起来了)。

公式也很简单:

uint16_t calc_duty_by_temp(float temp) { if (temp <= T_START) return 0; if (temp >= T_FULL) return PWM_PERIOD; float ratio = (temp - T_START) / (T_FULL - T_START); uint16_t duty = (uint16_t)(DUTY_MIN * PWM_PERIOD + (PWM_PERIOD - DUTY_MIN * PWM_PERIOD) * ratio); return duty; }

这里注意:如果DUTY_MIN设成30%,实际运行的时候占空比输出会从30%起步,然后线性升到100%。最低占空比要和"风扇能启动的最低电压"匹配,这个值通过上一节的软启动逻辑来保证启动阶段不受影响。

4.3 自动模式的完整状态机:状态切换要清晰

自动模式下,我把状态分成四个:OFF、STARTING、RUNNING、COOLING_DOWN。这么做的目的就是处理好启动那个特殊阶段:

  • OFF:温度低于T_START + 1°C,风扇停止,PWM输出0。
  • STARTING:温度超过T_START后进入,占空比固定60%持续300ms,启动完毕后进入RUNNING。
  • RUNNING:温度在T_START和T_FULL之间,按线性映射输出。如果温度跌回T_START - HYSTERESIS以下,进入COOLING_DOWN。
  • COOLING_DOWN:温度已经跌回去了,但为了避免频繁启停,风扇继续低速运转3秒,然后进入OFF。

这套状态机虽然比简单的if-else复杂一点,但逻辑非常清晰,也方便以后扩展。比如想加一个"温度超过50°C时强制报警",只需在RUNNING里加一个分支。

手动模式就简单得多:ADC读取电位器的占空比设定值(0~100%映射),PWM直接跟随输出。但是要注意,电位器调到很低时风扇也会出现不转的情况,所以手动模式下我也加了一个最低占空比限制,低于20%直接输出0,只保留一个"0~20%"低占空比区间作为风机停转区。

5. 显示与交互:OLED界面刷新和按键状态机

5.1 OLED接线与I2C地址确认

显示模块我用的是0.96寸SSD1306 OLED,I2C接口。SCL接PB6,SDA接PB7,也就是STM32F103的I2C1引脚。3.3V供电,GND共地。

这里有一个小坑:不用的OLED模块I2C地址可能是0x3C或0x3D,视模块上地址电阻而定。驱动初始化之前先用I2C扫描函数确认一下地址,比你写死0x3C然后发现屏幕不亮要快得多。我实际遇到过一个模块是0x3D,折腾了半小时才发现是地址问题。

如果你用的是4针SPI版本的OLED,也可以,但那就需要SPI接口(比如PA5、PA6、PA7)和额外的DC/CS/RST控制,会占用更多引脚。这个项目引脚够用,I2C两线制最省事。

5.2 显示布局与刷新策略:不要全屏清屏

OLED的驱动逻辑是:全屏刷新一次需要发送大约1KB的数据(128x64 / 8 = 1024字节),在I2C 400kHz速率下大概需要20多毫秒。如果你使用"每次更新先清屏再画全部内容"的策略,再加上上面的ADC映射、按键轮询、PWM调整这些操作,画面会明显闪烁,肉眼可见的那种。

我采用的方案是局部刷新:屏幕分成两个区域,上半部分显示温度数值和大大的温度图标,下半部分显示占空比百分比和模式(AUTO/MANUAL)。每次更新时,只有数值变化的部分才重新绘制。

具体在代码中,我在主循环里维护一个display_dirty标志,只有当以下条件满足时才会刷新对应区域:

  • 温度小数部分变化超过0.1°C
  • 占空比变化超过1%
  • 模式切换

这个做法把刷屏次数从每秒几十次降到1~2次,肉眼看起来画面是稳定不闪的。OLED本身有自刷新机制,不需要外部持续刷屏,这是很多新手容易走入的误区——以为要像LCD一样不断刷新才能维持显示。

5.3 按键消抖与状态机:用定时扫描代替硬件消抖电路

项目里用了两个按键:一个负责模式切换(自动/手动),一个负责手动模式下提高转速(配合电位器调节)。按键电路很简单,按键一端接GND,另一端接PB0,内部上拉。平时读取为高,按下为低。

按键消抖如果靠外接RC电路或者比较器,那就把简单问题复杂化了。软件消抖足够可靠,我是这样做的:主循环每10ms扫描一次按键,连续3次(即30ms)都读到按下状态才算真正按下,最后只触发一次动作。

这里必须强调:不要在按键处理里使用HAL_Delay。很多人写消抖写成"检测到按下就延时20ms再读一次",结果延时期间整个系统都卡住了,PWM输出、OLED刷新、温度采样全部停摆。正确做法是用非阻塞的轮询计数:

if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == RESET) { key_press_cnt++; if (key_press_cnt >= 3) { key_press_cnt = 0; mode = (mode == MODE_AUTO) ? MODE_MANUAL : MODE_AUTO; display_dirty = 1; } } else { key_press_cnt = 0; }

5.4 串口调试:printf重定向和不定长数据接收的坑

调试信息是嵌入式项目里最重要的观察手段,我把它当成这块板子的"眼睛"。为了能在串口助手里直接打印温度、占空比和模式,我把printf重定向到UART1。

重定向的核心是重写fputc函数,很多教程里直接在fputc里调用HAL_UART_Transmit发送一个字节,这在简单场景下没问题,但有个隐患:printf是同步阻塞的,如果串口波特率很低、数据量又大,主循环的执行周期会被拖慢。我实际是在调试时会保持115200波特率,printf输出一帧几十字节也就几毫秒,影响可以接受。

串口接收方面,如果你只是想调试,其实用一个简单的接收回调就够了。但如果想上位机发指令来改阈值参数(比如串口发"T_START=31"),那就必须处理不定长数据。这里推荐用串口空闲中断(IDLE Line Interrupt),DMA接收一帧数据后,在空闲中断里判断数据长度和帧格式。这个方法的代码量大一些,但彻底解决了"怎么知道一帧数据什么时候结束"的问题。

对应的过热词里有人搜索"stm32 hal库串口空闲中断"和"stm32串口接收不定长数据",说明这块确实是很多人的痛点。我自己的代码是在CubeMX里开启UART1的DMA接收,然后在中断回调里检查收到的一帧数据,解析出需要修改的参数。这样调试效率能大幅提升,不用每次都烧录固件去换阈值。

6. 实测数据与踩坑记录:让风扇稳定运转的五个细节

6.1 实测数据:从30°C到42°C的转速变化

把整个系统装好后,我用可调加热源(一台小型暖风机)在距离温度探头约30cm的地方做了一组实测。记录如下:

环境温度(°C)ADC原始值(滤波后)计算占空比(%)风扇实测转速(目测)
26.528030停
30.2256130低转速
33.8229447中转速
37.5202666高转速
42.31702100满速

实测结果基本符合线性映射的预期,温度变化时风扇转速没有出现跳变,也没有出现温度在阈值附近来回抖动的问题。在做了回差处理后,风扇从开始转到停转,温度跨度大约4°C左右,人耳听到的变化是平滑的。

6.2 五个容易翻车的细节:每一个我都踩过

第一,ADC采样时间太短导致读数偏低。默认的1.5周期采样时间对高内阻信号源来说完全不够,改成55.5周期后读数的线性度才正常。这个在CubeMX里很容易忽略,因为配置界面不会弹警告。

第二,PWM频率太低导致风扇啸叫。最初我按LED调光习惯把频率设在1kHz,风扇一开就是刺耳的噪声。改成25kHz后整个世界清净了。测试不同频率后,我确定本项目的风扇在25kHz最合适。

第三,续流二极管没有接导致MOS管损坏。我有一块实验板一开始没接二极管,正常工作时没问题,但只要连续开关风扇几十次,MOS管就开始发热,最终击穿。接上1N5819后这个问题完全消失。

第四,HAL_Delay出现在非主循环的上下文导致系统卡死。有一次我在定时器中断回调里加了HAL_Delay做消抖,结果整个系统复位死循环,现象就是程序动不动就"假死"。调试了很久才发现是延时函数在中断里阻塞了系统滴答定时器。永远不要在中断里使用HAL_Delay,这是HAL开发中最常见的坑之一。

第五,按键上拉配置错误导致按键不灵敏。PB0/PB1如果不配置内部上拉,按键按下时引脚状态不确定,扫描结果时好时坏。在CubeMX里选中GPIO_PULLUP,问题立刻解决。顺手把ADC引脚的输入模式设为模拟模式,否则漏电流会影响读数精度。

6.3 这几个方向值得继续扩展

这个项目做完之后,我很自然的想法就是继续升级。有几个方向我现在就在做,给大家参考:

一是加转速反馈。4线风扇一般带FG测速线,把FG线接到STM32的定时器输入捕获引脚,用"stm32定时器捕获测频率"的方式实时计算转速,然后根据目标转速做真正的闭环控制。这样即使电源电压波动、风扇轴承阻力变化,转速也能稳定在设定值附近。

二是把温度控制升级成PID。现在的线性映射是开环的,简单够用但不算精确。如果想要"稳定在某个目标温度",需要PID控制器,把温度误差映射成占空比增量。配合串口打印数据可以用串口绘图工具看响应曲线。

三是加一个蜂鸣器报警。温度超过50°C时蜂鸣器鸣叫,提醒环境异常。这个很实用,可以防止夏季设备过热导致故障。

四是把显示数据加上历史曲线。使用OLED的极低刷新率限制,画一个简化版的温度趋势折线图,大概能显示最近几十秒的温度走势。

6.4 最后分享一个小技巧:把调试信息做成"活数据"

调试完这个项目,我最满意的一个小改进是在串口输出里加了一个简单的帧格式,比如输出T=30.2 D=45 M=A\r\n,这样可以配合串口波形显示工具直接画出温度和占空比的实时曲线。调参的时候不用再频繁盯着OLED看,而是看曲线就能直观判断控制逻辑是否合理。

举个例子,实测时我通过曲线看到温度到达阈值后风扇占空比直接跳到30%,但由于风扇有启动惯性,转速其实滞后了大概1秒才上去。后来在代码里加入软启动和回差后,曲线的台阶感明显消失了,这远比肉眼观察风扇转得快慢要靠谱得多。

这个项目整体做下来,你会发现它把一个完整的嵌入式开发流程走了一遍:需求分析、硬件选型、原理设计、代码实现、软硬件联调、参数标定、功能扩展。每一个环节都有"看起来简单但做起来有坑"的地方,而真正涨经验的恰恰是这些坑。如果你也正在做这个项目,建议遇到问题先从时序和频率上找原因,不要一上来就怀疑代码逻辑。

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

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

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

立即咨询