This all started with me wanting to use an Arduino to make a tachometer. This is all now working fine, but it got me wondering what the highest frequency digital signal that I could read?

So I built a simple test sketch and have run this on both a Nano and an ESP32. The result was about 6300Hz on both. Anything above this gives chaotic results. This is somewhat lower than I expected.

Has anybody done better than this?

  • Doing it all in software, yes, it'll be slow. There's hardware built in to most MCUs that will do it for you that can measure MHz ranges.
    – Majenko
    Commented Jan 23, 2021 at 17:32
  • could use a cheap 8 bit counter ic as a frequency divider; just feed the last bit into the MCU where your tach is now, and feed the tach into the clock/tirgger of the IC.
    – dandavis
    Commented Jan 25, 2021 at 16:43

2 Answers 2


As Majenko wrote, when you only rely on software to measure the frequency on a IO pin, it will always be rather slow. And it also depends on the code, that you use. Functions like digitalRead() are always slower than directly working with the control registers of the pin (Reference). As you didn't show your code, we cannot say more about that.

If you need to measure higher frequencies or you want to do other things in between (like outputting the data somewhere), it is better to use the microcontrollers buildin hardware. There are multiple ways to measure the frequency. 2 immediately come to my mind:

  • You could assign an ExternalInterrupt or a PinChangeInterrupt to the signal pin (limited choice of pins here), which triggers each time you see a rising (or falling) edge on the signal. In the ISR (Interrupt Service Routine) you could count up a variable. That value is the number of rising (or falling) edges, thus periods of the signal. The frequency is this value divided by the duration of the measurement. Every time you calculate the frequency, you reset the counter; reading it again after a defined duration. This should be better than just polling, but its not the best. (Note that a PinChangeInterrupt triggers on every edge, so you have to divide the frequency by 2)

  • You could configure an unused Hardware Timer/Counter to use your signal as external clock source. Let it count up until it overflows (or up to a value defined by you) and let it trigger an interrupt. Since the Arduino framework already uses Timer0 for timekeeping, you could divide the max value of the Timer/Counter by the time passed and you will get the frequency. I think this can get you in the MHz range.(As with PinChangeInterrupt, the Counter will increment on every edge, so again a factor of 2 here)

Both ways will calculate the frequency as a mean over a specific time. If you have a changing frequency, you might wanna manipulate measurement frequency. In the first case you can change the interval, in which you read the counter variable. In the second case you can change the max value, to which the Timer/Counter will count.

what the highest frequency digital signal that I could read?

(I assume you just want to read the frequency of the signal) That depends on many factors. If we use the second case from above (configuring a Hardware Timer as Counter connected to the signal) we have a limit on the maximum frequency, that we can provide. I found the relevant part in the datasheet of Atmega328p (which is the microcontroller on the Nano):

Each half period of the external clock applied must be longer than one system clock cycle to ensure correct sampling. The external clock must be guaranteed to have less than half the system clock frequency (f ExtClk < f clk_I/O /2) given a 50/50% duty cycle. Since the edge detector uses sampling, the maximum frequency of an external clock it can detect is half the sampling frequency (Nyquist sampling theorem). However, due to variation of the system clock frequency and duty cycle caused by Oscillator source (crystal, resonator, and capacitors) tolerances, it is recommended that maximum frequency of an external clock source is less than f clk_I/O /2.5.

So we should stay below the controllers clock frequency divided by 2.5, which would be 16MHz / 2.5 = 6.4MHz for the Nano. But before you reach that, your code might introduce further delays, which make the measurement less accurate. That depends on the implementation.

  • Dear Chris and Majenko, Thanks for the excellent answers. I have tried the approach of using the signal to trigger an interrupt. That helped a bit. I have not tried to feed the signal into a hardware counter. I don't need to do this at the moment but it is a useful thing to know.
    – Rob W
    Commented Jan 23, 2021 at 19:16

My DaquinOscope https://www.daqarta.com/dw_rroo.htm can use burst sampling to grab 1024 samples at high sample rates, then send them to a host computer over USB for display and analysis. For digital-only inputs it can acquire over 400,000 samples per second on an UNO, with each sample being a reading of 8 pins to make one byte in the record. It does this by reading a digital port (PIND or PINB) in a tight loop, with an optional delay on each read for slower sampling. DaquinOscope is free, and it uses the open-source DaqPort https://www.daqarta.com/dw_rraa.htm, both included with Daqarta, complete with code that you can modify as desired.

  • Oh Wow! - I've just had a surf through your site - lots of interesting stuff. Many thanks, but you should have warned me - I could easily lose weeks of my life here. I do have a vague aspiration to build myself an arduino based 'scope but I want to be able to use an Android device (phone/tablet) for the UI.
    – Rob W
    Commented Jan 25, 2021 at 10:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.