Arduino Zero TCC Capture

Hi Alex,

It may or may not solve the problem, but you could try replacing micros() with micros2() function.

The micros2() function uses the timer counter TC3 to calculate micros() in a similar way to the AVR Arduinos such as the Uno, Mega, etc...

I've successfully used it in attachInterrupt() interrupt service routines for my flight control firmware.

Here's the code, (together with test output to the console):

// Micros2 function
volatile unsigned long timer2Counter;

void setup() {
  Serial.begin(115200);                            // Set up the serial port for test purposes
  
  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
  
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us 
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                            
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization
}

void loop() {
  Serial.println(micros2());                      // Testing the micros2() function
  delay(1000);                                    // Wait 1 second
}

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
uint32_t micros2() 
{
  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
}

Hi Martin,

wow cool... I will try it out.. thank you very much!
I will let you know if it helped or not.

Best regards,
Alex

Hello MartinL,
A couple of months ago you graciously assisted with generating a variable PWM frequency on pins that use TCC0 (Pin 3) and TCC1 (Pin 8). Using the same MCU, I am attempting to read the duty cycle of a 45hz PWM signal and running into a few issues.

First, the CPU is already a little taxed (SPI, calculating PID, sending serial data, etc.) and is producing erratic readings. Second, it does not appear that the examples that use TCCx will work since they will require feeding a different clock frequency to TCC0 and TCC1.

The basic pulseIn function worked but it added a 13ms delay to the loop processing that I am trying to avoid.

I saw that you were able to free the CPU by using DMAC in post #102. Is it possible to use DMAC for TC3 example you posted?

Requirements: Read the duty cycle of a 45hz, not interested in frequency, utilize DMAC, utilize TCx.

Appreciate the help!

Hi mwolter,

What sort of accuracy do you require the pulse width (duty-cycle) measurement? This essentially determines the timer's speed. The examples earlier in this thread are designed to read a radio controlled receiver, accurate to 1 microsecond, would that be enough for your project?

It might also be possible to use timer TCC2, if you're not using it already?

Also, which pin would you like to receive the incoming signal, any pin can be used except the Non-Maskable Interrupt (NMI), which is digital pin 2 on the Arduino Zero and digital pin 4 on the M0 Pro/M0.

The pulseIn() function blocks while waiting for the pulse, thereby making it unsuitable for time critical projects.

One microsecond should be more than enough. Not using TCC2 as far as I can tell. Pin 12 is available. Also is 11 if it is required to use TCC2.

Hi mwolter,

Here's the code that reads the period and pulse width of a 45Hz signal on D12 with TCC2/DMAC and outputs the values in microseconds (us) to the console:

// Setup TCC2 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 TCC2 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC2_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)&TCC2->CC[0].reg;                  // Take the contents of the TCC2 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 discriptor 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 TCC2 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC2_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)&TCC2->CC[1].reg;                  // Take the contents of the TCC2 counter comapare 0 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 discriptor as valid
  memcpy(&descriptor_section[1], &descriptor, sizeof(dmacdescriptor));   // Copy to the channel 1 descriptor
  
  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 = 48MHz/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;

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

  TCC2->EVCTRL.reg |= TCC_EVCTRL_MCEI1 |            // Enable the match or capture channel 1 event input
                      TCC_EVCTRL_MCEI0 |            //.Enable the match or capture channel 0 event input
                      TCC_EVCTRL_TCEI1 |            // Enable the TCC event 1 input
                      /*TCC_EVCTRL_TCINV1 |*/       // Invert the event 1 input         
                      TCC_EVCTRL_EVACT1_PPW;        // Set up the timer for capture: CC0 period, CC1 pulsewidth                                    
 
  TCC2->CTRLA.reg |= TCC_CTRLA_CPTEN1 |             // Enable capture on CC1
                     TCC_CTRLA_CPTEN0 |             // Enable capture on CC0
                     TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                     TCC_CTRLA_PRESCALER_DIV16;     // Set prescaler to 16, 16MHz/16 = 1MHz
                   
  TCC2->CTRLA.bit.ENABLE = 1;
  while (TCC2->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);
}

If you only require pulse width, then it's possible to just comment out the period DMAC channel.

Thank you Martin! Appreciate the help and will test it today.

Hi Martin,

thank you soooo much, it is working now!
You are so right, the problem was the micros() function of the MKR1010.
It was working as soon as I replaced the micros() against your awesome micros2() function inside the NewRemoteSwitch library.

