STM32F4温度传感器读取用于温控风扇

你有没有遇到过设备运行一会儿就“发烧”,风扇却还在慢悠悠转着,结果系统直接降频甚至死机?🔥 或者反过来——明明环境凉快得很,风扇却呼呼狂转,吵得人没法专注?😅

这背后其实就是 温控策略没做好 。而今天我们要聊的,就是一个既简单又聪明的解决方案:用 STM32F4 内置温度传感器 + PWM 调速风扇 ,打造一个静音、节能、响应迅速的闭环温控系统。

重点来了: 不需要外接任何温度芯片! 没有 DS18B20,也没有 NTC 热敏电阻,全靠 STM32F4 自带的“体温计”搞定。是不是听起来有点黑科技?😎 来吧,咱们一步步拆解这个小而美的设计。


先说结论:STM32F4 不仅性能强(Cortex-M4+FPU),还贴心地内置了一个模拟温度传感器,连到 ADC1 的第 16 通道上。只要打开开关,就能读出芯片附近的温度,再通过高级定时器输出 PWM 控制风扇转速——整个过程干净利落,硬件成本几乎为零 💸。

但别以为“内置”就意味着随便读读就行。实际用起来你会发现:
- 为什么每次上电读数都不太一样?
- 为什么待机时显示 50°C,明明手摸上去都没热?
- 风扇为啥启动困难,老是“卡一下”才转起来?

这些问题的答案,藏在几个关键细节里:ADC 配置、参考电压校正、两点校准、PWM 频率选择……我们一个个来看。


首先,那个所谓的“内部温度传感器”到底是个啥?

它其实是一段集成在芯片内部的模拟电路,输出电压随温度线性变化,斜率大约是 2.5mV/°C 。在常温 25°C 时,典型输出电压约 0.76V。这个信号被接到 ADC1_IN16 上,所以你得启用 ADC_CCR 寄存器里的 TSEN 位才能让它连通。

⚠️ 注意:它是测“芯片结温”的,不是环境温度!如果你的 MCU 正在跑浮点运算或驱动大负载,自己发热就会干扰测量结果。所以布局时尽量让它远离功率器件,必要时加个散热焊盘或者开窗 ↓

// 必须开启 TSEN 和 VREFINT 才能使用内部传感器
ADC->CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN;

而且每颗 STM32F4 出厂时都会在内存地址 0x1FFF7A2C 和 0x1FFF7A2E 写入两个校准值:
- TS_CAL1 :30°C 时的标准 ADC 值
- TS_CAL2 :110°C 时的标准 ADC 值

这两个值就是你的“黄金数据”,用来做线性拟合,大幅提高精度。不然光靠手册给的典型值,误差可能超过 ±5°C!


接下来是 ADC 配置。你以为随便初始化一下就能拿到准数?Too young too simple 😏

STM32F4 的 ADC 是 12 位的,理论上分辨率为 4096 级,但要发挥实力,必须注意三点:

  1. 采样时间要够长
    温度传感器输出阻抗较高,如果采样时间太短,充放电没完成就读数,结果肯定不准。建议至少设置为 ADC_SAMPLETIME_480CYCLES 。

  2. 关闭连续转换模式
    我们不需要一直采集,每隔几百毫秒来一次就够了,省电又稳定。

  3. 使用 VREFINT 校正 VDDA 波动
    很多人忽略这一点:你以为供电是稳定的 3.3V?实际上可能是 3.2V 或 3.4V。而 ADC 的参考电压正是 VDDA,一旦偏移,所有读数都跟着漂!

解决办法是同时读取 VREFINT 通道(内部 1.2V 基准) ,然后反推出真实的 VDDA 值:

float GetRealVdda(void) {
    uint32_t vref_adc = ReadAdcChannel(ADC_CHANNEL_VREFINT);
    // 出厂校准的 VREFINT 值存在 0x1FFF7A2A
    float vref_cal = *(uint16_t*)0x1FFF7A2A;
    return (3300.0f * vref_cal) / vref_adc;  // 单位:mV
}

有了真实 VDDA,再去算温度传感器的电压才靠谱。


现在进入重头戏:怎么把 ADC 数字值变成温度?

最简单的做法是套公式:

$ V_{sense} = \frac{ADC_VAL}{4095} \times V_{DDA} $

$ T = \frac{(V_{sense} - V_{25})}{0.0025} + 25 $

但这种方法依赖典型值,个体差异大。更稳的做法是利用那两个出厂校准点做线性插值:

$ T = 30 + \frac{(TS_CAL1 - ADC_DATA)}{(TS_CAL1 - TS_CAL2)} \times 80 $

这个公式的意思是:“我在 30°C 和 110°C 有两个已知点,当前读数在这两点之间哪?”——典型的两点标定法,工程中非常实用 ✅

完整函数可以这样写:

