[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 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
    	}
    


    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 = (256-104);	// prep first 1/1200th sec delay
    
    	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 very real input pulse (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.




    ZEZJ algorithm - Zero-Error and Zero-Jitter   New 21 Nov 2009.

    This new algorithm is an adaptation of my zero-error system, that again uses one easy constant to generate any period from any xtal. It also cancels any timer error with every new TMR0 interrupt. However this system is more sophisticated in that it also has zero jitter. This makes it ideal for generating exact frequencies, like a precise 1 second xtal locked output or an exact zero-jitter 50Hz reference or 1kHz reference etc.

    Zero Error Zero Jitter algorithm;
  • (define PERIOD as the period to be generated, in TMR0 ticks)
  • 1. Make X TMR0 interrupts, each is 100 ticks long (zero error)
  • 2. Make 1 TMR0 interrupt of 100-199 tick remainder (zero error)
  • 3. Generate the event (toggle a PIC pin etc).

    This is a pretty clever system, as it can make any length period, exactly, and the period is a simple constant defined in TMR0 ticks.

    The period is broken down into X delays of 100 TMR0 ticks, and a remainder that must be from 100 to 199 TMR0 ticks. The beauty of this very simple system is that the PERIOD value can never conflict with the TMR0 overflow that causes the interrupt. So the user can simply set the one simple PERIOD constant to any number of TMR0 ticks, and the software generates an exact zero-jitter period automatcially!

    I have supplied code below as a complete PIC 12F675 project to generate xtal-locked exact 50Hz frequency from a 4MHz xtal. The TMR0 runs at 1:1 prescaler ie 1MHz. The period generated is 10000 TMR0 ticks, which makes a 100Hz event. The event just toggles PIC pin GP0, obviously the toggling causes the frequency to be halved so it makes exactly 50 Hz. It was tested on a 12F675 with 4MHz xtal and made 50.000Hz.

    Note! If you wanted to use the code below to generate other xtal-locked frequencies, you just need to change the PERIOD value (and maybe the xtal);
  • toggle PERIOD = (xtal / 4 / freq / 2)
  • 1 second; 4MHz xtal, PERIOD = 500000
  • 50 Hz; 8MHz xtal; PERIOD = 20000
  • 60 Hz; 6MHz xtal, PERIOD = 12500
  • 60 Hz; 12MHz xtal, PERIOD = 25000
  • 1 second; 8.867238MHz cheap TV xtal, PERIOD = (8867238 / 4 / 2)

    
    /******************************************************************************
      ZeroJitter.c   Generates zero-error and zero jitter interrupt period.
      Open-source  -  21 Nov 2009  -  www.RomanBlack.com/one_sec.htm
    
      PIC 12F675, 4MHz xtal.
      This is like my zero-error 1 second timing system, that uses a convenient
      constant to set ANY period (with 1 timer tick resolution).
      However this system has zero jitter!
      Can be used to generate 1 second period, or 50Hz freq output etc.
    ******************************************************************************/
    
    // PERIOD sets the pin toggle freq; toggle PERIOD = (xtal / 4 / freq / 2) 
    #define PERIOD 10000   // (xtal 4Mhz) TMR0 1MHz, 10000 = 100Hz toggle (50Hz output)
    
    #define PER_COUNTS ((PERIOD / 100) - 1)  // don't edit this!
    #define PER_REMAINDER (PERIOD - (PER_COUNTS * 100))  // don't edit this!
    
    unsigned int pcount;    // used in interrupt to count PER_COUNTS 
    //-----------------------------------------------------------------------------
    
    
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    void interrupt()
    {
      //-----------------------------------------------------
      // this is the TMR0 overflow interrupt.
      // Note! TMR0 has a 3 tick write latency, writes must be -3
      //-----------------------------------------------------
      // check if time to toggle the output pin
      if(!pcount)
      {
        asm {
          movlw 0x01      ; // mask for pin 0
          xorwf GPIO,f    ; // toggle PIC pin GPIO.0
        }
        pcount = (PER_COUNTS+1);    // how many delays to make total
        TMR0 -= (PER_REMAINDER-3);  // first delay will be ==remainder
      }
      // else make a normal delay
      else
      {
        TMR0 -= (100-3);       // make another 100 tick delay
      }
      pcount--;
      //-----------------------------------------------------
      // clear the TMR0 overflow flag and exit
      INTCON.T0IF = 0;
    }
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    
    //=============================================================================
    //   MAIN
    //=============================================================================
    void main ()
    {
      //-----------------------------------------------------
      // PIC 12F675  setup ports
      ANSEL = 0;            // ADC off
      CMCON = 0x07;         // comparators off
      GPIO =   0b00000000;  // clear GPIO
      TRISIO = 0b00000000;  // All outputs
      WPU =    0b00000000;  // pin pullups; 1 = pullup on (for button)
    
      //-----------------------------------------------------
      // timer setup etc
      OPTION_REG = 0b00001000;    // TMR0 on, 1:1 prescale
      pcount = 0;
      INTCON = 0b10100000;  // GIE on, T0IE on (turn interrupt on) 
    
      //-----------------------------------------------------
      // main run loop here
      while(1)
      {
        continue;   // loop and do nothing, just let the interrupt happen
      }
    }
    //-----------------------------------------------------------------------------
    
    



    XTAL-locked 50Hz and 60Hz push-pull inverter   New 30th Nov 2009.

    This is a variation of the ZEZJ code shown above. It generates a period of mains freq * 8, then actives 2 PIC pins to give push-pull FET driving, so just one PIC and one XTAL makes a complete xtal-locked square-wave mains inverter brain. Code is fully tested.

    PIC pins;
    
  • GP0 - outputA, sequence; 01110000
  • GP1 - outputB, sequence; 00000111

  • Output frequency;
  • 50Hz (use 10 MHz xtal)
  • 60Hz (use 12 MHz xtal)

    Note! Frequency is exact, as good as the xtal accuracy. It also has zero jitter. So an inverter based on this brain will be accurate enough to drive clocks etc.

    For the power stage of an inverter you can use any schematic that uses 2 FETs (or 2 power transistors) driving a mains transformer. The 2 PIC outputs are active HIGH, so when the PIC pin is +5v it turns on the FET. This brain can even be suitable for high power square-wave inverters. There is a crude schematic below that shows the output stage.

    
    /******************************************************************************
      ZJ_Inverter.c   xtal-locked push-pull Inverter driver
      Open-source  -  30 Nov 2009  -  www.RomanBlack.com/one_sec.htm
    
      PIC 12F675, xtal (see below).
      This was based on my ZEZJ system for generating a frequency.
      It will drive push-pull FETs to make a mains inverter;
        10MHz xtal; 50Hz output (same code is used for both)
        12MHz xtal; 60Hz output  
      GP0 - this is push-pull outputA (hi = FET on)
      GP1 - this is push-pull outputB (hi = FET on)
      
    _BODEN_OFF _MCLRE_OFF _PWRTE_ON _WDT_OFF _HSOSC
    ******************************************************************************/
    
    // edit PERIOD to give the freq; PERIOD = (xtal / 4 / (freq*8))
    // Note! we need 8 period events per mains cycle, so use (freq * 8)
    #define PERIOD 6250   // 10Mhz xtal = 50Hz, 12MHz xtal = 60Hz.
    
    #define PER_COUNTS ((PERIOD / 100) - 1)  // don't edit this!
    #define PER_REMAINDER (PERIOD - (PER_COUNTS * 100))  // don't edit this!
    
    unsigned int pcount;    // used in interrupt to count PER_COUNTS 
    unsigned char outputs;  // shadow register used to drive output pins
    unsigned char outcount; // used to sequence the outputs
    //-----------------------------------------------------------------------------
    
    
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    void interrupt()
    {
      //-----------------------------------------------------
      // this is the TMR0 overflow interrupt.
      // Note! TMR0 has a 3 tick write latency, writes must be -3
      //-----------------------------------------------------
      // check if period has occured
      if(!pcount)
      {
        // first send the outputs with no latency
        asm {
          movf outputs,w      ; // send this value out to PIC pins
          movwf GPIO          ;
        }
        pcount = (PER_COUNTS+1);    // how many delays to make total
        TMR0 -= (PER_REMAINDER-3);  // first delay will be ==remainder
    
        // now handle push-pull output sequencing;
        // 01110000   GP0
        // 00000111   GP1
        outcount++;
        if(outcount >= 8) outcount = 0;         // 8 steps in sequence
        if(outcount == 0) outputs = 0b00000000; // load outputs ready to be used
        if(outcount == 1) outputs = 0b00000001;
        if(outcount == 4) outputs = 0b00000000;
        if(outcount == 5) outputs = 0b00000010;
      }
      // else make a normal delay
      else
      {
        TMR0 -= (100-3);       // make another 100 tick delay
      }
      pcount--;
      //-----------------------------------------------------
      // clear the TMR0 overflow flag and exit
      INTCON.T0IF = 0;
    }
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    
    //=============================================================================
    //   MAIN
    //=============================================================================
    void main ()
    {
      //-----------------------------------------------------
      // PIC 12F675  setup ports
      ANSEL = 0;            // ADC off
      CMCON = 0x07;         // comparators off
      GPIO =   0b00000000;  // clear GPIO
      TRISIO = 0b00000000;  // All outputs
    
      //-----------------------------------------------------
      // timer setup etc
      OPTION_REG = 0b00001000;    // TMR0 on, 1:1 prescale
      pcount = 0;
      outputs = 0;
      outcount = 0;
      INTCON = 0b10100000;  // GIE on, T0IE on (turn interrupt on) 
    
      //-----------------------------------------------------
      // main run loop here
      while(1)
      {
        continue;   // loop and do nothing, just let the interrupt happen
      }
    }
    //-----------------------------------------------------------------------------
    
    



    50Hz to 60Hz converting inverter!   New 30th Nov 2009 (waveform diagram added 8th Dec 2009).

    This code runs a push-pull squarewave inverter (like the project above) but it is mains-locked and frequency converting. So it will generate 50Hz mains from a 60Hz input, or the other way around. Only one line of code needs to be changed to select which way the conversion is done.

    Accuracy will be as good as the mains frequency, so it should be perfect for clocks etc. Code is fully tested.

    PIC pins;
  • GP0 - outputA
  • GP1 - outputB
  • GP2 - mains freq input, 0v to 5v, half cycle

    The output pins will drive push-pull FETs etc (like the project above). The mains input pin must be approx 0v to 5v waveform on PIC pin GP2. What I used connected 12v AC through a diode into a voltage divider, which gives half-wave pulses. The voltage divider used a 1k resistor into a 5k pot which was adjusted to make the pulses safe at 0v to 5v. I also added a 0.47uF greencap (polyester cap) from GP2 to ground. Coupled with the 5k pot this gave a nice smooth 5v pulse on GP2 when checked with the oscilloscope. If you don't have a 'scope, just turn the pot right down (so it stops working) then turn it up until the pulses are large enough so it is working reliably, then a touch more.





    I was pleasantly surprised with just how well this simple code works to generate a perfect 60Hz push-pull waveform from my 50Hz mains (or vice versa). The code uses a 4MHz xtal and a zero-error routine using TMR1 to generate 600Hz, a figure that works with both 50Hz and 60Hz. This is sync-locked to the incoming mains frequency, and generates exactly 10 or 12 pulses for each incoming mains cycle;





    Converting 50Hz to 60Hz (12 : 10);
  • Generate exactly 12 pulses for each 50Hz mains pulse
  • Generate a 60Hz push-pull output from 10 pulses

    Converting 60Hz to 50Hz (10 : 12);
  • Generate exactly 10 pulses for each 60Hz mains pulse
  • Generate a 50Hz push-pull output from 12 pulses

    The push-pull waveforms I saw on the 'scope were very nice, with jitter that was only a couple of uS. This requires a 4MHz xtal to make the precise pulse periods, AND a properly smoothed 12v AC half-cycle waveform (from a 12v AC transformer) to detect the incoming mains frequency. If your mains frequency is a bit rough it will still produce a perfect freq output, but the jitter may be larger. I suggest using the parts values shown above for the incoming mains freq.

    
    /******************************************************************************
      ZE_MainsConverter.c   mains freq converter push-pull inverter driver
      Open-source  -  30 Nov 2009  -  www.RomanBlack.com/one_sec.htm
    
      PIC 12F675, 4MHx xtal 
      This was based on my zero-error reduced-jitter system that
      generates accurate 50Hz mains locked to 60Hz mains input,
      or vice versa. The output frequency will be exact, as it is
      locked to the input frequency. Generally this code would be
      used to make a small inverter to drive an antique 60Hz mains
      clock from 50Hz mains etc. The mains input can be as simple
      as a diode and a voltage divider pot from 12v AC to PIC pin GP2,
      so it gives half-cycle mains freq input of 0v-5v at the PIC pin.
    
      GP2 - mains freq input (see above)  
      GP0 - this is push-pull outputA (hi = FET on)
      GP1 - this is push-pull outputB (hi = FET on)
      
    _BODEN_OFF _MCLRE_OFF _PWRTE_ON _WDT_OFF _HSOSC
    ******************************************************************************/
    
    #define PERIOD 1667    // note! PERIOD is 1667 for either conversion!
    #define PERHI (PERIOD / 256)          // don't edit this!
    #define PERLO (PERIOD - (PERHI*256))  // don't edit this!
    
    // now select one of these 2 options;
    #define OUTPULSES 12   // 50Hz in, 60Hz out
    //#define OUTPULSES 10   // 60Hz in, 50Hz out
    
    unsigned char pulse;    // to generate output pulses
    unsigned char outputs;  // shadow register used to drive output pins
    unsigned char outcount; // used to sequence the outputs
    //-----------------------------------------------------------------------------
    
    
    //=============================================================================
    //   MAKE OUTPUT       this generates the output frequency
    //=============================================================================
    void make_output(void)
    {
      //-----------------------------------------------------
      // first send the outputs to PIC pins (no latency)
      GPIO = outputs;
    
      // then sequence the PIC pins
      #if OUTPULSES == 12    // if 60Hz output
      {
        // 0011100000   GP0  (sequence of 10 pulses = one 60Hz cycle)
        // 0000000111   GP1
        outcount++;
        if(outcount >= 10) outcount = 0;        // 10 steps in sequence
        if(outcount == 0) outputs = 0b00000000; // load outputs ready to be used
        if(outcount == 2) outputs = 0b00000001;
        if(outcount == 5) outputs = 0b00000000;
        if(outcount == 7) outputs = 0b00000010;
      }
      #else      // else is 50Hz output
      {
        // 001111000000   GP0  (sequence of 12 pulses = one 50Hz cycle)
        // 000000001111   GP1
        outcount++;
        if(outcount >= 12) outcount = 0;        // 12 steps in sequence
        if(outcount == 0) outputs = 0b00000000; // load outputs ready to be used
        if(outcount == 2) outputs = 0b00000001;
        if(outcount == 6) outputs = 0b00000000;
        if(outcount == 8) outputs = 0b00000010;
      }
      #endif
    }
    //-----------------------------------------------------------------------------
    
    
    //=============================================================================
    //   MAIN
    //=============================================================================
    void main ()
    {
      //-----------------------------------------------------
      // PIC 12F675  setup ports
      ANSEL = 0;            // ADC off
      CMCON = 0x07;         // comparators off
      GPIO =   0b00000000;  // clear GPIO
      TRISIO = 0b00000100;  // GP2 is mains input, rest outputs
    
      T1CON = 0b00000001;   // TMR1 on, 1:1 prescale (1MHz)
    
      pulse = 0;            // and setup vars
      outputs = 0;
      outcount = 0;
    
      //-----------------------------------------------------
      // main run loop here
      while(1)
      {
        while(!GPIO.F2);      // wait for mains / edge to occur
        pulse = OUTPULSES;    // set number of outputpulses to make
        TMR1L = 7;            // rig TMR1 like a pulse just happened
        TMR1H = 0;
        goto do_pulse;        // make first pulse straight away!
    
        // now repeat this section until all pulses are made
        while(pulse)
        {
          while(!PIR1.TMR1IF);   // wait for TMR1 to roll (1 period)
          do_pulse:
          TMR1L -= PERLO;        // fix TMR1, retain error for next period
          TMR1H -= (PERHI + 1);
          PIR1.TMR1IF = 0;
    
          make_output();      // make the output pulse
          pulse--;
        }
      }
    }
    //-----------------------------------------------------------------------------
    
    



    50Hz to 60Hz converting Sinewave inverter!   New 8th Dec 2009.

    The same principle I described above can be used to make a more sophisticated 50Hz to 60Hz converting inverter, but now generating a "sinewave" made of 20 or more PWM steps.

    The procedure is basically the same as the project above, but it generates 24 "virtual pulses" per incoming 50Hz mains cycle, not 12. Using 24 pulses gives smoother steps of the sinewave. Then the 60Hz output frequency is generated from 20 pulses. This gives the required 24:20 conversion, similar to the project above that uses 12:10.





    The method for generating the voltage steps of the sinewave is optional. It could be done with multiple PIC outputs and a DAC, to generate voltage steps. Or a more energy efficient system could be used, such as using PWM on the PIC pin.

    Of course you would not be limited to 20 sine "steps" as shown here. 20 sine steps allows an easy conversion of 50Hz to 60Hz (freq), which is a conversion of 24 to 20 (as a period). 20 output sine steps is enough to give a reasonable quality sine after filtering, but there is no reason to be limited to only 20 sine steps.




    Accurate xtal-locked Sinewave inverter!   New 8th Dec 2009.

    This is a full 2 output push-pull PWM sinewave inverter.
    With a 10MHz xtal it will generate 50Hz dual sinewaves.
    With a 12MHz xtal it will generate 60Hz dual sinewaves.

    This manually generates the sinewave PWM using 20 small simple code loops, (most loops are copies of each other, some inverted). It does not require a PWM module and will run fine on any low-end PIC that has a TMR1. This code example was hardware tested on a 12F675 but a 12F629 or other cheap PIC would work as well.

    Sequencing the 20 PWM loops is done in a very simple zero-error TMR1 interrupt, that just subtracts the fixed period from TMR1 and increments the step variable (the only RAM variable used!).

    void interrupt()
    {
      // TMR1 interrupt. This is a very simple zero-error TMR1 correction.
      // It generates an interrupt every 2500 PIC instructions.
      // (this code was hardware tested to exact cycle accuracy)
      #define PERIOD 2500   // 10Mhz xtal = 50Hz, 12MHz xtal 60Hz. (don't edit)
      #define PERHI (PERIOD / 256)          // don't edit this!
      #define PERLO (PERIOD - (PERHI*256))  // don't edit this!
      TMR1L -= (PERLO - 2);   // fix TMR1, retain error for next period
      TMR1H -= (PERHI + 1);
      step++;
      PIR1.TMR1IF = 0;
    }
    




    Above is shown the 2 PWM sine push-pull outputs, for display purposes each output was filtered into a RC low-pass filter (39k, 0.1uF) and it shows dual sines of 3v peak to peak.

    Below is shown the PWM output (0v-5v digital) and the other PWM output is shown still filtered with the RC filter.





    Note I didn't count cycles to create the PWM loops, instead I just locked the PIC in each loop and measured the DC voltage on its output pin to determine the duty cycle, these were referenced to a 50:50 loop that was tested and produced 2.42v. I only had to create 4 PWM loops really, which were then copied and/or inverted (and 2 of the loops are 50:50 and 2 are 0:100 and 100:0). You can tweak my values if you like but as you can see from the sines on the CRO above they are probably good enough to build an inverter.

    PWM frequencies are from 25kHz to 47kHz (apart from the 2 DC values).

    C Source code is here.




    50Hz to 60Hz sinewave converting inverter!   New 9th Dec 2009.

    This one has push-pull sinewave outputs, and does not require a PIC with PWM module as the sinewave PWM is generated in software.

    This produces an exact 60Hz output which is mains-locked to the national 50Hz grid. It uses the same manual PWM loops as used in the project directly above.

    The procedure to convert 50Hz to 60Hz is the same as my other systems seen above;
  • For every 50Hz incoming mains cycle it generates 24 "virtual pulses"
  • Push-pull 60Hz sinewave outputs are sequenced over 20 virtual pulses.

    However this one does the freq conversion in an interrupt;
  • 1 int of 24; wait for GP2 \ edge (synchronise to 50Hz \ edge)
  • 23 ints of 24; generate the virtual pulse periods using TMR1




    The schematic above shows how to connect the 12v AC mains sensing input to GP2 and the 2 PWM sinewave push-pull outputs are GP0 and GP1.

    The code below is fully tested in hardware and I fine tuned the TMR1 "virtual pulse" period to remove interrupt latency, so it produces a VERY nicely locked 60Hz sinewave. Below is the result; the 50Hz is shown as the top waveform on PIC pin GP2 (filter values as seen above), and below it is one of the 60Hz sinewave outputs (filtered with 39k and 0.1uF for ease of display).

    This circuit generates a much nicer looking sinewave than my mains! :)





    I tested my 50Hz mains frequency which was running about 200 PPM fast, and then tested the 60Hz sine output was also running about 200 PPM fast, so that's perfect. (Not that it has much choice seeing that it's mains-locked.)

    //=============================================================================
    void interrupt()
    {
      //-----------------------------------------------------
      // This is a TMR1 overflow interrupt for 23 of 24 cases.
      // on 1 of 24 cases it acts as a GP2 interrupt on \ edge.
      // This is used to generate exactly 24 virtual pulses,
      // synchronised to the 50Hz mains input (GP2 \ edge).
      //-----------------------------------------------------
      if(!vpulse)  // if it is last vpulse0
      {
        // just leave TMR1 free running, this allows easy first sync!
        vpulse = 23;
      }
      else      // else is vpulse1-23
      {
        TMR1L = (256 - (PERLO - 3 - 16));   // load TMR1 for 1 vpulse period
        TMR1H = (256 - (PERHI + 1));
        vpulse--;         // sequence the next virtual pulse
        INTCON.INTF = 0;  // clear GP2 \ edge int flag
      }
      step++;             // sequence the next 60Hz PWM step
      PIR1.TMR1IF = 0;
    }
    //=============================================================================
    

    The full C Source code is here.




    Making multiple sinewaves of highly accurate frequency   New 21st Feb 2011.


    This is a based on a form of DDS (Direct Digital Synthesis) which is a modern name used as a "catch all" phrase to describe pretty much any form of making frequencies digitally (ie using a microcontroller). The actual mechanism is a "binary divided accumulator" which differs from the "Bresenham" systems above by being faster, but cruder.

    With a Bresenham system the frequency is generated by a ratio of the value added to the accumulator compared to the Bresenham overflow value of the accumulator. As both values are adjustable the Bresenham system is superior as it can replicate any ratio of the 2 values with absolute average frequency accuracy.

    A binary divided accumulator DDS (generally just called "DDS") still works as a ratio division of the value added to the accumulator compared to the accumulator overflow, but the accumulator overflow is fixed as a binary value. This adds speed as the Bresenham compare is no longer needed, but is inferior as a frequency generator as you can only really set one of the 2 values. For this reason a DDS system needs a higher resolution accumulator, and the value added to the accumulator must be chosen to give as close as possible to the desired output frequency but will generally never give the exact output frequency, just get very close to it.

    The DDS system below generates two combined (added) sinewaves as a single waveform, to generate DTMF with 2 combined sinewaves of very high accuracy frequencies. Commercial DTMF generator ICs will have a freq error in the region 0.2% to 0.7%. The code below that uses a 24bit accumulator will produce DTMF sinewaves better than 0.00001% frequency accuracy for each sine.

    The algorithm basics.
    1. Wait for sync with the PWM module (or interrupt)
    2. Add the constant period to the accumulator
    3. Use a high byte from the accumulator to reference position in a binary waveform table, load it in PWM
    4. Repeat!

    Note! The waveform table must be size in a round binary number; 32, 64, 128 or 256 etc.





    Implementation.
    The accumulator cycle is based on the PIC PWM module period, for speed and simplicity. Then once per PWM period the addition calc is done and a new value loaded from the sine table into the PWM duty so that the output of the PWM module generates a sinewave of exact and selectable frequency.


    The code below makes a reasonably accurate single sinewave (typically less than 0.1% freq error)

      // 16bit DDS algorithm to make one sinewave of accurate freq using
      // 16 MHz xtal (4 MIPS on PIC 16F)
      #define BDA_697Hz   365               // constant to make 697 Hz
      unsigned int wave   absolute 0x15;    // 16bit accumulator for the sinewave
      unsigned char wave_1 absolute 0x16;   // overload for fast access to byte 1
      const unsigned char sine64[64] = {
      50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
      50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
    
      // loop and generate dual sinewave DTMF tone
      PR2 = (128-1);            // PWM at period = 128
      while(1)
      {
        while(!PIR1.TMR2IF);  // sync to start of PWM cycle
        PIR1.TMR2IF = 0;
    
        // calc the sinewave, and load into PWM module CCPR2L
        wave += BDA_697Hz;              // zero error Accumulation 
        CCPR2L = sine64[wave_1 & 0x3F]; // Binary Divide output (/256) and keep 6 bits
      }


    The code below makes an extremely accurate freq sinewave;

      // 24bit DDS algorithm to make one sinewave of exact freq
      // 8 MHz xtal (8 MIPS on PIC 18F PLL)
      #define BDA_697Hz   46775             // constant to make exactly 697 Hz
      unsigned long wave   absolute 0x15;   // 32bit accumulator for the sinewave
      unsigned char wave_2 absolute 0x17;   // overload for fast access to byte 2
      const unsigned char sine64[64] = {
      50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
      50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
    
      // loop and generate sinewave with PWM module
      PR2 = (128-1);            // PWM at period = 128
      while(1)
      {
        while(!PIR1.TMR2IF);  // sync to start of PWM cycle
        PIR1.TMR2IF = 0;
    
        // calc the sinewave, and load into PWM module CCPR2L
        wave += BDA_697Hz;              // zero error Accumulation 
        CCPR2L = sine64[wave_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
      }


    The code below makes dual simultaneous accurate sinewaves (for DTMF);

      // 24bit DDS algorithm for dual simultaneous sinewaves on one PWM module
      // this is an example for DTMF generation
      // 8 MHz xtal (8 MIPS on PIC 18F PLL)
      #define Frow0 46775   // 697 Hz  DTMF rows
      #define Frow1 51674   // 770 Hz
      #define Frow2 57177   // 852 Hz
      #define Frow3 63149   // 941 Hz
      #define Fcol0 81135   // 1209 Hz  DTMF columns
      #define Fcol1 89657   // 1336 Hz
      #define Fcol2 99120   // 1477 Hz
      #define Fcol3 109589  // 1633 Hz
      unsigned long waveA   absolute 0x15;   // 32bit accumulator for the sinewaves
      unsigned char waveA_2 absolute 0x17;   // overload for fast access to byte 2
      unsigned long waveB   absolute 0x19;
      unsigned char waveB_2 absolute 0x1B;
      unsigned char pwm;
      const unsigned char sine64[64] = {
      50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
      50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
    
      // loop and generate dual sinewave DTMF tone
      PR2 = (128-1);            // PWM at period = 128
      while(1)
      {
        while(!PIR1.TMR2IF);  // sync to start of PWM cycle
        PIR1.TMR2IF = 0;
    
        // calc the A sinewave, 
        waveA += Frow0;               // zero error Accumulation 
        pwm = sine64[waveA_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
        // calc the B sinewave, and ADD the 2 waves together
        waveB += Fcol0;
        pwm += sine64[waveB_2 & 0x3F];
        pwm = (pwm >> 1);   // scale 0-200 back to 0-100 for PWM
        CCPR2L = pwm;       // load added sinewaves into PWM module
      }

    Below is improved DTMF code that includes "twist" to make the higher freq sinewave 28% larger;

      // 24bit DDS algorithm for dual simultaneous sinewaves on one PWM module
      // this is an example for DTMF generation
      // 8 MHz xtal (8 MIPS on PIC 18F PLL)
      #define Frow0 46775   // 697 Hz  DTMF rows
      #define Frow1 51674   // 770 Hz
      #define Frow2 57177   // 852 Hz
      #define Frow3 63149   // 941 Hz
      #define Fcol0 81135   // 1209 Hz  DTMF columns
      #define Fcol1 89657   // 1336 Hz
      #define Fcol2 99120   // 1477 Hz
      #define Fcol3 109589  // 1633 Hz
      unsigned long waveA   absolute 0x15;   // 32bit accumulator for the sinewaves
      unsigned char waveA_2 absolute 0x17;   // overload for fast access to byte 2
      unsigned long waveB   absolute 0x19;
      unsigned char waveB_2 absolute 0x1B;
      unsigned char pwm;
    
      // This uses dual sinewaves of 28% different amplitudes, to match the spec for "twist" in
      // telephone DTMF to ensure the higher freq has a higher amplitude, to cope with line losses.
      const unsigned char sine64low[64] = {
      39,42,46,50,53,57,60,63,66,68,71,72,74,75,76,77,77,77,76,75,74,72,71,68,66,63,60,57,53,50,46,42
      39,35,31,27,24,20,17,14,11,9,6,5,3,2,1,0,0,0,1,2,3,5,6,9,11,14,17,20,24,27,31,35};
      const unsigned char sine64high[64] = {
      50,54,59,64,68,73,77,81,85,88,91,93,95,97,98,99,99,99,98,97,95,93,91,88,85,81,77,73,68,64,59,54,
      50,45,40,35,31,26,22,18,14,11,8,6,4,2,1,0,0,0,1,2,4,6,8,11,14,18,22,26,31,35,40,45};
    
      // loop and generate dual sinewave DTMF tone
      PR2 = (128-1);            // PWM at period = 128
      while(1)
      {
        while(!PIR1.TMR2IF);  // sync to start of PWM cycle
        PIR1.TMR2IF = 0;
    
        // calc the A sinewave, 
        waveA += Frow0;               // zero error Accumulation 
        pwm = sine64low[waveA_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
        // calc the B sinewave, and ADD the 2 waves together
        waveB += Fcol0;
        pwm += sine64high[waveB_2 & 0x3F];
        pwm = (pwm >> 1);   // scale 0-200 back to 0-100 for PWM
        CCPR2L = pwm;       // load added sinewaves into PWM module
      }


    Calculating the constant to make a desired frequency;
    Each freq you want to generate needs a costant. This is a simple ratio that is determined by the PWM cycle frequency, the desired output frequency and the number of samples in the waveform table. It is also dependant on the size of the accumulator, ie 16bit, 24bit etc.

    A 16 bit system uses the high byte of a 16bit accumulator. So it is a factor of 256. Assuming a waveform table of 64 entries, the formula to make an output frequency is this;
    BDA_period = Fout * 64 * 256 / Fpwm

    So for our PIC 16F example with 16MHz xtal (4 MIPS) and PWM period of 128;
    Fpwm = 4000000 / 128 = 31250

    To generate the 697 Hz sinewave as seen in example above;
    BDA_period = 697 * 64 * 256 / 31250
    BDA_period = 365

    And for our PIC 18F example with 8MHz xtal PLL (8 MIPS) and PWM period of 128;
    Fpwm = 8000000 / 128 = 62500

    It uses a 24bit system, where the byte2 is used for the binary table, so is 65536;
    To generate the 697 Hz sinewave, with the 24bit system, as seen in example above;
    BDA_period = 697 * 64 * 65536 / 62500
    BDA_period = 46775

    Freqency accuracy;
    Using a constant of about 46000 and max error is 1/2 a count, means the max error is 1 part in 92000 or about 11 parts per million. This is better than the frequency accuracy of the PIC xtal, so this 24bit produced sinewave will have an output frequency accuracy around as good as the xtal itself. So this can be considered a "perfect" frequency xtal-locked sinewave. Also, with the DTMF example above this is the worst freq of the 8, so the other sine frequencies range up to twice this accuracy.

    And the nature of the system means that BOTH of the simultaneously generated sinewaves will each have this individual accuracy.

    Note! I have not provided a 32bit example, but you can see how easy it would be from the above examples. A 32bit system will have a sinewave frequency accuracy typically better than 50 parts per billion, a level of accuracy so much higher than the xtal accuracy it is not necessary. However it may be useful in some frequency conversion situations.

    Speed issues and variable overloading;
    The 3 code examples above use variable overloading to provide instant access to the higher byte in the multi-byte accumulator. Luckily the MikroC for PIC compiler allows this feature. If your C compiler does not allow 2 variables to be assigned to the same byte in PIC ram you can use a /256 or /65536 operation instead.

    So for the 16bit system you would replace this;
    CCPR2L = sine64[wave_1 & 0x3F]; // Binary Divide output (/256) and keep 6 bits
    With this;
    CCPR2L = sine64[(wave/256) & 0x3F]; // Binary Divide output (/256) and keep 6 bits

    And for the 24bit system you would replace this;
    CCPR2L = sine64[wave_2 & 0x3F]; // Binary Divide output (/65536) and keep 6 bits
    With this;
    CCPR2L = sine64[(wave/65536) & 0x3F]; // Binary Divide output (/65536) and keep 6 bits

    Depending how well your C compiler optimises the divide it may be as fast as the variable overloading, but most compilers are not that good as they will use a Div32/32 function which is slower and will use a lot of ROM.




    A flawless looking 697 Hz sinewave, 2.6v p/p, made from the PIC PWM module and filtered simply through a 2k7 resistor and 0.1uF capacitor. I tested with my good frequency meter and it said 697.00 Hz.

    Accurate sine DTMF project with C source code;
    I have done a PIC 18F project that generates all the DTMF codes with sinewaves, using a SmartGLCD module, you can see it on this tutorials page.




    Continued...

    If you liked this page please see my continued page; High accuracy PIC timing systems.




    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 practically become the "standard" way of making a 1 second clock period in a PIC interrupt. :)

    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]