Configuring TC module to count pulses from external input

Hello,

I have an Adafruit Feather M0 board, and I would like to configure its timers (at least one for now) to count pulses from an external source. I am using Arduino IDE 1.6.9. Just a quick note, Adafruit Feather M0 uses the same microprocessor as the Arduino Zero, except the the former do not have the EDBG chip on the board.

I found this code from MartinL posted here and modified it a little bit to try to configure the TC to count the pulses feed through pin 12 of the Feather M0 board.

void setup()   {                
  Serial.begin(115200);
  
  delay(7000);  //have ample time to catch text in serial monitor
  
  Serial.println("My Project");

  pinMode(DCDC_EN, OUTPUT);
  
  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 x
                    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
  
  digitalWrite(DCDC_EN, 0);                                               //Turn off sensor power
  delay(3000);

  NVIC_DisableIRQ(EIC_IRQn);
  NVIC_ClearPendingIRQ(EIC_IRQn);
  NVIC_SetPriority(EIC_IRQn, 0);
  NVIC_EnableIRQ(EIC_IRQn);

  // Enable GCLK for IEC (External Interrupt Controller)
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));

  // Choose EIC external interrupt to use
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  
  // Enable EIC
  EIC->CTRL.bit.ENABLE = 1;
  while (EIC->STATUS.bit.SYNCBUSY == 1) { }

  uint32_t ulPin = 12;                // Board pin to use
  EPioType ulPeripheral = PIO_EXTINT;
  
  if ( g_APinDescription[ulPin].ulPin & 1 ) // is pin odd?
  {
    uint32_t temp ;

    // Get whole current setup for both odd and even pins and remove odd one
    temp = (PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg) & PORT_PMUX_PMUXE( 0xF ) ;
    // Set new muxing
    PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg = temp|PORT_PMUX_PMUXO( ulPeripheral ) ;
    // Enable port mux
    PORT->Group[g_APinDescription[ulPin].ulPort].PINCFG[g_APinDescription[ulPin].ulPin].reg |= PORT_PINCFG_PMUXEN ;
  }
  else // even pin
  {
    uint32_t temp ;

    temp = (PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg) & PORT_PMUX_PMUXO( 0xF ) ;
    PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg = temp|PORT_PMUX_PMUXE( ulPeripheral ) ;
    PORT->Group[g_APinDescription[ulPin].ulPort].PINCFG[g_APinDescription[ulPin].ulPin].reg |= PORT_PINCFG_PMUXEN ; // Enable port mux
  }
  
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3

  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_TC3_EVCTRL |= TC_EVCTRL_TCEI |              // Enable the TC event input
                    /*TC_EVCTRL_TCINV |*/         // Invert the event input
                    TC_EVCTRL_EVACT_COUNT;          // Set up the timer for counting events
                   
  REG_TC3_READREQ = TC_READREQ_RREQ |             // Enable a read request
                    TC_READREQ_ADDR(TC_COUNT16_COUNT_OFFSET);        // Offset of the COUNT register
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization

  REG_TC3_CTRLBCLR = TC_CTRLBCLR_DIR;                     // Set register for counter to count up
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (write) synchronization

  REG_TC3_COUNT16_COUNT = 0x0000;               // Clear timer's COUNT value
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for synchronization
  
  REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set timer prescaler
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  Serial.println("Done with TC init");
  delay(1000);                        //wait for some delay before turning on power to sensors
    
  timer = millis();
  digitalWrite(DCDC_EN, 1);           
  delay(1000);                        //wait for some delay for power to stabilize
}

void loop() {
  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis())  timer = millis();
  
  if (millis() - timer > 100) { 
    timer = millis(); // reset the timer

    ReadCountValTC3();  //print COUNT register value every ~100ms.
  }
}

void ReadCountValTC3()
{
  static uint32_t sampleCntr=0;
  
  if(TC3->COUNT16.INTFLAG.bit.OVF && TC3->COUNT16.INTENSET.bit.OVF)
  {
    Serial.println("OVF!");
  } 
  else
  {
    REG_TC3_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(TC_COUNT16_COUNT_OFFSET);      // Offset address of the COUNT register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
    Serial.println(TC3->COUNT16.COUNT.reg, DEC);
    TC3->COUNT16.COUNT.reg = 0x0000;
//    REG_TC3_COUNT16_COUNT = 0x0000;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for synchronization
  }
}

