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

树莓派使用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代码,处理主设备发送的偏移量,根据偏移位置返回对应数据:

  1. 添加全局变量存储偏移量:
uint8_t readOffset = 0;
  1. 实现receiveEvent函数,读取主设备发送的偏移量:
void receiveEvent(int howMany) {
    if (howMany >= 1) {
        readOffset = Wire.read();  // 读取主设备发送的偏移量
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:52:33