Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 16 of 16

Thread: Jitter free square wave doubler with TeensyTimerTool?

  1. #1
    Senior Member
    Join Date
    Feb 2020
    Posts
    104

    Jitter free square wave doubler with TeensyTimerTool?

    I have a project where I'm getting a square wave at ~48 Hz and I need to create a ~96Hz square wave with minimal jitter.

    Last time I tested with the "machine", I was using a combination of interrupt and polling, the jitter on the falling edge was pretty bad, due to polling. It's feeding a PLL and takes too long to lock. But taking the trip to visit and test with the "machine" is a full day for me, and I'd like to reduce/eliminate the jitter so no more trips are necessary. There is also the possibility of having a variable delay in the future, tbd.

    My working solution so far:
    1. Setup OneShotTimer with TeensyTimerTool (great library!) to set my OUTPUT pin LOW
    2. Use a GPIO interrupt, on signal CHANGE, interrupt priority set to 0 on INPUT pin.
    3. In ISR, 1st thing I do is set the OUTPUT pin HIGH
    4. Then I set the OneShotTimer trigger at half the period

    This works great and I might leave it at that, my wave is fairly square, slightly delayed from the input, but I get about 25nS of jitter on my OUTPUT and I'm not sure if that is good enough.

    I could use external hardware (flip flop) in combination with the OneShotTimer, but it would eliminate the ability to add a variable delay.

    I'm thinking that I can get better using an input capture to trigger the OUTPUT pin and an interrupt at the same time. I would need to have the capture time to then set the OneShotTimer, so the square wave is near perfect.

    I'm looking for an example of input capture that triggers a pin and lets me have access to the time of the trigger.

    Thanks.

  2. #2
    Senior Member
    Join Date
    Apr 2020
    Location
    DFW area in Texas
    Posts
    304
    Quote Originally Posted by TeensyWolf View Post
    I have a project where I'm getting a square wave at ~48 Hz and I need to create a ~96Hz square wave with minimal jitter.
    Would it be possible to use the "button" library to take the 48Hz input clock as if it is a button input, & then setup the "button press action" to trigger on both edges of the input clock. When the "button press action" fires, you capture the number of millis() since the last action fired. Store that value divided by two into "period". You keep a local variable of the state of your "doubler" output (HIGH/LOW). For each "button press", you toggle the state of the "doubler". After "period" millis() have elapsed (using the normal elapsed millis monitoring methods in loop(), you toggle the state of "doubler" again. Each time "doubler" is toggled, you drive your "doubled clock" output with the current state. The "button" library does all the work of watching for the input to change & elapsed millis() does the work of managing the period. YMMV

    Mark J Culross
    KD5RXT

  3. #3
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    What Teensy model?

    Is the sketch doing anything else?

    Maybe there is a better hardware solution?

    Software could set up a 1MHz (?) interrupt that normally does nothing.
    > When you know the timing for the pulse trigger do the first output and set the next output value, calculate the ARM_DWT_CYCCNT value when the next change should happen
    > that do nothing _isr() instead on the next microsecond BEFORE the needed ARM_DWT_CYCCNT will appear. Then do a wait loop before triggering the desired output on schedule.
    -> change the next output value - or if appropriate - just use digitalToggle()
    -> update the next update value for ARM_DWT_CYCCNT and exit.

  4. #4
    Senior Member
    Join Date
    Feb 2020
    Posts
    104
    Quote Originally Posted by defragster View Post
    What Teensy model?

    Is the sketch doing anything else?

    Maybe there is a better hardware solution?

    Software could set up a 1MHz (?) interrupt that normally does nothing.
    > When you know the timing for the pulse trigger do the first output and set the next output value, calculate the ARM_DWT_CYCCNT value when the next change should happen
    > that do nothing _isr() instead on the next microsecond BEFORE the needed ARM_DWT_CYCCNT will appear. Then do a wait loop before triggering the desired output on schedule.
    -> change the next output value - or if appropriate - just use digitalToggle()
    -> update the next update value for ARM_DWT_CYCCNT and exit.
    Running T4 here.

    This looks like a good starting point: https://github.com/manitou48/teensy4...pt_capture.ino

    A GPT can't flip a pin directly on input capture or force a output compare, so I'll need some external hardware, flip flop & some logic perhaps, then I just need to grab the GPTx_ICR register, add my half-period value in 1/24E6 th of a second and store it in the GPTx_OCR register for the output compare. 2 ISRs, one on capture, one on compare.

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    What does the input look like? "a square wave at ~48 Hz " - is it constant and does it need to stay timed to that?

    Seems like "ability to add a variable delay." - is a delay in starting or changing the output wave on subsequent pulses?

    Is that T_4 doing anything else - or just needing to make that wave square and properly timed?

  6. #6
    Senior Member
    Join Date
    Feb 2020
    Posts
    104
    Quote Originally Posted by defragster View Post
    What does the input look like? "a square wave at ~48 Hz " - is it constant and does it need to stay timed to that?

    Seems like "ability to add a variable delay." - is a delay in starting or changing the output wave on subsequent pulses?

    Is that T_4 doing anything else - or just needing to make that wave square and properly timed?
    At the moment, it's not doing much. It will have a display at some point, and user input on a nav switch.

    But at 24mhz clock, jitter will be too much (41nS). I tried generating a square wave, free running from the input square wave, but the jitter was messing with the PLL. Maybe the 150MHz clock would be better at 6.6nS.

    I've gotten some flip flops on order, will try the hardware solution, then build on it from there. The input capture approach likely won't work for this thing.

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    Asked about 'other things running' - as the software approach suggested would block for some part microsecond(s) - depending on the interrupt timer rate that works while polling the clock/CYCCNT to toggle. Plus the recurring interrupt will take a few cycles doing nothing.

    No idea how responsive it would be - if it works with 600 MHz CPU clock the firing should be consistent withing ~4 clock cycles ... a couple of ns ?

    Would be interesting to see ...

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,635
    Does the generated 96Hz signal need to be phaselocked?
    If not, couldn't you simply measure the input frequency and generate the double one with a periodic timer?

  9. #9
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    Software version for p#3 was imagined like this - with assumptions as incorporated.

    Changed to 50 Hz input as it fixed the math for the DEMO PWM trigger - which was only looked at once. The calc can be edited in isrPin() - as long as the change won't jitter the output and mess up the PLL on the other end - and could be tricky to adjust while running.

    Both interrupts are the same default priority.

    Usage of Hz is liberal in the output line ... but it takes 50 Hz doubled to 100 Hz - but toggles at 200 times/sec

    Code:
    // https://forum.pjrc.com/threads/68503-Jitter-free-square-wave-doubler-with-TeensyTimerTool?p=291678&viewfull=1#post291678
    
    IntervalTimer myTimer;
    bool pinActive = false;
    volatile uint32_t nextToggle;
    volatile uint32_t debugToggle = 0;
    #define OUTPUT_RATE 200
    #define DEMO_RATE 50
    
    #define PRIOR2TICK 1000 // prior tick ready for tiggle time for 1 MHz _isr()
    void isrPin() {
      uint32_t myCYCCNT = ARM_DWT_CYCCNT;
      if ( !pinActive ) {
        digitalWriteFast( 16, 1 );
        pinActive = true;
        nextToggle = myCYCCNT + F_CPU_ACTUAL / (OUTPUT_RATE) - PRIOR2TICK;
      }
      // add check and update to nextToggle here if 48 Hz can slip
    }
    
    void isrTimer() {
      uint32_t myCYCCNT = ARM_DWT_CYCCNT;
      if ( myCYCCNT - nextToggle < PRIOR2TICK ) {
        // Wait until toggle needed
        myCYCCNT += PRIOR2TICK;
        while ( ARM_DWT_CYCCNT < PRIOR2TICK );
        digitalToggleFast( 16 );
        nextToggle += F_CPU_ACTUAL / (OUTPUT_RATE);
        debugToggle = myCYCCNT;
      }
    }
    
    elapsedMicros showCount;
    void setup() {
      Serial.begin(115200);
      Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
      pinMode( 15, INPUT );
      pinMode( 16, OUTPUT );
      myTimer.begin(isrTimer, 1);  // adjust PRIOR2TICK
      attachInterrupt(15, isrPin, RISING);
    
      // DEMO input for testing wire pin 14 to 15
      analogWriteFrequency( A0, DEMO_RATE ); // A0 is p#14
      analogWriteResolution(12); // 0-4095
      analogWrite( A0, 2048 );
      showCount = 0;
    }
    
    uint32_t myDebugToggle = 0;
    uint32_t loopCnt = 0;
    void loop() {
      loopCnt++;
      if ( debugToggle != myDebugToggle ) {
        Serial.printf( "toggle was %lu apart.  { %u Hz is %lu )\n", debugToggle - myDebugToggle, OUTPUT_RATE, F_CPU_ACTUAL / OUTPUT_RATE );
        myDebugToggle = debugToggle;
      }
      if ( showCount > 1000000 ) {
        Serial.printf( "\tLoops per second is %lu )\n", loopCnt );
        showCount -= 1000000;
        loopCnt = 0;
      }
    }
    The above is triggering on the 1/600,000,000th clock cycle each time - and still completing 3.5 million loop calls per second
    Code:
    ...
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    	Loops per second is 3570167 )
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    toggle was 3000000 apart.  { 200 Hz is 3000000 )
    ...
    Last edited by defragster; 10-25-2021 at 11:52 PM. Reason: ref p#3

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,635
    I was thinking of something simple like (untested):

    Code:
    #include "Arduino.h"
    #include "FreqMeasure.h"
    #include "TeensyTimerTool.h"
    using namespace TeensyTimerTool;
    
    constexpr uint8_t outPin = 0;  // any pin
    PeriodicTimer t(PIT);
    
    void setup()
    {
        FreqMeasure.begin(); // input pin 22 is fixed
        pinMode(outPin, OUTPUT);
        t.begin([]{digitalToggleFast(outPin);}, 2*96_Hz); // might be better to start in stopped mode and start timer after first measurement...
    }
    
    void loop()
    {
        if(FreqMeasure.available())
        {
            float period = FreqMeasure.countToNanoseconds(FreqMeasure.read())/4000.0f;  // EDIT: get quarter period in Ás, toggle needs to be called with T/2 
            t.setNextPeriod(period);
        }
    }

  11. #11
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    Quote Originally Posted by luni View Post
    I was thinking of something simple like (untested):

    ...
    Seemed from the OP it has to be sync'd to the incoming 48 Hz signal?

    Code in p#9 will start on time ... but likely drift from the incoming signal without some averaging and adjustment in isrPin().

    p#9 code also starts output without ref to pinActive - that isrPin(), not setup(), should do the myTimer.begin(isrTimer, 1);.

    It might average some 8 or 16 incoming 48 Hz ARM_DWT_CYCCNT hits before factoring that into 'nextToggle' calc {instead of hardcoded ref to only F_CPU_ACTUAL} then enabling the isrTimer() output.

    That factor would also be needed for " ability to add a variable delay. "

  12. #12
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,084
    > interrupt priority set to 0 on INPUT pin.

    This will jitter occasionally because various things unnecessarily turn *all* interrupts off and then do things for who knows how long. I propose that the highest priority interrupts not be disabled. I *think* this change to imxrt.h would do it without creating race conditions for things set at default interrupt priority.

    #define __disable_irq() __asm__ volatile ("MSR basepri, %0" : : "r" (16) : "memory");
    #define __enable_irq() __asm__ volatile ("MSR basepri, %0" : : "r" (0): "memory");

  13. #13
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,393
    You will alway have jitter because the core disables the interrupts at several places.
    That's independend of the priority.
    The only reliable way is - yes, disable all and do polling.
    Of course you will loose USB if you do this too long.

  14. #14
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,393
    A CPU, or better, software, is not the right tool for this job. The allowed jitter is too tight. Use a dedicated chip (PLL?) or some discrete logic.
    Or maybe there is way to do that in hardware with one of the timers.
    Perhaps the programmable delay block. But I don't know if it can be used with pins. You can find this information in the reference manual.
    Last edited by Frank B; 10-25-2021 at 07:59 PM.

  15. #15
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,393
    Maybe an XOR gate is good enough?
    Click image for larger version. 

