简体   繁体   English

使用 Raspberry Pi 更快地读取多个 DS18B20 温度传感器

[英]Read multiple DS18B20 temperature sensors faster using Raspberry Pi

My custom sensor dashboard requests new readings every second .我的自定义传感器仪表板每秒请求新读数。

This worked well, until I hooked up 3 DS18B20 temperature sensors (1-wire protocol, so all on 1 pin), which each take 750ms to provide new data.这工作得很好,直到我连接了 3 个 DS18B20 温度传感器(1 线协议,所以都在 1 个引脚上),每个需要 750 毫秒来提供新数据。

This is the class I currently use to read the temperature of each sensor:这是我目前用来读取每个传感器温度的类:

# ds18b20.py
# written by Roger Woollett

import os
import glob
import time


class DS18B20:
    # much of this code is lifted from Adafruit web site
    # This class can be used to access one or more DS18B20 temperature sensors
    # It uses OS supplied drivers and one wire support must be enabled
    # To do this add the line
    # dtoverlay=w1-gpio
    # to the end of /boot/config.txt
    #
    # The DS18B20 has three pins, looking at the flat side with the pins pointing
    # down pin 1 is on the left
    # connect pin 1 to GPIO ground
    # connect pin 2 to GPIO 4 *and* GPIO 3.3V via a 4k8 (4800 ohm) pullup resistor
    # connect pin 3 to GPIO 3.3V
    # You can connect more than one sensor to the same set of pins
    # Only one pullup resistor is required

    def __init__(self):
        # Load required kernel modules
        os.system('modprobe w1-gpio')
        os.system('modprobe w1-therm')

        # Find file names for the sensor(s)
        base_dir = '/sys/bus/w1/devices/'
        device_folder = glob.glob(base_dir + '28*')
        self._num_devices = len(device_folder)
        self._device_file = list()
        i = 0
        while i < self._num_devices:
            self._device_file.append(device_folder[i] + '/w1_slave')
            i += 1


    def _read_temp(self, index):
        # Issue one read to one sensor
        # You should not call this directly

        # First check if this index exists
        if index >= len(self._device_file):
            return False

        f = open(self._device_file[index], 'r')
        data = f.read()
        f.close()
        return data


    def tempC(self, index=0):
        # Call this to get the temperature in degrees C
        # detected by a sensor
        data = self._read_temp(index)
        retries = 0

        # Check for error
        if data == False:
            return None

        while (not "YES" in data) and (retries > 0):
            # Read failed so try again
            time.sleep(0.1)
            #print('Read Failed', retries)
            data = self._read_temp(index)
            retries -= 1

        if (retries == 0) and (not "YES" in data):
            return None

        (discard, sep, reading) = data.partition(' t=')

        if reading == 85000:
            # 85ºC is the boot temperature of the sensor, so ignore that value
            return None

        temperature = float(reading) / 1000.0

        return temperature


    def device_count(self):
        # Call this to see how many sensors have been detected
        return self._num_devices

I already tried to return the previous temperature reading if the current one isn't finished yet, however this didn't reduce the time it took to read a sensor, so I guess the only way is to do things asynchronously.如果当前温度读数尚未完成,我已经尝试返回之前的温度读数,但这并没有减少读取传感器所需的时间,所以我想唯一的方法是异步做事。

I could reduce the precision to reduce the time it takes per reading, but ideally I would read all of the sensors simultaneously on separate threads.我可以降低精度以减少每次读取所需的时间,但理想情况下,我会在单独的线程上同时读取所有传感器。

How can I best implement this?我怎样才能最好地实现这一点? Or are there other ways to improve the reading speed of multiple DS18B20 sensors?或者有没有其他方法可以提高多个 DS18B20 传感器的读取速度?

Thanks for any insights!感谢您的任何见解!

You're facing some limitations introduced by the Linux kernel driver.您正面临 Linux 内核驱动程序引入的一些限制。 If you were interacting with the OneWire protocol directly, you would only have a single 750ms read cycle for all three sensors, rather than (3 * 750ms) .如果您直接与 OneWire 协议交互,则所有三个传感器的读取周期只有一个750ms ,而不是(3 * 750ms) When speaking the 1-wire protocol directly, you can issue a single "convert temperature" command to all devices on the bus, as described here , and then read all the sensors.当直接使用 1-wire 协议时,您可以向总线上的所有设备发出一个“转换温度”命令,如此所述,然后读取所有传感器。

The Linux driver explicitly doesn't support this mode of operation : Linux 驱动程序明确不支持这种操作模式

If none of the devices are parasite powered it would be possible to convert all the devices at the same time and then go back to read individual sensors.如果没有任何设备是寄生供电的,则可以同时转换所有设备,然后返回读取单个传感器。 That isn't currently supported.目前不支持。 The driver also doesn't support reduced precision (which would also reduce the conversion time) when reading values.读取值时,驱动程序也不支持降低精度(这也会减少转换时间)。

That means you're stuck with a 750ms per device read cycle.这意味着您被困在每个设备读取周期 750 毫秒。 Your best option is probably placing the sensor reading code in a separate thread, eg:您最好的选择可能是将传感器读取代码放在单独的线程中,例如:

import glob
import threading
import time


# Note that we're inheriting from threading.Thread here;
# see https://docs.python.org/3/library/threading.html
# for more information.
class DS18B20(threading.Thread):
    default_base_dir = "/sys/bus/w1/devices/"

    def __init__(self, base_dir=None):
        super().__init__()
        self._base_dir = base_dir if base_dir else self.default_base_dir
        self.daemon = True
        self.discover()

    def discover(self):
        device_folder = glob.glob(self._base_dir + "28*")
        self._num_devices = len(device_folder)
        self._device_file: list[str] = []
        for i in range(self._num_devices):
            self._device_file.append(device_folder[i] + "/w1_slave")

        self._values: list[float | None] = [None] * self._num_devices
        self._times: list[float] = [0.0] * self._num_devices

    def run(self):
        """Thread entrypoint: read sensors in a loop.

        Calling DS18B20.start() will cause this method to run in
        a separate thread.
        """

        while True:
            for dev in range(self._num_devices):
                self._read_temp(dev)

            # Adjust this value as you see fit, noting that you will never
            # read actual sensor values more often than 750ms * self._num_devices.
            time.sleep(1)

    def _read_temp(self, index):
        for i in range(3):
            with open(self._device_file[index], "r") as f:
                data = f.read()

            if "YES" not in data:
                time.sleep(0.1)
                continue

            disacard, sep, reading = data.partition(" t=")
            temp = float(reading) / 1000.0
            self._values[index] = temp
            self._times[index] = time.time()
            break
        else:
            print(f"failed to read device {index}")

    def tempC(self, index=0):
        return self._values[index]

    def device_count(self):
        """Return the number of discovered devices"""
        return self._num_devices

Because this is a thread, you need to .start() it first, so your code would look something like:因为这是一个线程,你需要先.start()它,所以你的代码看起来像:

d = DS18B20()
d.start()
while True:
    for i in range(d.device_count()):
        print(f'dev {i}: {d.tempC(i)}')
    time.sleep(0.5)

You can call the tempC method as often as you want, because it's just return a value from the _values array.您可以根据需要随时调用tempC方法,因为它只是从_values数组中返回一个值。 The actual update frequency is controlled by the loop in the run method (and the minimum cycle time imposed by the sensors).实际更新频率由run方法中的循环控制(以及传感器施加的最小循环时间)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM