You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派Pico主从I2C通信故障求助:DHT11数据传输阻塞

树莓派Pico主从I2C通信阻塞问题解决方案

核心问题根源

你遇到的阻塞问题,本质是I2C从机的回调函数运行在中断上下文,如果在回调里执行传感器读取、延时、复杂计算这类阻塞/耗时操作,会直接打乱I2C总线的时序,导致主机等待超时、永久阻塞。哪怕用定时器触发采集,只要定时器回调(同样是中断上下文)里包含阻塞逻辑,也会影响I2C中断响应。


具体解决方案

1. 严格分离中断与应用上下文

所有耗时操作(DHT11读取、延时)必须放到应用线程里执行,I2C从机的回调只做最简短的数据传递:

  • 用全局变量存储预更新的温湿度数据
  • 后台线程定时读取传感器,更新全局变量
  • I2C回调仅根据主机指令返回现成的全局数据

从机示例代码:

import machine
import _thread
import time
from dht import DHT11

# 全局变量:存储预读取的温湿度数据
temp_data = b'\x00\x00'
humi_data = b'\x00\x00'
current_cmd = 0x00

# 初始化DHT11
dht_pin = machine.Pin(2)
dht_sensor = DHT11(dht_pin)

# 后台线程:负责定时读取DHT11
def sensor_update_thread():
    global temp_data, humi_data
    while True:
        # 执行DHT11读取(这里可以包含协议要求的延时)
        dht_sensor.measure()
        temp = dht_sensor.temperature()
        humi = dht_sensor.humidity()
        # 转换为字节格式,方便I2C传输
        temp_data = bytes([temp // 10, temp % 10])
        humi_data = bytes([humi // 10, humi % 10])
        time.sleep(10)  # 线程内的sleep不影响中断

# 启动后台线程
_thread.start_new_thread(sensor_update_thread, ())

# I2C从机回调:仅处理指令和返回数据
def on_recv(data):
    global current_cmd
    current_cmd = data[0]  # 仅存储主机指令,不做额外处理

def on_request():
    global current_cmd, temp_data, humi_data
    # 根据主机指令返回对应数据
    match current_cmd:
        case 0xF0:
            return temp_data[0:1]  # 温度整数部分
        case 0xF1:
            return temp_data[1:2]  # 温度小数部分(DHT11实际无小数,可返回0)
        case 0xF2:
            return humi_data[0:1]  # 湿度整数部分
        case 0xF3:
            return humi_data[1:2]  # 湿度小数部分(DHT11实际无小数,可返回0)
        case _:
            return b'\x00'

# 初始化I2C从机(假设使用论坛的i2c_slave类)
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0), freq=100000)
slave_addr = 0x40
import i2c_slave
slave = i2c_slave.I2CSlave(i2c, slave_addr, on_recv, on_request)

2. 主机侧添加超时保护

避免主机永久阻塞在readfrom,给读取操作添加超时逻辑:
主机示例代码:

import machine
import time

i2c = machine.I2C(1, scl=machine.Pin(3), sda=machine.Pin(2), freq=100000)
slave_addr = 0x40

def read_sensor(cmd, timeout_ms=500):
    i2c.writeto(slave_addr, bytes([cmd]))
    start_time = time.ticks_ms()
    # 循环尝试读取,超时则返回None
    while time.ticks_diff(time.ticks_ms(), start_time) < timeout_ms:
        try:
            data = i2c.readfrom(slave_addr, 1)
            return data[0]
        except OSError:
            time.sleep_ms(10)
    return None

# 主循环读取数据
while True:
    temp_int = read_sensor(0xF0)
    temp_dec = read_sensor(0xF1)
    humi_int = read_sensor(0xF2)
    humi_dec = read_sensor(0xF3)
    print(f"温度: {temp_int}.{temp_dec}℃, 湿度: {humi_int}.{humi_dec}%")
    time.sleep(2)

3. 替换第三方i2c_slave类(可选)

如果使用的第三方i2c_slave类存在中断处理缺陷,建议改用MicroPython官方原生的I2C从机模式(新版固件已支持):

# 原生I2C从机初始化示例
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0), freq=100000)
i2c.slave(slave_addr, on_recv=on_recv, on_transmit=on_request)

关键注意事项

  • 绝对不要在I2C从机的中断回调(on_recv/on_request)里执行任何阻塞操作,包括time.sleep()、传感器读取、复杂计算
  • 定时器回调同样属于中断上下文,不能放阻塞逻辑,必须用后台线程处理耗时任务
  • 确保I2C总线的频率匹配(主机和从机都设为100kHz或400kHz)

内容的提问来源于stack exchange,提问作者Andrei Poclid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:00:06