• I've been playing with a 16 char, 2 line 1602A LCD display using the HD44780 module written by Gordon (3 years ago!). While the module provides basic functionality to write characters to the display, there is a lot of interesting functionality in the HD44780 driver chip that isn't implemented, so I started enhancing the module to add some of this functionality.

    Also, found a bug... the HD44780 has a busy condition while its working on an instruction, and the busy flag needs to be checked before committing another operation. You can best see this manifest by writing to the display, then clearing and immediately writing again... the second "print" call will almost always screw up and you'll get junk in the display (at least for the 1602A display I'm playing with). I added code to fix this, requiring the ability to read from the HD44780, something not implemented in the current module.

    This is a Work In Progress, so the additional functionality in the attached module is not fully baked; for instance, all of it is implemented only for the 4-bit parallel interface. I haven't addressed the I2C connection option. Still, if you want to play around with it, here it is, attached.

    Added functionality:

    lcd = require("HD44780").connect(rs,en,d4,d5,d­6,d7[,rw]); //this is being pulled locally from my modules folder in the project directory

    The params in the connect statement above correspond directly the the pin names on the HD44780 chip. What's new here is the addition of the R/!W pin at the end. In wiring the display up, this needs to be connected to an actual pin on the MCU rather than simply grounded, as read operations are now supported. It's at the end so it can be left out, and the driver will be backwards compatible with code written for the older version (with the bug fix, though).

    lcd.print(string[, scroll]);


    While the display is 16 characters wide, the internal buffer in the 44780 is actually 40 chars per line. The display is actually a 16 character "window" on the underlying 40 character buffer. You can send a 'string' parameter up to length 40, and it will be written into the display buffer (called DDRAM), with the first 16 characters displayed. There are 44780 instructions to shift the display window left or right in DDRAM to display different parts of it.

    The optional 'scroll' parameter above if 'true' will cause the display to scroll left automatically when a string longer than 16 characters is written, until the last character is displayed. It then pauses for a bit, resets back to the beginning of the string, and repeats.

    The interval between single character shifts is by default 150ms (looked pretty readable to me), but can be set as desired with the 'sdly' property, like this:


    The code above takes effect immediately (if scrolling), and would move twice as fast as the default.


    This will shift the display window left or right by 'count' character positions. Direction is specified by the sign of 'count'.

    buf = lcd.read(addr,len,cg)

    Reads from either DDRAM (display buffer) or CGRAM (character generator RAM), and returns a Uint8Array of length 'len'. 'addr' is the starting address in the selected RAM buffer to read from. For DDRAM, the first line starts at addr 0, second line at 0x40. CGRAM is a completely different animal that I won't address in this post. Example:

    lcd.print("this is a test of the 2 line display");
    buf = lcd.read(8,16,false);

    After this code, the display would show "this is a test o", and buf would contain [97, 32, 116, 101, 115, 116, 32, 111, 102, 32, 116, 104, 101, 32, 50, 32] ("a test of the 2 ").


    Waits for the busy flag to clear after an operation is executed. This is really only useful if you are doing low-level things by directly writing to the control register. Used internally in the module, you probably don't ever need this because it's already accounted for in read/write operations (this was the bug fix).

    Finally, all the pins are now stored as properties on the instance, with the names shown above in the connect call. So, for example, the Register Select pin can be accessed as obj.rs; the set of 4 data pins can be access as an array with obj.data. This makes it easy to program directly at a low level while still using the high-level functions (like 'print') on an HD44780 instance.

    1 Attachment

  • Great enhancements...

    Questions: since scroll and shift are not the same - erwise you would have called it the same:

    1. Scroll is per line... individually per line... not sync between the lines? What I mean be that is that lines with different numbers of chars, for example 30 and 40, will have different scroll cycles: the shorter one completes 2 scroll cycles while the longer one goes through one.
    2. When having shifted (the view port) 20 chars to the right, what is the scrolling doing?
  • Unfortunately, the hardware simply shifts the entire 2-line display over the 40 char "buffers" together -- the two lines can't be shifted separately.

    The "scroll" is simply using the shift function of the HD44780 combined with setTimeout() and a little logic to handle the end condition to scroll/marquee the display.

  • ...in other words: The scroll uses the shift function, and the shift stops when the last char of longest line shows. Is that statement correct?

  • Pretty much... just that right now, there's no "awareness" of the second line in any of the code. While you can certainly write something there using the original code from @Gordon, the additions I've made are completely blind to that part of the hardware.

    As implemented in the attached code above, the scroll feature is really only usable for a single line up to 40char on the first line. The concept as implemented was intended to be able to start with a clear display, send something to display via the print method, and have it do something sensible if longer than 16 char.

    Because shift function of the hardware shifts both lines together over their respective buffers, makes shifting two lines of different lengths a bit awkward, if the shorter one isn't going to simply be sacrificed in "sensible" scrolling for the sake of displaying the entire longer line. A simple implementation would continue to scroll the shorter line off to the left as the rest of the longer line came into the display.

    That may not be anything to fret about, in terms of the user experience. Can't say until I see it. Adding the logic to scroll to the end of the longer line (basically a Math.max() call, essentially) should be simple.

    Handling the two lines independently would be a nightmare of moving data around in the buffer.

  • I wonder whether in cases where there is no RW pin (I can imagine many may not have it wired or want to wire it), we could just insert a short delay after clear? I know this has been an issue on and off for a while - originally it was fine, and then I boosted the interpreter speed and on some boards it stopped working... Then at some point it started again, and it looks like it's stopped again now :)

    I'm also aware of a few people using this on boards like the Olimexino, where memory is really scarce and there's no Graphics lib (and folks complain about the library even as it is). It looks to me like this could probably be designed as an extra library (HD44780_full?) that requires the original and adds the extra functionality? I could be wrong, but it seems like it'd work and be relatively efficient (and you could legitimately ignore the I2C version if you wanted to :)

    (also, I wonder whether IO over I2C is slow enough that the 'busy' flag isn't actually a problem on I2C?)

  • Since size is always an issue, this should be structured as an extension to the existing driver -- totally agree. Although compared to my GUI library, this thing is nothing more than thin smoke :-)

    Maybe even recast it as a 1602A display driver rather than a more generic HD44780 driver, as I think it safe to assume that that hardware always has the R/!W connection exposed. What do you think?

    Then, anyone with a different display implementation using the HD44780 can still use the 1602A driver for their display, even if its something else.

    It's not finished anyway, and won't be for a while. This, like the GUI project, are the output of my learning and experimenting with Espruino, MCUs in general, and the huge universe of "things" to attach as input sensors and output UI. So, unless there is someone out there dying to have this polished up so they can use it for a real project, it's on the "fiddle with it" shelf right now as my interest wanders around from one thing I'm messing with to another.

    Eventually (like, before the end of the calendar year) I plan to take a weekend to polish this, the GUI, a 28BYJ-48 stepper driver I just wrote this weekend (I'll be posting on that later today), and a few other tidbits into formal, submitted modules to the Espruino project on Github.

    Until then, I'll keep posting code in the forum for others to check out, looking for feedback.

  • Thanks! Sounds like a good plan - it's great to keep sharing what you're up to, and as you say there's no rush - if someone wants the code as-is they can just copy it. In fact they can even just require straight from the forum wirh require("https://espruino.microco.sm/api­/v1/files/678070619d2612bcbdf8ff93714632­84cd5173e2.js")

    Yeah, memory is tricky. You've got stuff like the Pico with 5000 16 byte variables, all the way to the micro:bit and Olimexino with 250 or less 12 byte vars :)

    I'm not sure I'd name your stuff 1602A... it might just be confusing - after all, I have a bunch of 20x4 displays that I'm sure this would work on too. It's just thinking of something that implies it has more features than the standard version :)

  • nm

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview

A bug in, and enhancements to the HD44780 module (2-line LCD display)

Posted by Avatar for dwallersv @dwallersv