How to get 1 Second with Microcontrollers.

Roman Black's source code improved and updated. Here is how to implement 1-second timebase for microcontrollers projects. (August 12, 2009)

 

On his web page, Roman Black indicates: "A very versatile Zero Cumulative Error timing system with PIC source code. What is it?

 

Bresenham's Algorithm is a system where 2 imperfect periods can be alternated to produce an average that matches any 'perfect' period. With most modern micros the easiest time period to generate is an overflow of the internal timer, generally 256 ticks or 65536 (256x256) ticks. Unfortunately, since most of these Micros run at crystal speeds like 4MHz and 20MHz, these overflow timed periods generated are binary and cannot be evenly divided into 1 second or any easily usable real-world clock value.

 

Brilliant programmer Bob Ammerman recognized this fact and mentioned his use of a Bresenham-type system for PIC micros. Later I did some more work on the idea to speed-optimize it for the PIC timer0 overflow which is available on all PICs and release the results as public domain open source. It should also work on any other micro with a binary-multiple internal timer.
"

 

To understand this theory, we need to know:

 

1st, the Microchip PIC uses 4 clock cycles per instruction, so if we use a 4Mhz Xtal, we will get exactly 1,000,000 instructions each second. [That is one million]
2nd, timer0 overflows every 256 instructions and can generate an interrupt.

 

So, based on this two facts, Let's see how Black's code works:

 

1.- Set a 24-bits constant value to a 24-bits period variable. The value is determined as Value = Osc Freq / 4.
2.- Every timer0 overflow; subtract 256 from a 24-bits period variable, and;
3.- When period variable goes less than zero; generate the 1 second event and ADD the 24-bits constant value (1,000,000 when Xtal = 4Mhz)

 

The sample code provided by Mr Black doesn't work anymore on PICs as instructions were changed on newer micro controllers from microchip. The first code was taken from Black's web page and the second one shows the updates that I made.

 

; ORIGINAL ZERO-ERROR ONE SECOND TIMER
; INTERRUPT VERSION

; Variables here
bres_hi		; hi byte of our 24bit variable
bres_mid		; mid byte
bres_lo		; lo byte
status_temp	; used for interrupt servicing
w_temp		; used for interrupt servicing

; Code here
org 0x000	; Set reset vector 0x000
goto setup 	; Setup interrupt and port stuff
org 0x004	; Interrupt vector

int_handler
movwf w_temp   ; save W & STATUS contents
movf STATUS,w
movwf status_temp
tstf bres_mid	; bres_mid =0?
skpnz		; no underflow, subtract 256
decf bres_hi,f	; underflow, so dec the msb
decfsz bres_mid,f
goto int_exit	; Not a second yet
tstf bres_hi	; test hi for zero too
skpz		; hsb & msb = 0, one second!
goto int_exit	; nz, so not one second yet.

; One second, so ADD the 24-bits constant
; value to the 24-bit period variable
movlw 0x0F 	; 0x0F4240 = 1,000,000
movwf bres_hi	; when Xtal = 4Mhz
movlw 0x42
movwf bres_mid
movlw 0x40
addwf bres_lo,f	; add the remainder in lsb
skpnc		; no overflow, so mid is still ok
incf bres_mid,f	; lsb overflowed, so inc mid

; Set the event when 1 second is reached,
; for example, set a flag, toggle a pin, etc
movlw b'00001000'
xorwf PORTA,f	; toggle bit 3 in port A

; LABEL int_exit
int_exit
; Reset tmr interrupt flag and restore registers
BCF INTCON,T0IF
movf status_temp,w
movwf STATUS
swapf w_temp,f
swapf w_temp,w
retfie		; return from interrupt

; LABEL setup, here starts the main code
setup
; disable pullups and set prescaler to WDT
movlw b'10001000'
banksel OPTION_REG
movwf OPTION_REG
banksel 0
clrf PORTA	;set port A as output
movlw b'00000000'
banksel TRISB
movwf TRISA
banksel 0
; set GIE ON and TOIE ON for interrupts
movlw b'10100000'
movwf INTCON

movlw 0x0F 	; get msb value
movwf bres_hi	; put in hi
movlw 0x42 +1	; get mid value
movwf bres_mid	; put in mid
movlw 0x40	; get lsb value
movwf bres_lo	; put in mid
goto main

; LABEL main
main

; put your main code here

goto main	; Loop the main code.
end

; UPDATED ZERO-ERROR ONE SECOND TIMER
; INTERRUPT VERSION - IMPROVED

; Variables here
bres_hi		; hi byte of our 24bit variable
bres_mid		; mid byte
bres_lo		; lo byte
value_hi		; hi byte of 1-second value
value_mid
value_lo
status_temp	; used for interrupt servicing
w_temp		; used for interrupt servicing

; Code here
org 0x000	; Set reset vector 0x000
goto setup 	; Setup interrupt and port stuff
org 0x004	; Interrupt vector

int_handler
movwf w_temp	; save W & STATUS contents
movf STATUS,w
movwf status_temp
movf bres_mid	; bres_mid =0?
btfsc status, z	; no underflow, subtract 256
decf bres_hi,f	; underflow, so dec the msb
decfsz bres_mid,f
goto int_exit	; Not a second yet
movf bres_hi	; test hi for zero too
btfss status,z	; hsb & msb = 0, one second!
goto int_exit	; nz, so not one second yet.

; One second, so ADD the 24-bits constant
; value to the 24-bit period variable
movf value_hi,w	; move value from variable
movwf bres_hi 	; so we don't change the code
movf value_mid,w
movwf bres_mid
movf value_lo,w
addwf bres_lo,f	; add the remainder in lsb
btfsc status, c	; no overflow, so mid is still ok
incf bres_mid,f	; lsb overflowed, so inc mid

; Set the event when 1 second is reached,
; for example, set a flag, toggle a pin, etc
movlw b'00001000'
xorwf PORTA,f	; toggle bit 3 in port A

; LABEL int_exit
int_exit
; Reset tmr interrupt flag and restore registers
bcf INTCON,T0IF
movf status_temp,w
movwf STATUS
swapf w_temp,f
swapf w_temp,w
retfie		; return from interrupt

; LABEL setup, here starts the main code
setup
; disable pullups and set prescaler to WDT
movlw b'10001000'
banksel OPTION_REG
movwf OPTION_REG
banksel 0
clrf PORTA	;set port A as output
movlw b'00000000'
banksel TRISB
movwf TRISA
banksel 0
; set values -  SET HERE the 1sec value
movlw 0x0f	; value_hi = 0x0f
movwf value_hi
movlw 0x42	; value_mid = 0x42
movwf value_mid
movlw 0x40	; value_lo = 0x40
movwf value_lo
movf value_hi,w 	; set values for 1sec
movwf bres_hi
movf value_mid,w
movwf bres_mid
movf value_lo
movwf bres_lo

; set GIE ON and TOIE ON for interrupts
movlw b'10100000'
movwf INTCON
goto main

; LABEL main
main

; put your main code here

goto main	; Loop the main code.
end

 

Here are the three main differences:

 

1. The value for 1 second was set to a variable, so no need to change it twice in the source code and it can be set or change during the main code.
2. No "+1" value was added to the bres_mid when starting but it can be implemented anyway.
3. The instructions "tstf" and "skpz" were replaced with the actual instructions for modern PIC microcontrollers: "addwf" and "btfsc" or "btfss". Code comparison:

 

ORIGINAL BLACK's CODE -
tstf 	bres_mid
skpnz
decf 	bres_hi,f
decfsz 	bres_mid,f
goto 	int_exit
tstf 	bres_hi
skpz
goto 	int_exit
UPDATED MICROCHIP CODE
movf 	bres_mid
btfsc 	status, z
decf 	bres_hi, f
decfsz 	bres_mid, f
goto 	int_exit
movf 	bres_hi
btfss 	status,z
goto 	int_exit

 

Now we will be able to build some clocks and/or timer projects using PIC micro controllers.

 


 < How To Control Multiple LedsHomepage
microcontroller Index
INSIDER V1.0> 

 

If any information, data, picture or design infringes a copyrighted material, please send me an e-mail asking to remove it along with the supporting data.
Some features may not work with Google Chrome. © 2006-2010 Jose Pino - Powered by JPC Alpha