float ReadTemperature(void) {
    float vdda = GetRealVdda();  // 获取真实VDDA
    uint32_t adc_val;

    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    adc_val = HAL_ADC_GetValue(&hadc1);

    float voltage = (adc_val * vdda) / 4095.0f;

    int ts_cal1 = *(uint16_t*)0x1FFF7A2C;
    int ts_cal2 = *(uint16_t*)0x1FFF7A2E;

    float temp = 30.0f + ((float)(ts_cal1 - adc_val)) * (80.0f / (ts_cal1 - ts_cal2));

    return temp;
}

💡 小贴士:这个函数不要频繁调用!每次调用前确保 TSEN 已开启,并且留出至少 10μs 让信号稳定(HAL 库一般会处理)。建议每 500ms 采样一次就够了。


接下来轮到风扇控制了。现代直流风扇大多支持 4 线 PWM 调速 ,只需要一根 GPIO 输出方波即可调节转速。

关键参数是什么? 频率!

推荐使用 25kHz 左右 。为什么?因为低于 20kHz 人耳能听到“滋滋”声,特别烦;而高于 25kHz 就听不见了,实现“静音调速”。

STM32F4 的高级定时器 TIM1/TIM8 支持高精度 PWM 输出。假设主频 84MHz,我们可以这样配置:

TIM_HandleTypeDef htim1;

void MX_TIM1_PWM_Init(void) {
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 84 - 1;        // 分频后计数频率 = 1MHz
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 42 - 1;           // 1MHz / 42 ≈ 23.8kHz → 接近25k
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
}

void SetFanSpeed(uint8_t duty) {
    uint32_t pulse = (duty * (htim1.Init.Period + 1)) / 100;
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse);
}

📌 注意:MOSFET 驱动要用 N沟道增强型场效应管 (如 IRFZ44N),栅极串联 100Ω 电阻防振荡,漏极接风扇正极,源极接地,风扇另一端接 12V 电源。别忘了并联一个续流二极管(1N4007)保护 MOSFET!


到这里,硬件和驱动基本齐活了。但真正让系统“智能”的,是控制逻辑。

最基础的是 多段阈值控制 :

温度范围 风扇行为
< 35°C 关闭(0%)
35~50°C 低速(40%)
50~65°C 中速(70%)
≥ 65°C 全速(100%)

但这有个问题:当温度刚好卡在边界附近时,风扇会反复启停,产生“乒乓效应”。解决方法很简单:加 迟滞(Hysteresis) 。

比如设定:
- 风扇开启温度:40°C
- 风扇关闭温度:35°C

这样即使温度在 38°C 上下波动,也不会来回切换状态,系统更稳定 🛠️

更进一步,可以用 PID 控制器 实现平滑调速。误差 = 设定温度 - 实际温度,经过比例、积分、微分运算后输出 PWM 占空比。好处是响应快、无超调、节能明显,适合对噪音敏感的应用场景。

当然,如果你不想从头写 PID,也可以试试简单的 查表法 + 插值 ,比如:

static const struct { float temp; uint8_t duty; } fan_curve[] = {
    {30, 0}, {40, 30}, {50, 60}, {60, 85}, {70, 100}
};

根据当前温度查表插值,也能获得不错的线性体验。


最后聊聊实战中的那些“坑”和优化技巧 💡

🔧 问题 1:风扇启动无力?
有些风扇需要较大的启动扭矩。解决方案是加入“启动脉冲”:刚打开时先给 100% 占空比持续 500ms,等转起来了再切回目标速度。

🔧 问题 2:温度跳变严重?
原始 ADC 数据噪声大?上 滑动平均滤波 !维护一个长度为 5 的队列,每次取平均值:

float filtered_temp = 0;
for(int i = 0; i < 5; i++) filtered_temp += ReadTemperature();
filtered_temp /= 5;

🔧 问题 3:长期运行温度越来越高?
检查 PCB 布局!MCU 是否靠近电源模块?是否有足够散热铜皮?考虑加一个小导热垫把热量引出去。

🔋 低功耗场景怎么办?
可以让系统大部分时间处于 STOP 模式,由 RTC 定时唤醒(比如每 2 秒)进行一次温度采样,判断是否需要启动风扇。这对电池供电设备尤其重要。

🔌 想远程监控温度?
预留 UART 或 I2C 接口,把温度数据上报给主机或 Wi-Fi 模块,接入 Home Assistant 或云端平台,实现可视化监控 👀


总结一下,这套基于 STM32F4 的温控方案,看似简单,实则暗藏玄机:

✅ 利用片内资源,免去外部传感器,节省 BOM 成本
✅ 结合双点校准与 VREFINT 补偿,显著提升测温精度
✅ 使用高频 PWM 实现静音调速,用户体验更好
✅ 支持灵活控制策略(阈值、PID、曲线映射)

无论是小型工控柜、网络路由器、LED 电源模块,还是 DIY 的迷你主机散热系统,都能轻松胜任。

最关键的是—— 代码量不大,调试门槛低,适合新手练手,又能满足工业级需求 。简直是嵌入式工程师的“瑞士军刀”级项目 🔧✨

下次当你看到某个设备风扇傻乎乎地一直全速转时,不妨想想:也许,它只是缺了一个聪明的 STM32F4?😏

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