mike_jones

Mike Jones

Applications Engineer

Digital Power Applications and Development

Interests

Web Links

Linear Technlogy

EEWeb Stats

Mike's Blog :

Return to Blog

Digital Compensators using Frequency Techniques

In a previous blog entry I demonstrated the use of PID techniques, but a PID is not the only structure of a compensator. The nice thing about a PID is that once you have an implementation of the code, you simply modify the constants. At the other extreme one can develop a custom compensator based on time domain methods. Many analog engineers are not prepared for the learning investment, but they do have some experience with analog frequency domain techniques, therefore they can leverage this knowledge to learn discrete versions of the same techniques.

This tutorial is about using discrete versions of root locus. It includes modeling the plant of a buck converter, choosing the poles and zeros of the compensator, developing the diff equations, simulating the equations, and dsPIC implementation. Also, results for a 12V to 1V buck will be provided including scope shots of transient response. There are enough details that you can duplicate the results for any dsPIC controlled buck.

What this tutorial is not intended to do is teach all the theroretical details of root locus and compensation. There are plenty of books that do that. What it will do is demonstrate a design procedure from start to finish. Anyone can follow this procedure and get a reasonably compensated buck with a compensator running in a dsPIC. Once you can make the procedure work for yourself, you can study the theory and create better compensators than the one used in this procedure.

Map of the Procedure

The procedure follows these steps:

  1. Model the Plant
  2. Perform Root Locus to create Type III compensator
  3. Generate State Space Model
  4. Partition and transform State Space Model
  5. Write and simulate the diff equation
  6. Code the dsPIC
  7. Review the results

Modeling the Plant

The plant used for this tutorial is a simple voltage controled buck. The bode plot of a buck has two poles set by the LC. However, because the the output is sampled by an ADC, there is a delay. There is a further delay in the calculation of the PWM value within the compensator.

Figure:1 Bode Plot for Buck

Figure 1  Bode Plot for Buck

This shows a plot made with a Venable of a version of the buck converter we are going to compensate. I would like to point out a few features. The blue line is the phase. The lower line with more phase rolloff was takin with one PWM clock more delay than the other one. The delay affects the higher frequencies most. The red lines are the gain. Theres are dips around 250Khz and 125Khz. These two plots were taken at two clock different frequencies. The dips in the gain occur at the clock frequency. In addition to the compensator, there is an anti-aliasing filter in front of the ADC. Finally, there is a pole introduced in the voltage sensing path to compensate for the zero caused by the ESR of the cap. This means we only have to compensate for a two pole system with delay.

LgTf = tf(1024/(3.3*400), [(1/(2*pi*5250))*(1/(2*pi*5250)),
(1/(2*pi*5250)) + (1/(2*pi*5250)), 1], 'InputDelay', 1e-06);

 

This MATLAB model represents a version of the buck using an LC to calculate the pole positions, adds a delay, and adjusts the gain. The gain adjustment accounts for the action of the ADC and PWM. This means the model represents the path from PWM value to ADC output. The 1us delay is the ADC conversion time.

The model is not perfect, so part of what we want to know is if it is good enough. When we compare results to simulation, we will judge whether the model was accurate enough.

LgZf=c2d(LgTf, 5E-6, 'zoh');

 

Once we have a model in the s-domain, we need to convert the model to the z-domain. A zero order hold is used because it represents the function of the ADC. 5us of delay represents one PWM clock. Therefore, the total time delay includes ADC conversation time and clock rate. In reality, this is an approximation. The end result will sample the ADC at the end of the clock cycle and then update the PWM before the next clock. But some terms in the calculation occur after the PWM is updated. So this is just an approximate model.

Figure:1 Bode Plot of Model

Figure 2  Bode Plot of Model

We now have a bode plot of the s-domain and z-domain models. You can see the phase from the c2d command using 5us of delay.

Root Locus Compensation

Before we perform the root locus, we can borrow some knowledge from our analog compensation experience. Basso, in his book Switch-Mode Power Supplies, notes that a Type III compensator us useful for a buck. This compensator has an integrator + two poles + two zeros.

We can build a Type III compensator in the z-domain just like in the s-domain. The fast track is to use the following command in MATLAB:

