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