3
$\begingroup$

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?

$\endgroup$
5
$\begingroup$

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;
            handleZeroCrossing();
        }
        else if (!isPositive && signalLevel > +threshold) {
            isPositive = true;
            handleZeroCrossing();
        }
    }
    

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.

$\endgroup$
  • 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$ – Alan Campbell 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$ – Ilmari Karonen Nov 9 '14 at 0:19
  • $\begingroup$ +1 on hardware op-amp / schmitt trigger + digital acquisition, rather than going through an ADC. $\endgroup$ – pichenettes 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
3
$\begingroup$

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?

$\endgroup$
0
$\begingroup$

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)

$\endgroup$
  • $\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.