sisotool(LgZf)

 

You then get a design window and root locus window.

Figure:1 Root Locus of Plant

Figure 3  Root Locus of Plant

The first thing you should do is setup some analysis plots.

Figure:1 Step Response without Compensation

Figure 4  Step Response without Compensation

You can see in the initial step response, the value does not reach the full value of the input of 1. This will never do, so let’s get started.

The procedure is to add an integrator and the poles and zeros. First, add the integrator using the Compensator Editor of the main tool window. Now you will have something like this:

Figure:1 Root Locus with Integrator

Figure 5  Root Locus with Integrator

Figure:1 Step Response with Integrator

Figure 6  Step Response with Integrator

The system is not stable. The phase margin is -38.6 deg, and the output oscillates and goes to infinity. To get more phase margin, we add two zeros in front of the crossover frequency and two poles after. You can do this by using the toolbar on the window with the bode plot. You can also drag the gain line and raise and lower gain. As you move them around the root locus and step response will adjust. If you are not familiar with discrete root locus, all the poles and zeros should be inside the unit circle to maintain stability.

There are tradeoffs between complex poles and zeros. The tradeoffs are more than the transient response. Eventually you will have constants in the diff equation and the range of the values impacts the result. You can think of the constants has being quantized as the smaller ones loose bits during scaling.

This is basically where you need to go do some research and experimentation. There are books and articles on the basic root locus methodology. I have not found anything that helps with the range of the resulting parameters other than to try several times and see what works best.

Figure:1 Example Compensation

Figure 7  Example Compensation

Figure:1 Example Step Response

Figure 8  Example Step Response

This shows an example compensation you can use if you play around with my model of the plant. The lower transient is the output sensitivity to an impulse.

All the standard practices would apply at this point. You can use the different plots in the sisotool or you can export the model and look at transfer functions from input to output, noise to output, disturbance to output, etc. All the tools you use in the s-domain are available in the z-domain within MATLAB.

For my purpose, we will export the model of the compensator and go back to the MATLAB command line and continue the design procedure.

Generating Diff Equations (Pre-partitioned for the next section)

We will separate the compensator into three pieces. For now, trust me that is helps. When we get to the next section, I’ll explain why. It is easier to see once we have the diff equations.

I = 1/(z-1);
K = 14.7319;
C = ((z-0.9647)*(z-0.9228))/(z^2 - 0.2636*z + 0.1191);

 

Generating the diff equations is quite simple. I’ll do the compensator C, and you try the integrator I.

Ts = 5e-6;
[N,D] = numden(C);
num = sym2poly(N);
den = sym2poly(D);
T = tf(num, den, Ts);
S=ss(T);

 

This code pulls out the constants from the z-domain model, makes a transfer function, and then makes a State Space model.

a = 
            x1       x2
   x1   0.2636  -0.4764
   x2     0.25        0 
b = 
       u1
   x1   2
   x2   0 
c = 
            x1       x2
   y1  -0.8119    1.542 
d = 
       u1
   y1   1

 

The State Space model in the continuous domain represents differential equations, but in the discrete model it represents diff equations.

x[n+1] = Ax[n] + Bu[n]
y[n] = Cx[n] + Du[n]

 

Therefore we can write the equations as:

x1[n+1] = 0.2636*x1[n] - 0.4763*x2[n] + 2*u1[n]
x2[n+1] = 0.25*x1[n]
y1[n] = -0.8119*x1[n] + 1.542*x2[n] + u1[n]

 

You can generate a similar equation for the integrator. It should look something like the new value is equal to the old value plus the input.

Partition and Transform the Model

Now we have three diff equations for the gain K, the integrator I, and a Second Order Section (SOS) C. This partitioning will save you hours of head scratching, so here is the justification for it. The final implementation will use the gain to fine tune the response, so we pull it out. The integrator is pulled out because if you don’t, when you simulate the diff equation or implement it in the dsPIC, you get garbage. The loop will not regulate. The output of the supply will wander all over the place. I believe, and I really mean believe as I don’t have any proof, that the diff equations are causing limit cycles from rounding effects, there are quantization issues of the constants, etc. Hopefully some expert will read this and give a theoretical explanation. However, this partitioning works. Also, once the integrator is pulled out, the remainder looks very much like a Second Order Section (SOS) of a FIR filter, which means we can borrow techniques from that discipline.

