| « PIC Memory Organization & EEPROM Control | Intro to the HD44780 character based LCD » |
The project:
This project is a simple but fun one. We are going to be using a tri-axis accelerometer to collect analog data from and then display it on the LCD. Our output on the LCD will look like:
|X||:|| ||Digit1||Digit2||Digit3|
|Y||:|| ||Digit1||Digit2||Digit3|
|Z||:|| ||Digit1||Digit2||Digit3|
The X:, Y:, and Z: are all static and only have to be sent once. (once you update the DDRAM address it stays there until power is cut) The digits however, have to be updated as we get new values, thus it is dynamically updated in our code and sent periodically.
This algorithm is fairly simple and works automatically. The procedure goes:
-Initialize the MCU
-Initialize the LCD
-Draw the static information
-Collect the analog information
-Convert the raw data into digits
-Display the data on the LCD.
The Code:
The Code: #include p16F690.inc
__config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)
org 0 ; Begginning of code
cblock 0x20
CHARLOCATION
RESULT
ONES
TENS
HUNDREDS
TEMP
DELAY1
DELAY2
COUNT
endc
PORTS:
bsf STATUS,RP1 ; Bank 2
movlw 0x14 ; RA2 & 4 Configured for analog AN2,3
movwf ANSEL ;
movlw 0x08 ; RB5 configured for analog (AN11)
movwf ANSELH
bcf STATUS,RP1
bsf STATUS,RP0 ; select Register Page 1
movlw 0x00 ; Move the hex value of 0 to multi purpose register W
movwf TRISC ; make IO PortC all output
movlw 0x16 ; Make Ra4,2 and 1 Input
movwf TRISA
movlw 0x20 ;RB5 Input
movwf TRISB
movlw 0x10 ;set A2d Clock
movwf ADCON1
bcf STATUS,RP0 ;Exit bank 1
;initialize
clrf PORTC
clrf PORTA
clrf PORTB
clrf COUNT
clrf TEMP
call INITIALIZELCD
call DRAWLCD
MAIN: ; Our MAIN routine gets the analog value off a port, and updates the LCD with the info
movlw 0x41
movwf ADCON0 ;Channel 2
call A2D ;Get A2D value
movwf RESULT
movlw 0x83 ;Char position 03
call UPDATELCD ;Call Update LCD, having the char position in W
movlw 0x4D
movwf ADCON0 ;Channel 3
call A2D
movwf RESULT
movlw 0xC3 ;Char Position 43
call UPDATELCD
movlw 0x6D
movwf ADCON0 ;Channel 11
call A2D
movwf RESULT
movlw 0x97 ;Char position 17
call UPDATELCD
call DELAY
goto MAIN ; Return to main
A2D:
nop
nop
nop
nop
nop ; wait 5 uS
bsf ADCON0,GO ; Start the conversion
btfss ADCON0,GO ; Bit will stay high untill conversion is complete
goto $-1 ; Untill then keep going back
movf ADRESH,w
return
INITIALIZELCD: ;Initialization Routine for the LCD
bsf PORTC,0 ;Put CMD 0 on port C
call SEND ;Call Send routine, Sends command and waits for busy flag
movlw 0x38 ; Set Function, 8 Bit, 5X10 Dots, Two line
movwf PORTC
call SEND
movlw b'00001100' ; Turn on the Display (0x0B
movwf PORTC
call SEND
movlw 0x06 ; Set entry mode to: Increment Adress counter, no shift.
movwf PORTC
call SEND
return
DRAWLCD: ; Routine which draws static display
movlw 0x80 ; Start with the DDRAM address 0x00
call WRITELOC ; Call Function to write the DDRAM address
movlw 'X' ;Move the ASCII value of 'X' to W
call WRITECHAR ; Call function to write the Char to DDRAM address
movlw 0x3A
call WRITECHAR
movlw 0xC0
call WRITELOC
movlw 'Y'
call WRITECHAR
movlw 0x3A
call WRITECHAR
movlw 0x94
call WRITELOC
movlw 'Z'
call WRITECHAR
movlw 0x3A
call WRITECHAR ; Continue untill all static elements drawm
return
WRITECHAR: ; Function to write a characture
movwf TEMP ; Move value to temp register
call BUSYCHK ; Check Busy flag
bsf PORTA,0 ; Turn on Data
movfw TEMP ; Move from Temp to W
movwf PORTC ; Then to PORTC
call SEND ; Send
bcf PORTA,0 ; Turn off Data
return
WRITELOC:
movwf TEMP ;Move value to temp
call BUSYCHK ;Check busy flag
movfw TEMP ;Temp -> W
movwf PORTC ;W -> Portc
call SEND ;SEND
return
UPDATELCD: ; Routine to update the dynamic elements of the LCD
call WRITELOC ; Init the LCD at the proper DDRAM location
clrf ONES ; Clear Vars
clrf TENS
clrf HUNDREDS
call CALCULATE ; Call calculate routine which takes raw A2D value and separates them into digits
movfw HUNDREDS ; Write the Hundreds digit to the LCD
call TABLE1
call WRITECHAR
movfw TENS ;Write Tens
call TABLE1
call WRITECHAR
movfw ONES ;Write Ones
call TABLE1
call WRITECHAR
return
CALCULATE: ; Basic math routine, takes Result from A2D and separates into 1's 10's and 100's
movlw 0x00
bcf STATUS,2
xorwf RESULT,w
btfsc STATUS,2
return
movlw 0x0A
bcf STATUS,2
subwf ONES,w
btfsc STATUS,2
goto UPDATETENS
decf RESULT,f
incf ONES,f
goto CALCULATE
UPDATETENS:
clrf ONES
incf TENS,f
movlw 0x09
bcf STATUS,2
subwf TENS,w
btfsc STATUS,2
call UPDATEHUNS
decf RESULT,f
goto CALCULATE
UPDATEHUNS:
clrf TENS
incf HUNDREDS,f
movlw 0x09
bcf STATUS,2
subwf HUNDREDS,w
btfsc STATUS,2
incf HUNDREDS,f
return
SEND: ;Use this routine to send a command to the LCD. Placement doesnt matter as it does two BSYFLG checks
;Send
bsf PORTB,4 ; Set E line High
nop
nop
nop
nop
bcf PORTB,4 ;Wait a bit then set it low, sending command to LCD
BUSYCHK:
bsf STATUS,RP0 ; Port C, all input
movlw 0xff
movwf TRISC
bcf STATUS,RP0
bsf PORTA,5 ;Set Read
nop
nop
bsf PORTB,4 ;E Line high
nop
nop
nop
nop
bcf PORTB,4 ;Wait, Now low, Polling busy flag
btfsc PORTC,7 ; Wait untill busy flag is clear.
goto $-7
bcf PORTA,5 ;Turn off Read
bsf STATUS,RP0
movlw 0x00
movwf TRISC
bcf STATUS,RP0 ;Port c all output
return
TABLE1: ;Generic Table: Decimal -> ASCII
addwf PCL;
retlw 0x30
retlw 0x31
retlw 0x32
retlw 0x33
retlw 0x34
retlw 0x35
retlw 0x36
retlw 0x37
retlw 0x38
retlw 0x39
return
DELAY: ;Generic Delay
decfsz DELAY1
goto DELAY
decfsz DELAY2
goto DELAY
return
end
The Breakdown:
This code is fairly simple, there are no interrupts to worry about. We set up our variables in available registers as always. Our ports routine is much of the same, we set the appropriate ports for analog function and then set the appropriate I/O
Initialize the LCD:
INITIALIZELCD: ;Initialization Routine for the LCD
bsf PORTC,0 ;Put CMD 0 on port C
call SEND ;Call Send routine, Sends command and waits for busy flag
movlw 0x38 ; Set Function, 8 Bit, 5X10 Dots, Two line
movwf PORTC
call SEND
movlw b'00001100' ; Turn on the Display (0x0B
movwf PORTC
call SEND
movlw 0x06 ; Set entry mode to: Increment Adress counter, no shift.
movwf PORTC
call SEND
return
Remember all those commands? We have to first tell the LCD what we want it to do before we start writing to the DDRAM. We set our LCD up for how were going to use it. This a fairly common initialization routine. We turn on the display, set 8 bit data control, two line, 5x10 font, and we have the address counter increment on write to DDRAM.
This routine we load the command we want into PORTC and then call our Send routine
The Send Routine:
SEND: ;Use this routine to send a command to the LCD. Placement doesnt matter as it does two BSYFLG checks ;Send bsf PORTB,4 ; Set E line High nop nop nop nop bcf PORTB,4 ;Wait a bit then set it low, sending command to LCD BUSYCHK: bsf STATUS,RP0 ; Port C, all input movlw 0xff movwf TRISC bcf STATUS,RP0 bsf PORTA,5 ;Set Read nop nop bsf PORTB,4 ;E Line high nop nop nop nop bcf PORTB,4 ;Wait, Now low, Polling busy flag btfsc PORTC,7 ; Wait untill busy flag is clear. goto $-7 bcf PORTA,5 ;Turn off Read bsf STATUS,RP0 movlw 0x00 movwf TRISC bcf STATUS,RP0 ;Port c all output return
To send data the the LCD we have to toggle the E line. We set it high momentarily then we set it low. Once we do that the LCD reads the command and performs an internal operation. As this happens the BSY Flag is set on the LCD and we poll this until it is clear. After which we continue with normal operation by exiting the routine
Drawing the Static Display:
We draw all the static information by first setting the DDRAM address position and then putting the value we wish to send into w and then calling a routine which sends the value to the LCD
WRITELOC: movwf TEMP ;Move value to temp call BUSYCHK ;Check busy flag movfw TEMP ;Temp -> W movwf PORTC ;W -> Portc call SEND ;SEND return
Basic routine that makes it a little more mechanical to write data to the LCD. The value in W is sent to the LCD
WRITECHAR: ; Function to write a characture movwf TEMP ; Move value to temp register call BUSYCHK ; Check Busy flag bsf PORTA,0 ; Turn on Data movfw TEMP ; Move from Temp to W movwf PORTC ; Then to PORTC call SEND ; Send bcf PORTA,0 ; Turn off Data return
Another routine specially designed for writing a character to a DDRAM address. This sets our PORTA,0 (Instruction/Data) high as we send Data to the LCD.
The MAIN routine:
The main routine is fairly simple, we generate an A2D result and then call the routine which does the dynamic updating of the LCD (we do this 3 times for each of the 3 axis (xyz)
Updating the LCD:
UPDATELCD: ; Routine to update the dynamic elements of the LCD call WRITELOC ; Init the LCD at the proper DDRAM location clrf ONES ; Clear Vars clrf TENS clrf HUNDREDS call CALCULATE ; Call calculate routine which takes raw A2D value and separates them into digits movfw HUNDREDS ; Write the Hundreds digit to the LCD call TABLE1 call WRITECHAR movfw TENS ;Write Tens call TABLE1 call WRITECHAR movfw ONES ;Write Ones call TABLE1 call WRITECHAR return
This is another simple routine, we take the A2D result, break it down into its individual digits and send it to the LCD. We provide the DDRAM address in w before we call the function, thus this can be used for more than 1 axis making our code much more efficient.
Conclusion:
Overall this is a fairly easy project that goes over the basics of controlling an HD44780 character LCD using a tri axis accelerometer as an analog input.
Ill leave you with a picture of the completed project as well the source and hex. Enjoy!
Whats next: Making it display proper units