尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
树莓派GPIO实战:DS18B20温控风扇PWM调速与回差控制
1. 项目缘起与整体设计思路1.1 为什么选择树莓派做温控风扇夏天一到书房里那台常年跑着备份任务的小主机就开始发烫摸上去外壳能到五六十度。一开始我图省事直接拿了个USB小风扇对着吹结果噪音大不说还费电关键是温度降下来之后风扇还在那呼呼转纯属浪费。后来手头正好有块吃灰的树莓派就想着能不能让它根据温度自动控制风扇启停和转速既安静又省电。这个需求其实非常典型温度采集 → 逻辑判断 → 执行输出是一个完整的闭环控制。树莓派在这类场景里有天然优势——它自带GPIO引脚能直接读取数字温度传感器也能通过PWM信号控制风扇转速不需要额外买单片机或者PLC。而且树莓派本身就跑着Linux写个Python脚本挂后台开机自启完全不用管。这个项目适合谁呢如果你刚接触树莓派想找一个有实际用途、难度适中、能学到GPIO操作和PWM控制的练手项目那这个温控风扇非常合适。它不像点个LED灯那么玩具化也不像搭建NAS那么复杂属于“跳一跳够得着”的难度。整个项目涉及的知识点包括单总线协议读取温度、PWM脉冲宽度调制、Python脚本编写、系统服务配置基本上把树莓派基础外设操作都串了一遍。1.2 核心方案选型与对比做温控风扇核心要解决三个问题怎么测温度、怎么控风扇、怎么定策略。每个问题都有几种常见方案我把自己试过和调研过的整理了一下。温度采集方案对比方案传感器型号接口方式精度成本推荐度单总线数字温度DS18B201-Wire±0.5°C低强烈推荐模拟温度LM35ADC±0.5°C低需额外ADC温湿度一体DHT11/DHT22单总线±2°C/±0.5°C低湿度可参考I2C温度BMP280I2C±1°C中兼测气压DS18B20是我最推荐的原因很简单数字输出、精度够用、防水探头版本便宜、一根线能挂多个。它用的是单总线协议树莓派内核直接支持只需要在配置里打开1-Wire接口就能在/sys/bus/w1/devices/下面看到温度值连驱动都不用装。DHT11虽然便宜但精度差、响应慢测温度还行测湿度凑合做温控有点勉强。风扇控制方案对比方案控制方式调速电路复杂度适用场景继电器开关GPIO高低电平不可调极低只开停三极管/MOS管PWM可调低直流风扇电机驱动模块PWM方向可调中大功率风扇成品PWM风扇直接PWM可调极低4线风扇如果你手头是普通的2线直流风扇用MOS管或者NPN三极管做开关配合PWM就能调速。如果是电脑上那种4线PWM风扇那就更简单了直接接树莓派的PWM引脚连驱动电路都省了。我这次用的是5V小风扇加一个NPN三极管S8050的方案成本几毛钱效果很稳。控制策略对比最简单的策略是阈值开关温度高于某个值就开风扇低于某个值就关。但这样有个问题——温度在阈值附近波动时风扇会频繁启停听着很难受。所以我加了一个回差迟滞比如高于45°C开低于40°C才关中间5°C的缓冲区。再进一步就是PWM线性调速温度越高转速越快这样噪音和散热能取得平衡。提示回差这个概念在温控里非常重要没有回差的控制系统基本都会振荡。你可以把它理解成“开和关用两个不同的阈值”避免在临界点反复横跳。1.3 系统架构与数据流整个系统的数据流很清晰我画不了图就用文字描述一下温度采集层DS18B20传感器通过单总线协议把温度数据传给树莓派内核内核在/sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave文件里暴露原始数据。数据处理层Python脚本定时读取这个文件解析出摄氏度数值做滑动平均滤波去掉毛刺。决策控制层根据当前温度和预设的阈值、回差、PWM映射曲线计算出风扇应该转多快。执行输出层通过RPi.GPIO或者gpiozero库向指定GPIO引脚输出PWM信号控制三极管导通程度从而调节风扇转速。持久化层脚本以systemd服务方式运行开机自启日志写到文件里方便排查。这个架构的好处是每一层都可以独立替换。比如你以后想换成I2C温度传感器只需要改采集层想换成继电器控制只需要改执行层。逻辑层和控制层基本不用动。2. 硬件准备与接线实操2.1 物料清单与选型建议先列一下我这次用到的东西都是很常见的件某宝上一搜一大把树莓派一块3B、4B、Zero 2W都行只要有40针GPIODS18B20温度传感器一个建议买防水探头版测环境温度或者贴在水冷头上都方便4.7kΩ电阻一个单总线需要上拉5V直流小风扇一个我用的4010规格电流0.1A左右NPN三极管S8050一个或者MOS管IRLZ44N大功率风扇用这个1kΩ电阻一个三极管基极限流1N4148二极管一个续流保护面包板、杜邦线若干选型上有几个点要注意。风扇的电压和电流一定要看清楚树莓派的5V引脚能输出的总电流有限Model B系列大概1A左右Zero系列更少。如果你风扇电流超过0.5A建议单独供电别直接从树莓派取电否则容易把树莓派搞重启。三极管的选择也要看风扇电流S8050最大集电极电流是0.5A小风扇够用大风扇就换MOS管比如IRLZ44N能扛几十安培而且导通电阻小发热低。注意DS18B20的防水探头版通常有三根线——红VCC、黑GND、黄DATA。普通TO-92封装的三根引脚分别是GND、DATA、VCC面对平面从左到右。买的时候看清楚接反了不会烧但读不出数。2.2 接线步骤与电路原理接线分两部分传感器部分和风扇驱动部分。DS18B20接线传感器VCC接树莓派3.3V物理引脚1或17传感器GND接树莓派GND物理引脚6、9、14等传感器DATA接树莓派GPIO4物理引脚7这是单总线默认引脚在DATA和VCC之间跨接一个4.7kΩ上拉电阻这个上拉电阻是必须的因为单总线协议是开漏输出没有上拉电阻的话总线拉不高数据读不出来。4.7kΩ是经验值太小了功耗大太大了上升沿变缓通信不稳定。风扇驱动接线NPN三极管方案风扇正极接树莓派5V物理引脚2或4风扇负极接三极管集电极C极三极管发射极E极接树莓派GND三极管基极B极通过1kΩ电阻接树莓派GPIO18物理引脚12这个引脚支持硬件PWM在风扇正负极之间反接一个1N4148二极管阴极接正极阳极接负极这里解释一下原理。三极管工作在开关状态时基极给高电平集电极和发射极导通风扇通电基极给低电平三极管截止风扇断电。但我们要调速所以基极给的是PWM信号——快速开关通过调节占空比来控制平均电压。比如50%占空比风扇就得到大约2.5V的平均电压转速大概是一半。那个续流二极管是保护三极管的。风扇是感性负载断电瞬间线圈会产生反向电动势电压可能高达几十伏没有二极管的话容易把三极管击穿。二极管给反向电流提供泄放回路这个钱不能省。提示GPIO18是树莓派上少数支持硬件PWM的引脚之一另外还有GPIO12、13、19。硬件PWM比软件PWM稳定得多不会因为系统负载高而抖动。如果你用gpiozero库它默认会优先用硬件PWM。2.3 树莓派系统配置与1-Wire启用硬件接好之后需要在系统层面打开1-Wire接口。树莓派官方系统Raspberry Pi OS从某个版本开始用raspi-config就能搞定。sudo raspi-config进去之后选Interface Options→1-Wire→Yes然后重启。重启后检查一下ls /sys/bus/w1/devices/如果看到类似28-00000xxxxxxx的目录说明传感器被识别了。进去读一下cat /sys/bus/w1/devices/28-00000xxxxxxx/w1_slave正常输出长这样c3 01 4b 46 7f ff 0c 10 1c : crc1c YES c3 01 4b 46 7f ff 0c 10 1c t28125t28125表示28.125°C。如果显示crc... NO说明通信有问题大概率是上拉电阻没接好或者线太长。如果你用的是较新的系统可能还需要在/boot/firmware/config.txt里手动加一行dtoverlayw1-gpio然后重启。这个overlay会把GPIO4配置成单总线模式。有些教程说加gpiopin4参数其实默认就是4不加也行。3. 控制脚本编写与核心逻辑3.1 温度读取与数据滤波读温度这件事内核已经帮我们做好了Python脚本只需要读文件、解析字符串。但直接读原始文件有个问题偶尔会读到无效值比如85°C这是DS18B20的上电默认值或者CRC校验失败。所以必须做校验和滤波。我一般用滑动平均滤波取最近5次的平均值。这样既能去掉毛刺又不会引入太大延迟。代码如下import glob import time def read_temp_raw(device_file): with open(device_file, r) as f: lines f.readlines() return lines def read_temp(device_file): lines read_temp_raw(device_file) # 检查CRC校验 while lines[0].strip()[-3:] ! YES: time.sleep(0.2) lines read_temp_raw(device_file) equals_pos lines[1].find(t) if equals_pos ! -1: temp_string lines[1][equals_pos2:] temp_c float(temp_string) / 1000.0 return temp_c return None def get_filtered_temp(device_file, window5): temps [] for _ in range(window): t read_temp(device_file) if t is not None and -10 t 80: # 合理范围过滤 temps.append(t) time.sleep(0.1) if not temps: return None return sum(temps) / len(temps)这里有几个细节。CRC校验必须做w1_slave文件第一行末尾的YES或NO就是校验结果NO说明数据不可信。范围过滤也很重要DS18B20上电时可能返回85°C这个值明显不合理直接扔掉。滑动窗口取5次每次间隔0.1秒总共0.5秒对温控来说完全够快。注意DS18B20的转换时间默认是750ms如果你读得太快可能读到的是上一次的转换结果。不过内核驱动会处理这个我们读文件时它已经等转换完成了。如果你发现读数变化很慢可以检查一下是不是转换精度设得太高。3.2 PWM调速与温度映射曲线PWM调速的核心是占空比映射。假设我们设定温度 ≤ 40°C风扇停转占空比0%温度 ≥ 55°C风扇全速占空比100%40°C到55°C之间线性映射映射公式很简单duty (temp - T_low) / (T_high - T_low) * 100但实际用的时候我发现线性映射在低温段噪音偏大。因为风扇在低占空比时可能转不起来或者有嗡嗡声。所以我改成分段映射温度区间占空比说明 40°C0%停转40-42°C30%低速静音42-45°C50%中速45-50°C75%高速 50°C100%全速这样每个区间内占空比固定风扇不会频繁变速噪音更稳定。而且30%的起始占空比能保证风扇能转起来不会卡住。代码实现import RPi.GPIO as GPIO FAN_PIN 18 GPIO.setmode(GPIO.BCM) GPIO.setup(FAN_PIN, GPIO.OUT) pwm GPIO.PWM(FAN_PIN, 100) # 100Hz频率 pwm.start(0) def temp_to_duty(temp): if temp 40: return 0 elif temp 42: return 30 elif temp 45: return 50 elif temp 50: return 75 else: return 100 def update_fan(temp): duty temp_to_duty(temp) pwm.ChangeDutyCycle(duty) return dutyPWM频率我选的100Hz。这个频率下风扇不会有可闻的啸叫三极管开关损耗也可以接受。如果你用4线PWM风扇频率建议设到25kHz以上超出人耳听觉范围更安静。3.3 回差控制与状态机设计前面说了回差的重要性这里具体实现一下。我用一个状态变量记录风扇当前是开还是关然后根据温度和回差决定是否切换状态。fan_on False TURN_ON_TEMP 45.0 TURN_OFF_TEMP 40.0 def control_logic(temp): global fan_on if not fan_on and temp TURN_ON_TEMP: fan_on True elif fan_on and temp TURN_OFF_TEMP: fan_on False if fan_on: return temp_to_duty(temp) else: return 0这个逻辑的意思是风扇关着的时候温度必须升到45°C才开风扇开着的时候温度必须降到40°C才关。中间5°C的区间里风扇保持当前状态不变。这样就避免了在42°C附近反复启停。实际跑下来这个策略非常稳。温度从40°C升到45°C大概需要几分钟风扇开启后温度会慢慢降回40°C然后停转。整个循环周期大概十几分钟风扇启停频率很低寿命也有保障。提示回差的大小要根据你的散热条件和环境温度来调。如果风扇一开温度就猛降回差可以设小一点比如3°C如果散热很慢回差设大一点比如8°C避免风扇长时间全速运转。3.4 完整脚本与systemd服务配置把上面的代码拼起来加上主循环和日志就是一个完整的脚本了。我把它保存为/home/pi/fan_control.py#!/usr/bin/env python3 import glob import time import logging import RPi.GPIO as GPIO logging.basicConfig( filename/home/pi/fan_control.log, levellogging.INFO, format%(asctime)s - %(message)s ) FAN_PIN 18 TURN_ON_TEMP 45.0 TURN_OFF_TEMP 40.0 CHECK_INTERVAL 5 # 秒 GPIO.setmode(GPIO.BCM) GPIO.setup(FAN_PIN, GPIO.OUT) pwm GPIO.PWM(FAN_PIN, 100) pwm.start(0) device_file glob.glob(/sys/bus/w1/devices/28-*/w1_slave)[0] def read_temp(): with open(device_file, r) as f: lines f.readlines() while lines[0].strip()[-3:] ! YES: time.sleep(0.2) with open(device_file, r) as f: lines f.readlines() equals_pos lines[1].find(t) if equals_pos ! -1: return float(lines[1][equals_pos2:]) / 1000.0 return None def temp_to_duty(temp): if temp 40: return 0 elif temp 42: return 30 elif temp 45: return 50 elif temp 50: return 75 else: return 100 fan_on False try: while True: temp read_temp() if temp is None: time.sleep(CHECK_INTERVAL) continue if not fan_on and temp TURN_ON_TEMP: fan_on True logging.info(fFan ON at {temp:.2f}C) elif fan_on and temp TURN_OFF_TEMP: fan_on False logging.info(fFan OFF at {temp:.2f}C) duty temp_to_duty(temp) if fan_on else 0 pwm.ChangeDutyCycle(duty) logging.info(fTemp{temp:.2f}C Duty{duty}%) time.sleep(CHECK_INTERVAL) except KeyboardInterrupt: pass finally: pwm.stop() GPIO.cleanup()然后配置systemd服务让它开机自启。创建/etc/systemd/system/fan-control.service[Unit] DescriptionTemperature Controlled Fan Aftermulti-user.target [Service] Typesimple ExecStart/usr/bin/python3 /home/pi/fan_control.py Restartalways Userpi [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable fan-control.service sudo systemctl start fan-control.service检查状态sudo systemctl status fan-control.service如果看到active (running)就说明跑起来了。日志可以用tail -f /home/pi/fan_control.log实时看。4. 调试与常见问题排查4.1 温度读不到怎么办这是最常见的问题现象是/sys/bus/w1/devices/下面没有28-开头的目录。排查顺序如下检查接线VCC、GND、DATA有没有接错上拉电阻有没有接。用万用表量一下DATA对VCC的电阻应该是4.7kΩ左右。检查1-Wire是否启用lsmod | grep w1看看有没有w1_gpio和w1_therm模块。没有的话回去raspi-config重新开。检查config.txtcat /boot/firmware/config.txt | grep w1确认有dtoverlayw1-gpio。换引脚如果GPIO4被其他功能占用了可以换到别的引脚在config.txt里加dtoverlayw1-gpio,gpiopin17。线太长单总线对线长敏感超过1米就容易出问题。换短一点的线或者降低上拉电阻到2.2kΩ。注意有些树莓派系统版本里1-Wire和摄像头、音频功能有冲突。如果你同时用了这些功能可能需要调整overlay的加载顺序。4.2 风扇不转或转速异常风扇不转先分清楚是电路问题还是控制问题。风扇完全不转用万用表量三极管基极电压如果PWM输出时电压在0-3.3V之间变化说明控制信号正常问题在驱动电路。检查三极管引脚有没有接反S8050的引脚顺序是E-B-C面对平面从左到右别搞错了。风扇一直全速转三极管击穿了集电极和发射极短路。换一个三极管并检查续流二极管有没有接反。风扇低速时嗡嗡响PWM频率太低调到100Hz以上试试。或者起始占空比太低风扇转不起来把最低占空比提到30%。风扇转速不随温度变化检查temp_to_duty函数的映射区间可能温度一直没进入调速区间。看日志确认温度值。还有一个坑树莓派的5V引脚和GPIO的3.3V电平不匹配。三极管基极需要约0.7V就能导通3.3V完全够用。但如果你用MOS管要注意选逻辑电平MOS管比如IRLZ44N它的开启电压是2V左右3.3V能驱动。普通MOS管如IRF540需要10V才能完全导通用3.3V驱动会发热严重。4.3 系统负载与PWM稳定性树莓派上跑PWM如果是软件PWM系统负载高的时候占空比会抖动风扇转速忽快忽慢。所以一定要用硬件PWM引脚。树莓派上支持硬件PWM的引脚是GPIO12、GPIO13、GPIO18、GPIO19其中GPIO18和GPIO13是同一路PWMGPIO12和GPIO19是另一路。用RPi.GPIO库时GPIO.PWM会自动选择硬件PWM如果引脚支持。用gpiozero库的话PWMOutputDevice也会优先用硬件PWM。但如果你同时用了音频输出GPIO18可能被音频占用这时候要换到GPIO13。另外检查间隔不要设得太短。我设的5秒对温控来说足够了。设成0.1秒的话CPU占用会上去而且温度变化没那么快纯属浪费。日志也不要写太频繁不然SD卡很快就写满了。可以改成只在状态切换时写日志或者用logging的RotatingFileHandler做日志轮转。4.4 常见问题速查表现象可能原因解决方法温度读不到1-Wire未启用raspi-config开启并重启温度读不到上拉电阻缺失DATA和VCC之间加4.7kΩ温度值85°C上电默认值等待转换完成再读温度值跳变电源干扰加滤波电容缩短线长风扇不转三极管接反检查E-B-C引脚顺序风扇全速三极管击穿更换三极管检查二极管风扇嗡嗡响PWM频率低调到100Hz以上转速不变化映射区间不对检查温度是否进入调速区服务启动失败路径或权限问题检查ExecStart路径和User日志文件过大写入太频繁用RotatingFileHandler5. 进阶优化与扩展思路5.1 用PID算法做更平滑的调速分段映射虽然简单但温度在区间边界时风扇转速会跳变。如果你追求更平滑的控制可以上PID算法。PID通过比例、积分、微分三项计算输出能让温度稳定在目标值附近风扇转速连续变化。不过PID调参是个体力活而且温控系统有大惯性积分项容易饱和。我的建议是先用分段映射跑起来觉得不够再上PID。对于风扇散热这种场景分段映射的体验已经足够好了没必要为了平滑而平滑。5.2 加个OLED屏幕显示温度手头有SSD1306 OLED屏的话可以用I2C接上实时显示当前温度和风扇转速。用luma.oled库几行代码就能驱动。这样不用登录树莓派就能看到状态调试的时候特别方便。5.3 接入Home Assistant做远程监控如果你家里有Home Assistant可以把树莓派的温度数据通过MQTT发出去在HA里做个仪表盘还能设置自动化——比如温度超过50°C时手机推送告警。树莓派上装个mosquitto-clientsPython脚本里加几行paho-mqtt的代码就行。5.4 多传感器与多点测温DS18B20支持一根总线挂多个传感器每个都有唯一的64位ID。你可以同时在CPU散热片、机箱内部、环境各放一个取最高值或者加权平均值来控制风扇。代码上只需要遍历/sys/bus/w1/devices/下所有28-目录分别读取即可。我个人在实际操作中的体会是这个项目最大的价值不在于温控本身而在于它把传感器读取、PWM输出、服务配置、日志排查这一整套流程都串了一遍。做完这个你再做其他树莓派项目比如环境监测、智能家居节点基本就是换传感器和换执行器的区别。硬件上花不了几十块钱但学到的东西能复用很久。最后再分享一个小技巧脚本里的温度阈值和映射区间最好做成配置文件或者环境变量这样改参数不用动代码重启服务就行省得每次都要编辑Python文件。
RELATED

相关推荐

三步跑通属于你自己的免费 LLM API:FreeLLMAPI 本地部署与接入完整教程

三步跑通属于你自己的免费 LLM API:FreeLLMAPI 本地部署与接入完整教程

三步跑通属于你自己的免费 LLM API:FreeLLMAPI 本地部署与接入完整教程 【免费下载链接】freellmapi 7.4 billion tokens per month. 34 free LLM providers. 635 free model endpoints. All behind one /v1 endpoint, plus any custom OpenAI-compatible endpoint.…

📅 2026/10/19 1:30:25
RocooFix静态修复完全指南:补丁如何注入ClassLoader实现代码替换?6步拆解完整流程

RocooFix静态修复完全指南:补丁如何注入ClassLoader实现代码替换?6步拆解完整流程

【免费下载链接】RocooFix another hotfix framework 项目地址: https://gitcode.com/gh_mirrors/ro/RocooFix 点击查看 免费下载 RocooFix 是一款轻量级 Android 热修复框架:线上出现 Bug 时,只需推送一个小型的 patch.jar,App …

📅 2026/10/19 1:30:25
TFLM在Arm MCU上的端侧AI部署实战:从模型转换到性能调优

TFLM在Arm MCU上的端侧AI部署实战:从模型转换到性能调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/19 1:20:01
MORE NEWS

