$TITLE(LCD-control) $NOMOD51 $NOLIST $INCLUDE(REG652.PDF) $LIST ;***************************************************************************** ;* This file contains the routines for controlling an LCD-module * ;* with an MCS-51 compatibel microcontoller * ;* * ;* All routines are written by Sven Rymenants * ;* * ;* I disclaim everything. The contens of this listing might be totally * ;* inaccurate, inappriate, misguided, or otherwise perverse - execpt for my * ;* name (hopefully I got that right) * ;* * ;* CONNECTION DIAGRAM * ;* All routines are written for controlling the module in a 4-bit mode. * ;* The lower four bits are don't care, so musn't be connected. * ;* * ;* /----------+ * ;* c | +----------------------------------+ * ;* o D0 o----------------o DB4 | * ;* n D1 o----------------o DB5 LL CCC DDDDD | * ;* t D2 o----------------o DB6 LL CC C DD DD | * ;* r D3 o----------------o DB7 LL CC DD DD | * ;* o D4 o----------------o RS LL CC C DD DD | * ;* l D5 o----------------o E LLLLL CCC DDDDD | * ;* l D6 o----------------o R/W | * ;* e D7 o---- N.C. +----------------------------------+ * ;* r | * ;* /----------+ * ;* ;***************************************************************************** ;***************************************************************************** ;* Constants used in the LCD-driver routines * ;***************************************************************************** ;Registers REG6 EQU 6h ;memory location of R6 (bank 0) REG7 EQU 7h ;memory location of R7 (bank 0) ;Cursor control CURSON EQU 0010b ;turn the cursor on CURSBLINK EQU 0001b ;set the blink mode CURSCOMM EQU 128d ;command for the cursor ;display commands CG_ADDR EQU 01000000b ;character generator address DD_ADDR EQU 10000000b ;display data address F_SET EQU 00100000b ;function set DATA_4 EQU 00000000b ; 4-bit data length DATA_8 EQU 00010000b ; 8-bit data length LINE_1 EQU 00000000b ; 1 line LINE_2 EQU 00001000b ; 2 lines FONT_5x7 EQU 00000000b ; 5x7 font FONT_5x10 EQU 00000100b ; 5x10 font CURS_DISP EQU 00010000b ;cursor/display shift C_MOVE EQU 00000000b ; cursor move D_SHIFT EQU 00001000b ; display shift SHIFT_L EQU 00000000b ; shift to the left SHIFT_R EQU 00000100b ; shift to the right ONOFF_SET EQU 00001000b ;on/off control DISP_ON EQU 00000100b ; display on CURS_ON EQU 00000010b ; cursor on BLINK_ON EQU 00000001b ; blink cursor on ENTRY_SET EQU 00000100b ;entry mode set C_INC EQU 00000010b ; increment C_DEC EQU 00000000b ; decrement LCDSHIFT EQU 00000001b ; display shift CHOME EQU 00000010b ;cursor home CLEARDISP EQU 00000001b ;clear the display ;***************************************************************************** ;* Constants used in the demo-program * ;***************************************************************************** MUARTADDR EQU 0F000h ;address of the MUART P1CON EQU MUARTADDR+0100b ;location of port1 config register PORT1 EQU MUARTADDR+1000b ;port1 data register RAM EQU 0A100h ;program start ;***************************************************************************** ;* This is the demo-program source. * ;* Normaly you would see this on the module: * ;* +-------------------------------/ /--+ * ;* |___LCD demo by Sven RYMENANTS | * ;* | ---11 010 0A 0*01 | * ;* +-------------------------------/ /--+ * ;* \---/ \------------/ * ;* datapointer register 1 The ___ contains an * ;* counter counter arrow, degrees and smiley * ;***************************************************************************** ORG RAM ACALL INITMUART ;initialise the MUART-chip ACALL INITLCD ;do the same with the LCD ;load an arrow in the first character MOV DPTR,#ARROW ;pointer to the data MOV A,#1d ;the first character ACALL LCDSIGN ;transfer the arrow to the LCD ;load degrees sign in the second character MOV DPTR,#DEGREES ;pointer to the data MOV A,#2d ;the first character ACALL LCDSIGN ;transfer the degrees to the LCD ;load a smiley-face in the thirth character MOV DPTR,#SMILEY ;pointer to the data MOV A,#3d ;the first character ACALL LCDSIGN ;transfer the degrees to the LCD ACALL LCDCLR ;clear the LCD MOV DPTR,#STR0 ;write the first string to the LCD ACALL LCDSTR ;.... MOV A,#CURSCOMM ;turn off the cursor ACALL LCDCURSOR ;.... MOV R1,0d ;fill the counter with 0 MOV DPTR,#0000d ;fill the DPTR-counter with 0 PUSH DPL ;make a backup of the datapointer PUSH DPH LOOP: MOV A,#72d ;set the cursor on location 72d ACALL LCDCURSOR ; this should be the second line POP DPH ;get the old datapointer POP DPL INC DPTR ;increase the counter PUSH DPL ;make a backup of the datapointer PUSH DPH MOV B,#'-' ;all leading characters are a '-' ACALL LCDDPTR ;write the datapointer to the screen MOV A,#78d ;go a littlebit further on the ACALL LCDCURSOR ; display MOV A,R1 ;move R1 to the accumulator ACALL LCDDEC ;send it in a full format to the LCD MOV DPTR,#STR1 ;leave some space by sending a string ACALL LCDSTR ; to the module MOV A,R1 ;move R1 to the accumulator ACALL LCDHEX ;send it in hex to the LCD MOV DPTR,#STR1 ;leave some space by sending a string ACALL LCDSTR ; to the module MOV A,R1 ;divide the counter value by 100 MOV B,#100d DIV AB MOV B,#1d ;show only 1 character (0, 1 or 2) ACALL LCDDECLEN ;send it to the LCD MOV A,#'*' ;write a star... ACALL LCDCHAR ; ...to the LCD MOV A,R1 ;let's calculate another value... MOV B,#10d DIV AB MOV B,#2d ;show 2 characters ACALL LCDDECLEN ;send it to the LCD INC R1 ;increment the counter SJMP LOOP ;again STR0: DB 01d, 02d, 03d, 'LCD demo by Sven RYMENANTS',000h STR1: DB ' ',000h ;***************************************************************************** ;* Initalisation of the MUART * ;***************************************************************************** INITMUART: MOV DPTR,#P1CON MOV A,#0FFh MOVX @DPTR,A RET ;***************************************************************************** ;* Initialisation routine for the LCD-module * ;***************************************************************************** INITLCD: MOV A,#0011b ;function set: 8-bit mode ACALL STROBE ;send data to the display ACALL DELAY5m ;wait for 5ms MOV A,#0011b ;function set: 8-bit mode ACALL STROBE ;send data to the display ACALL DELAY80u ;wait for 80us MOV A,#0011b ;function set: 8-bit mode ACALL STROBE ;send data to the display ACALL DELAY5m ;wait for 5ms MOV A,#0010b ;function set: 4-bit mode ACALL STROBE ;send data to the display MOV A,#F_SET OR DATA_4 OR LINE_2 OR FONT_5x7 ACALL LCDINSTR ;send instruction to the display MOV A,#ONOFF_SET OR DISP_ON ACALL LCDINSTR ;send instruction to the display MOV A,#ENTRY_SET OR C_INC ACALL LCDINSTR ;send instruction to the display RET ;***************************************************************************** ;* The LCDSIGN-routine sends the bitmap-code for one of the eight custom- * ;* definable characters to the display module. * ;* The datapointer points to the top row of pixels of the character. * ;* The accumulator contains the character number (1...8). * ;* IN: A = CHARACTER NUMBER * ;* DPTR = POINTER TO FIRST LINE OF PIXELS * ;* USES: A, DPTR, R6 * ;***************************************************************************** LCDSIGN: ANL A,#00000111b ;mask the character number SWAP A ;multiply by eight bij shifting the RR A ; things a littlebit around ORL A,#CG_ADDR ;add the CG-address to the mask ACALL LCDINSTR ;write the instruction to the display MOV R6,#008d ;8 lines to go LCDSIGN2: CLR A ;clear out the accu MOVC A,@A+DPTR ;get the pixel-code ACALL LCDCHAR ;send the line to the display INC DPTR ;next line up! DJNZ R6,LCDSIGN2 ;finnished? Nope, again MOV A,#DD_ADDR ;put the display to the data again ACALL LCDINSTR ;(do it now) RET ;that's it ;***************************************************************************** ;* LCDDECLEN sends the value of the accu to the LCD-module in a decimal * ;* format. The B-register contains the number of digits that should be * ;* displayed. * ;* IN: A = VALUE TO DISPLAY * ;* B = NUMBER OF DIGITS (1 or 2) * ;* USES: A, B * ;***************************************************************************** LCDDECLEN: XCH A,B ;move the number of digits in the accu CJNE A,#1d,LCDDEC1 ;two digit to display? SJMP LCDDEC2 ;only one digit to display ;***************************************************************************** ;* LCDDEC sends the value of the accu to the LCD-module in a decimal format. * ;* IN: A = VALUE TO DISPLAY * ;* USES: A, B * ;***************************************************************************** LCDDEC: MOV B,#100d ;load B with 100d DIV AB ;divide the number by 100d ADD A,#'0' ;create the correct ASCII-code ACALL LCDCHAR ;show the digit LCDDEC1: MOV A,B ;move the remainder to the accumulator MOV B,#10d ;load B with 10d DIV AB ;divide the remainder by 10 ADD A,#'0' ;yep, the same: correct ASCII-code ACALL LCDCHAR ;show the digit LCDDEC2: MOV A,B ;get the last remainder ADD A,#'0' ;let's make some ASCII out of it ACALL LCDCHAR ;and show the dam'n thing RET ;that's it... ;***************************************************************************** ;* LCDHEX sends the value of the accu to the LCD-module in a hexadecimal * ;* format. * ;* IN: A = VALUE TO DISPLAY * ;* USES: A * ;***************************************************************************** LCDHEX: PUSH ACC ;get a backup of the accumulator SWAP A ;move the high nibble to the lower ACALL LCDHEX1 ;calculate the ASCII value ACALL LCDCHAR ;show the digit on the module POP ACC ;get the backup of the accumulater ACALL LCDHEX1 ;calculate the ASCII value ACALL LCDCHAR ;show the digit RET LCDHEX1: ANL A,#0Fh ;we just need the lower nibble CJNE A,#0Ah,LCDHEX2 ;look if the value is smaller then 10 LCDHEX2: JC LCDHEX3 ;yes? add ASCII-code for 0 ADD A,#'A'-'0'-10d ;else add an other value LCDHEX3: ADD A,#'0' ;add ASCII-code for 0 RET ;***************************************************************************** ;* LCDDPTR sends the value of the datapointer to the LCD-module in a decimal * ;* format. * ;* The lowest digit is placed on the current cursor-position. * ;* All digits that are 0 will be displayed with the character in the * ;* B-register. If this register contains 0d, the digit space remains empty. * ;* IN: DPTR = VALUE TO DISPLAY * ;* B = 0-SIGN or 0d * ;* USES: A, B, DPTR, R5, R6, R7 * ;* Note: Display shift is placed to the shift right mode after executing the * ;* LCDDPTR-routine. * ;***************************************************************************** LCDDPTR: MOV A,#ENTRY_SET OR C_DEC ;shift to the left when writing ACALL LCDINSTR ;send instruction MOV R5,#5d ;5 digits to go LCDDPTR1: ACALL DIVBYTEN ;delen door 10 ADD A,#'0' ;create the correct ASCII-code ACALL LCDCHAR ;show it to the outside DEC R5 ;the digit is done MOV A,DPL ;are there any more digits to go? ORL A,DPH ; when yes, the accu is bigger than JNZ LCDDPTR1 ; zero and we do it again MOV A,B ;get the digit-character JZ LCDDPTR3 ;if it's a zero, there's nothing to do CJNE R5,#0d,LCDDPTR2 ;are there any digits to display? SJMP LCDDPTR3 ;no? Then you're done here LCDDPTR2: MOV A,B ;else get the character ACALL LCDCHAR ;show it DJNZ R5,LCDDPTR2 ;done with it? LCDDPTR3: MOV A,#ENTRY_SET OR C_INC ACALL LCDINSTR ;turn the display shift right on again RET ;***************************************************************************** ;* DIVBYTEN is a part of the LCDDPTR routine, but it can also be used as a * ;* stand-alone routine. The only thing this routines does is dividing the * ;* value of the datapointer by 10. The remainder is placed in the * ;* accumulator. * ;* IN: DPTR = VALUE * ;* USES: A, DPTR, R6, R7 * ;***************************************************************************** DIVBYTEN: MOV R6,#0d ;remainder=0 MOV R7,#16d ;16 bits to go CLR C ;clear the carry DIVBYTEN1: ACALL DIVBYTEN3 ;rotate the DPTR to the left MOV A,R6 ;move the carry-bit in the remainder RLC A ; trough the accumulator (try to do it MOV R6,A ; another way) SUBB A,#10d ;if the remainder is smaller than 10 JC DIVBYTEN2 ; go away MOV R6,A ;else keep the value-10 DIVBYTEN2: CPL C ;complement the carry DJNZ R7,DIVBYTEN1 ;all bits done? ACALL DIVBYTEN3 ;rotate the last carry MOV A,R6 ;keep the remainder in the accumulator RET DIVBYTEN3: MOV A,DPL ;move the complete datapointer to the RLC A ; left MOV DPL,A ; this can be done by rotating (trough MOV A,DPH ; the accumulator) the lower part to the RLC A ; left and afterwards the higher part MOV DPH,A ; and using the carry RET ;***************************************************************************** ;* CLRLCD cleans out the LCD-module and returns the cursor to position 0 * ;* USES: A * ;***************************************************************************** LCDCLR: MOV A,#CLEARDISP ;load the wipe instruction ACALL LCDINSTR ;send it to the module ACALL DELAY2m ;wait for 2ms (it takes a wile, ok?) RET ;***************************************************************************** ;* LCDSCROLL sets the scroll-mode of the display. * ;* When the accumulator is equal to zero the display scrolls to the right * ;* else it scrolls to the left. * ;* USES: A * ;***************************************************************************** LCDSCROLL: JZ LCDSCROL1 ;equal to zero? No prob MOV A,#SHIFT_R ;else set the right bit LCDSCROL1: ORL A,#CURS_DISP OR D_SHIFT ;add the codes ACALL LCDINSTR ;write the data to the LCD-module RET ;***************************************************************************** ;* LCDCURSOR moves the cursor trough the screen. * ;* The accumulator contains the location of the cursor. The MSB controlls * ;* the cursor functions: CURSORON : sets the cursor on * ;* CURSORBLINK : blinks the cursor * ;* IN: A = CURSOR LOCATION * ;* USES: A * ;* Note: The display is turned on when leaving the LCDCURSOR routine. * ;***************************************************************************** LCDCURSOR: JB ACC.7,LCDCURS1 ;bit 7 set? ORL A,#DD_ADDR ;add the display data address ACALL LCDINSTR ;put the cursor on the right spot RET LCDCURS1: ANL A,#00000011b ;only look at the two LSB's ORL A,#ONOFF_SET OR DISP_ON ACALL LCDINSTR ;set the cursor to the choosen mode RET ;***************************************************************************** ;* LCDSTR writes a string to the LCD-module. * ;* The datapointer points to the start of the string in the memory. The * ;* termination-code is 0h. * ;* IN: DPTR = BEGIN STRING * ;* USES: A, DPTR * ;***************************************************************************** LCDSTR: CLR A ;clear out the accumulator MOVC A,@A+DPTR ;get the character JZ LCDSTR2 ;termination character? (code 0) ACALL LCDCHAR ;nope, write the character to the LCD INC DPTR ;next character SJMP LCDSTR ;do it again... LCDSTR2: RET ;***************************************************************************** ;* LCDCHAR writes a single character to the LCD. * ;* The accu contains the character. * ;* IN: A = CHARACTER * ;* USES: A * ;***************************************************************************** LCDCHAR: PUSH ACC ;make a backup of the accu SWAP A ;het the lowest nibble ANL A,#0Fh ;we don't need the upper 4 bits ORL A,#00110000b ;set the ENABLE and RS(=DATA) lines ACALL STROBE ;write the data to the module POP ACC ;get the backup ANL A,#0Fh ;the upper 4 bits are up ORL A,#00010000b ;reset ENABLE, set RS(=DATA) ACALL STROBE ;write it to the module RET ;we're done ;***************************************************************************** ;* LCDINSTR writes an instruction to the LCD. * ;* The accu contains the instruction-code. * ;* IN: A = CODE * ;* USES: A * ;***************************************************************************** LCDINSTR: PUSH ACC ;make a backup of the accu SWAP A ;het the lowest nibble ANL A,#0Fh ;we don't need the upper 4 bits ORL A,#00100000b ;set ENABLE, RS=0(=INSTRUCTION) ACALL STROBE ;write the data to the module POP ACC ;get the backup ANL A,#0Fh ;the upper 4 bits are up ACALL STROBE ;write it to the module ACALL DELAY40u ;wait for 40us RET ;we're done ;***************************************************************************** ;* The STROBE-routine is used to write data to the LCD-display. * ;* The accu contains the value that should be written to the LCD-module * ;* IN: A = VALUE * ;* USES: A, DPTR * ;* STROBE for the use with a MUART * ;***************************************************************************** STROBE: PUSH DPH ;make a backup of the datapointer PUSH DPL ORL A,#00100000b ;set the ENABLE-line to 1 MOV DPTR,#PORT1 ;set pointer to port1 MOVX @DPTR,A ;write data to the MUART ANL A,#11011111b ;reset the ENABLE-line MOVX @DPTR,A ;write data to the MUART ACALL DELAY40u ;wait a little POP DPL ;get the old datapointer POP DPH RET ;***************************************************************************** ;* The STROBE-routine is used to write data to the LCD-display. * ;* The accu contains the value that should be written to the LCD-module * ;* IN: A = VALUE * ;* USES: A * ;* STROBE for the use with an controller-port * ;***************************************************************************** ;STROBE: ORL A,#00100000b ;set the ENABLE-line to 1 ; MOV P1,A ;write the value to the port ; ANL A,#11011111b ;reset the ENABLE-line ; MOV P1,A ;write the value to the port ; ACALL DELAY40u ;wait a little ; RET ;***************************************************************************** ;* All the DELAY routines are used to slow down the controller. Otherwise * ;* the pour display can't see clearly the data (poor thing). * ;***************************************************************************** DELAY5m: PUSH REG7 ;make a backup of R7 MOV R7,#59d ;load the right value DELAY5m1: ACALL DELAY80u ;wait for 80us DJNZ R7,DELAY5m1 ;still need to wait? POP REG7 ;get the backup of R7 RET DELAY2m: PUSH REG7 ;make a backup of R7 MOV R7,#27d ;load the right value DELAY2m1: ACALL DELAY80u ;wait for 80us DJNZ R7,DELAY2m1 ;still need to wait? POP REG7 ;get the backup of R7 RET DELAY80u: ACALL DELAY40u ;execute 2 times the 40us routine DELAY40u: PUSH REG7 ;make a backup of R7 MOV R7,#36d ;pushing an poping takes 4us DJNZ R7,$ ;wait... wait... wait... wait... POP REG7 ;get the backup of R7 RET ;***************************************************************************** ;* Table with data for some signs * ;***************************************************************************** ARROW: DB 00100b,01110b,01110b,10101b,00100b,00100b,00100b,00000b DEGREES: DB 00110b,01001b,01001b,00110b,00000b,00000b,00000b,00000b SMILEY: DB 01110b,10101b,11111b,10101b,10001b,11111b,01110b,00000b END