树莓派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
相关产品推荐
相关产品推荐

