In previous tutorials, we covered serial communication using the UART and serial software on Arduino. Universal asynchronous receiver/transmitter (UART), I2C, and SPI are the most commonly used serial interfaces in embedded systems.
UART is useful for full-duplex serial communication with a single device over two wires. The I2C interface or two-wire interface (TWI) is used for half-duplex synchronous serial communication with multiple devices in master-slave mode. The SPI bus is used for full-duplex synchronous serial communication with multiple devices.
Now in this tutorial, we will learn about synchronous serial communication on Arduino using the Inter-Integrated Circuit (I2C) bus.
The I2C bus
I2C, or TWI, is a master-slave synchronous serial communication protocol originally developed by Philips Semiconductors (which is now NXP). The I2C bus has only two wires: one is a data line (SDA) and the other is a clock line (SCL).
On a two-wire bus, hundreds of master and slave devices can communicate serial data using the I2C protocol.
I2C is a master-slave type bus and there must be at least one master device to control the two-wire bus.
The master device is responsible for synchronizing data transfers:
- Generating the clock signal
- Selecting a slave device for communication
- Controlling read/write operations on the bus
Since there is only one data line, only half-duplex communication with a single slave device is possible at a time. This means that any number of master devices can be connected to the I2C bus, but only one master can control the bus at the same time.
The wires are pulled HIGH by default using the pull-up resistors and the I2C drivers are open drain (i.e. they can only pull the line LOW). This successfully avoids any bus contention.
Each I2C slave connected to a bus must have a unique address. The master device uses in-band addressing to access communication with the slave devices. The I2C addresses of the various slave devices are decided by NXP.
Many slave devices have a configurable I2C address. In-band addressing can be 7 or 10 bits. In 7-bit addressing, 112 slave devices can be connected to the bus. In 10-bit addressing, 1008 slave devices can be connected to the bus. Additionally, the I2C master can use 7-bit and 10-bit addresses on the same bus.
Additionally, there are some breakout boards that allow communication with multiple slave devices using a common address. On these boards, slave devices are multiplexed through an integrated controller.
Implementing the I2C protocol simply requires two open drain channels on a device. In most controllers/computers, there is dedicated hardware to manage the I2C protocol.
The data transfer rate in I2C depends on the clock frequency generated by the I2C master.
There are standard I2C clock speeds, which include:
- 100 to 400 kHz (I2C standard)
- 1 MHz (fast mode I2C)
- 3.4 MHz (High Speed I2C)
- 5 MHz (ultrafast mode I2C).
Typically, the I2C bus uses TTL logic levels of 5V or 3V3. It is recommended to connect voltage compatible devices via the I2C bus, otherwise the low voltage device risks damaging its I2C channels when receiving data. If two devices have different TTL voltage levels, a suitable voltage shifter must be used between the devices and connected via the two-wire bus.
The I2C protocol
Data communication is always initiated by a master device on the TWI bus. A master device generates an initial condition for accessing the bus. When generating the start condition, it is decided which master will access the bus if two or more try to do so at the same time.
After doing this successfully, the master transmits an in-band address in the form of an address frame to select a slave device for communication. The master must read or write data to the selected slave device, which is indicated in the address box.
After selecting a slave device, the master can read or write data in the form of 8-bit data packets. After each data packet is exchanged, the receiving device sends an acknowledgment bit to the sender to indicate that the byte was successfully communicated.
After gaining access to the I2C bus, a master can generate multiple boot conditions to talk to a slave device multiple times – or to talk to multiple slave devices one after the other.
To release the bus to other masters, however, the master needs to generate a stop condition. Slave devices can never initiate communication on their own or control the bus, but they can temporarily hold the clock pulse (after a stop condition) to make the masters wait to access the bus. This is called clock stretching. (Learn more about the I2C protocol here ).
I2C bus applications
Since multiple devices can communicate data over just two wires using the I2C protocol, the I2C/TWI interface is widely used by sensors and embedded modules. Some examples of sensors that use the I2C bus for data communication are the ADXL345 accelerometer, the L3G4200D gyroscope, the MC5883L magnetometer, the BMP180 pressure sensor, the DS1307 RTC and others.
Using I2C on Arduino boards
Most Arduino boards have at least one I2C module. The I2C module can be accessible through one or more ports on the board.
The I2C pins are not internally pulled high, so you may need to pull them HIGH using external resistors, depending on the I2C device they are connected to.
The I2C pins on an Arduino UNO are shown here:
This table summarizes the location of the I2C pins on various Arduino boards…
The wire library
It's easier to talk to I2C/TWI devices using Arduino, in part because this platform provides a library: A wire to manage communication of Arduino boards on an I2C bus.
Arduino boards can be configured as an I2C master and an I2C slave. The wire library uses a 32-byte buffer, so any I2C data transfer must be within this limit. If more bytes are communicated in a transmission, the excess bytes are left out.
The wire library can be imported into an Arduino sketch using this instruction:
#include
The wire library has these methods:
fio.begin – used to join the I2C bus as a master or slave. This has this syntax:
Wire.begin
or
Wire.begin(address)
If the Arduino joins the I2C bus as a master, the method must be called without arguments. If the Arduino joins the I2C bus as a slave, an arbitrary 7-bit address must be passed as an argument. The slave sa can have an address between 8 and 127. Addresses 0 to 7 are reserved, however, and not used by Arduino.
Here are valid examples of this method:
Wire.begin //Arduino as I2C master
Wire.begin(8) // Arduino as I2C slave
Wire.setClock – used by Arduino and configured as an I2C master. This method is used to change the clock frequency of the I2C bus. I2C slaves have no minimum frequency, although 100 kHz is the baseline. This method has this syntax:
Wire.setClock(clockFrequency)
It takes the clock frequency in Hertz as an argument. Here is a valid example:
Wire.setClock(3200)
Wire.beginTransmission (address) – used by Arduino and configured as an I2C master. This method is used to initiate transmission to an I2C slave. The address of the selected I2C slave is passed as an argument to the function. It has this syntax:
Wire.beginTransmission(address)
And here is a valid example:
Wire.beginTransmission(8)
Wire.write – used to write data to the I2C bus. It can be used by Arduino as an I2C master and I2C slave. As a master, this method must be used between calls to the beginTransmission and endTransmission methods.
As a slave, this method can be used in response to a request from a master device. It can be used to transmit a value, a string, or a byte array. It has this syntax:
Wire.write(value)
or
Wire.write(string)
or
Wire.write(data, length)
Here are valid examples:
Wire.write(7)
Wire.write(“Hello”)
Wire.write(arraybytes, 5) //arraybytes is an array of 5 byte values
array of 5-byte values
Wire.onReceive(handler) – used by Arduino and configured as a slave. It assigns a handler function to be called in response when data is received from the I2C master. The user-defined handler function must take the number of bytes read from the master as an argument if necessary. The function must also be of type void and return nothing. It has this syntax:
Wire.onReceive(handler)
Here is a valid example:
Wire.onReceive(receiveEvent);
void receiveEvent
{
Serial.print(“data received from I2C master);
}
Wire.requestFrom – used by Arduino and configured as an I2C master. This method is used to request data from the I2C slave. The requested data is then retrieved by the Arduino master using the Wire.available and Wire.read methods. It has this syntax:
Wire.requestFrom(address, quantity)
Wire.requestFrom(address, quantity, stop)
The method takes two arguments – the I2C address of the slave device and the number of bytes requested. It has an optional Boolean argument – stop which can be 'True' or 'False'.
Stop is by default True. When True, a stop condition is generated after the request, releasing the bus. When False, a reset condition is generated after the request, allowing the Arduino master to continue using the bus. The method returns the number of bytes received from the slave device. Here is a valid example:
Wire.requestFrom(8, 6, true)
Wire.onRequest(handler) – used by Arduino configured as a slave. It assigns a handler function to be called in response when the I2C master requests data from this slave. The user-defined handler function takes no arguments and returns nothing. This method has this syntax:
Wire.onRequest(handler)
And here is a valid example:
Wire.onRequest(requestEvent);
void requestEvent
{
Wire.write(“Master, get this data”);
}
Yarn.available – used as Arduino is configured as master or slave. Returns the number of bytes available for recovery from the I2C bus. It must be called by the master Arduino after a call to requestFrom and by the slave Arduino inside the onReceive handler. It has this syntax:
Yarn.available
The method does not accept arguments. This is a valid example:
while (Wire.available )
{
char c = Wire.read;
}
Wire.read – Reads a byte transmitted from a slave device to a master after a call to requestFrom or if transmitted from a master to a slave. It has this syntax:
Fio.ler
The method does not accept arguments. This is a valid example:
Wire.requestFrom(2, 6);
while(Wire.available)
{
char c = Wire.read;
}
Wire.endTransmission – used by an Arduino and configured as an I2C master. This method is used to terminate transmission to the I2C slave and transmit queued bytes using the write method. Therefore, it must be called after a call to the write method. It has this syntax:
Wire.endTransmission
Wire.endTransmission(stop)
The method takes an optional argument – stop. It is a Boolean value, which if set to True generates a stop condition, releasing the bus. If set to False, generates a reset condition, allowing the master Arduino to continue accessing the bus.
This method returns a status byte, which can be 0, 1, 2, 3 or 4.
- If the status byte is 0, the transmission ended successfully.
- If it is 1, the data was too long to fit in the 32 byte buffer
- If 2, NACK was received at the broadcast address
- If it is 3, NACK was received in data transmission
- If it is 4, an error occurred.
Here is a valid example of this method:
Wire.beginTransmission(8);
Wire.write(“Hello, I'm I2C Master”);
Wire.endTransmission;
Arduino as I2C master
The Arduino can be configured as an I2C master by calling Wire.begin without any arguments. The Arduino master can set the I2C clock frequency using the Wire.setClock method if the clock speed needs to be modified.
It can transmit data to a slave by calling the Wire.beginTransmission , Wire.write , and Wire.endTransmission methods. It can also request data from a slave using the requestFrom method and retrieve the requested data using the Wire.available and Wire.read methods.
Arduino as I2C slave
The Arduino can join the I2C bus as a slave by calling the Wire.begin method with its address passed as an argument. It can execute a block of code in response to data received from the master using the Wire.onReceive(handler) method.
In the handler function, the received bytes can be read using the Wire.available and Wire.read methods. It can also execute a block of code in response to a request from the I2C master using the Wire.onRequest(handler) method.
In the handler function, the requested data can be sent using the Wire.write method.
In the next tutorial, we will cover how to interface an ADXL345 accelerometer sensor with Arduino using the I2C interface.
(tagsToTranslate)Arduino