Embedded System I2C Tutorial
This article is under construction
Contents
Theory of Operation
I2C is prounced "eye-squared see". It is also known as "TWI" because of the intial patent issues of this BUS. This is a popular, low throughput (100-1000Khz), half-duplix BUS that only uses two wires regardless of how many devices are on this BUS. Many sensors use this BUS because of its ease of adding to a system.
Open-Collector BUS
I2C is an open-collector BUS, which means that no device shall have the capability of internally connecting either SDA or SCL wires to power source. The communication wires are instead connected to the power source through a "pull-up" resistor. When a device wants to communicate, it simply lets go of the wire for it to go back to logical "high" or "1" or it can connect it to ground to indicate logical "0".
Pull-up resistor
Using a smaller pull-up can acheiver higher speeds, but then each device must have the capability of sinking that much more current. For example, with a 5v BUS, and 1K pull-up, each device must be able to sink 5mA.
Protocol Information
I2C was designed to be able to read and write memory on a slave device. The protocol may be complicated, but a typical "transaction" involving read or write of a register on a slave device is simple granted a "sunny-day scenario" in which no errors occur.
Write Transaction
A typical I2C write is to be able to write a register or memory address on a slave device. The protocol states that we will first send the slave address, then the memory address of the slave device, followed by the data to write to that memory address. To maximize throughput and avoid having to do this for each memory location, the memory address is considered "starting address". If we continue to write data, we will end up writing data to M, M+1, M+2 etc.
Code Sample | State Machine |
void i2c_write_slave_reg(void)
{
i2c_start();
i2c_write(slave_addr);
i2c_write(slave_reg);
i2c_write(data);
/* Optionaly write more data to slave_reg+1, slave_reg+2 etc. */
// i2c_write(data); /* M + 1 */
// i2c_write(data); /* M + 2 */
i2c_stop();
} |
Read Transaction
An I2C read is slightly more complex and involves more protocol to follow.
Code Sample | State Machine |
void i2c_write_slave_reg(void)
{
i2c_start();
i2c_write(slave_addr);
i2c_write(slave_reg);
i2c_start(); // Repeat start
i2c_write(slave_addr | 0x01); // Odd address
char data = i2c_read(0); // NACK if reading last byte
/* Optionaly read more data from slave_reg+1, slave_reg+2 etc.
*
*/
// data = i2c_read(1); /* M + 1 */
// data = i2c_read(0); /* M + 2 */
i2c_stop();
} |