如何用Python解析经I2C传输的异构C结构体数据
Got it, let's work through how to correctly parse your Arduino-sent struct data on the Raspberry Pi. The core challenge here is matching the exact memory layout of your C struct—since I2C sends raw bytes with no type metadata, we have to replicate how the AVR architecture (Arduino Mega2560) packs the struct, then unpack those bytes in Python.
Step 1: Fix the C Struct on Arduino (Critical!)
First, ensure your Arduino struct doesn't have hidden byte alignment padding. AVR compilers automatically add padding bytes to align struct members to word boundaries, which will break your Python parsing. Add __attribute__((packed)) to force the compiler to pack the struct tightly:
// Packed struct to eliminate alignment padding struct __attribute__((packed)) transferData { float latitude; float longitude; int int_val1; // Arduino's int is 16-bit (2 bytes) int int_val2; int int_val3; char status_char1; // 1 byte each char status_char2; }; // Verify struct size (print this to Serial to confirm!) void setup() { Serial.begin(9600); Serial.print("Struct size: "); Serial.println(sizeof(transferData)); // Should be 4+4+2+2+2+1+1 = 16 bytes }
Note: If you meant to use 32-bit integers instead of Arduino's 16-bit int, replace int with long (or int32_t for explicit typing) in the struct. Adjust the Python parsing accordingly later.
Step 2: Parse the Bytes in Python
Use Python's built-in struct module to unpack the raw I2C bytes. We'll also use smbus2 (a modern I2C library for Raspberry Pi) to read the data.
Install Dependencies
First, install the I2C library if you haven't already:
pip install smbus2
Python Parsing Code
import struct from smbus2 import SMBus # Configuration (adjust these to match your setup) I2C_BUS_NUMBER = 1 # Raspberry Pi 3+/4 uses bus 1; older models may use 0 ARDUINO_I2C_ADDRESS = 0x08 # Match the address set in your Arduino code def parse_arduino_struct(): # Calculate expected byte count (match the sizeof output from Arduino) EXPECTED_BYTES = 16 # For the 16-bit int struct; use 22 if using 32-bit longs with SMBus(I2C_BUS_NUMBER) as bus: # Read raw bytes from Arduino over I2C raw_byte_list = bus.read_i2c_block_data(ARDUINO_I2C_ADDRESS, 0, EXPECTED_BYTES) raw_bytes = bytes(raw_byte_list) # Unpack using struct format string: # < = little-endian (AVR uses little-endian) # f = 4-byte float (2x for latitude/longitude) # h = 2-byte signed int (3x for your int values) # c = 1-byte char (2x for status chars) unpacked = struct.unpack('<ffhhicc', raw_bytes) # Convert byte chars to ASCII strings status1 = unpacked[5].decode('ascii') status2 = unpacked[6].decode('ascii') # Return parsed data in a readable format return { 'latitude': unpacked[0], 'longitude': unpacked[1], 'int_val1': unpacked[2], 'int_val2': unpacked[3], 'int_val3': unpacked[4], 'status1': status1, 'status2': status2 } # Test the parser if __name__ == "__main__": data = parse_arduino_struct() print("Parsed I2C Data:") for key, value in data.items(): print(f"{key}: {value}")
Adjustments for 32-bit Integers
If you switched to 32-bit long in the C struct:
- Update the C struct to use
longinstead ofint - The struct size will become 4+4+4+4+4+1+1 = 22 bytes
- Change the Python format string to
<ffiiiccand setEXPECTED_BYTES = 22
Key Pitfalls to Avoid
- Byte Alignment: Always use
__attribute__((packed))on the C struct—without it, the compiler adds padding bytes that will throw off your Python parsing. - Byte Order: AVR is little-endian, so start your format string with
<. Using big-endian (>) will produce garbage values. - Type Matching: Double-check that your Python format characters match the C types:
- Arduino
float→ Pythonf(4 bytes) - Arduino
int(16-bit) → Pythonh(2 bytes) - Arduino
long(32-bit) → Pythoni(4 bytes) - Arduino
char→ Pythonc(1 byte)
- Arduino
- Read Length: Always read exactly the number of bytes returned by
sizeof(transferData)on Arduino—reading too many or too few will causestruct.unpackto fail.
内容的提问来源于stack exchange,提问作者Scott

