4、传感器驱动开发:称重传感器(HX711)驱动原理与代码实现、红外传感器(避障/人体感应)驱动、霍尔传感器(药盒开盖检测)驱动、温湿度传感器(DHT22)驱动

好,咱们进入实战课程最硬核的一章——传感器驱动开发。

药盒安全用药系统,说白了就是靠这些传感器来感知世界的。称重传感器告诉你药吃没吃,红外传感器告诉你人来了没,霍尔传感器告诉你盖子关没关,温湿度传感器告诉你环境适不适合存药。每个传感器都有自己的脾气,摸透了,系统就稳了。

4.1 称重传感器(HX711)驱动原理与代码实现

称重传感器,核心是应变片。受力后电阻变化,但变化极其微弱,微伏级别。这时候就需要HX711这个24位高精度ADC芯片来放大和转换。

驱动原理

HX711和单片机通信只需要两根线:DOUT(数据输出)和SCK(时钟)。时序上,我习惯这么理解:

  • DOUT拉低,表示数据准备好了。
  • SCK每给一个脉冲,就读一位数据,从高位到低位,共24位。
  • 最后再给25-27个脉冲,用来选择增益和通道。

嗯,这里要注意:HX711内部有个稳定时间,上电后要等几百毫秒再读数,否则数据会飘。我在项目中遇到过,刚上电就读,结果重量显示忽大忽小,排查了半天才发现是初始化时序问题。

关键点:HX711输出的是原始码值,不是直接重量。需要先标定,找到“空载”和“已知重量”对应的码值,然后线性换算。

代码实现(MicroPython)

from machine import Pin, Timer
import time

class HX711:
    def __init__(self, dout_pin, sck_pin):
        self.dout = Pin(dout_pin, Pin.IN)
        self.sck = Pin(sck_pin, Pin.OUT)
        self.sck.value(0)
        self.offset = 0
        self.scale = 1

    def read_raw(self):
        # 等待DOUT拉低
        while self.dout.value() == 1:
            pass
        data = 0
        for i in range(24):
            self.sck.value(1)
            time.sleep_us(1)
            data = data << 1
            if self.dout.value():
                data |= 1
            self.sck.value(0)
            time.sleep_us(1)
        # 第25个脉冲,设置通道A增益128
        self.sck.value(1)
        time.sleep_us(1)
        self.sck.value(0)
        # 处理符号位(24位有符号数)
        if data & 0x800000:
            data |= ~0xFFFFFF
        return data

    def tare(self, times=10):
        # 去皮操作
        total = 0
        for i in range(times):
            total += self.read_raw()
            time.sleep_ms(10)
        self.offset = total // times

    def get_weight(self):
        raw = self.read_raw()
        return (raw - self.offset) / self.scale

    def set_scale(self, scale):
        self.scale = scale
避坑指南:我曾经在标定时犯过一个低级错误——用砝码压上去后没等稳定就读数。HX711数据有抖动,建议取10次平均值。另外,电源纹波对精度影响很大,最好单独供电。

4.2 红外传感器(避障/人体感应)驱动

红外传感器分两种:避障型和人体感应型。别看都带“红外”俩字,原理完全不同。

避障型红外传感器

说白了就是发射管一直发红外光,遇到障碍物反射回来,接收管收到信号,输出低电平。没障碍物时输出高电平。就这么简单。

驱动代码更简单,就是个数字输入读取:

from machine import Pin

ir_sensor = Pin(12, Pin.IN)  # 假设接在GPIO12

def is_obstacle():
    return ir_sensor.value() == 0  # 低电平表示有障碍物

你想想看,这种传感器用在药盒上干嘛?我一般用它检测药盒是否被拿起,或者检测抽屉是否打开。但要注意,它检测距离有限,一般10-30cm,太远就不灵了。

人体感应型(PIR)传感器

这个就高级一点。它检测的是人体发出的红外辐射变化。人一走动,热释电元件感应到变化,输出高电平。

驱动代码:

pir_sensor = Pin(13, Pin.IN)  # 假设接在GPIO13

def detect_human():
    return pir_sensor.value() == 1  # 高电平表示有人
注意:PIR传感器有个“预热时间”,上电后大约30-60秒内输出不稳定。我在项目中吃过这个亏,一上电就检测,结果一直误报。后来加了个延时初始化,问题解决。

4.3 霍尔传感器(药盒开盖检测)驱动

霍尔传感器,检测磁场用的。药盒盖子上贴个小磁铁,盖子合上时霍尔传感器靠近磁铁,输出低电平;盖子打开,磁铁远离,输出高电平。