更多资讯

📰

ABAP Cloud 的 OLTP 开发,从数据增删改查走向完整的业务事务

采购申请页面上的保存按钮,背后通常不只有一次数据库写入。申请抬头需要保存,行项目需要关联,金额需要计算,供应商需要校验,当前操作者还必须具备相应权限。只要其中一项没有满足业务要求,系统就不能把这张申请当成一份完整、有效的业务单据。 这类围绕业务数据开展读取…

📰

从业务模型到服务交付,读懂 ABAP Cloud 架构图中的开发逻辑与 Clean Core 边界

图里有两块同名的 Integration Services,一块位于顶部,另一块位于底部。它们的位置很有讲究。顶部表示我们的应用把业务能力提供给其他系统,底部表示我们的应用调用其他系统的业务能力。同一个应用既可以是服务提供者,也可以是服务消费者,而中间的业务模型和业务逻辑负责把…

📰

【码动四季·秋】从第一次 issue 到 Committer:一个普通开发者的开源参与路径(附真实晋升时间线)

本文为 AtomGit 码动四季开源同行征稿活动参与文章 摘要:从第一次 issue 到 Committer 的完整路径,恰好是教程最少、坑最多的部分。本文以本人 2024-2026 的真实晋升时间线为骨架,给出五阶段动作清单:选社区看三个健康指标&#x…

📰

Camera HAL 防抖—GME详解

Camera HAL 防抖—GME详解 文档定位:供开发、学习、问题定位使用。 本文与四篇 EIS 文档共享同一套「节点 + 算法库 + 陀螺仪」技术底座。GME 与 EISv3、GyroRNN 同源——三者都加载同一个算法共享库 com.qti.eisv3,但各自的节点封装、配置段、vendor tag 段、功能聚焦点各不…

📰

从数据交换到业务协同,理解 SAP ABAP 的集成应用场景

在 SAP 官方的差旅审批示例中,一条差旅记录的创建并不是业务流程的终点。运行在 SAP BTP ABAP environment 中的差旅应用创建记录后,需要启动 SAP Build Process Automation 中的审批流程,将差旅状态设置为待审批。审批人在任务收件箱中完成操作,审批结果再通过回调返回 AB…

📰

Spring Boot+MyBatis+Thymeleaf构建客栈管理系统:从建表到部署的全流程实践

做客栈管理系统这类项目,最怕的不是业务复杂,而是技术栈堆得花里胡哨,最后连部署都费劲。我最近给一家小型客栈做的后台,用的是一条很稳的路:Spring Boot 做应用骨架,MyBatis 管SQL,MySQL 存核心…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