Arduino Zero TCC Capture

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

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.