Saturday, June 23, 2012

Running a HD44780 LCD over I2C (On the Raspberry Pi)

First Post! ... Now that that's over, I'm one of the lucky few/many people who has been able to get the Pi, so time to start spitting out libraries and tutorials. Today I'm going to start by focusing on getting an HD44780 lcd to work with the RasPi, since these are incredibly common, and some of the cheapest and easiest character lcds to work with.

One of the major problems plaguing the RasPi is the lack of GPIO, and the weird placement and random numbered GPIO pins broken out, making any code using them much more disorganized than if consecutive  GPIO pins had been used. In addition to the inconvinience of using the GPIO, the LCD uses 5v, while the RasPi is strict 3.3v. In order to prevent any chance of damage to my Pi, and to solve the problem, I opted to use a PCF8574 I2C 8 bit IO expander. This makes it easy enough to use the bottom four bits of the expander for LCD data, and the next 3 bits for RS, R/W, and EN. To control the LCD this way, I wrote up a nice library to get things working on the lcd.  In addition to this, I added a few extra bits to the library so that anyone can add more devices to it, and wrote a class for the tmp102 so we can have some live data to display.
The Raspberry Pi wired up to a serial bridge, PCF8574 Port Expander,
and a tmp102 12bit temperature sensor

eLinux has a guide on how to build and install the kernel, to get a updated kernel with the drivers use this git repository: https://github.com/bootc/linux
This library makes it very simple to use both these devices with the RasPi. Before using the library, you must load the i2c-dev module. At the time at which I started this project, the default Raspberry Pi linux tree was outdated and did not include spi and i2c userspace drivers.

After you get your new kernel installed, load the i2c-dev module, and place pylcd.py in whichever directory your working in, then we can move on to a simple sample program.




This is just a simple temperature display program using both the lcd and the tmp102. Despite being hard to see, the RasPi is connected through the i2c0 port with GPIO0 as SDA, and GPIO1 as SCL. Hopefully this library can help a few people get further with the RasPi and be part of some great projects. In the following weeks I plan on posting more content centered around the Raspberry Pi and other projects. If you use this in your project, please contact me, I would like to see how this libraries being put to use. Good Luck!




Links:

https://gist.github.com/2962884 The libraries somewhat regularly updated gist

55 comments:

  1. I'd just buy a FTDI usb to 5v TTL adapter and a sparkfun serial LCD along with 0.1" header inserts. (Assuming you dont have any laying around)

    Get a pen and rearrange the default pinout on the FTDI connector to match the LCD (you need to swap a couple wires) and then plug it into the USB. It should all work without the io expander, i2c drivers, or soldering

    ReplyDelete
    Replies
    1. This was mainly done since not everyone has a serial backpack for an LCD, or want to take up a USB port. While your suggestion would work fine, this was both a personal exercise with using i2c-dev in userspace and to have a cheap and quick way to use the LCD. I would think an io expander or shift register would be more available on hand than ordering a serial backpack. Thanks for the feedback.

      Delete
  2. Hi!

    Could you add the schematics for this project? I want to know how did you achieve the voltage translation between the RPi and the PCF8574.

    Thanks in advance

    ReplyDelete
    Replies
    1. There's no need for a voltage translation, the PCF8574 is actually going from the 3.3v i2c on the RasPi to the 3.5v on the HD44780 LCD.

      Delete
  3. lost what I had written. Anyway, most hd44780's can accept (VCC*0.7) voltage logic input. I can control a 5v lcd with a msp430 running at 3.3v pin output. The Lcd still needs 5v, but the logic does not. Just don't pull r/w high, don't put it into read mode. Most people tie r/w to ground anyway.

    I did use your same setup. Msp430 -> pcf8574 -> lcd. No need for level translations.

    ReplyDelete
    Replies
    1. Even if voltage translation isn't needed, in my experience, working with linux GPIO is worse than working with linux i2c, especially since the RasPi doesn't have 7 consecutive numbered GPIO, i can just write to the GPIO state registers and easily have it working, and i would have to deal with sysfs in python. This way, i can be sure my RasPi is safe, even if a bit unnecessary, and use multiple LCDs and devices on the i2c port, so I can save the GPIO for something else.

      Delete
  4. Firstly, good work :) I've been hoping someone would provide some Python code to do this.

    Unfortunately, I can't seem to get it to work. I've got a HD44780 with an I2C backpack (uses a PCA8574). Whenever I run the code it just causes the backlight to flicker and the cursor to move erratically.

    I've confirmed the LCD works on my Arduino using the Wire library. Any ideas where I might be going wrong?

    ReplyDelete
    Replies
    1. Do you have a schematic for this backpack. My code assumes that the first 4 bits are attached to the 4,5,6,7 data pins on the lcd. The next 3 bits (4,5,6), should be attached in the same order to 4-CLK/EN, 5-R/W, 6-RS. If the schematic is different for you, i'll try making the code a bit more modular for different pinouts.

      Delete
    2. I don't have the schematic, but I've been examining the traces and it looks like pins 9,10,11,12 of the 8574 are connected to 11,12,13,14 on the LCD. 4,5,6 are connected to 4,5,6 of the LCD as you suggest.

      Delete
    3. Ok, I'll go into the code and change some things. It seems your backpack is doing to opposite order that i am doing, so I'll have to fiddle with the code a bit to get it working.
      Mine: P0-P3 --> LCD 11,12,13,14; P4-P6 --> LCD 4,5,6
      Yours: P0-P2 --> LCD 4,5,6; P4-P6 --> LCD 11,12,13,14

      Delete
    4. I just added the feature to switch between the three most common pinouts (other pinouts seem unlikely to occur).

      Delete
    5. I managed to test this but it still isn't working. There is some progress though - I can see a character or two on the display, but the backlight still flashes. I tried it on all three reverse modes (I believe I should be using mode 1).

      The backpack I am using is a Chinese knock-off of this one:

      http://www.dfrobot.com/image/data/DFR0175/I2C%20LCD%20Backpack%20schematic.pdf

      Delete
    6. The schematic irv posted appears to match the pinout of my backpack, and I seem to be getting similar results. The lcd starts with the backlight on, the first time I try to send data to it with this, the backlight goes off. I get a few 0's on the first line of the lcd, but never anything that resembles what I was trying to display. Looking at the different options you've made available, I'm not sure I understand them. Which option would fit for a board with these connections?

      LCD CHIP
      RS P0
      RW P1
      E P2
      D4 P4
      D5 P5
      D6 P6
      D7 P7

      One other question - Why is it you only list 3 of the data pins, both in your 7/1/12 3:44am comment, and in the code comments describing the "reverse" options? I see P4,P5,P6, but not P7.

      Thanks!

      Delete
    7. I ended up getting this lcd working by using lcdproc and a modified driver. More information and the modified driver can be found here: http://www.neighborgeek.net/2013/02/using-16x2-lcd-with-i2c-on-raspberry-pi.html

      Delete
  5. Excellent, thank you. I will try it out later today and let you know how it goes :)

    ReplyDelete
  6. Here is my C++ HD44780 library for RaspberryPi. Easy to use.

    https://plus.google.com/101070371711981569549/posts/4EqiRHJnwXD

    or

    https://gitorious.org/rpi-gpio
    https://gitorious.org/rpi-hd44780

    ReplyDelete
    Replies
    1. Finally something "real" IMHO. Great C++ library that one can use and hope for a real time response of the hardware... Thanks

      Delete
  7. Do you have a better picture or schematic of your wiring?
    I'm not exactly sure how everything fits together in your picture.

    Also, would it be possible to use different GPIO-pins (instead of 0 and 1) for i2c?

    ReplyDelete
  8. Did you run the PCF8574 off 5V? With your own pullups on SDA/SCL and to what voltage? Or did you run the 8574 on 3.3V and assume the 44780 would accept 3.2V as a high?

    thanks...

    ReplyDelete
  9. I REALLY appreciate the work here! I am having a tought time getting this to work. I am using the LCD2004 daughterboard (same basic design as above) with a reverse=0. I get flashing on the LCD, but nothing else. Can you provide more detail on the commands sent?

    ReplyDelete
    Replies
    1. My library should have a few different modes for it, try them out. I can't find anything on the pinout of the daughterboard so it's hard for to give much more help

      Delete
    2. I am running the same backpack knock off as irv075. Same problems also - flickering, then nothing. Once in a while a character.

      Could the timing of the strobe be causing problems?

      thanks for the help!

      Delete
  10. I've given up and bought the components to make my own backpack based on the PCF8574. What is strange is that the knock-off works perfectly with the Arduino library...

    ReplyDelete
    Replies
    1. I am almost there. I read someplace, not sure where, that the Pi could be running too fast for some peripherals designed for the Arduino. Unfortunately the article implies that the kernel would need to be recompiled (or portions) to slow it down. Not something I am willing to do.

      Today I intend to unsolder the daughter board and try a direct method with the GPIO.

      Delete
    2. Interesting, i experienced no such flickering during my tests.it may be possible that the kernels gpio and SPI/I2C handling may have changed since I've used this. If you want I can post a schematic of a functioning setup.

      Delete
    3. I got it working! Here is the code:
      https://github.com/thorrak/pi-libs/blob/master/pylcd.py
      https://github.com/thorrak/pi-libs/blob/master/pylcd_test.py

      I added some additional commenting, and changed a few things as well to make it work with backpacks with even crappier pinouts. This is unfortunately my first time dabbling in Python so I guarantee there are easier ways of accomplishing this. Feel free to rewrite anything, republish, etc.

      Delete
    4. Great, i've made an actual github repo for this, if you could please fork my repo then add your changes and send a pull request so I can see all your changes in detail and review them before adding them to the library. https://github.com/tech2077/PyLCD

      Delete
  11. Thanks a lot for this.
    I do not own RasPI, but I am using a low-cost router with OpenWRT, lots of i2c devices connected to i2c-tiny-usb adapter and this would make a nice way how to display the information.
    Thanks again.

    ReplyDelete
  12. Hi,

    just a short question. I have this LCD with I2C-board and do find it at 0x27 (using i2cdetect) on bus 0. But, when I connect the 5v, GND, SCL and SDA to the display, it starts to flicker. Moreover, I cannot succeed with using any of the code given here (neither Thorraks).

    How do you guys wire this? Am I missing something?

    ReplyDelete
  13. Your script works fine but is it possible to tell us , how we can use own made characters?
    The HD44780 lcd's can be programmed in CGRAM from 0 to 7.
    In your code, if i read correct, you already have that part in it, but no where i can find a example for it! the code what i mean is.:

    / add custom characters (0 - 7)
    def lcd_load_custon_chars(self, fontdata):
    self.lcd_device.bus.write(0x40);
    for char in fontdata:
    for line in char:
    self.lcd_write_char(line)

    Best Regards.
    Pascal.

    ReplyDelete
    Replies
    1. It takes a 8x8 list of bytes that hold pixel data for the char, this is an untested feature as I never had a need for it, but if it works for you great.

      Delete
    2. I can't use that futere because no one tryed it and there is no example that can be found on the internet of it.
      So my qoustion was if you have a code for us with a example of it.
      Because there are symbols that for example can be used thats not in the standard symbol list of a HD44780 lcd.

      Delete
    3. Hello.

      All work fine, but how to use custom characters?
      Thanks.

      Delete
  14. Thanx for ur post nd ur article, today I just tell u about a biggest retailar in electronics, ya @Sargam Electronics is the biggest Retail Chain in Electronics in Delhi. Sargam deals with big brands like Samsung, Sony, Lg, Toshiba, Sansui, Hitachi, Voltas, Godrej, Azure, Nikon, Whirlpool, Dell, HP, Acer and many more brands. @Sargam Electronics offers you to buy many electronics products like laptop,mobile,camera,washing machine,refrigerator,air conditionar and multiple electronics products at huge discount.

    ReplyDelete
  15. Thanks for this. You saved me a bunch of time. However, I think I found a bug. I've got a type of LCD/i2c config which you call "reverse == 1". When I use that, it sends two different strobe signals, because you have this (forgive the underscores... blogspot is stripping out my indenting spaces):

    def lcd_strobe(self):
    __if self.reverse == 1:
    ____self.lcd_device.write((self.lcd_device.read() | 0x04))
    ____self.lcd_device.write((self.lcd_device.read() & 0xFB))
    __if self.reverse == 2:
    ____self.lcd_device.write((self.lcd_device.read() | 0x01))
    ____self.lcd_device.write((self.lcd_device.read() & 0xFE))
    __else:
    ____self.lcd_device.write((self.lcd_device.read() | 0x10))
    ____self.lcd_device.write((self.lcd_device.read() & 0xEF))

    If reverse == 1, the first *AND* third blocks get executed because the third block is executed in the case that reverse != 2. What I added, on my end, was an else before the 2nd 'if'...

    def lcd_strobe(self):
    __if self.reverse == 1:
    ____self.lcd_device.write((self.lcd_device.read() | 0x04))
    ____self.lcd_device.write((self.lcd_device.read() & 0xFB))
    __else:
    ____if self.reverse == 2:
    ______self.lcd_device.write((self.lcd_device.read() | 0x01))
    ______self.lcd_device.write((self.lcd_device.read() & 0xFE))
    ____else:
    ______self.lcd_device.write((self.lcd_device.read() | 0x10))
    _____self.lcd_device.write((self.lcd_device.read() & 0xEF))

    Works a little better. :) Of course, there's probably a way to do it with case statements, but I don't know any more about Python than I absolutely have to.

    ReplyDelete
    Replies
    1. I'll go and fix this on the gist, a nice addition of elif to it should work. Thanks for the contribution.

      Delete
  16. Same problem exists in lcd_write_char(), incidentally. Now my LCD works. Hooo boy! You should have *seen* the crazy stuff I was seeing with 2 sets of commands being sent to my LCD. :)

    ReplyDelete
  17. Okay... more fixes...

    Turns out that the lcd_load_custom_chars() didn't work for me. It was calling self.lcd_device.write(0x40) when it *should* have been calling self.lcd_write(0x40) so that the command would be properly written in two half-byte nibbles:
    ____def lcd_load_custom_chars(self, fontdata):
    #_______self.lcd_device.write(0x40);
    ________self.lcd_write(0x40);
    ________for char in fontdata:
    ____________for line in char:
    ________________self.lcd_write_char(line)


    Also, while I was at it (because I was messing with custom chars so that I could make interesting logos), I needed to be able to place the cursor anywhere I wanted, so I wrote:

    row_offsets = [ 0x80, 0xC0, 0x94, 0xD4 ]
    def lcd_locate_cursor(self, row=1, col=1):
    ____addr = self.row_offsets[row - 1] + col - 1
    ____self.lcd_write(addr)

    Now, in lcd_puts(), you can replace the:

    def lcd_puts(self, string, line):
    ____if line == 1:
    ________self.lcd_write(0x80)
    ____if line == 2:
    ________self.lcd_write(0xC0)
    ____if line == 3:
    ________self.lcd_write(0x94)
    ____if line == 4:
    ________self.lcd_write(0xD4)

    ____for char in string:
    ________self.lcd_putc(char)

    ... with just ...

    def lcd_puts(self, string, line):
    ____self.lcd_locate_cursor(line)
    ____for char in string:
    ________self.lcd_putc(char)

    ReplyDelete
  18. Man... I just can't stop tweaking. I've now added some thread-sync stuff so that you can now use this with multi-threaded code, and you don't have to worry about the i2c commands getting all interleaved. I'll stop posting code in the comments, because it's getting a little overboard. If you're interested in the code, let me know...

    ReplyDelete
  19. Joe, can you post a working code of the custom chars? every body look for it aorund the internet, did you have a full working code? please :(

    ReplyDelete
  20. I'll get it posted soon. I'm in the middle of a big overhaul of this, actually. I noticed that Matthew has this on Github, so I'll probably send him a pull-request once I have it working (and once I figure out how to *send* a pull request). Some improvements include:
    - Thread-safety (you can have multiple threads all sending stuff to the LCD and it won't cause problems with the signaling to the chip)
    - Backlight control
    - "Reverse" code is all moved to two methods which handle all of the moving around of the RS, Ena, R/W, Backlight, and data bits.
    - "Test" method which turns on each bit, one at a time, so that you can use a volt-meter to see which of your LCD pins each bit corresponds to. Then, you can figure out which "reverse" mode to use, or if you need to make your own.

    ReplyDelete
  21. I did the same using C programming. Anybody wants to try it out? http://karunadheera.com/index.php/archives/194
    You should however have Gordon's wiringPi - http://wiringpi.com/download-and-install/ first.

    ReplyDelete
    Replies
    1. I don' know if ur code will work with IIC/I2C/TWI/SP​​I module with 20x4 lcd that i bought from ebay.

      Delete
  22. Can this code will work that i bought from ebay? It is IIC/I2C/TWI/SP​​I Serial Interface2004 20X4 Character LCD Module Display Blue

    Thanks!

    ReplyDelete
  23. I deleted the I2c portion of the module because I use Adafruit's Python_I2C module. The line " self.lcd_device = i2c_device(addr, port)" gives error
    Traceback (most recent call last):
    File "test.py", line 5, in
    lcd=Hc44870.lcd(0x27,0)
    File "/home/root/Hc44870.py", line 31, in __init__
    self.lcd_device = i2c(addr, port)
    NameError: global name 'i2c' is not defined
    I not sure what properly should be here.

    Jim

    ReplyDelete
    Replies
    1. The code above doesn't work. So I switched to http://www.recantha.co.uk/blog/?p=4849

      It loaded right away without error problem.Copied and pasted
      working good I used indentation 4, using python 3.2.3
      Btw, I don't used AdaFruit library,

      I'm very happy with recantha's blog. I suggest u try this recantha's site.

      Delete
  24. U missed something
    self.lcd_device = i2c(addr, port)

    change to:
    self.lcd_device = i2c_device(addr, port)

    ReplyDelete
    Replies
    1. supraDecember 11, 2013 at 7:44 PM

      Thanks for responding! You said

      U missed something
      self.lcd_device = i2c(addr, port)

      change to:
      self.lcd_device = i2c_device(addr, port)

      I get the same error. I wo7uld really like to use Adafruit's I2C python code to use some of the other methods it has. I had changed the line as you saw as an attempt to make things work. The only other change I made was to delete the i2c_device lines. I had already loaded the Python_I2C module in my code.
      Jim

      Delete
    2. Did u added import Hc44870.py to test.py?
      I couldn't find code from AdaFruits

      Delete
    3. Python_I2C is a library and test.py is main program to test it?
      Did id u added import Python_I2C in test.py?

      Delete
  25. Thanks a million for this. This took me so long to get going on my beaglebone black. I have an i2c lcd backpack that I bought from ebay that doesn't work with anything else, and I couldn't find easy code to modify to make it work properly. I went through probably 50 sites before I found your sample code.

    ReplyDelete
  26. Man i just wanted to thank you for the code you provide here , it help me a lot to make work this lcd with my java app , now u show me the way , almost everythings work.

    your code is clear , clean and work perfectly, Good job !

    ReplyDelete