11
\$\begingroup\$

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.update_phase_difference(in_sig)
        self.freq_out += self.bw * self.phase_difference
        self.phase_out += self.beta * self.phase_difference + self.freq_out
        self.update_phase_estimate()


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
        pll.step(in_sig)
        ref.append(in_sig)
        out.append(pll.vco)
        diff.append(pll.phase_difference)
    #plt.plot(ref)
    plt.plot(ref)
    plt.plot(out)
    plt.plot(diff)
    plt.show()

Here is the output.

PLL (green) converging to input signal (blue)

\$\endgroup\$
1
\$\begingroup\$

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:
        self.step(sample_in)
        yield self.vco

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

Usage

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).

Plotting

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

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

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.