This tutorial I gave at the Embedded Linux Conference tries to answer the questions, providing links to more detailed description of the topics addressed and using the practical example of driving a 4WD drone, where an Arduino Mini Pro acts as slave and controls the 4 independent wheels.
The original document can be found here.
Note: This answer is currently work in progress, as I adapt the highlights from the link.
Typical Applications of the I2C bus
- Interfacing with relatively slow peripherals. Ex: sensors, mechanical actuators.
Controlling “fast” peripherals, that use other channels for exchanging data. Ex: codecs.
In a PC, the Operating System usually interacts over I2C with:
- temperature and battery voltage meters;
- fan speed controllers;
- audio codecs.
In case multiple bus controllers are available, peripherals are grouped by speed, so that fast ones are not penalized by slower ones.
A quick introduction to the I2C bus - key features
- Serial bus.
- Only 2 lines: Serial CLock and Serial DAta (plus ground).
- 4 speeds: 100kHz, 400kHz, 1MHz, 3.2MHz.
- Typically, 1 master device and 1 or more slaves.
- Communications are always initiated by a master device.
- Multiple masters can co-exist on the same bus (multi-master).
- Open-Drain: both SDA and SCL need pull-up resistors.
- “Clock Stretching”
- The master controls SCL, but a slave can hold it down (because open drain), if it needs to adjust the speed.
- The master must check for this scenario.
- A slave can get stuck and jam the bus: need for reset lines from the master to the slave.
- Typically 7-bit addressing, but also 10 bit is supported.
- Logical protocol: actual voltage levels are not specified and depend on individual implementations. Ex: 1.8V / 3.3V / 5.0V
Example of Bus Configuration
Characteristics of the Protocol (simplified)
- 2 message types: read and write
- Start / Stop bit - represented as “[“ and “]” in the rest of the answer
- Address: 7 or 10 bits
- R/W bit: R = 1 / W = 0 Used to discriminate the type of message sent.
- Data on the bus: (Address << 1 | R/W)
- Registers as information handlers, within the selected device.
Example of Bus traffic
Why create a custom I2C slave?
- Desired sensor/actuator unavailable with I2C interface.
- Less unique addresses available than slaves needed.
- Desired custom functionality on the slave:
- Semi-autonomous reactions to stimuli.
- Filtering/preprocessing input data.
- Power optimization: custom “sensor hub” does the housekeeping while the main processor is idle.
- Realtime response to inputs.
- [your imagination here]
How to design a custom I2C slave?
- Define requirements (see previous slide).
- Choose microcontroller or microprocessor.
- Choose Scheduler or Operating System (if any).
- Define communication sub-protocol:
- Define parameters and commands to be exchanged.
- Organize them into “registers” and choose a free address.
Design of the I2C Master
Key design criteria:
- Required computational power and average latency.
- PC-like device
- Embedded device, typically headless.
- Preferred programming language: interpreted vs compiled.
- Availability of busses/gpios for driving the slave(s):
- GPIOs only: bitbang the protocol
- I2C: user-space application vs kernel driver.
- No GPIOs/I2C interfaces available: USB to I2C adapter.
Debugging: Divide and Conquer
Take direct control of the bus with an ad-hoc device.
- Primarily for development purposes.
- Can both sniff the bus and drive it.
- Console interface over serial (ttyACM) port, including macros, or programmatic access for several programming languages.
- Built-in pullup resistors and voltage sources (5V / 3.3V)
- Supports many other protocols.
- References: Wikipedia, main page
USB to I2C Adapter
- Small footprint.
- Suitable for permanent installations.
- No need for special connections on the host: it can be used to interface with a typical PC.
- Variant available that is also SPI-capable.
- No console interface, only serial binary protocol.
- Requires protocol wrapper.
- Reference: protocol
sigrok and pulseview
sigrok(bakend component) logo
pulseview (visualizer) example
Example of low end logic Analyzer
- De-facto standard for PC-driven measurements on linux (but available on other OSes too).
- Support for vast range of logic analyzers, scopes and meters.
- Various protocol decoders, including I2C.
- Useful for visualizing the logical signals and debugging protocol errors.
- Even very low end, inexpensive HW can provide a whole new dimension to debugging.
- References: sigrok, pulseview, supported hardware
Example: steering a 4WD drone
Prototype built using 2 Arduino Mini Pro.
What does the slave do in the example?
The I2C slave:
- Controls the amount of torque applied to each wheel.
- Controls the direction each wheel spins.
- Measures the rotation speed of each wheel through an optical encoder (Odometer).
- Exposes the parameters above to the I2C Master.
High level block diagram of the I2C Slave.
- Enough pins/functions to provide for each wheel:
- 1 PWM output with independent configuration of the duty-cycle.
- 1 GPIO for registering odometer input as IRQ.
- 2 GPIOs for selecting:
- I2C HW block for interrupt-driven i2c exchanges.
- Dedicated pins for SPI-based programming.
- Small footprint.
- Low Cost.
- The board layout of the clone represented in the picture is optimized for mounting on a DIL socket.
Selecting the OS: ChibiOS
- RTOS: preemption, tasks, semaphores, dynamic system tic, etc.
- Small footprint: link only used code/data.
- Distinction between RTOS and BSP through HAL.
- GPLv3 for non-commercial use.
- Actively developed, but already mature.
- Supports 8bit AVR.
However it had limited BSP support for AVR, lack of:
- interrupts driver for AVR GPIOs (added).
- I2C support for AVR slave mode (custom).
Which had to be developed separately as part of the Drone SW for the AVR.
Defining the Communication Parameters
For each wheel:
Duty Cycle of the PWM signal used to drive it - 1 byte.
0xFF = max torque / 0x00 = no torque.
Direction of rotation - 1 byte.
- 0x00 = idle
- 0x01 = reverse
- 0x02 = forward
- 0x03 = locked
Average period in between slots of the optical encoder - 2 bytes.
- Writing anything resets the measurement.
Parameter Index - 1 nibble:
- 0 = Duty Cycle
- 1 = Direction
- 2 = Average Period
Wheel indexes - 1 nibble:
- 0 = Left Rear
- 1 = Right Rear
- 2 = Right Front
- 3 = Left Front
- 4 = All
Sub Protocol: Defining the Registers
Register format: 0xαβ
- α = Parameter Index
- β = Wheel Index
Address (chosen arbitrarily): 0x10
Bus Pirate format:
- [ = start bit
- ] = end bit
- r = read byte
- address times 2 (left shift 1), for R/W bit
Example - in Bus Pirate Format
[ i2c_addr reg_addr=(parm,wheel) reg_value ]
[0x20 0x20 0x02] Left Rear Forward
[0x20 0x21 0x01] Right Rear Backward
[0x20 0x22 0x01] Right Front Backward
[0x20 0x23 0x02] Left Front Forward
[0x20 0x14 0xFF] Wheels set to max torque
The car spins clockwise.