;****************************************************************************** ; 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 ;============================================================================== ; 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 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'00001000' ; mask for bit 3 xorwf PORTA,f ; toggle PORTA,bit3 (toggle the led) ;------------------------------------------------- ; 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 ; 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. ; stuff ; stuff ; stuff ; stuff ;------------------------------------------------- goto main_loop ; keep running the main code. ;------------------------------------------------------------------------------ ;============================================================================== end ; no code after this point. ;==============================================================================