树莓派多DS18B20温度传感器高速读取方案问询
核心结论先给
别做多线程读单传感器的方案,完全没用。1-Wire是半双工共享总线,同一时间只能处理一个通信请求,你开再多线程,内核驱动也会把IO请求串行排队,不仅提不了速,还可能因为并发访问sysfs节点出现CRC校验失败、读数错乱的问题,纯纯浪费精力。
你现在慢的根本原因是默认的读取逻辑每个传感器单独触发温度转换、单独等750ms转换完成,3个传感器串行下来光等转换就要2250ms,当然赶不上1秒刷新的要求。
最优提速方案(零成本,直接把总耗时压到750ms以内)
用DS18B20原生支持的全局温度转换指令:1-Wire协议支持主机发送一次通用转换命令,总线上挂的所有DS18B20会同时启动温度转换,等最长转换时间到了之后,再挨个读取每个传感器的暂存器拿结果——读暂存器单设备仅需几毫秒,3个加起来都不到20ms,总耗时直接从2250ms降到750ms+10ms,完全满足1秒刷新要求。
最省事的实现方式是直接开启w1-gpio驱动的异步模式:
- 编辑
/boot/config.txt,把原来的w1-gpio配置行改成:dtoverlay=w1-gpio,pullup=1,async=1 - 重启设备后,驱动会在后台自动按精度对应的周期,给所有传感器发全局转换指令,你读
w1_slave节点的时候直接返回最近一次转换完成的缓存值,完全没有阻塞等待,3个传感器挨个读也就几毫秒,1秒刷新留的余量非常足。
进一步提速的可选手段
- 降低测量精度压缩转换时间:DS18B20的转换时间和精度直接绑定:
- 12位(默认):最大750ms转换时间,分辨率0.0625℃
- 11位:最大375ms转换时间,分辨率0.125℃
- 10位:最大187.5ms转换时间,分辨率0.25℃
- 9位:最大93.75ms转换时间,分辨率0.5℃
如果你做的是涡轮机遥测场景,温度变化不需要0.0625℃的分辨率,降到11位就能把转换时间砍半,刷新频率甚至可以稳定到2Hz以上。精度配置可以直接写传感器的配置寄存器,永久生效。
- 后台线程缓存读数:如果不想改驱动配置,可以单独开一个守护线程,循环执行「触发全局转换→等待转换完成→批量读所有传感器值更新缓存」的逻辑,你的主仪表盘程序要读数的时候直接拿缓存里的最新值就行,完全不需要等IO,哪怕每秒请求10次都不会卡。
可直接复用的优化版代码
这个实现不依赖驱动异步模式,自带后台缓存、全局转换逻辑,直接替换你原来的DS18B20类就能用:
import os import glob import time import threading class DS18B20: def __init__(self, refresh_interval=0.8, precision=12): # 加载1-Wire内核驱动 os.system('modprobe w1-gpio pullup=1') os.system('modprobe w1-therm') # 不同精度对应的最大转换时间(留10ms冗余) self.conv_time_map = {9: 0.10, 10: 0.20, 11: 0.38, 12: 0.76} self.conv_time = self.conv_time_map.get(precision, 0.76) self.refresh_interval = refresh_interval # 扫描总线上所有DS18B20传感器 base_dir = '/sys/bus/w1/devices/' self.device_folders = glob.glob(base_dir + '28*') self._num_devices = len(self.device_folders) self._device_files = [f + '/w1_slave' for f in self.device_folders] # 温度缓存与线程锁 self._temp_cache = [None] * self._num_devices self._lock = threading.Lock() self._running = True # 启动后台轮询线程,设为守护线程随主进程退出 self._poll_thread = threading.Thread(target=self._background_refresh, daemon=True) self._poll_thread.start() def _trigger_all_convert(self): # 向总线发送全局转换指令,所有传感器同时开始测温 master_node = '/sys/bus/w1/devices/w1_bus_master1/w1_master_convert' try: with open(master_node, 'w') as f: f.write('1\n') except IOError: # 旧版驱动无该节点时,第一次读会自动触发转换,不影响逻辑 pass def _read_single_raw(self, index): try: with open(self._device_files[index], 'r') as f: return f.read() except IOError: return None def _parse_raw_temp(self, raw_data): if not raw_data or 'YES' not in raw_data: return None _, _, reading = raw_data.partition(' t=') # 过滤传感器上电默认的85℃错误值 if not reading or reading == '85000': return None return float(reading) / 1000.0 def _background_refresh(self): while self._running: self._trigger_all_convert() # 等所有传感器完成转换 time.sleep(self.conv_time) # 批量读取所有传感器结果 new_temps = [] for idx in range(self._num_devices): raw = self._read_single_raw(idx) new_temps.append(self._parse_raw_temp(raw)) # 线程安全更新缓存 with self._lock: self._temp_cache = new_temps # 计算剩余休眠时间,保证刷新间隔稳定 remain_sleep = self.refresh_interval - self.conv_time if remain_sleep > 0: time.sleep(remain_sleep) def tempC(self, index=0): if index >= self._num_devices: return None with self._lock: return self._temp_cache[index] def device_count(self): return self._num_devices def stop(self): # 主动停止轮询线程用 self._running = False self._poll_thread.join()
完全没必要尝试的方案
- 多线程/多进程分别读取单个传感器:受1-Wire总线物理限制,根本无法并行,只会增加调度开销,甚至引发读数错误
- 每个传感器单独接GPIO走独立1-Wire总线:除非你有多余的GPIO引脚且对并行度有极端要求,否则布线、配置成本极高,性价比远低于全局转换方案
内容的提问来源于stack exchange,提问作者kregus
相关产品推荐
相关产品推荐

