Best microcontroller for frequency counter up to 30MHz

I would like to make a frequency counter that can measure signals up to 30MHz. So I need a micro controller where I can count how many times a digital pin went high or low for up to 30 million times in a second. What spec of a micro controller would tell me if it can handle this?

If I use a divider (e.g. divide by 8) it only needs to count up 3.75MHz but then I lose precision.

I can think of no Arduino that can directly manage what your asking without using a binary counter.
You don't lose precision using a counter if you design the circuit to read all the pins (something like the 74VHC4040M) or if your just reading the MSB pin then allow the Arduino to inject clock signals so you can count the remainder from where you stopped counting..

A higher speed board, something ARM based, that would let you clock one of the timers from an external signal, and then like, put an interrupt on the overflow, and time how long it takes to count up to overflow would probably get you what you want.

But an AVR definitely can't - not fast enough (definitely can't run a timer that fast, even from external clock).

I can't recommend a board though - I don't know enough about the capabilities of the non-AVR boards to know which ones could do this. I mean, I know exactly how I'd do it on an AVR, but wouldn't work up to the speed you need.

That is a subjective decision. Try this link to get a bit more familiar with what you are trying to do. https://www.best-microcontroller-projects.com/article-frequency-counter.html. Getting the speed is not that great a problem if the correct hardware is used. You also need to define what you are going to use for a time base, the Arduino is not that stable. This response is to help you get started in solving your problem, not solve it for you.
Good Luck & Have Fun!
Gil

Shameless plug: The ESP32 has several hardware counters and timers.

Pulse Counter Pulse Counter (PCNT) - ESP32 - — ESP-IDF Programming Guide latest documentation

Timer https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/timer.html

The ESP32 chip contains two hardware timer groups. Each group has two general-purpose hardware timers. They are all 64-bit generic timers based on 16-bit prescalers and 64-bit up / down counters which are capable of being auto-reloaded.

If you are comfortable with writing in Macro Assembler there is the ULP core Page Not Found - ESP32 - — ESP-IDF Programming Guide latest documentation that can be programmed to be a timer or pulse counter or...

Hi CBnation,

The SAMD51, 120MHz, ARM Cortex M4F found on a number of Arduino compatible boards is capable of counting 30MHz pulses without any intervention from the CPU, except for reading the timer's COUNT register.

This is achieved by routing the 30MHz signal asynchronously from the microcontroller's pin to a 32-bit timer via its External Interrupt Controller (EIC) and Event System. The Event System is a 32 channel highway that allows peripheral-to-peripheral communication. It essentially allows a clockwork machine of interlinked inputs, outputs, timers and DMA interaction to occur without any CPU intervention at all.

The following code uses an Adafruit Feather M4 with a SAMD51. It sets up the microcontroller's Timer Counter TC0 to receive and count the input pulses from analog pin A4, routed through the EIC and Event System. It also sets up Timer Counter for Control applications TCC0, as a 30MHz, 50% duty cycle PWM output on digital pin D10. Connecting D10 to A4 allows the pulses to be counted. The number of pulses counted per second is output on the serial console:

// Adafruit Feather M4: Count input pulses with 32-bit timer TC0 on pin A4 and 30MHz test output with timer TCC0 on D10
void setup() 
{
  // Serial USB Port Output ///////////////////////////////////////////////////////////////////////////////
  
  Serial.begin(115200);           // Open the native USB port at 115200 baud
  while(!Serial);                 // Wait for the console to be opened
 
  // TCC0 Timer 30MHz Test Output /////////////////////////////////////////////////////////////////////////

  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK0;    // Connect 120MHz generic clock 0 to TCC0
  
  // 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(6);             
  
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE);                   // Wait for synchronization

  TCC0->PER.reg = 3;                                 // Set-up the PER (period) register for 30MHz output
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
  
  TCC0->CC[0].reg = 2;                               // Set-up 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
  
  // EIC Interrupt System ///////////////////////////////////////////////////////////////////////////////

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

  // TC0 Count Timer //////////////////////////////////////////////////////////////////////////////////

  GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel for TC0
                                   GCLK_PCHCTRL_GEN_GCLK0;     // Connect generic clock 0 at 120MHz

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

  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral
  
  // 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 TC event input                        
                            TC_EVCTRL_EVACT_COUNT;        // Set up the timer to count on event
  
  // Enable Timer  ///////////////////////////////////////////////////////////////////////////////
  
  TC0->COUNT32.CTRLA.bit.ENABLE = 1;                 // Enable timer TC0
  while (TC0->COUNT32.SYNCBUSY.bit.ENABLE);          // Wait for synchronization
}

void loop() 
{
  TC0->COUNT32.CTRLBSET.reg = TC_CTRLBCLR_CMD_READSYNC;     // Initiate read synchronization of COUNT register
  while (TC0->COUNT32.SYNCBUSY.bit.CTRLB);                  // Wait for CTRLBSET register write synchronization
  while (TC0->COUNT32.SYNCBUSY.bit.COUNT);                  // Wait for COUNT register read synchronization
  Serial.println(TC0->COUNT32.COUNT.reg);                   // Read and display the TC0 COUNT register 
  TC0->COUNT32.CTRLBSET.reg = TC_CTRLBCLR_CMD_RETRIGGER;    // Initiate timer reset
  while (TC0->COUNT32.SYNCBUSY.bit.CTRLB);                  // Wait for CTRLBSET register write synchronization
  delay(1000);                                              // Wait a second
}

Just using delay(1000) in the loop() function to create a one second delay isn't super accurate and is used just for demonstration purposes. You get values around 30,000,020 or thereabouts. To get an accurate time window another timer could be used to stop and start the count timer, (again using the event system).

Neither of the suggested solution is much better than using HW divide-by-n counter to reduce the input clock speed and use Arduino. I don't know why OP thinks it would reduce precision.

Neither of the suggested solution is much better than using HW divide-by-n counter to reduce the input clock speed and use Arduino. I don't know why OP thinks it would reduce precision.

I think that CBnation is suggesting that dividing the signal would reduce the precision of the measurement through reduced resolution.

I just tried out the SAMD51's frequency meter peripheral on my Adafruit Metro M4, using the measurement clock at 30MHz and a reference clock taken from the board's 32.768kHz crystal, divided down to 512Hz and taken over 128 periods or 0.25 seconds (1 / 512 * 128 = 0.25). 0.25 seconds was chosen as the frequency meter's measurement counter and reference timer are only 24-bits in length.

In theory using this method should give the measured frequency of 30MHz with a resolution of ±4Hz, but in reality the measurement values are:

29884928
29883904
29884416
29886464
29889024
29891584
29892096
29894656
29892096
29890560
29891072
29891072
29890560
29891072
29888512

The errors in accuracy and precision are due to the 32.768kHz crystal clock source, plus multiplication and jitter errors in the frequency and phase locked loops used generate the microcontroller's 120MHz main clock signal and 30MHz PWM output. Unfortunately, I don't have a signal generator to test it properly.

Here's the frequency meter code for the SAMD51. Note that I had to use the Adafruit Metro M4, as the frequency meter uses input directly from a generic clock input pin. I chose GCLK7, but the pins that use GCK7 aren't broken out on either the Adafruit Feather M4 or Itsy Bitsy M4. Plus the fact that the Itsy Bitsy M4 board uses the SAMD51's internal oscillator, rather than an external crystal. To operate just take the 30MHz output on digital pin D9 and connect it to the measurement clock input on D4. The output in Hertz is displayed in the console window.

// Adafruit Metro M4: Measure the input frequency using the SAMD51's frequency meter on D4
// 30MHz test PWM signal on D9

# define FREQM_GCLK_ID_REQ 6      // Definition of the frequency meter's reference clock not defined in the CMSIS files

