Using the ATSAMD51 to measure a fast 1usec pulse width

I need to measure a 1usec one-time pulse using an Adafruit Metro M4 Express.
Input on pin D9 or whatever works.
I would like to setup an input capture on pin D9 that on the rising edge would start a timer and on the falling edge would stop the timer. Once it indicates the event is complete, I would print the captured value converted to time to the serial port.
At a 120MHz clock, a 1us pulse would give a count of 120 give or take.
Each count would be around 8.33nsec. so I will have good resolution for this measurement.

I have been able to work through other peoples SAMD51 code examples and basically make out what's going on but have not been very successful doing it on my own yet. All the PORT, Peripheral, GCLK, MCLK and TCCx mapping is very confusing so far! But I am improving!
Is there a dummies book on how to get started with this microcontroller that has good working examples? Anything would help!

Anyway thanks for your time and your help!!

SAMD is not suited for beginners reading documentation. That said, you could just do the usual thing and use the pin-change-interrupt on said pin. If rising, save timer value. If falling calculate differene, and you are done.

Thanks zwieblum for the quick response!
I was thinking that the interrupt system would be to slow to handle the fast pulses?
I was hoping there was a way to control the timers directly by mapping the port pin directly to the timer/counter's control logic and bypass any third party interaction slowing it down?
Just open the gate and close the gate on the edges.

On the other note, if the documentation and experimenting is not the way to go what would you suggest?
Did you take a class or a seminar to get started on the SAMD? Was it from Microchip?

Again thanks for your time and help!

Well, the documentation is the right way to go, but it has ~ 2000 pages. "Port I/O Pin Controller" section has hints on EVSYS, but only for output, not input. A beginner will most likely be a bit surprised on the involvement needed to understand that documentation. Atmega328p is only ~ 250 pages, so that's manageable even for beginners.

Again thanks for the responce!
I have written a lot of code for the Atmeg328p basicaly for the Arduino UNO.
Most of it with internal reg. manipulation so I know how that one works.
Its just not fast enough for what I am trying to measure at 16MHz so I moved up to the SAMD at 120MHz.
So yes, I am a beginner on the SAMD but have written a lot of code over the years. As all new things, its a bit confusing and was hoping for some insite on how to do what I need so as to not waist a lot of time running down dead ends. If it can't be done that saves me a lot of time. But if it can be done that would be great! Being as yet unfamiler with all the internal workings of the SAMD I was hoping for the shortest learning path to succsess.
I will keep digging!
Thanks again!

Here is what I was looking at to give me some hope.
Section 49.8.9 Event Control for the TCC Timer/counter for control Applications

Event Control EVCTRL reg.
** Bits 5:3 – EVACT1[2:0] Timer/Counter Event Input 1 Action**
0x3 STOP, STOP TC on event
and
** Bits 2:0 – EVACT0[2:0] Timer/Counter Event Input 0 Action**
0x3 START, START TC on event

I just don't see how to map them together onto one port pin as in input event to the TCC.

Thanks

I could be wrong, but I don't think it will work, the docs only talk about output, not input:

Anyway, your ISR is at best just one instruction. IMO there should be no timing problems.

I will give the ISR a try and see if I can make that work.
Thanks for your help with this and I will let you know how it goes.
Thanks again!!

Surely the timers provide "capture" ability that is simpler than needing to figure out how the "event system" (and perhaps CCL) all work.
Datasheet says:

48.6.2.8

Capture Operations

To enable and use capture operations, the corresponding Capture Channel x Enable bit in the Control A register (CTRLA.CAPTENx) must be written to '1'.

A capture trigger can be provided by input event line TC_EV or by asynchronous IO pin WO[x] for each capture channel or by a TC event. To enable the capture from input event line, Event Input Enable bit in the Event Control register (EVCTRL.TCEI) must be written to '1'. To enable the capture from the IO pin, the Capture On Pin x Enable bit in CTRLA register (CTRLA.COPENx) must be written to '1'.

(Hmm. It looks like the fancier TCC timer(s) can only capture "events." And it does look like PORT input changes are not "events." (But it also looks like you could feed a PORT pin into the CCL, and use the CCL output as an event. ))

Hey westfw thanks for your response.
Looking in that same section:
4. Capture of the period and duty cycle on I/Os using PPW/PWP mode is possible using EIC and Event System.
This is what I think I need but not there yet with my SAMD understanding. Still working through it.
The pin change interrupt suggested by zwieblum is what I am currently working on to see if I can get that to work. Then I will try the PPW/PWP mode to see if I can get that to work.
All good fun!
If I get anything to work I will post it for review.
Thanks for your insight on this topic!

