Arduino frequency measurement to 2 or 3 decimal places

I am in the process of building a Proton Precession Magnetometer to calculate the earths magnetic field strength by measuring the precession frequency of protons in distilled water. I am using an Arduino Nano as my micro controller to control the energizing or the magnetometer, which is basically a resonant coil wound around a tube of distilled water, and then reading the returned precession frequency.

My set up is the Nano switches a DPDT relay to energise the coil from a 12v source then switch over to a receiving set of opamps / schmitt trigger IC into Pin 5 of the Nano to read the frequency which has a around 2.35 kHz with a bandwidth of +/- 500hz.

The problem I have is at the moment I can only get a frequency reading to 0.1hz resolution after counting the frequency for 5 seconds (ie 2351.5Hz).

But the result is quite stable compared to some other frequency codes I have tried.

I really want to get a stable result at least to 2 decimal places or better 3 decimal places over a shorter counting period ie (2351.523Hz).

Also I'm pretty new to arduino and coding.

My code at the moment is as follows:

#include <FreqCounter.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 8, 6, 7);

int mosfetPin =  9; // sets mosfet pin to pin 9 on arduino
int relayPin = 10; // sets relay pin to pin 10 on arduino

void setup() {

lcd.begin(16, 2);
pinMode(mosfetPin, OUTPUT); // sets mosfet pin, pin 9 on arduino as output
pinMode(relayPin, OUTPUT); // sets relay pin, pin 10 on Arduino as output
digitalWrite(mosfetPin, LOW); // sets 0v to pin 9, creating open cicuit and no power going thru negative line
digitalWrite(relayPin, HIGH); // sets relay pin, pin 10 to 5v and switches relay and connects the common to the NO and creates circuit from sensor to amp.

}

float frq = 0;

void loop() {

digitalWrite(relayPin, LOW);          //set relay to NC and connecting polarising battery to sensor
delay (50);                                // small delay prior to polarising the coil
digitalWrite(mosfetPin, HIGH); // connects negative power supply through mosfet and polarises the coil.
Delay(4000);                              // polarises the coil for 4 seconds
digitalWrite(mosfetPin, LOW);      // cuts power thru negative supply
delay(50);                                  // small delay prior to switching relay to sensor circuit
digitalWrite(relayPin, HIGH);        // switches relay over NO state to allow sensor signal to amp.

lcd.clear();
lcd.setCursor(0,0);
FreqCounter::f_comp= 300;         // Set compensation
FreqCounter::start(5000);            // Start counting with gatetime of 5000ms

lcd.setCursor(0,1);
lcd.print(frq/5);
lcd.print(" Hz");

delay(1000);
}


• Welcome to Electronics Stack Exchange! It would help us if you explicitly identified your question, i.e. have a question mark somewhere asking a specific question rather than a general call for help. A circuit schematic would also be a helpful addition, and if you identify the FreqCounter library you are using that would be appreciated, is it this one github.com/PaulStoffregen/FreqCount? – jramsay42 May 20 '20 at 7:15
• To do this by frequency measurement, simply count for longer (100x longer for 2 extra digits precision). To (potentially) get results faster, switch to period measurement instead, then invert to get frequency. Analyse the accuracy with which you can measure period (this is related to your clock freq) and measure the period of 1000 or 10000 cycles if you have to. – user_1818839 May 20 '20 at 8:04
• Yes the library is from github.com/PaulStoffregen/FreqCount. The signal returned from the sensor is a decaying sine wave, sounds like a 'ping'. So actually 5 seconds is too long and preferably a result after 1 second would be best. I have tried a few different code examples but at his stage only the freqcount library at 5 seconds has given me a consistent result matching my test frequency generator. – Phil Adkins May 20 '20 at 8:51
• the example number you show has 7 significant digits, not 2 or 3; that makes a large difference. So, is it right ot say you need a frequency precision of 7 significant digits. – Marcus Müller May 20 '20 at 9:42
• Regardless of how many digits you print, and regardless of how "stable" the value is, you must remember that the accuracy will be very poor. You are assuming that the clock frequency for the Arduino is exactly 16MHZ and it is not. For a consumer-grade product you should not expect better than about 100ppm. – Elliot Alderson May 20 '20 at 11:30

The problem is that the code is counting how many pulses happen during the measurement period. If you measure for 1 second, you have 1 Hz resolution. For 0.1 Hz or 0.01 Hz resolutions you need to measure for 10 or 100 seconds.

The other way to calculate this is how much time it takes between pulses, or between 100 or some other amount of pulses. The MCU has a 16 MHz clock, so in theory you can have up to 62.5 ns resolution. Depending on what hardware support you can use to help with the timing measurement (timer capture, external irq, or none) you could maybe expect better than 10 microsecond resolution.

Be aware that while you can count time in units of 16 MHz clock ticks for precision, the clock is not accurate, as it won't tick at exactly 16000000 Hz. The MCU uses a ceramic resonator for generating the clock, so it is only expected to have about 0.5% tolerance.

There are three ways to improve your resolution. Whether that translates into accuracy depends on your reference. You may or may not need accuracy. If you're prospecting for buried ferrous objects, then you only need to detect change over small distances and timescales, and you'll get away with a modest reference. If you want to detect changes over long periods of time, then you need to invest in a high quality reference.

1) Count a higher frequency - version a

You already have a fair bit of analogue there, and you already know the frequency ballpark. Using a PLL to multiply the signal frequency by 100 or even 1000 is quite possible. As a rule of thumb, a PLL needs at least 5 cycles to stabilise, so should be fast enough for your purposes. However, most regard PLLs as a bit of a black art, and setting up a fast stable loop is not for the beginner.

2) Count a higher frequency, version b

Swap your clock and input signals. Count your 16 MHz MCU clock, using a divided version of your 2.5 kHz input signal as the timebase. Divide by 256 would give you a 100 ms gate time, so 1.6 M resolution, better than 6 significant figures. As you're using C for your Arduino, the inversion from period to frequency is trivial.

3) Use a sigma delta frequency counter

This is a little-known technique that's rather cool. The problem with a standard counter is the +/- gate time error, so count for one second, get +/- 1 Hz error. What we can do is retime the input to fall precisely on the clock times, which eliminates this +/-1 error. The retiming is done in a noise-shaped way, so that a low pass filter can recover the average frequency to a large number of significant figures, increasing the number of sig fig over the stndard method by up to the retiming order. See this patent (long expired, so no legal problems) for a full explanation of the technique.

• While all of this is true, I would just emphasize that these techniques can improve resolution but not accuracy. The accuracy is still limited by the Arduino's oscillator. – Elliot Alderson May 20 '20 at 11:32

Counting pulses for 5 seconds gives you 11755 pulses. 2351 Hz / 11755 cycles give a resolution of 0.2 Hz.

You can instantly double the resolution by counting both low to high, and high to low transitions. But I'm guessing you want to do better than that.

To get any better you will need to start timing cycles. Many microcontrollers have an "input capture" module designed specifically for pulse timing.

I've done this recently with a teensy 3.6, and I highly recommend it. It has an input capture clock that can run at 60 MHz! Timing for 1 second with this will give you a resolution of ~0.00004 Hz.

Will it be accurate to 0.00004 Hz? Certainly not, but that will at least solve your resolution problem. Then it's just a matter of making sure the crystal is as accurate as possible, etc etc.

• At the moment I have only really have a bit of knowledge of the arduino, but is the teensy a similar kind of concept? ie I/O and uploading code? – Phil Adkins May 20 '20 at 8:58
• yes, but there's nothing about your problem that is device-specific. You can't get high frequency accuracy with short observation! Exactly as Drew says. It's a mathematical restriction, that has many well-known physical effects, for example the Heisenberg Uncertainty Principle is one of the consequences of the same fundamental mathematical principle. – Marcus Müller May 20 '20 at 9:49
• @PhilAdkins Yes the teensy is arduino compatible (it uses the ardunio IDE), but it comes with libraries for it's additional functions. – Drew May 20 '20 at 19:27
• @MarcusMüller The pulse capture can dramatically reduce the problem. Even if you could only break a cycle down into 4 segments, that's a huge increase in resolution without decreasing accuracy. – Drew May 20 '20 at 19:30
• @PhilAdkins The teensy also has example code specifically for measuring frequencies. Even without tweaking it's very accurate. pjrc.com/teensy/td_libs_FreqMeasure.html – Drew May 20 '20 at 19:32