Expanding Raspberry PI I/O using I²C on Azure IoT Edge

The GPIO of a Raspberry gives you the opportunity to interact with the physical world using digital pins and various IO busses like SPI and I²C.

In the past, in this blog, I already demonstrated how to access the GPIO of a Raspberry Pi.

In the last few months, I spent my spare time building a beerlift:

The beerlift is capable to serve multiple bottles of beer so each bottle has its bottle holder:

The bottle holder contains a switch to detect a bottle being placed or being removed. It also contains a LED so it can visualize if a bottle is placed or removed or eg. advertised.

I wanted to support up to sixteen bottles (so 32 switches and LEDs) which exceed the GPIO pin limitation of a Raspberry Pi.

Therefore, I bought myself a couple of MCP23017 I/O Expanders. This device offers sixteen digital inputs or outputs over a serial interface. I went for the I2C version:

Let’s see how we can use them in an Azure IoT Edge solution.

As you can see in the previous picture, there is a little mark (notch) at the top of the expander. This is the side with two banks of eight inputs or outputs. Also, pin 1 has a little gap as an extra marker.

This is how you put it on a breadboard and connect it to a Raspberry Pi:

All you need to do is connecting four wires to the RPi:

  • 3.3V to pin 9 and 18
  • Ground to pin 10, 15, 16, and 17
  • SCL to pin 12
  • SDA to pin 13

Connecting it to a Raspberry Pi looks like this:

Note: I do not use any resistors between the 3.3V connection and any pins. In case you experience the expander becoming hot, add some transistors.

Although the expander is fully connected, It’s not operational yet. You probably have to enable the I2C interface on the Raspberry Pi. For this to happen, open the Raspberry Pi Configuration and enable I2C at the Interfaces tab:

Otherwise, you will experience this message “Error: Could not open ‘/dev/i2c-1’: no such file or directory”.

Now test the interface to the expander by executing:

i2cdetect -y 1

This will show:

What does this mean?

You are shown that an I2C device is detected at address 0x20.

The I2C bus supports multiple devices, each having another address.

Our expander is configured for address 0x20 using pin 15, 16, and 17 of the expander. All three pins are connected to Ground. You can connect up to eight expanders just by giving them different addresses using these three pins.

Just connect another expander to the same I2C bus but connect one of the three address pins to the 3.3V connection:

Once connected, this result in the detection of two I2C addresses:

The original expander to the left is still available at address 0x20. The one at the right with only two of the three address pins connected to Ground and one to 3.3V is available at address 0x24:

Now, two of the maximum of eight expanders are connected.

I went for 3.3V on pin 17. If I had chosen 16, the address would be 0x22. If I used pin 15, it would have been 0x21…

Note: I noticed the addresses changing when the pin connection was unstable (wires disconnecting once in a while). This will result in exceptions when accessing expanders. Please check the wiring always.

Coding against the expanders

Now the IO expanders are put in place and these are reachable using the I2C bus. We can finally start coding against them.

I want to use C# for this. We are in luck, Microsoft already provides coding classes and examples for dozens of GPIO devices, including these IO expanders:

Note: This is just a small portion of all devices supported!

As you can see, the library is actively supported by Microsoft, it’s updated just a few days ago. Great!

I use the MCP23017 (E/SP) expander which is just one of the MCP23XX devices supported in the code. So all the heavy lifting is already done.

I just started a new .net core Console app to test the pins. First, I just stripped down the MSFT GitHub library and added only the files I needed for my app to communicate with the expanders:

I also had to add this NuGet library:

But there is an easier way, just add these two NuGet packages:

The GPIO devices and sensor classes are simply available as NuGet package. It’s a pre-release but still, this is a convenient way to deal with GPIO devices.

So the code results in:

using Iot.Device.Mcp23xxx;
using System;
using System.Device.I2c;
using System.Threading.Tasks;

