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
This post is going to go over using an HD44780 character LCD. These LCDs come in many sizes by are usually defined by how many characters they display; split by how many lines and characters per line.
The one thing about these LCDs is that they have a built in microcontroller, so we are going to be using our PIC microcontrollers to interface with the built in MCU on the HD44780 LCD.
Interfacing with the LCD is rather easy but often guides are very vague on exactly what you have to do. I will be going over how to statically and dynamically update the LCD using our PICs.
The LCDs work by first configuring how the LCD operates (usually an initializing routine you use from code to code. The way the LCD displays data is that each "character cell" corresponds to a DDRAM address (Display Data RAM) you load the ASCII Hex value into the DDRAM address and the corresponding cell displays that character. Thats it! Its pretty easy to use these LCDs.
How it works:
I would first like to post a wonderful link which i used to learn how to use these LCDs. It has all the info you need but is a little confusing if you don't know what to look for.
Link: http://home.iae.nl/users/pouweha/lcd/lcd.shtml
Any HD44780 character LCD with <=80 characters (less than or equal to) will have 16 pins. For MOST LCDs (ALWAYS consult your data sheet first)
Pin 1 is VSS(Ground)
Pin 2 is VDD(Pos voltage)
Pin 3 is Vee, or voltage for contrast adjustment you hook this up in a voltage divider configuration with a potentiometer or trimmer. You use this to adjust contrast.
Pin 4 is the RS line which controls whether your sending data or an instruction. 0 is for an instruction, 1 for data.
Pin 5 is Read/Write from/to the LCD, 0 for Write, 1 for Read.
Pin 6 is the most important, it is the Enable signal, The enable line for the HD44780 is edge triggered, on a falling edge (1->0) The HD44780 reads whatever is on the data bus. This is the one thing people have most trouble with.
Pins 7-14 is the primary data bus. Its 8 bits long and you can either hook it up to a spare 8 bit port or use serial communication and a shift register.
Pin 15 is the anode to the back light. Make sure you put correct voltage across this and the cathode
Pin 16 is the cathode to the back light.
Now that you know the pin out lets look at some instructions:
Commands:
Instructions control the operation of the LCD, They clear the DDRAM addresses, reset the address counter, set how the address counter functions, how the text displays and various other functions. We do all this before we send any data to the LCD, this initializes the LCD and gets it ready for accepting data and displaying it in a meaningful fashion.
To send an instruction we set the RS and R/W lines both low. When this happens we put the instruction we want onto our data bus and send it by setting the enable line from high to low.
The Instruction set:
Command: Clear Home
RS:0
RW:0
DB:0x01
This command clears the DDRAM and sets the address counter to 0x00
Command: Return Home
RS:0
RW:0
DB:0x02
Moves the address counter to 0x00, leaves the DDRAM untouched.
Command: Entry Mode
RS:0
RW:0
DB:0x04-0x07 (000001MS)
This controls what the cursor does when it gets a command. Bit "M" controls weather its to the left or the right. 0 will decrement the cursor position, (move it to the left) a 1 will increment the cursor position (move it to the right). S Controls if the display shifts (Where the text moves but not the cursor position) 0 is off and 1 is on.
Command: Display Control
RS:0
RW:0
DB:0x08-0x0F (00001DCB)
Sets weather the display is on(D) (useful), if the cursor is on(C) and if the cursor blinks(B). A 1 correlates to enabled.
Command: Cursor / Display shift.
RS:0
RW:0
DB:0x10-0x1F (0001SDxx)
This allows you to shift(S) the display(1) or the Cursor(0) and what direction(D) left(0), right(1)
Command: Function Set
RS:0
RW:0
DB:0x20-0x3F (001LNFxx)
Sets basic control information for the LCD
L - Data Length: 0 - 4-Bit communication (We will be using 8-bit) 1 - 8-bit communication
N - 1 line or 2 line: 0 - 1 line 1 - 2 line, (just set to 1)
F - Font: 0 - 5X7 Dots 1 - 5X10 dots. Set to 1
Command: Set CGRAM address
RS:0
RW:0
DB:0x40-0x7F (01(ADDRESS))
HD44780 LCDs support custom characters, CGRAM is where you set them, i will not be covering custom characters in this post.
Command: Set DDRAM address
RS:0
RW:0
DB:0x80-0xFF (1(ADDRESS))
This sets the DDRAM address which you will start sending data to.
Sending Command to the LCD:
To send commands to the LCD we have to do several things.
-Set the RS and RW line
-Put the data/instruction we want to send on our data port (any 8 bit port)
-Send it
To send data to the HD44780 we set the E line high, do a few NOPs and then set it low. The HD44780 reads the command and operates accordingly.
While the HD44780 is performing an operation we cannot send data to it. There are two ways we can make sure we don't send information while the LCD is busy. One is to just wait long enough for the LCD to finish. The other is to check the built in BUSY signal of the HD44780, I prefer this method as it is much quicker.
The Busy Flag:
RS:0
RW:1
DB:(BF(ADDRESS))
When we set RW to 1 (read/write) we read the busy flag as well as the address counter. To read the busy flag we set RW to 1 and then the E line from 1 to 0. This will put the busy flag on DB7. A simple BTFSS loop will allow you to check the busy flag.
Writing Data to the LCD:
Writing data encompasses several functions:
-Initializing the LCD
We set the proper functions for the LCD to operate and set it up for data transfer
-Selecting the first memory location
We send the first DDRAM address we want to write at (most parts sending 0x80 will set us to the first character)
-Writing the hex value for the char we wish to display
Command: Write
RS:1
RW:0
DB:Character
To write a char we just load the HEX value that corresponds to the data we wish to display, set RS to 1 and send the data (E1->0)
We keep writing as long as we want to stay on whatever line we started at. Every time you write, the address counter automatically increments, no need to keep resending the DDRAM address.
Command: Read
RS:0
RW:1
DB:Address Contents
This command reads the current address's contents.
With this, you have all the information to control the HD44780 based character LCD. I will be posting Code and a project involving this LCD so stay tuned!
The basis of this project is 5 7 segment displays, One of which the user controls and uses to select a number 0-9, and 4 of which store previously received values. There are 3 buttons; Increment decrement and send, all of do their respectable functions.
This project takes advantage of the following procedures:
Asynchronous Serial Communication – We use the UART module on the PIC16F690 to transmit data from one PIC to another. We use a 9600 baud with the transmitter and receiver set asynchronously. RB5 is RX and RB7 is TX
Multiplexed 7-segment Displays – One problem with running 5 segment displays is the sheer number of pins we’d need to run each one individually. Each display has 7 segments and a common cathode. For all of the displays that’s 40 pins we’d need to run all those displays. Multiplexing is where we only run one display at an instantaneous moment. We do this but loading the data for one display onto PORTC, then using another port to switch the CC connection using a bipolar transistor. We keep rotating the output on PORTC and the CC we are switching until we’ve gotten to the last display. This happens so fast all the displays seem like they’re on at one given time. One thing to consider when using a multiplexed display is that the outputs must be turned off when you rotate the display, otherwise you will get ghosting of the previous value.
Interrupts – We use the interrupt on change and the receive interrupt to handle user input.
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
;Declare Variables
cblock 0x20
DISPLAY
FIRSTDISP
SECONDDISP
THIRDDISP
FOURTHDISP
CURRENTDISP
STATUS_TEMP
TEMP
DELAY1
DELAY2
GROUND
W_TEMP
endc
goto PORTS ;Skip interrupt
;;;;;;;;;;;;;;
; INTERUPT ;
;;;;;;;;;;;;;;
org 0x04
INTERUPT:
bcf INTCON,7 ;Turn off interrupts
movwf W_TEMP ; Move W to temp
bcf STATUS,RP0 ; Goto bank 0
bcf STATUS,RP1 ; Goto bank 0
btfsc INTCON,0 ; Check for IOCA/B int
goto BUTTONPRESS ; Yes, goto button press routine
btfss PIR1,RCIF ; No, Check if value received
goto OUT ; No, leave interrupt
call RX ; Yes , receive the value
OUT:
movf W_TEMP,w ; Restore W
bcf PIR1,RCIF ; Clear flag
bcf INTCON,0 ; Clear flags
bsf INTCON,7 ; Restart interrupts
retfie ; Return from interrupt
BUTTONPRESS:
btfss PORTA,3 ;Check first button
call INCREASE ;Goto said function
btfss PORTB,4 ;Check Decrement button pressed,
call DECREASE ;Goto Said function
btfss PORTB,6 ;Check if TX button pressed
call TX ; Goto TX routine
call DELAY ; Button Debounce
goto OUT
INCREASE:
incf DISPLAY ;Increment display
movlw 0x0A ; Move 10d to W
bcf STATUS,2 ;Clear Z bit
subwf DISPLAY,w ;Subtract w from display
btfsc STATUS,2 ;Check if z bit set
clrf DISPLAY ; Yes, clear display
return ; no, return
DECREASE:
incf DISPLAY ;Increment display (incase 0)
bcf STATUS,2 ;Z bit
decf DISPLAY ;Decrement display
btfsc STATUS,2 ; Check z bit
goto INITIALIZE ;Yes, reinitialize the display
decf DISPLAY ;No, Decrement display
return
INITIALIZE:
movlw 0x09 ;Move 9d to w
movwf DISPLAY ; Move it to display
return
TX:
btfss PIR1 , TXIF ; TXIF is set if TXREG has no character
goto $-1 ; Loop until TXIF is cleared
movf DISPLAY,w ; Data to be transmitted over serial port
movwf TXREG ; Copy data to transmit register
bsf STATUS,RP0 ; RAM PAGE 1
btfss TXSTA , TRMT ; Wait until transmission is completed
goto $-1 ; Loop back until transmission completed
bcf STATUS,RP0 ; Bank 0
return
RX:
btfss PIR1,RCIF ; Wait for value to be recieved
goto $-1 ; loop
bcf PIR1,RCIF ; Clear flag
movf RCREG,w ; Move recieved word to w
movwf TEMP ; then to var temp
clrf FOURTHDISP ;Clear 4th display <- 3rd Disp <- Second Display <- First Display <- Temp
movfw THIRDDISP
movwf FOURTHDISP
movfw SECONDDISP
movwf THIRDDISP
movfw FIRSTDISP
movwf SECONDDISP
movfw TEMP
movwf FIRSTDISP
return
DELAY: ; Generic Delay routine
decfsz DELAY1
goto DELAY
decfsz DELAY2
goto DELAY
return
PORTS:
; Analog
bsf STATUS,RP1 ; Bank 2
clrf ANSEL ; All pins are digital I/O
clrf ANSELH ; All pins are digital I/O
bcf STATUS,RP1 ; Bank 0
; Tris
bsf STATUS,RP0 ; goto bank 1
movlw 0x00 ; Move the hex value of 0 to multi purpose register W
movwf TRISC ; make IO PortC all output
movlw b'01010000' ; Move move b'01010000' to w
movwf TRISB ; move to tris
movlw b'00001000' ;Make porta.3 input rest output
movwf TRISA
bcf STATUS,RP0 ; goto bank 0
; Interrupt
bsf INTCON,7 ;Set up global interrupts
bsf INTCON,6 ;Peripferal Interupts
bsf INTCON,3 ; IOCA/B interupt
bsf STATUS,RP0 ; Bank 1
bsf PIE1,5 ; Enable Interupt on Recieve
bcf STATUS,RP0 ; Bank 0
bcf PIR1,RCIF ; Clear Flag
bcf INTCON,0 ; IOCA/B change flag
; IOCB
bsf STATUS,RP1 ;Bank2
movlw b'01010000' ; Make ports 4 and 6 IOC
movwf IOCB
bcf STATUS,RP1
bsf STATUS,RP0 ;Bank1
; IOCA
movlw b'00001000' ;Make porta.3 IOC
movwf IOCA
;serial comm transmit
movlw d'25' ; Move the value of 25 or 0x19, to the baud rate genrator
movwf SPBRG ; this will make the baud rate = to 9600 at a 4mhz internal clock in high speed mode
bsf TXSTA,BRGH ;baud rate high
bsf TXSTA,TXEN ;Enable Transmission
bcf TXSTA,SYNC ;asynchronous
bcf STATUS,RP0 ;Exit bank 1
;Serial comm Receive
bsf RCSTA,SPEN ;Set serial port enable high
bsf RCSTA,CREN ; Enable receiver
; Initialize
clrf DISPLAY
clrf FIRSTDISP
clrf SECONDDISP
clrf THIRDDISP
clrf FOURTHDISP
movlw 0x01 ;Move 0x01 to ground
movwf GROUND
movlw 0x20 ;Start pointer at DISLAY location: 0x20
movwf FSR
;;;;;;;;;;;;;;;;;;
; MAIN ROUTINE ;
;;;;;;;;;;;;;;;;;;
MAIN:
bsf STATUS,RP0 ;goto bank 1 and turn off display
movlw 0xff
movwf TRISA
movlw 0xff
movwf TRISC
bcf STATUS,RP0 ;bank 0
;Begin rotate
movfw INDF
call TABLE
movwf PORTC
movfw GROUND
movwf PORTA
;Turn on Displays
bsf STATUS,RP0
movlw 0x00
movwf TRISC
movlw b'00001000'
movwf TRISA
bcf STATUS,RP0
;Finish rotating
rlf GROUND
call RA3
incf FSR
movlw 0x25
bcf STATUS,2
subwf FSR,w
btfsc STATUS,2
call CLEAR
goto MAIN
RA3:
btfss GROUND,3
return
rlf GROUND
return
CLEAR:
movlw 0x20
movwf FSR
movlw 0x01
movwf GROUND
return
TABLE: ;Generic lookup table
addwf PCL ; 1;
retlw b'00111111';0 ;;;;;;;
retlw b'00000110';1 6 ; ; 2
retlw b'01011011';2 ; 7 ;
retlw b'01001111';3 ;;;;;;;
retlw b'01100110';4 5 ; ; 3
retlw b'01101101';5 ; ;
retlw b'01111100';6 ;;;;;;;
retlw b'00000111';7 4
retlw b'01111111';8
retlw b'01100111';9
return
end
;end
The Breakdown:
Initialization:
We initialize our variables to hold temporary values as well as the current values in each display.
Ports:
We set all digital I/O, Port C all output, Port A 0,1,2,4,5 as output 3 as input, Port B 4,6 as input, 7 and 5 as TX and RX respectively. The I/O Structure for this code is:
PORTC – Primary 7-Segment driver
PORTA - Drives the line for the ground for the common cathode as well as the send input
PORTB – Handles serial comm., as well as increment and decrement inputs.
We use the interrupt on change for Port A and B; we set up that interrupt for its respectful inputs
We set up the UART module for asynchronous communication, high speed baud and we turn on TX and RX.
We finally clear the display variables, set the ground to the first position and set the pointer to look at the first digit’s memory location (0x20)
Main:
We turn off the outputs to prevent ghosting. Since we can only change one port at one time and the display uses to ports to display data we shut off the ports, change the value and then update the ports. Thus we prevent the previous value from being displayed on top of the current one. We display the current value by using the value in that digit’s block of memory into a lookup table and then outputting to PORTC, along with the respectable ground for that digit’s display being HIGH.
After that we update the pointer reference to the next digit and rotate the ground position. We must note however, when the ground is at RA3 (input only) we rotate it once more since were using it as input.
Once we reach the end of the rotation, we reinitialize the displays.
Interrupts:
The interrupt routine is fairly simple it handles 4 conditions.
-Increment pressed
-Decrement pressed
-Send pressed
-Value Receive
The first two conditions are fairly simple, the interrupt happens we pool for a button press and then we update the first digit’s value.
The send routine, pools for a button press, if it was the first digit is loaded into the TX reg and is sent out.
When a value is received, we enter the RX routine, we store the value into a temp register and we begin updating the displays.
Temp>>First>>Second>>Third>>Fourth>>Clear
We do this by rotating the display into the next one. I.e. the third display into the fourth, etc.
If no conditions are met, we just exit the interrupt.
Conclusion:
This is a fairly simple project but it takes advantage of the PIC’s resources and on board hardware, we end up using 19 of the 20 pins of the PIC MCU. This is a great demo for serial communication between two PIC MCUs. I’ve expanded this project to include two Zigbee RF modules in which the two modules talk to each other wirelessly.
Where to go from here:
There is one major downside to this project, and that is the displays. The 7-segments are horribly inefficient and lack much flexibility. One way I’d like to expand this project is to add a LCD screen and a keypad matrix to expand the user input and possible output.
General Note:
I went through the explanation of the code very quickly in this article. As the code gets more complicated I won’t be able to keep revisiting basic techniques and explaining everything. That is what the previous posts are for, to lay the basic ground work down. If you need further explanation feel free to email me, I’m glad to break it down if you need it.
Thanks once again for taking the time to read this, Source and hex as always, is below.
One of the last modules you will come to know and love is that of the USART module. There are many communication modules that PIC MCUs offer, but USART is going to be one of the simplest and easiest to get off the ground, sending and receiving data.
Fundamentals of communication:
There are two predominate forms of communication serial and parallel. In serial you send bit after bit and parallel you send word after word. Parallel had an advantage before we got some fast transfer rates as it could transfer more data at lower speeds, but it required more infrastructure and it was more error prone as well as complicated. Serial communication is very simple and can be done with as little as two wires. Because of its simplicity we use serial communication. With the USART module there are two types of serial communication, they are synchronous and asynchronous. Either case we have a Baud Rate, or the clock speed of the transmission. We must declare the baud rate before transmission can be done so that the hardware is in sync and expecting results at the right time
Asynchronous:
Asynchronous is full duplex communication, which means you can send an receive data at the same time. We can do this because we have a dedicated wire for transmission(TX) and a dedicated wire for receiving data (RX) Data can be received and sent at the same time. This method is very simple to set up and you can have the same code on all your devices. It is primarily used for device to device communication where synchronization is not important.
Synchronous:
Synchronous is half duplex, which means you can only send data or receive it a given time. In this setup we use one wire as data and the other as clock. In this setup we have a master PIC that sets the clock rate and salve PICs that run off of the master's clock. This is very useful for multi device communication but it requires two sets of code, one for the master and one for the slaves. This however provides great control over your application where the master can control the show and the slaves collect and format data. Lets actually look at what we have to do to send data.
The Registers:
PIE1: (bank 1) Peripheral interrupt enable register 1. This is the register that controls what peripheral interrupts we have on. To use a peripheral interrupt we must first set INTCON bit 6 high. (INTCON,PEIE)
PIR1: (bank 0) Stores the flags for the peripheral interrupts.
SPBRG: (bank 1) Baud rate generator. We move a literal into here which correlates to a baud rate for transmission. The actual baud rate depends on what you send and what the settings for transmission are.
TXSTA: (bank 1) Transmitter Status and Control register. This register controls weather we use asynchronous or synchronous communication as well as other functions related to the transmitter.
RCSTA: (bank 0) receive Status and Control Register. This register controls various operation of the receiver and its associated properties.
TXREG: (bank 0) transmission register, we move values to here that we want to be transmitted.
RCREG: (bank 0) receive register, bits received from transmission will be here. When we read this register the receive flag automatically clears.
Sample code:
#include
__config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)
org 0
cblock 0x20
TEMP
DISPLAY
DELAY1
DELAY2
W_TEMP
endc
goto PORTS;
org 0x04
INTERUPT:
movwf W_TEMP ;Store w to TEMP
bcf INTCON,7 ; Turn of interrupts
btfss PIR1,RCIF ; Wait until RCREG is full
goto $-1
bcf PIR1,RCIF ;Clear flag
movf RCREG,w ; Move RCREG to w
movwf DISPLAY ; Move w to DISPLAY
call ROUTINE
movf DISPLAY,w ; Move DISPLAY to w
movwf PORTC ; Move it to PORTC
movf W_TEMP,w ; Restore w
bsf INTCON,7 ; Turn on interrupts
retfie
ROUTINE:
bcf STATUS,0 ;Clear flag
rlf DISPLAY ;Rotate the display
btfss STATUS,0 ; Check flag
return ; Exit if not set
movlw 0x01 ; If it is, reset display
movwf DISPLAY
return
PORTS:
bsf STATUS,RP1 ;Bank 2
clrf ANSEL ; All pins are digital I/O
clrf ANSELH ; All pins are digital I/O
bcf STATUS,RP1 ;Bank 0
bsf INTCON,7 ;Set up global interrupts
bsf INTCON,6 ; Peripheral Interrupts
bsf STATUS,RP0 ;Bank 1
bsf PIE1,5 ; Interrupt on RX buffer full
bcf STATUS,RP0 ; Bank 0
bcf PIR1,RCIF ; Clear the flag
bsf STATUS,RP0 ; Bank 1
movlw 0x00 ; Move the hex value of 0 to multi purpose register W
movwf TRISC ; make IO PortC all output
movlw 0xff ; 0xff to w
movwf TRISA ;PORTA input
;serial comm transmit
movlw d'25' ; Move the value of 25 or 0x19, to the baud rate generator
movwf SPBRG ; this will make the baud rate = to 9600 at a 4mhz internal clock in high speed mode
bsf TXSTA,BRGH ;baud rate high
bsf TXSTA,TXEN ;Enable Transmission
bcf TXSTA,SYNC ;asynchronous
bcf STATUS,RP0 ;Exit bank 1
;Serial comm Receive
bsf RCSTA,SPEN ;Set serial port enable high
bsf RCSTA,CREN ; Enable receiver
clrf PORTB ; Clear
clrf PORTC ; Clear display
movlw 0x01 ; Initialize Display
movwf DISPLAY
MAIN:
bsf PORTC,0 ; Portc high
btfsc PORTA,3 ; Poll the button press
goto $-1 ; Goto instruction - 1
call TX ; Call transmission
bcf PORTC,0 ; Clear port c,0
call DELAY ; Call delay
goto MAIN
TX:
btfss PIR1 , TXIF ; TXIF is set if TXREG has no character
goto $-1 ; Loop until TXIF is cleared
movf DISPLAY,w ; Data to be transmitted over serial port
movwf TXREG ; Copy data to transmit register
bsf STATUS,RP0 ; RAM PAGE 1
btfss TXSTA , TRMT ; Wait until transmission is completed
goto $-1 ; Loop back until transmission completed
bcf STATUS,RP0
return
DELAY: ; Generic Dela routine
decfsz DELAY1
goto DELAY
decfsz DELAY2
goto DELAY
return
end
This code is fairly complicated but its not horrible. This is basic code for asynchronous transmission which transmits a value, which triggers an interrupt for the receiving PIC. That receiving PIC then takes the transmitted value and performs an RLF operation on the data then out puts it to PORTC.
The Routine:
1) Startup – Declare environmental variables and goto main port routine
2) Ports – Set up the ports for digital I/O, Enable Interrupts, Enable peripheral interrupts, Enable asynchronous transmitter and receiver. Initialize Display to 1
3) Pool the button and wait for press
4) Button is pressed, Transmit DISPLAY by loading it into the TXREG register and waiting for it to shift out.
5) Delay is called to De-bounce switch.
6) Other PIC receives value, interrupt triggers when RXREG fills
7) Move RXREG into DISPLAY and rotate the bit (if carry reset to 0x01)
8) Return from interrupt
The Breakdown:
;serial comm transmit
movlw d'25' ; Move the value of 25 or 0x19, to the baud rate generator
movwf SPBRG ; this will make the baud rate = to 9600 at a 4mhz internal clock in high speed mode
bsf TXSTA,BRGH ;baud rate high
bsf TXSTA,TXEN ;Enable Transmission
bcf TXSTA,SYNC ;asynchronous
bcf STATUS,RP0 ;Exit bank 1
;Serial comm Receive
bsf RCSTA,SPEN ;Set serial port enable high
bsf RCSTA,CREN ; Enable receiver
That is the bulk of the code, This section sets up our PIC for serial communication.
The most important part of this section of code is the baud rate. We primarily derive the baud rate from the data sheet and the frequency we run our PIC at. We generally want the least amount of error for our application. For 4MHZ 9600bps in high speed mode is most efficient and least error-prone. If we look at the data sheet to set that baud rate we send 0x19 to SPBRG.
Because we want high speed mode, we get BRGH high, We want to enable the transmitter so we enable TXEN and we want asynchronous communication so we set SYNC to 0.
For RCSTA we want to enable the serial port so we set SPEN high and we want to enable the receiver so we set CREN high.
This is the basic setup for the most efficient asynchronous data transmission with a 4MHZ internal oscillator .
For synchronous and other types of setups refer to the data sheet, its all very self explanatory. One thing you have to consider, there is going to be different setups for the MASTER PIC and the SLAVE PICs.
TX: btfss PIR1 , TXIF ; TXIF is set if TXREG has no character goto $-1 ; Loop until TXIF is cleared movf DISPLAY,w ; Data to be transmitted over serial port movwf TXREG ; Copy data to transmit register bsf STATUS,RP0 ; RAM PAGE 1 btfss TXSTA , TRMT ; Wait until transmission is completed goto $-1 ; Loop back until transmission completed bcf STATUS,RP0 return
This is the primary code that does the transmission. We first poll PIR1,TXIF to make sure it is clear, then we move the value we want into TXREG starting the transmission. We wait for the transmission to complete and then we leave.
Once the other PIC receives a value it triggers an interrupt and the following code gets executed.
btfss PIR1,RCIF ; Wait until RCREG is full goto $-1 bcf PIR1,RCIF ;Clear flag movf RCREG,w ; Move RCREG to w movwf DISPLAY ; Move w to DISPLAY
We wait until RCREG is full, then we move the value to DISPLAY (or wherever we want).
There we have it. Asynchronous data transmission from one PIC to another. Source and hex are below. Feel free to take and modify it. This will not be the only article on data transmission. This will be a topic we will be visiting frequently and getting very in depth with.
Pointers are a very fundamental part of programming, often you need to reference a memory location without directly addressing or calling it, pointers help us do that. With c/c++ pointers can be a little daunting because how they work isn't directly apparent. Luckily for us, assembly works directly with the ram of the MCU and using pointers is extremely easy and almost effortless.
The use of pointers with a PIC mcu is called indirect addressing. We use the FSR(File Select register) and INDF. Essentially we load the value we want to point to into FSR and we address it using INDF. Fairly simple, lets look at an example:
VALUE equ 0x20 movlw 0x03 movwf VALUE movlw 0x20 movwf FSR movf INDF,w movwf PORTC
The value on port c will be 0x03
First, we set up our variable VALUE to the general purpose address of 0x20.
Then, we set it equal to 0x03
We move the value of 0x20 to FSR (the location of VALUE)
Then we move INDF (The value of the address we loaded into FSR in this case 0x03) to w
Then w to PORTC
Very simple, with this in place you can save memory and make your programs extremely efficient and quick. Using the FSR and INDF will be essential to any remotely complicated code such as communications and robotics and other advance topics.