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
    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
    Feb 22, 2016 at 11:41
  • \$\begingroup\$ (the iter for the list of values is not necessary) \$\endgroup\$
    – oliverpool
    Feb 22, 2016 at 13:11

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .