给笔记本做了一套 ESP32 蓝牙温控风扇:电脑自己上报温度,风扇自己调速,不用的时候自动停下来。
折腾这个主要是嫌原来的散热底座太吵 —— 不管电脑烫不烫,扇子从头转到尾。改成 ESP32 控速之后,轻载时基本听不见声音,跑重活才拉起来。
实际使用演示
️ 效果预览
[image: ESP32智能温控风扇]
整体就是一台压风式散热器 + 一块 ESP32 控制板。PC 上的小工具读取 CPU / GPU 温度,通过蓝牙发过来,板子按温度调风扇转速,OLED 上实时显示两路温度和当前转速。
系统组成
[image: 系统架构]
整套分三段:
PC 端 — Windows 上的常驻小工具,读温度、发数据
ESP32 端 — 接收温度、算转速、驱动风扇、刷新屏幕
执行部分 — MOSFET 驱动电路 + PWM 风扇
通信走蓝牙串口,数据是纯文本格式,一行一条:
CPU52.3
GPU47.8
板子收到合法数据会回一个 ACK。这个确认很重要 —— 蓝牙串口经常出现「端口能打开、数据也能写进去,但对端根本没反应」的假连接,有没有 ACK 是判断链路真假的唯一依据。
️ 它是怎么工作的
[image: 工作流程]
从温度到转速的完整链路:
PC 端读取 CPU 和 GPU 温度,各取自身最高的传感器读数
每 2 秒通过蓝牙发送一次
ESP32 校验格式和取值范围,通过后回 ACK
取两路温度里较高的那个,查温度曲线得到目标转速
按限速规则平滑逼近目标,输出 PWM
屏幕同步刷新
温度到转速的对应关系是条连续曲线,中间做线性插值:
温度
55°C
60°C
65°C
70°C
75°C
80°C
转速
16%
23%
31%
40%
75%
100%
70°C 以下走得很平缓,日常办公基本在 20%~30% 之间,几乎听不见;75°C 开始往上拉,80°C 直接满速。曲线陡的那段留给真正需要散热的时候。
转速变化有限速:升速每秒最多 10%,降速每秒最多 3%。降速比升速慢是刻意的 —— 温度刚掉一点就把风撤掉,容易来回震荡,听感上就是风扇忽快忽慢。
️ PC 端做了什么
上位机是个 Python 小工具,打包成单文件,常驻系统托盘。
温度读取
调用 OpenHardwareMonitor 读取硬件传感器。CPU 上通常有好几个温度点(核心、封装、主板),取最高的那个更保险,避免低估了真正的热点。GPU 则同时兼容 NVIDIA / AMD / Intel 三家。读数超出 0~120°C 范围的一律丢掉,防止传感器抖动把异常值传下去。
找串口
不会死等某一个固定的 COM 口。优先用配置里指定的,找不到就按设备特征匹配,再找不到就扫所有带蓝牙字样的串口,最后兜底遍历全部可用端口。
打不开、写超时,或者连上了但收不到 ACK,都会把这个口标记成「坏的」,冷却 30 秒以上再跳过,去试下一个。有意思的是,一旦某个口回了 ACK,它的冷却标记会立刻解除 —— 说明它其实是好的,先前的失败可能只是暂时的。
休眠唤醒后自动恢复
电脑睡一觉醒来,蓝牙串口的状态其实是坏的 —— 端口句柄还在,底层链路已经断了。程序靠循环间隔判断这件事:间隔超过 8 秒就认为刚从休眠恢复,于是主动断开重连、重新初始化硬件监控,最长给 20 秒的恢复时间。
开机自启
首次运行会自动写开机启动项,不需要管理员权限。程序换目录了会自动更新路径;发现别处有指向自己的重复启动项也会顺手清掉。用源码方式跑的时候不动启动项,避免把开发机搞乱。
托盘图标
托盘图标本身就是一张实时温度曲线 —— 最近 30 个点连成的折线,按温度区间变色(低是绿色、中间橙色、高了红色),下面还标着 CPU 和 GPU 的整数温度。不用点开窗口,瞄一眼托盘就知道风扇跟没跟上。
ESP32 端做了什么
固件只做温控这一件事 —— 按键、手动模式、自定义曲线全部去掉了。目标是装好之后就不用再碰它。
数据校验
每一行都严格检查:前缀必须是 CPU 或 GPU、数值要能完整解析、数字后面不能拖多余字符、必须在 0~120°C 之间。任何一项不过关,整行丢掉,等下一帧。
蓝牙是流式传输,一帧可能被拆成几段到达。固件只认完整的换行符;如果缓冲区里有半截数据、超过 1 秒没等到后续,就把这半截丢掉重来 —— 不会把两次会话的数据拼在一起,也不会因为一个半包把接收卡死。
两路温度分开计时
CPU 和 GPU 各自独立计时,超过 12 秒没更新就判定这一路失效。这么做是因为有些平台的独显在休眠时会停报温度 —— 如果两路共用一个超时,CPU 明明正常也会被 GPU 拖累成「失效」,把风扇误停了。分开之后,GPU 掉线不影响 CPU 继续控温。
平滑调速
目标转速不会一步到位,而是每 100ms 往目标靠一点,配合前面说的升降速限制。温差超过 80°C 时例外 —— 直接满速,不讲平滑,先压温度。
另外风扇冷启动时先用 30% 转 1 秒再进入曲线,因为有些风扇占空比太低根本起转。
自动休眠
这是我最想要的一个功能。判断分两层:
蓝牙客户端消失 —— 电脑关机、断连、走远了,风扇立刻停
数据超时 —— 连接还在但 12 秒没数据,同样停
风扇是立刻停的,屏幕则要显示一个 3、2、1 的倒计时再关。多亮这 3 秒是有用的:能让人看明白设备是「因为没数据才停的」,而不是以为它死机了。倒计时只在一次有效通信结束时触发一次,不会反复闪。
蓝牙自动重连
蓝牙偶发假死时,固件有一次有限度的自恢复:只在曾经通过信、且连续 60 秒收不到数据时触发,重启一次蓝牙协议栈然后等电脑自己连回来。
这里有个我踩过的坑 —— 早期版本掉线后会反复重启蓝牙,结果电脑正常休眠(本来就是该断开)时,风扇端却在不停重启、反复广播。现在的策略是「断开了就安静等着」,电脑醒了会自己连回来,设备不需要做多余动作。一个通信周期内只恢复一次。
残留数据清理
这个是解决一个很具体的故障:快速重连的时候,新连接可能读到上一次会话的残留数据,导致风扇按错误的旧温度跑。
现在每次检测到断开(包括断开恰好发生在快速重连期间的情况),都会清掉两路温度的有效标志、清空接收缓冲、把蓝牙缓存里剩的字节一次性读空。之后只接受新到达的帧。
OLED 显示
128×64 的屏幕,左右两栏大字显示 CPU 和 GPU 温度,底下一行是连接状态(CON)、当前转速(SPEED xx%)和模式(QUIET)。屏幕刷新 200ms,温度无效时显示 --。
顺带一提,OLED 初始化失败的话温控照常跑 —— 显示只是附属功能,不该影响散热。
硬件接线
ESP32 引脚
接到哪
GPIO 5
风扇 MOS 管栅极(PWM 信号)
5V
OLED VCC
GND
共地
GPIO 21
OLED SDA
GPIO 22
OLED SCL
PWM 频率用 15kHz,超出人耳敏感范围,能少一点驱动噪声。
风扇一定要经过 MOSFET 驱动,GPIO 只能出毫安级的信号,直接带不动风扇。另外 ESP32 和风扇电源必须共地。
更新记录
2026.09.11 首次发布
ESP32 固件:保留 Quiet 自动温控,移除按键与手动模式
新增自动休眠:无连接 / 无数据自动停风扇、关屏
新增蓝牙自动连接:断线后单次恢复,不周期性重启
新增残留数据清理:断开即清,只接收新帧
上位机:Python 重写,托盘常驻 + 实时温度曲线
温控曲线:七锚点连续插值,80°C 满速
️ 项目地址
完整源码与可执行文件都在仓库里
https://github.com/gukey/ESP32-CPU-FAN
仓库里有完整的 ESP32 固件源码、PC 端源码、编译好的可执行文件、依赖 DLL,以及测试代码。想自己改的话直接拿源码改就行。
刷写教程和接线细节仓库里都有说明,硬件部分照着接就行。
️ 注意事项
刷固件时一定要先断开散热器的 12V 电源线。USB 的 5V 和 12V 同时接上会冲突,可能烧电脑 USB 控制器甚至烧板子。
上位机是没签名的程序,Windows 或杀毒软件可能报警。介意的话可以先看源码再运行。
温度数据来自硬件传感器,受驱动和主板差异影响可能有偏差。这只是散热辅助,不能替代主板自身的过热保护。
风扇的最低起动占空比各不相同,如果 30% 转不起来,需要在源码里把这个值调高重新烧录。
关于来源
这套东西的原始方案来自 究物研社 —— 压风式散热器的思路、风扇电路模块都是他们做的。ESP32 温控部分是 DALIAN213 的方案。
我这个版本是在他们的基础上继续改的,主要补充了自动休眠、蓝牙自动连接和残留数据清理这几块。借这个机会谢谢两位。
原作者方案说明:https://www.bilibili.com/video/BV1Lr421M7u2