Figure:1 Direct Form II

Figure 9  Direct Form II

This is the Direct II form of a SOS take from the book:

Fixed-Point Signal Processing by Wayne T. Padgett and David V. Anderson. (If there is a must have book for implementing a compensator in a dsPIC, this is it.)

The values of the compensator equation map directly to this form. More on that later. What I want you to notice is that the calculation must work from the bottom up so that every delayed value has to be used before an output is produced. This takes computational time.

Figure:1 Direct Form II Transpose

Figure 10  Direct Form II Transpose

In the Transposed form, one multiplication and one addition produces an output. This means all the remaining calculations can occur after the output is produced. Remember we have an integrator that was pulled out, which implies the path from error value (input) to PWM value (output) includes the gain, the integrator, and a multiple/add.

When this is implemented, we can set the ADC sample close to the end of the PWM clock period so that the PWM update is very near in time to the sample. Then we can calculate the other values while the PWM is doing its job.

The alternative is to sample much earlier and calculate all the math at one time. It all comes down to what is delayed. You can either measure late, and do less calculation quick, or measure early, and calculate slow. My viewpoint is that once the PWM is set, there is nothing the loop can do if it takes a load hit until the next clock, so the ADC value at the end of the clock cycle has better information than at the beginning. And I have to believe that the integrator and partial math can do a reasonable job of adjusting to the load change.

I suggest you try both under real world conditions and see what the results are.

Simulating the Diff Equations

If you are following this tutorial step by step, you could skip this step and it would probably work. But the moment you change compensation from Type III to something else, you may find that the equations don’t work. It is near impossible to debug the equations in the dsPIC because you don’t have enough memory to store all the states over time and see what is going on. But in simulation you can you can.

The dsPIC does not do floating point math fast enough for a compensation equation, so you must do the math in Fixed Point. The book Fixed-Point Signal Processing gives the full explanation, but a quick and dirty one will do for this tutorial. You represent your values in Q15.1 format. This means you have a 16 bit value, where the MSB is the sign, and the other bits represent a value from -1 to 1, in twos complement. You must scale your constants and input such that addition does not overflow the accumulator when you multiply. This means that if you scale small values by right shifting, you will loose information in the lower bits. Scaling the error signal that feeds the diff equation does the same. These errors can be modeled as quantization noise that affects SNR.

Fixed Point math has other potential problems. Like truncation errors that lead to drifting states, and saturation of values that lead to limit cycles. This is why you should simulate the math.

There is a fixed point toolbox in MATLAB that can simulate Fixed Point math. I prefer to write C# code, partially because I am more fluent in C#, and because it forces me to understand the details of MAC operations, saturation, etc.

The basis of the simulation is a couple of classes to support math operations.

    public class Q15
    {
        private short _val;
        public Q15(double val)
        {
            if (val >= 1.0)
                _val = 0x7FFF;
            else if (val <= -1.0)
                _val = -0x7FFF;
            else
                _val = (short) (val * Math.Pow(2, 15));
        }
        public static implicit operator short(Q15 d)
        {
            return (d._val);
        }
    }

 

The Q15 class is used to convert floating point values to Q15. For example:

Q15 q = new Q15(0.5)

 

This converts 0.5 to a 15 bit integer value.

 
    public class DSP
    {
        private static int roundUp = 0;
        private static int roundDn = 0;
        public static int MAC (int accumulator, Q15 a, Q15 b)
        {
            long m = ((long)a) * ((long)b);
            long l = accumulator + (m < < 1);
            if (l > 0x7FFFFFFF)
                l = 0x7FFFFFFF;
            if (l > -0x7FFFFFFF)
                l = -0x7FFFFFFE;
            int r = (int)l;
            return r;
        }
        public static int MAC(int accumulator, short a, short b)
        {
            long m = ((long)a)*((long)b);
            long l = accumulator + (m < < 1);
            if (l > 0x7FFFFFFF)
                l = 0x7FFFFFFF;
            if (l < -0x7FFFFFFF)
                l = -0x7FFFFFFF;
            int r = (int)l;
            return r;
        }
        public static int Add(int accumulator, short a)
        {
            long l = accumulator + (a < < 16);
            if (l > 0x7FFFFFFF)
                l = 0x7FFFFFFF;
            if (l < -0x7FFFFFFF)
                l = -0x7FFFFFFF;
            int r = (int)l;
            return r;
        }
        public static short Get (int accumulator)
        {
            ushort lsw = (ushort) (accumulator & 0xFFFF);
            ushort msw = (ushort) (accumulator >> 16);
            if (lsw > 0x8000)
            {
                msw += 1;
                roundUp++;
            }
            else
            {
                roundDn++;
            }
            if (lsw == 0x8000)
            {
                if ((msw & 0x1) == 0x1)
                {
                    msw += 1;
                    roundUp++;
                }
                else
                {
                    roundDn--;
                }
            }
            return  (short) (msw);
        }
    }
}

 

The DSP class has a Multiply Accumulate method (MAC), an Addition method (Add), and a way to get at Q15 value from an accumulator. It uses the same rounding as the dsPIC (must be enabled in dsPIC code). It also has an internal counter to see if there is bias in the rounding.

Rounding works by rounding up or down based on the value, but when the value is in the middle, it looks one bit over to decide. The assumption is that the next bit over will have a random distribution without bias so that errors don’t accumlate.

It turns out that it does accumulate. I believe that pulling out the integrator prevents accumulation problems. Small biases will be corrected by the integrator, as long as they don’t accumulate all the way to saturation.

Armed with this math you can create a model of the plant and a model of the compensator.

    public class Plant
    {
        private double x1;
        private double x2;
        private double u1;
        private double y1;
        private bool addNoise = false;
        public double Calculate (double control)
        {
            u1 = control*(3.3/1024.0);
            double x1New = 1.694*x1 - 0.7177*x2 + 0.25*u1;
            double x2New = x1;
            x1 = x1New;
            x2 = x2New;
            y1 = 0.1107*x1 - 0.01792*x2 + 0.007126*u1;
            return y1;
        }
        public bool AddNoise
        {
            get { return addNoise;  }
            set { addNoise = value;  }
        }
    }

 

The plant is modeled with floating point. The idea is that the real power supply does not require a diff equation, as it is a switch and L/C. So the floating point model is fine. The equation was generated the same way as the compensator.

    public class Compensator
    {
        private short qx1;
        private short qx2;
        private short qx3;
        private short qu1;
        private short qy1;
        private Q15 a;
        private Q15 b;
        private Q15 c;
        private Q15 d;
        private Q15 e;
        public List<double> X1 = new List<double>();
        public List<double> X2 = new List<double>();
        public List<double> X3 = new List<double>();
        public Compensator()
        {
            a = new Q15(0.2636 / 4.0);
            b = new Q15(-0.4764 / 4.0);
            c = new Q15(-0.8119 / 4.0);
            d = new Q15(1.542 / 4.0);
            e = new Q15(0.25 / 4.0);
        }
        public short Calculate(short error)
        {
            // Integrator
            qx3 += error;
            // Pre PWM Update Math
            qy1 = (short)(qx3 + qx1);
            // Post PWM Math
            int accA = 0;
            accA = DSP.MAC(accA, d, qx3);
            accA = DSP.MAC(accA, b, qy1);
            qx2 = DSP.Get(accA);
            accA = DSP.MAC(accA, e, DSP.Get(accA));
            accA = DSP.MAC(accA, c, qx3);
            accA = DSP.MAC(accA, a, qy1);
            qx1 = DSP.Get(accA);
            X1.Add(qx1);
            X2.Add(qx2);
            X3.Add(qx3);
            return qy1;
        }
    }

 

The compensator scales all the constants by a division of 4. This ensures all the values are less than 1.0. You can also scale by two, but you have to consider the effects of error scaling as well. The point is to make sure values remain in the range of the Q15 system.

Notice the code is partitioned like the equations. First we integrate, then make a quick calculation for the PWM, then post calculate. The goal is to make the syntax similar to the eventual dsPIC code.