为什么用霍尔而不用干簧管?我个人习惯,霍尔传感器寿命更长,没有机械触点,不会氧化。药盒每天开合几十次,用干簧管一年后可能就失灵了。

驱动代码:

hall_sensor = Pin(14, Pin.IN, Pin.PULL_UP)  # 内部上拉

def is_lid_closed():
    return hall_sensor.value() == 0  # 低电平表示盖子闭合

def is_lid_opened():
    return hall_sensor.value() == 1  # 高电平表示盖子打开
小技巧:磁铁和霍尔传感器的距离要控制在5mm以内,太远检测不到。我一般用钕磁铁,吸力强,体积小。另外,注意磁铁极性,装反了可能没反应。

4.4 温湿度传感器(DHT22)驱动

DHT22,精度比DHT11高不少。温度精度±0.5°C,湿度精度±2%RH。药盒里放这个,主要是监测环境是否适合存药。有些药怕潮,湿度超过60%就得报警。

通信协议

DHT22用的是单总线协议,一根线既当数据线又当时钟线。时序比较讲究:

  • 主机拉低总线至少18ms,然后拉高20-40us,等待DHT22响应。
  • DHT22拉低80us,再拉高80us,表示准备好发送数据。
  • 然后连续发送40位数据:16位湿度、16位温度、8位校验和。
  • 每位数据:拉低50us,然后拉高26-28us表示0,拉高70us表示1。

嗯,这里要注意时序的微秒级控制。MicroPython的time.sleep_us()精度有限,我建议用机器定时器或者直接循环等待。

代码实现

import machine
import time

class DHT22:
    def __init__(self, pin):
        self.pin = machine.Pin(pin, machine.Pin.OUT)
        self.pin.value(1)

    def read(self):
        # 发送开始信号
        self.pin.value(0)
        time.sleep_ms(20)
        self.pin.value(1)
        time.sleep_us(30)

        # 切换为输入
        self.pin = machine.Pin(self.pin, machine.Pin.IN)

        # 等待响应
        count = 0
        while self.pin.value() == 1 and count < 100:
            count += 1
            time.sleep_us(1)
        while self.pin.value() == 0 and count < 100:
            count += 1
            time.sleep_us(1)

        # 读取40位数据
        data = []
        for i in range(40):
            while self.pin.value() == 0:
                pass
            time.sleep_us(50)
            if self.pin.value() == 1:
                data.append(1)
            else:
                data.append(0)
            while self.pin.value() == 1:
                pass

        # 解析数据
        humidity = 0
        for i in range(16):
            humidity = (humidity << 1) | data[i]
        temperature = 0
        for i in range(16, 32):
            temperature = (temperature << 1) | data[i]
        checksum = 0
        for i in range(32, 40):
            checksum = (checksum << 1) | data[i]

        # 校验
        if (humidity + temperature) & 0xFF != checksum:
            return None, None  # 校验失败

        # 转换实际值
        humidity = humidity / 10.0
        if temperature & 0x8000:
            temperature = -(temperature & 0x7FFF) / 10.0
        else:
            temperature = temperature / 10.0

        return temperature, humidity
重要提醒:DHT22的读取间隔至少2秒,读太频繁会发热,导致数据不准。我曾经连续每秒读一次,结果温度越读越高,还以为传感器坏了。后来查手册才发现,人家要求最小读取间隔2秒。

4.5 传感器驱动整合思路

四个传感器都讲完了,怎么整合到药盒系统里?我建议这样:

传感器 检测内容 触发动作
称重传感器 药片是否取出 记录用药时间、剂量
红外避障传感器 药盒是否被拿起 唤醒系统、点亮屏幕
霍尔传感器 药盒盖子状态 检测是否异常开盖
温湿度传感器 环境温湿度 超限报警、记录日志

每个传感器驱动写成一个类,主程序里统一调度。用定时器轮询,比如每100ms读一次霍尔和红外,每2秒读一次温湿度,称重则在检测到开盖时才读取,省电。

核心思想:传感器驱动不是孤立的,要结合业务逻辑。比如称重传感器,不是一直读,而是“开盖后读一次,关盖后读一次”,差值就是取出的药量。这样既省电又准确。

好了,这一章内容不少。四个传感器,每个都有自己的脾气。你写代码的时候,多想想实际场景,多留点调试接口。传感器数据用串口打印出来看看,确认没问题再集成到主程序里。下一章咱们讲数据融合和异常报警逻辑,到时候这些传感器就要协同作战了。