Frequency measurement on Seeeduino Xiao at 200kHz

Hi hussat,

I've modified the code so that it should work with D6 as the input on the Xiao. I've tested the code on my custom SAMD21 board with 200kHz pulses, but using a different input pin.

The code receives the incoming pulses on D6 and routes them to the TCC0 timer via the EIC (External Interrupt Controller) and the Event System, (a 12-channel peripheral-to-peripheral highway). The TCC0 is configured to simply count the number of input events or pulses.

The TCC2 timer meanwhile is clocked from the on-board 32.768kHz external crystal and uses this to time a period of 1 second, over which the number of pulses are counted.

Calling the startConversion() function retriggers both timers. When the 1 second has elapsed, the TCC2's interrupt service routine is called and number of pulses counted by TCC0 is displayed on the Serial console.

I appreciate that this code is somewhat different from your AVR code. It's just that the SAMD21 operates in a completely different way.

Here's the code:

// Count the number of pulses on pin D6 (PB08) over a 1 second period
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_TCC0_TCC1;    // As a clock source for TCC0 and TCC1
  
  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
  //////////////////////////////////////////////////////////////////////////////////////////////     
                    
  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->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable oneshot operation
  while(TCC2->SYNCBUSY.bit.CTRLB);                   // Wait for synchronization
  
  NVIC_SetPriority(TCC2_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC2 to 0 (highest) 
  NVIC_EnableIRQ(TCC2_IRQn);         // Connect TCC2 to Nested Vector Interrupt Controller (NVIC)

  TCC2->INTENSET.reg = TCC_INTENSET_OVF;             // Enable overflow (OVF) interrupts on TCC2

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

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

  PORT->Group[PORTB].PINCFG[8].bit.PMUXEN = 1;                             // Enable the port multiplexer on port pin PB08 (D6)
  PORT->Group[PORTB].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_A;                // Set-up PB08 (D6) as an EIC (interrupt)
  
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO8;                                 // Enable event output on external interrupt 8
  EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE0_HIGH;                            // Set interrupt to detect a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT8;                                // Clear the interrupt flag on channel 8
  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_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 0
                    
  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_8) |    // Set event generator (sender) as external interrupt 8
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 |              // Enable TCC0 event 0 inputs
                     TCC_EVCTRL_EVACT0_INC;          // Increment the TCC0 counter on receiving an event 0
                                                                
  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization 
}

void loop()
{
  startConversion();                                       // Start a conversion over the 1 second integration window 
  delay(2000);                                             // Wait for 1 second
}

void startConversion()
{  
  TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer TCC2 to start the integration window 
  while (TCC2->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer TCC0 to start the count 
  while (TCC0->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization
}

void TCC2_Handler()
{
  TCC2->INTFLAG.bit.OVF = 1;                               // Clear the TCC2 overflow interrupt flag
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;          // Trigger a read synchronization on the COUNT register
  while (TCC0->SYNCBUSY.bit.CTRLB);                        // Wait for the CTRLB register write synchronization
  while (TCC0->SYNCBUSY.bit.COUNT);                        // Wait for the COUNT register read sychronization
  SerialUSB.println(TCC0->COUNT.reg);                      // Print the result
}
3 Likes