I need an algorithm to detect frequency and phase of a pure sine signal. The frequency of the input signal changes between 0 and 100 Hz.

The value of the signal gets captured with a frequency of 20 kHz (so I get 20000 values per second) - this is given and cannot be changed. I need to detect frequency and phase of this input signal and using PWM generate MCU interrupts with the same frequency as the input signal.

Can anyone suggest what algorithm to use to do this simple and efficient? Maybe Goertzel algorithm?


Note: I originally posted this answer for the Stack Overflow copy of this question, before realizing that it had also been asked here. It somewhat duplicates pichenettes' answer, but I felt it still worth (re)posting here, since it includes some extra details. (Whether those details are useful or not, I'll leave for you and the OP to judge.)

If you know your signal is a pure sine wave, you can just use zero crossing detection. Each cycle of the sine wave will have two zero crossings: one from negative to positive, and one from positive to negative. This will be a lot simpler and more efficient than trying to do something fancy like Fourier transforms.

There are a few details to keep in mind, though:

  • It's OK for the signal to be slightly biased, but if the bias might exceed the amplitude of the signal, you'll need to correct it somehow.

    You can either do this before sampling with an analog high-pass filter, or you can track a moving average of your sampled signal and use it as the "zero level" to compare to. Or you can, instead, look at zero crossings of the difference between successive samples (which correspond to the maximum and minimum of each cycle in the signal) to avoid any bias issues.

  • If your input (or ADC) is noisy, your samples might randomly fluctuate around the zero level when the signal is close to it. In such cases, naïvely comparing successive samples might detect multiple zero crossings where there's really just one.

    One way to fix this issue is to smooth your signal before processing it, but it may be easier and more efficient to implement hysteresis in your zero-crossing detector. That is, you'd only detect a positive-to-negative crossing when the signal dips below some pre-set threshold level −ε, and a negative-to-positive crossing only when it rises above +ε, like this pseudo-C code:

    boolean isPositive = (firstSample > 0);
    while (running) {
        int signalLevel = getNextSample();
        if (isPositive && signalLevel < -threshold) {
            isPositive = false;
        else if (!isPositive && signalLevel > +threshold) {
            isPositive = true;

In fact, you may not even need an algorithm for this at all, since, as noted in the comments below, you can implement zero-crossing detection with hysteresis in hardware with a simple Schmitt trigger, which basically converts the sine wave input into a square wave signal with the same frequency and (almost) the same phase, which you can then read as a simple digital input. You might even be able to use the output of the Schmitt trigger to drive the MCU interrupt pin directly.

  • 4
    $\begingroup$ If you feed your signal into a high gain op-amp's positive input, and the negative input is at zero volts, the output will be a square wave that goes to the positive rail when the input crosses above the zero volt point. It goes negative when input goes below zero. Count the time between positive-going edges and you have the frequency. If there's a DC bias, the duty cycle of the square wave will be wrong but the overall period will remain good. Having a sharp square wave improves phase measurement circuits, too. $\endgroup$ Nov 8 '14 at 23:34
  • 2
    $\begingroup$ @AlanCampbell: True. For a noisy signal, a Schmitt trigger would work even better. I was sort of assuming that the OP wanted to do this in software, since they asked specifically for an algorithm, but you're correct that using a DAC for this is really overkill, when all you really need is a hardware comparator and a digital input pin. (For all I know, the OP might even be able to drive the interrupts directly from the Schmitt trigger output.) $\endgroup$ Nov 9 '14 at 0:19
  • $\begingroup$ +1 on hardware op-amp / schmitt trigger + digital acquisition, rather than going through an ADC. $\endgroup$ Nov 9 '14 at 1:15
  • 1
    $\begingroup$ I need to do this in software, as I don't have any access to (already determined) hardware. $\endgroup$
    – jurij
    Nov 10 '14 at 10:09
  • $\begingroup$ Also, to add, it has been suggested to use Phase Locked Loop algorithm here, but I don't understand neither how it works, nor how to use it. $\endgroup$
    – jurij
    Nov 10 '14 at 10:12

If the signal is pure sinusoidal and noise-free, you can simply count the number of samples between positive zero crossings - and use this as an estimate of the period of your signal, from which you deduce the frequency. If necessary, wait and average over several periods to get a more robust estimate. There's a good chance this can be achieved without having to sample your signal - directly through an input pin of your microcontroller and through one of its timers.

As for the phase, well, phase relative to what?


Algorithm removed by consensus of replies.

  • $\begingroup$ On a synthetic noiseless pure signal, I can get 10^(-16) accuracy on a handful of samples. dsprelated.com/showarticle/1284.php The limit is the precision of the variable, it can do much better as it is theoretically exact. $\endgroup$ Jul 8 '20 at 11:19
  • $\begingroup$ @ Cedron Dawg Thanks for the link to your papers. I actually did also estimate the frequency by fitting FFT bins with a parabola (it looks like that is what you are suggesting) which I learned to do a decade ago from this CERN article:cds.cern.ch/record/738182/files/ab-2004-023.pdf. The accuracy was similar to the wave-shape fit I described above, but need to study your idea more to know which is better. By the FFT-CERN bin interpolation method I got 95996.90696 Hz sample (unknown error amount) rate instead of 95996.9088 and 95996.9090 Hz by sine shape fit. $\endgroup$ Jul 8 '20 at 11:47
  • $\begingroup$ The parabolic peak fit is also known as Jacobsen's estimator. It is an approximation and inherently limited requiring a large number of samples to get accurate. 1e-8 is not unreasonable for such a large number of samples. My methods (plural) are much more accurate. For an independent comparison using my original formulas see tsdconseil.fr/log/scriptscilab/festim/index-en.html The newer formulas are better yet. I also have time domain formulas which are significantly better than zero crossing interpolations starting with dsprelated.com/showarticle/1051.php $\endgroup$ Jul 8 '20 at 12:07
  • $\begingroup$ While OP hasn't specified, your answer deals with a recorded data, but that might be a live input. That this might be the case is due to the requirement of "simple and efficient" which might sound ambiguous, but the rest on this page understood the meaning behind it. In this case, the other answers are very much on point. I the data would be available beforehand, in full, then your answer is not "simple", but it is "efficient". $\endgroup$ Jul 8 '20 at 13:24
  • $\begingroup$ @aconcernedcitizen The OP is long gone. I was working with the premise of fischertranscripts that it need not be simple. For real time application of a single pure tone, for a quick an dirty estimate, I don't think you can do better than the time domain formulas I referenced. They are quite simple. I use the term "near instantaneous" in the titles very purposefully. My two bin solution only requires the calculation of two bins, not a full DFT, so in that sense it is aslo efficient, but you have to calculate the right two for the best results. You are correct, it is far from simple. $\endgroup$ Jul 8 '20 at 13:36

Frequency estimation of a sinusoid (in software) can be done robustly using an autocorrelation.

The center peak (lag of 0) will always be a (or the) maximum value. For a periodic signal, you'll get additional peaks every n lags. This second peak location tells you the period of the signal, in samples. However, you get no phase information.

You can then construct your own sinusoid of the same frequency, with a known phase reference. Then compute the cross-correlation function between your original signal and the reference signal. Now, the first peak value location will tell you the phase difference between the signals. Where the phase = 2*pi*(lag in samples)/(sinusoid period in samples)

  • $\begingroup$ I should add if the sinusoid period isn't a nice integral multiple of the sample period, then you can use the k-th peak in the autocorrelation function, and divide the resulting lag value by k to get a better estimate. $\endgroup$
    – wrjohns
    Mar 23 '16 at 18:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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