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 Leds | Homepage microcontroller Index |
INSIDER V1.0> |
