5.9 KiB
5.9 KiB
Arduino I2C Protocol
This document describes the I2C communication protocol between the Raspberry Pi (DRO application) and the Arduino (encoder interface).
Hardware Setup
I2C Configuration
- Bus: I2C Bus 1 (standard Raspberry Pi I2C)
- Arduino Slave Address: 0x08
- Data Rate: Standard (100 kHz) or Fast (400 kHz)
- Pull-up Resistors: 4.7 kΩ (typical I2C standards)
Connections
Raspberry Pi Arduino
GPIO2 (SDA) <-> SDA
GPIO3 (SCL) <-> SCL
GND <- GND
5V (optional)-> 5V (if Arduino powered separately)
Data Format
The Arduino transmits encoder positions as a 4-byte block:
Memory Layout
Byte 0-1: X Position (little-endian signed 16-bit integer)
Byte 2-3: Z Position (little-endian signed 16-bit integer)
Example
If encoder reads:
- X encoder: +5000 steps
- Z encoder: -200 steps
The transmitted bytes would be:
Byte 0: 0x88 (LSB of 5000)
Byte 1: 0x13 (MSB of 5000)
Byte 2: 0x38 (LSB of -200)
Byte 3: 0xFF (MSB of -200 in two's complement)
Python Parsing
import struct
# Read from I2C
data = bus.read_i2c_block_data(address, 0, 4)
# Convert to signed integers
x_position = int.from_bytes(bytes(data[0:2]), 'little', signed=True)
z_position = int.from_bytes(bytes(data[2:4]), 'little', signed=True)
# Or using struct:
x_pos, z_pos = struct.unpack('<hh', bytes(data))
Communication Protocol
Single Read Operation
Master (RPi) Slave (Arduino)
│
├─ START condition ────────────────→
│
├─ Address + READ ─────────────────→
│
↓ ↓
┌─────────────────────────────────────┐
│ Arduino sends 4 bytes of data │
│ (Acknowledgement from master) │
└─────────────────────────────────────┘
│
├─ STOP condition ──────────────────→
Timing
- Read Operation Time: ~1-2 ms at 100 kHz
- Poll Interval: 100 ms (10 Hz refresh)
- Data Transmission: Asynchronous (happens in parallel with GUI rendering)
Arduino Firmware Requirements
The Arduino sketch must:
-
Initialize I2C Slave
Wire.begin(0x08); // Address 0x08 Wire.onRequest(requestEvent); // Register event handler -
Maintain Encoder Position Variables
volatile int16_t x_position = 0; volatile int16_t z_position = 0; -
Update Positions from Encoders
- Attach interrupts to encoder pins
- Increment/decrement positions on encoder pulses
-
Respond to I2C Read Requests
void requestEvent() { byte buffer[4]; // Convert to bytes (little-endian) buffer[0] = (byte)x_position; buffer[1] = (byte)(x_position >> 8); buffer[2] = (byte)z_position; buffer[3] = (byte)(z_position >> 8); Wire.write(buffer, 4); }
Encoder Interface
The Arduino handles the low-level encoder reading:
Encoder Connection
Rotary Encoder (2-bit Gray code or quadrature)
│
├─ Signal A ──→ Arduino Interrupt Pin
├─ Signal B ──→ Arduino Pin
└─ GND ──────→ Arduino GND
Common Encoder Types
-
Quadrature Encoder
- 2 signals 90° out of phase
- Allows detection of direction and speed
- Typical: KY-040 module
-
Incremental Encoder
- Pulse + Direction signals
- Requires external direction determination
-
Absolute Encoder
- Maintains position across power cycles
- More complex protocol (often SPI/USB)
Error Handling
Arduino Side
- Buffer Overrun: Ensure encoder ISR is fast (< 100 µs)
- I2C Collision: Wire library handles this automatically
- Power Loss: Position may reset unless using volatile storage
Raspberry Pi Side
The DRO application handles:
try:
data = self._bus.read_i2c_block_data(self._address, 0, 4)
except IOError as e:
# I2C bus error (timeout, NACK, collision)
logger.error(f"I2C read error: {e}")
except OSError as e:
# System-level I2C error
logger.error(f"I2C OS error: {e}")
If a read fails, the last known position is retained.
Debugging I2C Communication
Check I2C Bus
# List I2C devices
i2cdetect -y 1
# Expected output for Arduino at 0x08:
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- -- 08 -- -- -- --
Manual Read Test
# Read 4 bytes from address 0x08, register 0
i2cget -y 1 0x08 0 i 4
# Expected output: four hex bytes
Python Test
import smbus2
bus = smbus2.SMBus(1)
data = bus.read_i2c_block_data(0x08, 0, 4)
print(f"Raw bytes: {[hex(b) for b in data]}")
x = int.from_bytes(bytes(data[0:2]), 'little', signed=True)
z = int.from_bytes(bytes(data[2:4]), 'little', signed=True)
print(f"X: {x}, Z: {z}")
bus.close()
Performance Characteristics
- Latency: ~2-5 ms (I2C + processing)
- Throughput: 100 reads/second (limited by 100 ms GUI update interval)
- Bandwidth: 4 bytes × 10 Hz = 320 bytes/second
- Jitter: ±10 ms (non-deterministic on Linux)
Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
| I2C not detected | Arduino not running | Upload firmware to Arduino |
| Data is zeros | Encoder not moving | Check encoder connections |
| Erratic values | Noise on I2C bus | Add pull-up resistors, shorten wires |
| Periodic dropouts | I2C collision | Use slower clock speed (100 kHz) |
| Position drifts | Encoder misconfiguration | Calibrate scale factor |