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

This is page designed, maintained and
(C)opyright 1999 by Geoff Knagge