Jeroen Swart

.NET Architect

NetDuino - Getting Started with I2C

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.

Comments (3) -

  • john

    1/14/2012 8:44:34 PM | Reply

    thanks for this

  • Glenn Graham

    1/30/2013 9:02:21 PM | Reply

    I am a new user of C# and I need to have a Netduino Plus communicate with an MPU-6050 3-axis Accelerometer/Gyro via I2C.  I used your very nicely written code as a basis for my program and was successful.  Thank you for this great information!

  • fahdil ahady

    9/6/2013 11:33:41 AM | Reply

    It's Incredible.

    I have netduinoplus and GSMshield icomsat v1.1. the Icomsat Has I2C port as output(its feature), and I want to use it as RTC. I mean, if we have set value on the board, should it be stored into the RTC bus, and every time we reset the netduino, we just need to read the last value which stored on RTC?

    i just dont get how's the concept how to store and read... So much appreciate for the explanation...

    thanks

Pingbacks and trackbacks (2)+

Loading