Arduino Zero TCC Capture

Hi Alex,

I mistakenly thought that the micros2() function rolled over after approximately 9 minutes, but actually the roll over is the same as the micros() function, around 70 minutes (1us * 2^32). This coincidentally matches your 60-70min of run time.

I ran a test comparing the output of both micros() and micros2() functions for over 90 minutes. Both rolled over without any issues and remained in sync throughout.

This suggests that the issue is in the code that calls the micros2() function, rather than within the function itself.

Kind regards,
Martin

Hi Martin,

thank you very much for running tests to identify the problem!
I didn't found the problem yet, but I will let you know if I find something new.

With best regards
Alexander

Hi Martin,

at the moment I am using for the micros2() function the following .h file:

#ifndef micros2_h
#define micros2_h

#include <Arduino.h>

#ifdef __cplusplus
extern "C" {
#endif


// ...

extern unsigned long micros2( void );

// ...




#ifdef __cplusplus
}
#endif

#endif

and for the .cpp file:

#include "micros2.h"

//***********************************************************************************
// Micros2 function for ARDUINO MKR SAMD21 Processor
//***********************************************************************************

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
unsigned long micros2( void ) 
{
  uint32_t m;
  uint8_t t;
     
  noInterrupts();                                 // Disable interrupts
  m = timer2Counter;                              // Get the number of overflows
  t = TC3->COUNT8.COUNT.reg;                      // Get the current TC3 count value

  if (TC3->COUNT8.INTFLAG.bit.OVF && (t < 255))   // Check if the timer has just overflowed (and we've missed it)
  {
    m++;                                          // Then in this case increment the overflow counter
  }  
  interrupts();                                   // Enable interrupts
  return ((m << 8) + t) / 2;                      // Return the number of microseconds that have occured since the timer started
}

// This ISR is called every 128us
void TC3_Handler()                    // ISR TC3 overflow callback function
{
  if (TC3->COUNT8.INTFLAG.bit.OVF)
  {
    timer2Counter++;                             // Increment the overflow counter
  }
  TC3->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;      // Rest the overflow interrupt flag
}   

//***********************************************************************************
//***********************************************************************************

Do you think there is a mistake inside the code?
The thing is, I used for the receiver functions the micros() instead of the micros2() function with an Arduino YUN and it is working without any problems..
As soon as I am just using the micros2() and the Arduino MKR 1010 Wifi it is working but just for 60-70mins..

Thank you in advance.
With best regards
Alex

Hi Alex,

The micros2() code is essentially the same as the micros() code used by the Arduino Yun, except that the SAMD21's timer is running 8 times faster to provide an accuracy of 0.5us rather than 4us.

The timer will rollover after 71 minutes, 35 seconds.

Is your code failing at a roughly between 60-70 minutes, or does it occur at an exact moment everytime?

What is the output of the micros2() function at the time of failure?

Kind regards,
Martin

Hi Martin,

sorry for the late reply.
I tried out a very simple code. I called every second the micros2() function without any additional functions inside loop. (no attachInterrupt function and no receiver functions)

  calls++;

  t = micros2();

  Serial.println("calls of micros function: ");
  Serial.println(calls);

  Serial.println(" ");
  Serial.println("micros output: ");
  Serial.println(t);

  delay(1000);

The output looks like follows:

...
micros output:
2147413951
calls of micros function:
2144

micros output:
930303
calls of micros function:
2145
...

-> the first rollover occurred after about 35.79 seconds.

...
micros output:
2146930303
calls of micros function:
4291

micros output:
930303
calls of micros function:
4292

-> the second rollover occurred after about 35.78 seconds.

the micros2() function works without any problems and after several rollovers.
I do not know why it occurs at every 35.7 seconds and not every 71,3 seconds.
But it works.

So the next tests will be, to call the micros2() function inside the receiver libraries.
The whole code with the receiver libraries called via an attachInterrupt function fails roughly between 60-70 minutes.

I will let you know if I will find something new.

Hi Alex,

Yes you're right, the micros2() function is rolling over every 35.7 minutes.

I missed this in my first test, because I only looked the results only after 71.3 minutes. At which point both outputs had rolled over (the micros2() function for the second time) and were therefore the same.

The reason is the return value of the micros2() function:

return ((m << 8) + t) / 2;

In this instance we're counting the number of 0.5us timer ticks and dividing by 2. The problem is that the 32-bit unsigned integer output overflows before the division by 2 occurs.

The Arduino Yun by comparison is counting the number of 4us timer ticks, then multiplying by 4:

return ((m << 8) + t) * 4;

...therefore this overflow issue doesn't happen.

To make the micros2() and micro() function rollover at the same time, is necessary to adjust the micros2() function to count 1us increments.

Just change the micros2() return value line, to remove the divide by 2:

return ((m << 8) + t);

...and slow down the timer by increasing its prescaler divider from 8 to 16:

TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                         TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                         TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode

I don't think that the rollover time is the reason for your code not functioning after 60-70 minutes though. The only time this could become an issue, is if your time between calling the micros2() function is greater than 35.7 minutes, (or 71.3 minutes with the code adjustments above).

Hi Martin,

I tried it out and changed the counting and the prescaler as you mentioned before.
The whole code incl. the receiver libraries still running for 15 hours without any problem now.

Thank you soo much for your help :smiley:

Do you think this settings influence the attachinterrupt function of the MKR1010 in some case?

With best regards,
Alex

Hi Alex,

Glad to hear that it's now working.

Do you think this settings influence the attachinterrupt function of the MKR1010 in some case?

I think the problem was caused by the micros2() function, but occured when subtracting the edgeTimeStamps[].

It's something that I hadn't considered until now, but subtracting two edgeTimeStamps[] unsigned long values to get the time difference (in microseconds), only works if the micros2() function also rolls over at the unsigned long maximum value. If the micros2() function rolls over too early and you subtract the unsigned long variables, you get some horrendously large value instead of the difference between them.

Now that the micros2() and the edgeTimeStamps[] variables are rolling over at the same point, your code should now function correctly.

Thanks Alex, looks like I'm going to have to change the micros2() function in my code as well.

Kind regards,
Martin

I am looking to count events using TCC2 (SAMD21), specifically I have a step and dir pin and would like the dir pin to change direction of count and the step pin to increment/decrease count.

I have not been able to get his working any if anyone might see my issue?

#define WAIT_TCC2_SYNC() while(TCC2->SYNCBUSY.reg)

void enableEIC(void)
{
PM->APBAMASK.reg |= PM_APBAMASK_EIC;
if (EIC->CTRL.bit.ENABLE == 0)
{
// Enable GCLK for IEC (External Interrupt Controller)
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));

// Enable EIC
EIC->CTRL.bit.ENABLE = 1;
while (EIC->STATUS.bit.SYNCBUSY == 1) { }
}
}

void setupStepEvent(void)
{
//we will set up the EIC to generate an even on rising edge of step pin
//make sure EIC is setup
enableEIC();

// Assign step pin to EIC
// Step pin is PA11, EXTINT11
pinPeripheral(PIN_STEP_INPUT, PIO_EXTINT);

//set up the direction pin PA10 to trigger external interrupt
pinPeripheral(PIN_DIR_INPUT, PIO_EXTINT); //EXTINT10

//***** setup EIC ******
EIC->EVCTRL.bit.EXTINTEO11=1; //enable event for EXTINT11
EIC->EVCTRL.bit.EXTINTEO10=1; //enable event for EXTINT10
//setup up external interurpt 11 to be rising edge triggered
//setup up external interurpt 10 to be both edge triggered
EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE3_RISE | EIC_CONFIG_SENSE2_BOTH;

//diable actually generating an interrupt, we only want event triggered
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT11;
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT10;

//**** setup the event system ***
// Enable GCLK for EVSYS channel 0
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;

GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EVSYS_CHANNEL_0));
while (GCLK->STATUS.bit.SYNCBUSY);
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EVSYS_CHANNEL_1));
while (GCLK->STATUS.bit.SYNCBUSY);

//setup the step pin to trigger event 0 on the TCC2 (step)
EVSYS->CHANNEL.reg=EVSYS_CHANNEL_CHANNEL(0)
| EVSYS_CHANNEL_EDGSEL_RISING_EDGE
| EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_11)
| EVSYS_CHANNEL_PATH_ASYNCHRONOUS;

EVSYS->USER.reg = EVSYS_USER_CHANNEL(1)
| EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_0);

//setup the dir pin to trigger event 2 on the TCC2 (dir change)
EVSYS->CHANNEL.reg=EVSYS_CHANNEL_CHANNEL(1)
| EVSYS_CHANNEL_EDGSEL_RISING_EDGE
| EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_10)
| EVSYS_CHANNEL_PATH_ASYNCHRONOUS;

EVSYS->USER.reg = EVSYS_USER_CHANNEL(2)
| EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_1);

//**** setup the Timer counter ******
PM->APBCMASK.reg |= PM_APBCMASK_TCC2;
// Enable GCLK for TC4 and TC5 (timer counter input clock)
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC2_TC3));
while (GCLK->STATUS.bit.SYNCBUSY);

TCC2->CTRLA.reg= TCC_CTRLA_SWRST; //reset TCC2
WAIT_TCC2_SYNC();
while(TCC2->CTRLA.bit.SWRST ==1);

TCC2->EVCTRL.reg=TCC_EVCTRL_EVACT0_COUNTEV | TCC_EVCTRL_EVACT1_DIR | TCC_EVCTRL_TCEI0 | TCC_EVCTRL_TCEI1;
WAIT_TCC2_SYNC();

TCC2->PER.reg=0x07FFFFF;
WAIT_TCC2_SYNC();

TCC2->COUNT.reg=0;
WAIT_TCC2_SYNC();

//TCC2->CTRLBSET.bit.CMD=TCC_CTRLBSET_CMD_RETRIGGER;
checkDirPin();

WAIT_TCC2_SYNC();
TCC2->CTRLA.reg |=TCC_CTRLA_ENABLE;
WAIT_TCC2_SYNC();

checkDirPin();
}

I was not sending the resync command before reading counter.

Now that this fixed I am counting but the direction is always counting positive.

Hi trampas,

The easiest way to get this working, is to set-up the event channels for asynchronous operation and the interrupt inputs to work on level rather than edge detection. This allows the input signals to pass through the event channels to the TCC2 counter unhindered and for the timer to clock off the event edges instead.

Setting up the event channels for asynchronous operation also means that you don't have clock them with a generic clock.

Here's an example that sets up TCC2 to count input events on D12, with D10 determining timer count direction. The TCC2 counter value is output to the console:

// Setup TCC2 to count input events on D12, with D10 determining timer count direction
void setup()
{
  SerialUSB.begin(115200);                   // Send data back on the Zero's native port
  while(!SerialUSB);                         // Wait for the SerialUSB port to be ready
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK0 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer on digital pin D10 count and D12 input
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].reg |= PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].reg |= PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
  
  // Set-up the pin as an EIC (interrupt) peripheral on D10 and D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg = PORT_PMUX_PMUXO_A | PORT_PMUX_PMUXE_A ;

  //attachInterrupt(10, NULL, HIGH);                                       // Attach interrupts to digital pin 10 (external interrupt 2)
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO2;                                 // Enable event output on external interrupt 2
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_HIGH;                            // Set event on detecting a HIGH level
 
  //attachInterrupt(12, NULL, HIGH);                                       // Attach interrupts to digital pin 12 (external interrupt 3)
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event on detecting a HIGH level
  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_TCC2_EV_0);              // Set the event user (receiver) as timer TCC2, event 0
  
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(2) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_1);              // Set the event user (receiver) as timer TCC2, 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_3) |    // Set event generator (sender) as external interrupt 3
                       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_EIC_EXTINT_2) |    // Set event generator (sender) as external interrupt 2
                       EVSYS_CHANNEL_CHANNEL(1);                           // Attach the generator (sender) to channel 1
  
  TCC2->EVCTRL.reg |= 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_TCINV0 |                                  // Invert the event 0 input
                      TCC_EVCTRL_EVACT1_DIR |                              // Set event 1 to change the counter direction
                      TCC_EVCTRL_EVACT0_COUNTEV;                           // Set event 0 to count the incoming events

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NFRQ;                                  // Set the TCC2 timer counter to normal frequency mode
  while (TCC2->SYNCBUSY.bit.WAVE);                                         // Wait for synchronization
                                                                                      
  TCC2->CTRLA.bit.ENABLE = 1;                                             // Enable TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);                                      // Wait for synchronization
}

void loop()
{
  TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;                         // Trigger a read synchronization on the COUNT register
  while (TCC2->SYNCBUSY.bit.CTRLB);                                       // Wait for the CTRLB register write synchronization
  while (TCC2->SYNCBUSY.bit.COUNT);                                       // Wait for the COUNT register read sychronization
  SerialUSB.println(TCC2->COUNT.reg);                                     // Output the TCC2 COUNT register
}

Hello Everyone,
I have implemented some of your code (dutifully acknowledged) in my project:

Thank you for posting!

I would like to thank the many people contributing to the topic "Zero TCC Capture"! The information I found here regarding the configuration of the timers was very valuable for my project.

In particular the code of MartinL using the DMAC for reading the PWM values is a wonderful piece of software, since it can deliver a fast capture of pulse count rate including timestamps without any load of the main CPU (#107, TCCCapture_DMAC_TCCount.ino). Thank you very much!

By adapting the code for my needs, I found a little flaw. Though the TCC counter compare registers have 24 bit, the measured output for the period (CC0) already overflows at 16bit, for example, by feeding the counter with a low enough frequency (e.g. 700 Hz). It turned out that this behaviour is due to the chosen beat size (HWORD, 16bit) for the DMA transfer. Changing the beat size to WORD (32bit) fixes this issue.

Simply replace the two instances of

descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;

by

descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_VALID;

Thanks again for your generous help!

MartinL:
Hi electro_95,

I finally solved it.

The code automatically captures an incoming digital waveform's period and pulse-width.

The incoming waveform is detected by enabling interrupts for level detection on the input pin. The event system is sort of a 12 channel highway between peripherals that allows them to communicate without CPU intervention. By enabling an output event for the interrupt pin (generator/sender) and the input event for a timer (user/receiver) and connecting them on a given channel, we can pass infomation about changes on the input pin to the timer using the event system. With the timer set to "Period and Pulse-Width Capture Action" mode, we can then automatically capture the period and pulse-width of the waveform.

The timer starts on the rising edge of the event line. The number of counter ticks to the subsequent trailing edge gives us the pulse-width, which is stored in the counter compare register 1 (CC1). The following rising edge gives us the period, which is stored in the counter compare register 0 (CC0). At this point the counter is reset back to 0 to start the cycle over again.

The following code outputs on the Zero's native port, the period and pulse-width of a radio controlled receiver channel in microseconds, (pulse-width: 1-2ms, period: ~22ms) using capture on TC3, at 1MHz (1us tick):

// 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
  }
}

Hi , this code is good also for Arduino M0 Pro ?

Hi lorenrus,

Below is an updated version of the code you describe. I've tested it on the throttle channel of my RC receiver, plugged into digital pin D12:

PW 1055
P 22020
PW 1055
P 22026
PW 1055
P 22017
PW 1055
P 22020
PW 1055
P 22017

It outputs 1ms (1055us) pulses at approximately 50Hz (22017us) intervals.

It should work on the Arduino M0 Pro, as hardwarewise it's very similar to the Arduino Zero, only that D2 and D4 are swapped (and the Zero has an addition ATN GPIO pin).

Here's the updated code:

// 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);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open
  
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
  
  GCLK->GENDIV.reg = 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

  GCLK->GENCTRL.reg = 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

  GCLK->CLKCTRL.reg = 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

  // Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Disable interrupts on external interrupt 3
  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_TC3_EVU);                // Set the event user (receiver) as timer TC3

  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_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC3->COUNT16.EVCTRL.reg |= 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
                  
  TC3->COUNT16.CTRLC.reg = 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_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)
 
  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
  
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16;       // Set prescaler to 16, 16MHz/16 = 1MHz
  
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;                       // 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("P ");
    SerialUSB.println(period);
    SerialUSB.print("PW ");
    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)            
  {
    TC3->COUNT16.READREQ.reg = 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 = TC3->COUNT16.CC[0].reg;                    // Copy the period  
    periodComplete = true;                                 // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC1)          
  {
    TC3->COUNT16.READREQ.reg = 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 = TC3->COUNT16.CC[1].reg;                // Copy the pulse-width
  }
}

MartinL:
Hi lorenrus,

Below is an updated version of the code you describe. I've tested it on the throttle channel of my RC receiver, plugged into digital pin D12:

PW 1055

P 22020
PW 1055
P 22026
PW 1055
P 22017
PW 1055
P 22020
PW 1055
P 22017



It outputs 1ms (1055us) pulses at approximately 50Hz (22017us) intervals.

It should work on the Arduino M0 Pro, as hardwarewise it's very similar to the Arduino Zero, only that D2 and D4 are swapped (and the Zero has an addition ATN GPIO pin).

Here's the updated code:



// 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);                        // Initialise the native serial port
  while(!SerialUSB);                              // Wait for the console to open
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;          // Switch on the event system peripheral
 
  GCLK->GENDIV.reg = 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

GCLK->GENCTRL.reg = 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

GCLK->CLKCTRL.reg = 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

// Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Disable interrupts on external interrupt 3
  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_TC3_EVU);                // Set the event user (receiver) as timer TC3

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_3) |    // Set event generator (sender) as external interrupt 3
                      EVSYS_CHANNEL_CHANNEL(0);                          // Attach the generator (sender) to channel 0

TC3->COUNT16.EVCTRL.reg |= 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
                 
  TC3->COUNT16.CTRLC.reg = 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_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)

TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;            // Enable compare channel 0 (CC0) interrupts
 
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16;      // Set prescaler to 16, 16MHz/16 = 1MHz
 
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;                      // 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("P ");
    SerialUSB.println(period);
    SerialUSB.print("PW ");
    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)           
  {
    TC3->COUNT16.READREQ.reg = 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 = TC3->COUNT16.CC[0].reg;                    // Copy the period 
    periodComplete = true;                                // Indicate that the period is complete
  }

// Check for match counter 1 (MC1) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC1)         
  {
    TC3->COUNT16.READREQ.reg = 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 = TC3->COUNT16.CC[1].reg;                // Copy the pulse-width
  }
}

Hi martin with your code and Arduino M0 Pro can i meausere frequency = 1 Mhz ?

Thank you

Hi lorenrus,

...can i meausere frequency = 1 Mhz ?

The example code provided is designed to operate at lower frequencies, in this case a RC receiver.

The TC capture can however run at much faster speeds, but how it's set-up depends on your minimum pulse-width and maximum period. It's a trade off: measuring faster, narrower pulses, reduces the maximum period before the timer overflows.

If you let me know the minimum pulse-width and maximum period of your input signal, I'll be able to suggest suitable timer settings.

MartinL:
Hi lorenrus,

The example code provided is designed to operate at lower frequencies, in this case a RC receiver.

The TC capture can however run at much faster speeds, but how it's set-up depends on your minimum pulse-width and maximum period. It's a trade off: measuring faster, narrower pulses, reduces the maximum period before the timer overflows.

If you let me know the minimum pulse-width and maximum period of your input signal, I'll be able to suggest suitable timer settings.

Hi
and thanks for reply

Yes infact, i modify the value of the prescaler to 1.

But if you show a code for higher frequency should be better.

i have to measure to two wave:

  • First wave is F = 115 200 Hz and T = 8,68 us and T/2 = 4 us
  • Second wave is F = 1 Mhz and T = 1 us and T/2 = 0.5 us

Another information, the value that you read in P e PW is already in us ?

Thank you

Hi lorenrus,

If the period is just two times the pulse-width, then timer overflow isn't an issue. This issue only occurs if you've got widely spaced pulses and the timer runs out (overflows) before the next pulse.

As you mention, chaging the timer's presaler is one option to increase the timer's speed from 1MHz to 16MHz. The other is by changing the generic clock (GCLK5) divisor to 1 instead of 3, increasing the timer's speed further from 16MHz to 48MHz:

GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 1 = 48MHz
                     GCLK_GENDIV_ID(5);          // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);             // Wait for synchronization

The value for the pulse-width and period are the number of elapsed timer ticks, so actual time is this value multiplied by the timer tick period (or 1 / timer frequency):

pulse-width or period = (1 / timer frequency) * number of ticks

For instance, in the example code the timer frequency is 1MHz:

pulse-width = (1 / 1MHz) * 1055 = 1055us

period = (1 / 1MHz) * 22017 = 22017us

I add my part of code, i generate a PWM wave with f = 96 khz.

I entered the code in the attachments as I was exceeding in the font size.

I obtain the result reported in photo.

the max freq of timer 1 is 16 Mhz if i put its prescaler to one ?

P = (1/16 000 000) * 509 = 31,81 us but my wave is F = 94 khz --> T = 10,64us.

What am I doing wrong?

Thank you

TCC_PulseW_Period_Count.ino (8.78 KB)