PIC16F84A 7 Segment Digital Clock

From spiderprojects
Jump to: navigation, search

The Story

Our Direct TV cable box doesn't have a clock on the face and the weather station clock I have on my nightstand isn't back lit unless you hit a button on it. So I decided to build my own LED clock. Electronica-pt.com has a great project posted complete with a schematic and assembly code for this very project. I used this as my baseline and made only a few modifications to get everything working to my satisfaction.

Circuit

I started with the Electronica-pt.com example, but modified the circuit to use 2, 2 digit seven segment displays instead of 4 single digit ones. I also added pull up resistors to the minute and hour push buttons. The last piece I added was a potentiometer to the ground line for the driving transistors to allow the LED brightness to be adjusted.

PIC16F84A Digital Clock


When it came time to layout the PCB design for this project I decided to break it up into 3 boards. One for the display, one for the push buttons, and one for the PIC and other components.


PCBDisplay.png
PCBSetBoard.png
PCBMainBoard.png
PCB Design


Once satisfied with the layout I transferred it to copper board using the toner transfer method. I designed these boards to be single sided, but etched the mirror pattern on one side. This will give me a cool display while ensuring one of the 2 paths has continuity. Plus I can easily solder on one side or the other, making sure I have continuity where it's hard to get in underneath components like the oscillator.


PCB Etched
PCBEtched.jpg
PCB Cleaned
boarder
PCB Tinned
boarder

Program

I built the circuit on a breadboard, programmed the PIC with the provided code and let it run for a while. After a couple days the clock was running a couple minutes slow. I set out to modify the code, but ended up piecing together the provided code and Roman Black's Zero Error 1 Second Timer code.

 Assembly code for the PIC16F84 Digital Clock
;******************************************************************************
; ZERO-ERROR ONE SECOND TIMER
; (Roman Black 2001, public domain, use it as you like)
; 
; INTERRUPT VERSION
;
; for PIC 16F84 at 4 MHz (or most PICs)
; (this code has been assembled with MPLAB and hardware tested)
; (for best viewing set TABS=5 in MPLAB editor)
;
; Note! See text: www.RomanBlack.com/one_sec.htm
;
; Generates an event every second (or other period) from any PIC
; with any clock frequency.
;
; This version uses the timer0 overflow interrupt.
; Code can be adapted for different clock speeds, period lengths
; and accuracy levels.
;
;******************************************************************************


;==============================================================================
; processor defined ;
	include <p16f84.inc>

;==============================================================================
; MPLAB stuff here

	LIST b=5, n=97, t=ON, st=OFF
	; absolute listing tabs=5, lines=97, trim long lines=ON, symbol table=OFF

	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
	; config for 16F84; code protect OFF, watchdog OFF, powerup timer ON,
	; oscillator is XT ready for 4 MHz crystal or resonator.

;==============================================================================
; Variables here

	CBLOCK 0x20			; start of ram in 16F84

		bres_hi			; hi byte of our 24bit variable
		bres_mid			; mid byte
		bres_lo			; lo byte
						; (we only need 3 bytes for this system)

		status_temp		; used for interrupt servicing
		w_temp			; used for interrupt servicing

		S1		;Seconds 1s digit
		S10		;Seconds 10s digit
		M1		;Minutes 1s digit
		M10		;Minutes 10s digit
		H1		;Hours 1s digit
		H10		;Hours 10s digit
		DEL		;Delay Variable
		DEL0	;Delay 0 Variable
		DEL01	;Delay 1 Variable
		DEL02	;Delay 2 Variable
		BLNKR	;Decimal Blinker

		INCRF	;Increment Flag
	ENDC


;==============================================================================
; Code here

	org 0x000 			; Set program memory base at reset vector 0x000
reset
	goto setup			; set up ints and port stuff

	org 0x004				; Interrupt vector, int handler code comes next.
;==============================================================================


;******************************************************************************
;  INTERRUPT HANDLER     (runs this code each timer0 interrupt)
;******************************************************************************
;
;------------------
int_handler				;
;------------------

	;-------------------------------------------------
						; first we preserve w and status register

	movwf w_temp      		; save off current W register contents
	movf	STATUS,w          	; move status register into W register
	movwf status_temp       	; save off contents of STATUS register

	;-------------------------------------------------
	; Note! we get here every 256 instructions, we
	; can now do our special one second timing system.				

	; This consists of three main steps;
	; * subtract 256 counts from our 24bit variable
	; * test if we reached the setpoint
	; * if so, add 1,000,000 counts to 24bit variable and generate event.
	;-------------------------------------------------
						; * optimised 24 bit subtract here 
						; This is done with the minimum instructions.
						; We subtract 256 from the 24bit variable
						; by just decrementing the mid byte.

	tstf bres_mid			; first test for mid==0
	skpnz				; nz = no underflow needed
	decf bres_hi,f			; z, so is underflow, so dec the msb

	decfsz bres_mid,f		; dec the mid byte (subtract 256)

						; now the full 24bit optimised subtract is done!
						; this is about 4 times faster than a "proper"
						; 24bit subtract.

	goto int_exit			; nz, so definitely not one second yet.
						; in most cases the entire int takes
						; only 16 instructions.
	;------------------------
						; * test if we have reached one second.
						; only gets here when mid==0, it MAY be one second.
						; only gets to here 1 in every 256 times.
						; (this is our best optimised test)
						; it gets here when bres_mid ==0.

	tstf bres_hi			; test hi for zero too
	skpz					; z = both hi and mid are zero, is one second!
	goto int_exit			; nz, so not one second yet.

	;-------------------------------------------------
	; Only gets to here if we have reached one second.

	; now we can generate our one second event, like add
	; one second to our clock or whatever.
	; (in this example we toggle a led)

	; The other thing we need to do is add 1,000,000 counts
	; to our 24bit variable and start all over again.
	;-------------------------------------------------
						; Add the 1,000,000 counts first.
						; One second = 1,000,000 = 0F 42 40 (in hex)

						; As we know hi==0 and mid==0 this makes it very fast.
						; This is an optimised 24bit add, because we can
						; just load the top two bytes and only need to do
						; a real add on the bottom byte. This is much quicker
						; than a "proper" 24bit add.

	movlw 0x0F			; get msb value 
	movwf bres_hi			; load in msb

	movlw 0x42			; get mid value
	movwf bres_mid			; load in mid

	movlw 0x40			; lsb value to add
	addwf bres_lo,f		; add it to the remainder already in lsb
	skpnc				; nc = no overflow, so mid is still ok

	incf bres_mid,f		; c, so lsb overflowed, so inc mid
						; this is optimised and relies on mid being known
						; and that mid won't overflow from one inc.

						; that's it! Our optimised 24bit add is done,
						; this is roughly twice as quick as a "proper"
						; 24bit add.
	;-------------------------
						; now we do the "event" that we do every one second.

						; Note! for this example we toggle a led, which
						; will give a flashing led which is on for a second
						; and off for a second.
						; Add your own code here for your one second event.

						; Note! My led is on porta,3
						; your led may be on a different pin.
	movlw b'00000001'		; mask for bit 3
	;xorwf PORTA,f			; toggle PORTA,bit3 (toggle the led)
	movwf INCRF			;Set increment flag bit 0
						
	;-------------------------------------------------
	; now our one second event is all done, we can exit the
	; interrupt handler.
	;-------------------------------------------------
						; finally we restore w and status registers.
						; also clears TMRO int flag now we are finished.
int_exit
	BCF INTCON,T0IF		; reset the tmr0 interrupt flag

	movf status_temp,w     	; retrieve copy of STATUS register
	movwf STATUS            	; restore pre-isr STATUS register contents
	swapf w_temp,f
	swapf w_temp,w          	; restore pre-isr W register contents
	retfie				; return from interrupt
;------------------------------------------------------------------------------



