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

Thread: Teensy4 QuadTimer4 losing counts while 1-3 work fine

  1. #1
    Junior Member
    Join Date
    Jan 2018
    Posts
    9

    Teensy4 QuadTimer4 losing counts while 1-3 work fine

    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);
    }

  2. #2
    Junior Member
    Join Date
    Jan 2018
    Posts
    9
    Update: QTimer4 always loses 24000 counts exactly at a time. This is surely significant, but I am not sure how.

  3. #3
    Junior Member
    Join Date
    Jan 2018
    Posts
    9

    Update 2

    Quote Originally Posted by janbbeck View Post
    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;

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,115
    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....

  5. #5
    Junior Member
    Join Date
    Jan 2018
    Posts
    9
    Quote Originally Posted by luni View Post
    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....

  6. #6
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,585
    @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
    		p->CH[i].LOAD = 24000;   // low time  (65537 - x) - 
    		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.

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,115
    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.

  8. #8
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    7,615
    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);
    	quadtimer_init(&IMXRT_TMR1);
    	quadtimer_init(&IMXRT_TMR2);
    	quadtimer_init(&IMXRT_TMR3);
    }
    As none of the PWM pins are using QuadTimer4...

  9. #9
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,585
    @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;
    		}
    		quadtimerFrequency(qtimer, info->module & 0x03, frequency);
    	}
    }
    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?

  10. #10
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    7,615
    Yes, there are a couple of pins that can be controlled by a couple of different timers, as I show in the excel document:
    Click image for larger version. 

Name:	screenshot.jpg 
Views:	9 
Size:	243.5 KB 
ID:	20744

    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
    {1, M(2, 2), 1, 2}, // FlexPWM2_2_A 6 // B0_10
    {1, M(1, 3), 2, 6}, // FlexPWM1_3_B 7 // B1_01
    {1, M(1, 3), 1, 6}, // FlexPWM1_3_A 8 // B1_00
    {1, M(2, 2), 2, 2}, // FlexPWM2_2_B 9 // B0_11[
    {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...

  11. #11
    Junior Member
    Join Date
    Jan 2018
    Posts
    9
    Thank you all for those very insightful comments!
    Setting the LOAD parameter does in deed synchronize the timers for me.

Posting Permissions

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