1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

ZEZJ zero error zero jitter period algorithm

Discussion in 'Microcontrollers' started by Mr RB, Nov 21, 2009.

  1. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    Hi, I've just updated my "zero error 1 second timer routines" page, there is a new section at the bottom that now supports zero-jitter period generation.

    Like the other code on the page it will generate any period (or frequency) from any xtal using juts one simple constant to set theperiod. But this new system generates the period with zero jitter (which many people have requested). It is now useful for xtal-locked signal generation etc like to generate exactly 50Hz output or a 1 second clock signal etc.

    The algortihm and code may not be immediately obvious so I will explain the basics;

    ZEZJ algorithm;
    (first, #define PERIOD in TMR0 ticks)
    1. generate X interrupts of 100 ticks length
    2. generate 1 interrupt of "remainder" length 100-199
    3. done! make the event (toggle PIC pin etc)

    It is "zero-error" as the interrupt always subtracts the 100 tick period (or remainder period) from TMR0, so that any immediate latency will be corrected on the next interrupt.

    It is "zero jitter" because the remainder period syncs to the #defined PERIOD so the event (toggle PIC pin) always happens at exactly PERIOD.

    The clever part is the "X*100 + remainder 100-199" because it means that the end of PERIOD can can never conflict with the TMR0 interrupt! This is because subtracting 100-199 will always result in a legal TMR0 count that will be corrected on the next int. So the result is that you can just #define PERIOD and it will automatically generate an exact jitter free period with no messy timing conflicts.

    # 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)

    Here is the code to generate 50Hz with a PIC 12F675 4MHz xtal.
    Code (text):

    /******************************************************************************
      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
      }
    }
    //-----------------------------------------------------------------------------
     
    This can be used on even the smallest cheapest PICs, I deliberately chose TMR0 for this reason. ROM needed is about 54, RAM needed is 2.

    Code was programmed and tested out perfectly, all values of PERIOD >=100.

    There is more info here;
    http://www.romanblack.com/one_sec.htm
     
    Last edited: Nov 21, 2009
  2. caroper

    caroper New Member

    Joined:
    Nov 26, 2009
    Messages:
    2
    Likes:
    0
    Asm?

    Hi Roman,

    I love your stuff and have used your Zero-error algorithm before.
    I was searching for it to use in my latest Project and found this post, which is even better suited to my application. However, being new to PIC's, I have acquired a reasonable level of competence in assembler, (I worked with 6089 Micro's 30 years ago) but have no experience in C, nor do I have a C compiler.
    Whilst I can try to reverse engineer your C code, into Assembler, may I impose on you to present an assembler version of the code? even a disassembled C object file would help, but I suspect the maths calls will be relatively inefficient compared to the optimised 24 maths of the original assembler implementation.

    Thanks for sharing
    Cheers
    Chris
     
    Last edited: Nov 26, 2009
  3. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    Sure that's an easy request. I deliberately used C-- (my name for C that was written to work like asm) so it would compile very small and fast, and can be easily converted to asm.

    And MikroC helps a lot because it makes these neat .ASM files so you can inspect the output asm and fine tune your C-- code;

    Code (text):

    ;  ASM code generated by mikroVirtualMachine for PIC - V. 7.0
    ;  Date/Time: 21/11/09 5:10:56 PM
    ;  Info: http://www.mikroelektronika.co.yu


    ; ADDRESS   OPCODE  ASM
    ; ----------------------------------------------
    $0000   $282A           GOTO    _main
    $0004   $   _interrupt:
    $0004   $00DF           MOVWF   STACK_15
    $0005   $0E03           SWAPF   STATUS, 0
    $0006   $0183           CLRF    STATUS
    $0007   $00A3           MOVWF   ?saveSTATUS
    $0008   $0804           MOVF    FSR, 0
    $0009   $00A2           MOVWF   ?saveFSR
    $000A   $080A           MOVF    PCLATH, 0
    $000B   $00A4           MOVWF   ?savePCLATH
    $000C   $018A           CLRF    PCLATH
    ;ZeroJitter.c,23 ::         void interrupt()
    ;ZeroJitter.c,30 ::         if(!pcount)
    $000D   $0820           MOVF    _pcount, 0
    $000E   $0421           IORWF   _pcount+1, 0
    $000F   $1D03           BTFSS   STATUS, Z
    $0010   $281A           GOTO    L_interrupt_0
    ;ZeroJitter.c,33 ::         movlw 0x01      ; mask for pin 0
    $0011   $3001           MOVLW   0x01
    ;ZeroJitter.c,34 ::         xorwf GPIO,f    ; toggle PIC pin GPIO.0
    $0012   $0685           XORWF   GPIO, f
    ;ZeroJitter.c,36 ::         pcount = (PER_COUNTS+1);    // how many delays to make total
    $0013   $3064           MOVLW   100
    $0014   $00A0           MOVWF   _pcount
    $0015   $3000           MOVLW   0
    $0016   $00A1           MOVWF   _pcount+1
    ;ZeroJitter.c,37 ::         TMR0 -= (PER_REMAINDER-3);  // first delay will be ==remainder
    $0017   $3061           MOVLW   97
    $0018   $0281           SUBWF   TMR0, 1
    ;ZeroJitter.c,38 ::         }
    $0019   $281C           GOTO    L_interrupt_1
    $001A   $   L_interrupt_0:
    ;ZeroJitter.c,42 ::         TMR0 -= (100-3);       // make another 100 tick delay
    $001A   $3061           MOVLW   97
    $001B   $0281           SUBWF   TMR0, 1
    ;ZeroJitter.c,43 ::         }
    $001C   $   L_interrupt_1:
    ;ZeroJitter.c,44 ::         pcount--;
    $001C   $3001           MOVLW   1
    $001D   $02A0           SUBWF   _pcount, 1
    $001E   $1C03           BTFSS   STATUS, C
    $001F   $03A1           DECF    _pcount+1, 1
    ;ZeroJitter.c,47 ::         INTCON.T0IF = 0;
    $0020   $110B           BCF INTCON, 2
    ;ZeroJitter.c,48 ::         }
    $0021   $   L_Interrupt_end:
    $0021   $0824           MOVF    ?savePCLATH, 0
    $0022   $008A           MOVWF   PCLATH
    $0023   $0822           MOVF    ?saveFSR, 0
    $0024   $0084           MOVWF   FSR
    $0025   $0E23           SWAPF   ?saveSTATUS, 0
    $0026   $0083           MOVWF   STATUS
    $0027   $0EDF           SWAPF   STACK_15, 1
    $0028   $0E5F           SWAPF   STACK_15, 0
    $0029   $0009           RETFIE
    $002A   $   _main:
    ;ZeroJitter.c,55 ::         void main ()
    ;ZeroJitter.c,59 ::         ANSEL = 0;            // ADC off
    $002A   $1303           BCF STATUS, RP1
    $002B   $1683           BSF STATUS, RP0
    $002C   $019F           CLRF    ANSEL, 1
    ;ZeroJitter.c,60 ::         CMCON = 0x07;         // comparators off
    $002D   $3007           MOVLW   7
    $002E   $1283           BCF STATUS, RP0
    $002F   $0099           MOVWF   CMCON
    ;ZeroJitter.c,61 ::         GPIO =   0b00000000;  // clear GPIO
    $0030   $0185           CLRF    GPIO, 1
    ;ZeroJitter.c,62 ::         TRISIO = 0b00000000;  // All outputs
    $0031   $1683           BSF STATUS, RP0
    $0032   $0185           CLRF    TRISIO, 1
    ;ZeroJitter.c,63 ::         WPU =    0b00000000;  // pin pullups; 1 = pullup on (for button)
    $0033   $0195           CLRF    WPU, 1
    ;ZeroJitter.c,67 ::         OPTION_REG = 0b00001000;    // TMR0 on, 1:1 prescale
    $0034   $3008           MOVLW   8
    $0035   $0081           MOVWF   OPTION_REG
    ;ZeroJitter.c,68 ::         pcount = 0;
    $0036   $01A0           CLRF    _pcount
    $0037   $01A1           CLRF    _pcount+1
    ;ZeroJitter.c,69 ::         INTCON = 0b10100000;  // GIE on, T0IE on (turn interrupt on)
    $0038   $30A0           MOVLW   160
    $0039   $008B           MOVWF   INTCON
    ;ZeroJitter.c,73 ::         while(1)
    $003A   $   L_main_2:
    $003A   $283A           GOTO    L_main_2
    ;ZeroJitter.c,77 ::         }
    $003B   $283B           GOTO    $
     
    Note the MikroC interrupt context saving and restore is a little more than you really need for this project, but the compiler just does that automatically.
     
    • Like Like x 1
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. caroper

    caroper New Member

    Joined:
    Nov 26, 2009
    Messages:
    2
    Likes:
    0

    Thanks Roman, Much appreciated.

    And thanks for the compiler recommendation.
    I Think I have just about mastered the midrange (or at least fully grasped, I'm a long way from being a master), and have ordered some PIC18 Chips to play with. My intention was to dive into Learning C when I got onto the more Powerful CPU's, so I will take a look at MikroC.
     
  6. Sceadwian

    Sceadwian Banned

    Joined:
    Oct 27, 2006
    Messages:
    14,047
    Likes:
    141
    Location:
    Rochester, US
    Zero error is a bit deceptive, the algorithm still as a granularity that can't be avoided.
     
  7. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    A granularity? It will generate zero jitter exact 50:50 duty output, at a frequency adjustable to the finest possible resolution you can get with a PIC (1 TMR0 tick) with no errors. It doesn't get any better than that from a zero jitter 50:50 system.

    If you need a finer-adjusted frequency that what is possible from 1 timer tick it is necessary to dither the output to give greater freq resolution, which is what my other "zero-error" timer routines do. They can make incredibly fine resolution freq output, but of course since that exceeds what is possible from the PIC timer it involves jitter.
     
  8. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    Ok, with all the talk of inverters on the forum lately I whipped up a couple of projects. This one is a xtal-locked zero-jitter push-pull inverter that will output 50Hz with a 10Mhz xtal, or 60Hz with a 12MHz xtal (using the same code).

    Code is fully tested and works very nicely, it uses the zero-jitter example above but generates 8 times the mains frequency, then the two push-pull outputs are sequenced like this;
    Code (text):

    GP0 - 01110000
    GP1 - 00000111
     
    This makes a pretty decent push-pull squarewave so it can be connected directly to a pair of FETs, so basically this is the entire code for a xtal-locked mains inverter for times when you need a very precise mains frequency generated;

    Code (text):

    /******************************************************************************
      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
      }
    }
    //-----------------------------------------------------------------------------
     
     
  9. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    This next one came out very nice, I am quite pleased with it considering the very small simple code. It is a mains-locked 50Hz to 60Hz converter with push-pull inverter outputs basically its a complete inverter that sync-locks to the mains frequency.

    This has been discussed a bit recently, for people who want to run old 60Hz clocks on 50Hz or vice versa.

    The procedure is simplicity itself;
    1. 4MHz xtal generates error corrected 600Hz pulses
    2. Each incoming 50Hz mains cycle causes 12 pulses to be generated.
    3. Every 10 pulses it generates a 60Hz push-pull cycle.
    (it works perfectly for 60Hz to 50Hz conversion too, just edit one line of code)

    [​IMG]

    This is the entire brain for a proper mains-locked freq-converting inverter so you can run old clocks from different frequency mains. Code is fully tested and looks VERY nice on the CRO; :D

    Code (text):

    /******************************************************************************
      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--;
        }
      }
    }
    //-----------------------------------------------------------------------------
     
    These push-pull inverters are also on my zero-error timing routines page;
    Zero-error 1 second timing algorithm
     
    Last edited: Nov 29, 2009
  10. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,637
    Likes:
    109
    Location:
    Michigan, USA
    Roman (Mr RB),

    Concerning 50/60 Hz Inverters, would there be any advantage using a 50 Hz or 60 Hz sine wave generator?

    Regards, Mike

    Code (text):
    /********************************************************************
     *                                                                  *
     *  Project: Sine 50-60                                             *
     *   Source: Sine_50-60.c                                           *
     *   Author: Mike McLaren, K8LH                                     *
     *     Date: 03-Dec-09                                              *
     *  Revised: 03-Dec-09                                              *
     *                                                                  *
     *  12F683 50/60 Hz PWM Sine Wave Generator experiment uses fifty   *
     *  pwm intervals to construct a sine wave spanning 50,000 cycles   *
     *                                                                  *
     *  60-Hz output = 16.667-msec period using a 12-MHz crystal        *
     *  50-Hz output = 20.000-msec period using a 10-MHz crystal        *
     *                                                                  *
     *                                                                  *
     *      IDE: MPLAB 8.14 (tabs = 4)                                  *
     *     Lang: SourceBoost BoostC v6.95, Lite/Free version            *
     *                                                                  *
     ********************************************************************/

    #include <system.h>

    #pragma DATA _CONFIG, _FCMEN_OFF&_IESO_OFF&_MCLRE_OFF&_WDT_OFF&_HS_OSC


    const rom char sine[] = { 125,140,156,171,185,198,210,221,230,238,
                              243,247,249,249,247,243,238,230,221,210,
                              198,185,171,156,140,124,109,093, 78, 64,
                               51, 39, 28, 19, 11,  6,  2,  0,  0,  2,
                                6, 11, 19, 28, 39, 51, 64, 78, 93,109 };

    unsigned char n = 0;        // sine table index, 0..49

    void interrupt()            //
    { pir1.TMR2IF = 0;          // clear TMR2 interrupt flag
      ccpr1l = sine[n++];       // setup next duty cycle
      if(n = 50) n = 0;         // reset index at end-of-period
    }

    void main()                 //
    {                           //
      cmcon0 = 7;               // comparator off, digital I/O
      ansel = 0;                // a2d module off, digital I/O
      trisio = 0;               // set all pins to outputs
      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
      t2con = 0b00000101;       // '0-------' unimplemented bit
                                // '-0000---' TOUTPS<3:0>, postscale 1
                                // '-----1--' TMR2ON, turn Timer 2 on
                                // '------01' T2CKPS<1:0>, prescale 4
      pr2 = 250-1;              // 250 x prescale 4 = 1000 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
      {                         //
      }                         //
    }
     
     
    Last edited: Dec 5, 2009
  11. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    Very nice simple elegant code. CCP modules make life easy huh. ;)

    Sinewave inverter is definitely superior, but for driving 5 watt clocks with synchronous motors then a modified squarewave will work just as well.

    You code does lack push-pull outputs too, they are pretty important driving a little transformer.

    All you would have to do to add that feature is manually detect the PWM event (not in an int) and manually set the 2 push-pull output pins. Manually testing the flag only costs a 0-2 inst (0-0.8uS with 10MHz xtal) latency per PWM pulse which self corrects every PWM pulse anyway. Considering the low freq that would work fine, and you would get 2 complimentary PWM outputs without needing an enhanced PWM module AND still have a high efficiency sinewave.

    And after that step it would not be much more effort to remove the CCP module completely and manually use TMR1 and TMR0, which woul dmake the code workable on just about any of the bottom end 8pin PICs.

    Also you might want to double check these lines;
    Code (text):

     *  50-Hz output = 16.667-msec period using a 12-MHz crystal        *
     *  60-Hz output = 20.000-msec period using a 10-MHz crystal        *
     
    :)

    (edit)Here's a thought, you could keep your code as is, and use the PIC comparator set to mode 011 to perform a hardware inversion of your PWM output. ie attach PWM output to comparator Cin- then Cout goes out to PIC pin and gives the inverted PWM output so you get 2 push-pull outputs from the same tiny elegant code.
     
    Last edited: Dec 5, 2009
  12. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,637
    Likes:
    109
    Location:
    Michigan, USA
    Thanks you for the nice comments Roman. I corrected that typo' in the program listing.

    Could that single-ended sine wave output drive a push-pull transistor pair or would that circuitry be too complex?

    I agree that you don't need a CCP module to implement a jitter free cycle accurate pair of sine wave outputs. Or for a pair of push-pull square wave outputs for that matter. You don't even need a timer module. You could do it all with a simple isochronous loop on a six pin 10F200 if necessary.

    <added>

    The comparator module as an inverter for the complimentary sine wave signal is a good idea.

    Regards, Mike
     
    Last edited: Dec 5, 2009
  13. Gayan Soyza

    Gayan Soyza Active Member

    Joined:
    Oct 23, 2006
    Messages:
    1,821
    Likes:
    19
    Location:
    Colombo
    Mr Roman & Mike you two are brilliant.Thanks for sharing those the wonderful ideas :)
     
  14. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    It only really needs an extra transistor to invert the signal for one FET. But the really nice solution I think is to just use a PIC and drive 2 FETs direct from the PIC pins so the PIC acts as a complete brain.

    I'd like to see your isochronous loop solution. Even with a 4MHz xtal a 50Hz cycle takes 20000 instructions.

    What would work with limited ROM would be to use 50 small loops, each hardcoded to generate your 50 sinewave PWM values. Then keep your 250 inst TMR2 interrupt, and just inc n and forcibly jump into each timing loop. No int context saving is needed. You actually only need 25 timing loops and sequence 0-24 then 24-0 in the interrupt.

    I did something like that years ago which generated 300 kHz dual phase PWM on an old PIC 16F84A (!) for microstepping a 2 phase stepper motor.
     
  15. pasanlaksiri

    pasanlaksiri Member

    Joined:
    Aug 28, 2006
    Messages:
    328
    Likes:
    4
    Location:
    Sri Lanka
    Hi Mr. RB Thanks to ur Zero-Error and Zero-Jitter algorithm my clock works dead accurately. Im testing it for days now. Still ZERO errors.
     
  16. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,637
    Likes:
    109
    Location:
    Michigan, USA
    You're right. A single chip solution is better.
    It wouldn't take much more then one of my "cycle accurate" fixed delay subsystems (macro and 9 word subroutine) and a simple isochronous loop like this for push-pull square wave output;
    Code (text):
    ;******************************************************************
    ;  Zero Jitter 50 or 60 Hz push-pull square wave output (12F675)  *
    ;******************************************************************
    ;  60-Hz output = 16.667-msec period (12-MHz osc - 50000 cycles)  *
    ;  50-Hz output = 20.000-msec period (10-MHz osc - 50000 cycles)  *
    ;******************************************************************
            radix   dec
    pCycles equ     50000/8         ; cycles_in_period/8 = 6250
    ;
    ;  GP0  _-------------____________________
    ;  GP1  __________________------------____
    ;       |   16.667-msecs / 20.000-msecs   |
    ;
    NewPeriod
            movlw   b'00000001'     ;                                 |B0
            movwf   GPIO            ;                                 |B0
            DelayCy(3*pCycles-1)    ; GP0 "on" for 3/8ths period      |B0
            clrf    GPIO            ;                                 |B0
            DelayCy(1*pCycles-2)    ; GP1:GP0 "off" 1/8th period      |B0
            movlw   b'00000010'     ;                                 |B0
            movwf   GPIO            ;                                 |B0
            DelayCy(3*pCycles-1)    ; GP1 "on" for 3/8ths period      |B0
            clrf    GPIO            ;                                 |B0
            DelayCy(1*pCycles-4)    ; GP1:GP0 "off" 1/8th period      |B0
            goto    NewPeriod       ;                                 |B0
    ;
    [​IMG]
    Now correct me if I'm wrong but couldn't we just drive the N-FET inputs with filtered sine wave patterns like those below to produce sine wave output instead of square wave output?

    [​IMG]

    If that's all that's required then I think it could be done quite easily without a CCP module or timer modules using just a simple isochronous loop. I'll take a try at this code in the next few days.

    Regards, Mike
     

    Attached Files:

  17. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    You made a beautiful schematic image (as always ;)) but it's probably best described as "Ultra-simple push-pull modified square wave inverter" rather than using my name as it is really a standard push pull inverter topology, just using the minimum parts that will actually do the job.

    That's tiny code for getting the job done. It would be nice to include the macro too if someone wants to build it. This page looks like it's becoming a resource for building tiny inverters which is pretty cool. :)

    As for using the RC filters and driving the FETs with analog sines, that's a bad idea because it runs the FETs in the linear region where they will dissipate a lot of power so inverter efficiency is low and the FETs now need to be big and have big heatsinks.

    Your original PWM sine is far better for efficiency, provided of course it has the dual push-pull PIC outputs.
     
  18. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    I hope you don't mind Mike but I merged my 50Hz to 60Hz converter code into your nice PWM sine generator code.

    This generates 24 "virtual pulses" (using TMR1) for every 50Hz incoming mains cycle.

    Then it generates the output 60Hz sine over 20 of those virtual pulses, hence making a 24:20 period conversion so the 60Hz sine output is freq locked to the 50Hz mains input and remains as perfect as the national grid.

    Also I changed the PWM period to 100 instructions (instead of your 1000 instructions) this bumps the PWM freq from 2.5kHz up to a nice 25kHz, giving less noise and easier filtering by the output transformer. It will need a new PWM table with 20 values in the 0-99 count range.

    The interrupt does nothing now, as the PWM period is automatic (every 100 instructions) and asynchronous to both the incoming and output mains frequencies. This is useful because it allows freq conversion in either direction with no hassles because the PWM does not need to be synced to either mains freq.


    Code (text):

    /********************************************************************
     *                                                                  *
     *  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
        }
      }                        
    }
     
    If you wanted to adapt this code to do 60Hz in to 50Hz out, it would need a 24 entry sine table, and do 24:20 inversion;
    Code (text):

        pulse = 24;           change to;  pulse = 20;
        if(n >= 20) n = 0;  change to;  if(n >= 24) n = 0;
     
    and still use the 10MHz xtal.

    (edit) This only makes a single PWM sine output, so if push-pull outputs are needed it requires a hardware inverter to generate outputB by generating and inverted outputA. A transistor and couple of resistors would be enough to do the hardware inversion.
     
    Last edited: Dec 7, 2009
  19. pasanlaksiri

    pasanlaksiri Member

    Joined:
    Aug 28, 2006
    Messages:
    328
    Likes:
    4
    Location:
    Sri Lanka
    Wow this thread is getting very interesting.
     
  20. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,637
    Likes:
    109
    Location:
    Michigan, USA
    Not sure I follow your 50 to 60 Hz convertors but anyway, is this the sine table you're looking for?

    Happy Holidays. Mike

    Code (text):
    Number of steps: 20
       Output range: 99
     Table or Array: t

            retlw   049             ; 000  +0
            retlw   064             ; 001  +0.30901699
            retlw   078             ; 002  +0.58778525
            retlw   089             ; 003  +0.80901699
            retlw   096             ; 004  +0.95105652
            retlw   099             ; 005  +1
            retlw   096             ; 006  +0.95105652
            retlw   089             ; 007  +0.80901699
            retlw   078             ; 008  +0.58778525
            retlw   064             ; 009  +0.30901699
            retlw   049             ; 010  -0.41020686e-9
            retlw   034             ; 011  -0.30901699
            retlw   020             ; 012  -0.58778525
            retlw   009             ; 013  -0.80901699
            retlw   002             ; 014  -0.95105652
            retlw   000             ; 015  -1
            retlw   002             ; 016  -0.95105652
            retlw   009             ; 017  -0.80901699
            retlw   020             ; 018  -0.58778525
            retlw   034             ; 019  -0.30901699
     
     
    Last edited: Dec 6, 2009
  21. Mr RB

    Mr RB Well-Known Member

    Joined:
    Jul 22, 2008
    Messages:
    4,716
    Likes:
    194
    Location:
    Out there
    Hehe I knew you would have a neat PERL app or EXCEL something to generate the sine values and save me reaching for my X2 and doing it the hard way.

    Thanks, the sine values have been added into code above.

    (edit) Here are a couple of pictures showing how the mains freq-locked 50Hz to 60Hz conversion works and why it only needs such simple code; :)
     

    Attached Files:

    Last edited: Dec 6, 2009

Share This Page