LCD Interface module
Below is the latest version of our LCD interface. Messages are passed by indexing the screen array with register C, and passing it to LCD_setmsg. The interrupt is called every 40 microseconds to update any changes that have been made to the desired output, and a counter is used to determine the time to wait before scrolling the screen.
This module relies on the presence of the data module somewhere in the program. Port initialisation is assumed.
Download:
; ///////////////////////////////////////////////////////////
; // LCD Interface Module
; // (c) 1999 Geoff Knagge, Andrew Carr, Mark Lynn
; // last modified : 22/10/1999
; //
; // Methods which are intended for use outside this module:
; // All methods preserve the registers.
; //
; // LCD_init - sets up the LCD module
; // LCD_reset - Tells the LCD to refresh its display
; // LCD_SetMsg - INPUT: C = screen number (00= first message)
; // LCD_Interrupt- updates the LCD display where necessary
; ///////////////////////////////////////////////////////////
LCD_sLine: .BYTE 0 ; Which entry of the list of scrolling lines
; is to be displayed on the top scrolling
; line
LCD_theseLines: .BYTE 0,0,0,0
; One byte for each line of the display.
; each points to the message that is
; currently being shown on the corresponding
; line of the display
LCD_scrollDelay = 38 ; How many times LCD_Interrupt is to be
; called before it scrolls the screeen
LCD_delayCount: .BYTE 0 ; Variable used to work count scrollDelay
LCD_table: .WORD 0 ; points to a screen to be displayed, in the
; following format :
; * 4 bytes, each representing the
; message code for each line. A zero
; indicates a scrolling line.
; * 1 byte, the number of scrolling
; lines
; * A list of bytes representing the
; lines to be scrolled
LCD_ws: .byte 0 ; which screen
;/////////////////////////////////////////////
LCD_Wait: ; Checks the LCD display and waits until it is
; not busy. No inputs or return values, but it will
; destroy any data latched on the outputs of port 2
push af
call io_2bRead
; output control signal 01H (read status, enable low)
mvi A,01H
call io_wtlcd ; write to low 3 bits of lcd control port
; output control signal 05H (read status, enable high)
mvi A,05H
call io_wtlcd ; write to low 3 bits of lcd control port
LCD_L4:
; read input
in lcdport
ani 80H ; mask everything execpt the busy signal
jnz LCD_L4 ; if ready is high, keep waiting
; output control signal 00H to send enable low
mvi a,0
call io_wtlcd ; write to low 3 bits of lcd control port
; set Port 2B to output mode
call io_2bWrite
pop af
ret
;///////////////////////////////////////////////////////////
LCD_Write: ; writes register A (the data) to port 2B,
; and register B (control signals) to 1C
call LCD_Wait
LCD_w2: ; This entrypoint is used by LCD_init, when
; we're unable to use the LCD's busy flag
push af
out lcdport ; output data to port 2B
mov A,B ; copy control signals to A
ani 0FBH ; mask bit 2 - enable signal low
call io_wtlcd ; write to low 3 bits of lcd control port
ori 04H ; Set bit 2 - enable signal high
call io_wtlcd ; write to low 3 bits of lcd control port
xra A ; all signals set low (to set enable low)
call io_wtlcd ; write to low 3 bits of lcd control port
pop af
RET
;////////////////////////////////////////////////////////////
LCD_Delay: ; waits for 15ms... an overkill, but we only
; use it a couple of times. No inputs, and
; registers are preserved
push bc
push af
mvi B,0AH
mvi C,77H
mvi A,0
LCD_L9:
dcx BC
cmp b
jnz LCD_L9
cmp c
jnz LCD_L9
pop af
pop bc
ret
;/////////////////////////////////////////////////////////////
LCD_Init:
call LCD_Delay ; wait 15ms for LCD to power up... not really
; necessary, but best to be safe
mvi A,30H
mvi B,0H
call LCD_w2 ; send control 00, data 30H
call LCD_Delay ; need to use our own delay at this stage
call LCD_w2 ; send control 00, data 30H
call LCD_Delay
mvi A,38
call LCD_w2 ; send control 00, data 38H
; we can now use the busy flag checker in LCD_Write... no
; need for using our own delay anymore.
mvi B,0
mvi A,38
call LCD_Write ; send control 00, data 38H
mvi A,0CH ; display on, no visible cursors
call LCD_Write ; send control 00, data 0FH
mvi A,01
call LCD_write
mvi c,0 ; set startup message
call lcd_SetMsg
call lcd_reset
ret
;/////////////////////////////////////////////////////////////
LCD_addresses: ; starting DDRAM addresses for each LCD line
.BYTE 00, 40, 14, 54
LCD_linewrite: ; writes the message from the message bank
; pointed to by register A, and displays it
; on the line in register C (00H is line 1)
PUSH HL
PUSH BC
PUSH DE
PUSH AF
LXI DE, LCD_addresses ; point DE to DDRAM address for line 1
MOV B,A ; B now contains the message code
MOV A,C ; A contains the target line of the display
LCD_L5:
CPI 0 ; A=0? (used because we can't guarantee that
; the Z flag is valid here...)
JZ LCD_J6 ; yes, DE points to address of target line
INX DE ; no, point DE to the address of next line
DCR A
JMP LCD_L5
LCD_J6: ; remember that B contains the message code
LDAX DE ; A = DDRAM address of LCD line to write
MOV C, A ; C = A
LXI HL, MessageBank ; point HL to first message in array
LXI DE, MsgLength ; DE = the length of array messages
LCD_L6:
DCR B ; B acts as a counter to find the message
JZ LCD_J7 ; does HL point to the right message?
DAD DE ; no, point it to the next one
JMP LCD_L6
LCD_J7: ; HL points to the desired message, B = 0
mov d,h
mov e,l ; DE=HL
Mov A,C ; A = DDRAM address of required LCD line
ORI 80 ; set bit 7 (code for DDRAM writing)
CALL LCD_Write ; Tell LCD that we're going to write text
MVI B,2 ; Setup control signals for text output
MVI C,0 ; initialise counter, for characters written
LCD_L7:
LDAX de ; LOAD A CHARACTER
CALL LCD_Write ; WRITE IT
INR C
inx de ; next character
MVi A,MsgLength
CMP C ; have all the characters been written?
JNZ LCD_L7 ; no, do another
POP AF
POP DE
POP BC
POP HL
RET
;/////////////////////////////////////////////////
LCD_reset: ; used when one of the message lines are modified and
; need updating on the LCD display fills the
; theseLines bytes with zeroes.
; no inputs, and registers are preserved.
PUSH DE ; PRESERVE REGISTERS
PUSH AF
PUSH BC
LXI DE,LCD_theselines ; POINT DE TO THESELINES ARRAY
MVI C,4 ; COUNTER = 4
MVI A,0 ; WHAT TO STORE IN EACH ARRAY ELEMENT
LCD_L1:
STAX DE ; STORE A IN ARRAY ELEMENT
INX DE ; NEXT ELEMENT
DCR C ; decrement counter
JNZ LCD_L1 ; IF NOT THE END, RELOOP
POP BC
POP AF
POP DE
RET
;//////////////////////////////////////////////////
LCD_SetMsg: ; Tells the LCD driver to change the set of lines
; being displayed to set specified by register C.
; C indexes an entry in the lookup table ScrnTable,
; which then points to the following data structure
; * 4 bytes, each representing the message code
; for each line. A zero indicates a scrolling
; line.
; * 1 byte, the number of scrolling lines
; * A list of bytes representing the lines to
; be scrolled
; C = 00H refers to the first message in the table
PUSH HL
PUSH BC
PUSH AF
mov a,c
sta lcd_ws
LXI HL,ScrnTable ; point HL to the first table entry
MVI B,0 ; BC = message set number
DAD BC ; Required entry is (start) + (BC)*2
DAD BC ; HL NOW POINTS TO THE correct look-up entry
mov c,m
inx hl
mov b,m
mov h,b
mov l,c ; HL points to the message data structure
SHLD LCD_table ; STORE IT IN THE POINTER
MVI A,0
LXI HL, LCD_sLine ; reset the scrolling line counter
MOV M,A
LXI HL, LCD_delayCount ; reset the scrolling delay counter
MOV M,A
POP AF
POP BC
POP HL
RET
;///////////////////////////////////////////////////
LCD_Interrupt:
PUSH HL
PUSH DE
PUSH BC
PUSH AF
; if this routine has been called the right amount of times,
; scroll the non-static lines up one row... by incrementing
; the pointer to the top scolling line
LXI HL,LCD_delayCount ; how many times this has been called
mov a,m
INR a ; increment that amount
mov m,a ; and save it for next time
LCDscp:
mvi b, LCD_scrolldelay ; the required number of times
cmp b
JNZ LCD_J1 ; have we reached that number?
XRA A ; yes, reset to zero
mov m,a ; and store it
; if that number of times was reached, we then need to
; increment to scrolling pointer, wrapping around to zero
; if necessary
LXI HL, LCD_sLine ; HL points to the counter
mov a,m ; get the top scrolling line counter
INR A ; and increment it
PUSH HL ; now check the current message set and see
LHLD LCD_table ; if our pointer equals the amount of lines
INX HL ; defined in the scrolling list. If so, we
INX HL ; need to reset our pointer back to the first
INX HL ; scrolling line
INX HL
CMP M ; byte 5 = the number of defined scroll lines
POP HL ; this doesn't affect any flags
JNZ LCD_J2 ; if we're below the limit, do nothing
XRA A ; otherwise reset to zero.
LCD_J2:
mov m,a ; save the new top scrolling line counter
LCD_J1: LXI HL,LCD_sLine
mov a,m ; load the counter of the top scrolling line
LHLD LCD_table ; HL POINTS TO MESSAGE DEFINITION
MOV E,A ; E iS "NEXT SCROLLING LINE TO DISPLAY"
MVI B,0
MVI D,0
MVI C,0 ; FOR COUNTER = 0 TO 3
LCD_L2:
PUSH HL
PUSH BC
DAD BC
MOV B,H
MOV C,L
LDAX BC ; GET CODE FOR THIS LINE FROM THE MESSAGE SET
POP BC
POP HL
CPI 0 ; IS IT A SCROLLING LINE?
JNZ LCD_J3 ; NO, PROCEED USING THIS CODE
; YES, FIND THE CODE FROM THE LIST
; OF SCROLLING LINES
PUSH HL
PUSH DE
INX HL; MOVE HL TO POINT TO START OF SCROLLING LINES LIST
INX HL; 5 INX HL'S ARE MORE EFFICIENT THAN OTHER METHODS OF
INX HL; DOING THIS CONSIDERING THAT ALL REGISTERS ARE BEING
INX HL; USED...
INX HL;
DAD DE; HL NOW POINTS TO THE REQUIRED ENTRY
MOV D,H
MOV E,L
LDAX DE; GET IT!
POP DE
POP HL
LCD_J3: ; A NOW CONTAINS THE CODE FOR THE LINE
PUSH HL
PUSH DE
LXI HL, LCD_theseLines
DAD BC ; HL POINTS TO CORRESPONDING LINE'S Code
CMP M ; ARE THEY THE SAME?
JZ LCD_J4 ; YES, NO NEED TO DO ANYTHING MORE
; NO, UPDATE THIS LINE
MOV D,H
MOV E,L ; DE = HL
STAX DE ; theselines[bc] = code for this line
CALL LCD_LINEwrite ; A = LINE CODE, C=LINE OF DISPLAY
LCD_J4: POP DE
POP HL
INR E; ; increment counter of next scrolling line
MOV A,E; ; A = next scrolling line to display
PUSH HL ; HL points to the message set definition
INX HL;
INX HL;
INX HL;
INX HL;
CMP M ; DOES THE thisSLine = NUMBER OF
; SCROLLING LINES?
POP HL ; AFFECTS NO FLAGS
JNZ LCD_J5 ; NO, DO NOTHING
MVI E,0 ; RESET thisSLine TO 0
LCD_J5:
INR C ; increment counter
MOV A,C
CPI 4 ; HAVE 4 LINES BEEN PROCESSED?
JNZ LCD_L2 ; IF NOT, GO BACK AND DO ANOTHER
mvi a,ISR_LCD ; reset LCD's ISR bit
out INT_Port
POP AF ; restore all registers
POP BC
POP DE
POP HL
EI
RET
|