I2C stands for Inter Integrated Circuit. It's a two wire serial bus designed by Philips around 1980 to allow microcontrollers and other integrated ciruits (like ADC's, DAC's, EEPROMS, GPIO) to communicate with each other. There are a number of implementations that are very similar to I2C, the most known variant is SMBus, this bus is used in PCs for communication with fan controls and the EEPROM's found on RAM devices. Another common name is TWI or Two Wire Interface. This term is used for devices that communicate like I2C but don't adhere the standard completely. The original standard had a maximum data rate of 100kbps. This wasn't fast enough for many applications so this limit is lifted to 400kbps, this is called Fast I2C. Since 1998 there is also a High Speed variant with a 3.4Mbps data rate but this variant is outside the scope of this page.
The bus has two wires with pull-up resistors. It allows one or more master devices to control up to 128 slave devices. Although the I2C bus allows more than one master to control the bus, many I2C master implementations doesn't support this correctly (Because this is required for the I2C standard these masters are called TWI masters). Master and slave devices only allowed to sink so no shortages can appear. The first wire is SCL and contains the clock signal. The clock signal is generated by the master. The slave devices are allowed to sink the clock line if they need more time to process the request. The second line is SDA and is the data line. Data is transmitted to and from the master on this wire.
All communication on the I2C is byte oriented and the MSB (Most Significant Bit) is send first. To transfer data the master puts a special Start signal on the bus. This signals that the bus is now controlled by a master and that the slave devices should pay attention. Next the master puts the slave address onto the bus. The slave address consists of a 7-bit address and one bit indicating if the master wants to (1) read from or (0) write to the slave device. Some datasheets give an 8-bit address, in that case you have to right shift it by 1 bit because .NET Micro Framework adds this last bit automatically.. When a device has the send slave address and is ready, it acknowledges that it has received the address. If no acknowledgement is received, it can be assumed that a slave with the given address is not present or not ready. Next, the data bytes are sent or received. The receiving device acknowledges each correctly received byte. After the bytes are send or received the master can end the transaction by sending a Stop signal to free the bus, or send a Repeated-Start signal to start a new transaction without releasing the bus. A Repeated-Start signal is useful when a master needs to send and receive data to complete a task. E.g. in case of an EEPROM the master starts a write transaction and sends the address it wants to read. Next, the master sends a Repeated-Start signal to read, and it reads the data on the requested memory address.
More information on the I2C bus can be found on:
Contents |
Depending on the device used .NET Micro Framework supports an I2C master device so you can control 1 or more slave devices. The base class for the master is I2CDevice. This name is a little ambiguous as it allows you to control more than one I2C slave device a better name would be I2CBus. The constructor for I2CDevice looks like this:
new I2CDevice(Configuration config);
The only parameter is a I2C Configuration which has the following constructor:
new Configuration(ushort address, int clockRateKhz);
The first parameter is the 7-bit address of the slave device you want to access. The second parameter is the clock rate in Khz.
To read data from an I2C slave you have to create a I2CReadTransaction. You can do this with:
YourI2CDevice.CreateReadTransaction(byte[] buffer)
Where YourI2CDevice is the device you created. The byte[] array you pass will be filled with the bytes received from the I2C slave and should be as big as the number of bytes you want to read.
To write data to an I2C slave you have to create a I2CWriteTransaction. You can do this with:
YourI2CDevice.CreateWriteTransaction(byte[] buffer)
Where YourI2CDevice is the device you created. The byte[] array you pass will should contain the bytes you want to send to the device.
Both I2CReadTransaction and I2CWriteTransaction derive from the I2CTransaction type. The Execute function explained below expects an array of I2CTransaction[] so you can mix them.
Now we have defined the transactions we want to make we want to execute them. You can do this with the Execute command. The command looks like this:
int YourI2CDevice.Execute (I2CTransaction [] xActions, int timeout )
Where YourI2CDevice is the device you created. The first parameter is the I2CTransaction[] array we have created using CreateReadTransaction and CreateWriteTransaction. The second parameter is the time in milliseconds the transaction may take (This can vary because slave devices allowed to stretch the clock signal). The function returns the transferred bytes. If all transactions are successful this should be the combined size of all transaction buffers.
Below is an example showing how to change the IO pins on a PCF8574A. The example program read the current pin values. Toggles it and write the toggled data byte.
// We want to read/write one bytebyte[] data = new byte[1];
// Create new I2CDevice for an PCF8574A (8 Bit I/O Expander)I2CDevice PCF8574A = new I2CDevice(new I2CDevice.Configuration(0x70, 100));
// We want to read the current pin levels from the PCF8574AI2CDevice.I2CTransaction[] ReadGPIO = new I2CDevice.I2CTransaction[] {
PCF8574A.CreateReadTransaction(data)
};
// Execute the ReadGPIO transaction, check if byte was successfully readint bytesRead = PCF8574A.Execute(ReadGPIO, 100);
if (bytesRead != 1) { return; }
// Toggle high/lowdata[0] = (byte) ~data[0];
// Now write the toggled byteI2CDevice.I2CTransaction[] WriteGPIO = new I2CDevice.I2CTransaction[] {
PCF8574A.CreateWriteTransaction(data)
};
// Execute the WriteGPIO transaction, check if byte successfully sendint bytesSend = PCF8574A.Execute(WriteGPIO, 100);
if (bytesSend != 1) { return; }
// Dispose I2CDevice PCF8574A.Dispose();
Debug.Print("Successfully toggled PCF8574A IO pins");
This example showcases how to combine multiple I2C Transactions. This is needed when you want to read bytes from a 24LC16 I2C EEPROM. First you need to write the memory address and then read the data bytes. It reads a byte from EEPROM memory address 0x05.
// We want to read one bytebyte[] data = new byte[1];
// Create new I2CDevice for an 24LC16 (EEPROM)using (I2CDevice _24LC16 = new I2CDevice(new I2CDevice.Configuration(0x50, 100)))
{ // Create a new Transaction I2CDevice.I2CTransaction[] ReadByte = new I2CDevice.I2CTransaction[] {
// We want to read from memory address 0x05_24LC16.CreateWriteTransaction(new byte[] { 0x05}),
// We want to read one byte_24LC16.CreateReadTransaction(data)
};
// Execute the ReadByte transaction, check if it was succesfull.int bytesTransfered = _24LC16.Execute(ReadByte, 100);
if (bytesTransfered == 2)
Debug.Print("Byte from EEPROM: " + data[0].ToString());
elseDebug.Print("Error reading byte from EEPROM");
}// The I2CDevice is now automatically disposed because we used the using keyword.
.NET Micro Framework allows you to create only one I2CDevice. Many applications require the I2C master to access more than one slave device. There are a number of options to solve this problem:
An example of the last solution is displayed below:
// Create an I2C Device with an empty configurationI2CDevice I2CBus = new I2CDevice(new I2CDevice.Configuration(0, 0));
// Update configuration to a PCF8574AI2CBus.Config = new I2CDevice.Configuration(0x70, 100);
// Do your PCF8574A transactions// ...// ...// Now update the configuration for a 24LC16 EEPROMI2CBus.Config = new I2CDevice.Configuration(0x50, 100);
// Do your 24LC16 transactions// ...// ...