namespace ExpanderConsoleApp
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            var connectionSettingsx20 = new I2cConnectionSettings(1, 0x20);
            var i2cDevicex20 = I2cDevice.Create(connectionSettingsx20);
            var mcp23017x20 = new Mcp23017(i2cDevicex20);

            mcp23017x20.WriteByte(Register.IODIR, 0b0000_0000, Port.PortA);
            mcp23017x20.WriteByte(Register.IODIR, 0b0000_0000, Port.PortB);

            while (true)
            {
                byte dataPortAswitch20 = mcp23017x20.ReadByte(Register.GPIO, Port.PortA);
                byte dataPortBswitch20 = mcp23017x20.ReadByte(Register.GPIO, Port.PortB);

                Console.WriteLine($"Port A = {dataPortAswitch20:D3} - Port B = {dataPortBswitch20:D3}");

                Task.Delay(5000).Wait();
            }
        }
    }
}

We register one expander device having two ports, A and B. Both are initialized as being readable.

We read every second every pin and check if 3.3V is set on those pins (of each separate port). Check out the green wires which could be replaced by switches:

This results in these values:

As you can see, Port A has a binary value of 2^7 which results in 128. This represents pin 28. Port B has the value 1 which is 2^0. This represents pin 1

If all pins of a port are filled, the port will have a value of 255. Check the order of the pins:

Note: we have created a loop so we can read the values once every few seconds. The documentation hints towards an event base solution in the future.

Now, we are able to read the values of an expander, let’s see how we can write values and lit a LED.

This is the code to use for both reading values on one expander and writing them to the other expander:

using Iot.Device.Mcp23xxx;
using System;
using System.Device.I2c;
using System.Threading.Tasks;

namespace ExpanderConsoleApp
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            var connectionSettingsx20 = new I2cConnectionSettings(1, 0x20);
            var i2cDevicex20 = I2cDevice.Create(connectionSettingsx20);
            var mcp23017x20 = new Mcp23017(i2cDevicex20);
            mcp23017x20.WriteByte(Register.IODIR, 0b0000_0000, Port.PortA);
            mcp23017x20.WriteByte(Register.IODIR, 0b0000_0000, Port.PortB);

            var connectionSettingsx24 = new I2cConnectionSettings(1, 0x24);
            var i2cDevicex24 = I2cDevice.Create(connectionSettingsx24);
            var mcp23017x24 = new Mcp23017(i2cDevicex24);
            mcp23017x24.WriteByte(Register.IODIR, 0b0000_0000, Port.PortA);
            mcp23017x24.WriteByte(Register.IODIR, 0b0000_0000, Port.PortB);


            while (true)
            {
                byte dataPortAswitch20 = mcp23017x20.ReadByte(Register.GPIO, Port.PortA);
                byte dataPortBswitch20 = mcp23017x20.ReadByte(Register.GPIO, Port.PortB);

                Console.WriteLine($"Port A = {dataPortAswitch20:D3} - Port B = {dataPortBswitch20:D3}");

                // echo the x20 switches on x24 LEDs 
                mcp23017x24.WriteByte(Register.GPIO, dataPortAswitch20, Port.PortA);
                mcp23017x24.WriteByte(Register.GPIO, dataPortBswitch20, Port.PortB);
                   
                Task.Delay(5000).Wait();
            }
        }
    }
}

Note: Both the read ports and write ports are initialized using 0b0000_0000. The difference is whether we expect the pins having 0 or 3.3 Volts.

The x20 expander now echoes the switch changes on the x24 expander LEDs.

I connected one LED as an example. We see a LED on pin 2 (of port B) being lit when pin 2 (on port B of the other expander) is switched on:

The LED is connected between the expander pin and Ground. A complementary resistor is added to protect the LED from too many volts.

Moving this code to a Docker module

This is quite a short paragraph 🙂

Creating a Docker container and adding elevated rights to it is already explained in my previous blog.

Just follow that explanation, it’s still valid.

Nothing is holding you back not expanding the IO of your Raspberry Pi.

Conclusion

The Iot.Device.Bindings NuGet package is a fun and easy way to integrate your GPIO sensors into your IoT projects running on eg. a Raspberry Pi.

You can have even more fun with it once you integrate it with multiple LEDs and bit-shifting to show creating LED patterns.

Geef een reactie

Vul je gegevens in of klik op een icoon om in te loggen.

WordPress.com logo

Je reageert onder je WordPress.com account. Log uit /  Bijwerken )

Google photo

Je reageert onder je Google account. Log uit /  Bijwerken )

Twitter-afbeelding

Je reageert onder je Twitter account. Log uit /  Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log uit /  Bijwerken )

Verbinden met %s