Go Down

Topic: Best microcontroller for frequency counter up to 30MHz (Read 3395 times) previous topic - next topic

CBnation

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.

Riva

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..
Don't PM me for help as I will ignore it.

DrAzzy

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.
ATTinyCore and megaTinyCore for all ATtiny, DxCore for DA/DB-series! github.com/SpenceKonde
http://drazzy.com/package_drazzy.com_index.json
ATtiny breakouts, mosfets, awesome prototyping board in my store http://tindie.com/stores/DrAzzy

gilshultz

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

This response is to help you get started in solving your problem, not solve it for you.
Good Luck & Have Fun!
Gil

Idahowalker

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

Pulse Counter https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/pcnt.html

Timer https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/timer.html
Quote
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 https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/ulp-legacy.html that can be programmed to be a timer or pulse counter or...



1/18/2018, got my first Arduino Uno.

MartinL

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:

Code: [Select]
// 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).

Smajdalf

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.

MartinL

Quote
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.

Code: [Select]
// 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)
  }
}

Smajdalf

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.)

MartinL

Hi Smajdalf,

Quote
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.

UKHeliBob

@johnwestlake

Topic reopened as requested, and "Report to moderator" was the correct way to ask
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

johnwestlake


@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
Brilliant, Thank you.

johnwestlake

#12
Dec 16, 2020, 12:05 pm Last Edit: Dec 17, 2020, 03:48 am by johnwestlake
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!  :)

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 :) ... 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!!!

MartinL

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):

Code: [Select]
// 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.

johnwestlake

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 :)

Go Up