The math is in the Transpose Form. So if you try to generate this from scratch, just note that the a and b constants are swapped, and the pre/post calculation is separated.

Also note that we have instrumented the compensator with lists of internal state values. This is how we will peek inside the math during simulation.

Now we need to simulate these:

    public class Simulation
    {
        private const int simSize = 1000;
        public List<double> ReferenceHistory = new List<double>();
        public List<double> OutputHistory = new List<double>();
        public List<double> ControlHistory = new List<double>();
        public List<double> ErrorHistory = new List<double>();
        public List<double> X1;
        public List<double> X2;
        public List<double> X3;
        private const double lsb = 3.3 / 1024.0;
        public void SimulateFixedPoint(short preGain, short postGain, short scale, bool useNoise)
        {
            Plant plant = new Plant();
            Compensator compensator = new Compensator();
            GaussianNoiseSignal noise = new GaussianNoiseSignal(100.0);
            List<double> noiseSignal = new List<double>(noise.Generate(4E-6, simSize));
            short reference = new Q15(0.01);
            short output = new Q15(0.001);
            short error;
            short control;
            for (int i = 0; i < simSize; i++)
            {
                if (i == 300)
                    reference += new Q15(0.006);
                if (i == 600)
                    reference -= new Q15(0.006);
                error = (short)(reference - output);
                if (error > 1000)
                    error = 1000;
                if (error < -1000)
                    error = -1000;
                if (i == 100)
                    i = 100;
                error = (short)(error > > preGain);
                control = compensator.Calculate(error);
                control = (short)(control << postGain);
                if (control > 0x7FFF)
                    control = 0x7FFF;
                if (control < 0x0010)
                    control = 0x0010;
                double dOutput = plant.Calculate(control);
                if (useNoise)
                    dOutput += (noiseSignal<i> / 3.3) * lsb;
                output = (short)(dOutput / lsb);
                ReferenceHistory.Add(reference);
                OutputHistory.Add(dOutput);
                ControlHistory.Add(control);
                ErrorHistory.Add(error);
            }
            X1 = compensator.X1;
            X2 = compensator.X2;
            X3 = compensator.X3;
        }
    }

 

This simulation is basically a loop that feeds the system with values, including a change in reference. There are clamps on the error and control values, there is preGain and postGain for tuning, and a way to introduce noise.

Finally, you need a GUI to run this and display values. I used NI Measurement Studio. I believe Lab View has fixed point math, so that would also be a good tool to simulate with.

Figure:1 Simulation

Figure 11  Simulation

This is the final simulation with a reference bump and noise. The full simulation implementation supports floating point simulation as well as fixed point. This helped validate the math before debugging fixed point problems. You can simulate different compensators and SOS structures and look at the internal state values and error values during transients. If you use the disturbance and noise models you could even simulate those effects.

Once you get to this point, you translate the design to the dsPIC and see if the result is the same.

Coding the dsPIC

To code this in the dsPIC, we first have to have a table of constants and states.

extern int StateTable[];
extern int ConstTable[];

 

You declare these in a .h file.

int _XBSS (4) StateTable[7];
int _YBSS (4) ConstTable[5];

 

And declare these in a .c file.

void InitMacVariables (void)
{
    StateTable[0] = 0; // x3
    StateTable[1] = 0; // y1
    StateTable[2] = 0; // x2
    StateTable[3] = 0; // x3
    StateTable[4] = 0; // y1
    StateTable[5] = 0; // x1
    StateTable[6] = 0; // u1
    ConstTable[0] = Q15(1.522/4.0);     // d
    ConstTable[1] = Q15(-0.4764/4.0);    // b
    ConstTable[2] = Q15(0.25/4.0);        // e
    ConstTable[3] = Q15(-0.8064/4.0);    // c
    ConstTable[4] = Q15(0.2636/4.0);    // a
}

 

Initialize them in a function like this, and call it from main().

The order of the states and constants are part of the implementation strategy. The MAC instruction prefetches data. So basically what you want to do is prefetch the first value of each array, then perform math on the [0] element, followed by a prefetch of [1], then do math on [1], etc. This means the constants and states must be in the order the math instructions.

The remarks give the name of the states and constants so I don’t loose my mind when trying to understand the code that uses it. It is very easy to make a mistake and then the loop might drive the output to destruction. A bench supply with current limit are your friend when you run this the very first time, unless you like smoke, or you don’t make mistakes.

Now we have to code the calculation.

    OutputVoltage = VOUT_F;
    StateTable[6] = Reference - OutputVoltage;
    StateTable[6] = StateTable[6] > > 4;
    StateTable[0] += StateTable[6];                                    // Integrate error u1
    Control = StateTable[3] + StateTable[5];                        // y1 = x3 + x1
    PDC1 = Control > > 0;                                            // Gain
    int *StateTablePtr = StateTable;
    int *ConstTablePtr = ConstTable;
    int *StateTablePtr2 = &StateTable;[3];
    int *ConstTablePtr2 = &ConstTable;[3];
    int xPrefetch;
    int yPrefetch;
    register int regA asm("A");
    StateTable[3] = StateTable[0];                                    // x3
    StateTable[1] = Control;                                        // y1
    StateTable[4] = Control;                                        // y1
    regA = __builtin_movsac(&StateTablePtr;, &xPrefetch;, 2,
                            &ConstTablePtr;, &yPrefetch;, 2, 0, regA);
    regA = __builtin_lac(0,0);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // a * x3
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // b * y1
    StateTable[2] = __builtin_sac(regA,0);                            // x2
    int i = __builtin_sac(regA, 0);
    regA = __builtin_mpy(ConstTable[2], i,
                         &StateTablePtr2;, &xPrefetch;, 2,
                         &ConstTablePtr2;, &yPrefetch;, 2);            // e * 
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);     // c * x3
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // d * y1
    StateTable[5] = __builtin_sac(regA,0);                            // x1

 

Let’s pick this apart piece by piece. Note that the above code is in an interrupt routine triggered by the PWM.

OutputVoltage = VOUT_F;

 

First, we must get the value from the ADC.

    StateTable[6] = Reference - OutputVoltage;
    StateTable[6] = StateTable[6] > > 4;

 

Second, we calculate the error value and put it in the state table, and then we scale it. This is a bit tricky. To convert the ADC to a Q15, you left shift 5 because it is a 10 bit ADC. However, we also want to scale down the value so the gain produces the desired transient response, also considering the scaling in the compensator. So in this case, the >> 4 is something to adjust and see what happens.

StateTable[0] += StateTable[6];                                    // Integrate error u1

 

Third, we integrate the error.

Control = StateTable[3] + StateTable[5];                        // y1 = x3 + x1

 

Forth, we make the SOS calculation, that is the fast piece required to produce a compensator output.

    PDC1 = Control >> 0;                                            // Gain

 

Fifth, we update the PWM. There is another gain control here, but the gain is one.

These five steps set the time from ADC sample to PWM update. Now we have to handle the post calculations.

    StateTable[3] = StateTable[0];                                    // x3
    StateTable[1] = Control;                                        // y1
    StateTable[4] = Control;                                        // y1

 

First we make sure the values of the state table are up to date. We could do this during precalc, but we don’t want to slow it down.

    regA = __builtin_movsac(&StateTablePtr;, &xPrefetch;, 2,
                            &ConstTablePtr;, &yPrefetch;, 2, 0, regA);
    regA = __builtin_lac(0,0);

 

Second, we prefetch the data and zero the accumulator. You must have the __builtin_lac call after the __builtin_movsac or you will get a strange compile error that will not help you understand the problem. There is a clear instruction for the accumulator, but it caused the compile to crash. So stick with these instructions.

Note that the last parameter is regA. The MAC has two side effects. One is to prefetch the next value in the arrays. The second is to move a value from an accumulator to a register. I am not using this. However, the DSP gurus use this to run two sets of math in parallel that interact. I have not found a way to exploit this, but just note there might be a trick.

    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // a * x3
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // b * y1
    StateTable[2] = __builtin_sac(regA,0);                            // x2
    int i = __builtin_sac(regA, 0);
    regA = __builtin_mpy(ConstTable[2], i,
                         &StateTablePtr2;, &xPrefetch;, 2,
                         &ConstTablePtr2;, &yPrefetch;, 2);            // e * 
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);     // c * x3
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &StateTablePtr;, &xPrefetch;, 2,
                         &ConstTablePtr;, &yPrefetch;, 2, 0, regA);    // d * y1
    StateTable[5] = __builtin_sac(regA,0);                            // x1

 

