Phased-locked loop

This chapter describes the Phase-locked loop (PLL) Component implemented in VSlib. Currently, the only implemented PLL algorithm is a Rotating Reference Frame (SRF) PLL.

General interface

The PLL Component is a composite Component, owning a AbcToDq0Transform and a PID. Therefore, all Parameters of these children Components must be set before the PLL is ready to be used.

The PLL Component implements a two interaction methods, called synchronise and synchroniseWithDQ. Both take three double-precision floating-point arguments: a, b, and c for each component of the three-phase current or voltage. The output of synchronise is a single value: \(\omega t\), being the output of the single iteration of locking the phase. The output is guaranteed to fit in \([0, 2\pi]\) range, if the angle offset is set to 0. The output of synchroniseWithDQ returns a tuple with \(\omega t\), and in addition d and q components of the DQ0 frame.

The algorithm implemented in VSlib is equivalent to the following Simulink implementation:

Matlab Simulink model of the SRF PLL.

For more details regarding the API, see the API documentation for SRF PLL.

Usage example

#include <numbers>

#include "rootComponent.h"
#include "srfPll.h"

using namespace vslib;

void your_function(RootComponent& root)
{
    SRFPLL pll("pll", root);
    // PI parameters need setting, here we assume the following are set:
    // kp  = 50;
    // ki  = 200;
    // kd  = 0.0;
    // kff = 0.0;
    // b   = 1.0;
    // c   = 1.0;
    // N   = 1.0; // should be 1 when kd = 0
    // T   = 1.0e-4;
    // f0  = 1e-12;
    // actuation limit at numerical limits

    const double v_a    = 1.0;
    const double v_b    = -0.5;
    const double v_c    = -0.5;

    const double wt_pll = converter.pll.synchronise(v_a, v_b, v_c);

    // If observability of d an q components is required:
    const auto [wt, d, q] = converter.pll.synchroniseWithDQ(v_a, v_b, v_c);

    return 0;
}

Example usage in a vloop:

#include "vslib.hpp"

namespace fgc::user
{
    class Converter : public vslib::RootComponent
    {
    public:
        Converter() noexcept
        : vslib::RootComponent("example"),
          interrupt_1("stg", *this, 128, vslib::InterruptPriority::high, RTTask),
          pll("pll_1", *this)
        {
        }

        // Define your interrupts here
        vslib::PeripheralInterrupt<Converter> interrupt_1;

        // Define your public Components here
        vslib::SRFPLL pll;

        void init() override
        {
            interrupt_1.start();
        }

        void backgroundTask() override
        {
        }

        static void RTTask(Converter& converter)
        {
            // Read the input 3-phase voltage values:
            const double v_a = converter.m_data[0];
            const double v_b = converter.m_data[1];
            const double v_c = converter.m_data[2];

            // if you need only the angle:
            const double wt_pll = converter.pll.synchronise(v_a, v_b, v_c);

            // If observability of d an q components is required:
            const auto [wt, d, q] = converter.pll.synchroniseWithDQ(v_a, v_b, v_c);
        }

        private:
            // actual source of data omitted for simplicity
            std::array<double, 3> m_data{0.0};
    };
}   // namespace fgc::user

Performance

The execution time of the SRFPLL Component averages to 365 ns per call. This is about 40 ns more than an AbcToDq0Transform and a PID controller, spent on calculating the next step of the forward Euler algorithm and a floating-point modulo operation.