I2C is a serial communication protocol, used for communication between IC's. It uses two wires: one for data (SDA) and one for a clock signal (SCL). For more details on this protocol, check this Wikipedia article.
The Netduino has one I2C port which is available on pin A4 (SDA) and A5 (SCL). The I2C protocol works as a bus, so you can connect more than one slave devices to the one I2C port of the Netduino. For the I2C bus to work properly, you need to add two pull-up resistors to pins A4 and A5, regardless of the number of devices connected to the bus.

For experimenting with I2C, I’m using two ‘plugs’ that I got from JeeLabs a while ago. A plug is a small board that is easily plugged into a JeeNode (an Arduino-compatible board). There are several plugs available, like an Analog Plug, a DC Motor Plug or a Gravity Plug. I have an I/O Expander Plug and a RTC Plug, which I will use in the code below. Most plugs only contain a single I2C chip and a few supporting components. So putting one (or more) I2C chips on a breadboard should be pretty easy.
For the examples, I created some classes that represent the I2C plugs: a I2CPlug base-class, an ExpanderPlug class and a RTCPlug class. All three classes are shown below. Add them to your project to be able to use the examples that follow.
public class I2CPlug
{
private const int DefaultClockRate = 400;
private const int TransactionTimeout = 1000;
private I2CDevice.Configuration i2cConfig;
private I2CDevice i2cDevice;
public byte Address { get; private set; }
public I2CPlug(byte address, int clockRateKhz)
{
this.Address = address;
this.i2cConfig = new I2CDevice.Configuration(this.Address, clockRateKhz);
this.i2cDevice = new I2CDevice(this.i2cConfig);
}
public I2CPlug(byte address)
: this(address, DefaultClockRate)
{
}
private void Write(byte[] writeBuffer)
{
// create a write transaction containing the bytes to be written to the device
I2CDevice.I2CTransaction[] writeTransaction = new I2CDevice.I2CTransaction[]
{
I2CDevice.CreateWriteTransaction(writeBuffer)
};
// write the data to the device
int written = this.i2cDevice.Execute(writeTransaction, TransactionTimeout);
while (written < writeBuffer.Length)
{
byte[] newBuffer = new byte[writeBuffer.Length - written];
Array.Copy(writeBuffer, written, newBuffer, 0, newBuffer.Length);
writeTransaction = new I2CDevice.I2CTransaction[]
{
I2CDevice.CreateWriteTransaction(newBuffer)
};
written += this.i2cDevice.Execute(writeTransaction, TransactionTimeout);
}
// make sure the data was sent
if (written != writeBuffer.Length)
{
throw new Exception("Could not write to device.");
}
}
private void Read(byte[] readBuffer)
{
// create a read transaction
I2CDevice.I2CTransaction[] readTransaction = new I2CDevice.I2CTransaction[]
{
I2CDevice.CreateReadTransaction(readBuffer)
};
// read data from the device
int read = this.i2cDevice.Execute(readTransaction, TransactionTimeout);
// make sure the data was read
if (read != readBuffer.Length)
{
throw new Exception("Could not read from device.");
}
}
protected void WriteToRegister(byte register, byte value)
{
this.Write(new byte[] { register, value });
}
protected void WriteToRegister(byte register, byte[] values)
{
// create a single buffer, so register and values can be send in a single transaction
byte[] writeBuffer = new byte[values.Length + 1];
writeBuffer[0] = register;
Array.Copy(values, 0, writeBuffer, 1, values.Length);
this.Write(writeBuffer);
}
protected void ReadFromRegister(byte register, byte[] readBuffer)
{
this.Write(new byte[] { register });
this.Read(readBuffer);
}
}
public class ExpanderPlug : I2CPlug
{
private const int ExpanderPlugAddress = 0x20;
public enum Registers
{
IODIR,
IPOL,
GPINTEN,
DEFVAL,
INTCON,
IOCON,
GPPU,
INTF,
INTCAP,
GPIO,
OLAT
};
public ExpanderPlug()
: base(ExpanderPlugAddress)
{
}
public ExpanderPlug(byte directions)
: base(ExpanderPlugAddress)
{
SetDirections(directions);
}
public void SetDirections(byte directions)
{
this.WriteToRegister((byte)Registers.IODIR, directions);
}
public void Write(byte values)
{
this.WriteToRegister((byte)Registers.GPIO, values);
}
public byte Read()
{
byte[] values = new byte[1] { 0};
this.ReadFromRegister((byte)Registers.GPIO, values);
return values[0];
}
}
public class RtcPlug : I2CPlug
{
private const int RtcClockPlugAddress = 0x68;
public RtcPlug()
: base(RtcClockPlugAddress)
{
}
public void Write(DateTime value)
{
byte[] dateBuffer = new byte[]
{
ConvertBinaryToBCD(value.Second),
ConvertBinaryToBCD(value.Minute),
ConvertBinaryToBCD(value.Hour),
ConvertBinaryToBCD(0),
ConvertBinaryToBCD(value.Day),
ConvertBinaryToBCD(value.Month),
ConvertBinaryToBCD(value.Year - 2000)
};
this.WriteToRegister(0, dateBuffer);
}
public DateTime Read()
{
byte[] dateBuffer = new byte[7];
this.ReadFromRegister(0, dateBuffer);
return new DateTime(ConvertBCDToBinary(dateBuffer[6]) + 2000,
ConvertBCDToBinary(dateBuffer[5]),
ConvertBCDToBinary(dateBuffer[4]),
ConvertBCDToBinary(dateBuffer[2]),
ConvertBCDToBinary(dateBuffer[1]),
ConvertBCDToBinary(dateBuffer[0]));
}
private static byte ConvertBinaryToBCD(int value)
{
return (byte)(value + 6 * (value / 10));
}
private static int ConvertBCDToBinary(byte value)
{
return value - 6 * (value >> 4);
}
}
As a first example, we’ll toggle all the pins of the expander plug like the blink example.
public class Program
{
public static void Main()
{
// initialize the expander plug, setting all pins as output
ExpanderPlug expanderPlug = new ExpanderPlug(0x00);
// do forever...
while (true)
{
expanderPlug.Write(0xff); // turn all the pins on
Thread.Sleep(250); // make the pins stay on for 250 ms
expanderPlug.Write(0x00); // turn all the pins off
Thread.Sleep(250); // make the pins stay off for 250 ms
}
}
}
You can use a LED (with a resistor in series) to test the level of each port as shown below.

