Monday, October 10, 2022

Micropython - Driving an LCM4004A LCD with dual HD44780 (clone) driver chips

 

This is the display I'm using:

There are a few vendors for these 40 column x 4 row displays, all using clones of the Hitachi HD44780 controller:

These have a character pallet built in and have memory for 8 custom characters when needed.  And they have memory/wiring to support 80 characters on a display, so this 4004 display with 160 characters uses two drivers.  They share the same Data, RS, RW pins, but each have a separate enable pin to instruct them separately when writing to different lines of the display.

I didn't find any libraries specifically for this display, most of them are for 2004 or 4002 displays with a but this one from Adafruit gets pretty close - it's for the correct driver chip, but needed to be adapted to support two driver chips.  I ended up completely gutting this to get stuff working without the extra code, but adapted the setup sequence and write8 functions from here:


I'm using an Arduino Nano Connect RP2040 here, so the pins will be a little different for a PI Pico or other chip. 

Generally, the way this works is we:
  • Define our pins
  • Initialize some globals with the values for specific instructions that the contorller understands
  • Call the initialize function to turn the display on
  • Subsequently use the "write8" function to write characters to the display.

What's left to do?
  • Figure out how to control the cursor and write to specific locations on the display.
  • Figure out how to turn on the backlight.
  • Get contrast control working.  I tried to do this with a 10k pot, but the display isn't responding.
  • Adapt this module to work with the fbconsole library for a fun terminal.


import time
from machine import Pin

D4 = Pin(26, Pin.OUT)
D5 = Pin(27, Pin.OUT)
D6 = Pin(28, Pin.OUT)
D7 = Pin(29, Pin.OUT)
E1 = Pin(12, Pin.OUT)
E2 = Pin(13, Pin.OUT)
RS = Pin(5, Pin.OUT)

# Commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# Entry flags
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# Control flags
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# Move flags
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# Function set flags
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00



def delay_microseconds(microseconds):
# Busy wait in loop because delays are generally very short (few microseconds).
end = time.time() + (microseconds/1000000.0)
while time.time() < end:
pass


def write8(value, enable_pin, char_mode=False):
"""Write 8-bit value in character or data mode. Value should be an int
value from 0-255, and char_mode is True if character data or False if
non-character data (default).
"""
print('write8', f'{value:#010b}', enable_pin, char_mode)
# One millisecond delay to prevent writing too quickly.
delay_microseconds(1000)

# Set the character/data bit
RS.value(1 if char_mode else 0)

# Write the Upper 4 bits
D4.value(((value >> 4) & 1) > 0)
D5.value(((value >> 5) & 1) > 0)
D6.value(((value >> 6) & 1) > 0)
D7.value(((value >> 7) & 1) > 0)

enable_pin.off()
delay_microseconds(1)
enable_pin.on()
delay_microseconds(1)
enable_pin.off()
delay_microseconds(1)

# Write the Lower 4 bits
D4.value((value & 1) > 0)
D5.value(((value >> 1) & 1) > 0)
D6.value(((value >> 2) & 1) > 0)
D7.value(((value >> 3) & 1) > 0)

enable_pin.off()
delay_microseconds(1)
enable_pin.on()
delay_microseconds(1)
enable_pin.off()
delay_microseconds(1)

def clear():
print('Clear')
write8(LCD_CLEARDISPLAY, E1) # command to clear display
delay_microseconds(3000) # 3000 microsecond sleep, clearing the display takes a long time
write8(LCD_CLEARDISPLAY, E2)


def initialize():
print('Initializing')
# Initialize display control, function, and mode registers.
displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF
displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_2LINE | LCD_5x8DOTS
displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT

for enable_pin in E1, E2:
write8(0x33, enable_pin)
write8(0x32, enable_pin)
# Write registers.
write8(LCD_DISPLAYCONTROL | displaycontrol, enable_pin)
write8(LCD_FUNCTIONSET | displayfunction, enable_pin)
write8(LCD_ENTRYMODESET | displaymode, enable_pin) # set the entry mode
clear()


if __name__ == '__main__':
initialize()
# Initialize the display.
for char in 'Hello World':
write8(ord(char), E1, True)
for char in 'Foo Bar! +~!123':
write8(ord(char), E2, True)


No comments: