I2C

Introduction

I2C protocol diagram

I2C is a simple yet very common protocol used for communication between logic systems.

It uses only two digital lines: SCL (clock signal) and SDA (data signal) to transmit serial data. Transmission is prefixed with an address byte, allowing many 'slave' devices to be operated over a single pair of wires (SCL & SDA).

You can find more information about the protocol itself on i2c.info as well as Wikipedia.

Pull-ups

The I2C bus requires pull-up resistors on both of its communication lines (SDA & SCL). These provide a 'default' value for the bus lines when no device is asserting the signal.

This may seem complex at first but it actually looks incredibly simple.

Basically we just need to add a resistor between each line and VCC (positive power supply voltage):

The I2C library

Dooba provides an 'i2c' library that you can use to easily communicate with devices using this technology.

How to use

First, we need to add the 'i2c' library to our dependencies in dfe.conf:

name: example_app
type: app
mmcu: atmega1284p
freq: 10000000
deps:
  - i2c

Initialization

Before we can communicate, we need to initialize the library. If we're using the substrate, this is as trivial as adding the following to our 'substrate' file:

# Use I2C
uses :i2c

If, however, we want to do this without substrate, we can do it manually from our C code:

#include <i2c/i2c.h>

void main()
{
    // Initialize I2C Subsystem
    i2c_init();

    // ...
}

Basic communication

Only four basic methods are needed to achieve I2C communication:

// Start Condition
void i2c_start();

// Stop Condition
void i2c_stop();

// Write
void i2c_write(uint8_t d);

// Read
uint8_t i2c_read(uint8_t ack);

Note: the 'ack' argument of the 'i2c_read' method will determine whether the 'TWEA' bit is set in the 'TWCR' (I2C Control Register). For most master-mode communication you can leave this as '0'.

Whenever we need to send large chunks of data (not just a single byte), we can use the following method:

// Send Data Chunk
void i2c_send(uint8_t *d, uint8_t s);

Accessing registers

Most I2C devices will offer access to some registers, which is the most common form of interaction over the I2C protocol. Luckily, most devices follow the same 'register-access' protocol.

Therefore, two methods are provided to simplify access:

// Generic Write Register
void i2c_write_reg(uint8_t addr, uint8_t reg, uint8_t val);

// Generic Read Register
uint8_t i2c_read_reg(uint8_t addr, uint8_t reg);

These will take care of the whole communication, including start/stop signals.

A rather simple example of how to use I2C is the mcp23008 library - a basic driver for the MCP23008 I/O expander.