I do have another question as I learn this stuff.
The interrupt service routine names like:
void EIC_1_Handler() or void TC0_Handler()

Where does this name "EIC_1_" come from? Is there a list of all valid interrupt names or do you just have to guess what it should be? I don't seem to find a list of them.
Also is Handler() only used for interrupt vectors or is it used in other functions as well?
Thanks

Well I got something to work with the resolution I needed!
It is based on MartinL's code from
SAMD51 TC PWP mode for measuring frequency - Inputs? - Using Arduino / Microcontrollers - Arduino Forum
I was able to modify that code to get what I needed. I was never able to get the TC PW capture mode to work in any configuration. After reading a lot of forums it looks like it never worked?
If someone knows how to do it I would like to see it.

Anyway the code follows. It uses the PPW capture mode MartinL wrote in the link attached.
Thankyou MartinL !!!!
I eliminated the 100KHz TCC0 test frequency generator and use a digital I/O pin to make my test pulse. It outputs on D8 and the input is on A3. Used a jumper wire and an O-scope to verify.
It sends the count result to the console. It looks accurate to +/-one count using the 120MHz input clock or +/-8.33nsec.

// Adafruit Metro M4 Express: Setup TC0 to count pulsewidth on input analog pin A3
// Reference https://forum.arduino.cc/t/samd51-tc-pwp-mode-for-measuring-frequency-inputs/683930/2
// Version 1 
// Does not use the TCC0 clock for a test pulse, it uses IO pin D8 to send a pulse

volatile uint16_t pulsewidth = 0; // This holds the measured pulsewidth
int PulseOutPin = 8;              // output a test pulse on digital pin D8 
int pcount = 1;                   // number of pulses to send

void setup()
{
  Serial.begin(115200);                                 // Open the serial port at 115200 baud
  while(!Serial);                                       // Wait for the console to open
  Serial.println("PulseMeasurementMetroM4ExpressV1");   // print out the file name
  delay(2000);

  pinMode(PulseOutPin,OUTPUT);     // setup pin as an output
  digitalWrite(PulseOutPin,LOW);   // start out driving the output pin low

  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral
  
  // Set up the generic clock (GCLK7) used to clock timers 
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 120MHz clock source by divisor 1: 120MHz/1 = 120MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         //GCLK_GENCTRL_SRC_DFLL;    // Generate from 48MHz DFLL clock source
                         GCLK_GENCTRL_SRC_DPLL0;     // Generate from 120MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DFLL1;   // Generate from 100MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization
  
  GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral TC0
                                   GCLK_PCHCTRL_GEN_GCLK7;     // Connect  120MHz generic clock 7 to TC0
  //----------------------------------------------------------------------------------------------------------------------
  //======================================== setup pulsewidth measurment on TC0 input on analog pin A3 (PA04)
  //----------------------------------------------------------------------------------------------------------------------

  // Enable the port multiplexer on analog pin A3
  PORT->Group[g_APinDescription[A3].ulPort].PINCFG[g_APinDescription[A3].ulPin].bit.PMUXEN = 1;
 
  // Set-up the pin as an EIC (interrupt) peripheral on analog pin A3
  PORT->Group[g_APinDescription[A3].ulPort].PMUX[g_APinDescription[A3].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA04A_EIC_EXTINT4);

  EIC->CTRLA.bit.ENABLE = 0;                        // Disable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization 
  EIC->CONFIG[0].reg = EIC_CONFIG_SENSE4_HIGH;      // Set event on detecting a HIGH level
  EIC->EVCTRL.reg = 1 << 4;                         // Enable event output on external interrupt 4 
  EIC->INTENCLR.reg = 1 << 4;                       // Clear interrupt on external interrupt 4
  EIC->ASYNCH.reg = 1 << 4;                         // Set-up interrupt as asynchronous input
  EIC->CTRLA.bit.ENABLE = 1;                        // Enable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
  // Select the event system user on channel 0 (USER number = channel number + 1)
  EVSYS->USER[EVSYS_ID_USER_TC0_EVU].reg = EVSYS_USER_CHANNEL(1);                   // Set the event user (receiver) as timer TC0 

  // Select the event system generator on channel 0
  EVSYS->Channel[0].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 
  
  TC0->COUNT16.EVCTRL.reg = TC_EVCTRL_TCEI |               // Enable the TCC event input
                            //TC_EVCTRL_TCINV |             // Invert the event input         
                            TC_EVCTRL_EVACT_PPW;            // Set up the timer for capture: CC0 period, CC1 pulsewidth


  NVIC_SetPriority(TC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC0 to 0 (highest)
  NVIC_EnableIRQ(TC0_IRQn);           // Connect the TC0 timer to the Nested Vector Interrupt Controller (NVIC)

  TC0->COUNT16.INTENSET.reg = TC_INTENSET_MC1 ;             // Enable compare channel 1 (CC1) interrupts (pulsewidth)
 
  TC0->COUNT16.CTRLA.reg = TC_CTRLA_CAPTEN1 ;               // Enable pulse capture on CC1 (pulsewidth)
                           TC_CTRLA_MODE_COUNT16;           // Set the timer to 16-bit mode
  
  TC0->COUNT16.CTRLA.bit.ENABLE = 1;                        // Enable the TC0 timer
  while (TC0->COUNT16.SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
   
}// end setup()

void loop()
{  while (pcount > 0)
  {
    TC0->COUNT16.INTENSET.reg = TC_INTENSET_MC1 ;                           // Enable compare channel 1 (CC1) interrupts (pulsewidth)
    Serial.print("Send ");Serial.print(pcount);Serial.println(" pulses");   // indicate the pulse is being sent
    
    digitalWrite(PulseOutPin,HIGH);       //send out a positive pulse
    digitalWrite(PulseOutPin,LOW);        //this sequence generates a 400 nsec pulse 
    if(TC0->COUNT16.INTENSET.bit.MC1==1)  // look for the pulsewidth to be read   
    {                                        
     Serial.print("pulsewidth = "); Serial.println(pulsewidth);  // this measures a count of 47.
     //                                                             47x(1/120MHZ) = 391.6 nsec which is off low by one count
    }
    pcount-=1; // reduce the pulse count by 1
  }                         
}// end loop()

void TC0_Handler()                                  // Interrupt Service Routine (ISR) for timer TC0
{  
  if (TC0->COUNT16.INTFLAG.bit.MC1==1)              // Check for match counter 1 (MC1) interrupt
  {
    pulsewidth = TC0->COUNT16.CC[1].reg;            // Copy the pulsewidth count from the capture reg.
    TC0->COUNT16.INTENCLR.reg = TC_INTENCLR_MC1;    // Disable compare channel 1 (CC1) interrupts                       
  }
}//end TC0_Handler()

Let me know what you think?
Thanks to everyone for helping out.
If you can make the PW capture mode work I would like to see it.
Thanks again !!!!

The ISR vectors names (EIC_1, TC0_handler) all come from the arduino core, you can see them in cortex_handlers.c in adafruit's SAMD core. (AppData\Local\arduino15\packages\adafruit\hardware\samd\1.7.13\cores\arduino)

That array matches table 10-1 in the datasheet. For an example, EXTINT 1 is on line 13. If you look at the exception table in cortex_handlers.c, that's where that name comes from. Not all peripherals get an independent ISR line, so you need to check the interrupt register from within the interrupt to tell what actually happened. Some other peripherals have multiple ISR lines, and you can map specific interrupts to specific lines if you want.

I could have sworn I had PW capture working, but it's been a while and I can't find any code. If you post what you have tried, I might be able to help.

If you look at table 54-6 in the datasheet, max clock frequencies, you'll see TC0 can actually be clocked at 200MHz. If you want even more accurate counting at high speeds, you can set up another GCLK running faster than 120MHz. Just make note of table 54-5 as not all GCLK can actually do 200MHz.

You might also need to change startup.c to reconfigure the DFLL. I think adafruit uses both by default, one for the CPU at 120MHz, and another for ADC at 100MHz. You could work with that, though. Just change around some prescalers...

Thanks microsootys!
That was great info.
On setting a new 200MHz GCLK, I will have to work through it as this is still new to me. But a higher speed would be great! That is why I moved to the ATSAMD51J19A on the Adafruit Metro M4 Express in the first place so I could get the 120MHz clock.
On my PW code, Its kind of all hacked up as I was trying to make it work but I will clean it up and show you what I was trying to do. It would be great if I/we could make that work.
I did see the list in 10-1 but the names did not exactly match what I had seen in other peoples code for their Handlers so this really helps.
Thanks again for the file info. and I will get my PW code back together.

microsootys,
I cleaned up my code attempt on using the PW capture in TC0.
As you can see I have a few random things commented out as I was trying different ways to get it to work. But in the end I had no luck. The top part of the code does work and I get the test waveform output but after that TC0 PW capture does not work. I never receive an interrupt indicating a capture.
Take a look when you get a chance.
Thanks for your time and help!

  /*
  ++++++++++++++NOTE: The test wavform output works but the PW input does not work yet++++++++++++++
  
 This uses the internal registers of the Microchip SAMD51 Micrcontroller found on the adfruit Metro M4 express ID 3382
 It is meant to capture a pulsewidth input on pin A3
 A test waveform using NPWM is output on pin D10, This will be the input signal that the Pulsewidth interface captures on A3
 Jumper output pin D10 to Input pin A3 and watch with O-scope
 Version 1
 This version removes most of the serial print lines to reduce the code size
  */

  // Globals
  int PeriodCount = 0xFF;// 0xFF set the top value for the frequency, this gives 469KHz, 2.13usec
  //int DutyCount = 0x80;  // 0x80 sets the duty cycle to 50% when PER = 0xFF, pulse is 1.064usec wide
  //int DutyCount = 0x40;  // 0x40 sets the duty cycle to 25% when PER = 0xFF, pulse is 532nsec wide
  int DutyCount = 0x10;  // 0x10 sets the duty cycle to 6.3% when PER = 0xFF, pulse is 135nsec wide
  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  //++++++++++++++++++++++++++++++++++ Setup the test waveform on TCC0 +++++++++++++++++++++++++++++++++++
  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void setup() {
  Serial.begin(115200); // Open the serial port at 115200 baud
  while(!Serial);       // Wait for the console to open
  delay(5000);
  Serial.println("-----------------------------------------------");
  Serial.println("Pulse_Width_Capture_Metro_M4_Express_V1");Serial.println(); // Print the file name
  Serial.println("**Setup a Test waveform on TCC0 channel 0");

  MCLK->APBAMASK.reg |= MCLK_APBAMASK_TC0 | //  APBA CLOCK IS ENABLED FOR TC0
                        MCLK_APBAMASK_EIC;  //  APBA CLOCK FOR EIC IS ENABLED

  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral
  //==========================Setup the Generic CLK ============================================================================
  // Set up the generic clock (GCLK7) used to clock timers 
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 120MHz clock source by divisor 1: 120MHz/1 = 120MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DPLL0;     // Generate from 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization
  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable perhipheral TCC0
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect  120MHz generic clock 7 to TCC0                               
  //========================== Setup the PORT Pin ==============================================================================
  // Enable the peripheral multiplexer on pin D10
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
  // Set the D10 (PORT_PA20) peripheral multiplexer to peripheral (even port number) G(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA20G_TCC0_WO0);  
  //========================== Setup the TCC0 Timer/Counter in NPWM mode =======================================================
  TCC0->WAVE.bit.WAVEGEN=TCC_WAVE_WAVEGEN_NPWM_Val;
  while (TCC0->SYNCBUSY.bit.WAVE);                   // Wait for synchronization
  TCC0->PER.reg=PeriodCount;                         // get the global value
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
  TCC0->CC[0].reg=DutyCount;                         // get the global value
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization
  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization

  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  //++++++++++++++++++++++++++++++++++ Setup Pulsewidth Capture on TC0 ++++++++++++++++++++++++++++++
  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  Serial.println("**Setup Pulse capture on TC0 channel 0");
  // Note: Using the GCLK->GENCTRL[7].reg from above
  GCLK->PCHCTRL[TC0_GCLK_ID].reg =  GCLK_PCHCTRL_CHEN |         // Enable perhipheral TC0 , clock mapping ID = 9
                                    GCLK_PCHCTRL_GEN_GCLK7;     // Connect  120MHz generic clock 7 to TC0                               
  //========================== Setup the PORT Pin ==============================================================================
  // Set the A3 (PORT_PA04) peripheral multiplexer to peripheral (even port number) E(4): TC0/WO[0], Channel 0
  //PORT->Group[g_APinDescription[17].ulPort].PMUX[g_APinDescription[17].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA04E_TC0_WO0); //(32.8.13) 
  // Set the A3 (PORT_PA04) peripheral multiplexer to peripheral (even port number) A(0): EIC/EXTINT[4]
  PORT->Group[g_APinDescription[17].ulPort].PMUX[g_APinDescription[17].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA04A_EIC_EXTINT4); //(32.8.13) connect the port pin to an external interrupt input 
 // PORT->Group[g_APinDescription[17].ulPort].PINCFG[g_APinDescription[17].ulPin].bit.INEN = 1;   //(32.8.14) input buffer for the I/O pin is enabled
  PORT->Group[g_APinDescription[17].ulPort].EVCTRL.reg =  PORT_EVCTRL_PORTEI0     | // (32.8.12) EVENT ACTION WILL TRIGGER ON ANY EVENT
                                                          PORT_EVCTRL_EVACT0_OUT  | // OUTPUT REGISTER OF PIN WILL SET TO LEVEL OF EVENT
                                                          PORT_EVCTRL_PID0(4);      // EVENT ACTION TO BE EXECUTED ON PIN 4
  PORT->Group[g_APinDescription[17].ulPort].PINCFG[g_APinDescription[17].ulPin].bit.PMUXEN = 1; //(32.8.14) peripheral multiplexer enable
  //========================== Setup the EVENT SYSTEM ============================================================================
  // Select the event system user on channel 0 (USER number = channel number + 1)
  EVSYS->USER[EVSYS_ID_USER_TC0_EVU].reg = EVSYS_USER_CHANNEL(1);                   // Set the event user (receiver) as timer TC0 USER_44 Channel 1-1=0
  // Select the event system generator on channel 0
  EVSYS->Channel[0].CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT  |               // MUST BE ZERO WHEN USING ASYNCHRONOUS PATH (31.7.8)
                                  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 EXTINT_4, 22 = 0x16 = EIC_EXTINT[4] (31.7.8 page 803)
  //========================== Setup NESTED VECTOR INTERRUPT CONTROLLER ========================================================
  __enable_irq();               // Not sure I need to do this
  NVIC_SetPriority(TC0_IRQn,0); // Set  the interrupt to the highest priority 
  //NVIC_EnableIRQ(TC0_IRQn);     // interrupt line mapping TCO_IRQn is line 107 (10.2.2)
  //NVIC_EnableIRQ(EIC_4_IRQn);   // external interrupt from PA04, A3  line 4
  //========================== Setup the EXTERNAL INTERRUPT CONTROLLER FOR PIN PA04, A3  =======================================
  EIC->CTRLA.bit.ENABLE = 0;                        // Disable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization 
  EIC->CONFIG[0].reg = EIC_CONFIG_SENSE4_BOTH;      // Set event on detecting  BOTH edges ON EXTINT4
  EIC->EVCTRL.reg = 1 << 4;                         // Enable event output on external interrupt EXTINT4
  //EIC->INTENSET.reg = 1 << 4;                       // SET interrupt ENABLE on external interrupt EXTINT4
  EIC->ASYNCH.reg = 1 << 4;                         // Set-up interrupt as asynchronous input EXTINT4
  EIC->CTRLA.bit.ENABLE = 1;                        // Enable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
  //========================== Setup the TC0 Timer/Counter CHANNEL 0 in Pulse Width Capture mode =======================================================
  TC0->COUNT16.EVCTRL.reg = TC_EVCTRL_EVACT_PW  |     // SETUP THE COUNTER TO PERFORM A PULSE WIDTH CAPTURE PW (48.6.2.8.3) (48.7.2.4)
                                TC_EVCTRL_TCEI  |     // ENABLE INCOMING EVENTS
                                TC_EVCTRL_OVFEO ;     // ENABLE THE OVERFLOW EVENT
                                //TC_EVCTRL_MCEO0 ;   // ENABLE CAPTURE EVENT OUTPUT
                                //TC_EVCTRL_TCINV ;   // SET FOR POSITIVE EDGE CAPTURE
  TC0->COUNT16.INTENSET.reg = TC_INTENSET_MC0 |       // ENABLE THE CAPTURE INTERRUPT FOR CHANNEL 0 (48.7.1.6)
                              TC_INTENSET_MC1 |       // ENABLE THE CAPTURE INTERRUPT FOR CHANNEL 1 (48.7.1.6)
                              TC_INTENSET_OVF;        // ENABLE THE OVERFLOW INTERRUPT FOR CHANNEL 0                        
  TC0->COUNT16.CTRLA.reg  = TC_CTRLA_CAPTEN0      |   //ENABLE PULSE CAPTURE ON CC0 (48.7.2.1)
                            //TC_CTRLA_COPEN0     |   // USE THE IO PIN AS THE TRIGGER SOURCE
                            TC_CTRLA_MODE_COUNT16 |   // PUT THE COUNTER INTO 16-BIT MODE
                            TC_CTRLA_ENABLE;          // ENABLE THE PERIPHERAL
  while(TC0->COUNT16.SYNCBUSY.bit.ENABLE); 
}// END SETUP()

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

//-------------------------TC0 INTERRUPT SERVICE ROUTINE---------------------------------
void TC0_IRQn_Handler()
{
  TC0->COUNT16.INTFLAG.reg = 0; // CLEAR THE INTERRUPT FLAGS
  Serial.println("TC0_IRQn");// lets see if I get any TC0 interrupts
  while(1); // hold
}// END TC0_IRQn_Handler()

//-------------------------EXTERNAL INTERRUPT 4 SERVICE ROUTINE---------------------------------
void EIC_4_Handler()
{
  EIC->CTRLA.bit.ENABLE = 0;  // disable the interrupts
  EIC->INTENCLR.reg = 1 << 4; // clear the interrupt flag
  Serial.println("EIC_4_");   // lets see if I get any EIC interrupts
 // while(1); // hold
}// END EIC_4_Handler()


Hi @jefftech123

Here's some example code at measures the period and pulse width on D9 of the Adafruit Metro M4. The code configures the External Interrupt Controller (EIC) to simply route the incoming signal asynchronously to timer TC0 via the microcontroller's event system. TC0 then measures the pulse timing:

// Setup TC0 to capture pulse-width and period on Adafruit Metro M4 on digital pin D9 (PA20) 
volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_t pulsewidth;

void setup()
{
  Serial.begin(115200);                              // Send data back on the Itsy Bitsy M4 native port
  while(!Serial);                                    // Wait for the Serial USB port to be ready
 
  MCLK->APBAMASK.reg |= MCLK_APBAMASK_TC0;           // Activate timer TC0
  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral
 
  GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel
                                   GCLK_PCHCTRL_GEN_GCLK0;     // Connect 120MHz generic clock 0 to TC0

  // Enable the peripheral multiplexer on pin D9
  PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;
  
  // Set the D9 (PORT_PA20) peripheral multiplexer to peripheral (even port number) A(0): EXTINT4
  PORT->Group[g_APinDescription[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA20A_EIC_EXTINT4);             
  
  EIC->CTRLA.bit.ENABLE = 0;                        // Disable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization 
  EIC->CONFIG[0].reg = EIC_CONFIG_SENSE4_HIGH;      // Set event on detecting a HIGH level
  EIC->EVCTRL.reg = 1 << 4;                         // Enable event output on external interrupt 4 
  EIC->INTENCLR.reg = 1 << 4;                       // Clear interrupt on external interrupt 4
  EIC->ASYNCH.reg = 1 << 4;                         // Set-up interrupt as asynchronous input
  EIC->CTRLA.bit.ENABLE = 1;                        // Enable the EIC peripheral
  while (EIC->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
  // Select the event system user on channel 0 (USER number = channel number + 1)
  EVSYS->USER[EVSYS_ID_USER_TC0_EVU].reg = EVSYS_USER_CHANNEL(1);         // Set the event user (receiver) as timer TC0 

  // Select the event system generator on channel 0
  EVSYS->Channel[0].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 

  TC0->COUNT32.EVCTRL.reg |= TC_EVCTRL_TCEI |             // Enable the TCC event input
                             /*TC_EVCTRL_TCINV |*/          // Invert the event 1 input         
                             TC_EVCTRL_EVACT_PPW;         // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  NVIC_SetPriority(TC0_IRQn, 0);       // Set the Nested Vector Interrupt Controller (NVIC) priority for TC0 to 0 (highest)
  NVIC_EnableIRQ(TC0_IRQn);            // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC0->COUNT32.INTENSET.reg = TC_INTENSET_MC1 |             // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;              // Enable compare channel 0 (CC0) interrupts
 
  TC0->COUNT32.CTRLA.reg |= TC_CTRLA_CAPTEN1 |               // Enable capture on CC1
                            TC_CTRLA_CAPTEN0 |               // Enable capture on CC0
                            //TC_CTRLA_PRESCALER_DIV1 |       // Set prescaler to 1, 120MHz/1 = 120MHz
                            //TC_CTRLA_PRESCSYNC_PRESC |      // Timer to wrap around on next prescaler clock
                            TC_CTRLA_MODE_COUNT32;          // Set the TC0 timer to 32-bit mode
                            
  TC0->COUNT32.CTRLA.bit.ENABLE = 1;                        // Enable TCC0
  while (TC0->COUNT32.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();
    Serial.print(period);             // Output the results
    Serial.print(F("   "));
    Serial.println(pulsewidth);
    periodComplete = false;           // Start a new period
  }
}

void TC0_Handler()                                        // Interrupt Service Routine (ISR) for timer TC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TC0->COUNT32.INTFLAG.bit.MC0)             
  {   
    isrPeriod = TC0->COUNT32.CC[0].reg;                   // Copy the period
    periodComplete = true;                                // Indicate that the period is complete
  }

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

Thanks MartinL
I had based my other code which is above in post 12 on your post

SAMD51 TC PWP mode for measuring frequency - Inputs? - Using Arduino / Microcontrollers - Arduino Forum

Which you did awhile back and it was very helpful !!
This code works very well and you have it in a 32-bit counter which is great!
Have you ever got the PW capture mode working? Best I can find is that nobody has been successful with that? Because in my situation I do not have a running frequency only a one time single pulse. This makes using the OVF flag on a long single pulse impossible.
Because the flag is coming from two places, CC0 and CC1 the OVF count gets messed up.
That is why I would like to use the PW capture mode since it only measures one thing, the pulsewidth and not the period. In this case the OVF would only come from one counter CC0 which I think I could then count the number of OVF and measure longer pulses. This was important in the 16-bit counter but the 32-bit counter could make the OVF unnecessary. At a 120MHz clock it gives me a 35.8sec pulse length where the 16-bit counter only gave me a 546usec pules before it OVF. If in my setup I get a 35sec pulse something else is very wrong :slightly_smiling_face: So 32-bits are great!!

I do have a question on the code. In the 16-bit counter example you posted in the past you had all of this:

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

But in the 32-bit version which is almost identical you did not need the above??
So I guess when you use GCLK[0] none of that is required or is it happening somewhere else?
Just trying to understand.

Anyway thank you very much for your help with all of this!!!

micosootys,

Ref post #13
Thanks, I took your advice and changed my TC0 capture to run at 200MHz!
And it looks like the Adafruit Metro M4 Express only uses DLLP[0].
I did not have to mess with startup.c thankfully! And as you will see below
ADC0, ADC1 and the USB are setup to run at 48MHz and the DAC is set to run at 12MHz.
The CPU is set at 120MHz.

After a hard reset this is how the clocks and their paths stacks-up:
In setup() I generated some code to display all the registers I needed to see.
Which is where all this came from.
loop() was left empty.

DFPLL[0] is set for 120MHz and DFPLL[1] is set for 100MHz.
GCLK Peripheral CH1 and 2 use Generator 5 as its reference input.
GCLK Peripheral CH10, 40 and 41 use Generator 1 as its reference input.
GCLK Peripheral CH42 uses Generator 4 as its reference input.

The Peripheral channel mapping is as follows. See page 156
Ch1 output goes to -> Ref for FDPLL0.
Ch2 output goes to -> Ref for FDPLL1.
Ch10 output goes to -> USB.
Ch40 output goes to -> ADC0.
Ch41 output goes to -> ADC1.
Ch42 output goes to -> DAC.
Nothing else was set up.

Generic Clock Generator 0,1, 2, 3 are divide by 0 or no division.
Generic Clock Generator 4 is divided by 0x4 = 4.
Generic Clock Generator 5 is divided by 0x30 = 48.

Generator 0 uses DPLL[0] as its input source. ( This is used for the CPU CLOCK)
Generator 1 uses DFLL48 as its input source. (This is used for the USB, ADC0, ADC1)
Generator 2 uses DPLL[1] as its input source. (Not used)
Generator 3 uses XOSC32K as its input source.( Not used)
Generator 4 uses DFLL48 as its input source. (This is used for the DAC)
Generator 5 uses DFLL48 as its input source. (This is used for the CPU CLOCK)
Generators 6-11 are not setup.(Not used)

It looks like after a hard reset, DFLL48 is running in open loop mode at 48MHz.

Below is my connection chart or clock paths taken after a reset.

  1. [DFLL(48MHz)]->[(/48)GEN5(1MHz)]->[CH1]->[(x120)FDPLL0(120MHz)]->
    [(/1)GEN0(120MHz)->[GCLK_MAIN] which is the CPU clock.

  2. [DFLL(48MHz)]->[(/48)GEN5(1MHz)]->[CH2]->[(x100)FDPLL1(100MHz)]->It is setup this far but not used.

  3. [DFLL(48MHz)]->[(/1)GEN1(48MHz)]->[CH10(USB)],[CH41(ADC0)],[CH41(ADC1)].

  4. [DFLL(48MHz)]->[(/4)GEN4(12MHz)]->[CH44(DAC)].

You read line 1 as follows:
DFLL at 48MHz feeds Generator 5 which divides the input by 48 giving a 1MHz output.
This feeds CH1 which feeds the FDPLL0 which multiplies the input freq. by 120 giving an output freq. of 120MHz. This then feeds Generator 0 dividing by 1 and sends the 120MHz to GCLK_MAIN which is the CPU clock.
Hope that makes sense?

OK, Wow!
Now to set my TC0 clock to 200MHz.
Here is the snippet of code I used to put TC0 onto DPLL[1] and run it at 200MHz

  // change the speed of the FDPLL[1] from 100MHz to 200MHz Max freq 
  // change the speed
  OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE=0;            // disable DPLL[1]
  while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);    // wait for synchronization
  OSCCTRL->Dpll[1].DPLLCTRLB.bit.LBYPASS=0;           // Clear the LBYPASS bit
  while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);    // wait for synchronization
  OSCCTRL->Dpll[1].DPLLCTRLB.bit.LTIME=0;             // keep LTIME = 0
  while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);    // wait for synchronization
  //OSCCTRL->Dpll[1].DPLLCTRLB.reg=0x000;             // Clear the LBYPASS bit, keep LTIME = 0 all in one step
  //while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);  // wait for synchronization
  OSCCTRL->Dpll[1].DPLLRATIO.reg=0xC7;                // set the LDR to 0xC7 = 199 to give an output of 200MHz MAX speed!
  while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.DPLLRATIO); // wait for synchronization
  OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE=1;            // enable DPLL[1]
  while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);    // wait for synchronization
  // done
 // FDPLL[1] using Generator 2
  GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel (9=GCLK_TC0,GCLK_TC1)
                                   GCLK_PCHCTRL_GEN_GCLK2;     // Connect 200MHz generic clock (2) to TC0

