Teensy 4.1: clock used by Quad timer ?

GregoryB

New member
Hello,


I am a newbie to this forum, and I am working on a project using a Teensy 4.1. I would like to output a square digital waveform at approximately 30 Hz and sample data at 10 kHz from an analog signal on another pin, with the goal of implementing a lock-in amplifier later on. I would like the digital waveform generation and the ADC sampling to share the same clock.

Therefore, I started implementing the digital waveform using the Quad Timer. My code is below, but there is something I do not understand. I read in the reference manual and in several forum posts that the Quad Timers use a 150 MHz clock. Based on this, I expected that setting PIN_TOGGLE_FREQ = 30 and using a prescaler of 128 would result in a pin_load value of (150 MHz / 128) / 30 − 1 = 39,061 (thus ok for a 16 bits counter) which should give a waveform period of (128 / 150 MHz) × 39,061 ≈ 0.03 s

Instead, I measure a period of roughly 0.095 s, corresponding to about 10.48 Hz (as measured with an oscilloscope on pin 6), which is three times lower than expected.

Is there a configuration error in my code, or is there something I am misunderstanding about how the Quad Timer works?

Thanks in advance for any advice or explanation.

Greg

Code:
#include <Arduino.h>
#include <imxrt.h>
#include <cstring>  // For strcpy, strtok, strcmp, strchr

#define QTMR4 (IMXRT_TMR4) 

/*
* Timer Configuration
*/
#define PIN_TOGGLE_FREQ   30u      // 10.48 Hz pin toggle ????

 
void setup(){ 

  // ============================================================
  // Enable clocks
  // ============================================================
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON);
  delayMicroseconds(10);  // Wait for clocks to stabilize

  // ============================================================
  // Pin mux
  // ============================================================
  // pin 6 on teensy 4,1, see manual for ALT1 = QTMR4_TIMER1
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_10 = 1; //page 518 at ALT1 = QTMR4_TIMER1
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_10 = 0x10B0; // page 395
 
  // ============================================================
  // QTMR4 setup (shared clock domain)
  // ============================================================
  uint32_t qtmr_clk = 150000000u;  // 150 MHz peripheral clock

  delayMicroseconds(10);

  // ----------------------------
  // QTMR4 Channel 1 -> Pin 10 Hz
  // ----------------------------
  QTMR4.CH[1].CTRL = 0;        // stop timer
  QTMR4.CH[1].CNTR = 0;

  // define toogle value for counter
  uint32_t pin_load = (qtmr_clk / (PIN_TOGGLE_FREQ)) - 1;

  QTMR4.CH[1].LOAD = pin_load;
  QTMR4.CH[1].COMP1 = int(pin_load/2);

  QTMR4.CH[1].CMPLD1 = pin_load/2;  // Same as COMP1
  QTMR4.CH[1].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD;


  QTMR4.CH[1].CTRL =
    TMR_CTRL_CM(1) |        // Count to compare mode
    TMR_CTRL_PCS(15) |       // Primary clock source: IPG clock (150 MHz) for QTIMER4 with 128 prescaler, see page 3000 of manual
    TMR_CTRL_LENGTH |       // Periodic mode
    TMR_CTRL_OUTMODE(3) |   // Toggle on compare + roll over
    (1 << 0);               // ENABLE: enable timer immediately

  // Enable output to pin 6 -  ?
  QTMR4.CH[1].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL;

}
 //end setup

 void loop(){
 }
 
Sorry, I can't dig into this quadtimer code right now.

But I can offer you known-good code that definitely does use quadtimer to create 30 Hz. If you want to dig into the quadtimer details, the implementation is in the core library pwm.c.

Code:
void setup() {
  analogWriteFrequency(10, 30);
  analogWrite(10, 128);
}

void loop() {
}

I checked with my scope that this definitely does give 30 Hz.

file.png
 
Hello,

Thanks, @jmarsh and @Paul. Dividing the timer load by the prescaler and updating the LOAD, COMP1, and CMPLD1 variables with the correct values (0, pin_load, and pin_load, since the counter is counting up) made pin 6 toggle at 30 Hz. See the updated code under:

Code:
#include <Arduino.h>
#include <imxrt.h>
#include <cstring>  // For strcpy, strtok, strcmp, strchr

#define QTMR4 (IMXRT_TMR4)
#define QTMR2 (IMXRT_TMR2)
#define QTMR1 (IMXRT_TMR1)

/*
* Timer Configuration
*/
#define PIN_TOGGLE_FREQ   30u     
#define PRESCAL 128u
 
void setup(){ 

  Serial.begin(9600);
  while (!Serial && millis() < 4000);
  // ============================================================
  // Enable clocks
  // ============================================================
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON);
  delayMicroseconds(10);  // Wait for clocks to stabilize

  // ============================================================
  // Pin mux
  // ============================================================
  // pin 6 on teensy 4,1, see manual for ALT1 = QTMR4_TIMER1
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_10 = 1; //page 518 at ALT1 = QTMR4_TIMER1
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_10 = 0x10B0; // page 395
 
  // ============================================================
  // QTMR4 setup (shared clock domain)
  // ============================================================
  uint32_t qtmr_clk = 150000000/128;  // Effective clock using 150 MHz peripheral clock corrected by  prescaler 128

  delayMicroseconds(10);

  // ----------------------------
  // QTMR4 Channel 1 -> Pin 6 at 30 Hz, with PIN_TOGGLE_FEQ = 30u and PRESCALER = 128
  // ----------------------------
  QTMR4.CH[1].CTRL = 0;        // stop timer
  QTMR4.CH[1].CNTR = 0;

  // define toggle value for counter
  uint32_t pin_load = (qtmr_clk / (2*PIN_TOGGLE_FREQ)) - 1;

  QTMR4.CH[1].LOAD = 0;
  QTMR4.CH[1].COMP1 = int(pin_load);

  QTMR4.CH[1].CMPLD1 = pin_load;  // Same as COMP1
  QTMR4.CH[1].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD;
  
  //Count to compare mode; Primary clock source: IPG clock (150 MHz) for QTIMER4 with 128 prescaler, see page 3000 of manual; Periodic mode; Toggle on compare + roll over
  QTMR4.CH[1].CTRL = 0b0011111000100011;
  // Enable output to pin 6
  QTMR4.CH[1].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL;
  //run counter
  QTMR4.CH[1].CTRL = QTMR4.CH[1].CTRL | TMR_CTRL_CM(1);

}
 //end setup

 void loop(){
 }

Now I’m wondering whether there’s a way to capture the state of pin 6 (or the timer) on the edge of the qtmr_clk clock without wiring it to another pin? The goal is to store N values of pin 6 periodically in a buffer, since I plan to use the waveform as my reference signal for the lock-in. .

@Paul, when using:
Code:
void setup() {
  analogWriteFrequency(10, 30);
  analogWrite(10, 128);
}

Is it possible to configure one of the ADCs to sample exactly on the edge of the clock used by analogWrite?

Greg
 
Back
Top