;******************************************************************************
;  SETUP     (runs this only once at startup)
;******************************************************************************
;
;------------------
setup					; goto label
;------------------

	;-------------------------------------------------
	; Note! 16F84 version.
	; Note! here we set up peripherals and port directions.
	; this will need to be changed for different PICs.
	;-------------------------------------------------
						; OPTION setup
	movlw b'10001000'		;
		;  x-------		; 7, 0=enable, 1=disable, portb pullups
		;  -x------		; 6, 1=/, int edge select bit
		;  --x-----		; 5, timer0 source, 0=internal clock, 1=ext pin.
		;  ---x----		; 4, timer0 ext edge, 1=\
		;  ----x---		; 3, prescaler assign, 1=wdt, 0=timer0
		;  -----x--		; 2,1,0, timer0 prescaler rate select
		;  ------x-		;   000=2, 001=4, 010=8, 011=16, etc.
		;  -------x		; 
						; Note! We set the prescaler to the wdt, so timer0
						; has NO prescaler and will overflow every 256 
						; instructions and make an interrupt.
						;
	banksel OPTION_REG		; go proper reg bank
	movwf OPTION_REG		; load data into OPTION_REG
	banksel 0				; back to normal bank 0
	;-------------------------------------------------
						; PORTB pins direction setup
						; 1=input, 0=output
	clrf PORTB			;
						;
	movlw b'00000000'		; all 8 portb are outputs
						;
	banksel TRISB			; go proper reg bank
	movwf TRISB			; send mask to portb
	banksel 0				; back to normal reg bank
	;-------------------------------------------------
						; PORTA pins direction setup
						; 1=input, 0=output
	clrf PORTA			;
						;
	movlw b'00000000'		; all 5 porta are outputs,
						; (with 16F84 porta only has lower 5 bits)
						;
	banksel TRISB			; go proper reg bank
	movwf TRISA			; send mask to porta
	banksel 0				; back to normal reg bank
	;-------------------------------------------------
						; INTCON setup
						;
						; for this code example, we enable the timer0
						; overflow interrupt.
						;
						; enable interrupts last
						; interrupt setup
	movlw b'10100000'		; GIE=on TOIE=on (timer0 overflow int)
	movwf INTCON			; set up.

	;-------------------------------------------------
	; Note! Now the hardware is set up we need to load the
	; first count for one second into our 24bit bres variable.
	;-------------------------------------------------
						; Note! This example uses 4 MHz clock, which is
						; 1,000,000 counts per second.
						;
						; We require a 1 second period, so we must load
						; 1,000,000 counts each time.
						; 1,000,000 = 0F 42 40 (in hex)
						;
						; We also need to add 256 counts for the first time,
						; so we just add 1 to the mid byte.
						; Check mid overflow if needed.

						; here we load the 24bit variable.
	movlw 0x0F			; get msb value 
	movwf bres_hi			; put in hi

	movlw 0x42 +1			; get mid value (note we added 1 to it)
	movwf bres_mid			; put in mid

	movlw 0x40			; get lsb value
	movwf bres_lo			; put in mid

	CLRF	S1
	CLRF	S10
	CLRF	M1
	CLRF	M10
	CLRF	H1
	CLRF	H10
	CLRF	DEL
	CLRF	DEL0
	CLRF	DEL01
	CLRF	DEL02
	CLRF	BLNKR
	BSF	STATUS,RP0
	CLRF	TRISB
	CLRF	TRISA
	BSF		INTCON,T0IE
	BSF		INTCON,GIE
	BCF		STATUS,RP0
						; now setup is complete, we can start execution.
	;-------------------------------------------------
	goto main				; start main program

;------------------------------------------------------------------------------



;******************************************************************************
;  MAIN     (main program loop)
;******************************************************************************
;
;------------------
main						; goto label
;------------------

	;-------------------------------------------------
	; Note! This example uses the timer0 overflow interrupt.
	; This will interrupt our main program every 256 instructions
	; and do the one second timer system.
	;-------------------------------------------------
main_loop					; 
						; Note! here you have your main program code,
						; or calls to the main program pieces.
						; The interrupt does all the one second timer stuff.
	CALL	KEY
	CALL	SCAN
	BTFSC	INCRF,0		;Test if the increment flag, bit0 is set
	CALL	INCRSR		;Run Increment Subroutine
	; stuff
	; stuff

	;-------------------------------------------------
	goto main_loop			; keep running the main code.

;------------------------------------------------------------------------------

;SCAN FOR KEY PRESS
;--------------------------------------------------------
KEY		;BSF		OPTION_REG,7	;Disable PortB Pullup
		;BCF		INTCON,GIE		;Disable Interrupt
		BSF		STATUS,RP0		;Bank 1
		MOVLW	B'11101111'		;PortB 4 output
		MOVWF	TRISB			;PortB 4 output
		BCF		STATUS,RP0		;Bank 0
		MOVLW	B'00000000'		;Clear PortB (Sets Z)
		MOVWF	PORTB			;Clear PortB
		CALL	DELAY02			;2+9us
;SET MINITUS
;  **********************************
SM1	BTFSC	PORTB,1 ;Check if Minute set is pressed (Pressed=0)
	GOTO	SH1		;if not goto hours
	CALL	INCR_SM	;if yes increment minutes
	GOTO	KEYX	;exit keypress
;***********************************
;SET HOUR
;***********************************
SH1	CALL	DELAY02 ;2+9us
	BTFSC	PORTB,2 ;Check if Hour set is pressed (Pressed=0)
	GOTO	KEYDE	;if not goto main loop
	CALL	INCR_SH	;if yes inrement hours
;***********************************

;***********************************
KEYX	BSF		STATUS,RP0	;Bank 1
		CLRF	TRISB		;PortB all output
		BCF		STATUS,RP0	;Bank 0
		MOVLW	.100		;Move 100 into DEL02
		MOVWF	DEL02		;Move 100 into DEL02
		CALL	SCAN		;Loop through display scan 100 times
		DECFSZ	DEL02,1		;
		GOTO	$-.2		;
		;BSF		INTCON,GIE	;Enable Interrupt
		;BCF		OPTION_REG,7;Enable PortB Pullup
		RETURN
		
