Teensy4 QuadTimer4 losing counts while 1-3 work fine

Status
Not open for further replies.

janbbeck

Member
I have cascaded the 4 quadTimers into 4 64-bit counters for external clocks. I found that this works fine for timers 1-3 but 4 loses counts.

Symptoms and what I have tried:
- I feed all 4 timers the same signal and compare counts. Timer1-3 all work as expected, keeping he same count. Timer4 cannot keep up.
- Without changing the outcome, I have varied the external pins, as well as the internal routing for all timers, using both the assigned pins (similar to IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1) and the crossbar. So for example, I have variously fed QTimer4 with pin 9 (as most examples do), or with pin 4 using the crossbar. When using the crossbar, I have tried to feed the signal into each of the 4 inputs going into Qtimer4. I have varied the inputs to the other timers as well. All variations have the same outcome.
- When changing to an internal clock, it can count at 150 Mhz, no problem. It never loses count. This only happens with external signals.
- I have tried outputting a signal from the Teensy using analogWriteFrequency(8, 40000000); analogWrite(8, 128); - varying the frequency from 20,000 up to 40,000,000 with the same outcome.
- External function generator at up to 3,000,000 Hz ( because I thought the analogWriteFrequency might cause the conflict) - same outcome
- longer or shorter wires. Fewer wires. Using different pins - same outcome

I am attaching my code showing identical setup of all 4 timers; generating a clock on pin 8 and forwarding it to pins 1-4 which is where the timers are assigned.

I would appreciate any insight or even if anyone could just duplicate this (program into Teensy4.0 with pins 1-4 & 8 connected). I don't have a second Teensy to try this (I am thinking maybe I broke it)

Code:
#include <inttypes.h>   // needed for uint64_t printf
#include <U8x8lib.h>

U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE);

IMXRT_TMR_t * TMR1 = (IMXRT_TMR_t *)&IMXRT_TMR1;
IMXRT_TMR_t * TMR2 = (IMXRT_TMR_t *)&IMXRT_TMR2;
IMXRT_TMR_t * TMR3 = (IMXRT_TMR_t *)&IMXRT_TMR3;
IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;

void xbar_connect(unsigned int input, unsigned int output)
{
  // function to make setting the crossbar SEL fields easier; 2 of these SEL fields are fit into a 32 register; there are many of these fields....
  if (input >= 88) return;
  if (output >= 132) return;
  volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2);
  uint16_t val = *xbar;
  if (!(output & 1)) {
    val = (val & 0xFF00) | input;
  } else {
    val = (val & 0x00FF) | (input << 8);
  }
  *xbar = val;
}

