Aqui os dejo un programa basado en el 16f84 a 4mhz, pero puede ir para cualquier micro y cualquier frecuencia.
Es extremadamente exacto, al menos simulado en MPLAB me da un error de 0 us en un minuto porque se autocorrige!!!
Entiendo que en la realidad puede ser otra cosa, pero vamos... que aún no enviaremos ningún cohete a Marte tripulado con un pic
El siguiente setup es para 4 mhz, entonces el valor a cargar en los 3 bytes de corrección es 1.000.000, o sea 0F 42 40 en hexa. Pero si quereis por ejemplo usar un xtal de 20mhz no teneis más que cambiar esos valores por algo 5 veces mas grande, o sea 5.000.000 que serían 4C 4B 40 en hexa.
Tomado de la web
http://www.romanblack.com/one_sec.asm;******************************************************************************
; 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
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.
;==============================================================================