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


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.

    This is also a 50Hz to 60Hz converting inverter, but it uses PWM to generate a "sinewave" made of 20 actual PWM steps.

    The PWM period and duty cycle is generated automatically from the PIC PWM module, so it requires a newer PIC that has a PWM module. This example uses the PIC 12F683, which is still a cheap 8pin PIC but it has the built-in PWM.

    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.

    This following PWM sinewave code was written by Mike McLaren (K8LH) and all I have done is add the frequency conversion code into the main loop of Mike's fast neat PWM sinewave generator, to synchronise his 60Hz sinewave generator to the incoming 50Hz mains.

    If you want to see Mike's other 60Hz PWM sinewave code, and our discussions you can see it in this thread; Zero-Jitter Inverters Thread

    The code below actually only generates one PWM output (as the PIC only has one PWM module) however, the second output (to make it push-pull) can be generated by a simple hardware inverter, as the 2 outputs need to be the opposite of each other. (A hardware inverter only requires 1 transistor and 2 resistors.)

    
    /********************************************************************
     *                                                                  *
     *  Project: Sine 50Hz to 60Hz converter                            *
     *   Source: HybridSine_50-60.c                                     *
     *   Author: Mike McLaren, K8LH (main code and sine routines)       *
     *   Author2:RomanBlack (24:20 routine for 50Hz to 60Hz convert)    *
     *     Date: 07-Dec-09                                              *
     *  Revised: 07-Dec-09                                              *
     *                                                                  *
     *  12F683 60 Hz PWM sinewave output  to drive small inverter.      *
     *  PIC is freq locked to 50Hz mains frequency input, and converts  *
     *  50Hz input to a 60Hz sine output. (not yet hardware tested)     *
     *  NOTE! TO drive push-pull outputs this requires a hardware       *
     *  inverter to make the 2nd output, a transistor will do.          *
     *                                                                  *
     *      IDE: MPLAB 8.14 (tabs = 4)                                  *
     *     Lang: SourceBoost BoostC v6.95, Lite/Free version            *
     *                                                                  *
     *  GP2 - CCP1 - PWM sinewave output                                *
     *  GP3 - 50Hz freq in (must be filtered 0v-5v)                     *
     *                                                                  *
     ********************************************************************/
    
    #include <system.h>
    
    #pragma DATA _CONFIG, _FCMEN_OFF&_IESO_OFF&_MCLRE_OFF&_WDT_OFF&_HS_OSC
    
    #define PERIOD 2083    // for 50Hz input and 10MHz xtal (50Hz / 24)
    #define PERHI (PERIOD / 256)          // don't edit this!
    #define PERLO (PERIOD - (PERHI*256))  // don't edit this!
    
    // 60Hz output; sine table needs 20 entries, 0-99 range as PWM is 100 cycles
    const rom char sine[] = { 49,64,78,89,96,99,96,89,78,64,
                              49,34,20,9,2,0,2,9,20,34};
    
    unsigned char n = 0;        // sine table index, 0..19
    unsigned char pulse;        // for pulse sequencing
    
    void interrupt()            //
    {
      pir1.TMR2IF = 0;          // clear TMR2 interrupt flag
      // nothing else needed in int now!
    }
    
    void main()                 //
    {                           //
      cmcon0 = 0x07;            // comparator not used!
      ansel = 0;                // a2d module off, digital I/O
      trisio = 0b00001010;      // GP3 is freq in, GP1 is Cin
      gpio = 0;                 // set all output latches to '0'
      pir1 = 0;                 // clear peripheral interrupt flags
      pie1.TMR2IE = 1;          // set Timer 2 interrupt enable bit
      tmr2 = 0;                 // clear Timer 2 register
      ccp1con = 0b00001100;     // '00------' unimplemented bits
                                // '--00----' DC1B1:DC1B0 duty cycle bits
                                // '----1100' active hi PWM mode
      t1con = 0b00000001;       // TMR1 on, at 1:1 prescale
      t2con = 0b00000101;       // '0-------' unimplemented bit
                                // '-0000---' TOUTPS<3:0>, postscale 1
                                // '-----1--' TMR2ON, turn Timer 2 on
                                // '------00' T2CKPS<1:0>, prescale 1:1
      pr2 = 100-1;              // 100 x prescale 1:1 = 100 instruct cycles
      intcon = 0b11000000;      // '1-------' GIE, enable global ints
                                // '-1------' PEIE, enable peripheral ints
                                // '--0-----' T0IE, TMR0 ints disabled
                                // '---0----' INTE, off
                                // '----0---' GPIE, IOC disabled
                                // '-----000' T0IF/INTF/GPIF flags
    
      while(1)                  // main program loop
      {
        // loop here and do the freq conversion; 24:20 (50Hz in, 60Hz out)
        while(!GPIO.F3);        // wait for 50Hz mains / edge to occur
        pulse = 24;             // set number of pulses per 50Hz cycle
        TMR1L = 0;              // 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 the output pulse
          ccpr1l = sine[n];     // setup next sine duty cycle
          n++;                  // 20 sine steps = 60Hz cycle
          if(n >= 20) n = 0;
    
          pulse--;              // one more input pulse processed
        }
      }
    }
    
    



    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.





    For the 20 entry PWM table I used the values from Mike McLaren K8LH, as seen in the project above. 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.




    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]