Arduino Zero TCC Capture

Hi Daniel,

Here's some code that runs the TCC1 timer PWM at 50% duty-cycle, 40kHz on D9:

// Output 40kHz PWM on timer TCC1 (10-bit resolution)
void setup()
{
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (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 GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the PWM channel on pin D9 
  PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC1 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NPWM;        // Setup single slope PWM on TCC1
  while (TCC1->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation: 959 = 50kHz
  REG_TCC1_PER = 1199;      // Set the frequency of the PWM on TCC1 to 40kHz
  while(TCC1->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC1_CC1 = 599;      // Set the duty cycle of the PWM on TCC1 to 50%
  while(TCC1->SYNCBUSY.bit.CC1);
 
  // Enable TCC1 timer
  REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { }

Unfortunately, there isn't much information on how to set things up. The best source of information is the SAMD21 datasheet.

The register definitions are stored (at least on my Windows machine) at:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include... directory.

In this directory there are two sub-directories, "instance" and "component".

The "instance" directory contains the header files with definitions for each instance of peripheral, for example: "tcc0.h", "tcc1.h", etc...

The "component" directory contains the header files with definitions for peripheral's register bitfields, for instance "tcc.h". This is common to all TCC peripherals, so in this case there's only on file per peripheral.

In the present code it seems that both TCC0 and TCC1 are used ? (Feed GCLK 4 to...)

The SAMD21 hardware doesn't offer the option of clocking the TCC timers with separate generic clocks. The generic clock is instead fed to timer pairs: TCC0 & TCC1, TCC2 & TC3, and finally TC4 & TC5.

In the present code the generic clock is fed to timer TCC0 and to timer TCC1, but TCC1 isn't being used.

Kind regards,
Martin

Hi MartinL,

Ok , understood and noted !

Many thanks for this, I'll inquire inside these ATMEL directories

Kind Regards,

Daniel

Hi MartinL

I have tested the above # 94 sketch (40 kHz via TCC1 on D9), but be obliged to do some adjustments to get it working (See attachement).

To summarise, I needed to do the 2 following modifications :

// Feed GCLK4 to TCC0 and TCC1
GCLK->CLKCTRL.reg = 0x441A; // Modified for IDE 1.7.11 by MartinL forum Arduino.cc (Changing Arduino Zero PWM Frequency - Arduino Zero - Arduino Forum)

and the following one :

// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
REG_TCC1_WAVE |= TCC_WAVE_POL(0xF) | // Reverse the output polarity on all TCC1 outputs
TCC_WAVE_WAVEGEN_DSBOTH; // Setup dual slope PWM on TCC1
and following....

Since "Normal (single slope) PWM operation" seems not working.

Hopefuly I have now a sketch running under TCC1, but could you explain why these modifications are needed ?

Then I tried to change the pin output from 9 to 7...and I never got it working.

Could you please adjust the sketch to do this, so I can understand how to pass from D9 to D7 output pin ?

What I did ( but it's not working !) :

// Enable the port multiplexer for the PWM channel on pin D7
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

And :
// Connect the TCC1 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
// F & E peripherals specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;

I fear this last modification to be the pb since I don't understand which number needs to be put in ??
Many thanks again,
Kind Regards
Daniel
40kHz_TCC1.ino (2.88 KB)

Hi Daniel,

The changes shouldn't be necessary, I tested the TCC1, single slope PWM example #94 above and I'm getting 40kHz, 50% duty-cycle on D9.

In your code, you've done the same thing, but configured the output for dual slope PWM instead.

The TCC1 timer output isn't available on D7. Your next best option is to use D8 that can output TCC1 on channel 0.

To activate D8, change the pin multiplexer (PMUX) configuration to:

PORT->Group[g_APinDescription[8].ulPort].PMUX[g_APinDescription[8].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;

You'll also need to change the counter compare register to channel 0:

REG_TCC1_CC0 = 599;      // Set the duty cycle of the PWM on TCC1 to 50%
while(TCC1->SYNCBUSY.bit.CC0);

You'll need to set it to 299 if you're using dual slope PWM.

Kind regards,
Martin

Hi MartinL

Everything is going perfectly ! I made a mistake (not hooking my oscilloscope to the correct Pin.....bah....)

So Many thanks again for your precious help !

Now I have aother idea, could we imagine to code a PSK rather than an FSK2 ?

Do you have an idea to control the phase of a generated PWM signal at 40 kHz ?

The idea would be to change by 180 ° each time I need to générate a different bit (0 or 1).

Have a good day

Kind Regards

Daniel

PS : Perhaps that we need to open a new thread for this approach ?

Hi Daniel,

Do you have an idea to control the phase of a generated PWM signal at 40 kHz ?

If you require a 180° phase shift then you could try using the polarity bits to invert the waveform in the WAVB (Waveform Buffer register).

Buffered registers such as CCxB, PERB etc..., should be used when changing the duty-cycle/period during operation. Loading these registers cause the output to update at the beginning of the next timer cycle (overflow), preventing the changes from causing glitches on your waveform output. (Changes to the CCx, PER, etc... take effect on the output immediately).

An example of loading a buffered counter compare register:

REG_TCC0_CCB0 = 1500;
while(TCC0->SYNCBUSY.bit.CCB0);

Another alternative, for fine control over the PWM signal is to use dual slope critical PWM, where two of the timer's counter compare channels (CCx) are used to control the position of both the rising and falling edges of the PWM output. This could be used to generate whatever phase shift in the signal you require.

Kind regards,
Martin

Dear MartinL

I'm coming back to you since I got some pbs when associating the TTC0 code above to my previous designed sketches (RTC and LCD working both on I2C). I'm also using millis() to display the time each 5 sec etc...

This is my code (joined piece).

In fact it works as TCC0 capture, but both serialUSB and RTC seem to be blocked by TCC interruptions.

When I comment TCC0 initialisation in the void setup, serialUSB and RTC are running well again !!

Could you help ?

I fear I2C and/or RTC as non compatible with TCC0 ?

Kind regards

Daniel

Rx_TCC_PW_P_C_essai.ino (8.83 KB)

Hi Daniel,

If you're using a 40kHz input, then I think it's most probably because your input frequency is too high for the SAMD21 with the additional RTC and I2C code.

Your options as I see it are as follows:

  1. If possible use a lower input frequency.
  2. If you don't need both pulse-width and period data then disable the interrupt for the one you don't need.
  3. Use the SAMD21's Direct Memory Access Controller (DMAC) to trigger on the TCC0 input, get it to read the pulse-width/period and move this data to a memory location that can be accessed by your main program.
  4. Use a faster micro-controller.

Kind regards,
Martin

Hi Daniel,

Ok, I think I've found a possible solution.

Rather than using the TCC0_Handler() interrupt service routine, it instead uses the SAMD21's Direct Memory Access Controller (DMAC). The DMAC shoulders the burden of receiving the period and pulse width data from the TCC0 timer, thereby freeing the CPU from this task.

The code uses DMAC channels 0 and 1 to continuously read the period and pulse width data from TCC0's registers and moves this information to the variables "dmacPeriod" and "dmacPulsewidth".

In the loop() portion of your sketch the CPU only has to read these variables. This should free up the CPU, giving it time to run your RTC and I2C routines.

Here's the code:

// Setup TCC0 to capture pulse-width and period with DMAC transfer
volatile uint32_t dmacPeriod;
volatile uint32_t dmacPulsewidth;

typedef struct                              // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));               // Write-back DMAC descriptors
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));         // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                     // Place holder descriptor

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels
 
  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_0) | DMAC_CHCTRLB_TRIGACT_BEAT; 
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[0];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC0->CC[0].reg;                  // Take the contents of the TCC0 counter comapare 0 register
  descriptor.dstaddr = (uint32_t)&dmacPeriod;                       // Copy it to the "dmacPeriod" variable
  descriptor.btcnt = 1;                                             // This takes one beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;   // Copy 16-bits (HWORD) and flag the descriptor as valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor
  
  DMAC->CHID.reg = DMAC_CHID_ID(1);                                 // Select DMAC channel 1
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_1) | DMAC_CHCTRLB_TRIGACT_BEAT; 
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[1];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC0->CC[1].reg;                  // Take the contents of the TCC0 counter comapare 1 register
  descriptor.dstaddr = (uint32_t)&dmacPulsewidth;                   // Copy it to the "dmacPulseWidth" variable
  descriptor.btcnt = 1;                                             // This takes 1 beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;    // Copy 16-bits (HWORD) and flag the descriptor as valid
  memcpy(&descriptor_section[1], &descriptor, sizeof(dmacdescriptor));   // Copy to the channel 1 descriptor
  
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  REG_GCLK_GENDIV = 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

  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

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

  //attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;                              // Set event detecting a HIGH level
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization
 
  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                                    
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization

  DMAC->CHID.reg = DMAC_CHID_ID(0);                 // Select DMAC channel 0
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 0
  DMAC->CHID.reg = DMAC_CHID_ID(1);                 // Select DMAC channel 1
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 1
}

void loop()
{
  SerialUSB.print(dmacPeriod);                      // Output the results
  SerialUSB.print(F("   "));
  SerialUSB.println(dmacPulsewidth);
}

Kind regards,
Martin

Thanks MartinL

Just three comments :

1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?

2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()" ?

3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++; in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?

Thanks again,

Kind Regards,

Daniel

Hello ,

I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):

  1. I want to use a pair of TC4 - TC5 working in 32 bit mode.
  2. I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
    So TC4 should act as a event generator and TC5 as a user.
    which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
    chaining.
  3. My understanding goes like: initialisation path ( PM -> APBCMASK ->EVSYS -> EVGEN ->USER)
  4. The problem is to setup this connection. What all registers should be initialised to make it work
    and how ?

Thank you .

Hi Daniel,

1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?

I raised the issue with the Arduino SAMD core development team and they've flagged it as a bug, so I expect it might be fixed in a subsequent Arduino Zero build.

In the DMAC example code above, I commented out attachInterrupt() function and replaced it by setting the interrupt registers directly.

2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()" ?

The "dmacPeriod" and "dmacPulsewidth" variables now hold the latest measurements. The DMAC is reading them instead of the CPU having service the TCC0_Handler() each time. The DMAC is effectively handling the TCC0 timer for the CPU.

3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++; in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?

I'll have to think about this one....

It might be possible to use the event system to get another of the SAMD21's timers to count the number of TCC0 periods that have elapsed.

Kind regards,
Martin

Hi parnal,

I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):

  1. I want to use a pair of TC4 - TC5 working in 32 bit mode.
  2. I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
    So TC4 should act as a event generator and TC5 as a user.
    which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
    chaining.
  3. My understanding goes like: initialisation path ( PM -> APBCMASK ->EVSYS -> EVGEN ->USER)
  4. The problem is to setup this connection. What all registers should be initialised to make it work
    and how ?

Looks like your issue is similar to the Daniel's requirement in point (3) above. I'll see if I can come up with a solution using the TC timers in 32-bit mode.

Kind regards,
Martin

