0% found this document useful (0 votes)
53 views6 pages

Accurate TMR0 Interrupts

This document describes Bresenham's algorithm, which can be used to generate accurate average time periods from imperfect internal timer periods in microcontrollers. It provides code examples for using Bresenham's algorithm with PIC microcontrollers to generate a 1 second period from the timer0 overflow period, which is imperfect due to the crystal frequency. The code subtracts from a running total when the overflow occurs and adds the target 1 second period back when the total reaches zero, maintaining long-term accuracy while alternating between shorter and longer individual periods.

Uploaded by

frank_grimes
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
53 views6 pages

Accurate TMR0 Interrupts

This document describes Bresenham's algorithm, which can be used to generate accurate average time periods from imperfect internal timer periods in microcontrollers. It provides code examples for using Bresenham's algorithm with PIC microcontrollers to generate a 1 second period from the timer0 overflow period, which is imperfect due to the crystal frequency. The code subtracts from a running total when the overflow occurs and adds the target 1 second period back when the total reaches zero, maintaining long-term accuracy while alternating between shorter and longer individual periods.

Uploaded by

frank_grimes
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

Zero-error 1 second Timer

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;

(assuming a 4MHz crystal, and 1000000 timer0 ticks/second)


 Every timer0 overflow; subtract 256 from our Period variable
 When Period variable gets less than zero; generate the 1 second event and ADD another
1000000 to it.

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.

My optimisation for the PIC timer0:

 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.

PIC assembler source code!

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.

PIC assembler source code using timer0 interrupt (11 kb)

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.

PIC assembler source code using NO interrupts! (11 kb)

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.

Making a 1 second period using C code


This system will work on a PIC or any micro that has a timer and can use C source code. This does
the same thing as the assembler code above.
C code for a 1 second period with a 1MHz timer (4MHz xtal);

// uses 1 variable; unsigned long bres


// gets here every TMR0 int (every 256 ticks)

bres += 256; // add 256 ticks to bresenham total

if(bres >= 1000000) // if reached 1 second!


{
bres -= 1000000; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}

C code for a 1 second period with a 5MHz timer (20MHz xtal);


// uses 1 variable; unsigned long bres
// gets here every TMR0 int (every 256 ticks)

bres += 256; // add 256 ticks to bresenham total

if(bres >= 5000000) // if reached 1 second!


{
bres -= 5000000; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}

C code for a 1 second period with a funky 12.391 MHz junkbox xtal);

// uses 1 variable; unsigned long bres


// gets here every TMR0 int (every 1024 xtal ticks)

bres += 1024; // add 1024 xtal ticks to bresenham total

if(bres >= 12391000) // if reached 1 second!


{
bres -= 12391000; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}

C code for a 1 second period with a 16bit timer1 at 1MHz (4MHz xtal);

// uses 1 variable; unsigned long bres


// gets here every TMR1 16bit int (every 65536 ticks)

bres += 65536; // add 65536 ticks to bresenham total

if(bres >= 1000000) // if reached 1 second!


{
bres -= 1000000; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}
Special C code examples
This next one is similar to the 1 second generators above. However it is optimised for speed and
code space, because it uses a 16bit unsigned int variable for bres instead of a 32bit unsigned long.
We do this by using values for the int period and the 1second period that are in the same ratio to
each other, but smaller! We divide both values by 16.

Optimised C code for a 1 second period with a 1MHz timer (4MHz xtal);

// uses 1 variable; unsigned int bres


// gets here every TMR0 int (every 256 ticks)

bres += 16; // add (256/16) ticks to bresenham total

if(bres >= 62500) // if reached (1000000/16) 1 second!


{
bres -= 62500; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}

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.

Generating hundredths of seconds from a Dallas 32768Hz oscillator;

// uses 1 variable; unsigned int bres


// TMR1 runs at 32768Hz, from external osc
// gets here every TMR1 bit3 toggle, is 1/2048th second

bres += 100; // add 100 to bresenham total

if(bres >= 2048) // if reached 1/100th of a second!


{
bres -= 2048; // subtract 1/100th of a second, retain
error
do_100th_event(); // update clock, etc
}

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;

// uses 1 variable; unsigned char bres


// gets here every 120Hz, synced to US mains freq

bres += 100; // add 100 to bresenham total

if(bres >= 120) // if reached 1/100th of a second!


{
bres -= 120; // subtract 1/100th of a second, retain
error
do_100th_event(); // update clock, etc
}

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.

Advanced bresenham timing techniques


Clock xtal super-fine calibration.

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

Multiply by 1Mhz to give the correct 1 second period; = 1000003.3602 ticks

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

// uses 1 variable; unsigned long bres


// gets here every TMR0 int (every 256 ticks)

bres += 256000; // add (256*1000) ticks to bresenham total

if(bres >= 1000003360) // if reached 1 second!


{
bres -= 1000003360; // subtract 1 second, retain error
do_1sec_event(); // update clock, etc
}

For more information, visit http://www.romanblack.com/one_sec.htm

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy