I've added a few examples for interfacing a character LCD with the z80ctrl IO expander board. The specific LCD I am using was purchased from Amazon. However, almost every character LCD you can find will use a standard interface based on the HD44780 chip, and my examples should work with any of them.
Since the LCD has a parallel interface to begin with, one could argue that it's a bit convoluted having the Z80 talk to the AVR over the parallel bus, which converts it to a serial signal to send to the IO expander, which converts it back into a parallel signal to send to the LCD. To that, I say: Shut up, one! It's my project and I'll do what I want!
The pinout for a character LCD is also fairly standardized. This is from the LCD I bought:
Hooking up the LCD to the IO expander board is straightforward:
- Connect pins GPA0-GPA3 from the GPIO A connector to DB4-DB7 on the LCD
- Connect pins GPA4, GPA5, and GPA6 from the GPIO A connector to RS, R/W, and E on the LCD, respectively
- Hook up 5V power to VDD and A on the LCD
- Hook up ground to VSS and K on the LCD
- Connect a 10K pot to V0 to adjust the contrast
Once it's hooked up, I wrote a simple MBASIC program that prints "hello, world" on the LCD:
10 GOSUB 1000 50 C=&H33:GOSUB 3000: REM RESET SEQUENCE, PART 1 60 C=&H32:GOSUB 3000: REM RESET SEQUENCE, PART 2 70 C=&H28:GOSUB 3000: REM 4-BIT MODE, 2 LINES, 5x8 CHARS 80 C=&HD:GOSUB 3000: REM DISPLAY ON, BLINKING BLOCK CURSOR 90 C=&H1:GOSUB 3000: REM CLEAR DISPLAY 100 INPUT "STRING TO PRINT"; S$ 110 GOSUB 5000 120 END 1000 REM INITIALIZE GPIO PORT 1010 OUT 0, 1: REM GPIO CHIP 1 1020 OUT 1, 0: REM DATA DIRECTION REGISTER A 1030 OUT 2, 0: REM ALL OUTPUT 1040 OUT 1, &H12: REM GPIO REGISTER A 1050 RETURN 2000 REM SEND BYTE IN C TO LCD 2010 OUT 2, &H40 OR M OR (C\16) : REM TOP NYBBLE WITH ENABLE HIGH 2020 OUT 2, M OR (C\16) : REM TOP NYBBLE WITH ENABLE LOW 2030 OUT 2, &H40 OR M OR (C AND &HF): REM BOTTOM NYBBLE WITH ENABLE HIGH 2040 OUT 2, M OR (C AND &HF): REM BOTTOM NYBBLE WITH ENABLE LOW 2050 RETURN 3000 REM SEND COMMAND IN C TO LCD 3010 M=0 3020 GOSUB 2000 3030 RETURN 4000 REM SEND CHAR IN C TO LCD 4010 M=&H10 4020 GOSUB 2000 4030 RETURN 5000 REM SEND STRING IN S$ TO LCD 5010 FOR I = 1 TO LEN(S$) 5020 C=ASC(MID$(S$,I,1)) 5030 GOSUB 4000 5040 NEXT 5050 RETURN
The BASIC program has several subroutines that illustrate how to interface the LCD:
- 1000 initializes the GPIO port on the IO expander to talk to the LCD. It sets the IODIRA register (address 0) to 0 to make all the pins outputs. Then it selects the GPIOA register (address &H12) which will be used by the rest of the program to control the output.
- 2000 sends the byte in C to the LCD with the register select bit set in M. To reduce the number of pins required, the LCD is used in 4-bit mode. Therefore the program has to sends the byte 4 bits at a time, clocking the enable line each time.
- 3000 is a wrapper that sends a command to the LCD by setting M=0 and calling 2000
- 4000 is a wrapper that sends a character to the LCD by setting M=1 and calling 2000
- 5000 sends a complete string in S$ to the LCD. It iterates over each character in the string and converts it to ASCII, then calls 4000 to send each character individually
- The main program starting at 10 sends a series of commands to initialize the LCD by setting the value of C and calling 3000 for each one. Then it sets the value of S$ to "hello, world" and calls 5000 to send the string.
- Note: MBASIC is slow enough that I didn't have to check the LCD's busy flag between writes, which would have made the code considerably more complicated. Check out the assembly program below to see how that's done.
I have also written an assembly program to retrieve the date and time from the RTC and display it on the LCD. Since it's quite long, I won't reproduce it here, but be sure to check it out if you'd like an example of using the IO expander from Z80 assembly. The code is heavily commented so it should be easy enough to follow.