Go Down

Topic: Arduino Zero TCC Capture (Read 35979 times) previous topic - next topic

mladek89

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

MartinL

#151
Jan 17, 2019, 10:53 pm Last Edit: Jan 18, 2019, 09:52 pm by MartinL
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: http://www.gammon.com.au/forum/?id=12127.

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


mladek89

Hi Martin,

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

Code: [Select]

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

MartinL

Hi Alex,

Thanks for the code snippet.

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

Code: [Select]
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

mladek89

#154
Jan 18, 2019, 04:39 pm Last Edit: Jan 18, 2019, 05:37 pm by mladek89
Hi Martin,

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

Code: [Select]
  //***********************************************************************************
  // 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.

mladek89

#155
Jan 18, 2019, 05:19 pm Last Edit: Jan 18, 2019, 05:35 pm by mladek89
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)

Code: [Select]

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

MartinL

#156
Jan 18, 2019, 11:17 pm Last Edit: Jan 18, 2019, 11:32 pm by MartinL
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

mladek89

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

mladek89

#158
Jan 22, 2019, 12:11 am Last Edit: Jan 22, 2019, 12:13 am by mladek89
Hi Martin,

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

Code: [Select]
#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:

Code: [Select]
#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

MartinL

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

mladek89

#160
Jan 28, 2019, 12:08 am Last Edit: Jan 28, 2019, 12:22 am by mladek89
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)

Code: [Select]
  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.

MartinL

#161
Jan 28, 2019, 09:55 am Last Edit: Jan 28, 2019, 10:20 am by MartinL
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:

Code: [Select]
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:

Code: [Select]
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:

Code: [Select]
return ((m << 8) + t);
...and slow down the timer by increasing its prescaler divider from 8 to 16:

Code: [Select]
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).

mladek89

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 :D

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

With best regards,
Alex

MartinL

Hi Alex,

Glad to hear that it's now working.

Quote
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

trampas

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();
}

Go Up