229 lines
5.9 KiB
Markdown
229 lines
5.9 KiB
Markdown
# 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)
|