树莓派5通过smbus2向Arduino Mega发送双字节I2C数据故障排查
问题根源与解决办法
1. I2C数据传输格式不匹配
树莓派端每次仅发送2字节(单个参数),且分多次发送,但Arduino的receiveEvent期望一次性读取3个电机的所有参数(共18字节),导致Arduino无法正确触发回调或解析数据。
修改树莓派代码:一次性发送完整数据块
将所有参数打包成连续字节流,一次性发送,避免多次小数据传输的时序问题:
from smbus2 import SMBus from time import sleep import struct addr = 0x8 # I2C从机地址 bus = SMBus(1) # /dev/i2c-1 steps = [600, 600, 600] speeds = [500, 500, 500] accels = [200, 200, 200] while True: # 打包所有参数为大端双字节格式的连续字节流 data_buffer = b"" for idx in range(3): data_buffer += struct.pack(">h", steps[idx]) data_buffer += struct.pack(">h", speeds[idx]) data_buffer += struct.pack(">h", accels[idx]) # 一次性发送所有数据 bus.write_i2c_block_data(addr, 0, list(data_buffer)) sleep(2)
2. Arduino回调注册时机与逻辑错误
Wire.onReceive(receiveEvent)注册在home()之后,虽不影响初始化,但回调逻辑存在多个问题:digitalWrite(ledPin, '1')错误,应使用整数HIGH或1- 假设一次性收到18字节,但实际每次仅收到2字节,导致
parse_Integer返回0 - 用
int解析long类型参数,存在溢出风险
修改Arduino关键代码:
void setup() { // 先注册I2C回调,确保通信就绪 Wire.begin(0x8); Wire.onReceive(receiveEvent); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); // 电机初始化 for (int i = 0; i < MAX_STEPPERS; i++) { steppers[i].setMaxSpeed(100000); steppers[i].setAcceleration(800); steppers[i].disableOutputs(); steppers[i].setCurrentPosition(0); } home(); } void receiveEvent(int howMany) { digitalWrite(ledPin, HIGH); // 校验数据长度是否符合预期 if (howMany != 18) { while(Wire.available()) Wire.read(); // 清空无效数据 digitalWrite(ledPin, LOW); return; } for (int i = 0; i < MAX_STEPPERS; i++) { // 读取双字节大端数据并转换为long类型 byte high_byte = Wire.read(); byte low_byte = Wire.read(); receivedSteps[i] = ((long)high_byte << 8) | low_byte; high_byte = Wire.read(); low_byte = Wire.read(); receivedSpeed[i] = ((long)high_byte << 8) | low_byte; high_byte = Wire.read(); low_byte = Wire.read(); receivedAcceleration[i] = ((long)high_byte << 8) | low_byte; } // 更新电机参数 for (int i = 0; i < MAX_STEPPERS; i++) { steppers[i].setAcceleration(receivedAcceleration[i]); steppers[i].setMaxSpeed(receivedSpeed[i]); steppers[i].move(receivedSteps[i]); } runAllowed = true; delay(100); // 留时间观察LED状态 digitalWrite(ledPin, LOW); } // 修正runMotors的循环判断逻辑,使用AccelStepper自带的distanceToGo() void runMotors() { if (runAllowed) { bool all_motors_done = false; while (!all_motors_done) { all_motors_done = true; for (int i = 0; i < MAX_STEPPERS; i++) { steppers[i].enableOutputs(); steppers[i].run(); if (steppers[i].distanceToGo() != 0) { all_motors_done = false; } } } runAllowed = false; } if (!runAllowed) { for (int i = 0; i < MAX_STEPPERS; i++) { steppers[i].disableOutputs(); } } }
3. 硬件与权限检查
- 确认树莓派I2C已启用:通过
raspi-config的Interfacing Options开启I2C - 确认I2C连线正确:SDA接SDA、SCL接SCL、GND共地
- 树莓派运行代码需权限:要么用
sudo执行,要么将用户加入i2c组:sudo usermod -aG i2c pi,重启后生效
内容的提问来源于stack exchange,提问作者FlamingIce
相关产品推荐
相关产品推荐

