Arduino Zero TCC Capture

Hi MartinL

I'm using your "TCCCapture_DMAC_TCCount.ino".

What I need is to "restart" the TCCount timer (TC4 & slave TC3) to 0 when the value of REG_TC4_COUNT32_COUNT rises a dedicated number (i.e lenght of bit measurement in my application).

Then how can I reset the TC timer to 0 and let it count again ?

Kind Regards

Daniel

Hi Daniel,

You can do this by setting the TC4 (plus TC3) to match frquency (MFRQ) mode.

In MFRQ mode the timer period can be set by the CC0 (counter compare 0) register, instead of the timer's maximum value:

REG_TC4_COUNT32_CC0 = 1919;                     // Set the TC4 CC0 register as the TOP value in match frequency mode
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

The timer will return to 0 when it reaches the value in the CC0 register.

The timer is set to MFRQ mode in the CTRLA register:

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

Kind regards,
Martin

Hii Martin,

This is my code for clock chaining in Due and I wish to do the same on Zero.

uint32_t *TC_CV = (uint32_t *)(0x40084010); //for timer 1 channel 0 (see datasheet p.888)
uint32_t startTime;
uint32_t wantedTime;
uint32_t endTime;
uint32_t addup = 0;


void setup() {

pmc_set_writeprotect(false);

/*************  Timer Counter 1 Channel 0 to generate PWM pulses thru MCK  ************/

PMC->PMC_PCER0 |= PMC_PCER0_PID30;                          //PID30 from the peripheral identifiers for Timer 1 channel 0, the command enables PWM
TC1->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1      // MCK/2, clk on rising edge
                              | TC_CMR_WAVE                 // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC         // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR           // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET   ;          // Set TIOA0 on RC compare match
                                            
// Frequency VARIANT_MCK is 84MHz, counter clocked with MCK/2 = 42MHz:
TC1->TC_CHANNEL[0].TC_RC =  VARIANT_MCK / 1000000 / 2;      //  count to 42 --> 1MHz on TIOA0
TC1->TC_CHANNEL[0].TC_RA = VARIANT_MCK / 1000000 / 4;       //  half value 21 (50% duty cycle on TIOA0)


TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;    // Software trigger TC0 counter and enable CLOCK


/*************  Timer Counter 1 Channel 1 to generate PWM pulses thru TIOA0  ************/

PMC->PMC_PCER0 |= PMC_PCER0_PID31;                        //PID31 from peripheral identifier from Timer 1 channel 1
TC1->TC_BMR = TC_BMR_TC1XC1S_TIOA0;                      // Timer Counter 1  XC1 is internally clocked by TIOA0
TC1->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_XC1            // External clock XC1 (i.e. TIOA0) selected
                              | TC_CMR_WAVE                // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;        // UP mode with automatic trigger on RC Compare

TC1->TC_CHANNEL[1].TC_CV = 0;
TC1->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;   // Software trigger TC1 counter and enable CLOCK


Serial.begin(250000);

}

void loop() {
  
    while (true){
    startTime = TC1->TC_CHANNEL[1].TC_CV; //*TC_CV
    //while(TC1->TC_CHANNEL[1].TC_CV == startTime)addup+=1;
    for(int i=0; i<5; i++);
    endTime=TC1->TC_CHANNEL[1].TC_CV;
    
    Serial.println(startTime);
    Serial.println(endTime);
    Serial.println(endTime-startTime);
    Serial.println(TC1->TC_CHANNEL[0].TC_RC);
  
    Serial.println("--");
    delay(500);
    }
}

and I wish to do this : "2) Do you wish the timers to free run and overflow at their maximum (2^32)?"

Hi parnal,

Ok, so this is ard_newbie's Arduino Due timer chaining code from here: Arduino Due - Clock Chaining - Arduino Due - Arduino Forum.

If you tell me what this code does, I'll implement for you on the SAMD21.

Kind regards,
Martin

Hii Martin,

The program is:

  1. Here I wanted to use Timer 1 which is having 3 channels (T1_CH0; T1_CH1; T1_CH2).
  2. I initialised 2 channels channel 0 and 1.
  3. I have set: channel 0 in waveform mode with master clock (MCK/2 which is 96/2 for due) with trigger on RC compare. After Timer 1 channel 0 is done counting it generates a output TIOA0 and this is fed input to Timer 1 channel 1 .
  4. Set Channel 1 with same parameters as channel 0 (waveform mode, rc compare ) then set the input as TIOA0 instead of master clock.
  5. Then simply print the counter value. (check pg. 861 Clock chaining diagram from datasheet SAM3X / SAM3A Series )

Hi parnal,

The following sets up the 16-bit TC3 timer at 48MHz and gets it to overflow every microsecond (1MHz). On each overflow the timer outputs an event that is received by the 32-bit TC4 (with TC5 as the slave). TC4 is set up to increment each time it receives an event, in other words it's clocked at 1MHz.

There appears to be an error in the SAMD21 datasheet. It says that TC4 is chained with TC3 for 32-bit mode, in reality however it's chained to TC5.

This code does the same as the Arduino Due TC timers:

// Setup TC3 to clock TC4/TC5 in 32-bit mode at 1MHz using the event system 

