| « Intro to the HD44780 character based LCD | Ground control to Major Tom. (Serial communication) » |
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.