RC2024 – Part 9 – Using The LCD Module From A Z80 Assembly Language Program

The RC2014 LCD module showing example text.

I am planning on using an LCD screen as part of this year’s Retro Challenge. I want to use my new Rotary Encoder Module to be able to scroll the content on the screen.

I have bought the official RC2014 LCD Driver Module, and I have a 4*20 screen attached to it. This gives me 4 lines of 20 characters.

Spencer provides example BASIC code to control the screen. I want to control it using Z80 assembly language. Spencer does warn that this will not be as straight forward as the LCD will not respond fast enough. He does point us towards Mike Sutton’s blog for a solution. This involves us reading the status register on the LCD to see if it is ready for the next instruction.

I created two routines to send data to the LCD screen in Z80 assembly language. One to send a command byte and return when completed. The other to send a data byte and return when completed.

LCD_R   EQU 218
LCD_D   EQU 219

; Sends a command byte to the LCD.
; A - Command in
send_command:
    out (LCD_R),a
.lcd_busy:
    in a,(LCD_R)
    rlca
    jr c,.lcd_busy
    ret

; Sends a data byte to the LCD
; A - Byte in
send_data:
    out (LCD_D),a
.lcd_busy:
    in a,(LCD_R)
    rlca
    jr c,.lcd_busy
    ret

Another issue is the layout of the LCD screen. The first 20 characters are the first line, the next 20 are the third line, the next 20 are the second line, and the last 20 are the last line. This means we can’t simply loop through all characters in order to display them. Instead, we must be aware that every 20 characters we need to pick the next batch from elsewhere in memory.

In our case, the first line is at start of our data block. The second line is 40 bytes from the start. The third line is 20 bytes from the start. The fourth line is 60 bytes from the the start. This gives us 80 bytes in total.

This is how I solved this in Z80 assembly language.

; Display 4 lines of consecutive text on the LCD
; HL - address of text to display on the LCD
show_four_lines:
    ld b,20
.line1loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line1loop

    ld de,20
    add hl,de
    ld b,20
.line2loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line2loop    

    ld de,40
    sub hl,de
    ld b,20
.line3loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line3loop

    ld de,20
    add hl,de
    ld b,20
.line4loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line4loop 

    ret      


puff:
    db "Retro Challenge 2024"
    dc "                    "
    db "  LCD RC2014 Test   "
    db "    Robert Price    "

An alternative could be to store the lines in screen order. This would be easier to iterate, but would make the text harder for a human to read in the source code.

My final code looks like this. I have to send some commands to setup the LCD before I can display my text.

    OUTPUT LCD.z80

    ORG $9000

LCD_R   EQU 218
LCD_D   EQU 219

    ld a,56         ; Function 8 bit, 2 lines, 5x8 dot font
    call send_command
    ld a,12         ; Display on, cursor off, no blink
    call send_command
    ld a,1          ; clear the display
    call send_command

    ld hl,puff      ; the address of the text
    call show_four_lines

    ret

; Sends a command byte to the LCD.
; A - Command in
; A, C registers used.
send_command:
    out (LCD_R),a
.lcd_busy:
    in a,(LCD_R)
    rlca
    jr c,.lcd_busy
    ret

; Sends a data byte to the LCD
; A - Byte in
; A, C registers used.
send_data:
    out (LCD_D),a
.lcd_busy:
    in a,(LCD_R)
    rlca
    jr c,.lcd_busy
    ret

; Display 4 lines of consecutive text on the LCD
; HL - address of text to display on the LCD
; A, B, C, D, E, H, L registers used.
show_four_lines:
    ld b,20
.line1loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line1loop

    ld de,20
    add hl,de
    ld b,20
.line2loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line2loop    

    ld de,40
    sub hl,de
    ld b,20
.line3loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line3loop

    ld de,20
    add hl,de
    ld b,20
.line4loop:
    ld a,(hl)
    inc hl
    call send_data
    djnz .line4loop 

    ret      

puff:
    db "Retro Challenge 2024"
    dc "                    "
    db "  LCD RC2014 Test   "
    db "    Robert Price    "