NodeMCU and HD44780 – part 4: drivers, i2c and double E

Time to take another step with NodeMCU and HD44780. This time we will separate driver logic from lcd logic. Why? Because it is much more flexible for our lcd (direct and bffered) to have general write, set_xy and move wiring specific logic to driver. This way we can just swap gpio with i2c without any problems.
This imply that we will also write i2c driver 🙂
Another matter is hooking screens with size 40×4. In fact they are two smaller lcds, 40×2, with shared all signals except E1 and E2. How it works? You set required signals and select top screen with E1 or bottom with E2.
Lets get to work.
Code is available in boilerplate

Extract GPIO driver

Our driver require a nice name, name like: lcd_hd44780_gpio.lua.
First move pin assignment from main module to driver. Slightly change constructor and remove pins attribute. Add drv attribute.
Move pins initialization to driver and in its place put call to it.
Due to problem with 4 and 8 bit mode (first call is 8 bit command and then it switch to 4 bit mode) we need to add another function: command4(). Finally move command, _send, _write4, _write8, _write_direct to driver.
Almost forgot, tweak a little bit a flush() function.
After this err small refactoring driver is separated from lcd module.

Add second enable

Once again, why we need second E? Big screens like 40×4 are in fact two 40×2 lcds. That is why it has 2 E signals, each for each display.
Our function needs to know to which screen we write. How to detect it? Simply by looking at coords and screen size. If we have size 40×4 and we are in line higher than 2 – it needs E2. In other cases it needs E1.

Screen detection goes to main class not driver. So lets quickly add it and add E signals to main module and GPIO driver.

Each of our functions needs enable as parameter because both command and write calls send() and send is using E, so it need to now which E we want:)
Finally as we have two screens, we need to initialise both.
After rather long refactor, remember that we have no tests :(, code is ready for dual enable signals.

I2C wiring

Hurray! Time to wire some err wires, connect some connectors and display something on display 😀
Connection goes between three elements, NodeMCU, PCF8574 and LCD.
Lets look at pins:

LCD                                     PCF8574                    
13 -------- GND               GND ----- A0   Vcc ---- +5V            
14 -------- +5V               GND ----- A1   SDA ---- D1         
12 --/\/\ [potentiometer]     GND ----- A2   SCL ---- D2         
       \--- GND              LCD4 ----- P0   INT                 
11 [RS]---- P4               LCD3 ----- P1   P7
10 -------- GND              LCD2 ----- P2   P6 ----- LCD15
 9 [E]----- P5               LCD1 ----- P3   P5 ----- LCD9
15 [E2] --- P6                GND ----- GND  P4 ----- LCD11
 4 [DB4]--- P0
 3 [DB5]--- P1
 2 [DB6]--- P2                          NodeMCU
 1 [DB7]--- P3                        SDA --- D1 
17 --/\/\ [potentiometer]             SCL --- D2
       \--- +5V                       +5V --- VU
18 -------- GND                       GND --- G

It was a nice surprise that 5V from NodeMCU was enough to power screen, we do not need external power supply.


I2C driver

Main question, why i2c? Main reason: it takes only two pins and not six!
I will use CharLCD as a guide, we are using i2c there so we just need to reimplement an idea.
After some research I know that we only need address because bus is always 0.
I2C works differently from GPIOs, we cannot set signals and than call send. We are sending all in one packet. That’s why if you look into code you will see internal buffer and internal rs flag. Functions set that as needed and function send put this all together. By doing in such way we have same function calls in gpio and this driver.
Our code has a bug with recalculating cursor position. It is adding length but when it is out of row it does not increase y.
Another fixed bug is with calculating cursor address.


How to use all of this? Lets see how the code that displays content from top image looks like:

drv = require("lcd_hd44780_i2c")
lcd = require("lcd_hd44780")

drv.addr = 0x20
drv.pins = {
    RS= 4,
    E1= 5,
    E2= 6,
    DB4= 0,
    DB5= 1,
    DB6= 2,
    DB7= 3,

lcd.lcd(drv, 40, 4)
lcd.cursor_visible = 0
lcd.set_xy(18, 1)
lcd.write('Are you looking at me?')

lcd.set_xy(30, 0)

lcd.set_xy(0, 0)
lcd.set_xy(0, 1)
lcd.set_xy(0, 2)
lcd.set_xy(0, 3)
lcd.write(" `(_)--------(_)\"")

This is quite simple 🙂


As you can see this post is almost without code. I started coding and forgot about post ;; So this time only brief explanation and link to download.
But what have we done? Separated module lcd from drivers, added support for 40×4 with two E lines and added i2c driver.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s