Go Down

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

dlabun

Hi Martin,

I was thinking more for people who design custom boards based on the SAMD21... I have a custom board in the design process right now and I am contemplating using an I2C adjustable XO from SiLabs. Could prove useful to the people that really want to customize the clocks on the SAMD21.

athtest

Thanks for the code.
I am trying to count pulses with timer while sleeping (RTCZero -> standby). Is that possible?
Right now it is working fine while M0 is ON. The problem is that it does not count while sleeping.
Is there solution?

DR49

Hi MartinL

Several months ago you gave me very good inputs to manage my frequency generation based on my M0Pro and TCC timers. It's working very well and the "transmissioin" part of my project is now completed !

For the reception part I need a frequency-detector (-meter !) working around 40 kHz sine wave input signal. This signal is an FSK2, made with 2 different frequencies aroun 40 kHz, then I need to know which frequency is present at a time, not more !

Each burst is lasting 30 ms.

I wondered if your sketch above (pulse period / pulse count) could be use to get an accurate frequency meter ?

Could you help to provide me the relevant code modifications to get such function ?

Many thanks

DR49

MartinL

#78
Feb 17, 2018, 10:24 am Last Edit: Feb 18, 2018, 06:31 pm by MartinL
Hi DR49,

Here's an example that uses the TCC0 timer on digital pin D12 that should work at 40kHz:

Code: [Select]
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
volatile uint32_t isrCount;
uint16_t period;
uint16_t pulsewidth;
uint32_t count;

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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                     // Enable event output on external interrupt 3
  attachInterrupt(12, callback, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_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
                                       
  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  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
}

void loop()
{
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                   
    pulsewidth = isrPulsewidth;
    count = isrCount;
    interrupts();
    SerialUSB.print(period);                      // Output the results
    SerialUSB.print(F("   "));
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.println(count);
    periodComplete = false;                       // Start a new period
  }
}

void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
    isrCount++;
  }
}

void callback(){}                               // Dummy callback function

I've set the generic clock divisor and the timer prescaler to 1, so that the timer runs at 48MHz.

MartinL

#79
Feb 17, 2018, 10:28 am Last Edit: Feb 18, 2018, 06:31 pm by MartinL
Here's another example that uses the synchronous event path, that triggers on the interrupt rising edge rather than the HIGH level:

Code: [Select]
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(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

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_EVSYS_0;     // Feed the Event Sys channel 0
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  attachInterrupt(12, callback, RISING);                                      // Attach interrupts to digital pin 12 (external interrupt 3)
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_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_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_RISING_EDGE |                  // Rising event edge detection
                      EVSYS_CHANNEL_PATH_SYNCHRONOUS |                    // Set event path as synchronous
                      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
                                       
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  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
}

void loop()
{
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                   
    pulsewidth = isrPulsewidth;
    interrupts();
    SerialUSB.print(period);                      // Output the results
    SerialUSB.print(F("   "));
    SerialUSB.println(pulsewidth);
    periodComplete = false;                       // Start a new period
  }
}

void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
  }
}

void callback(){}                               // Dummy callback function

DR49

Hi Martin L

Many thanks for this. I'll try to run it on my M0PRO.

W'll tell you about the results.


Yesterday I have tested this previous code from this thread (See attachment), unfortunately it doesn't work (no serial monitor output) with different inputs pulses on D12 like : 40 kHz or 5 kHz / pulse width 20 µs and 3,3V max
It seems the interrupt is not triggered by the signal....
A serial print in the TCC Handler never happened on the monitor...

I wonder if it is necessary to pull up / down Pin 12 with a resistor ?

I also not really understand the AttachInterrupt "NULL" function ? Could you clarify (for me) !

I come back with results of your last post,

Thanks again

Daniel

DR49

Hi MartinL

The first code (HIGH) is not working even with a signal as low as 10 kHz / 3,3 V cc / 35 µS pulse width....

I didn't test the second one (RIZING) but will try later.

For the first code I think that the interrupt is not taken into account. Periodcomplete and TCC handler never passed...

I recall my devices and sofware I'm using :
M0PRO
IDE 1.8.5 from arduino.cc
I follow your indications of our last thread 2016 (See attachement)

Is there anything missing ?

Kind Regards

Daniel
 

MartinL

#82
Feb 17, 2018, 05:32 pm Last Edit: Feb 18, 2018, 07:16 pm by MartinL
Hi Daniel,

I think the TC3_capture_pulseW_and_periode.ino file you attached isn't working, because the you're using the Serial port at a baud rate of 9600bps.

I just tested the first TCC0 example above with my Arduino Due providing the 40kHz PWM input signal. The Zero is able to read and output to the console, both the signal's pulse width and period. I'm using the Zero's native USB port: SerialUSB.

The reason why the attachInterrupt() function is using NULL as an argument, is that we're interested in setting up the D12 pin as an interrupt, so that it can trigger an event on the event channel. However, in this instance we don't need to make use of the interrupt's callback function, so instead of inserting the callback function's name, we instead insert a NULL or 0.

