Full native Z80 Graphics Library for the 128x64 Pixel LCD Screen
The above image demonstrates most of the graphical routines. This includes line drawing, circles, filled shapes, text and pixel drawing.
- Files in this repository
- LCD Screens
- Example Code
- Programming Guide
- INIT_LCD
- CLEAR_GBUF
- CLEAR_GR_LCD
- CLEAR_TXT_LCD
- SET_GR_MODE
- SET_TXT_MODE
- DRAW_BOX
- DRAW_LINE
- DRAW_CIRCLE
- DRAW_PIXEL
- FILL_BOX
- FILL_CIRCLE
- PLOT_TO_LCD
- PRINT_STRING
- PRINT_CHARS
- DELAY_US
- DELAY_MS:
- SET_BUF_CLEAR
- SET_BUF_NO_CLEAR
- CLEAR_PIXEL
- FLIP_PIXEL
- LCD_INST
- LCD_DATA
- SER_SYNC
- DRAW_GRAPHIC
- INV_GRAPHIC
- INIT_TERMINAL
- SEND_CHAR_TO_GLCD
- SEND_STRING_TO_GLCD
- SEND_A_TO_GLCD
- SEND_HL_TO_GLCD ;Send register HL to the LCD
- SET_CURSOR
- GET_CURSOR
- DISPLAY_CURSOR
- Future Work
- lcd_128x64_glib.z80 - Z80 Graphics library for the ST7920 Controller
- lcd_3d_demo.z80 - 3D Frame rotation program
- lcd_mad_program.z80 - MAD Magazine face drawing
- lcd_maze_gen.z80 - Maze Generation program
- ST7920.pdf - ST7920 Datasheet
- QC12864B.pdf - LCD Screen Datasheet
- 3D_Fundamentals.JPG - Amstrad Basic 3D Frame article
- add_on_PCB directory - Add-on Board for the TEC-1 Computer
There are a few variants of these LCD screens, but all typically use the ST7920 LCD Controller. The LCD Screen that I used is the QC12864B. This screen has two ST7921 Panels (128 x 32) stacked one above the other. Other LCD boards might not do this. If so, the PLOT_TO_LCD function will need to be modified. (future work)
These screens have Graphics Display RAM (GDRAM) and Display Data RAM (DDRAM) areas. GDRAM is for drawing pixels and DDRAM is for displaying text or characters. Both RAM areas __can be__ displayed at the same time.The Pinout and connection to the Z80 for the QC12864B board is as follows:
Pin | Name | Description | Serial1 | Parallel |
---|---|---|---|---|
1 | VSS | Ground | GND | GND |
2 | VDD | Power | 5v | 5v |
3 | V0 | Contrast | N/A | N/A |
4 | D/I | IR/DR (CS) | 5v | A74 |
5 | R/W | R/W (SID) | D0 | RD (inverted)2 |
6 | E | Enable (SCLK) | D1 | Port 7 4 (inverted)2 |
7 | DB0 | Data | N/A | D0 |
8 | DB1 | Data | N/A | D1 |
9 | DB2 | Data | N/A | D2 |
10 | DB3 | Data | N/A | D3 |
11 | DB4 | Data | N/A | D4 |
12 | DB5 | Data | N/A | D5 |
13 | DB6 | Data | N/A | D6 |
14 | DB7 | Data | N/A | D7 |
15 | PSB | Serial/Para | GND | 5v |
16 | NC | |||
17 | RST | Reset | RST | RST |
18 | VEE | LCD Drive | N/A | N/A |
19 | A | Backlight | 5v/NC | 5v/NC |
20 | K | Backlight | GND/NC | GND/NC |
1 Three communication lines are need for Serial SPI transfer. Pin 4 is Chip Select (CS). As there is only one peripheral connected, this can just be tied high at 5v. Pin 5 is Serial Input Data (SID) and Pin 6 is Serial Clock (SCLK). Pin 5 and 6 are to be latched via a 74HC74 Dual D-Flip Flop. The inputs to the latch are D0 (SID) and D1 (SCLK). Have a look at the schematic below.
2 A CMOS CD4049 Inverter Buffer is needed to invert the input
3 Some 128x64 LCD Screens have different Pinouts than in the table above.
4 I use Port 7
and A7
to connect the LCD screen to the TEC computer. You will need to modify these two constants if connecting the LCD in a different way. See the header part of the lcd_128x64_glib.z80 file to modify these values.
To see the library working, have a look at the example code provided. The LDC_3D_demo.z80 has a DISPLAY_MENU
label that displays the above picture. Look at this piece of code for better understanding.
Also, see the library and these files in action on YouTube and YouTube!
Thanks to PCBWay for sponsoring this video. ** For $5 off your first order at PCBWay, click here **. PCBWay Printed Circuit Boards the Easy Way.
The Graphics Library has routines that interact with the LCD Screen. Routines are called via a Jumpblock. These routines are at the start of the Graphics Library. By default the library is placed at location 0x3000
. If this location is changed, the start locations of the functions are also to be changed.
For example, to initalise the LCD, do something like this:
CALL 3000H
Or to make it more readable create a label and do this:
G_INIT_LCD: EQU 3000H
CALL G_INIT_LCD
In summary, here are the routines and their default address locations:
Address | Routine | Description |
---|---|---|
3000H | INIT_LCD | Initalise the LCD |
3003H | CLEAR_GBUF | Clear the Graphics Buffer |
3006H | CLEAR_GR_LCD | Clear the Graphics LCD Screen |
3009H | CLEAR_TXT_LCD | Clear the Text LCD Screen |
300CH | SET_GR_MODE | Set Graphics Mode |
300FH | SET_TXT_MODE | Set Text Mode |
3012H | DRAW_BOX | Draw a rectangle between two points |
3015H | DRAW_LINE | Draw a line between two points |
3018H | DRAW_CIRCLE | Draw a circle from Mid X,Y to Radius |
301BH | DRAW_PIXEL | Draw one pixel at X,Y |
301EH | FILL_BOX | Draw a filled rectangle between two points |
3021H | FILL_CIRCLE | Draw a filled circle from Mid X,Y to Radius |
3024H | PLOT_TO_LCD | Display the Graphics Buffer to the LCD Screen |
3027H | PRINT_STRING | Print Text on the screen in a given row |
302AH | PRINT_CHARS | Print Characters on the screen in a given row and column |
302DH | DELAY_US | Microsecond delay for LCD updates |
3030H | DELAY_MS | Millisecond delay for LCD updates |
3033H | SET_BUF_CLEAR | Clear the Graphics buffer on after Plotting to the screen |
3036H | SET_BUF_NO_CLEAR | Retain the Graphics buffer on after Plotting to the screen |
3039H | CLEAR_PIXEL | Clear one pixel at X,Y |
303CH | FLIP_PIXEL | Invert one pixel at X,Y |
303FH | LCD_INST | Send a parallel or serial instruction to LCD |
3042H | LCD_DATA | Send a parallel or serial data to LCD |
3045H | SER_SYNC | Send serial synchronize byte to LCD |
Here is the detailed routine descriptions with examples
Initalise the LCD Screen. Needed to be called before any other routine
- Entry: No conditions
- Exit: All registers corrupt
CALL INIT_LCD ;Initalise the LCD Screen
Clear the Graphics Buffer. The Graphics Buffer or GBUF is the internal memory area that contains pixel data for the LCD. The drawing routines write to the GBUF. Once all pixels are set, this buffer is then plotted to the LCD with the PLOT_TO_LCD Routine. Clearing the GBUF is a good way to ensure the pixel area is empty.
- Entry: No conditions
- Exit: All registers corrupt
CALL CLEAR_GBUF ;Clears the Graphics Buffer
Clear the Graphics LCD Screen. This routine clears the GDRAM or Graphics screen on the LCD.
- Entry: No conditions
- Exit: All registers corrupt
CALL CLEAR_GR_LCD ;Clears the Graphics LCD Screen
Clear the Text LCD Screen. This routine clears the DDRAM or Text screen on the LCD.
- Entry: No conditions
- Exit: All registers corrupt
CALL CLEAR_TXT_LCD ;Clear the Text LCD Screen
Set the LCD to Graphics Mode. This routine puts the LCD in Graphics mode (Extended Instructions) and any further instructions to the LCD will be for the graphics screen. It only needs to be called once if multiple graphics routines are used.
- Entry: No conditions
- Exit: AF, DE corrupt
CALL SET_GR_MODE ;Set Graphics Mode
Set the LCD to Text Mode. This routine puts the LCD in Text mode (Basic Instructions) and any further instructions to the LCD will be for the text screen. It only needs to be called once if multiple text routines are used.
- Entry: No conditions
- Exit: AF, DE corrupt
CALL SET_TXT_MODE ;Set Text Mode
Draws a single-line box between two points X1,Y1 and X2,Y2.
- Entry:
- B = X1-coordinate (0-127)
- C = Y1-coordinate (0-63)
- D = X2-coordinate (0-127)
- E = Y2-coordinate (0-63)
- Exit: AF, HL corrupt
LD BC, 0020H ;X0, Y0
LD DE, 7F3FH ;X1, Y1
CALL DRAW_BOX ;Draw a outline box from X0,Y0 to X1,Y1
Draws a straight line between X1,Y1 and X2,Y2. Uses Bresenham Line drawing algorithm
- Entry:
- B = X1-coordinate (0-127)
- C = Y1-coordinate (0-63)
- D = X2-coordinate (0-127)
- E = Y2-coordinate (0-63)
- Exit: All registers corrupt
LD BC, 0010H ;X0, Y0
LD DE, 7F30H ;X1, Y1
CALL DRAW_LINE
Draws a circle from a mid point to a radius. Uses Bresenham Circle drawing algorithm
- Entry:
- B = Mid-X-coordinate (0-127)
- C = Mid-Y-coordinate (0-63)
- E = Radius (1-63)
- Exit: All registers corrupt
LD BC, 0818H ;Mid X, Mid Y
LD E, 08H ;Radius
CALL DRAW_CIRCLE
Draws a single Pixel.
- Entry:
- B = X-coordinate (0-127)
- C = Y-coordinate (0-63)
- Exit: AF, HL corrupt
LD BC, 4020H ;X,Y
CALL DRAW_PIXEL
Draws a filled box between X1,Y1 and X2,Y2.
- Entry:
- B = X1-coordinate (0-127)
- C = Y1-coordinate (0-63)
- D = X2-coordinate (0-127)
- E = Y2-coordinate (0-63)
- Exit: AF, HL corrupt
LD BC, 0020H ;X0, Y0
LD DE, 7F3FH ;X1, Y1
CALL FILL_BOX ;Draw a filled box from X0,Y0 to X1,Y1
Draws a filled circle from a mid point to a radius. This routine iteratively calls the DRAW_CIRCLE routine increasing the radius until is equals the register E. There might be gaps in the filled circle, but hey it looks just like what you get on a BASIC program.
- Entry:
- B = Mid-X-coordinate (0-127)
- C = Mid-Y-coordinate (0-63)
- E = Radius (0-63)
- Exit: All registers corrupt
LD BC, 1018H ;Mid X, Mid Y
LD E, 08H ;Radius
CALL FILL_CIRCLE
This routine draws the Graphics Buffer or GBUF to the Graphics LDC screen. It is usually called after one of the drawing routines is called.
- Entry: No conditions
- Exit: All registers corrupt
CALL PLOT_TO_LCD ;Display the Graphics Buffer to the LCD Screen
Prints ASCII text on the screen on a given row. There are 4 text rows on the LCD screen. The text to be displayed is to be defined directly after the CALL routine and is to be terminated with a zero.
Here are the 128 characters that are available. Conveniently, Alphanumeric characters align with the ASCII table.
- Entry:
- A = row number (0-3)
- Text = "String" on the next line, terminate with 0
- Exit: All registers corrupt
LD A,2
CALL PRINT_STRING
DB 02H, " This Text ", 1BH ,00H
; This will display a smiley face, "This Text"
; and a left arrow on row 2 of the LCD screen.
Print Characters on the screen in a given row and column. This routine is similar to the one above but character row and column placement can be made. Characters to be printed are to be terminated with a zero.
Note: Even though there are 16 columns, only every second column can be written to and two characters are to be printed. IE: if one character is to be printed in column 2, then set B=0 and print " x", putting a space before the character.
- Entry:
- B = column (0-7)
- C = row (0-3)
- HL = start address of text data
- Exit: All registers corrupt. HL will be at the end of the text data table.
LD HL, MENU_DATA ;Load HL with Menu data table
LD BC, 0102H ;Column 1, Row 2
CALL PRINT_CHARS ;Display Text
...
MENU_DATA:
DB "Hello!",0
Delay loop for LCD to complete its instruction. Every time a command is sent to the LDC, it requires a small amount of time to complete that operation. IE: setting extended instruction mode. Time needed for most operations is defined in the LDC specification. It is usually around 72us. This routine is used internally, but can also be used here. The delay time depends on how fast the CPU is running at. A variable V_DELAY_US
can be changed to suit your LDC setup. I found that my LDC can handle a delay of 0004H
.
This routine will delay the cpu by V_DELAY_US
and can be used if outputting instructions to the LDC directly.
- Entry: No conditions
- Exit: DE, AF corrupt
LD A, 02H ;Home command
OUT (LCD_IR), A ;Move the cursor to the top of LCD
CALL DELAY_US ;Delay the CPU by V_DELAY_US
This is the same as the above routine, but the delay can be software controlled.
- Entry: DE = delay value
- Exit: DE, AF corrupt
LD A, 01H ;Clear command
OUT (LCD_IR), A ;Clear the LCD screen
LD DE, 0050H ;Longer Delay
CALL DELAY_MS ;Delay the CPU by DE
On every PLOT_TO_LCD, Clear the graphics buffer GBUF. Calling this routine will clear the graphics buffer on every draw to LCD. This is useful if doing animation that require a new drawing to be display on every plot or frame.
- Entry: No conditions
- Exit: AF corrupt
CALL SET_BUF_CLEAR ;Clear GBUF on plot
Do not clear the graphics buffer on every PLOT_TO_LCD. Calling this routine will not clear the graphics buffer on every draw to LCD. This is useful adding graphics data to an existing drawing. This is used in the MAD example, where lines are being continually drawn to the screen until the whole picture is complete.
- Entry: No conditions
- Exit: AF corrupt
CALL SET_BUF_NO_CLEAR ;Do not clear GBUF on plot
Removes or clears a single Pixel.
- Entry:
- B = X-coordinate (0-127)
- C = Y-coordinate (0-63)
- Exit: AF, HL corrupt
LD BC, 4020H ;X,Y
CALL CLEAR_PIXEL
Inverts a single Pixel. If the Pixel is on, it will turn off and if the Pixel is off, it will turn on.
- Entry:
- B = X-coordinate (0-127)
- C = Y-coordinate (0-63)
- Exit: AF, HL corrupt
LD BC, 4020H ;X,Y
CALL FLIP_PIXEL
Send an instruction to the LCD. This routine will send the value of Register A
to the Instruction Register of the LCD Screen. The routine is universal regardless of Serial or Parallel connection.
For Serial connection, this will also send the Synchronization Byte.
- Entry:
- A = Value to send
- Exit: AF, DE (Parallel only) corrupt
LD A, 38H ; Clear Screen
CALL LCD_INST
Send data to the LCD. This routine will send the value of Register A
to the Data Register of the LCD Screen. The routine is universal regardless of Serial or Parallel connection.
For Serial connection, a call to SER_SYNC is to be done prior. This is because only one Synchronization Byte is needed for multiple (up to 256) Data bytes.
- Entry:
- A = Value to send
- Exit: AF, DE (Parallel only) corrupt
;Parallel example
LD A, "B" ; The letter B
CALL LCD_DATA
Send a Serial Synchronization Byte to the LCD. This routine is only needed for Serial connection AND prior to an LCD_DATA call. The Register A
must be 02H
- Entry:
- A = 02H
- Exit: AF corrupt
LD A, 02H
CALL SER_SYNC ;Data Block Sync
LD A, "T" ;Letter T
CALL LCD_DATA
LD A, "E" ;Letter E
CALL LCD_DATA
LD A, "C" ;Letter C
CALL LCD_DATA
Draw an ASCII character or Sprite to the LCD at the current cursor. ASCII characters are 6x6 or 5x5 Pixels and most have a gap to the left and bottom for spacing. PLOT_TO_LCD is still required to be called after all graphics have been drawn.
Graphics data is in the format of up to 16 bytes across and 64 bytes down, where a BIT
set will indicate a pixel to be drawn. If graphics is less than 8 bits wide, then bits are read from the least significant bit.
- Entry:
- A = ASCII number or if A=0 Then
- HL = Address of graphic data
- B = width of graphic in pixels (1-128)
- C = height of graphic in pixels (1-64)
- A = ASCII number or if A=0 Then
- Exit: All Corrupt
LD A, 00H ;Custom graphic
LD HL, PICTURE ;Data table address
LD B, 16 ;B=16 pixels wide
LD C, 08 ;C=8 pixels down
CALL DRAW_GRAPHIC ;Draw the GLCD Buffer
CALL PLOT_TO_LCD ;Display on GLCD
PICTURE:
.db 10000011b,11000001b
.db 10000100b,00100001b
.db 10001010b,01010001b
.db 10001000b,00010001b
.db 10001010b,01010001b
.db 10001001b,10010001b
.db 10000100b,00100001b
.db 10000011b,11000001b
This example will display this image from the current cursor position
Inverse graphics printing. Calling this routine will TOGGLE the inverse drawing flag. The initial state is normal. If in inverse mode, a pixel drawn using the DRAW_GRAPHIC routine is displayed if a BIT
is not set.
- Exit: AF corrupt
CALL INV_GRAPHIC ;Toggle inverse flag
Initialize the LCD for terminal emulation. This routine is to be called before any TERMINAL routine is called. It will set up the graphics and scroll buffers. It also Clears the GBUF, sets cursor to top left and displays cursor. This routine will as call INIT_LCD.
- Exit: All Corrupt
CALL INIT_TERMINAL ;Start terminal emulation
Send or handle ASCII characters to the GLCD screen. This routines displays ASCII characters to the GLCD screen and handles some special control characters. It also handles scrolling history of 10 lines. Characters are drawn at the current cursor position. The Cursor will increment when a character is drawn. Character will automatically be displayed on the LCD.
Some special characters are:
-
CR / 0DH = will move the cursor down and reset it column
-
LF / 0AH = is ignored
-
BS / 08H = will delete the character at the cursor and move cursor back one
-
HT / 09H = will TAB 4 spaces
-
UP / 05H = will scroll up one line if any
-
DN / 06H = will scroll down one line if any
-
Entry:
- A = ASCII character to send to the GLCD screen. OR
- A = 0, draw the cursor only
-
Exit: All Corrupt
LD A,65 ;ASCII 'A'
CALL SEND_CHAR_TO_GLCD ;Display on LCD
LD A,0DH ;Carriage Return
CALL SEND_CHAR_TO_GLCD ;Do a CR on the LCD
Send a string of characters to the GLCD. Prints a string pointed by DE. It stops printing and returns when either a CR is printed or when the next byte is the same as what is in register A.
- Entry:
- DE = address of string to print
- A = character to stop printing.
- Exit: All Corrupt
LD DE,TEXT ;Text to display
XOR A ;Terminate with 0
CALL SEND_STRING_TO_GLCD
TEXT: .db "Hello World!",0
Display the register A in ASCII on the GLCD at the current cursor
- Entry:
- A = value to convert and display
LD A,7BH ;Set A=7BH
CALL SEND_A_TO_GLCD ;Display "7B" on LCD
Display the register HL in ASCII on the GLCD at the current cursor
- Entry:
- HL = value to convert and display
LD HL,0A6C0H ;Set HL=0A6C0H
CALL SEND_HL_TO_GLCD ;Display "A6C0" on LCD
Set the Graphic cursor position for Terminal Emulation. Update is ignored if either X,Y input is out of bounds.
- Entry:
- B = X position in pixels (0-127)
- C = Y position in pixels (0-63)
- Exit: AF corrupt
LD BC,4020H ;Set cursor to middle of screen
CALL SET_CURSOR
Get the current cursor position
- Exit:
- B = X position in pixels (0-127)
- C = Y position in pixels (0-63)
CALL GET_CURSOR
Turn the cursor ON or OFF. Default is Cursor ON
- Entry:
- A = 0, Turn cursor on, A = non zero, Turn cursor off
LD A,1
CALL DISPLAY_CURSOR ;Turn cursor off
Displaying Text on this screen is clumsy as text can only be placed at defined locations. I plan to make a graphics font of 128 characters that is smaller in size, possibly 5x7 and can be placed anywhere on the screen.
Some LCD screens have a slightly different way data is written to them in terms of how the screens are laid out. The QC12864B as two ST9721 screens placed on top of each other, but are connected in series. Plotting to the LCD routine will need to be changed for other LCD screen layouts.