void setup()
{
  Serial.begin(9600);
  // initialize and clear display
  u8x8.begin();
  u8x8.setPowerSave(0);
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);          // turn on clock to xbar


  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_02 = 1;            // IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_02 (pin1) to ALT1 mux port: XBAR1_INOUT16
  IOMUXC_XBAR1_IN16_SELECT_INPUT = 0;                 // connect GPIO_AD_B0_02_ALT1 (pin1) to XBAR1
  IOMUXC_GPR_GPR6 |= 0b10;                            // connect XBAR1_OUT87 to QTIMER1_TIMER1
  xbar_connect(16, XBARA1_OUT_QTIMER1_TIMER1);
  TMR1->CH[0].CTRL = 0;                   // stop
  TMR1->CH[1].CTRL = 0;                   // stop
  TMR1->CH[2].CTRL = 0;                   // stop
  TMR1->CH[3].CTRL = 0;                   // stop
  TMR1->CH[0].CNTR = 0;                   // set count to 0
  TMR1->CH[1].CNTR = 0;                   // set count to 0
  TMR1->CH[2].CNTR = 0;                   // set count to 0
  TMR1->CH[3].CNTR = 0;                   // set count to 0
  TMR1->CH[0].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR1->CH[1].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR1->CH[2].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR1->CH[3].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR1->CH[0].CMPLD1 =  0xffff;
  TMR1->CH[1].CMPLD1 =  0xffff;
  TMR1->CH[2].CMPLD1 =  0xffff;
  TMR1->CH[3].CMPLD1 =  0xffff;
  TMR1->CH[3].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR1->CH[3].CTRL |= TMR_CTRL_PCS(6);    // Primary Count Source: CH[2] output
  TMR1->CH[2].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR1->CH[2].CTRL |= TMR_CTRL_PCS(5);    // Primary Count Source: CH[1] output
  TMR1->CH[1].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR1->CH[1].CTRL |= TMR_CTRL_PCS(4);    // Primary Count Source: CH[0] output
  TMR1->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR1->CH[0].CTRL |= TMR_CTRL_PCS(1);    // Primary Count Source: Counter 1 input pin


  // set up QTimer2 to get signal from pin 2
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_04 = 3;              // IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_04 (pin2) to ALT3 mux port: XBAR1_INOUT06
  IOMUXC_XBAR1_IN06_SELECT_INPUT = 0 ;                // connect GPIO_EMC_04_ALT3 (pin2) to XBAR1
  IOMUXC_GPR_GPR6 |= 0b1000000;                       // connect XBAR1_OUT92 to QTIMER2_TIMER2
  xbar_connect(6, XBARA1_OUT_QTIMER2_TIMER2);

  TMR2->CH[0].CTRL = 0;                   // stop
  TMR2->CH[1].CTRL = 0;                   // stop
  TMR2->CH[2].CTRL = 0;                   // stop
  TMR2->CH[3].CTRL = 0;                   // stop
  TMR2->CH[0].CNTR = 0;                   // set count to 0
  TMR2->CH[1].CNTR = 0;                   // set count to 0
  TMR2->CH[2].CNTR = 0;                   // set count to 0
  TMR2->CH[3].CNTR = 0;                   // set count to 0
  TMR2->CH[0].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR2->CH[1].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR2->CH[2].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR2->CH[3].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR2->CH[0].CMPLD1 =  0xffff;
  TMR2->CH[1].CMPLD1 =  0xffff;
  TMR2->CH[2].CMPLD1 =  0xffff;
  TMR2->CH[3].CMPLD1 =  0xffff;
  TMR2->CH[3].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR2->CH[3].CTRL |= TMR_CTRL_PCS(6);    // Primary Count Source: CH[2] output
  TMR2->CH[2].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR2->CH[2].CTRL |= TMR_CTRL_PCS(5);    // Primary Count Source: CH[1] output
  TMR2->CH[1].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR2->CH[1].CTRL |= TMR_CTRL_PCS(4);    // Primary Count Source: CH[0] output
  TMR2->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR2->CH[0].CTRL |= TMR_CTRL_PCS(2);    // Primary Count Source: Counter 2 input pin



  // set up QTimer3 to get signal from pin 3
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_05 = 3;              // IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_05 (pin 3)  to ALT3 mux port: XBAR1_INOUT07
  IOMUXC_XBAR1_IN07_SELECT_INPUT = 0 ;                // connect (pin3) to XBAR1 GPIO_EMC_05 for Mode: ALT3
  IOMUXC_GPR_GPR6 |= 0b100000000;                     // connect XBAR1_OUT94 to QTIMER3_TIMER0
  xbar_connect(7, XBARA1_OUT_QTIMER3_TIMER0);

  TMR3->CH[0].CTRL = 0;                   // stop
  TMR3->CH[1].CTRL = 0;                   // stop
  TMR3->CH[2].CTRL = 0;                   // stop
  TMR3->CH[3].CTRL = 0;                   // stop
  TMR3->CH[0].CNTR = 0;                   // set count to 0
  TMR3->CH[1].CNTR = 0;                   // set count to 0
  TMR3->CH[2].CNTR = 0;                   // set count to 0
  TMR3->CH[3].CNTR = 0;                   // set count to 0
  TMR3->CH[0].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR3->CH[1].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR3->CH[2].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR3->CH[3].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR3->CH[0].CMPLD1 =  0xffff;
  TMR3->CH[1].CMPLD1 =  0xffff;
  TMR3->CH[2].CMPLD1 =  0xffff;
  TMR3->CH[3].CMPLD1 =  0xffff;
  TMR3->CH[3].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR3->CH[3].CTRL |= TMR_CTRL_PCS(6);    // Primary Count Source: CH[2] output
  TMR3->CH[2].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR3->CH[2].CTRL |= TMR_CTRL_PCS(5);    // Primary Count Source: CH[1] output
  TMR3->CH[1].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR3->CH[1].CTRL |= TMR_CTRL_PCS(4);    // Primary Count Source: CH[0] output
  TMR3->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR3->CH[0].CTRL |= TMR_CTRL_PCS(0);    // Primary Count Source: Counter 0 input pin


  //IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1;               // QT4 Timer2 on pin 9
  // set up QTimer4 to get signal from pin 4
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06 = 3;              // IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06 (pin 4) to ALT3 mux port: XBAR1_INOUT08
  IOMUXC_XBAR1_IN08_SELECT_INPUT = 0 ;                // connect (pin4) to GPIO_EMC_06 for Mode: ALT3
  IOMUXC_GPR_GPR6 |= 0b1000000000000000;              // connect XBAR1_OUT101 to QTIMER4_TIMER3
  xbar_connect(8, XBARA1_OUT_QTIMER4_TIMER3);

  TMR4->CH[0].CTRL = 0;                   // stop
  TMR4->CH[1].CTRL = 0;                   // stop
  TMR4->CH[2].CTRL = 0;                   // stop
  TMR4->CH[3].CTRL = 0;                   // stop
  TMR4->CH[0].CNTR = 0;                   // set count to 0
  TMR4->CH[1].CNTR = 0;                   // set count to 0
  TMR4->CH[2].CNTR = 0;                   // set count to 0
  TMR4->CH[3].CNTR = 0;                   // set count to 0
  TMR4->CH[0].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR4->CH[1].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR4->CH[2].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR4->CH[3].COMP1 =  0xffff;            // send count signal to next counter on overflow at 0xffff
  TMR4->CH[0].CMPLD1 =  0xffff;
  TMR4->CH[1].CMPLD1 =  0xffff;
  TMR4->CH[2].CMPLD1 =  0xffff;
  TMR4->CH[3].CMPLD1 =  0xffff;
  TMR4->CH[3].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR4->CH[3].CTRL |= TMR_CTRL_PCS(6);    // Primary Count Source: CH[2] output
  TMR4->CH[2].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR4->CH[2].CTRL |= TMR_CTRL_PCS(5);    // Primary Count Source: CH[1] output
  TMR4->CH[1].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMR4->CH[1].CTRL |= TMR_CTRL_PCS(4);    // Primary Count Source: CH[0] output
  TMR4->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR4->CH[0].CTRL |= TMR_CTRL_PCS(3);    // Primary Count Source: Counter 3 input pin

  CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON); // enable QTMR1
  CCM_CCGR6 |= CCM_CCGR6_QTIMER2(CCM_CCGR_ON); // enable QTMR2
  CCM_CCGR6 |= CCM_CCGR6_QTIMER3(CCM_CCGR_ON); // enable QTMR3
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON); // enable QTMR4

  analogWriteFrequency(8, 20000);  //  max 75mhz at 150 mhz bus speed
  analogWrite(8, 128);
}