In the "WInterrupts.cpp" file, where the code for the attachInterrupt() function is located, you'll find the following comment and that the code tests to see that the callback function isn't 0 (NULL):

Code: [Select]
// Only store when there is really an ISR to call.
  // This allow for calling attachInterrupt(pin, NULL, mode), we set up all needed register
  // but won't service the interrupt, this way we also don't need to check it inside the ISR.
  if (callback)
  {

Kind regards,
Martin

MartinL

At 40kHz, 50% duty cycle, the example should be reading 1200 for the period, as:

48MHz / 1200 = 40kHz

and 600 for the pulse width, as:

600 / 1200 * 100 = 50% duty cycle

DR49

Thanks Martin

Why the Serial port at a baud rate of 9600bps should'nt works ?

What is the advantage to use the USB serial link rather than the serial port ?

Ok for the NULL argument, noted !

I join the attachement in .pdf where you explained to me how to proceed to match M0PRO with Zero , including also two pictures showing my signal input of today.

The blue trace (max 3.3 V) is the one I apply on Pin 12 (from a 5 Vcc at the input of a translation circuit in 3.3 V in order to protect the input of the M0PRO.

Regards

Daniel

MartinL

Hi Daniel,

Quote
Why the Serial port at a baud rate of 9600bps should'nt works ?

What is the advantage to use the USB serial link rather than the serial port ?
It's because the SerialUSB port is capable of running at a high data rate. At 40kHz the TCC0 interrupt service routine is being called 40000 times a second, this amount of data is simply too much for the Serial port running at 9600bps to handle.

If you connect your M0 Pro up to the USB native port, upload the code in message #78 of this thread, hook up the signal to digtial pin D12 then open the console window, you should start to receive the pulse width and period data.

Kind regards,
Martin

DR49

Hi Martin

After a number of tests, still nothing appears on Serial USB munitor.

The following has being tested without success (ATMEL EDBG installed on IDE 1.8.5):

M0 PRO with Genuino zero bootloader / Genuino Native Port Serial USB 115200 / Board Genuino Zero (Native port)

Idem with :                                   / M0PRO Native Port serial USB  115200 /  Board Genuino Zero (Programming port)
                               
M0PRO with M0PRO bootloader / M0PRO Native Port serial USB  115200 /  Board M0PRO (Native port)

Idem with :                          M0PRO Native Port serial USB  115200 /  Board M0PRO (Programming port)


When I put a Serial USB print "xx" in the Void setup, all configs provide the text on the USB monitor.


The signal input on Pin 12 is 40 kHz _ 12.5 µs pulse width (50 / 50 %)

I try what you recommended once to feed GCLK4 to TCC0 & TCC1, despite I got now the 1.8.5 IDE release :

// Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 0x441A;                      // Modified for compatibility IDE 1.7.11 MartinL forum Arduino.cc (https://forum.arduino.cc/index.php?topic=346731.15)

without result, but is it compatible with GCLK 5 used in the TCC sketch above ?

Thanks for help !

Regards

Daniel









       

MartinL

#87
Feb 18, 2018, 06:29 pm Last Edit: Feb 18, 2018, 07:19 pm by MartinL
Hi Daniel,

I tested the code with an older build of the Arduino Zero core code and this worked fine. However, testing with the latest Arduino Zero core, as you mention doesn't work.

I've investigated and it appears that Arduino have recently made code changes to the attachInterrupt() that breaks some of the TCCCapture code examples on this thread.

This is because the new attachInterrupt() code now tests for a NULL pointer and bypasses the pin set-up if it finds it. This prevents the attachInterrupt() from being used to set-up events without a callback function.

I'm going to take this up with Arduino developers on Github. In the meantime, the easiest workaround is to enter a dummy callback function that does nothing.

I've updated the example code in #78 above.

Sorry about that, should've tested on the latest Arduino Zero core code to begin with. The code should finally work now.

Kind regards,
Martin

MartinL

#88
Feb 18, 2018, 06:58 pm Last Edit: Feb 18, 2018, 07:04 pm by MartinL
I've now raised the issue with Arduino developers on Github: https://github.com/arduino/ArduinoCore-samd/issues/309.

DR49

Hi Martin !

We did work together to finally make it working !!
I explain :

You have found the pb thanks to your fine analysis of last changes for this crazy AttachInterrupt function / You are great as usual !!!

On my side I put in my code some recommandations given  in this # 56 thread and FORTUNATELY it mentionned another way to write the AttachInt !!! replacing it by a number of lignes...

Since I feared the Int was not taken into account, I decided to choose and put this code !

AND IT BEGAN TO WORK !!!!

I put in attachement, just for info,  the sketch which is working now both with Zero or M0 bootloader...

You will notice that I also add some lines (I put *** in comment to point it out), in particular the line after the one enabling event :
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;     // Enable TCC0 Bus clock (Timer counter control clock) 
And because I am not used with serialUSB, I came back to serial Prog....

I will now test YOUR updated code (# 78), even with USB port (!!) and w'll be back to you for results...


Many thanks again

Kind regards

Daniel

Go Up