Here is a simple Phase Locked Loop, which is a circuit used in radio communications for synchronisation between transmitter and receiver.

The loop works by calculating the (phase) difference between the input signal, and a reference oscillator, and then adjusting the reference until the phase difference is zero. In this code, the adjustment is made by approximating a digital biquad filter's output - simply by multiplying by the Q factor of the filter.

My main concern with this code, is that it would require some re-writing if I wanted to extend it.

import numpy as np
import pdb

class SimPLL(object):
    def __init__(self, lf_bandwidth):
        self.phase_out = 0.0
        self.freq_out = 0.0
        self.vco = np.exp(1j*self.phase_out)
        self.phase_difference = 0.0
        self.bw = lf_bandwidth
        self.beta = np.sqrt(lf_bandwidth)

    def update_phase_estimate(self):
        self.vco = np.exp(1j*self.phase_out)

    def update_phase_difference(self, in_sig):
        self.phase_difference = np.angle(in_sig*np.conj(self.vco))

    def step(self, in_sig):
        # Takes an instantaneous sample of a signal and updates the PLL's inner state
        self.freq_out += self.bw * self.phase_difference
        self.phase_out += self.beta * self.phase_difference + self.freq_out

def main():
    import matplotlib.pyplot as plt
    pll = SimPLL(0.002)
    num_samples = 500
    phi = 3.0
    frequency_offset = -0.2
    ref = []
    out = []
    diff = []
    for i in range(0, num_samples - 1):
        in_sig = np.exp(1j*phi)
        phi += frequency_offset

Here is the output.

PLL (green) converging to input signal (blue)


1 Answer 1


I didn't find much to say about your coding style.

Maybe in_sig is not a perfect name: signal_in would be easier to read in my opinion (putting the _in at the end is more consistent with you other variable names)

it would require some re-writing if I wanted to extend it.

What kind of extension ?

Using iterators to get the samples

For your input functions, you could use a generator instead:

def sinusoid(initial_phi, frequency_offset):
    """Generates a sinusoidal signal"""
    phi = initial_phi
    while True:
        yield np.exp(1j*phi)
        phi += frequency_offset

When initially called, this function will return an iterator: iter_sin = sinusoid(3.0, -0.2)

And add set_signal_in and signal_out methods in your SimPLL class:

def set_signal_in(self, signal_in):
    """Set a iterator as input signal"""
    self.signal_in = signal_in

def signal_out(self):
    """Generate the output steps"""
    for sample_in in self.signal_in:
        yield self.vco

(maybe signal_out could generate some tuples with the sample_in and phase_difference, or even yield self)


You can then do a pll.set_signal_in(iter_sin).

If you have a list of data, you can then do pll.set_signal_in(list_of_values).


For plotting a limited amount of points, you may use itertools.islice

  • \$\begingroup\$ Thank you! The main extensions I was thinking about were: handling non-sinusoidal (i.e. digital) signal_in, having different loop filters, and being able handle inputs as a whole array as well as on a per-sample basis. \$\endgroup\$
    – Tom Kealy
    Commented Feb 22, 2016 at 11:17
  • \$\begingroup\$ Your are welcome! The iterator suggestion might address some of your requests (with iter(list_of_values) for an array of samples): I updated my answer accordingly. To have different loop filters, I think you can create new child classes of SimPLL with another step method. Or create a PLL class without any step method (but the rest of the logic) and different child classes for the different loop filters. \$\endgroup\$
    – oliverpool
    Commented Feb 22, 2016 at 11:41
  • \$\begingroup\$ (the iter for the list of values is not necessary) \$\endgroup\$
    – oliverpool
    Commented Feb 22, 2016 at 13:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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