char buffer[100];

uint64_t counter_MEAS = 0;
uint64_t counter_X    = 0;
uint64_t counter_Y    = 0;
uint64_t counter_Z    = 0;

static uint64_t prev_counter_Z    = 0;


void loop()
{
  volatile uint16_t counter_MEAS_top = TMR1->CH[3].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_X_top    = TMR2->CH[3].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_Y_top    = TMR3->CH[3].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_Z_top    = TMR4->CH[3].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  counter_MEAS =  counter_MEAS_top;                          // 64-bit read of QTimer1
  counter_MEAS =  counter_MEAS * 65536  + TMR1->CH[2].HOLD;
  counter_MEAS =  counter_MEAS * 65536  + TMR1->CH[1].HOLD;
  counter_MEAS =  counter_MEAS * 65536  + TMR1->CH[0].HOLD;
  counter_X    =  counter_X_top;                             // 64-bit read of QTimer2
  counter_X    =  counter_X    * 65536  + TMR2->CH[2].HOLD;
  counter_X    =  counter_X    * 65536  + TMR2->CH[1].HOLD;
  counter_X    =  counter_X    * 65536  + TMR2->CH[0].HOLD;
  counter_Y    =  counter_Y_top;                             // 64-bit read of QTimer3
  counter_Y    =  counter_Y    * 65536  + TMR3->CH[2].HOLD;
  counter_Y    =  counter_Y    * 65536  + TMR3->CH[1].HOLD;
  counter_Y    =  counter_Y    * 65536  + TMR3->CH[0].HOLD;
  counter_Z    =  counter_Z_top;                             // 64-bit read of QTimer4
  counter_Z    =  counter_Z    * 65536  + TMR4->CH[2].HOLD;
  counter_Z    =  counter_Z    * 65536  + TMR4->CH[1].HOLD;
  counter_Z    =  counter_Z    * 65536  + TMR4->CH[0].HOLD;

  sprintf(buffer, "M %" PRIu64 "\n", counter_MEAS );
  u8x8.drawString(0, 4, "     ");
  u8x8.drawString(0, 4, buffer);
  Serial.println(buffer);
  sprintf(buffer, "X %" PRIu64 "\n", counter_X );
  u8x8.drawString(0, 5, "     ");
  u8x8.drawString(0, 5, buffer);
  Serial.println(buffer);
  sprintf(buffer, "Y %" PRIu64 "\n", counter_Y );
  u8x8.drawString(0, 6, "     ");
  u8x8.drawString(0, 6, buffer);
  Serial.println(buffer);
  sprintf(buffer, "Z %" PRIu64 "\n", counter_Z );
  u8x8.drawString(0, 7, "     ");
  u8x8.drawString(0, 7, buffer);
  Serial.println(buffer);
  Serial.println();
  delay(1000);
}
 
