The right way to count frequencies from a few Hertz up to 6 MHz using an Arduino board. Configure hardware timers and use interrupts. Add a display and build a low cost frequency meter.
A good way of measuring frequency is by counting input signal transitions that happen in a specific amount of time. This requires knowledge of timers and interrupts. The method is more difficult to implement and to do it right you need to set some registers.
This has been done before and although it was hard to find, I discovered code that can count frequencies up to 8 MHz if the input signal has a duty cycle of 50%. The only drawback is that frequency input pin is fixed to digital pin 5. The upper range is however not limited to only a few MHz. With some extra hardware (a prescaler IC) frequencies of hundreds of MHz can be measured with enough accuracy.
Hardware is very simple: hook up any display to Arduino, but avoid using pin 5. Don’t have one? Write measurements to serial port. The only piece of hardware you need is the AVR development board. Software is the biggest challenge. My code is based on the library written by Martin Nawrath and code published by Nick Gammon.
What you see in my breadboard in the above photo is the Arduino Nano compatible board and a simple crystal oscillator built around an old 74LS04 hex inverter.
The software uses two timers. The first timer will be configured to use the unknown frequency as clock source. We’ll count its overflows using an interrupt. The second timer will fire an interrupt at precise intervals. This interrupt routine reads the current value of the first timer. Using this and actual overflows count, frequency can be calculated. The first step is to check ATmega328 datasheet:
The external clock must be guaranteed to have less than half the system clock frequency (fTn < fclk_I/O / 2) given a 50% duty cycle. […] However, due to variation of the system clock frequency and duty cycle caused by the tolerances of the oscillator source (crystal, resonator, and capacitors), it is recommended that maximum frequency of an external clock source is less than fclk_I/O /2.5. An external clock source can not be prescaled.Therefore the maximum frequency you can count with an ATmega328 Arduino is 16/2.5 = 6.4 MHz. Not much, but still way better than pulseIn. I’ll use Timer1 (which is 16 bit) to count input pulses of the unknown signal. With an input frequency of, let’s say maximum 8 MHz, the 16 bit register will overflow (reach maximum value of 65535) after 8.192 ms. That is too short for low frequency signals. The timer resolution needs to be extended with an overflow counter. On each overflow, a counter variable will increment (increasing timer resolution). So, Timer1 increments on each rising edge applied to D5 pin.
Timer2 will keep the… time. It’s an 8 bit timer, but we’ll let it count up to 124 only (this means 125 “ticks”). Counting frequency is determined by setting the prescaler to 128. With the 16 MHz clock frequency of Arduino boards, Timer2 will “tick” with a frequency of 16 MHz / 128 = 125 kHz. Count 125 times with a frequency of 125 kHz. How much time has passed? Well, 1 millisecond. Timer2 overflows at each 1 ms. How many times it overflows before Timer1 frequency is computed is a variable that can be changed if you wish. High frequencies can be sampled in short periods, while low frequency signals may be sampled for a longer period of time for accurate reading. The default value of samplingPeriod variable is set to 200 ms. I was able to measure as low as 50 Hz (20 ms period) with it—only 10 samples are counted.
Here is the code (also on GitHub):
// Arduino frequency counter from a few Hz up to 6 MHz // One Transistor, 2018 // https://www.onetransistor.eu/ // // Based on: // * Frequency Counter sketch by Nick Gammon (CC BY 3.0 AU) // http://www.gammon.com.au/timers // * FreqCounter library by Martin Nawrath (LGPL 2.1) // http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-frequency-counter-library/ // set sampling period here (in milliseconds): unsigned int samplingPeriod = 200; // Timer 1 overflows counter volatile unsigned long overflow1; void init_Timer1() { overflow1 = 0; // reset overflow counter // Set control registers (see datasheet) TCCR1A = 0; // normal mode of operation TCCR1B = bit(CS12) | bit(CS11) | bit(CS10); // use external clock source TCNT1 = 0; // set current timer value to 0 TIMSK1 = bit(TOIE1); // enable interrupt on overflow } ISR(TIMER1_OVF_vect) { overflow1++; // increment overflow counter } // Timer 2 overflows counter volatile unsigned int overflow2; void init_Timer2() { overflow2 = 0; // reset overflow counter GTCCR = bit(PSRASY); // reset prescalers // Set control registers (see datasheet) TCCR2A = bit(WGM21); // CTC mode TCCR2B = bit(CS22) | bit(CS20); // prescaler set to 1/128, "ticks" at 125 kHz OCR2A = 124; // counts from 0 to 124, then fire interrupt and reset; TCNT2 = 0; // set current timer value to 0 TIMSK2 = bit(OCIE2A); // enable interrupt } // interrupt happens at each 125 counts / 125 kHz = 0.001 seconds = 1 ms ISR(TIMER2_COMPA_vect) { if (++overflow2 < samplingPeriod) // add an overflow and check if it's ready return; // still sampling unsigned long totalSamples = (overflow1 << 16) + TCNT1; float freqHz = totalSamples * 1000.0 / samplingPeriod; Serial.print("Frequency: "); Serial.print((unsigned long)freqHz); Serial.println("Hz"); // reset timers TCNT1 = 0; overflow1 = 0; TCNT2 = 0; overflow2 = 0; } void setup() { // enable serial output Serial.begin(115200); Serial.println("Arduino Frequency Counter"); Serial.println(); // Disable Timer0; millis() will no longer work TCCR0A = 0; TCCR0B = 0; // start timer 1 (count frequency) init_Timer1(); init_Timer2(); } void loop() { // nothing here; interrupts perform everything // you can add user input that changes sampling period }
Unlike the projects I based my code upon, my sketch performs continuous frequency counting and displaying after each sampling period. Note that sampling period should be adjusted depending on desired frequency range. This directly affects display update interval. Autoranging is also possible by increasing sampling period if counted samples are few and the other way round. This code has been developed and tested only on ATmega328. Other microcontrollers may have different registers. Be sure to check the forum of Nick Gammon where he adapts similar code for ATmega2560 and explains very good how this is working.
It’s pretty easy to build a frequency meter now. Just add a display and input buffer. Unless you will only measure 5 V signals, you need a buffer circuit. This can be built using a transistor, an opamp or a Schmitt trigger.
whats the frequency of the crystal?
ReplyDeleteThat crystal is used to make a test frequency generator, which I measure with Arduino. Any crystal below 6.4 MHz can be measured.
DeleteIs the code suitable for Arduino mega, if so what would be the input pin instead of pin 5
ReplyDeletePin 47 may be used on ATmega2560. See http://www.gammon.com.au/timers, section Frequency Counter sketch for Atmega2560.
DeleteWhy is Timer0 deactivated: "TCCR0A = 0; TCCR0B = 0;" ? Is it not possible to let Timer0 activated and use it to generate a transformed output frequency signal based on the last measured input frequency (e.g. output a signal on pin 6 with half of the frequency measured on pin 5)?
ReplyDelete