Many sensors on the market use an I2C interface. The datasheets for these sensors usually simply recall how an I2C bus works by copying and pasting the original Philips/NXP specifications, but for engineers implementing PC- or Internet-based automation solutions today, this isn't necessarily the most relevant explanation. So here's our crash course for new I2C users.
What is I2C?
I2C is a serial data transmission standard, like RS232 for example, but using different transmission conventions, which make it much easier to integrate into a chip. I2C is the acronym for "Inter IC", meaning "between integrated circuits". This is why many sensors and other electronic circuits are designed to be interrogated and/or configured via an I2C connection.
The I2C protocol enables several sensors or circuits, also known as nodes, to be daisy-chained on a common electrical bus. The bus master takes the initiative in sending commands or requesting measurements from auxiliary circuits.
An I2C bus has at least three wires:
- Ground (GND)
- Clock signal (SCL)
- Data signal (SDA)
Usually, one or two power supply wires supplied by the master are added. Two supply wires are often used if the voltage of the I2C signals on the SCL/SDA lines (typically 1.8V or 3.3V) is different from that required to supply the auxiliary circuits (typically 3.3V or 5V).
Typical I2C bus architecture
Both SCL and SDA transmission lines are in the up position when the bus is at rest, pulled up to the bus voltage by fixed resistors (called pull-ups). When a node (master or slave) needs to transmit a signal, it simply pulls the desired line to ground. When it releases the pull-up, the line returns to bus voltage.
It's always the master who takes the initiative to transmit a message, and it's he alone who has control over the down edges of the clock line (SCL). Let's see how it communicates with other circuits.
Communicating on an I2C bus
Addressing
As there may be several auxiliary circuits, each circuit must have a unique address, and messages must be addressed. All messages sent by the master therefore begin with a byte consisting of the 7-bit address of the target circuit, plus a low-order bit indicating the direction of transmission. This first byte can be calculated as follows:
- the address of the node for which the message is intended, multiplied by two
- +0 if the message is to send a series of bytes to the specified node
- +1 if receiving a series of bytes from the specified node
Examples:
When the master sends bytes to node 11, the message starts with
22 ...
When the master reads bytes from node 11, the message starts with
23 ...
(note: in this article, bytes are always represented in hexadecimal, i.e. with values from 00 to FF)
Message content
Apart from the use of the first byte described above, message content can be arbitrarily defined by the manufacturer of each circuit. In most cases, however, the convention used is indexed register access:
- the master sends the index of the register of interest in the 2nd byte of the message
- to modify the register (write), the master adds the value to be written to the 3rd byte
- to read the register (read), the master repeats a read message directly after the 2nd byte
Examples:
To write FF to register 05 of node 11, the master will send the following message:
22 05 FF
To read the value in register 05 of node 11, the master will use the following two messages
22 05 23 xx
The byte xx is not sent by the master, but received by the master, and corresponds to the value of the register.
Access to consecutive registers
In general, it is possible to write and read several consecutive registers simply by adding bytes to the message, without having to start a new message for each register. This is particularly useful for reading multi-byte coded values, which are then transmitted atomically.
Examples:
To write FF EE to registers C0 and C1 of node 11, the master will send the following message:
22 C0 FF EE
To read the value of registers C0 and C1 on node 11 in one go, the master will use the following two messages
22 C0 23 xx xx
Again, the xx represent bytes that are not sent by the master, but received, and correspond to the register values.
Encoding and decoding an I2C transmission
So far, we've described the messages exchanged without worrying about how they were delimited, or how the master could check that the recipient had received each byte sent. These aspects are part of the coding of messages on data lines.
Message delimitation
Given the importance of recognizing the first byte, which determines the recipient of the message, the I2C protocol provides a very clear mechanism for determining the state in which the bus is (at rest between two messages, in transmission, etc.) based on conditions detected during transitions occurring on the SCL and SDA lines:
- The START condition, denoted by the symbol {S} and defined by the descent of the SDA line while SCL is still high, signals the start of a message.
- The STOP condition, denoted by the symbol {P} and defined by the SDA line rising while SCL is still high, marks the end of a message.
- Between a START and a STOP, bits are transmitted by lowering the SCL line, changing the value of SDA according to the bit to be transmitted, and then raising the SCL line. The bit is not read by the other party until the SCL line is raised.
- The RESTART condition, noted by the symbol {R}, consists in re-issuing a START condition in the middle of a message, without first passing through the STOP condition. It is used to link two messages directly, typically to read the value of a specified register. It is not supported by all I2C circuits.
Transmitted byte acknowledgement
To guarantee correct reception of sent bytes, after each byte has been transmitted, the node for which the byte was intended acknowledges receipt with an ACK bit, denoted by the {A} symbol, by setting the SDA line to zero. If the receiver hasn't recognized that a byte has been transmitted to it, or if it's not connected to the bus at all, it won't pull the SDA line to ground, which will be interpreted by the master as a NAK, denoted {N} and lead it to abandon the communication. In some cases, a node may voluntarily send a NAK to signify the end of the data to be transmitted. This is generally the convention used by the master to quit data transmission when a message is received.
Examples of coding:
To repeat the previous examples, the coding of the message
22 C0 FF EE
is encoded on the I2C bus using the following transitions and bytes:
{S} 22{A} C0{A} FF{A} EE{A} {P}
Reading a register is described by the messages
22 C0 23 xx xx
can be coded on the I2C bus using RESTART in the following way:
{S} 22{A} C0{A} {R} 23{A} xx{N} {P}
Sending I2C messages with the Yocto-I2C
The Yocto-I2C allows you to initiate transmissions on an I2C bus as the bus master. You can either simply specify the bytes to be sent and received, and let the Yocto-I2C automatically manage the coding of the various bus states, or specify all the states yourself. Whichever option you choose, the module will return all acknowledge receipts and bytes received on the I2C bus, prefixed by the address of the node that sent them.
To experiment with transactions using the Yocto-I2C interactive interface, launch VirtualHub and click on the module's serial number. You can then test the sending of messages in the web interface, either by bytes or by specifying all states, as illustrated below:
The two ways of sending messages interactively
When sending bytes, you don't even need to encode the address byte yourself: you simply prefix your message with the @ symbol followed by the module address, and then specify only the bytes to be sent and received. Yocto-I2C will do all the coding for you, including generating the address byte at the start of each message.
To run transactions from your own program, use the YI2cPort class in our library. You can then:
- either construct a string identical to the one used interactively above and use the writeLine or queryLine methods to send it blind, or send it and read the resulting transaction; or
- use the i2cSendBin or i2cSendAndReceiveBin methods, which work directly on byte arrays
Finally, you can configure the Yocto-I2C itself to periodically initiate transactions of your choice and analyze the result using the job system. You'll find various examples in the various blog articles listed on the Yocto-I2C product page.
A real-life example
Let's take a look at a real-life example of how to convert the information found in a sensor datasheet into measurement transmissions. Take, for example, the TAOS TCS34725 sensor, the use of which we demonstrated in a previous article.
Checking voltages
The first things to check before connecting the sensor are the supply voltage and the I2C bus voltage. Here's what you'll find on page 3 of the datasheet:
We can see that this chip can be purchased in several variants, using different I2C bus voltages: 3.3V or 1.8V. In this case, you need to be careful which model you use!
A little further down we find the following table:
The ideal supply voltage seems to be 3V, but a regulated voltage of 3.3V is still within the recommended range.
If you want to interface your sensor directly via a compute board's built-in I2C bus, beware of the voltages supported on the I2C bus: an Arduino can only communicate on a 5V I2C bus, and a Raspberry Pi is designed to communicate with a 3.3V I2C bus. If this is not the voltage of your sensor, you'll need to add an I2C level-changer circuit between the two. Because of the bidirectional nature of I2C transmission, it's not possible to use a simple voltage divider.
If, like us, you're using a Yocto-I2C, just take care to select the right supply and communication voltages in the device configuration.
Registers
As indicated in the first table above, the address of the sensor on the bus depends on the variant you have, but is most likely 29. To send data to this sensor, we therefore start the message with byte 52 (because in hexadecimal, 29 x 2 = 52), and to receive data, we start a message with byte 53.
This sensor uses the classic register system, as shown on page 13 of the datasheet:
We can see that there are configuration registers (ENABLE, CONTROL, CONFIG), and registers which seem to be intended to hold measurements (RDATAL, ...). It is very common to have to activate and configure the sensors first, before being able to read the measurements. To find out more, don't hesitate to take a closer look at the datasheet, to understand how the registers are used.
The first register described is called COMMAND, and is a little unusual. It corresponds to the byte containing the index of the register concerned (the 2nd byte of messages sent by the master). Instead of simply being a register index, this sensor combines additional information in this byte.
In particular, the top bit must always be set to 1. So, for example, to access register 00, byte 80 must be written instead of 00.
The first real register is ENABLE (index 00). This is the register we use to activate the sensor.
It's important to read the fine print: to activate the sensor, first set the Power ON (PON) bit, wait at least 2.4ms, then set the RGBC Enable (AEN) bit. The corresponding commands are:
52 80 01 52 80 03
with a short wait in between.
Once the sensor has been activated, it still needs to be configured. The main parameter described in the datasheet is the integration time (RGBC, index 01), which simultaneously determines measurement frequency and sensitivity. For a 10Hz reading, the datasheet indicates that the D5 value should be used. We therefore send the command52
81 D5
All that remains is to periodically read the registers containing sensor status and measured values. These are all the registers from index 13 onwards. To read several consecutive registers, the description of the COMMAND byte above tells us that we need to add one more bit (20). The value of the register byte will therefore be B3. The message structure will therefore be
53 B3 xx xx xx ...
Your turn: experimentation and troubleshooting
The Yocto-I2C allows you to test message sending in a very simple way. You can start by sending a trivial message comprising just the start condition, the sensor address byte, and the stop condition, and see if the sensor acknowledges it with an {A}. Then you can send more complex messages, and for each one directly see the reaction on the bus in the form of detailed states.
Some sensors tend to crash if you send them invalid commands. Don't hesitate to disconnect and reconnect them after an unsuccessful attempt!
Find out more
For the sake of brevity, this article has not covered all of the details and special cases provided for in the I2C specification. If you'd like to find out more, you'll find a myriad of details at https://www.i2c-bus.org.