Update: QTimer4 always loses 24000 counts exactly at a time. This is surely significant, but I am not sure how.
 
Update 2

Update: QTimer4 always loses 24000 counts exactly at a time. This is surely significant, but I am not sure how.

I found that this is not related in any way to the cascading of the timers. If I only use the lower 16 bits and do the upper 48 bits in software, I get the same results. I.e Qtimers 1-3 have identical counts, while qtimer4 lags, occasionally losing 24000 counts at a time:

Code:
volatile uint16_t counter_REF_top  = TMR1->CH[0].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_X_top    = TMR2->CH[0].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_Y_top    = TMR3->CH[0].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  volatile uint16_t counter_Z_top    = TMR4->CH[0].CNTR;     // read all 4 timers, setting the HOLD values; preserve memory access, don't optimize
  if (counter_REF_top < previous_counter_REF_top)            // counter overflowed
  {
    counter_REF_rollover++;
  }
  previous_counter_REF_top = counter_REF_top;
  counter_REF = counter_REF_rollover * 65536 + counter_REF_top;
  if (counter_X_top < previous_counter_X_top)            // counter overflowed
  {
    counter_X_rollover++;
  }
  previous_counter_X_top = counter_X_top;
  counter_X = counter_X_rollover * 65536 + counter_X_top;
  if (counter_Y_top < previous_counter_Y_top)            // counter overflowed
  {
    counter_Y_rollover++;
  }
  previous_counter_Y_top = counter_Y_top;
  counter_Y = counter_Y_rollover * 65536 + counter_Y_top;
  if (counter_Z_top < previous_counter_Z_top)            // counter overflowed
  {
    counter_Z_rollover++;
  }
  previous_counter_Z_top = counter_Z_top;
  counter_Z = counter_Z_rollover * 65536 + counter_Z_top;

in fact, it is not any of the timers inside of Qtimer4 either, as I get exactly the same result, regardless of which 16bit timer inside of Qtimer4 I use:

Code:
  TMR4->CH[3].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR4->CH[3].CTRL |= TMR_CTRL_PCS(3);    // Primary Count Source: Counter 3 input pin
  TMR4->CH[2].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR4->CH[2].CTRL |= TMR_CTRL_PCS(3);    // Primary Count Source: Counter 3 input pin
  TMR4->CH[1].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR4->CH[1].CTRL |= TMR_CTRL_PCS(3);    // Primary Count Source: Counter 3 input pin
  TMR4->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMR4->CH[0].CTRL |= TMR_CTRL_PCS(3);    // Primary Count Source: Counter 3 input pin