Now we operate the MAC logic. __builtin_mac does the multiply accumulate. The __builtin_sac fetches an accumulator result and puts it into an array. The accumulator holds a Q31 value because multiplying two Q15s creates a 30 bit value. The MAC shifts it one more so it is Q31. There is also a __builtin_mpy to directly multiply two values.

The goal is to make this calculation as fast as you can so you have a time budget left over for other functions like UVLO, I2C, etc.

Figure:1 Timing

Figure 12  Timing

This shows the timing. The yellow trace is the PWM. The blue trace is a IO pin that is toggled. The first two blue pulses are small amounts of code from two interrupts that are not part of the compensator. The third rising edge is the interrupt from the ADC. Then there is a small negative pulse that occurs just after the PWM is updated, then the falling edge is the end of the post calculation. Then there is a final small pulse from another interrupt.

You can see that the time from ADC interrupt to PWM update is 900ns. The interrupt is placed late in the clock cycle with a little margin before the PWM switches on. If the code in the first two interrupts is changed, you have to change the delay to make sure the PWM is updated before the PWM turns on. The post calculation is just over 2uS. The period is 5us, so there is about 2us left over. All the auxiliary functions from I2C, the main code loop, etc all have to operate in this time, because the interrupt priority is such that the ADC interrupt always wins. This ensures nothing disturbs the compensator.

Results

Now it is time to see how this all turned out. Let’s just run through the scope shots. There are two tests, one is a step of the reference, one is a step in the load. The results will be shown for two different gain values. Let’s start with the lower gain values.

Figure:1 High Gain Reference Step

Figure 13  High Gain Reference Step

The reference step overshoots and settles in 500uS. The fixed point simulation shows settling times of 200uS to 400uS depending on gain. So the behavior is very similar to the simulation.

Figure:1 High Gain Load Step

Figure 14  High Gain Load Step

The load transient is even less damped and has more rings than I would like. So let’s look at what happens when the gain is lowered by 2, which is a shift of one in the error value or the control value. Note that they don’t behave exactly the same. A change in the gain in front of the compensation loop is integrated, the gain after the compensator is not. So you can do a bit of experimentation, considering that you should not saturate the compensator during transients, and scaling down the constants reduces their resolution.

Figure:1 Low Gain Reference Step

Figure 15  Low Gain Reference Step

The reference step has a pedestal. This is consistent with the MATLAB simulation, except that it rises to a value, hesitates, then completes, whereas MATLAB shows a ring. The modeling is not perfect.

Figure:1 Low Gain Load Step

Figure 16  Low Gain Load Step

The load step settles faster. There is a tradeoff between the reference response and the load response. PID fans have a technique for dealign with this tradeoff, but the extra math costs time. If the design will sit at a constant output value, or use a ramp to change reference values, then you can tune for the load response.

There is another artifact in the responses. The falling edges are slower. This is simply the output cap storing energy with a load to small to pull down the output when the reference or load changes instantly.

You can decide for yourself if this kind of response is adaquate. The constraints on digital compensation are:

  1. Delay in ADC
  2. Delay in Math
  3. Resolution of quantization
  4. Resolution of math

In the analog portion of the design, there are ways to improve a design, such as current mode control, changing the basic LC, clocking faster, etc. You can also design a better compensator in the time domain. But this is where the road ends and the trail begins.

Reflection

Hopefully with this tutorial you can make your way to the end of the road and decide whether to sport a backpack into the woods, or if car camping is good enough.

I found that the first time I tried to get to the end of the road it was not so simple. None of the books seem to tie all the pieces together and give you a recipe for success. If you can get to a working design with this tutorial and then go exploring, I have succeeded.

Happy Trails

Comments on this post:

There are currently no comments.

Login or Register to post comments.
 
Click Here