树莓派使用Python SMBus2读取I²C数据块突然失效求助
I²C通信异常:Python读取Arduino数据全为255,但i2ctransfer命令正常
问题背景
我正在开发基于I²C总线的Arduino(从设备)到树莓派(主设备)的数据传输应用,数据传输突然失效。重新安装SMBus2库后问题仍存在。
测试代码及结果
树莓派端Python测试代码(读取7字节数据):
#! /usr/bin/python3 from smbus2 import SMBus slaveAddress = 10 blockSize = 7 with SMBus(1) as bus: # 从地址slaveAddress读取blockSize字节,偏移量0 block = bus.read_i2c_block_data(slaveAddress, 0, blockSize) print(block)
输出始终为:
[255, 255, 255, 255, 255, 255, 255]
但执行Linux命令:
i2ctransfer -y 1 r7@0x0a
能得到预期结果:
0x32 0x01 0x00 0x00 0x00 0x01 0x00
额外尝试
编辑1:尝试逐个字节读取,结果仍全为255
#! /usr/bin/python3 from smbus2 import SMBus slaveAddress = 0x0a blockSize = 7 block = [] with SMBus(1) as bus: for x in range(blockSize): block.append(bus.read_byte_data(slaveAddress, x)) print(block)
编辑2:Arduino端核心代码
#include <Wire.h> #define Cool 0 // 半导体制冷器开启为HIGH #define POK 1 // 电源故障输入 #define Fan 9 // 风扇继电器开启为HIGH #define PiCtl 14 // 树莓派控制引脚 #define ADDR1 15 // I2C地址位1 #define ADDR2 16 // I2C地址位2 #define SCL 23 // I2C时钟引脚 #define SDA 24 // I2C数据引脚 #define PTC 13 // 热电偶模拟输入 int I2C; bool True = 1; bool False = 0; float Temp; bool FanSet; bool PowerFail; bool Responded; unsigned long TimeStamp; union Response { struct { int Temperature; // 2字节 unsigned long Living; // 4字节 uint8_t Command; // 1字节 uint8_t FanValue; // 1字节 uint8_t CoolStat; // 1字节 uint8_t PowerStat; // 1字节 uint8_t padding; // 1字节 }; uint8_t bytes[11]; }; union Response Reply; void setup() { pinMode(ADDR1, INPUT_PULLUP); // 地址偏移0或1 pinMode(ADDR2, INPUT_PULLUP); // 地址偏移0或2 I2C = (2 * digitalRead(ADDR2)) + digitalRead(ADDR1) + 8; // 设置I2C地址8-11 Wire.begin(I2C); // 初始化I2C通信 Wire.onRequest(requestEvent); // 当主设备请求数据时调用 Wire.onReceive(receiveEvent); // 当收到主设备数据时调用 Reply.PowerStat = 1; // 重置电源状态 Reply.FanValue = 1; // 设置风扇为开启状态 Reply.CoolStat = 0; // 设置制冷器为关闭状态 } void TempCheck() { Reply.Temperature = analogRead(PTC); } void PowerCheck() { bool Power = digitalRead(POK); if (Power) { if (PowerFail) { PowerFail = False; // 重置电源故障变量 } Reply.Command = 1; // 重置关机命令 Reply.PowerStat = 1; // 重置电源状态 } else { TimeStamp = millis(); // 重置计时器 int Iterations = 0; // 重置时间计数器 Reply.PowerStat = 0; // 设置电源状态为故障 Reply.Command = 1; } } // 主设备请求数据时执行的函数 void requestEvent() { Wire.write(Reply.bytes, sizeof Reply); // 返回预定义的响应数据 Responded = True; // 标记已响应 Reply.Living = millis(); // 更新响应时间戳 } void loop() { PowerCheck(); TempCheck(); delay (2000); } // 未实现的接收事件函数 void receiveEvent(int howMany) { // 原代码未处理该事件 }
问题原因
核心差异在于协议类型不同:
i2ctransfer执行的是纯I²C读操作:直接向从设备请求指定字节数的数据,不需要额外发送寄存器偏移量。- SMBus库的
read_i2c_block_data和read_byte_data方法遵循SMBus协议:会先发送一个字节的寄存器偏移量给从设备,再请求数据。
你的Arduino代码中,requestEvent函数直接返回整个Reply.bytes数组,完全没有处理主设备发送的偏移量,导致树莓派端读取到的是无效的填充数据(表现为255)。
修复方案
方案1:修改Python代码,使用纯I²C读操作(推荐)
放弃SMBus的带偏移量读方法,改用smbus2的i2c_msg实现和i2ctransfer一致的纯I²C读操作:
#! /usr/bin/python3 from smbus2 import SMBus, i2c_msg slaveAddress = 0x0a blockSize = 7 with SMBus(1) as bus: # 创建纯I²C读消息,读取blockSize字节 read_msg = i2c_msg.read(slaveAddress, blockSize) # 执行读操作 bus.i2c_rdwr(read_msg) # 将消息转换为列表输出 block = list(read_msg) print(block)
该方案无需修改Arduino代码,直接匹配现有从设备的行为,避免了协议差异问题。
方案2:修改Arduino代码,支持SMBus偏移量
修改Arduino代码,处理主设备发送的偏移量,根据偏移位置返回对应数据:
- 添加全局变量存储偏移量:
uint8_t readOffset = 0;
- 实现
receiveEvent函数,读取主设备发送的偏移量:
void receiveEvent(int howMany) { if (howMany >= 1) { readOffset = Wire.read(); // 读取主设备发送的偏移量 } }
- 修改
requestEvent函数,根据偏移量返回对应数据:
void requestEvent() { // 计算可返回的字节数,避免越界 int availableBytes = sizeof(Reply) - readOffset; if (availableBytes < 0) availableBytes = 0; // 从偏移位置开始发送数据 Wire.write(Reply.bytes + readOffset, availableBytes); Responded = True; Reply.Living = millis(); }
修改后,Python端原有的SMBus读方法即可正常工作。
内容的提问来源于stack exchange,提问作者Leslie Rhorer
相关产品推荐
相关产品推荐