Next, we’ll configure all pins as input. You can connect any pin to 3.3V using a resistor and see how the value changes. For the most stable results, connect all the input pins to ground if not connected to 3.3V.
public class Program
{
public static void Main()
{
// initialize the expander plug, setting all pins as input
ExpanderPlug expanderPlug = new ExpanderPlug(0xff);
// do forever...
while (true)
{
byte value = expanderPlug.Read(); // get the values of all the pins
Debug.Print("value: " + value.ToString());
Thread.Sleep(250);
}
}
}
For the final example I connected the RTC plug. A date is first written, then continouosly retrieved to show that the clock is running.
public class Program
{
public static void Main()
{
// initialize the RTC plug
RtcPlug rtcPlug = new RtcPlug();
// write the current date/time to the clock
DateTime now = new DateTime(2011, 7, 31, 15, 58, 0);
rtcPlug.Write(now);
Debug.Print("now: " + now.ToString());
// do forever...
while (true)
{
// read the current date/time from the clock
DateTime value = rtcPlug.Read();
Debug.Print("value: " + value.ToString());
Thread.Sleep(250);
}
}
}
The Micro .NET Framework does have an internal clock and DateTime.Now does work, but it is reset to '01-01-2009 00:00:00' every time you restart the device. Since the Netduino doesn't have an on-board RTC, it doesn't know the actual date/time. You could use an RTC as shown above and change the current date/time by calling Utility.SetLocalTime once during the initialization of your program. If you really want to get the actual date/time (from an RTC) when calling DateTime.Now, you'll need to update the firmware.
Next: working with steppermotors.