and then just change
Code:
volatile uint16_t counter_Z_top    = TMR4->CH[0].CNTR;
like
Code:
volatile uint16_t counter_Z_top    = TMR4->CH[2].CNTR;
 
Just a guess: Teensyduino uses TMR1 - TMR3 for PWM generation. AFAIK It doesn't touch TMR4. So, it might be that TMR1-TMR3 get some settings during startup which you don't do for TMR4. Again, just a wild guess....
 
Just a guess: Teensyduino uses TMR1 - TMR3 for PWM generation. AFAIK It doesn't touch TMR4. So, it might be that TMR1-TMR3 get some settings during startup which you don't do for TMR4. Again, just a wild guess....

Thank you for taking the time. That is an interesting thought....
 
@janbbeck

In the Teensy 4 core you will a file called startup.c, in there you will see that it calls pwm_init (which is in pwm.c) when the T4.x starts up. To save some time here is what pwm_init and quadtimer_init :
Code:
void quadtimer_init(IMXRT_TMR_t *p)
{
	int i;

	for (i=0; i < 4; i++) {
		p->CH[i].CTRL = 0; // stop timer
		p->CH[i].CNTR = 0;
		p->CH[i].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL | TMR_SCTRL_FORCE;
		p->CH[i].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD;
		// COMP must be less than LOAD - otherwise output is always low
		[COLOR="#FF0000"][B]p->CH[i].LOAD = 24000;   // low time  (65537 - x) - [/B][/COLOR]
		p->CH[i].COMP1 = 0;  // high time (0 = always low, max = LOAD-1)
		p->CH[i].CMPLD1 = 0;
		p->CH[i].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) |
			TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6);
	}
}

void pwm_init(void)
{
	//printf("pwm init\n");
	CCM_CCGR4 |= CCM_CCGR4_PWM1(CCM_CCGR_ON) | CCM_CCGR4_PWM2(CCM_CCGR_ON) |
		CCM_CCGR4_PWM3(CCM_CCGR_ON) | CCM_CCGR4_PWM4(CCM_CCGR_ON);
	CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON) | CCM_CCGR6_QTIMER2(CCM_CCGR_ON) |
		CCM_CCGR6_QTIMER3(CCM_CCGR_ON) | CCM_CCGR6_QTIMER4(CCM_CCGR_ON);
	flexpwm_init(&IMXRT_FLEXPWM1);
	flexpwm_init(&IMXRT_FLEXPWM2);
	flexpwm_init(&IMXRT_FLEXPWM3);
	flexpwm_init(&IMXRT_FLEXPWM4);
	quadtimer_init(&IMXRT_TMR1);
	quadtimer_init(&IMXRT_TMR2);
	quadtimer_init(&IMXRT_TMR3);
}
Looks like it loads 24000 by default.
 
Looks like it loads 24000 by default.
Ah, that probably means that TMR4 is correct and TMR1-TMR3 do the wrong thing. I.e. preloading the counter with 24000 instead of the probably intended 0.
 
Yes PWM does get init from startup.c and it does init the first 3 timers but not the fourth.

Code:
void pwm_init(void)
{
	//printf("pwm init\n");
	CCM_CCGR4 |= CCM_CCGR4_PWM1(CCM_CCGR_ON) | CCM_CCGR4_PWM2(CCM_CCGR_ON) |
		CCM_CCGR4_PWM3(CCM_CCGR_ON) | CCM_CCGR4_PWM4(CCM_CCGR_ON);
	CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON) | CCM_CCGR6_QTIMER2(CCM_CCGR_ON) |
		CCM_CCGR6_QTIMER3(CCM_CCGR_ON) | CCM_CCGR6_QTIMER4(CCM_CCGR_ON);
	flexpwm_init(&IMXRT_FLEXPWM1);
	flexpwm_init(&IMXRT_FLEXPWM2);
	flexpwm_init(&IMXRT_FLEXPWM3);
	flexpwm_init(&IMXRT_FLEXPWM4);
	[COLOR="#FF0000"]quadtimer_init(&IMXRT_TMR1);
	quadtimer_init(&IMXRT_TMR2);
	quadtimer_init(&IMXRT_TMR3);[/COLOR]
}
As none of the PWM pins are using QuadTimer4...
 
