[Back to Home Page]

www.RomanBlack.com

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 09th June 2009.


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 4Mhz xtal (1MHz timer), 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 4Mhz xtal (1MHz timer) 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
    	}
    


    Generating a higher input frequency to reduce jitter

    This is an advanced technique to reduce jitter where the two periods are similar or where the output frequency is higher than the input frequency. It works better at low frequencies and requires the input frequency to be fixed and known. This is ideal to fix the type of jitter seen in the 120Hz -> 100Hz picture above.

    It generates 1200Hz from the 120Hz input, by generating 10 "fake" events from each real 120Hz input event. Then the bresenham system generates the 100Hz output frequency from the "fake" 1200Hz frequency. This reduces jitter by a factor of 10.

    C code for low-jitter generation of 100Hz from 120Hz mains

    	// PIC code, 4MHz xtal, TMR1 at 1:8 prescale 
    	// uses 2 variables;
    	//   unsigned int bres
    	//   unsigned char pulse
    
    	start:
    
    	// wait here for 120Hz pulse
    	while(!check_120Hz());	
    
    	// now generate 10 "fake" pulses, each is "1200Hz"
    	// which is 833uS. PIC 1Mhz xtal, TMR1 1:8 prescale,
    	// we use TMR1L period of 104 = 832uS 
    	// note! TMR1L loop is also another zero-error system
    	// that we keep subtracting 104 from while retaining
    	// its internal error.
    
    	pulse = 10;		// make 10 "fake" pulses
    	TMR1L = 0;		// make first pulse immediately
    
    	while(pulse)
    	{
    		// wait here for fake 1200Hz pulse	
    		while(TMR1L.F7);
    
    		// now fix TMR1L and do the main bres event
    		TMR1L -= 104;	// subtract 1/1200th of a second
    
    		bres += 100;
    		if(bres >= 1200)	// if 100th sec reached!
    		{
    			bres -= 1200;
    			do_100th_event();	// update clock, etc
    		}
    
    		// subract a pulse, see if 10 done yet
    		pulse--;
    	}
    
    	// gets here after 10 "fake" 1200Hz pulses,
    	// need to detect a real 120Hz pulse, then
    	// do it all again!
    
    	goto start;
    


    The code above behaves much like a digital PLL (Phase Locked Loop) in that it MUST generate 10 pulses for every real input pulse (with zero cumulative error), and the output is generated from that, again with zero cumulative error.

    The two advanced techniques above can be used to provide very fine adjustment of virtually any periods or frequencies.



    History

    I put my original "Zero-error 1 second Timer" up on my web page in June 2001 and it has been used by a huge amount of people making PIC clocks and for other timing uses to make one timing frequency from another. Since then it has practically become the "standard" way of making a 1 second clock period with a microcontroller. :)

    This web page was updated in June 2009 to add all the C-code examples and the advanced techniques.


    Tools

    If using large numbers with assembler, you need to convert large decimal numbers (like 1000000) to a 24-bit hex value for the PIC to use, so I created a simple decimal-hex-binary converter;




    Click here to download HexCon 1.0



    - end -

    [Back to Home Page]