Pages

Wednesday, May 28, 2014

I2C IO Expanders for Raspberry Pi

I've had my raspberry pi for over 6 months now, but so far I haven't played around with it other than turning it on/off occasionally. I had to admit my embedded linux/firmware skills is a bit outdated, the last time I played around with linux device drivers and VHDL was over almost 10 years ago. For the last few years, I've been developing applications in C++/Java and haven't really gotten into low level stuff. The raspberry pi provided a platform for me to revisit those lessons lost, retool and learn new things. It's time to go back to developing firmware and device drivers again.
The first thing I noticed about the raspberry pi was the lack of available GPIO's it exposed, I had a B board Revision 2.0 hardware and it only came with a 26 pin header. Some pins on the header are dedicated for other stuff - like SPI and UART. I booted on a stock kernel that came with package, and it was up and running. The first problem I ran into was debugging, the UART pins exposed on the raspberry pi headers are 3.3V and incompatible with the voltages with TTL levels on the serial port. My nearest parts supplier carried a FTDI raspberry pi cable that converts 3.3V levels to the PC's TTL 5V levels. This allows me to debug the kernel while its booting up - quite important if you're debugging device drivers. A jtag cable is a lot better, but I don't need it for now. The second problem IOs that you can connect to the header, luckly I2C is present on the GPIO's, and this is what we'll use to extend IOs for our devices.
I2C is a two wire protocol developed by Philips in the 1980's to let IC/components talk to each other. There are a lot of resources in the internet if you would like to learn more about this protocol, I'm not going to discuss that here.
Looking at the available components out there, I came across this MCP23017 chip manufactured my Microchip. This chip is an IO expander which uses the I2C bus, its I2C address is selectable from a set of A0-A2 pins on the chip (base address starts at 0x20). I've choosen I2C over SPI since we only have a few GPIO than can be used for the chip select pin on top of the SPI signals. The chip also provides 2 configurable input/output 8-bit ports, and two interrupt out pins which I can use to trigger interrupts on the raspberry pi.
With the list of parts needed for the project, I ordered the RS232 usb cable for the raspberry pi, a PI cobbler kit (so that I can connect the GPIO lines to my breadboard) and the MCP23017 chip from my local supplier (Element14). The GPIO cobbler kit requires some soldering, this will allow us to connect the GPIO headers directly to our breadboard. The FTDI RS232 cable is a bit expensive (SGD $20+), while the MCP23017 chips costs around SGD $2.
From the available parts I had with me, I quickly wired a test circuit for the MCP23017. The two 8 bit ports are connected to the led and dip switches. It's address is hard wired to 0x21 by connecting the A0-A2 pins to "001" (Note that the base address for the MCP23017 is 0x20). The resistors I choose are random, as they came from what's available with me at the moment, they'll serve as current limiters to the output of the MCP23017 chip as we don't want our LEDs to connect to ground directly. Here's the completed circuit I have wired up:

Here's the actual circuit wired up:


After loading the Pi with a custom kernel. I made sure that the i2c drivers are loaded at boot time by the raspberry pi. I found good tutorial on how to do that on Adafruit's website. Once i2c core drivers is enabled at the kernel, I can start turning on/off our LED's and getting the values of our DIP switches.

First up, I need to know if the i2c device is detected, I did this by running i2cdetect. Note that on the raspberry pi B rev. 2 board, i2c present on the headers are coming from i2c master at bus 1 (there are 3 on the pi, but only bus 2 is connected to the headers). This is the reason for the "i2cdetect -y 1" as I that's the only bus exported on the header.




Great! as noted, the address at 0x21 was detected. But gefore I can start turning on/off the leds or read from the dip switch, I first need to configure the direction of PortA and PortB pins on the MCP23017. I did this by accessing register 0x00 for PortA, setting bits '0' for output or '1' for input. Simillarly, PortB's direction register is at 0x01. To configure the led output and dip switch as input, I need to issue two I2C write commands to address 0x21, register 0x00, and value 0x00 (for setting PORTA as an output port) and an i2c write at address 0x21 with register 0x01 and 0xFF as value (for setting PORTB as input port). An i2c write command is done via the i2cset command below.


Now that I have set the direction register, I can now set the leds on or off. The register address of PORTA from the MCP23017 specs is located at 0x14, so to set all leds to on, I issued an i2c write to address 0x21, register 0x4 with 0xFF to turn on all leds.

>sudo i2cset -y 1 0x21 0x14 0xFF

Simillarly, to read values from the dip swiches, issue an i2cget command with address 0x21 and register 0x13 (PORTB reg).




So far so good. But how can we access these settings programmatically say via a C or C++ program? There are several ways of doing this, on the raspberry pi there's a driver for i2c that resides in /dev/i2c-0 or /dev/i2c-1, we can either use the instance of /dev/i2c-1 (bus 1) open it just like any other file in Linux, specify the address through a ioctl calls and write to it. Will try to discuss the user space code for accessing the leds and dip switches on my next post.


No comments: