Teensy4 QuadTimer as 64-bit counter

Status
Not open for further replies.

janbbeck

Member
I am cascading a QTimer into a 64-bit counter, and it works. But I am having a strange thing happen on startup, that I would like to understand.

I am posting the full code at the end, but the most important thing are the the CNTR initializations:
Code:
  TMRx->CH[0].CNTR = 0;                   // set count to 0
  TMRx->CH[1].CNTR = 0;                   // set count to 0
  TMRx->CH[2].CNTR = 0xffff;              // set count to 0xffff
  TMRx->CH[3].CNTR = 0;                   // set count to 0
Using there, here is what I get from the serial monitor:
Code:
0
0
65535
0

17897
49
0
0


so, at first the values are bad, then they turn fine and work as intended. I can also just add a 100ms delay after the timer setup and then the values are ok in the main program.

If I set all CNTR to 0, then the 3rd and 4th values start out at 0 as expected, but then change to 1, like this:

Code:
0
0
0
0

3397
49
1
1


I guess a carry over bit needs to be specified or something...
The 0xffff and wait a few ms workaround functions, but I do want to understand what is going on though, so does anyone here know?

Code:
#include <U8x8lib.h>

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

IMXRT_TMR_t * TMRx = (IMXRT_TMR_t *)&IMXRT_TMR4;

void setup()
{
  Serial.begin(9600);
  // initialize and clear display
  u8x8.begin();
  u8x8.setPowerSave(0);
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.drawString(0, 0, "Started !");
  
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON); // enable QTMR4
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1;        // QT4 Timer2 on pin 9

  TMRx->CH[0].CTRL =0;                    // stop
  TMRx->CH[1].CTRL =0;                    // stop
  TMRx->CH[2].CTRL =0;                    // stop
  TMRx->CH[3].CTRL =0;                    // stop
  TMRx->CH[0].CNTR = 0;                   // set count to 0
  TMRx->CH[1].CNTR = 0;                   // set count to 0
  TMRx->CH[2].CNTR = 0xffff;              // set count to 0xffff
  TMRx->CH[3].CNTR = 0;                   // set count to 0
  TMRx->CH[0].SCTRL = 0;                  // clear all overflow etc flags
  TMRx->CH[1].SCTRL = 0;                  // clear all overflow etc flags
  TMRx->CH[2].SCTRL = 0;                  // clear all overflow etc flags
  TMRx->CH[3].SCTRL = 0;                  // clear all overflow etc flags
  TMRx->CH[3].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMRx->CH[3].CTRL |= TMR_CTRL_PCS(6);    // Primary Count Source: CH[2] output  
  TMRx->CH[2].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMRx->CH[2].CTRL |= TMR_CTRL_PCS(5);    // Primary Count Source: CH[1] output  
  TMRx->CH[1].CTRL  = TMR_CTRL_CM (7);    // Count Mode:           Cascaded counter mode
  TMRx->CH[1].CTRL |= TMR_CTRL_PCS(4);    // Primary Count Source: CH[0] output
  TMRx->CH[0].CTRL  = TMR_CTRL_CM (1);    // Count Mode:           Count rising edges of primary source
  TMRx->CH[0].CTRL |= TMR_CTRL_PCS(2);    // Primary Count Source: Counter 2 input pin
  //delay(100);                           // settle timer???
}

char buffer[100];

void loop()
{
  sprintf(buffer, "%u", TMRx->CH[0].CNTR);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  u8x8.drawString(0, 2, "     ");
  u8x8.drawString(0, 2, buffer);
  Serial.println(buffer);
  sprintf(buffer, "%u", TMRx->CH[1].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  u8x8.drawString(0, 3, "     ");
  u8x8.drawString(0, 3, buffer);
  Serial.println(buffer);
  sprintf(buffer, "%u", TMRx->CH[2].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  u8x8.drawString(0, 4, "     ");
  u8x8.drawString(0, 4, buffer);
  Serial.println(buffer);
  sprintf(buffer, "%u", TMRx->CH[3].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  u8x8.drawString(0, 5, "     ");
  u8x8.drawString(0, 5, buffer);
  Serial.println(buffer);
  Serial.println();
  delay(1000);
}
 
After much more testing, I would like to add that no matter what I try I cannot get the 3rd 16 bit counter to overflow into the 4th one. So instead of a 64-bit counter, the maximum I can get to work is 48 bits. Is that an errata in the chip, or am I doing something wrong?
 
it looks like you are using pin 9 as "clock source" (quad timer 4 timer 2), what do you have connected to pin 9?
your last init should be start quadtimer 4 timer 2: TMRx->CH[2].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(2) | TMR_CTRL_LENGTH ;
Or maybe you are using timer 0 input pin ?? or do you need to set PCS() to use the IP bus clock?
some QTMR discussions in huge T4 beta test thread, e.g. https://forum.pjrc.com/threads/54711-Teensy-4-0-First-Beta-Test?p=211019&viewfull=1#post211019

and my pin 9 counting example (only 32 bits)
https://github.com/manitou48/teensy4/blob/master/qtmr_count.ino

chain example, 48 bits (32-bit counter of microseconds)
https://github.com/manitou48/teensy4/blob/master/qtmr_cascade.ino

EDIT: i extended chain example to 64-bits, and i'm seeing what you were seeing, highest timer goes to 1 ??
Code:
0 ticks 0 ms   20007 us 46552007
ch1 20013 ch2 0 ch3 0
0 ticks 0 ms   1020012 us 47552013
ch1 36979 ch2 15 ch3 1
0 ticks 0 ms   2020018 us 48552019
ch1 53945 ch2 30 ch3 1
 
Last edited:
it looks like you are using pin 9 as "clock source" (quad timer 4 timer 2), what do you have connected to pin 9?
your last init should be start quadtimer 4 timer 2: TMRx->CH[2].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(2) | TMR_CTRL_LENGTH ;
some QTMR discussions in huge T4 beta test thread, e.g. https://forum.pjrc.com/threads/54711-Teensy-4-0-First-Beta-Test?p=211019&viewfull=1#post211019

and my pin 9 counting example (only 32 bits)
https://github.com/manitou48/teensy4/blob/master/qtmr_count.ino

Thanks for the help

The clock source was an external 5 Mhz signal, but I have since gone over to doing what you did and just connected a pin(to test a faster clock):
Code:
  analogWriteFrequency(8, 75000000);  //  max 75mhz at 150 mhz bus speed
  analogWrite(8, 128);

Trying to understand what is going on here...
- why do I have to set up ch2 last?
- if I just want to roll over at 0xffff why set TMR_CTRL_LENGTH ?

So my thought is
- external signal goes into pin9 which goes to timer 2 input. Hence
TMR4->CH[0].CTRL = TMR_CTRL_CM (1); // Count Mode: Count rising edges of primary source
TMR4->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin

-then the other channels are just cascading. So timer2 input gets re-routed to timer0 and timer1 cascades into timer2 instead of using the original timer2 input

Is that wrong?

I just swapped timer 0 and 2, so that 2 gets the initial clock and then cascades into the others - same behavior.

I did look at the thread you suggested and also looked at the qtmr_count.ino qtmr_capture.ino and qtmr_cascade.ino and this: https://github.com/mjs513/FreqCount/tree/FreqCount-T4-Branch
as well as the full datasheet.

I can cascade 2 timers easily, and 3 with the 0xffff workaround, but not 4...
 
i changed my qtmr_cascade sketch to config 48-bit microsecond counter like the following
Code:
  int cnt;

  cnt = 150 ; // 1 us
  TMRx->CH[0].CTRL = 0; // stop
  TMRx->CH[0].CNTR = 0;
  TMRx->CH[0].LOAD = 0;  // start val after compare
  TMRx->CH[0].COMP1 = cnt - 1;  // count up to this val and start again
  TMRx->CH[0].CMPLD1 = cnt - 1  ;
  TMRx->CH[0].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8 ) | TMR_CTRL_LENGTH ;  // no prescale
  cnt = 65536;
  TMRx->CH[1].CTRL = 0; // stop
  TMRx->CH[1].LOAD = 0;  // start val after compare
  TMRx->CH[1].COMP1 = cnt - 1;
  TMRx->CH[1].CMPLD1 = cnt - 1;
  TMRx->CH[1].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(4) | TMR_CTRL_LENGTH ;  //clock from clock 0

  TMRx->CH[2].CTRL = 0; // stop
  TMRx->CH[2].LOAD = 0;  // start val after compare
  TMRx->CH[2].COMP1 = cnt -1  ;
  TMRx->CH[2].CMPLD1 = cnt - 1;
  TMRx->CH[2].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(5)  ;  //clock from clock 1

  TMRx->CH[3].CTRL = 0; // stop
  TMRx->CH[3].LOAD = 0;  // start val after compare
  TMRx->CH[3].COMP1 = cnt -1 ;
  TMRx->CH[3].CMPLD1 = cnt -1;
  TMRx->CH[3].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(6)  ;  //clock from clock 2

timer3 doesn't flip from 0 to 1 at start, so it seems to be working. channel 0 timer uses IP bus clock (150mhz) prescaled by (150-1) to count microseconds.

48-bit micros: uint64_t us48 = TMRx->CH[1].CNTR + 65536LL * TMRx->CH[2].HOLD + 65536LL * 65536LL * TMRx->CH[3].HOLD;
 
Last edited:
i changed my qtmr_cascade sketch to config 48-bit microsecond counter like the following
Code:
  int cnt;

  cnt = 150 ; // 1 us
  TMRx->CH[0].CTRL = 0; // stop
  TMRx->CH[0].CNTR = 0;
  TMRx->CH[0].LOAD = 0;  // start val after compare
  TMRx->CH[0].COMP1 = cnt - 1;  // count up to this val and start again
  TMRx->CH[0].CMPLD1 = cnt - 1  ;
  TMRx->CH[0].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8 ) | TMR_CTRL_LENGTH ;  // no prescale
  cnt = 65536;
  TMRx->CH[1].CTRL = 0; // stop
  TMRx->CH[1].LOAD = 0;  // start val after compare
  TMRx->CH[1].COMP1 = cnt - 1;
  TMRx->CH[1].CMPLD1 = cnt - 1;
  TMRx->CH[1].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(4) | TMR_CTRL_LENGTH ;  //clock from clock 0

  TMRx->CH[2].CTRL = 0; // stop
  TMRx->CH[2].LOAD = 0;  // start val after compare
  TMRx->CH[2].COMP1 = cnt -1  ;
  TMRx->CH[2].CMPLD1 = cnt - 1;
  TMRx->CH[2].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(5)  ;  //clock from clock 1

  TMRx->CH[3].CTRL = 0; // stop
  TMRx->CH[3].LOAD = 0;  // start val after compare
  TMRx->CH[3].COMP1 = cnt -1 ;
  TMRx->CH[3].CMPLD1 = cnt -1;
  TMRx->CH[3].CTRL = TMR_CTRL_CM(7) | TMR_CTRL_PCS(6)  ;  //clock from clock 2

timer3 doesn't flip from 0 to 1 at start, so it seems to be working. channel 0 timer uses IP bus clock (150mhz) prescaled by (150-1) to count microseconds

Thanks! With that I was able to track down the problem. Not setting the COMP value was my mistake. The datasheet clearly states that the OFLAG is the clock for the next cascaded unit, so it has to be set to 0xffff or will cause the next counter to count too early. I don't think CMPLD1 needs to be set if TMR_CTRL_LENGTH is not set. Would you agree with that assessment?
 
Ok, so I am back onto this project.
TMR4 works fine (due to help from here; thanks again), I cannot get the other TMR1-3 to run. This works:

Code:
IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;

void setup() {
  Serial.begin(115200);
  // set up QuadTimer4 
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON);           // enable QTMR4 clock
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1;                  // QuadTimer4 Counter 2 on pin D9
  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(2);    // Primary Count Source: Counter 2 input pin
  
  analogWriteFrequency(8, 75000000);  //  max 75mhz at 150 mhz bus speed
  analogWrite(8, 128);

}
char buffer[100];
void loop() {
  sprintf(buffer, "%u", TMR4->CH[0].CNTR);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR4->CH[1].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR4->CH[2].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR4->CH[3].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);

  Serial.println();
  
  delay(200);

}

And this does not:

Code:
IMXRT_TMR_t * TMR1 = (IMXRT_TMR_t *)&IMXRT_TMR1;

void setup() {
  Serial.begin(115200);
  // set up QuadTimer1 
  CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON);           // enable QTMR1 clock
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 1;                  // QuadTimer1 Counter 0 on pin D10 using ALT1
  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(0);    // Primary Count Source: Counter 0 input pin
  
  analogWriteFrequency(8, 75000000);  //  max 75mhz at 150 mhz bus speed
  analogWrite(8, 128);

}
char buffer[100];
void loop() {
  sprintf(buffer, "%u", TMR1->CH[0].CNTR);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR1->CH[1].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR1->CH[2].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);
  sprintf(buffer, "%u", TMR1->CH[3].HOLD);  // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels;
  Serial.println(buffer);

  Serial.println();
  
  delay(200);

}

By works, I mean an external signal on the configured pin counting the QuadTimer. I used pin 8 as a convenient clock source here, so I can just bridge pins for testing.

Any suggestions at all as to what I may be missing would be greatly appreciated. Thanks in advance.

Jan
 
The teensy core uses TMR1 CH0 for PWM, so it pre-configures some of the registers. You need to clear a couple more registers, try adding line in RED
Code:
...
  TMR1->CH[0].CTRL = 0;                   // stop
  [COLOR="#FF0000"]TMR1_SCTRL0 = TMR1_CSCTRL0 = 0;[/COLOR]
  TMR1->CH[1].CTRL = 0;                   // stop
...
or alternate syntax, TMR1->CH[0].SCTRL = TMR1->CH[0].CSCTRL = 0;
 
The teensy core uses TMR1 CH0 for PWM, so it pre-configures some of the registers. You need to clear a couple more registers, try adding line in RED
Code:
...
  TMR1->CH[0].CTRL = 0;                   // stop
  [COLOR="#FF0000"]TMR1_SCTRL0 = TMR1_CSCTRL0 = 0;[/COLOR]
  TMR1->CH[1].CTRL = 0;                   // stop
...
or alternate syntax, TMR1->CH[0].SCTRL = TMR1->CH[0].CSCTRL = 0;

Thank you for the help!
That worked!
 
So it -almost- works. I set up TMR1 and 4.There is count glitching going on. Feeding both timers with the same 10khz 3V external square wave (test signal from oscope) causes the counts to diverge slowly.
Then i added TMR3 to see if it kept the same count as 1 or 4 maybe, but now I have 3 counters with slowly diverging counts. The scope shows really clean signals on the pins.

Feeding the counter with a square wave made by the Teensy itself results in the same behavior.

Is there another configuration I forgot to set that causes this havoc? Thanks in advance!

Code:
IMXRT_TMR_t * TMR1 = (IMXRT_TMR_t *)&IMXRT_TMR1;
IMXRT_TMR_t * TMR3 = (IMXRT_TMR_t *)&IMXRT_TMR3;
IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;


void setup()
{
  Serial.begin(115200);
  
  // set up QuadTimer1 
  CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON);           // enable QTMR1 clock
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 1;                  // QuadTimer1 Counter 0 on pin D10 using ALT1
  //IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00|= 0b1000000000000000; // enable hysteresis in pin
  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].LOAD = 0;
  TMR1->CH[1].LOAD = 0;
  TMR1->CH[2].LOAD = 0;
  TMR1->CH[3].LOAD = 0;
  TMR1->CH[0].SCTRL = TMR1->CH[0].CSCTRL = 0; 
  TMR1->CH[1].SCTRL = TMR1->CH[1].CSCTRL = 0; 
  TMR1->CH[2].SCTRL = TMR1->CH[2].CSCTRL = 0; 
  TMR1->CH[3].SCTRL = TMR1->CH[3].CSCTRL = 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(0);    // Primary Count Source: Counter 0 input pin
    
  // set up QuadTimer3 -  D14
  CCM_CCGR6 |= CCM_CCGR6_QTIMER3(CCM_CCGR_ON);  // enable QTMR3 clock
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 1;      // Daisy Chain 1 - QT3 Counter 2 conects to pin D14 ALT1
  IOMUXC_QTIMER3_TIMER2_SELECT_INPUT  = 1 ;     // Daisy Chain 2 - QT3 Counter 2 conects to pin D14 ALT1
  //IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02|= 0b1000000000000000; // enable hysteresis in pin
  TMR3->CH[0].CTRL = 0;                   // stop
  TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; 
  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].LOAD = 0;
  TMR3->CH[1].LOAD = 0;
  TMR3->CH[2].LOAD = 0;
  TMR3->CH[3].LOAD = 0;
  TMR3->CH[0].SCTRL = TMR3->CH[0].CSCTRL = 0; 
  TMR3->CH[1].SCTRL = TMR3->CH[1].CSCTRL = 0; 
  TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; 
  TMR3->CH[3].SCTRL = TMR3->CH[3].CSCTRL = 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(2);    // Primary Count Source: Counter 2 input pin
  
  // set up QuadTimer4 - MEAS3 on D9
  CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON);           // enable QTMR4 clock
  IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1;                  // QuadTimerT4 Counter 2 on pin 9
  //IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_11|= 0b1000000000000000; // enable hysteresis in pin
  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].LOAD = 0;
  TMR4->CH[1].LOAD = 0;
  TMR4->CH[2].LOAD = 0;
  TMR4->CH[3].LOAD = 0;
  TMR4->CH[0].SCTRL = TMR4->CH[0].CSCTRL = 0; 
  TMR4->CH[1].SCTRL = TMR4->CH[1].CSCTRL = 0; 
  TMR4->CH[2].SCTRL = TMR4->CH[2].CSCTRL = 0; 
  TMR4->CH[3].SCTRL = TMR4->CH[3].CSCTRL = 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(2);    // Primary Count Source: Counter 2 input pin

}

char buffer[100];

void loop()
{
  uint16_t T1Counter = TMR1->CH[0].CNTR;
  uint16_t T3Counter = TMR3->CH[0].CNTR;
  uint16_t T4Counter = TMR4->CH[0].CNTR;
  
  Serial.println(T1Counter);
  Serial.println(T3Counter);
  Serial.println(T4Counter);
  Serial.println();
  
  delay(200);
}
 
I ran your 3 timer sketch, feeding pins with 10khz from analogWrite() on pin 8. The low order digits from the three counters seem to match
Code:
29433
29433
29433

31433
31433
31433

33433
33433
33433
...
29796
29796
29796

31796
31796
31796
The drift of the count clusters is from the imprecision of print's + delay(200).
 
Last edited:
Thank you for the help! Trying the same code this morning works to 99%. Maybe I latched some hardware buffer during experimentation yesterday and it recovered over night...

The last problem is that I sometimes see a 1 count difference between the timers, and that is clearly because they are read at different times:
Code:
  uint16_t T1Counter = TMR1->CH[3].CNTR;
  uint16_t T3Counter = TMR3->CH[3].CNTR;

Is there a way to read the 4 QuadTimers in such a way that all the 16 .HOLD values update at the same time?
 
For anyone else who come across this topic, here some additional information:
It seems that keeping the PWM code present, still causes the occasional count disparity.

Code:
  analogWriteFrequency(8, 10000000);  //  max 75mhz at 150 mhz bus speed
  analogWrite(8, 128);

I don't know why. But since I was just using that PWM for testing, and it does not occur when counting external pulses instead, I am moving on.

The closest I can approximate a simultaneous read of the 4 64-bit counters is this:
Code:
  //constant delay timer read
  asm volatile("ldr    r0 ,=0x401dc00a  \n\t" // load address of TMR1_CNTR0 into r0
               "ldr    r1 ,=0x401e000a  \n\t" // load address of TMR2_CNTR0 into r1
               "ldr    r2 ,=0x401e400a  \n\t" // load address of TMR3_CNTR0 into r2
               "ldr    r3 ,=0x401e800a  \n\t" // load address of TMR4_CNTR0 into r3
               "ldrh   r4 ,[r0],#0      \n\t" // hold TMR1 by reading TMR1_CNTR0
               "ldrh   r5 ,[r1],#0      \n\t" // hold TMR2 by reading TMR2_CNTR0
               "ldrh   r6 ,[r2],#0      \n\t" // hold TMR3 by reading TMR3_CNTR0
               "ldrh   r7 ,[r3],#0      \n\t" // hold TMR4 by reading TMR4_CNTR0
               :
               :
               : "r0","r1","r2","r3","r4","r5","r6","r7"
               );

This causes the 16 HOLD values to be set. Just execute this at a high-enough priority so it does not get interrupted.
 
Status
Not open for further replies.
Back
Top