The Due's a nice bit of kit. Running at 84MHz, it's a powerful microcontroller and really does supercharge your Arduino projects.
Shame the Due's not produced by Arduino.cc anymore, although it's still manufactured by Arduino.org.
The Due's a nice bit of kit. Running at 84MHz, it's a powerful microcontroller and really does supercharge your Arduino projects.
Shame the Due's not produced by Arduino.cc anymore, although it's still manufactured by Arduino.org.
Hi Everyone,
Sorry for being away from the forum so long. I'm happy that this discussion carried and it looks like others have been making discoveries and getting help with TCC0. For awhile I was happy with using TC instead but now I want to try again to migrate to TCC0. Using some of the code share earlier I put together this to try to do a PPW capture on PA04. For the life of me, I cant figure out why this wont work, any help is appreciated!
void setup() {
Serial.begin(115200);
while (!Serial) ;
Serial.println("Hello World");
pinMode(4,INPUT);
attachInterrupt(4,NULL,HIGH);
// Enable clock for TC
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1) ;
while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID( GCLK_CLKCTRL_ID_EIC));
while ( GCLK->STATUS.bit.SYNCBUSY == 1 );
TCC0->CTRLA.reg &= ~TCC_CTRLA_ENABLE; // Disable TC
while (TCC0->SYNCBUSY.bit.ENABLE == 1); // wait for sync
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_CPTEN0; // Set perscaler
while (TCC0->SYNCBUSY.reg & (TCC_SYNCBUSY_PER | TCC_SYNCBUSY_PERB));
TCC0->PER.reg = 0xFFFFFFFF; // Set counter Top using the PER register
while (TCC0->SYNCBUSY.bit.PER == 1); // wait for sync
TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI1 | // enable input event
TCC_EVCTRL_EVACT1_PPW; // event action = PPW
// Interrupts
TCC0->INTENSET.reg = 0; // disable all interrupts
TCC0->INTENSET.bit.OVF = 1; // enable overfollow
TCC0->INTENSET.bit.MC0 = 1;
// Enable InterruptVector
NVIC_EnableIRQ(TCC0_IRQn);
// Enable TC
TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE ;
while (TCC0->SYNCBUSY.bit.ENABLE == 1); // wait for sync
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
EIC->EVCTRL.bit.EXTINTEO4 = 1;
EIC->INTENCLR.bit.EXTINT4 = 1;
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | EVSYS_CHANNEL_CHANNEL(2);
EVSYS->USER.reg = (uint16_t) (EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0 | EVSYS_USER_CHANNEL(1)));
}
void loop() {
delay(250);
Serial.println(REG_TCC0_CC0);
}
void TCC0_Handler()
{
Tcc* TC = (Tcc*) TCC0; // get timer struct
if (TC->INTFLAG.bit.OVF == 1) { // A overflow caused the interrupt
TC->INTFLAG.bit.OVF = 1; // writing a one clears the flag ovf flag
//irq_ovf_count++; // for debug leds
Serial.println("OVF");
}
if (TC->INTFLAG.bit.MC0 == 1) { // A compare to cc0 caused the interrupt
TC->INTFLAG.bit.MC0 = 1; // writing a one clears the flag ovf flag
Serial.println("MC0");
}
}
Hi electro_95,
The TCC timers' set-up is subtly different from that of the TC timers.
The following code sets-up capture pulse-width and period on TCC0 on digital pin D12:
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EVSYS_USER = 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
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TCC0_EVCTRL |= 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_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
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)
REG_TCC0_INTENSET = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_ENABLE; // 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(period); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(pulsewidth);
periodComplete = false; // Start a new period
}
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for match counter 0 (MC0) interrupt
if (TCC0->INTFLAG.bit.MC0)
{
isrPeriod = REG_TCC0_CC0; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TCC0->INTFLAG.bit.MC1)
{
isrPulsewidth = REG_TCC0_CC1; // Copy the pulse-width
}
}
Luckily, the code's a bit shorter as the TCC timer requires less synchronization.
You're the best! Thank you so much for the help, this code does exactly what I was trying to do.
Is there actual documentation that you used to figure this out or is it purely from trial and error? Maybe I'm missing it but I felt like the datasheet described what PPW capture was like in concept but provided very little info on how to actually configure it. Especially to route the event to EV_1 and that MCEI0 and MCEI1 needed to be enabled (same with CPTEN0 and CPTEN1).
Anyway, many many thanks, this one was frustrating.
Now dare I ask (or even attempt) how to put these PPW measurements straight into memory via DMA?
Hello All,
Thanks for discussion on this thread, it has helped me with my project.
I am hoping to get help understanding the code though. Your help is appreciated.
Some background on the code and use:
I am using an arduino MKR1000 which is very similar to the Zero.
The code input captures 3 pwm signals using 3 TCs
The code builds off of the code in this thread to accomplish this, but does not use an ISR
The code works (at bottom sniped and attached to post), I am just curious about how a few lines work mainly the assignments of
REG_EVSYS_USER
REG_EVSYS_CHANNEL
and why I can set the REG separately and it seems to complete 3 functions. I would have thought I would need to OR the commands together like:
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC5_EVU); // Set the event user (receiver) as timer TC5
REG_EVSYS_USER |= EVSYS_USER_CHANNEL(2) | // Attach the event user (receiver) to channel 1 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4
REG_EVSYS_USER |= EVSYS_USER_CHANNEL(3) | // Attach the event user (receiver) to channel 2 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU); // Set the event user (receiver) as timer TC3
A snip of the working code:
// Code will initalize timers on mkr to do 3 input captures and
// output 3 pwm waves that vary duty cycle at a fixed freq
//Input capture. Using TC3 TC4 and TC5
void setup() {
// put your setup code here, to run once:
SerialUSB.begin(115200); // Send data back on the MKR1000's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
init_clocks(); // Initalize the TC's for inputcapture
}
void loop() {
getpulse(); // Function to collect the PWM signals
period = isrPeriod1;
pulsewidth = isrPulsewidth1;
SerialUSB.print(period); // Output the results...
}
void init_clocks(){
SerialUSB.println("setup clocks...");
\\ Feed clock code from before
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
SerialUSB.println("feeding clocks..");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK5 to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("feeding clocks part 2.."); // Doing this a second time does not make sense to me, but it works and does not overwrite the previous registry writting
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the GCLK5 to TCC2 and TC3
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO2; // Enable event output on external interrupt 2 used iwth TC5
attachInterrupt(A1, NULL, HIGH); // Attach interrupts. Not sure why this is nesseary
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO4; // Enable event output on external interrupt 4 to be used with TC4
attachInterrupt(6, NULL, HIGH);
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3 to be used with TC3
attachInterrupt(A2, NULL, HIGH); // Attach interrupts to digital pin A2 (external interrupt 3).
//
SerialUSB.println("setting event system..");
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC5_EVU); // Set the event user (receiver) as timer TC5
// Doing this a second time does not make sense to me, but it works and does not overwrite the previous registry writting
REG_EVSYS_USER = EVSYS_USER_CHANNEL(2) | // Attach the event user (receiver) to channel 1 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4
// Doing this a third time does not make sense to me, but it works and does not overwrite the previous registry writting
REG_EVSYS_USER = EVSYS_USER_CHANNEL(3) | // Attach the event user (receiver) to channel 2 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU); // Set the event user (receiver) as timer TC3
SerialUSB.println("setting up channel..");
REG_EVSYS_CHANNEL = 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_2) | // Set event generator (sender) as external interrupt 2
EVSYS_CHANNEL_CHANNEL(0);
REG_EVSYS_CHANNEL = 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(1);
//
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(2); // Attach the generator (sender) to channel
//
SerialUSB.println("setting up input capture..");
REG_TC5_EVCTRL |= TC_EVCTRL_TCEI | // Enable the TC event input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TC4_EVCTRL |= TC_EVCTRL_TCEI | // Enable the TC event input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TC3_EVCTRL |= TC_EVCTRL_TCEI | // Enable the TC event input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TC5_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x06); // Offset of the CTRLC register
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
REG_TC5_CTRLC |= TC_CTRLC_CPTEN1 | // Enable capture on CC1
TC_CTRLC_CPTEN0; // Enable capture on CC0.
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (write) synchronization
REG_TC5_CTRLA |= TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TC_CTRLA_ENABLE; // Enable TC5
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
// Repeat for TC4 and TC3
//
SerialUSB.println("setup complete");
}
void getpulse()
{
REG_TC5_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x18); // Offset address of the CC0 register. need to find the addresses for all
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPeriod1 = REG_TC5_COUNT16_CC0; // Copy the period
REG_TC5_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x1A); // Offset address of the CC1 register
while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPulsewidth1 = REG_TC5_COUNT16_CC1; // Copy the pulse-width
//
// Repeat for TC4 and TC3
}
MKR1000_3inputCapture.ino (11.7 KB)
Hi sundancekis,
The event system's User Multiplexer register (REG_EVSYS_USER) is a 16-bit register, but rather than being a simple register, it is in fact an interface or gateway to the event system multiplexer.
The register allows you to connect one of the event system's 12 channels to a peripheral (such as a timer), called a USER, that is the intended recipient of the event. It's possible to write to this register multiple times to connect various event channels to different users.
The fact that it's an interface to the event system multiplexer means that you can't simply read this register. You first have to do an 8-bit write to the user portion of the register, (the least significant byte), then read the register to get back the channel to which it's attached.
The register is described on page 420 of the SAMD21 datasheet.
The channel register (REG_EVSYS_CHANNEL) words in a similar manner.
Does anyone have a complete example they could post? I can't seem to combine all of snippets of code in this post into a working project.
Hi dlabun,
I can't seem to combine all of snippets of code in this post into a working project.
The TC/TCC period and pulse width capture can be boiled down to two examples that read the CPPM (pulse position modulation) input from a RC (radio controlled) receiver.
If you're interested in a different period and pulse width times, then it's necessary to change the generic clock divisor (GCLK_GENDIV_DIV(x)) and/or the timer prescaler (TC_CTRLA_PRESCALER_DIVx or TCC_CTRLA_PRESCALER_DIVx), so that the maximum period won't cause the timer to overflow.
The TC3 example:
// Setup TC3 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the GCLK5 to TCC2 and TC3
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU); // Set the event user (receiver) as timer TC3
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TC3_EVCTRL |= TC_EVCTRL_TCEI | // Enable the TC event input
/*TC_EVCTRL_TCINV |*/ // Invert the event input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TC3_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x06); // Offset of the CTRLC register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
REG_TC3_CTRLC |= TC_CTRLC_CPTEN1 | // Enable capture on CC1
TC_CTRLC_CPTEN0; // Enable capture on CC0
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (write) synchronization
//NVIC_DisableIRQ(TC3_IRQn);
//NVIC_ClearPendingIRQ(TC3_IRQn);
NVIC_SetPriority(TC3_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 0 (highest)
NVIC_EnableIRQ(TC3_IRQn); // Connect the TC3 timer to the Nested Vector Interrupt Controller (NVIC)
REG_TC3_INTENSET = TC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TC_CTRLA_ENABLE; // Enable TC3
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // 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(period); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(pulsewidth);
periodComplete = false; // Start a new period
}
}
void TC3_Handler() // Interrupt Service Routine (ISR) for timer TC3
{
// Check for match counter 0 (MC0) interrupt
if (TC3->COUNT16.INTFLAG.bit.MC0)
{
REG_TC3_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x18); // Offset address of the CC0 register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPeriod = REG_TC3_COUNT16_CC0; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TC3->COUNT16.INTFLAG.bit.MC1)
{
REG_TC3_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x1A); // Offset address of the CC1 register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPulsewidth = REG_TC3_COUNT16_CC1; // Copy the pulse-width
}
}
...and the TCC0 example:
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EVSYS_USER = 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
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TCC0_EVCTRL |= 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_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
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)
REG_TCC0_INTENSET = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_ENABLE; // 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(period); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(pulsewidth);
periodComplete = false; // Start a new period
}
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for match counter 0 (MC0) interrupt
if (TCC0->INTFLAG.bit.MC0)
{
isrPeriod = REG_TCC0_CC0; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TCC0->INTFLAG.bit.MC1)
{
isrPulsewidth = REG_TCC0_CC1; // Copy the pulse-width
}
}
Hi Martin,
Thank you once again for the fine examples. My particular usage is taking the information in this discussion in a slightly different direction, counting number of pulses occurring within an adjustable time period (aka, a radiation sensor).
Greetings Mister Martin.
I need your guidance and help with follow subject.
I have got a project based in Arduino Leonardo which has become bigger for this little platform, so I decided to move to Arduino M0.
One of task Arduino must carry on is to count pulses; in Leonardo I had configured a free-running counter in T1 timer/counter module.
For Arduino M0, I have taken code from this post; basically I need to set up Arduino to count pulses entered in Pin 12 (Pin 28 from SAM21G = PA19), code is as follow.
#define PinLED 13
volatile boolean Pp = true;
void setup()
{
SerialUSB.begin(19200);
while(!SerialUSB);
pinMode(PinLED, OUTPUT);
// Configuración del pin PA19 (Pin 12 Arduino) para contar eventos
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
PORT_WRCONFIG_WRPMUX |
PORT_WRCONFIG_PMUXEN |
PORT_WRCONFIG_PMUX( PORT_PMUX_PMUXE_H_Val ) | //PMUX set to H alternate function
PORT_WRCONFIG_INEN |
PORT_WRCONFIG_WRPINCFG |
PORT_WRCONFIG_PINMASK( 0x08 ) ; //Mask other pins except PA19
SerialUSB.println("PORT configurado");
// - Enable TCC0 Bus clock (Timer counter control clock)
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;
// Configuración de GCLK
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the input clock by (x)
GCLK_GENDIV_ID(6); // Set division on Generic Clock Generator (GCLK) 6
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK GENDIV realizada para el primer reloj");
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 6
GCLK_GENCTRL_SRC_GCLKIN | // Set the clock source to generator input pad, set to PA19 in PORT module
GCLK_GENCTRL_ID(6); // Set clock source on GCLK 6
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
SerialUSB.println("Configuración GCLK GENCTRL realizada para el primer reloj");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK6 | // ....on GCLK6
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK6 to TCC0/TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK CLKCTRL realizada para el primer reloj");
/* Configuración de TCC0 */
REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE; // DISABLE TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Comenzando configuración de TCC0");
while (TCC0->SYNCBUSY.bit.CTRLB);
REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR; // Clear DIR bit for counter to count up
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for (write) synchronization
SerialUSB.println("Configuración TCC0 CTRLB realizada para el tercer reloj");
TCC0->PER.reg = 0xFFFF; // Set counter Top using the PER register
while (TCC0->SYNCBUSY.bit.PER); // wait for sync
SerialUSB.println("Registro TCC0 PER inicializado");
while (TCC0->SYNCBUSY.bit.CTRLB);
TCC0->CTRLBSET.bit.CMD = TCC_CTRLBSET_CMD_READSYNC_Val;
while (TCC0->SYNCBUSY.bit.CTRLB);
REG_TCC0_COUNT = 0x0000; // Clear timer's COUNT value
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for synchronization
SerialUSB.println("Registro TCC0 COUNT inicializado para el tercer reloj");
REG_TCC0_CTRLA = TCC_CTRLA_PRESCALER_DIV1; // Set timer prescaler
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Configuración TCC0 CTRLA realizada para el tercer reloj");
}
void loop()
{
delay(1000); // Wait one second
digitalWrite(PinLED, Pp);
Pp = !Pp;
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register read sychronization
SerialUSB.print("TCC0: ");
SerialUSB.println(REG_TCC0_COUNT, DEC); // Print the result
//REG_TCC0_COUNT = 0x00; // Clear the COUNT register
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register write sychronization
}
What I experience is code gets stuck in every TCC0 synchronization polling
REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR; // Clear DIR bit for counter to count up
while (TCC0->SYNCBUSY.bit.CTRLB);
If I comment every ‘while’ code run properly, but TCC0 count register is always ‘0’.
For fast checking I have tied pin 13 and pin 12, but LED on board stays off when I join them, if I detach them, LED blinks every second.
For the references in this post (Changing Arduino Zero PWM Frequency - Arduino Zero - Arduino Forum), I know M0 and Zero boards are differents. I am using IDE 1.8.0 from arduino.org, code compiles well but always get stuck in those whiles from TCC0.
Any help it will be appreciated.
Hi Petirrojo31,
I've also tried to get this working. However like you, I found that activating the GCLK input pad causes the Zero's microcontroller to exhibit some strange behaviour. The code below sets up GCLK 4 input on digital pin 6 (D6), routes this to timer TCC0 and outputs the timer's COUNT register to the console.
Here are my observations:
If the input is left floating the counter counts and if a pull-up resistor is introduced it stops. So far so good.
Pulling the signal to ground through the pull-up causes the timer to jump up a number of counts each time. Again, as the signal isn't debounced this is as expected.
Set-up a digital IO to switch on and off every half a second and use this as an input to the GCLK pin. The program stalls, no output from the console.
Set-up a digital IO to switch on and off every half a second on another Arduino Zero and use this as an input the GCLK pin. The program takes a couple of seconds to start and correctly counts, but only outputs to the console every 4 seconds or so, despite there being no delay in the loop(). Possibly a synchronization issue?
There seems to be some strange interaction going on that causes the program to stall or slow. If the clock source on the other hand is taken from an internal source such as the 48MHz clock, then the program works just fine. So far I haven't yet found a solution.
Here's the code I used:
// Setup TCC0 counter to count inputs from the D6 pin and display its COUNT register in the console
void setup()
{
SerialUSB.begin(115200); // Set up USB port at 115200 baud
while(!SerialUSB); // Wait for the console to be started
// Output the clock on the D6 pin (chosen as it happens to be one of generic clock 4's IO)
// Enable the port multiplexer (mux) on the D6 pin
PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
// Switch port mux to peripheral function H - generic clock I/O
// Set the port mux mask for even port pin number, D6 = PA20 = 20 is an even number, PMUXE = PMUX Even
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the generic clock input by 1
GCLK_GENDIV_ID(4); // Set division on Generic Clock Generator (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = /*GCLK_GENCTRL_IDC |*/ // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 4
/*GCLK_GENCTRL_SRC_DFLL48M |*/GCLK_GENCTRL_SRC_GCLKIN | // Set the clock source to GCLK input
GCLK_GENCTRL_ID(4); // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK4 | // ....on GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1
TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register read sychronization
SerialUSB.println(REG_TCC0_COUNT, HEX); // Output the TCC0 COUNT register
}
Come to think of it, in observation 2 above the timer should continue outputting to the console, even when the pull-up resistor is attached.
I think the issue is that the generic clock (GCLK) source is driving the whole timer peripheral and not just the timer's counter. So when the GCLK stops the whole timer peripheral and all its registers stop responding and therefore the program appears to stall, as it just sits there waiting for register synchronization.
It might be better to use another method to count incoming pulses. Perhaps using the TCC capture code above, but instead of measuring the pulse width in the timer's interrupt handler, just increment a "count" variable.
Hello Mister Martin.
As you have pointed expertly in your response, I have also detected for TCC0 that if GCLK is configured with pin input external clock (GCLK_GENCTRL_SRC_GCLKIN), all peripherals register stay waiting for clock signal to continue working, so if there is not signal applied to that specific pin (as my case when pulses come later when sensor is plugged to board), program will stall.
MartinL:
I think the issue is that the generic clock (GCLK) source is driving the whole timer peripheral and not just the timer's counter. So when the GCLK stops the whole timer peripheral and all its registers stop responding and therefore the program appears to stall, as it just sits there waiting for register synchronization.
Yesterday (2nd january), I have tried with next code taken from this post...
#define PinLED 13
volatile boolean Pp = true;
volatile boolean periodComplete;
volatile unsigned long tcc0_cnt;
void setup() {
// put your setup code here, to run once:
SerialUSB.begin(19200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
pinMode(PinLED, OUTPUT);
digitalWrite(PinLED, LOW);
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
//PM->APBCMASK.reg |= PM_APBCMASK_TCC0; // Enable TCC0 Bus clock (Timer counter control clock)
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK GENDIV realizada");
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK GENCTRL realizada");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK CLKCTRL realizada");
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
attachInterrupt(12, NULL, RISING); // Attach interrupts to digital pin 12 (external interrupt 3)
SerialUSB.println("Configuración EXTINT_03 -Pin 12- realizada");
REG_EVSYS_USER = 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
SerialUSB.println("Configuración EVE_SYS_USER realizada");
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");
REG_TCC0_EVCTRL |= 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_TCEI0 | // Enable the TCC event 0 input
//TCC_EVCTRL_TCINV1 | // Invert the event 1 input
//TCC_EVCTRL_EVACT1_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
TCC_EVCTRL_EVACT0_INC; // Set up the timer to increment TC on event
SerialUSB.println("Configuración TCC0_EVCTRL realizada");
//NVIC_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
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)
SerialUSB.println("Configuración NVIC/IRQ realizada");
REG_TCC0_INTENSET = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0 | // Enable compare channel 0 (CC0) interrupts
TCC_INTENSET_CNT; // Enable counter interrupt
SerialUSB.println("Configuración TCC0_INTENSET realizada");
REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Módulo TCC0 habilitado");
/*
NVIC_DisableIRQ(TCC0_IRQn); SerialUSB.println("Disable...");
NVIC_ClearPendingIRQ(TCC0_IRQn); SerialUSB.println("Cleared...");
NVIC_SetPriority(TCC0_IRQn, 1); SerialUSB.println("Setted priority...");
NVIC_EnableIRQ(TCC0_IRQn); SerialUSB.println("Enable IRQ");
*/
}
void loop() {
// put your main code here, to run repeatedly:
delay(1000); // Wait one second
digitalWrite(PinLED, Pp);
Pp = !Pp;
SerialUSB.print("TCC0 Counter: ");
SerialUSB.println(tcc0_cnt, DEC); // Print the result
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for counter interrupt
if (TCC0->INTFLAG.bit.CNT)
{
tcc0_cnt = REG_TCC0_COUNT; // Copy TCC0 count value
periodComplete = true; // Indicate that the period is complete
}
if (TCC0->INTFLAG.bit.MC0){};
if (TCC0->INTFLAG.bit.MC1){};
}
But I get nothing in the count (always '0'); I am looking how to set register properly so count pulses can work fine.
This subject is driving me crazy, this is a part from bigger project and I feel I am making a tempest in a teapot.
Thanks for your advice and help.
Vladimir.
Hi Vladimir,
In your code you need to change the 4 to 3, in order to get 16MHz from GCLK5:
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) | // Divide the 48MHz system clock by 3 = 16MHz
Reading the TCC0 COUNT register is pretty convoluted:
It requires you to first request a read synchronization for the TCC0 COUNT, by setting the TCC_CTRLBSET_CMD_READSYNC bits in the TCC0's Control B register (REG_TCC0_CTRLBSET).
This Control B register itself then has to be write synchronized.
We then to need wait for read synchronization with the COUNT register.
Finally the COUNT register can be read.
Just place this bit of code:
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register read sychronization
...before the line:
tcc0_cnt = REG_TCC0_COUNT; // Copy TCC0 count value
...in your code.
Alternatively, the following code is just the TCC0 example above, but with "isrCount" and "count" variables included, that count the number of pulses:
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
volatile uint32_t isrCount;
uint16_t period;
uint16_t pulsewidth;
uint32_t count;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EVSYS_USER = 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
REG_EVSYS_CHANNEL = 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_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TCC0_EVCTRL |= 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_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
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)
REG_TCC0_INTENSET = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_ENABLE; // 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;
count = isrCount;
interrupts();
SerialUSB.print(period); // Output the results
SerialUSB.print(F(" "));
SerialUSB.print(pulsewidth);
SerialUSB.print(F(" "));
SerialUSB.println(count);
periodComplete = false; // Start a new period
}
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for match counter 0 (MC0) interrupt
if (TCC0->INTFLAG.bit.MC0)
{
isrPeriod = REG_TCC0_CC0; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TCC0->INTFLAG.bit.MC1)
{
isrPulsewidth = REG_TCC0_CC1; // Copy the pulse-width
isrCount++;
}
}
Kind regards,
Martin
Greetings Martin,
After a long week, after to read many forums and application notes from different Atmel devices, finally I could get a code for Arduino M0 board to count pulses.
I implemented it as simple as I could.
#define PinLED 13
volatile boolean Pp = true;
volatile unsigned long PulseCnt, tcc0_cnt, Period, PWidth;
void setup() {
// put your setup code here, to run once:
SerialUSB.begin(19200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
pinMode(PinLED, OUTPUT);
digitalWrite(PinLED, LOW);
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
PM->APBCMASK.reg |= PM_APBCMASK_TCC0; // Enable TCC0 Bus clock (Timer counter control clock)
/*
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz system clock by 3 = 16MHz
GCLK_GENDIV_ID(2); // Set division on Generic Clock Generator (GCLK) 2
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = //GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 2
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(2); // Set clock source on GCLK 2
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // .... on GCLK0...
GCLK_CLKCTRL_ID_EIC; // ... to feed the GCLK0 to EIC peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // ....on GCLK0...
GCLK_CLKCTRL_ID_TCC0_TCC1; // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");
//PORT->Group[PORTA].DIRCLR.reg = PORT_DIRCLR_DIRCLR(1<<19); // Set pin PA19 pin as input
PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_A; // Connect PA19 pin to peripheral A (EXTINT[3])
PORT->Group[PORTA].PINCFG[19].reg |= PORT_PINCFG_PMUXEN; // Enable pin peripheral multiplexation
//PORT->Group[PORTA].PINCFG[19].bit.INEN = PORT_PINCFG_INEM; // Enable input pin buffer
//attachInterrupt(12, NULL, RISING); // Attach external interrupt to digital pin 12 (external interrupt 3)
SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");
/* - Configuración del EIC - */
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event from pin on external interrupt 3 (EXTINT03)
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_RISE; // Set event on rising edge of signal
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
//REG_EIC_INTENSET = EIC_INTENSET_EXTINT3; // External interrupt is enable
/* - Configuración del EVSYS - */
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel n=0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0); // Set the event user (receiver) as timer TCC0, event 1
SerialUSB.println("Configuración EVE_SYS_USER realizada");
REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event output edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");
/* - Configuración de TCC0 - */
REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE; // Disable TCC0 peripheral
//while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Comenzando configuración de TCC0");
REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR; // Clear DIR bit to count up
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for (write) synchronization
SerialUSB.println("Configuración TCC0 CTRLB realizada");
REG_TCC0_EVCTRL |= TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
//TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
//TCC_EVCTRL_TCINV1 | // Invert the event 1 input
TCC_EVCTRL_EVACT0_COUNT; // Set up TCC timer/counter to count on event
SerialUSB.println("Configuración TCC0_EVCTRL realizada");
REG_TCC0_CTRLA |= //TCC_CTRLA_CPTEN0 | // Enable capture on CC0
//TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Módulo TCC0 habilitado");
}
void loop() {
// put your main code here, to run repeatedly:
delay(1000); // Wait one second
digitalWrite(PinLED, Pp);
Pp = !Pp;
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register read sychronization
SerialUSB.print("TCC0 count: ");
//SerialUSB.println(tcc0_cnt, DEC); // Print the result
SerialUSB.println(REG_TCC0_COUNT, DEC); // Print the result
}
/*
void EIC_Handler() // Interrupt Service Routine (ISR) for External Interrupt Controller (EIC)
{
if(EIC->INTFLAG.bit.EXTINT3)
EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT3; // Clear interrupt flag by writing '1' on it
}
*/
I would share with you and others some tips I found when I have tried to get the code works.
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
PM->APBCMASK.reg |= PM_APBCMASK_TCC0; // Enable TCC0 Bus clock (Timer counter control clock)
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // .... on GCLK0...
GCLK_CLKCTRL_ID_EIC; // ... to feed the GCLK0 to EIC peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // ....on GCLK0...
GCLK_CLKCTRL_ID_TCC0_TCC1; // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");
I have taken Generic Clock 0 (GCLK0) because it is enabled by default. You can choose any other Generic clock generator from the nine available [0…8] as you need them whit a special or specific configuration (source, frequency, prescaler, etc.); for additional configuration you could use GENDIV, GENCTRL and other registers, “one at time” for every peripheral that need a generic clock with special cettings, or at once if a generic clock is shared among peripherals, but for assignment to each peripheral CLKCTRL register ought to be written one at time.
I chose the long one...
//PORT->Group[PORTA].DIRCLR.reg = PORT_DIRCLR_DIRCLR(1<<19); // Set pin PA19 pin as input
PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_A; // Connect PA19 pin to peripheral A (EXTINT[3])
PORT->Group[PORTA].PINCFG[19].reg |= PORT_PINCFG_PMUXEN; // Enable pin peripheral multiplexation
//PORT->Group[PORTA].PINCFG[19].bit.INEN = PORT_PINCFG_INEM; // Enable input pin buffer
//attachInterrupt(12, NULL, RISING); // Attach external interrupt to digital pin 12 (external interrupt 3)
SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");
/* - Configuración del EIC - */
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event from pin on external interrupt 3 (EXTINT03)
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_RISE; // Set event on rising edge of signal
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
The other way is much shorter
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event from pin on external interrupt 3 (EXTINT03)
attachInterrupt(12, NULL, RISING); // Attach external interrupt to digital pin 12 (external interrupt 3)
If you seek attachInterrupt() function code in in WInterrupts.c and wiring_private.c files in your PC (mine is in C:\Users\MyName\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.11\cores\arduino\WInterrupts.c), you will see that function configures exactly same parameters for EIC peripherals, in the end attachInterrut is only for external interrupts.
I chose the long way because I did not want to configure any interruption to avoid implementation of any interrupt service rutine (ISR), in this way code (i think) runs faster; in fact EVSYS peripheral routes the event from pin to TCCn whitout CPU intervention (24. EVSYS – Event System - 24.1. Overview – Page 433), so this is a fast way to get an external signal into TCCn peripheral.
The rest is TCC0 peripheral configuration that I found in this post and other forums, and I could implement thanks to guidance and help from Mister Martin, so I have a code for M0 to count pulses. I do not know what is the highest frequency this code can count pulses, I do not have a signal generator neither oscilloscope and I am too lazy to build a 555 oscillator to test code; I have used LED pin (pin 13 from Arduino board) to toggle every second and feed into external interrupt 3 pin (pin 12 from Arduino board).
I hope this code can help others in their projects.
Vladimir Zúñiga.
I've been testing Petirrojo31 code and so far up to 1MHz it's pretty damn accurate. By 1MHz the error is within about +/-150 Hz. Nice job man!
Hi,
I'm working on a motorbike datalogger and need a 100ms interrupt.
I'm actually using this code which is working fine (thank you all !!) :
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 0x1B) ;
while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync
// The type cast must fit with the selected timer mode
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC (to enable configuration mode)
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as Match Frq
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256; // Set prescaler
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CC[0].reg = 18743; // 18743 OK
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0
// Enable InterruptVector
NVIC_EnableIRQ(TC3_IRQn); // interrupt > TC3_Handler(){}
TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
But I just saw that my compare value (18743) is not good after a 10 minutes test (I get a few ms more than the real time, if I use 18742 I get a few ms less).
So I would like to use a 32 bits counter.
If I understand well, in 32 bits mode, CC0 is a 32 bits value, so I can put more than 65535 in CC0. But I can put even 1 000 000, it seems to stop at 65535.
I only change :
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64;
TC->CC[0].reg = 256000;
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 0x1B) ; // Register CLKCTRL (Page 106) / 0x1B = TCC2,TC3
while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync
// The type cast must fit with the selected timer mode
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC (to enable configuration mode)
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32; // Set Timer counter Mode to 32 bits (Page 630)
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as Match Frq
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64; // Set prescaler
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// CC0 & CC1
TC->CC[0].reg = 256000;
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0
// Enable InterruptVector
NVIC_EnableIRQ(TC3_IRQn); // interrupt > TC3_Handler(){}
TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
Am I missing something ?
Hi quichedood,
The TC timers are 16-bits, however it's possible to pair them in 32-bit mode.
On the SAMD21G you've only got the option to pair TC3 and TC4. To configure the paired 32-bit counter you have to access it through the registers of the even numbered timer counter, in this case TC4.
Here's the information from the SAMD21 datasheet:
COUNT32: This mode is achieved by pairing two 16-bit TC peripherals. TC3 is paired with TC4,
and TC5 is paired with TC6. TC7 does not support 32-bit resolution.
When paired, the TC peripherals are configured using the registers of the even-numbered TC (TC4 or
TC6 respectively). The odd-numbered partner (TC3 or TC5 respectively) will act as slave, and the Slave
bit in the Status register (STATUS.SLAVE) will be set. The register values of a slave will not reflect the
registers of the 32-bit counter. Writing to any of the slave registers will not affect the 32-bit counter.
Normal access to the slave COUNT and CCx registers is not allowed.