What I struggle with is understanding how to configure the EVENT system, the EIC, GCLK, and PORT (MUX's), so I the signal from the pin where I feed the pulses to can propagate to the TC3 timer module. I read these modules in SAMD21 datasheet but I personally think the information there is just not enough for me to be able to manipulate the registers to do what I wanted to do. Or maybe it's just me who thinks it is what it is.

Any help from you guys would be greatly appreciated. Thanks a lot!

-Ruch

@Ruch,

My guess is there are not too many 'feathers' out with our forum members which is why no one is answering.

My previous experience with Adafruit forums has not been a great experience, but you did pay a premium for your board and a portion of that cost is for technical support... maybe you will get lucky if you ask in the Adafruit forum.

good luck,

Ray

Hello Mister Ruch.

After much reading and consult I have implemented code to count pulses form external sources applied to Arduino M0 board.

#define PinLED  13


void setup() {
  // put your setup code here, to run once:
  
  SerialUSB.begin(19200);                   // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready

  pinMode(PinLED, OUTPUT);
  digitalWrite(PinLED, LOW);
  
 
  REG_PM_APBCMASK |=  PM_APBCMASK_EVSYS;    // Switch on the event system peripheral
  PM->APBCMASK.reg |= PM_APBCMASK_TCC0;     // Enable TCC0 Bus clock (Timer counter control clock)


  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK0 |     // .... on GCLK0...
                     GCLK_CLKCTRL_ID_EIC;         // ... to feed the GCLK0 to EIC peripheral
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
  
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0...
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");
  

  /* - Configuración del EIC - */
  
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;         // Enable event from pin on external interrupt 3 (EXTINT03)
  attachInterrupt(12, NULL, RISING);              // Attach external interrupt to digital pin 12 (external interrupt 3)
  SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");
  
  
  /* - Configuración del EVSYS - */
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel n=0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 1
  SerialUSB.println("Configuración EVE_SYS_USER realizada");

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event output 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
  SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");


  /* - Configuración de TCC0 - */

  REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE;             // Disable TCC0 peripheral
  SerialUSB.println("Comenzando configuración de TCC0");

  REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;          // Clear DIR bit to count up
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for (write) synchronization
  SerialUSB.println("Configuración TCC0 CTRLB realizada");

  REG_TCC0_EVCTRL |= TCC_EVCTRL_TCEI0 |           // Enable the TCC event 0 input
                     //TCC_EVCTRL_TCEI1 |         // Enable the TCC event 1 input
                     //TCC_EVCTRL_TCINV1 |        // Invert the event 1 input
                     TCC_EVCTRL_EVACT0_COUNT;     // Set up TCC timer/counter to count on event
  SerialUSB.println("Configuración TCC0_EVCTRL realizada");
  
  REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  SerialUSB.println("Módulo TCC0 habilitado");

}


void loop() {
  // put your main code here, to run repeatedly:

  delay(1000);  // Wait one second

  PORT->Group[PORTA].OUTTGL.reg = PORT_OUTTGL_OUTTGL(1<<17);  // Toggle PORT_PA17 (Pin 13 = LED)
  //REG_PORT_OUTTGL0 = PORT_PA17;

  REG_TCC0_CTRLBSET = 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.print("TCC0 count: ");
  SerialUSB.println(REG_TCC0_COUNT, DEC);         // Print the result

}

For incoming pin you must select a pin with external interrup (EXTINTxx) capability and attach an external interrupt ( attachInterrupt() ) with arduino or Feather M0 pin number to complete EIC configuration. variant.cpp file has pin-out connection between arduino pin and SAMD21Gxx chip, you can find it in C:\Users\YourName\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.11\variants\arduino_mzero\variant.cpp

Vladimir Zúñiga.

Hi Petirrojo31, I tried your code but I always get "TCC0 count: 0" I tried setting the pulldown for the D12 pin but no success. Any idea what could be missing? Does that code work right for you?
Thanks!
Roberto

Hi roberto_in,

Here's some code that'll capture and display the period and pulse width of an input signal on D12, using timer TCC0.

The code is designed to work with an RC receiver with an accuracy of 1us. Greater resolution is possible by running the TCC0 timer faster, (than the 1MHz used in this example):

// 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
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |    // Divide the 48MHz system clock by 3 = 16MHz
                     GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  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 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*/

  GCLK->CLKCTRL.reg = 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

  // Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
  
  //attachInterrupt(12, NULL, HIGH);                                       // Attach interrupts to digital pin 12 (external interrupt 3)
  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.reg |= EIC_CTRL_ENABLE;                                        // 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_1);              // Set the event user (receiver) as timer TCC0, event 1

  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

  TCC0->EVCTRL.reg |= 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)
 
  TCC0->INTENSET.reg = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                       TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  TCC0->CTRLA.reg |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                     TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                     TCC_CTRLA_PRESCALER_DIV16;      // Set prescaler to 16, 16MHz/16 = 1MHz               
                     
  TCC0->CTRLA.bit.ENABLE = 1;                        // 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 = TCC0->CC[0].reg;                 // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

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