Files
pi-dro/docs/ARDUINO_I2C_PROTOCOL.md
2026-03-21 23:51:53 +01:00

229 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```python
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:
1. **Initialize I2C Slave**
```cpp
Wire.begin(0x08); // Address 0x08
Wire.onRequest(requestEvent); // Register event handler
```
2. **Maintain Encoder Position Variables**
```cpp
volatile int16_t x_position = 0;
volatile int16_t z_position = 0;
```
3. **Update Positions from Encoders**
- Attach interrupts to encoder pins
- Increment/decrement positions on encoder pulses
4. **Respond to I2C Read Requests**
```cpp
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
1. **Quadrature Encoder**
- 2 signals 90° out of phase
- Allows detection of direction and speed
- Typical: KY-040 module
2. **Incremental Encoder**
- Pulse + Direction signals
- Requires external direction determination
3. **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:
```python
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
```bash
# 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
```bash
# Read 4 bytes from address 0x08, register 0
i2cget -y 1 0x08 0 i 4
# Expected output: four hex bytes
```
### Python Test
```python
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 |
## References
- [I2C Specification](https://www.i2c-bus.org/)
- [Arduino Wire Library](https://www.arduino.cc/en/Reference/Wire)
- [Raspberry Pi I2C Setup](https://learn.adafruit.com/adafruit-16-channel-pwm-servo-driver/using-the-adafruit-library)