How do I remove “jitter” when detecting frequency of Sine Wave

Input: Sine wave of any frequency centred at 1.66V

Output: Square wave with a frequency equal to input

I have been working on implementing a common DSP problem -- Detect the frequency of a Sine Wave. I have used a handful of posts from stack exchange to get me started, and found This answer best suited for my problem. However, implementing the solution and measuring both the input signal (sine wave) and the output signal (square wave) with an oscilloscope, it appears I am getting some "Jitter" on my output signal.

Does anyone know what could be causing this? Or is this just... as accurate a detection I can get?

Here is the code I am using to detect the frequency of the sine wave:

volatile int period;             // equal to the number of samples taken between zero crossings
volatile float frequency;        // the calculated frequency of the sine wave
int numSamplesTaken = 0;         // used in frequency calculation formula

int currValue = 0;               // the current sampled value of sinewave input
int prevValue = 0;               // the previous sampled value of sinewave input

int zero_crossing = 32767;       // ADC range is 0v - 3.3v, so the midpoint of the sine wave should be 1.65v (ie. 65535 / 2 = 32767)
int threshold = 500;             // for handling hysteresis
int isPositive = false;          // whether the sine wave is rising or falling

// interupt occuring at 8000hz
void sampleSignal() {

currValue = input.read_u16();   // convert analog voltage input (sine wave) to a 16 bit number

if (currValue >= (zero_crossing + threshold) && prevValue < (zero_crossing + threshold) && isPositive) {

output.write(0);               // write digital output pin LOW
isPositive = false;

} else if (currValue <= (zero_crossing - threshold) && prevValue > (zero_crossing - threshold) && !isPositive) {

output.write(1);               // write digital output pin HIGH
period = numSamplesTaken;      // how many samples have occurred between positive zero crossings
frequency = 8000 / period;     // sample rate divided by period of input signal
numSamplesTaken = 0;           // reset sample count
isPositive = true;
}

prevValue = currValue;
numSamplesTaken++;
}


• For a single tone signal, I don't think you can get better and as efficient results than with this technique: dsprelated.com/showarticle/1284.php – Cedron Dawg Jun 21 '20 at 14:46
• if you're doing zero crossing, try doing a moving average of the measured period. a simple way to do that is to count the sample periods between the first and last of 20 zero crossings and divide by 10. or the first and last of 200 zero crossings and divide by 100. – robert bristow-johnson Jun 24 '20 at 4:38

Your interrupt seems to occur at 8 kHz. There are 2 obvious jitter causes that I can see.

First of all if $$\frac{f_s}{f} \neq N$$ where N is an integer, $$f_s$$ is the sampling frequency and $$f$$ the signal frequency, you will get jitter since the sampling instants will not be "periodic". Try to set f to 80 Hz, you will have 100 samples per period, check if that fixes your jitter.

Secondly, there could be latency caused by your ADC (is it integrated to your microcontroller? SPI, I2C?), latency caused by the ISR, etc. that could cause the jitter.

• I am having a hard time understanding your formula - what is f suppose to represent? Is N period? As for latency, I can't imagine there being much since the ADC is internal to the MCU and the interupt routine is the only task I have given the MCU 🤷‍♂️ – scottc11 Jun 21 '20 at 15:35
• f : frequency of the signal fs : sampling frequency – Ben Jun 21 '20 at 16:04

Interrupt driven sampling can be surprisingly jittery all by itself. It depends on a great many things how much time elapses between the desired moment of time and the sample time.

The easiest solution would be use an ADC that can be synchronized to a simple clock. One edge tells the ADC to start the sample, then the other edge can drive your interrupt to read the result. Even if it is a built-in ADC, your chip may be able to set up for that.

If that isn't possible here, a tougher solution would be to set up a high-res timer, and then time-stamp each sample, and use the time info to adjust your measurements accordingly.