But I do not understand the whole code of you... How does the TC3_handler function() work?.. How will that function be called?
Do you have some literature to understand that assembler code? I would like to understand that code.

Thank you very much!

With best regards,
Alexander

Dear Martin,

is it possible to use your micros2() function for two libraries in parallel?
My problem is, if I am using two different libraries for reading different 433Mhz signals I would need in both libraries the new micros2() function.

If I am using the same function, I get an error message that some functions has been used already.
Do I need to set a second timer for that purpose?

Thank you so much for your help!

With best regards
Alexander

Hi Alexander,

To use the micros2() function in two different libraries it's necessary to declare the function in a header file, say "micros2.h". The function declaration just specifies its parameters and return value, terminated with a semicolon, (rather than the function definition that contains the function's code in the curly brackets {}):

extern unsigned long micros2(void);

This header file can be placed in the Arduino IDE's "libraries" directory (where the sketches are stored by default). The file can then be included at the beginning of both libraries:

#include <micros2.h>

Doing this essentially defers the resolution of the micros2() function from compiler to the linker, allowing the function to be called by both libraries.

For example, if you look at the standard micros() code, you'll see that the function is declared in the Arduino core file "delay.h":

#ifdef __cplusplus
extern "C" {
#endif

// ...

extern unsigned long micros( void ) ;

// ...

#ifdef __cplusplus
}
#endif

...but defined in the file "delay.c":

unsigned long micros( void )
{
  uint32_t ticks, ticks2;
  uint32_t pend, pend2;
  uint32_t count, count2;

  ticks2  = SysTick->VAL;
  pend2   = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)  ;
  count2  = _ulTickCount ;

  do
  {
    ticks=ticks2;
    pend=pend2;
    count=count2;
    ticks2  = SysTick->VAL;
    pend2   = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)  ;
    count2  = _ulTickCount ;
  } while ((pend != pend2) || (count != count2) || (ticks < ticks2));

  return ((count+pend) * 1000) + (((SysTick->LOAD  - ticks)*(1048576/(VARIANT_MCK/1000000)))>>20) ;
  // this is an optimization to turn a runtime division into two compile-time divisions and
  // a runtime multiplication and shift, saving a few cycles
}

Hi Martin,

thank you so much for your help!
I have tried to write your Code as .h and .cpp files.

But the problem is that it didn't know the GCLK etc. commands.
error: 'GCLK' was not declared in this scope
error: 'GCLK_GENDIV_DIV' was not declared in this scope etc.

I am already using the 1.8.7 Arduino.cc IDE.

Do you know where the problem is? Are these files correct written?

header file looks like:

#ifndef micros2_h
#define micros2_h

#include <Arduino.h>


class micros2{
public:
micros2();
~micros2();
void micros_2();
void TC3_Handler();
};


#endif

cpp File looks like this:

#include "micros2.h"
#include <Arduino.h>

micros2::micros2(){
  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
  
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us 
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                            
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization
}

micros2::~micros2(){/*nothing to destruct*/}

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
void micros2::micros_2() 
{
  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 micros2::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
}

Thank you in advance with best regards
Alex

Hi Alex,

Including the "Arduino.h" header file like you've done, should be enough to make the CMSIS (Cortex Microcontroller Software Interface Standard) register definitions such as GCLK, TC3 etc... visible to your sketch.

When writing small C++ programs, I just use a simple editor such as Notepad++, then place the .h and .cpp files in a folder of the same name and deposit them in the Arduino IDE libraries directory. It's then possible to include the header file in your sketch and compile using the Arduino IDE. If there are any errors in your C++ code the compiler in the Arduino IDE will flag them up. This should allow the register definitions to become visible in your sketch.

In your code, I suggest removing #include <Arduino.h> line from your .cpp file, as it's already been included in the header file. Also, the micros2() function should return an unsigned long value.

The TC3_Handler() function has already been declared outside the scope of you C++ class, in the Atmel CMSIS "samd21g18a.h" file:

//...
void TCC0_Handler                ( void );
void TCC1_Handler                ( void );
void TCC2_Handler                ( void );
void TC3_Handler                 ( void );
void TC4_Handler                 ( void );
void TC5_Handler                 ( void );
//...

One way to implement this to create a static C++ member function, say: static void TC3_IrqHandler(), then call this from the TC3_Handler() function at the bottom of the .cpp file:

void TC3_Handler()            // TC3 Interrupt Service Routine
{
  micros2::TC3_IrqHandler();  // Call the micros2() interrupt handler function 
}

Another option is to declare a micro2() object, by including the line:

extern micros2 micros2;

... at the bottom of the .h file, then instantiate (define) it at the bottom of the .cpp file:

micros2 micros2();

... following this you can just call on the non-static C++ member function: void TC3_IrqHandler(), also at the bottom of the .cpp file:

void TC3_Handler()            // TC3 Interrupt Service Routine
{
  micros2.TC3_IrqHandler();  // Call the micros2() interrupt handler function 
}

Hi Martin,

thank you soo much for your support and help!
I haven't tried your hints but I will do that in the next days for sure.

I wish you a Merry Christmas and a Happy New Year!

Best regards,
Alex

Hi Martin,

I am very happy to let you know that its working now :smiley:
thank you sooo much for your help!

With best regards,
Alex

Hi Martin,

At the moment, both receiver libraries working with ARDUINO MKR 1010 Wifi, the ISR callback functions and the micros2() function but for some mysterious reasons, the attachInterrupt - ISR functions stop working after about 30-40 mins. All other functions like webserver combined with transmitting signals etc. is still running. Just the receiving part with the micros2() and the attachInterrupt() function is not working anymore. I am using the CHANGE mode for the attachInterrupt() and I have directly connected the receiver output wire directly to two input pins of the microcontroller. One pin is for one receiver library and the other pin for the other library. So I am using two ISR callback functions at the same time with the same micros2() function. Do I need to reset the interrupt flag or something else? At the ARDUINO YUN it worked over years without any problem. Do you know what the reason could be?

Thank you very much!
With best regards
Alex

Hi Alex,

It shouldn't be necessary to write to the interrupt flags to clear them, as this is done automatically by the Arduino code, (in the EIC_Handler() interrupt service routine).

Could it be the micros2() counter rolling over? Roll overs need to be handled in a particular way. An excellent explanation is provided on Nick Gammon's website: Gammon Forum : Electronics : Microprocessors : millis() overflow ... a bad thing?.

The micros2() function rolls over every 9 minutes or so.

EDIT: This is wrong, actually the micros2() function rolls over every 70 minutes or so like micros().

Kind regards,
Martin

Hi Martin,

thank you for the reply.
I am using that function for the micros2:

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
uint32_t micros2()
{
  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 could the problem be?

Best regards
Alex

Hi Alex,

Thanks for the code snippet.

Have you also set-up the continous read synchronization in the setup() portion of the code?:

REG_TC3_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                  TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization

Might I ask how you're using or calling the micros2() function in your attachInterrupt()'s callback function?

Kind regards,
Martin

Hi Martin,

I think I added the important code snippet you described. I used the following code:

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

  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
  
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us 
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                            
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization

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

I tried the two receiver libraries with the micros2() function without any other additional services like webserver etc. and now it seems to work for one hour now. I will try to figure it out if it works for a longer period of time.

Hi Martin,

I have to revise the previous findings. After about 60-70mins the interrupt function didn't work anymore. So it is still the problem of the receiving libraries or the micros2() function.

The attachedInterrupt calls a function of the receiver class which contains the micros2() function: (it is just a code snippet of the function)

	static byte receivedBit;		// Contains "bit" currently receiving
	static NewRemoteCode receivedCode;		// Contains received code
	static NewRemoteCode previousCode;		// Contains previous received code
	static byte repeats = 0;		// The number of times the an identical code is received in a row.
	static unsigned long edgeTimeStamp[3] = {0, };	// Timestamp of edges
	static unsigned int min1Period, max1Period, min5Period, max5Period;
	static bool skip;

	// Filter out too short pulses. This method works as a low pass filter.
	edgeTimeStamp[1] = edgeTimeStamp[2];
	edgeTimeStamp[2] = micros2();

	if (skip) {
		skip = false;
		return;
	}

	if (_state >= 0 && edgeTimeStamp[2]-edgeTimeStamp[1] < min1Period) {
		// Last edge was too short.
		// Skip this edge, and the next too.
		skip = true;
		return;
	}

	unsigned int duration = edgeTimeStamp[1] - edgeTimeStamp[0];
	edgeTimeStamp[0] = edgeTimeStamp[1];

Best regards