Counting random pulses on SAMD51

I'm trying to build a random pulse counter (rising edge) for the SAMD51/M4.
The input pulses are ~30ns in width, and can be anything up to 10mhz (10 million random pulses per second).

I am using a Feather M4 Express board.

The idea is to use external interrupt pin A0 (PA02 = EXT_INT_2) as input, so it counts without the MCU intervention (asynchronous), and store the counts in TCC0 for random reading at any time. The input signal could be another pin and another counter (but need 24 bit counter or as large as possible).

AFAIK the EVSYS is needed for this task to avoid loading the MCU (ie trying to count using traditional interrupts which have an upper limit of ~1mhz counting bandwidth). Eventually I need to make several counters from different input pins, on the same MCU (use TCC0 & TCC1 for 24 bit, and then use 16 bit TC counters together to form 32bit counters).

I've come up with the code below based mostly on posts by MartinL in this thread (also this thread was somewhat useful).
The first link above is for a pulse counter which works perfectly on SAMD21/M0. The trick is to port this to SAMD51 which seems to be quite different in setting up the event system and the counters to pick up the rising edges of the input signal.

Not sure what I'm missing, any help would be greatly appreciated!

//Board: Adafruit Feather M4 Express
//Below is an example that sets up TCC0 (24 bit) to count input events on A0(PA02 = EXT_INT_2).
//The TCC0 counter value is output to the console:
void setup()
{
  Serial.begin(115200);
  delay(1000);

  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS; //// or: MCLK->APBBMASK.bit.EVSYS_ = 1; // Switch on the event system peripheral
  MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC;   //not needed?
  MCLK->APBBMASK.reg |= MCLK_APBBMASK_TCC0;  //not needed?

  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 

  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0/TCC1 peripheral channel - PCHCTRL[25] is TCC0, TCC1
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0/TCC1

  // Enable the port multiplexer on A0 input
  PORT->Group[g_APinDescription[A0].ulPort].PINCFG[g_APinDescription[A0].ulPin].bit.PULLEN = 0;
  PORT->Group[g_APinDescription[A0].ulPort].PINCFG[g_APinDescription[A0].ulPin].bit.INEN = 1;
  PORT->Group[g_APinDescription[A0].ulPort].PINCFG[g_APinDescription[A0].ulPin].bit.PMUXEN = 1;

  // Set-up the pin as an EIC (interrupt) peripheral D2 A(0)
  PORT->Group[g_APinDescription[A0].ulPort].PMUX[g_APinDescription[A0].ulPin >> 1].reg = PORT_PMUX_PMUXE(0) | PORT_PMUX_PMUXO(0);

  //attachInterrupt(A0, NULL, RISING);            // Attach interrupts to digital pin 2 (external interrupt 2)
  EIC->CTRLA.bit.ENABLE = 0;
  //EIC->INTENCLR.bit.EXTINT = (1<<2);
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO(2);      // Enable event output on external interrupt 2
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_HIGH;   // Set event on detecting a HIGH level
  EIC->CTRLA.bit.ENABLE = 1;                      // Enable EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);               // Wait for synchronization

  EVSYS->USER[EVSYS_ID_USER_TCC0_EV_0].reg = EVSYS_USER_CHANNEL(1); // Attach the event consumer (receiver) to channel 0 (n + 1)

  EVSYS->Channel[0].CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |    // No event edge detection is required in asynchronous mode
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                  // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_2);    // Set event generator (sender) as external interrupt 2
                       //| EVSYS_CHANNEL_CHANNEL(0); implied above???     // Attach the generator (sender) to channel 0

  TCC0->EVCTRL.reg |= TCC_EVCTRL_TCEI0 |           // Enable the TCC event 0 input
                      TCC_EVCTRL_EVACT0_COUNTEV;   // Set event 0 to count on incoming events

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NFRQ;   // Set the TCC0 timer counter to normal frequency mode
  while (TCC0->SYNCBUSY.bit.WAVE);          // Wait for synchronization

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

void loop()
{
  //read TCC0 continuously
  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
  Serial.println(TCC0->COUNT.reg);                   // Output the TCC0 COUNT register
  delay(100);
}

Hi DonEmi,

The code on the following thread on reply #5 can be used to read random pulses using the Adafruit Feather M4: Best microcontroller for frequency counter up to 30MHz - Microcontrollers - Arduino Forum.

It generates a 30MHz test signal on digital pin D10 and counts the incoming pulses asynchronously on analog pin A4. It uses the External Interrupt Controller (EIC) and Event System to route the pulses to the TC0 (plus TC1) timer in 32-bit mode.

Should you require a different pin, it's possible to reconfigure the EIC to receive the signal on almost any of the Feather M4's inputs.

You can use the EIC to route signal edges to counters (arbitrary even receivers, I guess) without actually causing an interrupt?! (and therefore, doing things with them much more quickly than you could handle interrupts.)

That seems pretty neat...

Dear MartinL,

Most excellent example, exactly what I needed, thank you!
It works very well with the 30mhz input from D10.
( I tried and googled for days, how did I not find it!! ::slight_smile: )

I will have to do more testing with the fixture that has the specific 30ns pulses which are very random and its more difficult to tell how many actual pulses were in a given time frame. But I think this at least gives me a great head start.

I see there are 8 TC counters on the SAMD51.
Do you think it's possible to achieve 4 total 32bit timers using the TC counters? (from 4 different pins)
The datasheet specifies 16bit is default for the TCs but doesnt specifically say they are all 16bit capable, or maybe I missed it.

Also, I'm asking because some peripherals in the Arduino core might be using TC counters, I am not sure if that's true and which TCs might be used.

I see there are 8 TC counters on the SAMD51.
Do you think it's possible to achieve 4 total 32bit timers using the TC counters? (from 4 different pins)
The datasheet specifies 16bit is default for the TCs but doesnt specifically say they are all 16bit capable, or maybe I missed it.

The SAMD51J19A variant used on the Adafruit Feather M4 has a total of 6 TC timers, TC0 through to TC5. Larger variants such as the SAMD51N and SAMD51P contain the full complement of 8 TC timers. Pairs of 16-bit TC timers, TC0-TC1, TC2-TC3 and TC4-TC5 can chained together to form 32-bits.

This is achieved by simply setting the even numbered TC timer to 32-bit mode and accessing it through this (even numbered) timer's registers:

TC0->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32;              // Set-up TC0/TC1 timers in 32-bit mode

In addition the SAMD51 on the Feather M4 has 5 TCC counter/timers, TCC0 through to TCC4. TCC0 and TCC1 are 24-bit, while the rest are 16-bit. While it's not possible to chain them like the TC timers, it is possible to get a TCCx timer to generate an event on overflow and have a second TCCx timer count upon receiving this event. It should then be possible to read the COUNT registers of both timers, shift the high order word of second timer to the left, then logically OR the result into a uint64_t data type.

Also, I'm asking because some peripherals in the Arduino core might be using TC counters, I am not sure if that's true and which TCs might be used.

The Arduino timing functions such as micros(), millis() and delay() use the microcontorller's "systick" or system tick timer, so don't interfere with anything. Some Arduino functions such as analogWrite() and servo library use TC timers, but provided you don't call these functions in your sketch, they're free to use as you wish.

You can use the EIC to route signal edges to counters (arbitrary even receivers, I guess) without actually causing an interrupt?! (and therefore, doing things with them much more quickly than you could handle interrupts.)

That seems pretty neat...

The SAMD51 datasheet was a bit vague on the specific detail, but it does seem possible to route external signals asynchronously through the EIC peripheral and on to the event system without the interrupt system getting in the way.

At first I thought it's a somewhat a convoluted approach, however, once the external signal is on the event system, I guess it opens up a whole set of possibilities for triggering various on-chip peripherals with minimal CPU intervention.

Althought I haven't tested it, I think it should be possible to do the same on the SAMD21 as well.

It looks like you might also get something similar on Mega0/Tiny0/Tiny1 except they have the pins be event generators directly (via PORT0_PINn and similar), rather than going through the EIC (the EIC is rather different on AVR.) And/or the CCL?

Hmm...

MartinL:
The SAMD51J19A variant used on the Adafruit Feather M4 has a total of 6 TC timers, TC0 through to TC5. Larger variants such as the SAMD51N and SAMD51P contain the full complement of 8 TC timers. Pairs of 16-bit TC timers, TC0-TC1, TC2-TC3 and TC4-TC5 can chained together to form 32-bits.

MartinL,

Thank you for all your help.

I was able to make 2 separate counters running in parallel, one for a 32bit TC0 and one for a 24bit TCC0.
I believe this can be expanded to add TC2 and TC4 also, as 32bit counters.

General purpose PWM is also required so once I do that I have to figure out how to use the remaining TCC counters for that purpose, since they are more feature rich anyway for finer PWM control.