Name:	Frequenzverdoppler_mit_XOR.png 
Views:	13 
Size:	1.4 KB 
ID:	26285

    Sorry, no article in English. With tuning RC you can adjust the width of the impulses.

    https://de.wikipedia.org/wiki/Freque...g_(Elektronik)

    It it was 48kHz, i would have an Idea.. the Teensy 4 has hardware SPDIF in with a PLL... I doubt it can lock to 48Hz. Or, maybe it can?

  16. #16
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,259
    The software solution p#9 , p#3 seems to work okay - against fixed single test incoming pin rise ...

    With 10X freq of 500 Hz to 2000 toggles and the printing gets in the way {bottom}, but the jitter is only +/- 6 clock cycles even then : so 6 parts in 600,000,000 isn't much.

    Doing only output on the second (except when CYCCNT misses the mark) and with 20,000 output toggles { 100X more} loop count went up to 6.5M without excess debug output: Loops per second is 6561838
    Code:
    #define OUTPUT_RATE 20000
    #define DEMO_RATE 5000
    There is rare jitter with toggle every 30,000 cycles - possibly from the two interrupts of same priority hitting at the same time:
    Code:
    toggle was 30000 apart.  { 20000 Hz is 30000 )
    	Loops per second is 6561838 )
    
    toggle was 30000 apart.  { 20000 Hz is 30000 )
    	Loops per second is 6561838 )
    ...
    toggle was 29994 .vs. 30000 )
    toggle was 30006 .vs. 30000 )
    	Loops per second is 6561665 )
    After running 12926 seconds a total of 263 misses - the odd one is on startup with prior CYCCNT ​unset
    Code:
    And the rest are paired:
    59 at  toggle was 29994 .vs. 30000 )
    59 at  toggle was 30006 .vs. 30000 )
    
    48 at toggle was 30001 .vs. 30000 )
    48 at toggle was 29999 .vs. 30000 )
    
    24 at toggle was 30002 .vs. 30000 )
    24 at toggle was 29998 .vs. 30000 )
    This code did the above - wire with p#14 to #15 and output on #16:
    Code:
    // https://forum.pjrc.com/threads/68503-Jitter-free-square-wave-doubler-with-TeensyTimerTool?p=291678&viewfull=1#post291678
    
    IntervalTimer myTimer;
    bool pinActive = false;
    volatile uint32_t nextToggle;
    volatile uint32_t debugToggle = 0;
    #define OUTPUT_RATE 20000
    #define DEMO_RATE 5000
    
    #define PRIOR2TICK 1000 // prior tick ready for tiggle time for 1 MHz _isr()
    void isrPin() {
      uint32_t myCYCCNT = ARM_DWT_CYCCNT;
      if ( !pinActive ) {
        // Could average some 8 to get 'measured' cycles from the input before doing pinActive
        // External clock not likely to match the hardcoded "F_CPU_ACTUAL / (OUTPUT_RATE)"
        digitalWriteFast( 16, 1 );
        pinActive = true;
        myTimer.begin(isrTimer, 1);  // adjust PRIOR2TICK
        nextToggle = myCYCCNT + F_CPU_ACTUAL / (OUTPUT_RATE) - PRIOR2TICK;
      }
      // add check and update to nextToggle here if 48 Hz can slip
    }
    
    void isrTimer() {
      uint32_t myCYCCNT = ARM_DWT_CYCCNT;
      if ( myCYCCNT - nextToggle < PRIOR2TICK ) {
        // Wait until toggle needed
        myCYCCNT += PRIOR2TICK;
        while ( ARM_DWT_CYCCNT < PRIOR2TICK );
        digitalToggleFast( 16 );
        nextToggle += F_CPU_ACTUAL / (OUTPUT_RATE);
        debugToggle = myCYCCNT;
      }
    }
    
    elapsedMicros showCount;
    void setup() {
      Serial.begin(115200);
      Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
      pinMode( 15, INPUT );
      pinMode( 16, OUTPUT );
      attachInterrupt(15, isrPin, RISING);
    
      // DEMO input for testing wire pin 14 to 15
      analogWriteFrequency( A0, DEMO_RATE ); // A0 is p#14
      analogWriteResolution(12); // 0-4095
      analogWrite( A0, 2048 );
      showCount = 0;
    }
    
    uint32_t myDebugToggle = 0;
    uint32_t loopCnt = 0;
    void loop() {
      loopCnt++;
      if ( debugToggle != myDebugToggle ) {
        if ( showCount > 1000000 ) {
          Serial.printf( "\tLoops per second is %lu )\n", loopCnt );
          Serial.printf( "\ntoggle was %lu apart.  { %u Hz is %lu )\n", debugToggle - myDebugToggle, OUTPUT_RATE, F_CPU_ACTUAL / OUTPUT_RATE );
          showCount -= 1000000;
          loopCnt = 0;
        }
        if ( debugToggle - myDebugToggle != F_CPU_ACTUAL / OUTPUT_RATE )
          Serial.printf( "toggle was %lu .vs. %lu )\n", debugToggle - myDebugToggle, F_CPU_ACTUAL / OUTPUT_RATE );
        /*    else {
              Serial.print( "." );
              if ( !(loopCnt % 100) ) {
                Serial.printf( "\ntoggle was %lu apart.  { %u Hz is %lu )\n", debugToggle - myDebugToggle, OUTPUT_RATE, F_CPU_ACTUAL / OUTPUT_RATE );
                //Serial.println( );
              }
          }
        */
        myDebugToggle = debugToggle;
      }
    }
    Before string output was shortened the 6 cycle jitter was the same (each '.' dot is response on the expected cycle) - but the loop count went down from 3.5M before: Loops per second is 3166258
    Code:
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    ......................................................
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    toggle was 300006 .vs. 300000 )
    toggle was 299994 .vs. 300000 )
    ..................................................
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    toggle was 300006 .vs. 300000 )
    toggle was 299994 .vs. 300000 )
    ..................................................................	Loops per second is 3166258 )
    ..........
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    .......................................................................................................................
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    toggle was 300006 .vs. 300000 )
    toggle was 299994 .vs. 300000 )
    .........................
    toggle was 300000 apart.  { 2000 Hz is 300000 )
    toggle was 300006 .vs. 300000 )
    toggle was 299994 .vs. 300000 )
    ..................................................
    Last edited by defragster; 10-26-2021 at 03:47 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •