The four files described below provide a complete program to display characters anywhere on a 2 line by 16 character LCD using a 4 data and 3 control line interface. Figure 1 shows the required connections. The programs are written in Microchip MPASM assembler and can be included in an MPLAB project. Check the special setup required.
| LCD pins | Function | 16X84 pins
| 14-11
| D7-4
| DB7-4
| 5
| R/W
| DB3
| 4
| RS
| DB2
| 6
| E
| DB1
| |
The complete test program consists of four files. The main program is LCD_TST.ASM, and is here used only to show examples of how the LCD driver is called and my method of creating a calibrated delay. The LCD driver itself is LCD.ASM and is simply included into your program, typically at the end. There are two definition files called RKMACROS.INC and TIMER.INC which are from my project toolbox. The former defines the macros used while the latter computes at compile time the required internal timer time constants. Both can be left out after appropriate editing of the source files.
This version of the test program shows sending strings from program memory directly to the lcd driver using the sendit subroutine. However, my requirement is that I can send strings from both program or data memory, and a later version will show how these strings are assembled in data memory and then passed to the LCD driver.
| A zipped copy of these four files is available: |
LCD.ZIP
|
Please refer all queries to
Ron Kreymborg
NOLIST ;****************************************************************** ; ; A set of standard macros. ; ; Ron Kreymborg ; ;****************************************************************** intoff macro bcf intcon,gie btfsc intcon,gie goto $-2 endm bank0 macro bcf STATUS,RP0 endm bank1 macro bsf STATUS,RP0 endm dotris macro arg1, arg2 bsf STATUS,RP0 movlw arg1 movwf arg2 bcf STATUS,RP0 endm dodelay macro arg1 movlw arg1 call delay endm LIST
; PRESCL and TIMER computation NOLIST ; Take the processor clock frequency in Hz (clock), and ; the required time delay in uSecs (dusec), and compute ; a value for the prescaler (PRESCL) and another for ; TMR0 (TMRVAL). _cycle equ clock >> 2 _dfreq equ 1000000 / dusec _divr equ _cycle / _dfreq if (_divr < 512) error "Delay time too short. Assign PSA to WDT." endif _test set _divr / 256 _testB equ _divr % 256 if (_testB > 0) _test set _test + 1 endif if (_test > 1) _temp set 2 PRESCL set 0 endif if (_test > 2) _temp set 4 PRESCL set 1 endif if (_test > 4) _temp set 8 PRESCL set 2 endif if (_test > 8) _temp set 16 PRESCL set 3 endif if (_test > 16) _temp set 32 PRESCL set 4 endif if (_test > 32) _temp set 64 PRESCL set 5 endif if (_test > 64) _temp set 128 PRESCL set 6 endif if (_test > 128) _temp set 256 PRESCL set 7 endif if (_test > 255) error "Timer requirement is outside range" endif TMRVAL equ 256 - (_cycle / _temp / _dfreq) LIST
;****************************************************************** ; ; Test program for LCD driver. ; ;****************************************************************** title "LCD Driver (lcd.asm) Test" list p=pic16f84,r=dec,n=80,x=off,st=off include p16f84.inc include rkmacros.inc errorlevel -302 ; no bank warnings clock equ 10000000 ; my crystal frequency dusec equ 1000 ; required delay is 1 mSec include timer.inc ; (note include must be after ; previous two definitions) ; Data - CBLOCK 0x0C ; start of data area dval pntr offset ENDC ;**************************************************************** org 0 ; for future expansion goto start ; org 4 ; retfie ; ; All messages and the sendit/gbyt routines must be in the ; first 256 bytes of program memory. mess1 dt 0x00,"Lat 38",0xdf,"56'42.4",0x22,0 mess2 dt 0x40,"Lng 178",0xdf,"23'19.8",0x22,0 ; Send the message in program memory pointed to by W to the LCD. sendit movwf pntr ; save pointer to data clrf offset ; clear "next byte" offset call gbyt ; get the LCD address iorlw 0x80 ; set as address command call lcd_c ; send command snd1 incf offset,f ; step offset to next byte call gbyt ; get it iorlw 0 ; terminating null? btfsc STATUS,Z return ; yes, done call lcd_d ; send to LCD data memory goto snd1 ; around again gbyt movf pntr,w ; get base address addwf offset,w ; add offset movwf PCL ; get char ;**************************************************************** ; Main program begins start clrwdt ; setup 1mSec timer bsf STATUS,RP0 movlw b'11010000' | PRESCL ; prescaler to TMR0 movwf OPTION_REG bcf STATUS,RP0 dotris 0,PORTA ; PORTA all outputs for now clrf PORTA call lcd_init ; initialise the LCD driver movlw mess1 ; send first line call sendit movlw mess2 ; send second line call sendit loop goto loop ; Delay routine using the internal timer. Will delay for W ; times the value of "dusec" in microseconds. delay movwf dval dy1 clrf TMR0 bcf INTCON,T0IF movlw TMRVAL ; set timer movwf TMR0 dy2 btfss INTCON,T0IF ; timer overflow yet? goto dy2 ; no decfsz dval,f ; yes, all cycles? goto dy1 ; no return ; yes, delay finished include lcd.asm end
;****************************************************************** ; ; Routines to display on a 2x16 character LCD display. ; ; LcdInit - call this subroutine before using the display. It uses ; bits 7-1 of PORTB, leaving bit-0 free (although it will ; be configured as an output). ; ; Lcd_C - Sends the byte in W as a command to the display. Checks ; if busy first. ; ; Lcd_D - Sends the byte in W as data. Checks for busy first. ; ; Assumes it can call a routine called "delay" with a required ; time delay in mSecs in W. ; Uses an additional three (3) levels of the stack. ; ; Address of first line on the LCD is 0 ; Address of second line is 64 ; ; Ron Kreymborg ;****************************************************************** lcd_com macro arg1 movlw arg1 call lcd_c endm ; Data - CBLOCK ; lcd data area lcd_flags lcd_temp ENDC ; Flags - lcd_Bflg equ 0 lcd_RSflg equ 1 ; I/O portB - lcd_BUSY equ 7 ; input - LCD is busy (0x80) lcd_R_W equ 3 ; output - LCD Read/Write (0x08) lcd_RS equ 2 ; output - Register Select (0x04) lcd_E equ 1 ; output - LCD Enable (0x02) lcd_init clrf PORTB dotris b'00000000',PORTB ; all outputs and low to start bcf lcd_flags,lcd_RSflg dodelay 15 ; 15mSec power up delay movlw b'00110000' ; 8-bit mode movwf PORTB call lcd_clk dodelay 4 ; 4mSec wait movlw b'00110000' ; 8-bit mode movwf PORTB call lcd_clk dodelay 1 ; 1 mSec wait movlw b'00110000' ; 8-bit mode movwf PORTB call lcd_clk dodelay 4 ; 4mSec wait movlw b'00100000' ; set for 4-bit movwf PORTB call lcd_clk ; Reset sequence is done - initialise for us lcd_com b'00101000' ; 4-bits, 2-lines, 5x7 lcd_com b'00001000' ; display off, cursor off, blink off lcd_com b'00001100' ; display on lcd_com b'00000001' ; clear display lcd_com b'00000110' ; increment, no display shift return ; Check whether the LCD is busy. Loop until it isn't. When ; it's free, output the byte passed in W, MS nibble first. lcd_d bsf lcd_flags,lcd_RSflg ; set RS flag for data goto lcd_o1 lcd_c bcf lcd_flags,lcd_RSflg ; clear RS flag for commands lcd_o1 movwf lcd_temp ; save control word dotris b'11110000',PORTB ; RB7-4 as inputs for busy movlw 1 << lcd_R_W movwf PORTB ; set up for read lcd_o2 bcf lcd_flags,lcd_Bflg ; assume not busy bsf PORTB,lcd_E ; clock high nop ; little wait btfsc PORTB,lcd_BUSY ; busy set? bsf lcd_flags,lcd_Bflg ; yes bcf PORTB,lcd_E ; clock low nop ; little wait nop call lcd_clk ; get low bits but ignore (for now) btfsc lcd_flags,lcd_Bflg ; was it busy? goto lcd_o2 ; yes dotris b'00000000',PORTB ; RB7-4 outputs again movf lcd_temp,w ; get word to send call lcd_o3 ; send high nibble swapf lcd_temp,w ; get word again lcd_o3 andlw 0xf0 ; just high bits (clears all control bits) movwf PORTB btfsc lcd_flags,lcd_RSflg ; was lcd_RS set? bsf PORTB,lcd_RS ; yes call lcd_clk ; write current nibble return lcd_clk bsf PORTB,lcd_E ; pulse E line high nop bcf PORTB,lcd_E return end