void setup() 
{
  // Serial USB Port Output ///////////////////////////////////////////////////////////////////////////////
  
  Serial.begin(115200);           // Open the native USB port at 115200 baud
  while(!Serial);                 // Wait for the console to be opened
 
  // TCC0 Timer 30MHz Test Output /////////////////////////////////////////////////////////////////////////

  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK0;    // Connect 120MHz generic clock 0 to TCC0
  
  // Enable the peripheral multiplexer on pin D9
  PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].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[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXE(MUX_PA20G_TCC0_WO0);             
  
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE);                   // Wait for synchronization

  TCC0->PER.reg = 3;                                 // Set-up the PER (period) register for 30MHz output
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
  
  TCC0->CC[0].reg = 2;                               // Set-up 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
  
  // Generic clock set up //////////////////////////////////////////////////////////////////////////////
  
  MCLK->APBAMASK.reg |= MCLK_APBAMASK_FREQM;        // Power up the frequency meter peripheral

  // Enable the peripheral multiplexer on pin D4
  PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
  
  // Set D4 the peripheral multiplexer to peripheral GCLK (12): GCLK7 Input
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXO(MUX_PB13M_GCLK_IO7);
  
  // Set up the generic clock (GCLK6) to be used as the frequency meter's reference clock at 512Hz
  GCLK->GENCTRL[6].reg = GCLK_GENCTRL_DIV(5) |       // Divide the 32.768kHz clock source by divisor 64: 32.768kHz/2^(5+1) = 512Hz
                         GCLK_GENCTRL_DIVSEL |       // Set divisor to 2^(n + 1)
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK6
                         GCLK_GENCTRL_SRC_XOSC32K;   // Generate from 32.768kHz external crystal clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL6);               // Wait for synchronization

  GCLK->PCHCTRL[FREQM_GCLK_ID_REQ].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel for FREQM REQ clock
                                         GCLK_PCHCTRL_GEN_GCLK6;     // Connect generic clock 6 at 512Hz
 
  // Set up the generic clock (GCLK7) to be used as the frequency meter's measurement clock on input D4
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_GCLKIN;    // Generate GCLK7 from GPIO input pin
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization
  
  GCLK->PCHCTRL[FREQM_GCLK_ID_MSR].reg = GCLK_PCHCTRL_CHEN |        // Enable perhipheral channel for FREQM MSR clock
                                         GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 
  
  // Frequency meter set up //////////////////////////////////////////////////////////////////////////////
  
  FREQM->CFGA.reg = 128;                  // Measure frequency over period (1 / 512Hz) * 128 = 0.25 seconds
  FREQM->CTRLA.bit.ENABLE = 1;            // Enable the frequency measurement peripheral  
  while (FREQM->SYNCBUSY.bit.ENABLE);     // Wait for synchronization
 
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
}

void loop() 
{
  FREQM->CTRLB.bit.START = 1;              // Start a frequency measurement peripheral
  while (FREQM->STATUS.bit.BUSY);          // Block until the measurement is complete  
  if (FREQM->STATUS.bit.OVF)               // Check if the VALUE register has overflowed?
  {
    Serial.println(F("Overflow"));         // Indicate that on overflow of the VALUE register has occured
    FREQM->STATUS.bit.OVF = 1;             // Clear the overflow status bit
  }
  else
  {
    uint32_t measuredFrequency = (FREQM->VALUE.reg / 128) * 512;  // Calculate the measured frequency over 0.25 seconds
    Serial.println(measuredFrequency);                            // Output the measured frequency (with 4Hz resolution, in theory at least)
  }
}

MartinL:
I think that CBnation is suggesting that dividing the signal would reduce the precision of the measurement through reduced resolution.

How is the resolution reduced? With slower reference clock it takes longer to get the same precision. OP did not tell us target precision and time available to get the result.
(If you know that divided-by-8 signal is EXACTLY 1.2345 MHz than the original signal is EXACTLY 8*12345 MHz.)

Hi Smajdalf,

How is the resolution reduced? With slower reference clock it takes longer to get the same precision. OP did not tell us target precision and time available to get the result.
(If you know that divided-by-8 signal is EXACTLY 1.2345 MHz than the original signal is EXACTLY 8*12345 MHz.)

I agree, assuming that there's time available for the reference clock to be slowed down. In the above example, I just used the fastest sample time that made full use of the 24-bit measurement counter and reference timer resolution found in the SAMD51's frequency meter.

