The I2C Bus - uraich/IoT4AQ GitHub Wiki
The I2C Bus
Introduction
While the PMS5003 uses an asynchronous serial line, formally used to connect dumb terminals to a computer, and which uses start and stop bits for synchronization, the I2C bus uses an extra clock line and is therefore synchronous. The I2C (the Inter Integrated Circuit) protocol was invented by Philips in the early 1980's. Explaining all the details of how I2C works exceeds the scope of these lectures. For more information you may read the Sparkfun tutorial on I2C. I2C is a master-slave protocol, where the master, in our case the ESP32 CPU, initiates the data transfer.
In the hardware kit for the workshop we have two I2C slave devices: The DS3231 RTC (Real Time Clock) uses I2C and the 2004-LCD display is equipped with an I2C PCF8574 remote 8-bit I/O expander.
The I2C protocol
I2C uses 4 connections:
- Vcc and GND
- SCL: a clock line
- SDA: a data line The diagram below shows a data transfer on the I2C bus:
A few comments on this diagram: We see that the first data byte contains an I2C address (A0-A6). In fact each I2C slave has its own hardwired address. Since A0..A6 corresponds to 7 bits, a total of max. 127 devices can be connected to the bus (in fact the number is slightly smaller). The master sends out the address and checks the ACK (acknowledge bit), which is sent by the slave, if it sees that it has been addressed. Trying to address all addresses between 0 and 127 and checking the acknowledge bit, lets us find out, which devices are connected to the bus. The 8th bit of the first byte, sent by the master, tells the slave if a read or a write operation is requested. If we want to write a register on an I2C slave, then first the I2C address of the slave will be written (with the R/W bit low, indicating a write cycle), then the register address within the slave is transmitted and finally the data to be written to that address. For more details about the I2C protocol as it is used within our two I2C slave modules have a look at the DS3231 data sheet and the PCF8574 data sheet.
The Wire library
The Arduino SDK provides the wire library of functions allowing to access I2C slave modules. In fact, for our exercises we do not really need to understand all the details of the I2C bus, since these are hidden in libraries built around the wire library (LiquidCrystal_I2C for the LCD display and RTClib for the DS1307 RTC). For an understanding of the inner workings of these libraries however, we must know how I2C works. There is also a small program supplied to the workshop (i2cScan.ino), which checks for the I2C devices connected to the bus. This can come handy to discover cabling problems and this program uses the wire library directly. You can also access the DS3231 without using RTClib, in which case knowledge of I2C and the wire library is needed.
The most important functions are:
- WIRE.begin(): Initializes the I2C bus.
- WIRE.startTransmission(i2c_address): creates an I2C start condition and sends the slave address
- WIRE.endTransmission(): creates the stop condition
- WIRE.write(data) or WIRE.write(string) or WIRE.write(data,data_length): sends data
- WIRE.requestFrom(i2c_address, noOfData): requests to read a set of data
- WIRE.available(): check if data are available
- WIRE.read(): read a data byte
I2C scan
Having a look at the protocol diagram, we can see that the master sends a slave address, and the ACK bit is pulled down by the slave, once it sees its address. This can be used to find out which slave is connected to the bus.
Here we see that two slaves are connected:
- 0x68: DS3231 RTC
- 0x27: PCF8574 remote 8-bit I/O expander
Hardware connections
These are the connections needed to make I2C modules work. You can connect several modules onto the same SCL and SDA lines:
I2C slave | ESP32 GPIO |
---|---|
Vcc | 3.3V |
GND | GND |
SCL | GPIO 22 |
SDA | GPIO 21 |