Tuesday, 16 June 2015

Inter-Integrated Circuit (I2C) protocol basics (with I²C master example)

Inter-Integrated Circuit (I2C)



  • As the name suggests, Inter-IC (or the Inter-Integrated Circuit), often shortened as I2C (pronounced eye-two-see), I2C (pronounced eye-squared-see), or IIC, was developed as a communication protocol to interact between different ICs on a motherboard, a simple internal bus system. 
  • It is a revolutionary technology developed by Philips Semiconductor (now NXP Semiconductors) in 1982, and is used to connect low speed peripherals (like keyboard, mouse, memory, IO/serial/parallel ports, etc.) to the motherboard (containing the CPU) operating at much higher speed.
  • These days you can find a lot of devices which are I2C compatible manufactured by a variety of companies (like Intel, TI, Freescale, STMicroelectronics, etc). 
  • The Inter-integrated Circuit (I2C) Protocol is a protocol intended to allow multiple “slave” digital integrated circuits (“chips”) to communicate with one or more “master” chips. 
  • Like the Serial Peripheral Interface (SPI), it is only intended for short distance communications within a single device. 
  • Like Asynchronous Serial Interfaces (such as RS-232 or UARTs), it only requires two signal wires to exchange information.
  • I2C bus is used by many integrated circuits and is simple to implement. 
  • Any microcontroller can communicate with I2C devices even if it has no special I2C interface.

I2C terminology

  • Transmitter This is the device that transmits data to the bus
  • Receiver This is the device that receives data from the bus
  • Master This is the device that generates clock, starts communication, sends I2C commands and stops communication
  • Slave This is the device that listens to the bus and is addressed by the master
  • Multi-master I2C can have more than one master and each can send commands
  • Arbitration A process to determine which of the masters on the bus can use it when more masters need to use the bus
  • Synchronization A process to synchronize clocks of two or more devices
This resulted in few upgrades to the standard-mode I2C specifications:
  • Fast Mode – supports transfer rates up to 400 kbit/s
  • High-speed mode (Hs-mode) – supports transfer rates up to 3.4 Mbit/s
  • 10-bit addressing – supports up to 1024 I2C addresses
The before mentioned reference design is a bus with a clock (SCL) and data (SDA) lines with 7-bit addressing. The bus has two roles for nodes: master and slave:

Master node — node that generates the clock and initiates communication with slaves
Slave node — node that receives the clock and responds when addressed by the master

The bus is a multi-master bus which means any number of master nodes can be present. Additionally, master and slave roles may be changed between messages (after a STOP is sent).

There may be four potential modes of operation for a given bus device, although most devices only use a single role and its two modes:

master transmit — master node is sending data to a slave
master receive — master node is receiving data from a slave
slave transmit — slave node is sending data to the master
slave receive — slave node is receiving data from the master

example of the I²C protocol as an I²C master: 

// Hardware-specific support functions that MUST be customized:
#define I2CSPEED 100
void I2C_delay(void) { volatile int v; int i; for (i=0; i < I2CSPEED/2; i++) v; }
bool read_SCL(void); // Set SCL as input and return current level of line, 0 or 1
bool read_SDA(void); // Set SDA as input and return current level of line, 0 or 1
void clear_SCL(void); // Actively drive SCL signal low
void clear_SDA(void); // Actively drive SDA signal low
void arbitration_lost(void);
 
bool started = false; // global data
void i2c_start_cond(void) {
  if (started) { // if started, do a restart cond
    // set SDA to 1
    read_SDA();
    I2C_delay();
    while (read_SCL() == 0) {  // Clock stretching
      // You should add timeout to this loop
    }
    // Repeated start setup time, minimum 4.7us
    I2C_delay();
  }
  if (read_SDA() == 0) {
    arbitration_lost();
  }
  // SCL is high, set SDA from 1 to 0.
  clear_SDA();
  I2C_delay();
  clear_SCL();
  started = true;
}
 
void i2c_stop_cond(void){
  // set SDA to 0
  clear_SDA();
  I2C_delay();
  // Clock stretching
  while (read_SCL() == 0) {
    // add timeout to this loop.
  }
  // Stop bit setup time, minimum 4us
  I2C_delay();
  // SCL is high, set SDA from 0 to 1
  if (read_SDA() == 0) {
    arbitration_lost();
  }
  I2C_delay();
  started = false;
}
 
// Write a bit to I2C bus
void i2c_write_bit(bool bit) {
  if (bit) {
    read_SDA();
  } else {
    clear_SDA();
  }
  I2C_delay();
  while (read_SCL() == 0) { // Clock stretching
    // You should add timeout to this loop
  }
  // SCL is high, now data is valid
  // If SDA is high, check that nobody else is driving SDA
  if (bit && read_SDA() == 0) {
    arbitration_lost();
  }
  I2C_delay();
  clear_SCL();
}
 
// Read a bit from I2C bus
bool i2c_read_bit(void) {
  bool bit;
  // Let the slave drive data
  read_SDA();
  I2C_delay();
  while (read_SCL() == 0) { // Clock stretching
    // You should add timeout to this loop
  }
  // SCL is high, now data is valid
  bit = read_SDA();
  I2C_delay();
  clear_SCL();
  return bit;
}
 
// Write a byte to I2C bus. Return 0 if ack by the slave.
bool i2c_write_byte(bool send_start,
                    bool send_stop,
                    unsigned char byte) {
  unsigned bit;
  bool nack;
  if (send_start) {
    i2c_start_cond();
  }
  for (bit = 0; bit < 8; bit++) {
    i2c_write_bit((byte & 0x80) != 0);
    byte <<= 1;
  }
  nack = i2c_read_bit();
  if (send_stop) {
    i2c_stop_cond();
  }
  return nack;
}
 
// Read a byte from I2C bus
unsigned char i2c_read_byte(bool nack, bool send_stop) {
  unsigned char byte = 0;
  unsigned bit;
  for (bit = 0; bit < 8; bit++) {
    byte = (byte << 1) | i2c_read_bit();
  }
  i2c_write_bit(nack);
  if (send_stop) {
    i2c_stop_cond();
  }
  return byte;
}

No comments: