Accurate TMR0 Interrupts
Accurate TMR0 Interrupts
A very versatile Zero Cumulative Error timing system with PIC source code
Roman Black - orig June 2001 - update Aug 2006 - update 21 Nov 2009 - again 21st Feb 2011.
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 recognised 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-optimise 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.
All code on this page is open-source, please mention me if you use my methods or code.
Basic Theory
Bresenham's algorithm was originally designed for speedily calculating imperfect periods in grid
movement on a 2 dimensional matrix like an X-Y Plotter. A similar system can be used for
generating one average timed period from ANY other timed period (like the PIC timer0 overflow
period).
So we can generate a "perfect" average 1 second period from the imperfect PIC timer0 overflow in
a way which is very fast and leaves the PIC with a lot of free time for other tasks. The big advantage
is that the 2 periods are completely independant, so ANY crystal speed can be used to generate
ANY timed period.
The basic theory is shown above. A 20 unit period is needed, but the closest period the PIC can
generate is 16 or 24 units (for example). So the PIC alternates between periods of 16 and 24 to
generate a "perfect" period of 20. Athough each generated period itself is imperfect, the overall
average period is perfect because this is a "Zero Cumulative Error" system.
Making a 1 second period with any PIC (assembler)
Basic procedure to generate a 1 second period;
Because we ADD the next 1000000 ticks to the next second, the cumulated error is still contained
within the Period variable. This means that the NEXT second will be adjusted by the error that was
left in the variable. Every period will self-adjust it's length so over time there is Zero Cumulative
Error.
Using a 3 byte Period variable means it can subtract 256 simply by decrementing the MID byte
Instead of going BELOW zero, event is generated on HIGH and MID bytes both equal zero,
which is easier to test and avoids handling negative values
When event is detected, HIGH and MID bytes must equal zero, so the 24-bit add becomes many
times faster than a "proper" 24-bit add.
To summarise, it is extremely quick and easy to subtract the 256 ticks for every timer0 overflow,
and it is extremely quick and easy to ADD the 24-bit value for the next timed period.
This first source code generates a 1 second event from a PIC with 4MHz crystal and the timer0
overflow interrupt. This is the code that is easiest to use for most designs.
This next source code generates a 1 second event from a PIC with 4MHz crystal and timer0, but
does not require an interrupt so it will still work with the cheapest PICs, or for designs where you
choose not to use the interrupt.
Note! You can use the 2 code examples above on any PIC and with any crystal. Just adjust the 24-
bit period value bres_ to the number of timer0 ticks per second and it will generate a 1 second
period. You can also generate longer or shorter periods than 1 second simply by changing that
value.
C code for a 1 second period with a funky 12.391 MHz junkbox xtal);
C code for a 1 second period with a 16bit timer1 at 1MHz (4MHz xtal);
Optimised C code for a 1 second period with a 1MHz timer (4MHz xtal);
The next one was for my master LED clock for my home automation system. It uses a Dallas
DS32KHz temperature compensated high accuracy oscillator module. But I need to display
hundredths of seconds on my nice 12digit LED display, and the oscillator produces 32768
pulses/second, so a hundredth of a second is 327.68 pulses... Ouch!
Fortunately it is easy to make hundredths of seconds (with zero error) from 32768Hz using a
bresenham algorithm. I just test TMR1 bit3 in another interrupt, and check for anytime TMR1L.F3
toggles, which occurs 2048 times each second. This next math may not be immediately obvious but
if we have an event every 1/2048th of a second, and add 100 every event, then after 1 second we
have a total of 204800. So now it becomes obvious that every 1/100th of a second that value grows
by 2048. See below.
The next example was to allow generating of 100th second period from a incoming frequency of
120Hz from the US mains voltage. I wrote this code for someone who wanted to build my 12 digit
LED clock with hundredths of seconds and have it synchronised to the US mains for
timekeeping accuracy. In Australia the mains is 50Hz, so that is easy. But for "frequency impaired"
people in the USA they can use this code to get 100Hz events from the rectified mains signal at
120Hz.
Generating hundredths of seconds from US mains 120Hz freq;
With this specific example the per-unit jitter is quite large, the max jitter error is 1/120th of a second
(see below). However the clock will still keep perfect time, it is only the "hundredths" digit that will be
affected and that is too fast to read anyway. The overall visual effect of the clock displaying
hundredths and tenths of a second is maintained.
The int period and 1second period value can both be multiplied by the same number, giving an
increase in the timer resolution much greater than the original resolution of the xtal value in Hz.
This next example uses a PIC with 1Mhz xtal, and testing against a GPS receiver over a month
shows the PIC clock is 9 seconds fast. So the 1second correction value is calculated as;
(1month+9secs) / 1month which in seconds is; 2678409 / 2678400
Which is 1.0000033602
Since we are using an unsigned long variable for the bresenham accumulator the same code will
take a value up to 4.29 billion with no issues. So we just multiply both the periods by 1000, which
gives 1000 times finer resolution to calibrate the clock but still uses the same simple C code.
C code for PIC 1Mhz xtal super-fine calibration