@KurtE
Very true. The only other place that I see the use of QuadTimers is when you do AnalogWriteFrequency:
Code:
void analogWriteFrequency(uint8_t pin, float frequency)
{
	const struct pwm_pin_info_struct *info;

	if (pin >= CORE_NUM_DIGITAL) return;
	//printf("analogWriteFrequency, pin %d, freq %d\n", pin, (int)frequency);
	info = pwm_pin_info + pin;
	if (info->type == 1) {
		// FlexPWM pin
		IMXRT_FLEXPWM_t *flexpwm;
		switch ((info->module >> 4) & 3) {
		  case 0: flexpwm = &IMXRT_FLEXPWM1; break;
		  case 1: flexpwm = &IMXRT_FLEXPWM2; break;
		  case 2: flexpwm = &IMXRT_FLEXPWM3; break;
		  default: flexpwm = &IMXRT_FLEXPWM4;
		}
		flexpwmFrequency(flexpwm, info->module & 0x03, info->channel, frequency);
	} else if (info->type == 2) {
		// QuadTimer pin
		IMXRT_TMR_t *qtimer;
		switch ((info->module >> 4) & 3) {
		  case 0: qtimer = &IMXRT_TMR1; break;
		  case 1: qtimer = &IMXRT_TMR2; break;
		  case 2: qtimer = &IMXRT_TMR3; break;
		  default: qtimer = &IMXRT_TMR4;
		}
		[COLOR="#FF0000"]quadtimerFrequency(qtimer, info->module & 0x03, frequency);[/COLOR]
	}
}
But i think Pin 8 that he is using to do an AnalogWriteFrequency is FLEXPWM1 (using your spreadsheet. The only thing i am not sure of is that QT4-1 and FLEXPWM4 are shown on the same pin. Could there be a conflict? Unless somewhere else TMR4 is being used?
 
Yes, there are a couple of pins that can be controlled by a couple of different timers, as I show in the excel document:
screenshot.jpg

In those couple of cases the flexPWM timer is used. That is we use the upper part as you see in this extract from PWM:
Code:
const struct pwm_pin_info_struct pwm_pin_info[] = {
	{1, M(1, 1), 0, 4},  // FlexPWM1_1_X   0  // AD_B0_03
	{1, M(1, 0), 0, 4},  // FlexPWM1_0_X   1  // AD_B0_02
	{1, M(4, 2), 1, 1},  // FlexPWM4_2_A   2  // EMC_04
	{1, M(4, 2), 2, 1},  // FlexPWM4_2_B   3  // EMC_05
	{1, M(2, 0), 1, 1},  // FlexPWM2_0_A   4  // EMC_06
	{1, M(2, 1), 1, 1},  // FlexPWM2_1_A   5  // EMC_08
[COLOR="#FF0000"]	{1, M(2, 2), 1, 2},  // FlexPWM2_2_A   6  // B0_10[/COLOR]
	{1, M(1, 3), 2, 6},  // FlexPWM1_3_B   7  // B1_01
	{1, M(1, 3), 1, 6},  // FlexPWM1_3_A   8  // B1_00
[COLOR="#FF0000"]	{1, M(2, 2), 2, 2},  // FlexPWM2_2_B   9  // B0_11[[/COLOR]
	{2, M(1, 0), 0, 1},  // QuadTimer1_0  10  // B0_00
	{2, M(1, 2), 0, 1},  // QuadTimer1_2  11  // B0_02
	{2, M(1, 1), 0, 1},  // QuadTimer1_1  12  // B0_01
/CODE]
As you can see both pins 6 and 9 show up as type 1... 

@Paul - Maybe we should update teensy4\pwm.c comments to show that 
in addition to OctoWS2811 and AudioInputAnlog that can both use QuadTimer4, so does the ADC library maybe use the QTimer4
That is for logical ADC1 we use 4.3 and for ADC0 4.0...
 
Thank you all for those very insightful comments!
Setting the LOAD parameter does in deed synchronize the timers for me.
 
Status
Not open for further replies.
Back
Top