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

Thread: T4: jitter-free low frequency output pulse?

  1. #1
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676

    T4: jitter-free low frequency output pulse?

    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?

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,761
    There were some good posts along this line on the T4 Beta thread - perhaps @manitou / @mjs513

  3. #3
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676

    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.

  4. #4
    Senior Member
    Join Date
    Mar 2015
    Location
    UK
    Posts
    300
    @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.

  5. #5
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676
    @TelephoneBill - thank you very much, I see https://forum.pjrc.com/threads/54711...l=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 by JBeale; 08-19-2019 at 04:39 PM.

  6. #6
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,201
    Quote Originally Posted by JBeale View Post
    @TelephoneBill - thank you very much, I see https://forum.pjrc.com/threads/54711...l=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

  7. #7
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,535
    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.

  8. #8
    Senior Member
    Join Date
    Mar 2015
    Location
    UK
    Posts
    300
    Quote Originally Posted by JBeale View Post
    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;
          }
        }
      }
    }

  9. #9
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,201
    Quote Originally Posted by JBeale View Post
    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).

  10. #10
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676

    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!

  11. #11
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676
    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 by JBeale; 08-20-2019 at 01:16 AM.

  12. #12
    Senior Member
    Join Date
    Mar 2015
    Location
    UK
    Posts
    300
    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 by TelephoneBill; 08-20-2019 at 08:56 AM.

  13. #13
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    4,116
    Quote Originally Posted by TelephoneBill View Post
    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.

  14. #14
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    676
    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.

  15. #15
    Senior Member
    Join Date
    Aug 2016
    Posts
    131
    maybe you could glue or something a 1 wire temp sensor directly to the crystal?

  16. #16
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    2,201
    Quote Originally Posted by JBeale View Post
    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...mperature-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)

Posting Permissions

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