Hello! I'm Arsenijs, I like building stuff with Raspberry Pi's and lately I've been working with character displays, as well as their I2C backpacks, so I've collected some hacks I can show you. Today, I'll tell you how to save a lot of pins on Raspberry Pi if you're using those popular HD44780-based character displays, like the one on the second picture.
For this, I'm using a 1$-a-piece I2C LCD backpack which are available in large quantities on eBay (those which I have on the header image) and are typically used with 5V Arduinos. This backpack is typically run from 5 volts due to the fact that it supplies its VCC to the HD44780 display, and these LCDs most often are 5V-only - and we all remember Raspberry Pi doesn't like 5V on its GPIOs! However, with a single cut trace and a pin added to a header you can modify it so that the backpack itself works with 3.3V (perfect for Pi) and supplies the 5V necessary for the screen to work.
Moreover, I'll also tell you how to hack this backpack so that it can interface with up to 8 buttons using I2C! Not only that, but I'll show you how use interrupt capability of PCF8574 to avoid unnecessary loading of I2C bus and therefore reduce CPU load by monitoring just a single GPIO pin instead of asking about the buttons' state again and again.
I'll also provide you with Python code for both button reading and output to screen. It's easy to read and modify to suit your needs =)
In the end, you can add a screen and buttons (or multiples of them both!) to your project using just 2 I2C pins (can be shared among multiple devices) and an optional 1 GPIO pin. Moreover, if you analyze my code and the IC datasheet, you can even use those boards to increase your GPIO count on the Pi. Those backpacks are using PCF8547 ICs, and these ICs have a lot more features than just driving an LCD. If that's what you're interested to hear about, continue reading and you'll get a glimpse of a recent big project of mine that's sure to catch your attention ;-)
Step 1: Connecting Your Backpack to the LCD
This is a closeup the backpack's board. It's designed to be pin-to-pin compatible to those displays. Those backpacks are typically supplied with male pins... Just like the displays =) But even with that in mind, connecting the backpack to your LCD is simple and can be done in many different ways:
- You can take an LCD without headers soldered on and solder the backpack on it
- You can desolder the male header of the backpack and add a female header to it, in which the LCD could plug into.
- You could leave both the backpack and LCD with male pins and make a cable to connect them pin-to-pin.
- You could use a breadboard, or even female-to-female wires.
Anyway, keep in mind that the backpack's pin 1 (the rightmost pin of the header) goes to the pin 1 of the LCD (leftmost when looking at the display from the "display things" side and rightmost when looking at the display from the PCB side). If you connect them in some another way, it most likely will not break anything, just that the display obviously won't work in that arrangement.
All is well, but you cannot just connect this backpack out-of-the-box to your Raspberry Pi and expect it to work. The possible outcomes of this are:
- It won't work
- It will fry your Raspberry Pi
- The display won't show anything
Step 2: Explaining and Fixing the Incompatibility
What's the real problem with those backpacks and Raspberry Pi? Well, as I said, the LCDs need 5V power, especially for the contrast. This backpack is mainly used for 5V Arduino+LCD setups, so it uses 5V for the onboard IC and its I2C pullups, too. Thus, the I2C lines have 5V on them, which isn't suitable for connection to your Pi. Pi has pullups, too, so a viable alternative would be removing the backpack's onboard pullups - except that it then stops working reliably. Why? Minimum voltage the backpacks accepts as high logic level is 0.7VCC = 5V*07 in this case = 3.5V, which is less than 3.3 of Raspberry Pi's and the situation is what programmers call "undefined behaviour" - it might work or it might not. In my case, it didn't work. The solution? The backpack's controller IC is perfectly OK with 3.3V as VCC, just that the displays are not. Thus, we need to isolate display's VCC line, feed the controller with 3.3V and have a separate 5V line - for the display, and that's a very simple hardware mod.
Look at the first picture - with the board I'm using it's a single cut trace. Once you've cut that trace, check that pin 2 of the header (LCD's VCC) is not connected to *anything* on the board, including VCC on 4-pin header. Use your multimeter's continuity checking tool for that. If it's not OK, or your expander looks similar but is totally different, leave me a pic of both sides of your expander in the comments, I'll show you which traces to cut and where to connect 5V =) Once it's clear 5V is not connected to anything, you can solder a short piece of wire to the pin 2 solder a single 2.54 pin at the end of that wire and hotglue it in place so that it's more of a 5-pin header than a 4-pin header. That's not obligatory, of course, but I prefer this type of connection for its modular approach.
You can simply remove the second pin of the LCD (LCD VCC pin) so that it isn't connected to the backpack, then connect that LCD pin with a wire to 5V line of your RPi, running your backpack from 3.3V and LCD from 5V, but not modifying the backpack.
That's it! Now connect the backpack to your Raspberry Pi:
- LCD 5V VCC (pin we added) -- 5V
- GND -- GND
- VCC -- 3.3V
- SDA -- SDA
- SCL -- SCL
Now, let's check the connection and get the software!
Step 3: Software for the LCD
Make sure you have I2C kernel module loaded by using:
sudo modprobe dev_i2c
Now you need to know which I2C bus your Raspberry Pi has available on GPIOs:
It'll output "/dev/i2c-x", where x is your bus number. Now detect the adapter by using the i2cdetect command, inserting your bus number:
sudo i2cdetect -y bus_number
It should show your adapter's I2C address in the table (more about addresses in "Step 5. Connecting more than one backpack to a single I2C port" chapter). If you don't have i2cdetect on your RPi, apt-get the i2c-tools package.
Is it detected? If yes, great! If not, check your connections. Make sure the backpack's onboard LED is glowing - that means the IC is powered.
Here's my Python code for driving the backpack. It works well and has basic commands, and you can easily add your own by utilising the built-in Screen.command(lcd_command) function, which just sends a command to the LCD. Been using this code constantly for a year now and it outlived a couple of displays.
Download it from GitHub or get the attached version. If you launch it from command-line as "python pcf8574_lcd.py", it will self-test. If you import it from another Python script, it will just have a Screen object you can instantiate and use to output things. Also, it works with different addresses, display row/column combinations and such - just adjust initialisation variables and you can make it work with 20x2, 16x4, 16x1 and 20x4 displays. You might need to pass different addr and bus parameters if you have different adapter address and I2C bus number. In that case, use the bus number you've found earlier and I2C address you've found in the i2cdetect output.
One drawback - you need to run it as root. If you need to run it as unprivileged user, notify me in the comments and I'll explain what needs to be done so you can run this as a user (this deserves its own Instructable, honestly).
Step 4: Using the Backpack for 8 Buttons + Interrupts
This backpack is also fairly easy to hack for reading buttons, giving you up to 8 buttons per board (you can do 4x4 matrix, but that's out of scope of this instructable, I can outline the approach in the comments if you're interested).
I've found backpack's schematic of this module on the internet, and it matches my board. I've also mapped the IC pins and on-board connections on the board image for you to see.
As you might notice from the schematic, only 7 pins are brought out on the external header. First of all, let's connect those available. All the buttons need to be connected to pins of expander as they are on the second picture, just use pins 4-6 and 11-14. Don't forget the button pullups, I use 1K resistors, but you can use larger ones.
P3 pin, however, is not connected to the header. It's driving a transistor, as well as pulled up to VCC, so you need to desolder both the transistor and the resistor pulling the pin up.
If your board is not the same as mine (they might have different version, use your DMM's continuity testing feature, hold one pin on IC's P3 pin and touch various resistors and transistors on the board. When it rings, you've found a part that might interfere with "input" function).
Once you've removed the transistor and resistor, attach a wire to any of the spots to which P3 is connected to and connect that wire to the 8th button. Now you have all pins available!
Only problem is that to understand whether any of the buttons have been pressed or not, you need to repeatedly send commands to board, getting the state of pins and comparing them to the data you have. That's a CPU-consuming approach, we can do better. How?
Another useful function to get out of this IC is interrupt function. Basically, that's a GPIO pin you can connect to your Raspberry Pi that changes state once any of the buttons are pressed/released. Thus, you can only monitor one GPIO pin and not flood the I2C bus with requests. Thankfully, the INT pin of the IC is not connected to anything, so you can just solder a wire to it. Then you need to connect it to one of Raspberry Pi GPIOs - I chose GPIO4. One more thing not to forget is that this INT pin needs a pullup - so I added a 1K resistor.
Once again, connect the backpack to your Raspberry Pi:
- GND -- GND
- VCC -- 3.3V
- SDA -- SDA
- SCL -- SCL
Hardware part is finished, let's see the code!
Step 5: Button Reading Software
For code, grab this, it's attached as well.. It's a nice little script that supports both interrupt-based and polling modes, updating the state each 100 milliseconds. You can alter it a little and use it as "sudo python pcf8574_buttons.py" for testing, then after you're done testing import it from another script and use just ButtonPanel object like this:
buttons = ButtonPanel(addr = 0x27, int_pin = 4)
specifying the INT pin number if you have it connected. If not, just use:
buttons = ButtonPanel(addr = 0x27)
Once your run buttons.start(), it'll be printing the button number, but you can easily modify it to call different functions according to pressed button numbers. Just like the previous script, it has a bus keyword argument if you need to change the I2C bus. This also needs to be run as root. Once again, ping me in the comments and I'll explain what needs to be done so you can run this as a user.
Step 6: Using Multiple Adapters on One Bus
Here's one thing to remember. Those boards might have different controller IC revisions - PCF8574 or PCF8574A. I know this because I have many of those adapters and they have different ICs. What changed is, among maybe other parameters, the addressing scheme. How?
Those ICs have 3 address inputs, which gives us 2^3=8 possible addresses. You can see the A0,A1,A2 pads. They're pulled up to VCC by default, which makes them all 1s. By shorting, say, a A2 pad, you're making A2 bit in the address a 0 and therefore are changing the I2C address. (shorting these pads is as simple as making a solder bridge.) That might come in very handy if you want to add one screen and 8 buttons, or two screens, or... Arbitrary combination of screen and buttons - my scripts are able to use different addresses, as you can see, and they do not generally interfere between themselves, so using both a screen and buttons works great =) But about addresses...
Look at your expander, then see the table attached above. Then, if you see you have 2ICs or more of one revision (either with A suffix or without), you need to change addresses. You can leave one of the backpacks with pads unbridged (thus using the HHH address) and bridge different combinations of pads on all the others. Different revisions won't conflict though, so if you have two adapters, one with PCF8574 and another with PCF8574A, they won't conflict no matter the combination. Also, this thing means we can have up to 16 backpacks on one bus if we connect 8 boards which have PCF8574 and 8 which have PCF8574A.
Also, in case of multiple backpacks there's one more problem. All of those boards have onboard I2C pullups. That's good for a single MCU-backpack combo but as the adapter size starts to increase, the common value of all those pullups starts to decrease (Electronics 101 - putting resistors in parallel), and as it decreases, it becomes more and more hard for backpacks&MCU to drive the line low, which can affect the communication negatively. To aviod that, you might need to remove some of the pullups, you can even leave just a single pair of them, and, as the Raspberry Pi boards all have pullups, you can basically remove all the SDA&SCL pullups from the boards. It's easy to find them on the boards - these are the resistors whose one side is attached to VCC and other is attached either to SDA or to SCL.
If you have 2 different I2C buses, though, you can simply use one bus per one adapter, or even up to 16 adapters (including the revision trick) on one bus, making the numbers enormously big, if you need that many =) Just remember to specify different bus numbers, mmkay?
Step 7: Conclusion&my Project Announcement
As I've mentioned, I've been working with character LCDs and I2C backpacks a lot, and the reason is a big project of mine that might help many Raspberry Pi enthusiasts. What is it?
It's called pyLCI, which stands for (Python-based) Linux Control Interface. It provides a simple management interface to computers running Linux (including but not limited to Raspberry Pi) which you can use to change its settings, connect to wired/wireless networks, control various programs, run custom scripts and much more. My system allows you, for example, to shutdown your Pi, or connect to a wireless network so you can SSH ito your Pi and work on your project, or even control your Pi-based Internet radio you're making, changing channels and adjusting the volume - all of that without SSH or having to plug a monitor and keyboard! It's capable of doing much more - its core is written in Python, so it's easy to understand, modify and make it fit to your needs.
I've put a lot of effort in it so that it's universal and, as a result, it supports many input&output ways, such as GPIO-connected screens and buttons, USB keyboards and numpads, 2 types of I2C expanders and PiFaceCAD hat - and there's many more to come as I continue working. It's incredibly cheap to add since it uses 2$-a-piece HD44780-controlled screens and all types of things that have buttons, which can even be simple pushbuttons wired to GPIO, so the minimal setup cost is around 5$ (or less), and chances are you already have the parts. There's much more effort to be put into that, however, so I welcome your ideas on how you would like to use such a system. If you want to see the code, it's on GitHub.
Of course, the big question is - how can you install and start using it? Answer is simple - you already can install the system core, but I want to make the configuration part more usable so that you don't have to read the code to install&configure it. I'm making the 1.0 release which will contain the configuration&installation helper scripts, along with some basic functions you'll be able to use right away, on project's GitHub. Curent planned release date is 1st of April, and if you want to beta-test my project, as well as give me feedback on my idea, I'm @CRImier at Hackaday, and my e-mail is email@example.com . Also, consider following my project on Hackaday to receive updates about the release, and if you give my project a skull (it's free!), it will help me buy more hardware to make it supported, as well as allow me to spend more time on making it better =)
That's it for today. I hope you enjoy my Instructable, feel free to comment if you don't understand things, want this or that feature or are simply thankful for e information I'm sharing - I'll be getting email notifications, and I always have my e-mail client open =)
Meet you in my next Instructables!