At the same as SPI, analyzed in a previous article, the I2C (Inter-Integrated Circuit) is a synchronous communication bus used to connect and exchange data between a microprocessor and external devices; it was developed by Philips, now NXP, today it is "de facto" standard.

Bus description

The I2C bus is also known as two-wire as it is characterized in all the effects of only two "wires":

  • SDA (Serial Data Line) : line for the data transfer;
  • SCL (Serial CLock) : clock for data exchange;

The lines abovelines  are always characterized by a pull-up resistor that has the task of maintaining the signal "high" (logic 1) in conditions of idle while the interconnected components (master and slave) have the task of lowering the level to transfer a logic 0 and release it to bring it back to idle and transfer a logic 1, this behavior is typical of the open-drain lines.

425px-I2C_svg_thumb2

Similarly to SPI, you can have multiple slaves connected to the bus and a single master to communicate with, the main difference is that there is no signal SS (Slave Select) but the master selects the slave with which communicate through addressing. In fact, the master transmits the slave address on the SDA line before starting the transfer of real data, and this is typically 7-bit address (up to 128 slaves) but is planning to extend up to 10 bits (up to 1024 slave).

I2C has a fundamental characteristic that allows the presence of more than one master on the bus (multi master mode).

The communication protocol

The communication protocol is characterized by the following steps:

  1. START Condition : the master lowers the SDA still holding the SCL high to indicate the START condition to the slave and then the start of a transmission;
  2. Addressing : the master sends a byte (MSB first) on the bus, in which the first 7 bits represent the address of the slave to communicate with and the last bit indicates the type of operation you want to perform (0 = write, 1 = read) ;
  3. Slave acknowledge : if there is one slave on the bus with this address, it responds with an ACK bit (logic 0);
  4. Communication : at this point, the master can send and / or receive data from the slave in a synchronous manner thanks to the movement of the SCL. For each byte exchanged is always expected an ACK from the counterparty;
  5. STOP Condition : the master raise the SCL still holding down SDA and the STOP condition is provided to the slave and then the end of the transmission;

798px-I2C_data_transfer_svg_thumb2

The clock is always driven by the master but in some cases the slave can maintain low value to introduce delay and prevent the master sends other data (maybe it needs more time to process the data already received): This feature is called "clock stretching".

.Net Micro Framework : the supported classes for the bus

The. Net Micro Framework considerably simplifies the use of the I2C bus by using the I2CDevice class (namespace Microsoft.SPOT.Hardware, Microsoft.SPOT.Hardware.dll assemblies), whose constructor expects a parameter of type I2CDevice.Configuration to be suitably configured, this configuration allows you to set:

  • Address : slave address;
  • ClockRateKhz : clock frequency;

All the above configuration parameters are always closely tied to the device with which you want to communicate and to be found in the datasheet.

   1: I2CDevice.Configuration config = 
   2:                 new I2CDevice.Configuration(I2C_ADDRESS, I2C_CLOCK_RATE_KHZ);
   3:  
   4: I2CDevice i2c = new I2CDevice(config);

The I2CDevice class provides a single method Execute() to be able to do one or more "transactions" on the bus representing the operations of reading and writing with the slave. This method requires as input an array of objects I2CDevice.I2CTransaction and a timeout. The I2CDevice.I2CTransaction class is the base class for the I2CDevice.I2CReadTransaction class, in the case of a read transaction, and for I2CDevice.I2CWriteTransaction class, in the case of a write transaction.

Creating an instance for each of the two classes above can be carried out through the following two static methods of the class I2CDevice:

  • CreateReadTransaction() : creates an instance of the I2CDevice.I2CReadTransaction class associating it with the byte array received as input buffer for receiving data from the slave (initially empty);
  • CreateWriteTransaction() : creates an instance of the I2CDevice.I2CWriteTransaction class associating it with the byte array received as input buffer containing the data to be transmitted to the slave;

Ultimately, the procedure for using I2C plans to create an array of "transactions" of reading and / or writing (obviously mixed) and execute these transactions in a single stroke, finding himself the transmitted data to the slave and buffers reception with the required data.

Imagine having a component I2C characterized by a series of internal registers and wanting to read the content of one of them. This type of communication is characterized by two I2C "transactions" , the first writing in order to send the slave address of the register to be read (be careful! Do not speak of the address slave which is sent before) and the second reading to be able to read the content.

   1: byte write = { REG_ADDRESS };
   2: byte read = new byte[1];
   3:  
   4: // create I2C write and read transaction
   5: I2CDevice.I2CTransaction i2cTx = new I2CDevice.I2CTransaction[2];
   6: i2cTx[0] = I2CDevice.CreateWriteTransaction(write);
   7: i2cTx[1] = I2CDevice.CreateReadTransaction(read);
   8:  
   9: // execution
  10: i2c.Execute(i2cTx, I2C_TIMEOUT);

Deep into the HAL

With I2C (as already seen for the SPI), each OEM must implement a layer (HAL and PAL) to serve as a "bridge" between the (CLR managed code to high-level) and the particular hardware below.

We take as reference the Netduino board (generation 1), which has as a microcontroller Atmel AT91. By downloading the source code of the firmware (they are open source) from the official site, we can find the implementation code in managed C # class I2CDevice (Framework \ Core \ Native_Hardware \ I2C.cs) in which the GetI2CPins() method is invoked on the HardwareProvider current instance.

   1: public I2CDevice(Configuration config)
   2: {
   3:     this.Config = config;
   4:  
   5:     HardwareProvider hwProvider = HardwareProvider.HwProvider;
   6:  
   7:     if (hwProvider != null)
   8:     {
   9:         Cpu.Pin scl;
  10:         Cpu.Pin sda;
  11:  
  12:         hwProvider.GetI2CPins(out scl, out sda);
  13:  
  14:         if (scl != Cpu.Pin.GPIO_NONE)
  15:         {
  16:             Port.ReservePin(scl, true);
  17:         }
  18:  
  19:         if (sda != Cpu.Pin.GPIO_NONE)
  20:         {
  21:             Port.ReservePin(sda, true);
  22:         }
  23:     }
  24:  
  25:     Initialize();
  26:  
  27:     m_disposed = false;
  28: }

After a series of invocations to cascade through the CLR to the implementation of the HAL, the GetPins() method is invoked  on the AT91_I2C_Driver class (DeviceCode \ Targets \ Native \ AT91 \ DeviceCode \ AT91_I2C \ AT91__I2C.cpp) that returns the identifiers of pins of the processor associated with the I2C port.

   1: void  AT91_I2C_Driver::GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
   2: {
   3:     NATIVE_PROFILE_HAL_PROCESSOR_I2C();
   4:  
   5:     scl = AT91_TWI_SCL;
   6:     sda = AT91_TWI_SDA;
   7: }

In the case of Netduino board (generation 2) that has a STM32 processor, the reading function of the I2C pins is I2C_Internal_GetPins() (DeviceCode \ Targets \ Native \ STM32 \ DeviceCode \ STM32_I2C \ STM32_i2c_functions.cpp).

   1: void I2C_Internal_GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
   2: {
   3:     scl = I2Cx_SCL_Pin;
   4:     sda = I2Cx_SDA_Pin;
   5: }

Conclusion

The I2C bus unlike the SPI is obviously not full duplex being characterized by a single data line and is even slower in terms of speed. The main advantage is that you do not have the complexity of a selection signal of the slave and be able to work in multi master mode.

The. NET Micro Framework enables you to use this bus very easily with a single class and the concept of read / write I2C "transactions" in order to perform the communication in a single "shot".

Very soon you will see a real example of application of this bus (like the SPI bus) with a managed driver I developed for an NFC chip from NXP !