@johnwestlake

Topic reopened as requested, and "Report to moderator" was the correct way to ask

@johnwestlake

Topic reopened as requested, and "Report to moderator" was the correct way to ask

UKHeliBob:
@johnwestlake

Topic reopened as requested, and "Report to moderator" was the correct way to ask

Brilliant, Thank you.

MartinL,

An old post I asked the moderators to kindly bump up so I could ask some questions.

I'm a retired hardware designer with no in-depth software experience (apart from Sinclair / Amstrad CPC Basic from my childhood days :D ). I'm self teaching myself "C" on the Arduino - and for sure its making me feel VERY VERY stupid.

So I'm working on a hardware design where I need to confirm that a couple of external "Clock" inputs are within a correct frequency range - anywhere from say 30KHz to 100MHz... So I need the system MCU to act as a frequency counter when an signal is sensed on 1 or 2 inputs.

Please understand I'm a real beginner here with C so please go easy with me and I hope I dont shame myself too much! :slight_smile:

I plan to use the SAMD51 (in QFN64 pin package) - the SAM range of MCU's have the FREQM clock which you implemented on the Audafruit Metro M4. Unfortunately I have the bigger Audafruit Metro M4 Grand Central (for testing, before I design my own PCB) which used the larger 128pin QFN ATSAMD51P20A and I could not get any output from your "Adafruit Metro M4 SAMD51's frequency meter peripheral" code, No 30MHz output on any pin, and it appears to "freeze" at:-

Line 61 while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization

One would assume its waiting endlessly for "synchronization" - which as I have no 30MHz clock / any clock connected to the input its "waiting" with no escape :D ... (no input/ output as I my Grand central ports dont match up with your code)

I did managed to get your earlier code for the Adafruit Feather M4 (using the microcontroller's Timer Counter TC0) to output atleast the "30MHz" clock on PA18 D35 on the Metro M4 Grand Central. But I could not find any of the Grand Central GPIO inputs that would be the Frequency input....

What I dont understand is why the Clock is outputted on the Grand Central's Port PA18 (not Port PA20 as you defined for your smaller Audafruit Metro M4) - I know theboards use different packaged IC's, but surely from a software perspective the hardware ports remain the same??? (where common / available between the different packages).

Also, your using some very crypt "command syntax" in your code to setup the internal hardware clock blocks of the Atmel SAMD51's where did you get this info from? - I have very vague idea its maybe specified by ARM CMSIS - but I could find no documentation - help etc.... (maybe I'm wrong about CMSIS)!!!

GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC1 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK0; // Connect 120MHz generic clock 0 to TCC0
etc etc...

Could you please post a link to the relent documents / info?

Also, I measured the "30MHz" output with a "real" frequency meter and as you suspected its A: Very jittery - like VERY jittery!!! and B: a Massive PPM Offset... It would be interesting to see how accurate the FREQM can be made to function with a decent external reference clock to the MCU (you mentioned 4Hz)... which would be decent... understandably related to the Input / Reference clock ratio and counter length...

It would be something I'd like to try with some software help from you :slight_smile: ... I'll have no problem with the hardware - but OMG, self learning C is a pig, and not helped when having to work at such a complex level for a true beginner (Setting up up the Atmel SAMD internal Clock blocks / ARM CMSIS etc.)

My hardware will have an Ultra precise 48MHz external Clock, which I'd like to use as the SAM reference clock... Once I can get to grips with configuring its clock blocks / C etc :D !!!

Any help (I understand this is a old thread) would be very much appreciated - hopefully making my pending Christmas period "programming" somewhat less frustrating and painful - I dread both C and configuring the SAM clock blocks!!!

Hi johnwestlake,

I re-tested the SAMD51 frequency meter with the help of my trusty Arduino Due, with the Due providing a PWM test output at 28MHz. The Due uses a 12MHz crystal, which I suspected would result in a more stable signal, as the SAM3X8E's internal PLL only needs to multiply the crystal frequency by 7, to create its 84MHz main clock.

Sure enough, the jitter has now gone, with the SAMD51's console outputting a stable frquency:

27998720
27998720
27998720
27998720
27998720
27998720
27998720
27998720
27998720
27998720
27998720

Regarding your Adafruit Metro M4 Grand Central, the frequency meter input won't be on D4, as it is on Metro M4, it's instead on UART1_RX. The easiset thing to do is to change the output configuration to work with the microcontroller's port pins, rather than the Arduino pin numbering system, (although the frequency meter (GCLK7) input will be on UART1_RX on your Grand Central):

// Enable the peripheral multiplexer on pin PB13
PORT->Group[PORTB].PINCFG[13].bit.PMUXEN = 1;
  
// Set D4 the peripheral multiplexer to peripheral GCLK (12): GCLK7 Input
PORT->Group[PORTB].PMUX[13 >> 1].reg |= PORT_PMUX_PMUXO(MUX_PB13M_GCLK_IO7);

This makes the code SAMD51 board agnostic, so it should work on either the Metro M4 or the Grand Central.

Hi MartinL,

A big thank you to getting back to me.

Can you please clarify "The easiest thing to do is to change the output configuration to work with the microcontroller's port pins".

Sorry for such a NOB question, but how do I do this, and where do you get the information? I noticed in your earlier code the "non Arduino" Port defines syntax, where did you get this information?

Also, why are the ports different between the Metro M4 and Grand Central M4? according to the Atmel datasheets although they are different IC packages, the ports should still be the same (where available) - is there some software define which is redefining these ports / pins?

I have an Arduino Due - did you have to modify the your earlier software for this board (over the Metro M4 version)?

Thanks in advance :slight_smile:

... but how do I do this, and where do you get the information? I noticed in your earlier code the "non Arduino" Port defines syntax, where did you get this information?

The CMSIS register definitions for the SAMD51 are buried (on my Windows machine) at:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\ATMEL\samd51\include...

Here you'll find the directories "component", "instance" and "pio".

The component directory contains the register definitions and bitfields for each peripheral type, for example adc.h, tc.h, tcc.h, etc...

These definitions can be used to specify the entire register (in the "tcc.h" file):

TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE;  // Enable the TCC0 timer

Or alternatively a bit or bitfield in the register:

TCC0->CTRLA.bit.ENABLE = TCC_CTRLA_ENABLE_Pos;  // Enable the TCC0 timer

or alternatively:

TCC0->CTRLA.bit.ENABLE = 1;  // Enable the TCC0 timer

These "component" definitions are usually the preferred method of specifying registers.

An alternative notation is specified in the "instance" directory, which contains an instance of each periperal, for example tc0.h, tc1.h, tc2.h, tcc0.h, tcc1.h etc...

These defintions can also be used to specify an entire register (in the "tcc0.h" file):

REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE;  // Enable the TCC0 timer

However, this notation is less commonly used.

The "pio" register is used for GPIO definitions. Personally, I rarely use it.

Also, why are the ports different between the Metro M4 and Grand Central M4? according to the Atmel datasheets although they are different IC packages, the ports should still be the same (where available) - is there some software define which is redefining these ports / pins?

There are three sets of pin numbers: the IC pin numbers (1, 2, 3, etc..), the port pin numbers (PA00, PA01, PA02, etc...) and the Arduino pin numbers (D0, D1, D2, etc...).

As you mention, the SAMD51 microcontrollers themselves maintain port pin continuity across the different size chip variants. However, unlike on their SAMD21 (M0) boards, Adafruit doesn't mantain port pin to Arduino pin continuity on their SAMD51 (M4) series: the Metro M4, Feather M4, Itsy Bitsy M4 and Metro M4 Grand Central port pins do not go to the same Arduino board pins. This means for example that port pin PB13 on the Metro M4 is connected to D4, while the same port pin on the Grand Central is connected to UART RX1.

This means that it's possible to write register code for the Adafruit Metro M0 and have it work on the same Arduino pins on the Feather M0, the same is sadly not true for the Metro M4 and Feather M4.

I have an Arduino Due - did you have to modify the your earlier software for this board (over the Metro M4 version)?

The code I used for the Arduino Due, that generates a 28MHz PWM pulse on D9 is as follows, I just connected the grounds between the boards and plugged D9 output on the Due into the D4 input on my Metro M4:

// Output a 28MHz PWM waveform on pin D9 (PWML4)
void setup() {
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                            // Enable the PWM Controller 
  PIOC->PIO_ABSR |= PIO_ABSR_P21;                               // Set PWM pin perhipheral type A or B, in this case B
  PIOC->PIO_PDR |= PIO_PDR_P21;                                 // Set PWM pin to an output
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);             // Set the PWM clock rate to 84MHz (84MHz/1=84MHz) 
  PWM->PWM_CH_NUM[4].PWM_CMR = PWM_CMR_CPRE_CLKA;               // Enable single slope PWM and set the clock source as CLKA
  PWM->PWM_CH_NUM[4].PWM_CPRD = 3;                              // Set the PWM frequency 84MHz/28MHz = 3 
  PWM->PWM_CH_NUM[4].PWM_CDTY = 2;                              // Set the PWM duty cycle 
  PWM->PWM_ENA = PWM_ENA_CHID4;                                 // Enable the PWM channel 4 
}

void loop() {}

Martin,

Thank you for your concise reply, sorry for the delay in replying - I needed to digest how much I still need to learn to move forward.

I was unsure of the command syntax eg. "PMC->PMC_PCER1 |= PMC_PCER1_PID36; "

I've only learnt that the -> "operator" is pure C - not proprietary or unique to CMSIS syntax...

So "PMC" above is a pointer to a structure,

So PMC->PMC_PCER1 refers to an element called PMC_PCER1 of a structured pointed to by PMC.

Now that I understand this I can read up more about C structures etc... before I try to work with what currently appears to me as just "obscure" code...

I'm still lost as to where you get the info about these structures - is there a document that describes these structures and there defines / syntax (with regards to configuring the hardware registers of the SAM in software)... how does one know these "commands"?

I cannot see how the Datasheet info on the SAMD hardware registers is then translated into commands that the C compiler understands... as you can see I really still have a lot to learn here...

Its times like this where hardware design seems so much more simple - with software having so many layers that you need to understand... which I currently have very little insight!

Hi johnwestlake,

The primary source of register information is usually the microcontroller's datasheet. Normally, each microcontroller has a set of files defining each register.

Most microcontroller manufacturers provide some sort of library as a hardware abstraction layer, such as Atmel's ASF (Advanced Software Framework), but personally I find this it easier to just use the datasheet in conjuction with the register definition files opened in a file editor like Notepad++.

It takes a little while to become familiar with the definitions and how they relate to the datasheet. Each microcontroller is different from the next, but it does gradually become easier with time.

Each of the microcontroller's peripherals are mapped in memory at specific locations (memory addresses). The definitions simply provide a human readable name for each these locations.

For example to use the Arduino Due's PWM controller, it's necessary to first turn it on.

This information is provided by the SAM3X8E microcontroller datasheet, in the PWM Controller section 39, 39.5.2 Power Managment is states that the peripheral must first be enabled in the Power Management Controller (PMC).

Not obvious this one, but in section 11, 11.1 Peripheral Identifiers states that the PWM Controller's identifier is 36. (The later SAMD datasheets are a bit easier to follow in this respect).

Going to the Power Management Controller (PMC) section, there's a register table that includes a set of Peripheral Clock Enable Registers (PCER). PMC_PCER1 contains the PID (Peripheral ID) for peripheral 36.

The register definitions for the SAM3X8E are found here:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\ATMEL

Entering the "sam3xa" and "include" directories then opening the "sam3x8e.h" definition file there's an entry for the PMC peripheral base address:

#define PMC        ((Pmc    *)0x400E0600U) /**< \brief (PMC       ) Base Address */

The PMC definition is simply a pointer to the base address of the PMC peripheral struct (structure) in C code.

The structure it points to, is located by entering the "component" directory and opening the "pmc.h" file. In the structure there's an entry for the PMC_PCER1 register, offset from the base address by 0x0100(hex). This structure maps the memory location of all the PMC peripheral's registers.

 __O  uint32_t PMC_PCER1;     /**< \brief (Pmc Offset: 0x0100) Peripheral Clock Enable Register 1 */

To access this register using C, we call on the pointer to the structure PMC, then use the arrow operator (again in C), to access the contents of the specified structure's member PMC_PCER1:

PMC->PMC_PCER1

However, to activate the PWM controller we neet to set the bit PID36. In the same "pmc.h" file there are definitions for each PMC's register bitfields. The bit(field) we require is PMC_PCER1_PID36. To set it without disturbing all the other bits settings in the register, we do a read-modify-write by reading the PMC->PMC_CER1 register, ORing it the PMC_PCER1_PID36 bitfield then writing the result back to the register (using the symbols |=):

PMC->PMC_PCER1 |= PMC_PCER1_PID36;

This line activates the PWM controller.

Hope this describes the process, I can be quite tedious, but does become faster with time. Often I find it necessary think about what I would like to achieve, read the datasheet chapter on the peripheral in question, concentrate on the registers that do what I need (ignoring those that don't), then search out the associated register definitions.

Martin,

Merry belated Christmas and best wishes for the New Year!!

Wow, once again a very informative reply, thank you for taking the time to explain and try to help me out here.

Sorry for the delay in reply - I didn't receive a notification from the forum about your latest post.

I set a target to teach myself Verilog over the Christmas period so I can start working with FPGA's and although its still very early days I find Verilog easier then working with MCU's and C.

I'm glad that I taught myself the basics of C as one can see that Verilog is "C Like" in many ways.

I did order a Metro M4Express which has arrived yesterday, so I'll try your original code.. I plan to use the 64pin packaged SAMD51 on my PCB anyway so maybe its better to work with the M4 Express over the larger Grand Central I originally had on hand.

At this time for the PCB design, I just need to know which I/O pins I need for the Ref Clock and Freq (Input) to be counted... then later I can pull my hairout trying to understand the Atmel internal registers... I still dont understand how to access the registers one finds in the device datasheets from the Arduino software - my C level is still very basic and I need to understand the "layers" between software and the hardware registers... I should start with understanding how C "Structures" work...

Hi John,

Happy New Year!

...how to access the registers one finds in the device datasheets from the Arduino software...

Fortunately the SAMD51's frequency meter one of the simpler peripherals. To get an understanding of how the frequency meter code relates to the register definitions, it's probably worth looking at it's register definitions in the file: "freqm.h" and how they relate to the SAMD51's frequency meter code above:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\ATMEL\samd51\include\component\freqm.h.

I just need to know which I/O pins I need for the Ref Clock and Freq (Input) to be counted...

The other thing with the SAMD family of microcontrollers, is that the on-chip peripherals can be driven with clocks that are asynchronous to the main CPU clock. This allows for complete peripheral flexibility, at the expense of extra complexity. This means that it's often necessary to route an on-chip or external clock source to a perhipheral via the SAMD's generic clock system, before it can be used.

In the case of the frequency meter, it's necessary to configure two generic clocks, one as the measurement clock and the other as the refrence clock. As the Arduino core code uses generic clocks 0 to 4 for the SAMD51's 120MHz, 48MHz, 100MHz, 32.768kHz and 12MHz clock sources respectively and the fact that only generic clocks 0 to 7 can be routed to the microcontroller's IO pins, I chose generic clocks 6 and 7.

Generic clock 6 acts as a the reference, and in the example code above is generated from Metro M4's external 32.768kHz crystal clock source. It's also possible to alternatively supply this clock source from an external input pin as well, if you have a more accurate reference. In this case, generic clock 6 is on port pin PB12, that's physical pin 25 on the 64-pin chip variant, or pin D7 on the Adafruit Metro M4. The on-board 32.768kHz crystal is most likely accurate to around ±20ppm.

Generic clock 7 is the measurement signal and is configured to be supplied from an external input, in this case generic clock 7 is on port pin PB13, or physical pin 26 (again on the 64-pin variant), or D4 on the Metro M4.