There is probably a better way to do this but this did work!
If there is a cleaner way I would be happy to see it.
Also did I get my self into unknown trouble or leave something important out?
Let me know if you see something that does not look right?

My new connection chart for this is:
[DFLL(48MHz)]->[(/48)GEN5(1MHz)]->[CH2]->[(x200)DPLL1(200MHz)]->[(/1)GEN2(200MHz)]-> [CH9(TC0,TC1).

Since nothing but this code is using DPLL[1] nothing is being overclocked.
I left DFLL48 and DPLL[0] alone so as to not mess-up the CPU clock or other stuff.

So it all works and getting better accuracy at the 200MHz !

So thanks again for the suggestion! In this project I have learned a lot about coding for this ATSAMD51 processor!
Starting from scratch and getting here was only possible because the help from you and the others!
Thanks to all!
Now I just need to get more timers running! It never ends. Great fun!!!
Sorry it was so wordy. :roll_eyes:

Hi @jefftech123

The SAMD51 microcontroller has a variety of possible external and on-chip clock sources. These include:

  • External 32.768kHz crystal oscillator (XOSC32K)
  • On-chip 32.768kHz ultra low power oscillator (OSCULP32K)
  • On-chip 48MHz Digital Frequency Locked Loop (DFLL48M)
  • On-chip 200MHz Digital Phase Locked Loop 0 (DPLL200M0)
  • On-chip 200MHz Digital Phase Locked Loop 1 (DPLL200M1)

There's also the possibility of configuring up to two external 8-48MHz external crystals, but these aren't implemented on Adafruit's Metro M4 board.

These clock sources can be programmatically connected to one of 12 generic clocks (GCLK). GCLK0 is reserved as the main clock (MCLK) for the CPU. The Adafruit SAMD core code assigns GCLKs 0 to 5, the rest are free to use as you wish:

  • GCLK0 - DPLL200M0 set to 120MHz
  • GCLK1 - DFLL48M set to 48MHz (in open loop mode)
  • GCLK2 - DPLL200M1 set to 100MHz
  • GCLK3 - XOSC32K (for boards with an external 32.768kHz crystal)
  • GCLK4 - 12MHz (for DAC, DFLL48M divided by 4)
  • GCLK5 - 1MHz (DFLL48M divided by 1)

Note that the GLCK's are able to divide down the clock sources to a required frequency. The GCLKs in turn can be configured to drive the microcontroller's on-chip peripherals. It's possible to either tap into these pre-configured GLCKs, or alternatively set up you own.

Here's an example of setting up DPLL200M1 at 200MHz then configuring GCLK7 to drive timers TCC0 and TCC1:

MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral

OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE = 0;              // Disable DPLL1
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);        // Wait for register synchronization
  
OSCCTRL->Dpll[1].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDRFRAC(0x00) |      // Set Loop Driver Ratio (LDR) fractional component to 0
                                 OSCCTRL_DPLLRATIO_LDR(199);            // Set Loop Driver Ratio (LDR) integer component to 199 (+ 1) for 200MHz  
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.DPLLRATIO);                     // Wait for register synchronization
  
OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE = 1;                              // Enable DPLL1
while(OSCCTRL->Dpll[1].DPLLSYNCBUSY.bit.ENABLE);                        // Wait for register synchronization
  
while(!OSCCTRL->Dpll[1].DPLLSTATUS.bit.CLKRDY ||                        // Wait for DPLL1 clock output to become active
      !OSCCTRL->Dpll[1].DPLLSTATUS.bit.LOCK);                           // Wait for DPLL1 to achieve lock
  
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 200MHz clock source by divisor 1: 200MHz/1 = 200MHz
                       GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                       GCLK_GENCTRL_GENEN |        // Enable GCLK2
                       GCLK_GENCTRL_SRC_DPLL1;     // Select 200MHz DPLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization

GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 perhipheral channel
                                  GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0 at 200MHz

GCLK->PCHCTRL[TCC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                  GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC1 at 200MHz

Thanks MartinL,
Its good to know we agree on the clocks.
It verifies that I am starting to get/understand this thing!
I like your code is much cleaner and more robust.
Will put that to good use!
Thanks.