Hi Daniel and parnal,

I've added the counter using the TC3 and TC4 timers in 32-bit mode. In this mode, TC4 acts as the master and TC3 the slave, this just means that the registers are accessed through TC4's interface.

The TC4 timer in 32-bit mode is set-up to increment each time it receives an event from interrupt pin on the event system.

Using 32-bits with a 40kHz input frequency will allow the TC timer to count for almost 30 hours before rolling over back to 0.

If you require longer than this then it'll be necessary to get TC4 to generate an overflow event to the 16-bit TC5 timer. Using TC5 as well will give you 223 years before it rolls over! Probably enough for most projects, in any case we won't be around when this happens.

Please find the code attached. Unfortunately it exceeds the 9000 character limit, so I can't display it in this message.

Kind regards,
Martin

TCCCapture_DMAC_TCCount.ino (8.95 KB)

Hi parnal,

Here's a cut down version of the above code, that only counts the number of incoming interrupt pulses on D12 using timer TC4 in 32-bit mode, (but doesn't use the DMAC or TCC0):

// Setup TC4 in 32-bit mode to count interrupt pulses using the event system on digital pin D12
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(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

  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_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

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

  //attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;                              // Set event detecting a HIGH level
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization

  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4
  
  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                                    

  REG_TC4_EVCTRL |= TC_EVCTRL_TCEI |              // Enable asynchronous events on the TC timer
                    TC_EVCTRL_EVACT_COUNT;        // Increment the TC timer each time an event is received

  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_MODE_COUNT32 |        // Set the TC4 timer to 32-bit mode in conjuction with timer TC3
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  REG_TC4_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                    TC_READREQ_ADDR(0x10);        // Offset of the 32-bit COUNT register
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
}

void loop()
{
  SerialUSB.println(REG_TC4_COUNT32_COUNT);       // Output the results
}

Thank you so much.
The only part where I have a problem is this :

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

//attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization

Why is it necessary to attach an external interrupt, cant it be done using internal interrupt ?
In simple words when the TC4 counts to its maximum count it displays the count and again starts over .

Hi Parnal,

In the earlier builds of the Arduino core code, it was possible to set-up a pin as an interrupt by using the attachInterrupt() function and passing a NULL argument as the function callback pointer:

attachInterrupt(12, NULL, HIGH);           // Attach interrupts to digital pin 12 (external interrupt 3)

However, in the later builds this was changed so that attachInterrupt() would skip pin set-up if a NULL pointer was detected.

This means at the moment it's necessary to either use attachInterrupt() with a dummy (empty) callback function, or set-up the pin using regsiter manipulation.

I've raised the isse on Github with the Arduino SAMD core developers, as this changes reduces the flexibility of this function.

The first section of code just switches the pin from GPIO to the peripheral multiplexer and then selects the peripheral as the External Interrupt Controller (EIC):

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

The following lines then just activate the interrupt on D12, which is on EIC channel 3 and set the pin sensing to interrupt on a HIGH level:

REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;       // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;           // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE;                            // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY);                             // Wait for synchronization

Hi parnal,

I think I probably got the wrong end of the stick with regard to your requirements, it's just that this thread was dealing with capturing an external signal and for this it's necessary to use an external interrupt.

If you're application doesn't require an external interrupt then it's possible to just use TC timer in 32-bit mode with internal interrupts and events.

Hello Martin,

Thank you again, but that is where my problem is how to fed TC3 to TC4 internally ? :frowning:

Hi parnal,

Setting the TC4 timer to 32-bit mode automatically feeds one timer from another. Essentially it turns timer TC4 into a 32-bit timer. The SAMD21's hardware does this for you, chaining TC4 to TC3 in the background.

Reading the TC4's 32-bit count register gives you a seamless 32-bit result.

It's also possible to set-up CCx counter compare and overflow interrupts in 32-bits.