I2C Tutorial
by Reinhard Grafl, creator of EV3 Basic
I²C (Inter-Integrated Circuit protocol) is a protocol intended to allow multiple “slave” digital integrated circuits (“chips”) to communicate with one or more “master” chips. I²C is pronounced I-squared-C but also in use is I2C (pronounced I-two-C). See this Sparkfun article, this Robot Electronics article or this Wikipedia article.
Why access I2C directly?
Normally interfacing to sensors from the EV3 brick is done using the easy-to-use Sensor.Read... commands. But some third-party devices are not compatible with the default sensor protocol and require extra programming. The vendors of such devices normally provide some programming blocks for the original graphics programming environment that handle all the details of the communication.
For EV3Basic there is probably no such support available, but normally there is some documentation specifying the low-level communication protocol. Having this information, the Sensor.CommunicateI2C or one of the other I2C related commands can be used to implement any such protocol.
Introduction to I2C
I2C is a communication facility that allows a single master to access multiple slaves on a common bus for reading and writing data. In the case of the EV3 each sensor port with its attached sensor device(s) can form such a bus.
The communication is done by the master sending a bunch of data bytes to a slave or by requesting a bunch of data bytes. The slaves themselves cannot initiate any data transfer. Because there can be multiple slaves connected to the bus, the master needs to make clear to which slave it wants to talk. This is done by sending a slave address (a number from 0 to 127) before communication. The designer of the system must make sure that no two slaves on a bus have the same address to avoid conflict.
The I2C protocol itself only provides a means for communication, the meaning of the data totally depends on the devices in question.
CommunicateI2C
This command of EV3Basic is the only way to access the I2C functionality directly in a raw form. It combines the operation of sending an arbitrary number of bytes and receiving some bytes back. Example:
W[0]=60 W[1]=70 W[2]=80 R=Sensor.CommunicateI2C(1,44,3,4,W)
This will first send the bytes 60,70,80 to the slave with address 44 that is connected to sensor port 1. Then it will fetch 4 bytes from the same slave. The received bytes are stored in the array R.
I2C Registers
The concept of registers is basically not part of the I2C protocol, but this concept is used so widely, that it is now a kind of standard way to do communication with many devices. On devices that use registers, all data transfer is done by the master writing into the slave's registers or reading data from the registers. A register is basically just a storage location that can hold one byte of data and is accessed via its register number (or register 'address' - but using this name will probably lead to confusion because the slaves themselves already have an I2C-address).
Since version 1.2.5, EV3Basic directly provides support for reading/writing from/to registers of I2C devices that follow the register standard.
Sources of confusion
Slave address vs. Register address
Many device documents refer to the slave address and the register number both just as 'address'. Be sure to understand the difference and what the documentation really means in each case.
Slave address is pre-multiplied by 2
The inner working of the protocol combines the 7 bits of the slave address (values from 0 to 127) with a single bit that specifies the direction (read or write) to give 8 bits that need to be transmitted from master to slave. Sometimes the developers of a device just write this combined value into the program to save a few processor cycles during execution. When this value finds is way into the documentation, a slave address of for example 80 will be written 160 there. It is sometimes quite hard to figure out what the real address is. If in doubt, you need to write a test program to check if a slave address is valid.