ISR Timer Capture on SAMD21

void Init()
{  
  //Setup Timer 1 - counts events on pin D5
  TCCR1A = 0;                       //reset Timer 1            
  TCCR1B = (1<<ICES1) + (1<<CS12);  //start Timer 1 and interrupt with noise cancel
  TIMSK1 = (1<<ICIE1);              //Interrupt on Capture (ICIE1)
}

volatile uint16_t tim;

//Timer1 capture interrupt, rising edge on D8
ISR (TIMER1_CAPT_vect)
{
  tim = ICR1;
}

I have this capture code from Arduino Mega here. Does anyone know how to do this on the SAMD? I'm a beginner on the platform. Thanks!

Hi rsardu,

I feel that you first need to explain what you're trying to achieve with your current code.

There's no direct one-to-one translation, between timer code on the Atmega2560 and the SAMD21.

I want to count pulses on a pin (infinite, high frequency e.g. 20 MHz). Another pin triggers captures (read out the counter value into a volatile variable). The counter runs infinite.

Hi rsardu,

Just to clarify, you require one input receiving a 20MHz high frequency signal and a second trigger pin to read out the counter's value?

I was just wondering over what sort of time period you intend to measure the count? It's just that the SAMD21's timers are finite and will rollover (overflow) back to zero, once their maximum count value is exceeded.

If for example, your using the SAMD21's TC4/TC5 32-bit timer/counter to count the pulses in a 20MHz signal, the timer will overflow in:

2^(32) / 20MHz = 214.75 seconds

...or in other words, after around 3 and a half minutes of operation.

Just to clarify, you require one input receiving a 20MHz high frequency signal and a second trigger pin to read out the counter's value?
Yep!!!

No, the 32 bit counter will rollover after approx. 4.6 ms. (20*10e6 / 2^32). But I don't care about the absulute value, because I need the differences within the micro second range. A 32 bit counter is sufficient for that. Unfortunately, the AVR chips can't count such high frequencies and now I don't know how to set the ports and timers on the SAMD cortex arch.

Hi rsardu,

No, the 32 bit counter will rollover after approx. 4.6 ms. (20*10e6 / 2^32).

If you think about it, 2^32 - 1 = 4,294,967,295. This is the 32-bit timer/counter's maximum value.

It's counting a signal that's producing 20,000,000 pulses a second.

Therefore in one second, the timer/counter has counted to: 20,000,000, in two seconds: 40,000,000 and so on...

...after 214.75 seconds ((2^32 - 1) / 20MHz), the timer/counter has reached a value of 4,294,967,295, it maximum value and overflows.

Here's the code, it generates an 1 second output trigger on D11 (PA16). If you then connect this to the trigger pin input on D10 (PA18), it will trigger a measurement every second. Pin D12 (PA19) meanwhile counts the input pulses from an external source.

// Count the number of pulses on pin D12 (PA19) using 1 second test trigger pulse on D11 (PA16) 
// for a trigger input on D10 (PA18)
volatile boolean countRead = false;                // Count read flag
volatile uint32_t count;                           // Variable to hold the TCC0 count value

void setup()
{
  SerialUSB.begin(115200);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  ////////////////////////////////////////////////////////////////////////////////////////
  // Generic Clock Initialisation 
  ////////////////////////////////////////////////////////////////////////////////////////

  GCLK->GENDIV.reg =  GCLK_GENDIV_DIV(1) |          // Select clock divisor to 1                     
                      GCLK_GENDIV_ID(4);            // Select GLCK4         
 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |            // Set the duty cycle to 50/50 HIGH/LOW 
                      GCLK_GENCTRL_GENEN |          // Enable GCLK                   
                      GCLK_GENCTRL_SRC_XOSC32K |    // Select GCLK source as external 32.768kHz crystal (XOSC32K)                          
                      GCLK_GENCTRL_ID(4);           // Select GCLK4             
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |          // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK0 |      // GCLK0 at 48MHz 
                      GCLK_CLKCTRL_ID_TC4_TC5;      // As a clock source for TC4 and TC5
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |          // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |      // GCLK4 at 32.768kHz
                      GCLK_CLKCTRL_ID_TCC2_TC3;     // As a clock source for TCC2 and TC3

  //////////////////////////////////////////////////////////////////////////////////////////////
  // TCC2 Initialisation - reference timer: measures a 1s period
  //////////////////////////////////////////////////////////////////////////////////////////////     
                    
  PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 1;                             // Enable the port multiplexer on port pin PA16 (D11)
  PORT->Group[PORTA].PMUX[16 >> 1].reg |= PORT_PMUX_PMUXE_E;                // Set-up PA16 (D11) as an EIC (interrupt)
  
  TCC2->PER.reg = 32767;                             // Set the period (PER) register for a PWM period of 1s
  while (TCC2->SYNCBUSY.bit.PER);                    // Wait for synchronization

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set timer to Normal PWM mode (NPWM)
  while (TCC2->SYNCBUSY.bit.WAVE);                   // Wait for synchronization

  TCC2->CC[0].reg = 16384;                           // Set the duty cycle to 50%
  while (TCC2->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC2->CTRLA.bit.ENABLE = 1;                        // Enable TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

  ////////////////////////////////////////////////////////////////////////////////////////
  // TC4 Initialisation - measurement counter: counts the number incoming of pulses
  ////////////////////////////////////////////////////////////////////////////////////////

  PORT->Group[PORTA].PINCFG[19].bit.PMUXEN = 1;                            // Enable the port multiplexer on port pin PA19 (D12)
  PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_A;               // Set-up PA12 (D12) as an EIC (interrupt)
  
  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->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Clear the interrupt flag on channel 3
  EIC->CTRL.bit.ENABLE = 1;                                                // 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_TC4_EVU);                // Set the event user (receiver) as timer TC4 event 
                    
  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

  TC4->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI |         // Enable TC4 event input
                            TC_EVCTRL_EVACT_COUNT;   // Increment the TC4 counter upon receiving an event
                                                                
  TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32;    // Chain TC4 with TC5 to create a 32-bit timer

  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                 // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);          // Wait for synchronization 

  TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT |      // Enable a continuous read request
                             TC_READREQ_ADDR(0x10);   // Offset of the 32-bit COUNT register

  attachInterrupt(10, trigger, RISING);              // Initialise the trigger pin on D10
}

void loop()
{
  while(!countRead);                                 // Block until the trigger() function returns
  countRead = false;                                 // Reset the count read flag
  SerialUSB.println(count);                          // Print the result
}

void trigger()
{
  count = TC4->COUNT32.COUNT.reg;                    // Read the COUNT register
  countRead = true;                                  // Set the count read flag
}

I tested the D12 (PA19) input with a 21MHz signal from my Arduino Due, here's the results:

1267
21000487
41999707
62998928
83998148
104997368
125996588
146995808
167995028
188994249
209993469
230992688
251991909

If you subtract one result from the next, you can see it measures the 21MHz signal relatively well. Not too bad for a 48MHz microcontroller and better than I expected.

1 Like

Great. I couldn't have done it alone. Many thanks Martin. It's amazing how much more code is needed compared to AVR. I did the test with 10MHz. Counts very nice:

17:03:15.628 -> 4255272992
17:03:16.641 -> 4265272992
17:03:17.653 -> 4275272992
17:03:18.632 -> 4285272992
17:03:19.644 -> 305696
17:03:20.623 -> 10305696
17:03:21.636 -> 20305696
17:03:22.648 -> 30305696
17:03:23.627 -> 40305696
17:03:24.639 -> 50305696
17:03:25.652 -> 60305696
17:03:26.631 -> 70305696
17:03:27.663 -> 80305695
17:03:28.659 -> 90305696
17:03:29.657 -> 100305695

Hi rsardu,

It's amazing how much more code is needed compared to AVR.

Yes, unlike the AVR microcontrollers, there's unfortunately no easy way to route an input to a timer.

Hello Martin,

your working example is using these pins:
a) Count the number of pulses on pin D12 (PA19)
b) using 1 second test trigger pulse on D11 (PA16)
c) for a trigger input on D10 (PA18)

I would like to use these pins for the SPI, as these are provided as default for SPI (D10, D11, D12, D13).

I could put D10 on D5 and it works. --> attachInterrupt(5, trigger, RISING); //D5=PA15
I don't need D11 (1 Hz).

Can I put D12 on D6? (D2..D9, A0..A5) would be free. The pin probably works with other interrupts, channels, EIC etc. I am not familiar with the settings. What changes do I have to make? Many thanks!

Hi rsardu,

Can I put D12 on D6? (D2..D9, A0..A5) would be free. The pin probably works with other interrupts, channels, EIC etc. I am not familiar with the settings. What changes do I have to make?

Yes, it's possible to route any interrupt pin through to any timer, via the event system. The information for what interrupt channel to use can be found from Arduino Zero schematic (attached) and the SAMD21's Port Multiplexing table in section 7.

From the schematic (on sheet 2) it's possible to find the port pin for D6, it PA20.

In the port multiplexing table, row PA20, column A, the external interrupt is on channel 4: EXTINT[4]. As PA20 is an even port pin, we select the multiplexer switch for position A (EIC) on the even port: PORT_PMUX_PMUXE_A.

Just change the following lines of code and the input should work on D6:

  PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;                            // Enable the port multiplexer on port pin PA20 (D6)
  PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_A;               // Set-up PA20 (D6) as an EIC (interrupt)
 
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4;                                 // Enable event output on external interrupt 4
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4;                                // Clear the interrupt flag on channel 4
  EIC->CTRL.bit.ENABLE = 1;                                                // 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_TC4_EVU);                // Set the event user (receiver) as timer TC4 event
                   
  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_4) |    // Set event generator (sender) as external interrupt 4
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

Arduino-Zero-schematic.pdf (779 KB)

Thank you Martin, works great!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.