I have this ingenious code from MartinL that counts clocks on pin PA20. That even goes up to 20MHz. I have now read in the manual on page 939 (36.5 Maximum Clock Frequencies) that the timers TCC0, TCC1, TCC2 and TC3 even work up to 96 MHz input clock frequency. Do I understand this in the right manner? Can I do this with a code change?
////////////////////////////////////////////////////////////////////////////////////////
// Event system peripheral
////////////////////////////////////////////////////////////////////////////////////////
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
////////////////////////////////////////////////////////////////////////////////////////
// Generic Clock Initialisation
////////////////////////////////////////////////////////////////////////////////////////
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Select clock divisor to 1
GCLK_GENDIV_ID(4); // Select GLCK4
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_XOSC32K | // Select GCLK source as external 32.768kHz crystal (XOSC32K)
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable generic clock
GCLK_CLKCTRL_GEN_GCLK0 | // GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TC4_TC5; // As a clock source for TC4 and TC5
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable generic clock
GCLK_CLKCTRL_GEN_GCLK4 | // GCLK4 at 32.768kHz
GCLK_CLKCTRL_ID_TCC2_TC3; // As a clock source for TCC2 and TC3
////////////////////////////////////////////////////////////////////////////////////////
// TC4 Initialisation - measurement counter: counts the number incoming of pulses
////////////////////////////////////////////////////////////////////////////////////////
//---D6 (PA20)--------------------------------
PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1; // Enable the port multiplexer on port pin PA20 (D6)
PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_A; // Set-up PA20 (D6) as an EIC (interrupt)
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4; // Enable event output on external interrupt 4
EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4; // Clear the interrupt flag on channel 4
EIC->CTRL.bit.ENABLE = 1; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4 event
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | // Set event generator (sender) as external interrupt 4
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
//-------------------------------------
TC4->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI | // Enable TC4 event input
TC_EVCTRL_EVACT_COUNT; // Increment the TC4 counter upon receiving an event
TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32; // Chain TC4 with TC5 to create a 32-bit timer
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
Once DPLL has achieved lock, it's possible to route the 96MHz clock on to one of the spare generic clocks, to act as a clock source for one of the faster timers. Note that according to the datasheet (same maximum clock frequency table), the generic clock should remain undivided at this higher speed.
At the moment I'm a little perplexed. Without PLL with 48 MHz, the chip can only count 20 MHz without problems. Unfortunately, I don't have a suitable function generator to test it out precisely. I only have one 30 MHz crystal module left. However, the chip can no longer count the 30 MHz correctly. Could it be that there is a limit with the SAMD21? According to the data sheet, the PA20 input should be able to reach 48 MHz.
At these high frequencies the capture timer's resolution isn't high enough to measure the incoming pulses with any precision, it can just mearly detect the presence or absence of the signal within certain frequency bounds.
Also, the SAMD21 is only able to take a snapshot measurement now 'n' again, since servicing the timer's interrupt service routine, to capture pulse width and period, takes considerably longer than the incoming pulse's period.
I ran some tests generating a 20MHz and 30MHz signals using my SAMD51 Metro M4 board and found that it's possible to measure the presence of a 30MHz input signal using the SAMD21's capture timer with the 96MHz DPLL, but only if the ISR is gated by turning it off once the capture is complete then turning it on only when the results have been sent to the console:
Here's the code that I used:
// Setup TCC0 to capture pulse-width and period on D6 (PA20)
volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_t pulsewidth;
void setup()
{
SerialUSB.begin(115200); // Configure the native USB port
while (!SerialUSB); // Wait for the console to be ready
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable the generic clock
GCLK_CLKCTRL_GEN_GCLK1 | // Select GCLK1 using either external XOSC32K or internal OSC32K oscillator depending on the board
//GCLK_CLKCTRL_GEN_GCLK2 | // Select GCLK2 using the OSCULP32K ultra low power 32k oscillator
GCLK_CLKCTRL_ID_FDPLL; // Connect GCLK1 to GCLK_DPLL input
SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_REFCLK_GCLK; // Select GCLK_DPLL as the clock source
SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(11) | // Generate a 96MHz DPLL clock source from the external 32kHz crystal
SYSCTRL_DPLLRATIO_LDR(2928); // Frequency = 32.768kHz * (2928 + 1 + 11/16) = 96MHz
SYSCTRL->DPLLCTRLA.reg = SYSCTRL_DPLLCTRLA_ENABLE; // Enable the Digital Phase Locked Loop (DPLL)
while (!SYSCTRL->DPLLSTATUS.bit.LOCK); // Wait for the DPLL to achieve lock
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Divide the 96MHz DPLL clock source by 1 = 96MHz
GCLK_GENDIV_ID(4); // Set division on Generic Clock Generator (GCLK) 4
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 4
//GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_SRC_FDPLL | // Set the clock source to FDPLL96M at 96MHz
GCLK_GENCTRL_ID(4); // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Route GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 |
GCLK_CLKCTRL_ID_TCC0_TCC1;
// Enable the port multiplexer on digital pin D6
PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D6
PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXO_A;
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4; // Enable event output on external interrupt 4
EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4; // Clear the interrupt flag on channel 4
EIC->CTRL.reg |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1); // Set the event user (receiver) as timer TCC0, event 1
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | // Set event generator (sender) as external interrupt 4
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
TCC0->EVCTRL.reg |= TCC_EVCTRL_MCEI1 | // Enable the match or capture channel 1 event input
TCC_EVCTRL_MCEI0 | //.Enable the match or capture channel 0 event input
TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
/*TCC_EVCTRL_TCINV1 |*/ // Invert the event 1 input
TCC_EVCTRL_EVACT1_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
NVIC_SetPriority(TCC0_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn); // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
TCC0->INTENSET.reg = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
TCC0->CTRLA.reg |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
// TCC_CTRLA_PRESCSYNC_PRESC | // Reload timer on the next prescaler clock
TCC_CTRLA_PRESCALER_DIV1; // Set prescaler to 1 96MHz/1 = 96MHz
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
if (periodComplete) // Check if the period is complete
{
noInterrupts(); // Read the new period and pulse-width
period = isrPeriod;
pulsewidth = isrPulsewidth;
interrupts();
SerialUSB.print(F("PW: ")); // Output the results
SerialUSB.print(pulsewidth);
SerialUSB.print(F(" "));
SerialUSB.print(F("P:" ));
SerialUSB.println(period);
periodComplete = false; // Start a new period
TCC0->INTENSET.reg = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
}
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for match counter 0 (MC0) interrupt
if (TCC0->INTFLAG.bit.MC0)
{
isrPeriod = TCC0->CC[0].reg; // Copy the period
periodComplete = true; // Indicate that the period is complete
TCC0->INTENCLR.reg = TCC_INTENCLR_MC1 | // Disable compare channel 1 (CC1) interrupts
TCC_INTENCLR_MC0; // Disable compare channel 0 (CC0) interrupts
}
// Check for match counter 1 (MC1) interrupt
if (TCC0->INTFLAG.bit.MC1)
{
isrPulsewidth = TCC0->CC[1].reg; // Copy the pulse-width
}
}
Hello Martin!
Your help is very welcome! I do not need any pulse widths etc. My application is very simple. I just want to count without end and read out the ticks, like in the example code. And if possible as high as possible up to 96 MHz. That should work with the fast timers TCC0, TCC1, TCC2 and TC3 and the the FDPLL. Unfortunately, I know very little about it. Maybe I have to switch to the SAMD51, but I thought the SAMD21 can do that with its 96MHz timers.
// Setup TC4 in 32-bit mode to count incoming pulses on digital pin D6 using the Event System
// NB: Timers TCC0, TCC1, TCC2 and TC3 work up to 96 MHz
void setup()
{
SerialUSB.begin(115200); // Initialise the native serial port
while(!SerialUSB); // Wait for the console to open
//-------------------------------------------
// Generic Clock
//-------------------------------------------
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable generic clock
GCLK_CLKCTRL_GEN_GCLK0 | // GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TC4_TC5; // As a clock source for TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
//-------------------------------------------
// Port
//-------------------------------------------
PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1; // Enable the port multiplexer on port pin PA20 (D6)
PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_A; // Set-up PA20 (D6) as an EIC (interrupt)
//-------------------------------------------
// External Interrupt Controller (EIC)
//-------------------------------------------
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4; // Enable event output on external interrupt 4
EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4; // Clear the interrupt flag on channel 4
EIC->CTRL.bit.ENABLE = 1; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
//-------------------------------------------
// Event System
//-------------------------------------------
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4 event
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | // Set event generator (sender) as external interrupt 4
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
//-------------------------------------------
// Counter Timer 32bit TC4+5
//-------------------------------------------
TC4->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI | // Enable TC4 event input
TC_EVCTRL_EVACT_COUNT; // Increment the TC4 counter upon receiving an event
TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32; // Chain TC4 with TC5 to create a 32-bit timer
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
}
//-------------------------------------------
// Loop
//-------------------------------------------
void loop()
{
volatile uint32_t count;
count = TC4->COUNT32.COUNT.reg; // Read the COUNT register
SerialUSB.println(count); // Print the result
delay(1000);
}
@rsardu I'm not sure I understand when you mention "without end", seeing as the timer is finite and will overflow at some point. How would you collect any meaningful information once the timer has overflowed? Or do you propose to reset it each time a count reading is taken?
Also, the period of the incoming pulses incredibly short, which raises other issues with regard to retrieving the count results. Is the counting timer to be stopped, in order to retrieve the results, or alternatively allowed to free-run?
If the counting timer is allowed to free-run, pulses at 30MHz will still be being counted after the 100s timeframe has elapsed. This will occur while CPU is preparing to read the COUNT register.
The counter runs free. At some point the counter overflows and starts over. But that doesn't matter, because I calculate the difference (the new count value) with div and mod operations and the previous last counter reading. The counter register is read periodically (e.g. 100 secs) by a hardware pin interrupt.
I managed to get the SAMD21 board to count pulses at 30MHz from my SAMD51 Metro M4. It did require the TCC timers to be clocked at 96MHz, which suggests that they're not just counting asynchronously from the input signal.
I chained the 24-bit TCC0 and TCC1 timers together to count the 30MHz pulses over 100s. That's around 3 billion pulses! TC4 provides the 100 second timeframe, calling an ISR when it's time to read the result. I let the timers free-run.
Here's the code with the input on PA20 (D6):
// Capture the number of pulses on D6 (PA20) over a 100 second timeframe
volatile boolean timeframeComplete;
void setup()
{
SerialUSB.begin(115200); // Configure the native USB port
while (!SerialUSB); // Wait for the console to be ready
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable the generic clock
GCLK_CLKCTRL_GEN_GCLK1 | // Select GCLK1 using either external XOSC32K or internal OSC32K oscillator depending on the board
//GCLK_CLKCTRL_GEN_GCLK2 | // Select GCLK2 using the OSCULP32K ultra low power 32k oscillator
GCLK_CLKCTRL_ID_FDPLL; // Connect GCLK1 to GCLK_DPLL input
SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_REFCLK_GCLK; // Select GCLK_DPLL as the clock source
SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(11) | // Generate a 96MHz DPLL clock source from the external 32kHz crystal
SYSCTRL_DPLLRATIO_LDR(2928); // Frequency = 32.768kHz * (2928 + 1 + 11/16) = 96MHz
SYSCTRL->DPLLCTRLA.reg = SYSCTRL_DPLLCTRLA_ENABLE; // Enable the Digital Phase Locked Loop (DPLL)
while (!SYSCTRL->DPLLSTATUS.bit.LOCK); // Wait for the DPLL to achieve lock
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Divide the 96MHz DPLL clock source by 1 = 96MHz
GCLK_GENDIV_ID(4); // Set division on Generic Clock Generator (GCLK) 4
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 4
//GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_SRC_FDPLL | // Set the clock source to FDPLL96M at 96MHz
GCLK_GENCTRL_ID(4); // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Route GCLK4 at 96MHz to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 |
GCLK_CLKCTRL_ID_TCC0_TCC1;
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Route GCLK0 at 48MHz to TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID_TC4_TC5;
// Enable the port multiplexer on digital pin D6
PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D6
PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_A;
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4; // Enable event output on external interrupt 4
EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4; // Clear the interrupt flag on channel 4
EIC->CTRL.reg |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0); // Set the event user (receiver) as timer TCC0, event 0
EVSYS->USER.reg = EVSYS_USER_CHANNEL(2) | // Attach the event user (receiver) to channel 2 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC1_EV_0); // Set the event user (receiver) as timer TCC1, event 0
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | // Set event generator (sender) as external interrupt 4
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC0_OVF) | // Set event generator (sender) as TCC0 overflow
EVSYS_CHANNEL_CHANNEL(1); // Attach the generator (sender) to channel 2
TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
TCC_EVCTRL_OVFEO | // Enable overflow event output
TCC_EVCTRL_EVACT0_COUNTEV; // Increment the count upon receiving an event on channel 0
TCC1->EVCTRL.reg = TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
TCC_EVCTRL_EVACT0_COUNTEV; // Increment the count upon receiving an event on channel 0
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
TC4->COUNT32.INTENSET.reg = TC_INTENSET_OVF; // Enable timer TC4 overflow interrupts
TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC | // Set TC4 to reset timer on next prescaler clock
TC_CTRLA_PRESCALER_DIV2 | // Set the TC4 prescaler to divide by 2
TC_CTRLA_WAVEGEN_MPWM | // Set the timer to Match PWM Mode (MPWM)
TC_CTRLA_MODE_COUNT32; // Chain TC4 with TC5 to create a 32-bit timer
TC4->COUNT32.CC[0].reg = 24002560ul * 100ul - 1; // Time 100 seconds
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TCC1->CTRLA.bit.ENABLE = 1; // Enable TCC1
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
if (timeframeComplete) // Check if the period is complete
{
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Request TCC0 COUNT register read synchronization
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for CTRLB synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for read synchronization
uint64_t lowerWord = TCC0->COUNT.reg; // Read the TCC0 COUNT register
TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Request TCC1 COUNT register read synchronization
while (TCC1->SYNCBUSY.bit.CTRLB); // Wait for CTRLB synchronization
while (TCC1->SYNCBUSY.bit.COUNT); // Wait for read synchronization
uint64_t upperWord = TCC1->COUNT.reg; // Read the TCC1 COUNT register
uint64_t count = upperWord << 24 | lowerWord; // Combine the upper and lower word sections
SerialUSB.print(F("Count: ")); // Output the results
SerialUSB.println(count);
timeframeComplete = false; // Start a new timeframe
}
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
if (TC4->COUNT32.INTFLAG.bit.OVF) // Check for the overflow (OVF) interrupt
{
TC4->COUNT32.INTFLAG.bit.OVF = 1; // Reset the overflow interrupt flag
timeframeComplete = true; // Indicate that the timeframe is complete
}
}
Here's the output for 30MHz over 100s, (manually resetting the SAMD21 board for each output line):
Wow, you are really amazing. You should write books about the SAMD. I tried the 30 MHz and it works! I also found an old quartz with 50 MHz that didn't work. Probably because the PLL has 96 MHz and twice the frequency of 50MHz is greater than 96MHz. Presumably 40 MHz would still work. Can your SAMD51 output 40 MHz? Your sentence "not just counting asynchronously from the input signal" made me think a bit. Could it be that there are small incorrect measurements due to jitter now? Thank you very much for your efforts.
I tested the code with a 40MHz signal from the SAMD51, but the SAMD21 can't manage it, measuring around 27MHz. There must be a limit somewhere between 30 to 40MHz.
The 30MHz clock source from the SAMD51 isn't super accurate (or precise), since it's ultimately derived from the microcontroller's 48MHz DFLL in open loop mode, (rather than the more accurate closed loop mode).
Sorry, I probably didn't mention that the TCC0 and TCC1 timers are 24-bit. They're quite large, but not large enough to store the enormous numbers required by your application, hence the chaining to produce a 48-bit timer.
Hi Martin, I would be very interested to know what is the maximum possible "counting" frequency on your SAMD51 board? Maybe 60 MHz? For the SAMD21 the data sheet states 96MHz and for the SAMD51 180 MHz as the maximum timer input clock frequency. I would be very interested.
In the code for the SAMD51, I used the microcontroller's second Digital Phase Locked Loop (DPLL1), to generate a 200MHz clock source for the capture timers TCC0 and TCC1. That way, they can be run at high speed without exceeding the chip's datasheet specs.
Other than that the code is similar with a few register differences. The only other issue is that Adafruit's ArduinoCore-samd doesn't currently support the output of uint64_t numbers with Serial.print(ln). I'm currently in the process of raising a pull-requirest on Github with Adafruit to remedy the situation.
Here's the code, input on PA20 as before:
// Capture the number of pulses on PA20 over a 100 second timeframe
volatile boolean timeframeComplete;
void setup()
{
Serial.begin(115200); // Configure the native USB port
while (!Serial); // Wait for the console to be ready
MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS; // Switch on the event system peripheral
OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE = 0; // Disable DPLL1
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE); // Wait for register synchronization
OSCCTRL->Dpll[1].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDRFRAC(0x00) | // Set Loop Driver Ratio (LDR) fractional component to 0
OSCCTRL_DPLLRATIO_LDR(199); // Set Loop Driver Ratio (LDR) integer component to 199 (+ 1) for 200MHz
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.DPLLRATIO); // Wait for register synchronization
OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE = 1; // Enable DPLL1
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE); // Wait for register synchronization
while(!OSCCTRL->Dpll[1].DPLLSTATUS.bit.CLKRDY || // Wait for DPLL1 clock output to become active
!OSCCTRL->Dpll[1].DPLLSTATUS.bit.LOCK); // Wait for DPLL1 to achieve lock
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) | // Divide the 200MHz clock source by divisor 1: 200MHz/1 = 200MHz
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK2
GCLK_GENCTRL_SRC_DPLL1; // Select 200MHz DPLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC0 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC0 at 200MHz
GCLK->PCHCTRL[TCC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC1 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC1 at 200MHz
GCLK->PCHCTRL[TC4_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable the TC4 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 1 to TC4 at 48MHz
// Enable the port multiplexer on port pin PA20
PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on PA20
PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA20A_EIC_EXTINT4);
EIC->CTRLA.bit.ENABLE = 0; // Disable the EIC peripheral
while (EIC->SYNCBUSY.bit.ENABLE); // Wait for synchronization
EIC->CONFIG[0].reg = EIC_CONFIG_SENSE4_HIGH; // Set event on detecting a HIGH level
EIC->EVCTRL.reg = 1 << 4; // Enable event output on external interrupt 4
EIC->INTENCLR.reg = 1 << 4; // Clear interrupt on external interrupt 4
EIC->ASYNCH.reg = 1 << 4; // Set-up interrupt as asynchronous input
EIC->CTRLA.bit.ENABLE = 1; // Enable the EIC peripheral
while (EIC->SYNCBUSY.bit.ENABLE); // Wait for synchronization
EVSYS->USER[EVSYS_ID_USER_TCC0_EV_0].reg = EVSYS_USER_CHANNEL(1); // Set the event user (receiver) as timer TCC0, channel (n + 1)
EVSYS->USER[EVSYS_ID_USER_TCC1_EV_0].reg = EVSYS_USER_CHANNEL(2); // Set the event user (receiver) as timer TCC1, channel (n + 1)
// Select the event system generator on channel 0
EVSYS->Channel[0].CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4); // Set event generator (sender) as external interrupt
// Select the event system generator on channel 1
EVSYS->Channel[1].CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC0_OVF); // Set event generator (sender) as TCC0 overflow
TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
TCC_EVCTRL_OVFEO | // Enable overflow event output
TCC_EVCTRL_EVACT0_COUNTEV; // Increment the count upon receiving an event on channel 0
TCC1->EVCTRL.reg = TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
TCC_EVCTRL_EVACT0_COUNTEV; // Increment the count upon receiving an event on channel 0
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
TC4->COUNT32.INTENSET.reg = TC_INTENSET_OVF; // Enable timer TC4 overflow interrupts
TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC | // Set TC4 to reset timer on next prescaler clock
TC_CTRLA_PRESCALER_DIV2 | // Set the TC4 prescaler to divide by 2
TC_CTRLA_MODE_COUNT32; // Chain TC4 with TC5 to create a 32-bit timer
TC4->COUNT32.WAVE.reg = TC_WAVE_WAVEGEN_MPWM; // Set TC4 to Match PWM mode (MPWM)
TC4->COUNT32.CC[0].reg = 24002560ul * 100ul - 1; // Time 100 seconds
while (TC4->COUNT32.SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.SYNCBUSY.bit.ENABLE); // Wait for synchronization
TCC1->CTRLA.bit.ENABLE = 1; // Enable TCC1
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
if (timeframeComplete) // Check if the period is complete
{
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Request TCC0 COUNT register read synchronization
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for CTRLB synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for read synchronization
uint64_t lowerWord = TCC0->COUNT.reg; // Read the TCC0 COUNT register
TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC; // Request TCC1 COUNT register read synchronization
while (TCC1->SYNCBUSY.bit.CTRLB); // Wait for CTRLB synchronization
while (TCC1->SYNCBUSY.bit.COUNT); // Wait for read synchronization
uint64_t upperWord = TCC1->COUNT.reg; // Read the TCC1 COUNT register
uint64_t count = upperWord << 24 | lowerWord; // Combine the upper and lower word sections
Serial.print(F("Count: ")); // Output the results
Serial.println(count);
timeframeComplete = false; // Start a new timeframe
}
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
if (TC4->COUNT32.INTFLAG.bit.OVF) // Check for the overflow (OVF) interrupt
{
TC4->COUNT32.INTFLAG.bit.OVF = 1; // Reset the overflow interrupt flag
timeframeComplete = true; // Indicate that the timeframe is complete
}
}