T4: jitter-free low frequency output pulse?

Status
Not open for further replies.

JBeale

Well-known member
I'm looking for an accurately-settable, low frequency, jitter-free output pulse from Teensy 4. The Frequency Timer 2 library allows setting the period in microseconds, but is not yet ported to T4. The analogWriteFrequency(pin, freq) command does work, but the minimum frequency seems to be around 18 Hz. While it takes frequency as a floating-point value, I'm not sure what the true resolution is. Looking at cores/teensy4/pwm.c I don't know how to tell whether it's using flexpwm or quadtimer, or if there is any performance difference. Before I dive into the timer hardware registers, is there any other example of hardware frequency output on T4?
 
PWM, clocks, prescalers and timing resolution

Thanks, I will search for it. When I do this:
Code:
  analogWriteFrequency(4, 20); 
  analogWrite(4, 128);
I get an output from pin 4 which is fairly close to 20 Hz, and it is stable over time, but it is not exact. On my T4 I measure the signal about 5 ppm fast, so the square wave period is 0.25 microseconds less than exactly 50 milliseconds .

I tried setting other, slightly lower values to compensate, and I found that PWM frequency = 19.99991703 Hz gives me an output signal 20 ppm too slow, but 19.99991704 is 5 ppm too fast. So the PWM output period is changing in steps of 1.25 microseconds, and at this output frequency I have 25 ppm resolution. I suppose that must be related to a 16-bit timer which resolves 1/(2^16) or 15 ppm at best, and less with whatever prescaler is needed to get the desired cycle period into range without an overflow. I guess the counter clock must be around 800 kHz in this case.

If there was a 150 MHz clock driving a 32-bit timer, I would expect 6.67 nsec period resolution would be possible all the way out to 28 seconds. Not sure if there is some other limitation though.
 
@JBeale - You might find Beta Tests post #3578 and #3586 interesting. These refer to making the 150 MHz peripheral clock available for GPT2, which has a 32 bit counter.

I worried about making program code changes to the gating (the manual has a warning note), but found that only GPT and PIT peripherals need to be disabled when making the change.

You might wish to investigate the consistency of ISR timing (with elevated ISR NVIC priority and lowered systick). From memory, I seem to recall finding this much more "regular/predictable" than T3. I wondered why this might be true. Is there a better interupt mechanism for saving register states? I think I also measured the ISR latency to be around 100 nS (?) compared to 200 nS for T3. If this latency does turn out to be consistent then even a "digitalWriteFast" inside an ISR might have small jitter? I intend to look into this myself when chores permit.
 
@TelephoneBill - thank you very much, I see https://forum.pjrc.com/threads/54711-Teensy-4-0-First-Beta-Test?p=209786&viewfull=1#post209786 looks very encouraging. Is your 150 MHz clock testing code available anywhere (github, etc.) ?
Also, I haven't done much with direct access to hardware timers on the Teensy platform. Is there some reference that tells me what T4 hardware modules are already being used in the background even without loading any libraries? I assume delay(), delayMicroseconds() as well as analogWrite() etc. all have hardware support.

The i.MX RT1060 reference manual mentions the chip has two GPT timers, with the GPT details starting on p. 3073. Did you select GPT2 because Teensy core is using GPT1 for other purposes?

To generate a clean output signal I would use the output compare function. Looking at the T4 schematic, the three "output compare" signals from GPT2 are GPIO_AD_B0_06, _07, _08 and they all connect only to the MKL02Z32 (bootloader chip?) and are not available as Teensy GPIO pins. GPT1 however has output compare GPIO_EMC_35, 36, 37 and two of those at least are available as pads on the bottom of the T4 board (30, 31).

Thanks for any pointers!
 
Last edited:
@TelephoneBill - thank you very much, I see https://forum.pjrc.com/threads/54711-Teensy-4-0-First-Beta-Test?p=209786&viewfull=1#post209786 looks very encouraging. Is your 150 MHz clock testing code available anywhere (github, etc.) ?
Also, I haven't done much with direct access to hardware timers on the Teensy platform. Is there some reference that tells me what T4 hardware modules are already being used in the background even without loading any libraries? I assume delay(), delayMicroseconds() as well as analogWrite() etc. all have hardware support.

The i.MX RT1060 reference manual mentions the chip has two GPT timers, with the GPT details starting on p. 3073. Did you select GPT2 because Teensy core is using GPT1 for other purposes? Thanks for any pointers!

I have some T4 timer sketches at https://github.com/manitou48/teensy4

GPT isn't used by Teensy core, but recent T4 update to threads library is using GPTx

update: see later thread on slow PWM for T4
 
Last edited:
At least on the T 3.x, there was a historical lower limit for the analogWriteFrequency, caused by the bus clock (48 or 60MHz, depending on F_CPU), the maximum prescaler value (128), and the maximum 65535 modulo value of the timers. This was later (but still on T3.x) improved by using alternatively the 32.768kHz RTC clock to allow lower frequencies. But whatever clock source you use, there is always a sort of granularity, since the period of the output signal has always to be an integer multiple of the clock divided by prescaler period, and that won't change with the T4. So, even if you can request arbitrary frequencies with a float value, the clock source, prescaler and timer modulo will always be set to values as close as possible to your request without forcibly hitting it precisely.
 
Is there some reference that tells me what T4 hardware modules are already being used in the background even without loading any libraries? I assume delay(), delayMicroseconds() as well as analogWrite() etc. all have hardware support.

I also wondered what peripherals might be already in use. I don't have any knowledge - perhaps one of the experts will point you in the right direction.

Attached is some commented code on using GPT2. I chose GPT2 because the outputs/inputs were convenient (seem to remember some pin restriction on GPT1 - was it the capture not being available?). This example uses the 32bit counter to count 75,000 ticks of the 150 MHz clock, giving a HALF PERIOD of 500uS or 1 KHz frequency, but the limit would be H.P. of 28 seconds (2^32 times 6.667 nS). With the ppm error, you would need some tweaking of the compare value to get the precision. Note there is only one counter, even though there are three compare registers.

Notice in the RM diagram on page 3074 (Fig 51-1) that it is ONLY Compare1 that can reset the counter on a comparison match, which is why I also set this in my code, even though I use Compare3 as the toggle output signal. It took me a while to understand how these Compare's work for this GPT style timer (as you may read in my RED ALERT concern in the Beta Tests).

My ISR fires every 500 uS, so I count 2000 ISRTicks to get a one second flash of the LED.

Code:
//TESTT4004 - GPT TEST PROGRAM for T4
//===================================
//Author: TelephoneBill
//Date: 19 AUG 2019
//Version: 001

//NOTES: Using GPT2 as the timer. GPT2 Output Compare3 (1 KHz) is on pin 16. CompareValue = 0x000124F7 = (75,000 - 1)
//Compare1 is the counter reset mechanism. Compare2 is not used here. Compare3 is the toggle output on pin 16.

//definitions
uint32_t Save1, Save2;
byte Byte1;
volatile uint32_t ISRTicks = 0;

//SETUP
//=====
void setup() {
  //initialise general hardware
  Serial.begin(115200);             //setup serial port
  pinMode(13, OUTPUT);              //pin 13 as digital output
  FlashLED(4);                      //confidence boost

  //set clock gating registers to inhibit GPT and PIT clocks (while changing to 150 MHz)
  Save1 = CCM_CCGR0;                          //save current state 0
  Save2 = CCM_CCGR1;                          //save current state 1
  CCM_CCGR0 &= 0xF0FFFFFF;                    //inhibit GPT2 (CG12,CG13)
  CCM_CCGR1 &= 0xFF0FCFFF;                    //inhibit GPT1 and PIT (CG6,CG10,CG11)
  
  //change the mux setting for IPG_CLK_ROOT (set bit6 = 0). This enables the 150 MHz.
  CCM_CSCMR1 &= 0xFFFFFFBF;
  
  //restore GPT and PIT gated clocks to former values
  CCM_CCGR0 = Save1;
  CCM_CCGR1 = Save2;
  
  //configure clocks for GPT2 module
  CCM_CCGR0 |= 0x0F000000;                    //enable clocks to GPT2 (CG12,CG13)

  //configure GPT2 for test
  GPT2_CR = 0;                                //clear the control register, FRR = 0 means restart after Compare
  GPT2_SR = 0x3F;                             //clear all prior status
  GPT2_PR = 0;                                //prescale register set divide by 1
  GPT2_CR |= GPT_CR_CLKSRC(1);                //clock selection #1 (peripheral clock = 150 MHz)
  GPT2_CR |= GPT_CR_ENMOD;                    //reset count to zero before enabling
  GPT2_CR |= 0x04800000;                      //Compare3 = toggle mode, Compare2 = toggle mode
  
  GPT2_OCR1 = 0x000124F7;                     //Compare1 value (74,999 decimal)
  GPT2_OCR2 = 0x000124F7;                     //Compare2 value (74,999 decimal)
  GPT2_OCR3 = 0x000124F7;                     //Compare3 value (74,999 decimal)
  GPT2_IR = 0x00000004;                       //enable interrupt for Compare flag  
  GPT2_CR |= GPT_CR_EN;                       //enable GPT1 counting at 150 MHz

  //configure Teensy pin 16 as Compare3 output
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_07 = 8;    // GPT2 Compare3 is now output on pin 16
  
  //enable GPT1 interrupt within NVIC table
  attachInterruptVector(IRQ_GPT2, GPT2_isr);  //declare which routine performs the ISR function
  NVIC_ENABLE_IRQ(IRQ_GPT2);
}


//ISR ROUTINE FOR GPT1
//====================
//FASTRUN puts this code into RAM to run twice as fast
FASTRUN void GPT2_isr(void) {
  GPT2_SR = 0x00000004;                       //reset the interrupt flags in status register
  ISRTicks++;
  if (ISRTicks>=2000) {
    ISRTicks = 0;  
    digitalWriteFast(13, 1);  
  }
  if (ISRTicks==100) {
    digitalWriteFast(13, 0);  
  }
  asm volatile("dsb");
}


//MAIN LOOP
//=========
void loop() {
  //call KeyInput() routine
  KeyInput();                         //no key currently used, but could be employed to change timer period dynamically
}

//SUBROUTINES
//===========
//Flash LED routine
void FlashLED(int m) {
  for (int n=0;n<m;n++) {
    digitalWriteFast(13, 1);          //set pin 13 high
    delay(100);
    digitalWriteFast(13, 0);          //set pin 13 low
    delay(100);
  }
}

void KeyInput() {
  //process any keystrokes available
  if (Serial.available()>0) {
    //read the incoming byte
    Byte1 = Serial.read();
    if (Byte1>0x20) {
      switch (Byte1) {
      case 'A':  //
        //task goes here...
        break;
      }
    }
  }
}
 
Thanks, I will search for it. When I do this:
Code:
  analogWriteFrequency(4, 20); 
  analogWrite(4, 128);
I get an output from pin 4 which is fairly close to 20 Hz, and it is stable over time, but it is not exact. On my T4 I measure the signal about 5 ppm fast, so the square wave period is 0.25 microseconds less than exactly 50 milliseconds .

I tried setting other, slightly lower values to compensate, and I found that PWM frequency = 19.99991703 Hz gives me an output signal 20 ppm too slow, but 19.99991704 is 5 ppm too fast. So the PWM output period is changing in steps of 1.25 microseconds, and at this output frequency I have 25 ppm resolution. I suppose that must be related to a 16-bit timer which resolves 1/(2^16) or 15 ppm at best, and less with whatever prescaler is needed to get the desired cycle period into range without an overflow. I guess the counter clock must be around 800 kHz in this case.

If there was a 150 MHz clock driving a 32-bit timer, I would expect 6.67 nsec period resolution would be possible all the way out to 28 seconds. Not sure if there is some other limitation though.

Visit core hardware/teensy/avr/cores/teensy4/pwm.c to see calculation of quadtimer PWM prescale and divider. With 150mhz bus clock, lowest frequency is 150000000/128/65536 = 17.881393 hz. From pwm.c, request for 20 hz, gets prescale 128 and divisor 58593 = 20.000256 hz, or using 58594 to get 19.999914 hz. And your T4's 24mhz crystal has some frequency error (± 10 ppm?, measure with GPS PPS, varies with temperature).
 
thank you TelephoneBill!

Attached is some commented code on using GPT... (from TelephoneBill)
Outstandingly useful, thank you very much! With your code, merely by substituting in a timer constant of (74999999-611) I was able to achieve a 1 PPS output on my particular T4 that stayed within 4 ppb (!) of my GPSDO reference, at least for a few minutes until it drifted off again, probably due to my unit's -0.24 ppm/C tempco. The T4 is now the highest resolution adjustable pulse generator I've ever had. The closest thing previously was a Parallax Propeller with 100 MHz clock, but that was in general much more limited, and I'd also done some FPGA projects but they were big and clumsy for general-purpose things.

I will look forward to understanding everything you did with this code. My reading of the manual is that that any write to the OCR1 register always resets the counter to 0, so you can't make real-time fine adjustments to the output timing with that channel without interrupting the output. However I was thinking I could adjust frequency continuously, if I relied on OCR2 or OCR3 with the free-running timer mode, and used the output compare interrupt routine to calculate and write in a new compare value each half cycle of output. Then I thought it wouldn't work because the listed output compare pins were not made available on T4. However it seems there is some kind of IOMUXC_SW_MUX_CTL_PAD_* magic that I need to learn about. Thanks again!
 
With a few slight changes to TelephoneBill's code, I have a precision pulse generator with an adjustable period. I can tweak it with 'u' or 'd' serial commands (a manual-feedback PLL) to keep the rising edge within a microsecond of my GPSDO signal. Neat!

Code:
//TESTT4004 - GPT TEST PROGRAM for T4
//===================================
//Author: TelephoneBill
// (slight mods by JBeale)
//Date: 19 AUG 2019
//Version: 001

//NOTES: Using GPT2 as the timer. GPT2 Output Compare3 (1 KHz) is on pin 16. CompareValue = 0x000124F7 = (75,000 - 1)
//Compare1 is the counter reset mechanism. Compare2 is not used here. Compare3 is the toggle output on pin 16.

//definitions
uint32_t Save1, Save2;
byte Byte1;

volatile uint32_t ISRTicks = 0;
volatile uint32_t countTarget = 0;
volatile uint32_t timerTicks = 74999999-611;  // ideal: (75M-1) for 1PPS out with 150MHz clk

//SETUP
//=====
void setup() {

// 75M-153 => slow by 0.4 ppm
  
  //initialise general hardware
  Serial.begin(115200);             //setup serial port
  pinMode(13, OUTPUT);              //pin 13 as digital output
  FlashLED(4);                      //confidence boost
  Serial.println("Teensy 4 32-bit GPT timer test v1");             //setup serial port

  //set clock gating registers to inhibit GPT and PIT clocks (while changing to 150 MHz)
  Save1 = CCM_CCGR0;                          //save current state 0
  Save2 = CCM_CCGR1;                          //save current state 1
  CCM_CCGR0 &= 0xF0FFFFFF;                    //inhibit GPT2 (CG12,CG13)
  CCM_CCGR1 &= 0xFF0FCFFF;                    //inhibit GPT1 and PIT (CG6,CG10,CG11)
  
  //change the mux setting for IPG_CLK_ROOT (set bit6 = 0). This enables the 150 MHz.
  CCM_CSCMR1 &= 0xFFFFFFBF;
  
  //restore GPT and PIT gated clocks to former values
  CCM_CCGR0 = Save1;
  CCM_CCGR1 = Save2;
  
  //configure clocks for GPT2 module
  CCM_CCGR0 |= 0x0F000000;                    //enable clocks to GPT2 (CG12,CG13)


  //configure GPT2 for test
  GPT2_CR = 0;                                //clear the control register, FRR = 0 means restart after Compare
  GPT2_SR = 0x3F;                             //clear all prior status
  GPT2_PR = 0;                                //prescale register set divide by 1
  GPT2_CR |= GPT_CR_CLKSRC(1);                //clock selection #1 (peripheral clock = 150 MHz)
  GPT2_CR |= GPT_CR_ENMOD;                    //reset count to zero before enabling
  GPT2_CR |= 0x04800000;                      //Compare3 = toggle mode, Compare2 = toggle mode
  
  GPT2_OCR1 = 0xFFFFFFFF;                     //Compare1 value (default value, full 32-bit word)
  GPT2_OCR2 = timerTicks;                     //Compare2 value 
  GPT2_OCR3 = timerTicks;                     //Compare3 value 

  countTarget = timerTicks;                   // the first target value, when Ctr starts from 0
  
  GPT2_IR = 0x00000004;                       //enable interrupt for Compare flag  
  GPT2_CR |= GPT_CR_EN;                       //enable GPT1 counting at 150 MHz

  //configure Teensy pin 16 as Compare3 output
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_07 = 8;    // GPT2 Compare3 is now output on pin 16
  
  //enable GPT1 interrupt within NVIC table
  attachInterruptVector(IRQ_GPT2, GPT2_isr);  //declare which routine performs the ISR function
  NVIC_ENABLE_IRQ(IRQ_GPT2);
}


//ISR ROUTINE FOR GPT1
//====================
//FASTRUN puts this code into RAM to run twice as fast
FASTRUN void GPT2_isr(void) {
  GPT2_SR = 0x00000004;                       //reset the interrupt flags in status register
  // GPT1_CNT is counter register
  countTarget += timerTicks;         // new counter capture target is +1 half-cycle from last one
  GPT2_OCR2 = countTarget;           // set new Compare2 value 
  GPT2_OCR3 = countTarget;           // set new Compare3 value 
  
  ISRTicks++;
  if (ISRTicks>=2000) {
    ISRTicks = 0;  
    // digitalWriteFast(13, 1);  
  }
  if (ISRTicks==100) {
    // digitalWriteFast(13, 0);  
  }
  asm volatile("dsb");
}


//MAIN LOOP
//=========
void loop() {
  //call KeyInput() routine
  KeyInput();                         //no key currently used, but could be employed to change timer period dynamically
}

//SUBROUTINES
//===========
//Flash LED routine
void FlashLED(int m) {
  for (int n=0;n<m;n++) {
    digitalWriteFast(13, 1);          //set pin 13 high
    delay(100);
    digitalWriteFast(13, 0);          //set pin 13 low
    delay(100);
  }
}

void KeyInput() {
  //process any keystrokes available
  if (Serial.available()>0) {
    //read the incoming byte
    Byte1 = Serial.read();
    if (Byte1>0x20) {
      switch (Byte1) {
      case 'u':  // up : increase delay period
        timerTicks += 1;
        Serial.println(timerTicks);            
        break;
      case 'd':  // down : decrease delay period
        timerTicks -= 1;
        Serial.println(timerTicks);             
        break;
      }
    }
  }
}
 
Last edited:
The 24 MHz crystal is situated close to the main chip and this will probably affect the "ambient temp" of the crystal module, either by convection - or possibly by conduction (in addition to room ambient changes).

I wonder if another heat source could be used to help stabilise the module even further - a bit like an oven with an OCXO. The on-board LED is not that far away, and if either a small insulated housing could enclose the LED with both 24 MHz and 32 KHz modules, then pulses of the LED might control such a heat source. This would need some temperature measurement mechanism to be effective (although raising module ambient to be dominant would help). Maybe the ratio of the 32 KHz drift to the 24 MHz frequency might work as a temperature indicator? They will both have curves that are unlikely to be tracking perfectly, so one potential method. Another would be to use a DS18B20.

If not the onboard LED, then perhaps another external LED (flat) in direct contact with the 24 MHz module top, and driven from a digital output pin...

Another thought, is there any correlation between CPU internal temp monitoring and 24 MHz drift? (TEMPMON - Ch18, page 1273)

Just some ideas for future experiments.
 
Last edited:
The 24 MHz crystal is situated close to the main chip and this will probably affect the "ambient temp" of the crystal module, either by convection - or possibly by conduction (in addition to room ambient changes).

…..

Another thought, is there any correlation between CPU internal temp monitoring and 24 MHz drift? (TEMPMON - Ch18, page 1273)

Just some ideas for future experiments.

TEMPMON is active on startup. So in any sketch you are running you have access to the internal temp by simply doing:
Code:
tempmonGetTemp()
The function returns a float.
 
Yes, I used tempmonGetTemp() to produce this graph from which I estimated my T4's 24MHz osc tempco at -0.24 ppm/C near room temp. My board's tempco also rolls off to near zero when the ambient was raised from 23 C to 43 C.

It is very convenient that the T4 cpu has a built-in temperature sensor. Although it returns a float, the granularity is only slightly better than 1 degree C, and it measures the processor die itself rather than the 24MHz crystal, so I expect you can do better with an external sensor.
 
Yes, I used tempmonGetTemp() to produce this graph from which I estimated my T4's 24MHz osc tempco at -0.24 ppm/C near room temp. My board's tempco also rolls off to near zero when the ambient was raised from 23 C to 43 C.
FWIW, over a wider temperature range, a crystal's response to temperature change is a quadratic. See
https://forum.pjrc.com/threads/24628-Interesting-Temperature-Data
(I sometimes put my teensy in the freezer for 30 minutes and then measure frequency as the board warms to room temperature ... and then maybe a hair dryer. I just use the temperature probe off of my DMM)
 
Status
Not open for further replies.
Back
Top