Raspberry Pi与Arduino I2C通信问题:如何同步响应且适配短周期命令?
Alright, let's tackle this I2C communication issue you're facing between Raspberry Pi (master) and Arduino (slave) paired with a DALI power supply. The core problems here are getting fresh responses from Arduino after its 40ms DALI operation, while also handling high-frequency commands that can't afford to wait around. Let's break this down with practical solutions and code examples.
Problem Breakdown
First, let's clarify why you're seeing stale responses:
- When RPi sends a command and immediately reads the response, Arduino's
loop()hasn't finished the 40ms DALI operation yet, so it returns old cached data. - Some commands need to run on a <100ms cycle, so blocking RPi to wait for the full 40ms DALI process isn't feasible.
Solution: State Machine + Dual Data Buffers
The fix relies on splitting your commands into two categories, each handled with dedicated logic on Arduino, and matching RPi code to interact with those states.
1. Commands That Need Fresh DALI Responses (Wait for Arduino)
For commands like "toggle light and get updated status", we'll have Arduino track a processing state, and RPi polls until the operation is done.
2. High-Frequency Periodic Commands (No Wait Needed)
For commands that just need the latest DALI state on a tight cycle, Arduino will maintain a real-time buffer that gets updated periodically in loop(), so RPi can read it instantly.
Arduino Code Implementation
This code uses a state machine to track ongoing DALI operations, plus a buffer for fast updates:
#include <Wire.h> #define I2C_SLAVE_ADDR 0x08 // Define command types to distinguish our two use cases enum CommandType { CMD_GET_DALI_FRESH_STATUS = 0x01, // Requires waiting for DALI CMD_GET_DALI_FAST_STATUS = 0x02 // High-frequency, use cached data }; // Track command state and data buffers struct CmdHandler { CommandType activeCmd; bool isProcessing; byte freshDaliResponse; byte fastDaliBuffer; }; CmdHandler cmdHandler; void setup() { Wire.begin(I2C_SLAVE_ADDR); Wire.onReceive(receiveRpiCommand); Wire.onRequest(sendResponseToRpi); // Initialize state cmdHandler.isProcessing = false; cmdHandler.freshDaliResponse = 0x00; cmdHandler.fastDaliBuffer = 0x00; } void loop() { // Update fast buffer every 50ms (fits under 100ms cycle) static unsigned long lastFastUpdate = 0; if (millis() - lastFastUpdate >= 50) { lastFastUpdate = millis(); cmdHandler.fastDaliBuffer = readDaliQuickState(); // Replace with your DALI read } // Handle ongoing DALI operations for fresh commands if (cmdHandler.isProcessing) { static unsigned long processStartTime = 0; // Wait for 40ms DALI operation to complete if (millis() - processStartTime >= 40) { cmdHandler.freshDaliResponse = readDaliFinalState(); // Replace with your DALI read cmdHandler.isProcessing = false; } } } // Receive command from RPi void receiveRpiCommand(int byteCount) { if (byteCount >= 1) { byte cmdByte = Wire.read(); cmdHandler.activeCmd = (CommandType)cmdByte; if (cmdHandler.activeCmd == CMD_GET_DALI_FRESH_STATUS) { cmdHandler.isProcessing = true; processStartTime = millis(); // Mark start of DALI process } // Fast commands don't need processing state set } } // Send response back to RPi void sendResponseToRpi() { switch(cmdHandler.activeCmd) { case CMD_GET_DALI_FRESH_STATUS: if (cmdHandler.isProcessing) { Wire.write(0xFF); // Send "still processing" flag to RPi } else { Wire.write(cmdHandler.freshDaliResponse); // Send fresh data } break; case CMD_GET_DALI_FAST_STATUS: Wire.write(cmdHandler.fastDaliBuffer); // Send cached fast data break; default: Wire.write(0xEE); // Send unknown command error break; } } // Replace these two functions with your actual DALI communication code byte readDaliQuickState() { // Simulate fast DALI status read return random(0x00, 0xFF); } byte readDaliFinalState() { // Simulate full DALI operation response return random(0x00, 0xFF); }
Raspberry Pi Python Code
This code interacts with Arduino's state machine, handling both command types appropriately:
import smbus2 import time I2C_SLAVE_ADDR = 0x08 CMD_FRESH_STATUS = 0x01 CMD_FAST_STATUS = 0x02 # Initialize I2C bus (use 0 for RPi 1, 1 for RPi 3/4/Zero) i2c_bus = smbus2.SMBus(1) def get_fresh_dali_status(): """Get updated status after Arduino completes DALI operation""" i2c_bus.write_byte(I2C_SLAVE_ADDR, CMD_FRESH_STATUS) # Poll until processing is done (add timeout to avoid infinite loop) timeout_start = time.time() while time.time() - timeout_start < 0.1: # 100ms max wait response = i2c_bus.read_byte(I2C_SLAVE_ADDR) if response != 0xFF: # 0xFF = still processing return response time.sleep(0.005) # Wait 5ms between polls return None # Return None if timeout def get_fast_dali_status(): """Get latest cached status for high-frequency queries""" i2c_bus.write_byte(I2C_SLAVE_ADDR, CMD_FAST_STATUS) return i2c_bus.read_byte(I2C_SLAVE_ADDR) # Test the fresh status command print("Fresh DALI status:", get_fresh_dali_status()) # Test high-frequency command (80ms cycle, under 100ms) print("\nHigh-frequency status updates:") for i in range(5): print(f"Update {i+1}: {get_fast_dali_status()}") time.sleep(0.08)
Key Notes for Reliability
- Add Timeouts: The RPi's polling loop includes a 100ms timeout to prevent hanging if Arduino gets stuck.
- Avoid Command Collisions: If RPi sends a new command while Arduino is processing another, add logic in
receiveRpiCommandto return a "busy" response (e.g., 0xDD) instead of overwriting the active command. - Optimize DALI Calls: If your DALI library supports asynchronous operations, you can replace the millis-based wait with interrupts to free up Arduino's loop even more.
内容的提问来源于stack exchange,提问作者Benas Laurinaitis