uint32_t timeMicros;

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
  
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by 1 = 48MHz
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4
  
  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                      EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                      EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_OVF) |         // Set event generator (sender) as TC3 overflow
                      EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0                                

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

  REG_TC3_EVCTRL |= TC_EVCTRL_OVFEO;              // Enable an event output on overflow of the TC3 timer

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

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

  REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC3 into match frequency (MFRQ) mode   
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

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

  timeMicros = micros();
}

void loop()
{
  SerialUSB.print(REG_TC4_COUNT32_COUNT);       // Output the results
  SerialUSB.print(F("   "));
  SerialUSB.println(micros() - timeMicros);
  
  //SerialUSB.println(REG_TC3_COUNT32_COUNT);     // Test timer TC3
}

Hii Martin,

Works perfect. Thanks a lot :slight_smile:

Thanks Martin,

I understand your point using MFREQ.

But it would be simplest for me to stop / restart the TCCount when it is appropriate.

If it is more complex to do so, tell me how to calculate the elapsed time of the TC : For example what is the time for your 1919 example ?

Kind regards

Daniel

Hi Daniel

The TC timer is simply counting the number of input pulses. So the time elapsed depends on how fast you're clocking the input and if that input's regular or not. If there's no input, the counter will stop counting.

The match frequency mode just resets the TC timer back to 0 when it's COUNT register matches the value in the CC0 register.

Taking the 1919 value in the example with a regular 40kHz PWM input gives you:

Time elapsed = (1 / 40000) * (1919 + 1) = 48ms

Add 1 to the 1919 to take into account that we're counting from 0.

If you want to stop the timer in your code:

REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_STOP;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);

...and to restart...

REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_RETRIGGER;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);

Kind regards,
Martin

Great MartinL, in fact I realised this calculation afterwards !

The point is that I can get noisy frequencies and then TC elapsed time can be different from one bit to another.

So I prefer to control the start / stop.

I'll try this and give you my results back

Many thanks

Kind regards

Daniel

Hi MartinL

It's working PERFECTLY !!! (RTC, I2C, Native port, TCC, TC, millis etc...) !!

Using "restart timer" at the relevent place, it's OK !

Thank you very much again,

Kind Regards

Daniel

Hi guys,

I hope someone can help me.
I am using a MKR1010 board with a SAMD21 and I am trying to change the frequency for the attachInterrupt function to use the external interrupt. I have a receiver on a pin and the external interrupt function is called via attachInterrupt(pin, ISR, CHANGE) to find a valid signal. I think that I need to change the frequency of the EIC (External Interrupt Controller). The SAMD21 uses 48MHz and I would need 16Mhz for the external interrupts. How can I change the frequency? I have no experience in changing the registers :frowning:

Kind Regards
Alex

Hi Alex,

What's the minimum pulse width and minimum/maximum period of the signal you're receiving?

Is the period (frequency) regular, or are the pulses generated at random or irregular intervals?

The reason I ask, is that if your requirement isn't too stringent then you could just use the attachInterrupt() function and measure the time between the edges using micros(). This is also good if the pulses occur at irregular intervals.

If however you require greater timing resolution then using interrupts connected to an internal timer via the SAMD21's event system is the way to go.

Hi Martin,

thank you for your fast reply :slight_smile:
I am using the NewRemoteSwitch Library for 433Mhz signals. I have used that library with the Arduino Uno and YUN and it works great. I do not know exactly the signal width and frequency. The only thing I know is that the attachInterrupt function for external interrupts works with the Uno and YUN like a charm. Both of them are based on 16MHz oscillator. I tried to slow down the interrupt function of the MKR1010 with a fixed timer5 library and called the attachInterrupt function inside the timed based interrupt function. It worked but not reliable enough. It seems that this was the evidence that a frequency change could solve the problem. What do you think about that?

Thank you very much for your help!

Best regards
Alex

Hi Martin,

this is what I found out about the signal:

Protocol. (Copied from Wieltje, Klik aan Klik uit protocol - Forum - Circuits Online,
but with slightly different timings, as measured on my device.)
_ _
'0': | || |_____ (T,T,T,5T)
_ _
'1': | |_____| |
(T,5T,T,T)
_ _
dim: | || | (T,T,T,T)
T = short period of ~260µs. However, this code tries
to figure out the correct period
A full frame looks like this:

  • start pulse: 1T high, 10.44T low
  • 26 bit: Address
  • 1 bit: group bit
  • 1 bit: on/off/[dim]
  • 4 bit: unit
  • [4 bit: dim level. Only present of [dim] is chosen]
  • stop pulse: 1T high, 40T low

Hi Alex,

Does the attachInterrupt interrupt service routine (ISR) funcion in Uno/Yun library happen to use the micros() function?

The reason I ask is because in the past I've found that the micros() function doesn't work in the ISR on the Arduino Zero. This might explain way it works on the Uno/Yun and not on the Zero.

Kind regards,
Martin

Hi Martin,

ohh thank you for the hint.
Yes the library I am using, uses the micros function:

It is called in the void NewRemoteReceiver::interruptHandler() function.

Do you know how it can be handled with the MKR1010? I think the processor is the same SAMD21 as the ZERO uses?

Thank you very much!

Best regards,
Alex

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!