Jitter free square wave doubler with TeensyTimerTool?

Status
Not open for further replies.

TeensyWolf

Well-known member
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.
 
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
 
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.
 
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/blob/master/gpt_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.
 
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?
 
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.
 
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 ...
 
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?
 
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:
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);
    }
}
 
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. "
 
> 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");
 
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.
 
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:
Maybe an XOR gate is good enough?
Frequenzverdoppler_mit_XOR.png

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

https://de.wikipedia.org/wiki/Frequenzverdopplung_(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?
 
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:
Status
Not open for further replies.
Back
Top