What is I2C and How it works
The Inter-Integrated-Surface (I2C) Protocol is a synchronous serial communication bus that allows the transfer of data between a chip(master central processor) and multiple peripheral ICs (slaves) on the same circuit board using just two wires. Basically, we use it for short-distance, intra-board communication.
I2C is one of the many serial Data-transfer communication mechanisms. I2C is commonly in:
- Interfacing Displays
- Interfacing Cameras
- Bluetooth interconnects.
I2C is a half-duplex communication protocol, which means Signaling and Data can only travel in one direction at a time( MPU/MCU towards sensor| From Senor towards MPU/MCU).The bus will execute both read and write operations at the same time. The Inter-Integrated Circuit( I2C) Protocol, allows multiplexing multiple devices to share data with one another. It is a bi-directional half-duplex two-wire bus system for transmitting and receiving data between masters (M) and slaves (S). These two wires are known as the Serial clock line (SCL) and the Serial data line (SDA).
In I2C communication, Masters and Slaves play critical roles. The master initiates communication, creates a clock, and terminates communication, while the slave is handled by the master and behaves in accordance with the master command. Multiple masters will also be able to interact with multiple slaves.
I2C also offers the best features of Serial Peripheral Interface (SPI) and Universal Asynchronous Receiver-Transmitter (UARTs). Just like in SPI, using an I2C connection, you can control multiple slaves using the singular or multiple masters. An I2C connection uses only two wires to transfer data between devices like a UART:
1) SDA (Serial Data) – The line for the master and slave to send and receive data.
2) SCL (Serial Clock) – The line that carries the clock signal.
Bit Rate and Baud Rate
The baud rate is the rate at which data is transmitted over a transmission medium. When addressing electronics that use serial communication, we use the term baud rate sometimes. In the form of a serial port, “9600 baud” means that the serial port can transmit a limit of 9600 bits per second.
When baud rates exceed 76,800, the cable length must be shortened. Because of how much of the wire is untwisted around each unit, the higher the baud rate, the more sensitive the cable becomes to installation efficiency.
Bit rate and Baudrate are two terms that are commonly used in communication systems. Baudrate is the number of signal units transmitted per second, whereas Bit rate is the number of bits (0 &1, also known as bit intervals) transmitted per second. Therefore, bauds are capable of determining the required bandwidth of the channel, not in terms of bits.
|Bit Rate||Baud Rate|
|Bit rate is the transmission of the number of bits per second.||Baud rate is the number of signal units per second.|
|It can be defined as per second travel number of bits.||It can be defined as a per second number of changes.|
|Bit rate focuses on computer efficiency.||Baud rate focuses on data transmission.|
|Bit Rate = Baud rate x the number of bit per baud||Baud Rate = Bit rate / the number of bit per baud|
Bit rate = baud rate x the number of bit per baud
Baud rate = bit rate / the number of bits per signal unit
Synchronous peripheral communication means that the transfer of data is taking place one at a time in regular intervals as set by the clock line. This is very useful for connecting multiple microcontrollers logging data into one single memory card or a LED.
FUN FACT: Ideally we pronounce I2C as I -squared- C and Philips Semiconductors was the inventor of I2Cin 1982.
Features Of I2C
You can scale up an I2C network very quickly and effectively. New devices can directly be connected to the two I2C bus lines (SDA and SCL).
You can connect multiple peripheral devices to the same couple of pins (SDA and SCL) from your board to an I2C connection. It assigns a 7-bit address to each device connected for better communication with the circuit board.
Usually, the speed of the I2C bus is constant that is 100khz, 400khz, or 1mhz. An I2C is an excellent protocol to connect devices that don’t require a lot of data and fast response.
I2C clock and data line require to have pull-up resistors to prevent from floating to other values. Since we connect multiple devices to these two lines, the I2C protocol requires the devices to stop driving the lines when not in use. Most I2C boards have these pull-up resistors inbuilt.
- You can adjust the data transfer speed anytime whenever you need, unlike UART, where a prior agreement on data transfer rate is the requirement.
- The hardware is less complicated than with UARTs.
- The ACK/NACK bits used in transfer give the confirmation of each successfully transferred frame.
|Maximum Speed||Standard Mode= 100 kbps|
|Fast Mode= 400 kbps|
|High Speed Mode= 3.4 Mbps|
|Ultra Fast Mode= 5 Mbps|
|Synchronous or Asynchronous||Synchronous|
|Serial or Parallel||Serial|
|Max# of Masters||One|
|Max# of Slaves||128|
|Clock Speed||100Khz/400 kHz/3.4Mhz/5Mhz|
I2C implementations exist in both hardware and software implementation by machine.I2C and machine.SoftI2C classes.
- The hardware I2C performs the read/write and is faster as it uses the hardware support of the system. The only restriction here is that some specific pins will be occupied.
- You can implement Software I2C by “bit-banging” and with no restriction on the pin’s use age. These classes have the same methods but different ways of construction.
You can display the I2C protocol connection using MicroPython by connecting and reading an I2C temperature sensor connected to an ESP 8266.
Requirements To Perform This Tutorial
- An ESP 8266 circuit board with MicroPython. Some other boards should also work just to check if there is any difference in the I2C usage of that particular board.
- An MCP9808 highly accurate I2C temperature sensor.
- Breadboard and jumper wires.
Also, you have to connect all the components the same way, as shown in the picture below:
Setup of I2C
We first need to initialize the access to the I2C bus. To initialize, connect to the board’s serial or REPL and run the following commands:
import machine i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4))
Using these commands, the machine module containing API for hardware access will be imported. Then the following parameters initialise and create an instance of the I2C class:
- SCL pin
- SDA pin
You need to pass each pin as an instance of machine pin class and not as numbers.
To make sure if the connected device is able to communicate with the boars, run the following code,, which will scan for any devices connected and return their address once the I2C bus is all set up.
The device address will be returned (like 24 over here) that was found by the I2C bus. If no value is being returned, this means that the device is not properly connected.
The memory address stores the data for all the I2C sensors and devices. Therefore, you access the device by using both its I2C address and memory location in the device. The MCP908 device used in this experiment first creates some variables that will hold the memory address value as it simplifies the code.
address = 24 temp_reg = 5 res_reg = 8
NOTE: These values are for the MCP908 temperature sensor and may differ for other devices.
To read the temperature, you will need the value of the 16-bit temperature register (address 5). With these two devices (address 24)(address 5) register the value by calling the following function:
data = i2c.readfrom_mem(address, temp_reg, 2) print(data)
The list of Parameters used in this function are:
- Device address – address of I2C device.
- Memory address – address of memory to read the device on.
- Number of bits(read) – number of bits from memory that are to be read and returned.
readfrom_mem function will return a byte string along with the data received from the device. Also, make sure that the number of byte String should match the number of required bytes.
You can also read the data into a buffer using readfrom_mem_into function like the following:
data = bytearray(2) i2c.readfrom_mem_into(address, temp_reg, data) print(data)
This method is as likely as the readfrom_mem and takes the address and memory value as the first two parameters. The buffer or bytearray object with its value will be the third device. The buffer itself will fill the number of bytes of data.
The readfrom_mem_into function allows us to save memory and increase performance by the creation of a buffer to receive data ahead of time. This is very useful to read I2C data in a loop that is constantly calling the read function. Using the readfrom_mem function in a loop will assign memory to hold, and the result in slowing down of the program. Creating a buffer before the loop and using the readfrom_mem_into function to fill the buffer can prevent the need to collect the unused memory which is a very slow process.
After having the temperature register value, put the 12-bit value in degrees Celsius with the following function:
def temp_c(data): value = (data << 8) | data temp = (value & 0xFFF) / 16.0 if value & 0x1000: temp -= 256.0 return temp temp_c(data)
It is not printing the temperature in degrees Celsius. Now place and hold your finger on the MCP9808 so as to heat it up. Check the temperature again.
temp_c(i2c.readfrom_mem(address, temp_reg, 2))
Now it printed the rise in temperature.
You can also write the data from the memory while reading it using the writeto_mem function. If MCP9808 has a resolution register that allows the configuration of the speed and accuracy of the temperature measurements, then by default, the device will be set to the slowest and most accurate measurement resolution. You can alter the device by writing a 0 value to the resolution register (address 8) so that you get a faster but less accurate output. The code for the same:
i2c.writeto_mem(address, res_reg, b'\x00')
The writeto_mem function takes the device address and the memory address as the first two parameters, just like the readfrom_mem function. The third parameter used here is a byte String with bytes to send to the device. Here the hex value is 0x00. The \x syntax in a byte string allows you to specify a raw hex value instead of a printable character.
Since now the reading is less accurate, there will be fewer digits after the decimal point. For example:
temp_c(i2c.readfrom_mem(address, temp_reg, 2))
By setting up the register value to 3, you can get back the most accurate measurement resolution like the following:
i2c.writeto_mem(address, resolution, b'\x03') temp_c(i2c.readfrom_mem(address, temperature, 2))
There are some I2C devices that don’t provide their data to the memory or register. These devices are meant to just send and receive bytes of data with no associated register/memory address. You need to use the following functions instead of the register/memory operations that have the same use.
- readfrom function: It reads the number of bytes from the device and returns them as a new byte string. This works the same way as the readfrom_mem function but without a memory address parameter.
- readfrom_into function: This function reads the number of bytes from the device and saves them in a buffer. This works the same way as the readfrom_mem_into function but without a memory address parameter.
- writeto function: This function writes a byte string to the device. It works the same way as the writeto_mem function but without any memory address parameter.
Wrapping It Up
This was a tutorial on how to program and interface I2C on an ESP8266 using MicroPython. We used an MCP908 I2C temperature sensor to perform the memory and register address functions. Almost all the I2C sensors available uses this same memory/register mode of operation. Also, the ESP 8266 MicroPython port is capable of performing “raw” or primitive I2C operations that act at a level lower than the device or the memory operation.