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

5.9 KiB
Raw Permalink Blame History

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:

  1. Initialize I2C Slave

    Wire.begin(0x08);  // Address 0x08
    Wire.onRequest(requestEvent);  // Register event handler
    
  2. Maintain Encoder Position Variables

    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

    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:

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

References