KEYDE	BSF		STATUS,RP0	;Bank 1
		CLRF		TRISB	;PortB all output
		BCF		STATUS,RP0	;Bank 0
		;BSF		INTCON,GIE	;Enable Interrupts
		;BCF		OPTION_REG,7;Enable PortB Pullup
		RETURN
;--------------------------------------------------------

;INCREMENT Routine
;--------------------------------------------------------
INCRSR
	movlw b'00000001'		; mask for bit 0
	xorwf BLNKR,f			; toggle PORTB,bit0 (toggle the led)
	Call INCR	;Increment seconds	
	CLRF INCRF	;Clear the increment flag
	RETURN
;--------------------------------------------------------

;INCREMENT 1 SECOND AND CASCADE IF APPLICABLE
;--------------------------------------------------------
;SECONDS
INCR	INCF	S1,1		;1us
		MOVF	S1,0		;1us
		BCF		STATUS,Z	;1us
		XORLW	.10			;1us
		BTFSS	STATUS,Z
		RETURN
		CLRF	S1
		INCF	S10,1
		MOVF	S10,0
		BCF		STATUS,Z
		XORLW	.6
		BTFSS	STATUS,Z
		RETURN
		CLRF	S10
;MINUTES
INCR_SM	INCF	M1,1
		MOVF	M1,0
		BCF		STATUS,Z
		XORLW	.10
		BTFSS	STATUS,Z
		RETURN
		CLRF	M1
		INCF	M10,1
		MOVF	M10,0
		BCF		STATUS,Z
		XORLW	.6
		BTFSS	STATUS,Z
		RETURN
		CLRF	M10
;HOURS
INCR_SH	INCF	H1,1
		SWAPF	H10,0
		ADDWF	H1,0
		BCF		STATUS,Z
		XORLW	13h
		BTFSS	STATUS,Z
		GOTO	$+6
		CLRF	H1
		CLRF	H10
		MOVLW .1	
		MOVWF	H1
		RETURN
		MOVF	H1,0
		BCF		STATUS,Z
		XORLW	.10
		BTFSS	STATUS,Z
		RETURN
		CLRF	H1
		INCF	H10,1
		RETURN
;--------------------------------------------------------

;DELAY01 770us
;--------------------------------------------------------
DELAY01	DECFSZ	DEL,1	;255*1us/2us
		GOTO	$-.1	;255*2us
		CLRF	PORTB	;1us
		RETURN			;2us
;--------------------------------------------------------

;DELAY02 9us
;--------------------------------------------------------	
DELAY02	MOVLW	.2		;1us
		MOVWF	DEL01	;1us
		DECFSZ	DEL01,1	;1us/2us
		GOTO	$-.1	;2us
		RETURN			;2us
;--------------------------------------------------------	

; SEVEN SEGMENT DISPLAY CONNECTING  TABLE 3us
;--------------------------------------------------------	
TABLE	ADDWF	PCL,1;				hgfedcba  segments
		RETLW	B'01111110'		;0 ;
		RETLW	B'00001100'		;1
		RETLW	B'10110110'		;2
		RETLW	B'10011110'		;3
		RETLW	B'11001100'		;4
		RETLW	B'11011010'		;5
		RETLW	B'11111010'		;6
		RETLW	B'00001110'		;7
		RETLW	B'11111110'		;8
		RETLW	B'11011110'		;9
;--------------------------------------------------------
		
;SCAN THROUGH THE 7Seg DISPLAYS
;--------------------------------------------------------		
SCAN	MOVLW	B'00000001'  ;SEGMENT 01
		MOVWF	PORTA
		MOVF	M1,0
		CALL	TABLE
		IORLW	BLNKR
		MOVWF	PORTB
		CALL	DELAY01
		MOVLW	B'00000010'	;SEGMENT 02
		MOVWF	PORTA
		MOVF	M10,0
		CALL	TABLE
		IORLW	BLNKR
		MOVWF	PORTB	
		CALL	DELAY01	
		MOVLW	B'00000100'	;SEGMENT 03
		MOVWF	PORTA
		MOVF	H1,0
		CALL	TABLE
		IORLW	BLNKR
		MOVWF	PORTB	
		CALL	DELAY01
		MOVLW	B'00001000'	;SEGMENT 04
		MOVWF	PORTA
		MOVF	H10,0
		CALL	TABLE
		IORLW	BLNKR
		MOVWF	PORTB
		CALL	DELAY01
		RETURN
;--------------------------------------------------------


;==============================================================================
	end					; no code after this point.
;==============================================================================

The new code is well commented and the clock ran accurately for over a week.

Finished Product

I still have to populate the board and fabricate the enclosure, but I'll post pictures when I have one complete.