Introduction
You'll probably want to provide a display device for your 6502 machine and the easiest way to do that is probably with a character-based LCD module. These are available in various sizes from 1 line of 16 characters to 4 lines of 40 characters. Some also have LED or electro-luminescent backlights to make them easier to read. Unfortunately LCD modules can be fairly expensive, but you can often find them at bargain prices from surplus electronic parts suppliers.
There is a common standard for LCD modules and you'd be unlucky if you had a module which behaved differently. It's a good idea to check the datasheet before you order, though this may not be possible if you're buying a cheap surplus device. Anyway, a 'standard' module will have a 14-pin interface (or 16-pin for a backlit device) and a Hitachi HD44780 controller chip. There are also HD44780-compatible controllers such as the Epson SED1278.
InterfaceHere's a schematic for connecting a standard LCD module to a 6502 system:
As you can see, these devices aren't too unfriendly to interface with. I'll run through the main points:
Here I will provide some code snippets to show you the basics of how to use an LCD module in your own software. For the complete HD44780 instruction set and detailed programming information do a quick web search and you should turn up many useful links.
First, some constants to go at the top of your program code. 'LCD' is the address at which your address decoder places the LCD module. 'LCD0' and 'LCD1' are then defined for access to the module's two registers. 'MSGBASE' is a two-byte location which is used to point to strings that you want to print on the LCD - I place it in my zero-page data area for speed.
ZPDATA EQU $00 ;zero-page data area
LCD EQU $D300 ;LCD module address
ORG LCD
LCD0 .ds 1
LCD1 .ds 1
ORG ZPDATA
MSGBASE .ds 2 ;address of message to print on LCD
This function, 'LCDBUSY', will poll the LCD module to ensure it is ready to receieve a new command. It is called by most of the following functions.
; *** Wait for LCD busy bit to clear
; registers preserved
LCDBUSY PHA
LCDBUSY0 LDA LCD0 ;read from LCD register 0
AND #$80 ;check bit 7 (busy)
BNE LCDBUSY0
PLA
RTS
Here is the function 'LINIT', which initialises the display. You will call this during your machine's reset sequence.
; *** LCD initialisation
LINIT LDX #$04 ;do function set 4 times
LINIT0 LDA #$38 ;function set: 8 bit, 2 lines, 5x7
STA LCD0
JSR LCDBUSY ;wait for busy flag to clear
DEX
BNE LINIT0
LDA #$06 ;entry mode set: increment, no shift
STA LCD0
JSR LCDBUSY
LDA #$0E ;display on, cursor on, blink off
STA LCD0
JSR LCDBUSY
LDA #$01 ;clear display
STA LCD0
JSR LCDBUSY
LDA #$80 ;DDRAM address set: $00
STA LCD0
JSR LCDBUSY
RTS
LINITMSG fcs "LCD init done. "
.byte $00
'LCDCLEAR' can be called whenever you want to clear the display.
; *** Clear LCD display and return cursor to home
; registers preserved
LCDCLEAR PHA
LDA #$01
STA LCD0
JSR LCDBUSY
LDA #$80
STA LCD0
JSR LCDBUSY
PLA
RTS
This function, 'LCDPRINT', prints a single character to the LCD. You put the character code in the accumulator before calling the function. The LCD character set is similar to ASCII, but you should refer to Peer Ouwehand's page for a full listing.
Note that this function has been written for a 40 character module (40x1 or 20x2) in which the 40 characters are stored in two non-contigous blocks of 20 in the LCD's memory. The function takes care of moving between the two blocks though it doesn't wrap round at the end. You might need to adjust this for the memory layout of different types of LCD module - see the links at the end for memory maps.
; *** Print character on LCD (40 character)
; registers preserved
LCDPRINT PHA
STA LCD1 ;output the character
JSR LCDBUSY
LDA LCD0 ;get current DDRAM address
AND #$7F
CMP #$14 ;wrap from pos $13 (line 1 char 20)...
BNE LCDPRINT0
LDA #$C0 ;...to $40 (line 2 char 1)
STA LCD0
JSR LCDBUSY
LCDPRINT0 PLA
RTS
The 'LCDHEX' function displays the value in the accumulator as a two-digit hex number. It makes use of the 'LCDPRINT' function, above.
; *** Print 2 digit hex number on LCD
; A, X registers preserved
LCDHEX PHA
LSR A ;shift high nybble into low nybble
LSR A
LSR A
LSR A
TAY
LDA HEXASCII,Y ;convert to ASCII
JSR LCDPRINT ;print value on the LCD
PLA ;restore original value
PHA
AND #$0F ;select low nybble
TAY
LDA HEXASCII,Y ;convert to ASCII
JSR LCDPRINT ;print value on the LCD
PLA
RTS
; *** Lookup table for HEX to ASCII
HEXASCII fcs "0123456789ABCDEF"
'LCDSTRING' makes use of 'LCDPRINT' to display an entire string on the LCD. Before calling the function, store the address of your string in 'MSGBASE'.
; *** Print string on LCD
; registers preserved
LCDSTRING PHA ;save A, Y to stack
TYA
PHA
LDY #$00
LCDSTR0 LDA (MSGBASE),Y
BEQ LCDSTR1
JSR LCDPRINT
INY
BNE LCDSTR0
LCDSTR1 PLA ;restore A, Y
TAY
PLA
RTS
Here is an example of how to call the 'LCDSTRING' function.
MEMMSG1 fcs "Memory test... "
.byte $00 ;terminating null for string
LDA #MEMMSG1
STA MSGBASE ;store high byte of message address
LDA #MEMMSG1/256
STA MSGBASE+1 ;store low byte of message address
JSR LCDSTRING ;print message
Last page update: December 27, 2000.