mike_jones

Mike Jones

Applications Engineer

Digital Power Applications and Development

Interests

Web Links

Linear Technlogy

EEWeb Stats

Mike's Blog :

Return to Blog

Design of Isolated Boost Switch Mode Power Supplies (Part 7)

Most analog jocks can whip out a Type I-III controller, and if they have never done it before, they can hit the books and usually succeed. All their intuitions about frequency response methods, the need for phase margin, how damping factor affects overshoot, and how to pick an op amp apply. But once one enters the digital controller world, new intuitions have to be built ground up.

In this blog entry I will walk through designing a digital controller with the dsPIC and all the pitfalls I ran into doing it the first time. The result is far from perfect and it will take more work to perfect the design, but I got far enough to share insights and hope a few experts might add a few tricks to help us out.

Plan of Attack

This is going to be a long post, so I will outline where we are going. First, the overall plan is to design a controller in the frequency domain, then translate it to the digital domain. In a follow on post I will cover designing directly in the digital domain, and hopefully cover the problems this post leaves unresolved.

I will cover:

  1. Frequency Response of the Converter
  2. Modeling a controller in MATLAB
  3. Translating the controller to a PID
  4. Creating the PID difference equation
  5. Implementing the PID in a dsPIC
  6. Instrumenting the PID and getting data via I2C and displaying it
  7. Trade offs between traditional C code and mac instructions
  8. Limit cycling and phase delays

Phew! I am tired already.

Frequency Response

The first step is to measure the frequency response of the plant. In an analog design you can inject an AC signal into the loop at the output voltage divider and measure at any point you wish. This means you can measure from the control input of the plant to the output. But when you have a digital controller, the you cannot measure between the controller output and the PWM input. Therefore, I ran the plant open loop. I injected an AC signal into the voltage FB pin, scaled it internally, and drove the peak current value with it. Then measured the output. To start, I used a generator and scope and made some measurements by hand.

Figure:1 Bode Plot by Scope

Figure 1  Bode Plot by Scope

The main thing to notice is the little bump at 8Khz. Is it real? I took the data a second time and it was gone, but the phase break was still there. Time for real tools. I borrowed a Venable which is much more accurate and faster.

But before I show the plots, what do I expect? I did not work out the math for a small signal model, but we know that a boost has a Right Half Plane Zero (RHPZ). Why? Boost converters use a two step energy transfer. First, the PWM is set, and an energy is stored in an inductor, then second, the inductor is connected to the output and energy is delivered to the load. This delay causes the RHPZ. At the zero’s frequency, the gain decreases 20db/Decade like a LHPZ, but the phase lags like a pole. What could be worse than that? More gain and less phase margin! Well, what can be worse is the RHPZ can vary with duty cycle (not covered in this post).

Figure:1 Filtered and Unfiltered Bode

Figure 2  Filtered and Unfiltered Bode

There are three plots. Unfiltered, which is where the voltage feedback is taken, filtered, which is where the load is attached, and filtered with an extra cap on the output of the pi filter to match the large cap on the input. In all cases the phase has shifted more than 180 when you cross unity. It looks like there is a pole around 100Hz, and another one around 10Khz. Then the wiggle looks like a RHPZ and pole, or a pair of them.

Since the FB is in front of the filter, we don’t have to deal with the additional pole/s from the filter, and we certainly don’t want to handle the effects of the extra capacitor.

Figure:1 Load Effects

Figure 3  Load Effects

This shows the effect of passive vs. active loads. Nothing radically different.

Figure 4  Passive vs Current Loads

This shows the effect of different active loads. Poles do shift around a bit and the controller needs to remain stable over the range.

In all these cases, there is a one clock delay from the ADC sample to the current/PWM change.

Figure:1 Clock Delay Effects

Figure 5  Clock Delay Effects

This shows the effect of adding one clock delay. I pushes the phase 15 deg or so.

Fundamentally, we have two sources of delay. The first is the natural operation of the plant, the two step energy transfer, which causes a RHPZ that makes the phase roll off faster. And, we have a delay in the ADC and controller pipeline. Both delays compromise phase margin.

Designing the Controller

My goal was to design a PID controller that worked well enough to enable software development for the supply, including UVLO, soft start, and other functions. Then, follow on with a more advanced controller (future post). This means the complexity of the controller is less than the the plant. The plant has at least three poles, and one or two RHPZs. The PID mainly has a pole and a couple of zeros.

The basic outline is as follows:

  1. Model the plant’s poles and zeros
  2. Model the RHPZ/s as a delay
  3. Choose closed loop transfer function
  4. Derive the controller
  5. Convert controller to PID
  6. Plot the results

Modeling the plant

First, the form of the equation:

loopgain=K_{p}\left ( -tz1\cdot s+1 \right )\frac{e^{-H\cdot s}}{\left ( tp1\cdot s+1 \right )\left ( tp2\cdot s+1 \right )\left ( tp3\cdot s+1 \right )}

We have three poles, one RHPZ and an exponent for the clock delay. Then we multiple the top and bottom by (tz1*s+1) to get:

loopgain2=K_{p}\cdot \left ( tz1\cdot s+1 \right )\cdot \left ( -tz1\cdot s+1 \right )\frac{e^{-H\cdot s}}{\left ( tp1\cdot s+1 \right )\left ( tp2\cdot s+1 \right )\left ( tp3\cdot s+1 \right )\left ( tz1\cdot s+1 \right )}

Now, the Pade approximation says that (-tz*s+1)/(tz*s+1) can be modeled as a delay as follows:

Gs=K_{p}\cdot \left ( tz1\cdot s+1 \right )\frac{e^{-\left (H+2\cdot tz1 \right )\cdot s}}{\left ( tp1\cdot s+1 \right )\left ( tp2\cdot s+1 \right )\left ( tp3\cdot s+1 \right )}

Now we have to use real values, like:

GsTf = tf([2.2736e-04 100], [7.4990e-14, 4.0389e-08, 0.0014, 1], InputDelay, 8.5473e-06)

Figure:1 Bode Plot of Model

Figure 6  Bode Plot of Model

This shows the model. It is not exact. It was generated by holding up my thumb, looking at the Venable data, picking poles and zeros, and then comparing. Consider it a ball park model.

It is hard to tell from the Venable data if there are one or two RHPZ, so I created a model with a double zero and double pole and compared it. The transient response was nearly identical, so I stuck with the simpler model.

Once the model is in place, we have to pick the final closed loop transfer function.

Ts=\frac{e^{-\left (H+2\cdot tz1 \right )\cdot s}}{\left ( K1\cdot s+1 \right )}\left ( K2\cdot s+1 \right )

I chose a simple two pole system with delay. Now you can find the controller as follows:

Rs=\frac{Ts}{Gs\left ( 1-Ts \right )}

Now to create a PID, you have to play a trick. You can find this trick explained in a paper titled “A New Design Method of PID Controller for Inverse Response Processes with Dead Time” Chen, Tank, Zhang 2005.

The basic idea is to perform an Maclaurin expansion, which is just a special form of Taylor series. When done, you pick of the first three terms and they match the form of a PID.

Ms = Rs * s;
M0 = limit(Ms, s, 0);
M0_p = limit(diff(Ms, s, 1), s, 0);
M0_pp = limit(diff(Ms, s, 2), s, 0);
RsNew = (1/s) * (M0 + M0_p * s + (M0_pp * s^2 / 2));
Kf = M0_p;
Ti = 1/M0;
Td = M0_pp/2;

I’ll let you reverse engineer this with the paper, but basically you are taking derivatives and plugging them into the PID form, then calculating the constants.

At this stage you put in constants for the model, and the closed loop transfer function, and the controller, and generate the PID.

The end result was a 1mS rise time 83 deg phase margin.

If you try this, here is my suggestion. Get the paper and try to duplicate its results in MATLAB. Once you can duplicate the results, pick different response times in the closed loop function, tweak the gain and see what it does to damping, etc.

Difference Equations

Now the controller has to be converted into a difference equation. This makes some assumptions, such as the poles and zeros placed by the math have the same effect when converted into the time domain. If the ADC sample rate is 20-30 times faster than the gain bandwidth of the system, it will be close enough, or at least closer than you could get by hand tuning the PID.

There are two forms of difference equations: positional and velocity. Positional form directly calculates the control value, and velocity calculates a rate. In some control systems the rate directly controls a motor or other actuator. In our power supply, it is added to the last value to produce a new control value. Velocity equations are used because it is easier to manage wind up protection, etc. I chose to use a velocity equation.

The basic equations are:

p\left ( tk \right )=k*\left ( br\left ( tk \right )-y\left ( tk \right ) \right )

e\left ( tk \right )=r\left ( tk \right )-y\left ( tk \right )

d\left ( tk \right )=\frac{Td}{Td+Nh}-\left ( d\left ( tk-1 \right ) -kN\left ( y\left ( tk-1 \right ) \right )\right )

u\left ( tk \right )=p\left ( tk \right )+i\left ( tk \right )+d\left ( tk \right )

i\left ( tk+1 \right )=i\left ( tk \right )+\frac{kh}{Ti}*e\left ( tk \right )

r = reference
e = error
y = output
p = proportional
d = derivative
i = integral
u = controller output

This version has two extra features: set point weighting and derivative filtering. Set point weighting gives two degrees of tuning freedom. You can tune the PID for load transient, and then use the b parameter to tune for reference transient. N controls a filter of the derivative. Derivatives are sensitive to noise.

For more information about these equations, search for PID Control, Chapter 6, Karl Johan Astrom, 2002 on the internet and you will find all the equations I used and a fuller explanation.

Implementing in the dsPIC

Rather than tell you what not to do, I will walk you through my code evolution and the results, because I can show scope shots of all the bad things that happened so you can recognize them.

The first version of the code looked something like this:

Figure:1 Implementing in the dsPIC

Figure 20  Implementing in the dsPIC

So lets break this down. There is a filter on the FB voltage. Three calculations for DeltaP, I, and D. These are added to the last control value. The control value is scaled. Then the DAC that control peak current is set. Finally, the historical values are updated.

So I code this up, and miracles of miracles, I am regulating without any oscillations. So I test the reference and load transients.

Figure:1 Reference Step b = 0.5

Figure 7  Reference Step b = 0.5

I get a little overshoot. I tweak b.

Figure:1 Reference Step b = 0.1

Figure 8  Reference Step b = 0.1

Not to bad. How about the load transient?

Figure:1 Load Transient I = 1A

Figure 9  Load Transient I = 1A

Also not too bad. I’m thinking, this is too good to be true. This is so easy…

Stability Problems

Fresh from the celebratory pizza with the kids, I put the scope on AC Coupling and scale up and oops, I see this:

Figure:1 Strange Behavior

Figure 10  Strange Behavior

And this:

Figure:1 More Strange Behavior

Figure 11  More Strange Behavior

Unfortunately, these patterns varied with duty cycle, load, etc. Furthermore, I can’t see what is going on inside the PID. So I decided to collect data and pull it out by I2C. Perhaps I can blog on the I2C later, but basically I bought an Aardvark and Beagle from Total Phase. Use their C# bindings and NI Measurement Studio. I create 3-5 arrays in the code and the C# application tells it the sample rate and when to collect, then pulls the data back and plots it. I can either plot the velocities or integrate them.

Figure:1 Saw Tooth Waveform

Figure 12  Saw Tooth Waveform

This was my first look. A saw tooth on the control value.

So I add some code to show the integrated values.

Figure:1 Integrating Waveforms

Figure 13  Integrating Waveforms

The time frame is smaller, so you don’t see the saw tooth. But notice the three terms are ramping! Seems the pizza came too soon!

Now this took a lot of head scratching, but this is what happened: there were truncation errors in the math. Let’s have a look at the math.

DeltaI=\_\_builtin\_mulss(bi, Error)>>15

All the multiplies looked like this. The built in function multiplies two 16 bit numbers and generates a 32 bit number and then it is shifted back to a 16 bit number. In all cases, the variables are in Q15 fixed point format. This means all values are less than 1.0 and allows integer calculations.

Well, when you shift, you truncate. When you truncate, you accumulate errors. When you accumulate errors, you get the ramps you see in the data.

Now, what about the sawtooth? Well when the control value exceeded the largest number in Q15 format, it became negative. Then my saturation logic (not shown in the code), said, this is smaller than zero, so let’s set it to zero. And then the cycle started all over again.

The solution looked like this:

DeltaI = \_\_builtin\_mulss(bi, Error)
Control = ControlM1 + (DeltaP + DeltaI + DeltaD) >> 15;

The results of the multiplies are stored in a 32 bit variable, and shifted at the end one time. All the ramping disappears. Pizza? Not this time.

Figure:1 Noisy Output

Figure 14  Noisy Output

I crank the scope down to 20mV and there is yet another level of onion. I decide to put this problem aside and work on the calculation speed. Normally one implements the code with MAC instructions. This uses the multiply accumulator in the dsPIC. The way it works is you create a couple of tables, one for X Memory, one for Y Memory, and the MAC multiplies and accumulates while prefetching the next data.

The code looks like this:

ControlDifferences[0] = Reference;
ControlDifferences[1] = Output;
ControlDifferences[2] = ReferenceM1;
ControlDifferences[3] = OutputM1;
ControlDifferences[4] = Error;
ControlDifferences[5] = 0; // OutputM1M2
ControlDifferences[6] = OutputM2;

PIDCoefficients[0] = kb;
PIDCoefficients[1] = cdMk;
PIDCoefficients[2] = -kb;
PIDCoefficients[3] = k;
PIDCoefficients[4] = bi;
PIDCoefficients[5] = cd;
PIDCoefficients[6] = 0;

 

These are the precalculated coefficients and the data.

regA = __builtin_movsac(&ControlDifferencesPtr;, &xPrefetch;, 2,
                            &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
regA = __builtin_lac(ControlM1,0);   // Load the acc with the last value.

 

Then the first prefetch is done,and you put the last control value in the accumulator.

regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);
    regA = __builtin_mac(regA, xPrefetch, yPrefetch,
                         &ControlDifferencesPtr;, &xPrefetch;, 2,
                         &PIDCoefficientsPtr;, &yPrefetch;, 2, 0, regB);

 

Then you mac the thing by the number of constants.

Control = __builtin_sac(regA,0);

    CMPDAC1 = Control >> 3;

    ControlDifferences[2] = ControlDifferences[0];
    ControlDifferences[6] = ControlDifferences[3];
    ControlDifferences[3] = ControlDifferences[1];
    ControlDifferences[5] = ControlDifferences[6] -
                        ControlDifferences[3] -
                        ControlDifferences[3];
    ControlM1 = Control;

 

Finally you put the accumulated value in the control variable, update the current DAC, then save historical values.

You can see why I implemented the simple math first. It is also hard to pull out the data and plot it when the math is in this form.

As it turned out, this code was a little slower than the old code. Bummer.

But it worked, so I decided to go back and solve the noisy signal problem. I turn on the scope, and look what I see:

Figure:1 Noisy Output with MAC

Figure 15  Noisy Output with MAC

At first I though I changed something else. Back and forth between code changes and it tracks the code change. After scratching my head and reading the docs, I discover something else is different with a MAC. The mac has a rounding mechanism, including a way to handle tie breakers; intentionally designed to prevent drifting values. So now I have:

Figure:1 Typical Output with MAC

Figure 16  Typical Output with MAC

Another layer of the onion peeled, and another one to go. A friend was kind enough to look at this and suggest it might be limit cycling. I said limit what? Think of it this way, if you have an ADC and PWM, and let’s say the PWM has a resolution of 4 bits, and the ADC is 8 bits. The algorithm tries to set a value using a 8 bit resolution, but the PWM has no matching value. So you get an error. So the wrong output causes the PID to move the plant, changing the ADC. And then you get cycles.

To learn more, go find this paper: “Quantization Resolution and Limit Cycling in Digitally Controlled PWM Converters” Peterchev, Sanders, 2003.

Now in this design, we have a 10 bit ADC followed by math that sets a 10 bit DAC that controls a 16 bit PWM. So the easy trick is to artificially increase the resolution of the ADC. All it takes is to set a couple of bits to zero. (Rounding would be better).

Figure:1 Reduced ADC Resolution (8 bits)

Figure 17  Reduced ADC Resolution (8 bits)

Things are better, but not perfect. So I decide, rather than reduce the resolution of the ADC, lets apply a dither to the DAC. The dither works by adding noise to the DAC values that when filtered result in more bits. For example, if you dither one code back and forth, the average is 1/2 lab, and you have one more bit. Here is how you implement it:

int ThreeBitDither[8][8] = {
    { 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 1 },
    { 0, 0, 0, 1, 0, 0, 0, 1 },
    { 0, 0, 1, 0, 0, 1, 0, 1 },
    { 0, 1, 0, 1, 0, 1, 0, 1 },
    { 0, 1, 0, 1, 1, 0, 1, 1 },
    { 0, 1, 1, 1, 0, 1, 1, 1 },
    { 0, 1, 1, 1, 1, 1, 1, 1 }
};

CMPDAC1 = (Control >> 3) + ThreeBitDither[Control & 0x7][DitherPos];
  if (DitherPos == 7)
    DitherPos = 0;
  else
    DitherPos++;

 

Basically you make a look up table. The technique is fully described in the Quantization paper. Here is what the output looked like when the ADC was 12 bits, and thee bits are added to the DAC.

Figure:1 Dither with Limit Cycles

Figure 18  Dither with Limit Cycles

Now we really have limit cycles. My thought was, for the dither to work, it requires the output inductor to filter the high frequency content of the dither so the average is fed back to the controller via the ADC. But this design does not have an output inductor other than the filter, which is after the FB point.

So I added a little filtering.

Figure:1 Filtering and Dither

Figure 19  Filtering and Dither

The limit cycles are gone. But the base line 500Hz is bigger. 30mV of 500Hz noise. The noise is not very sinusoidal. So it might still be some form of limit cycling. Time to get back on the Venable.

Figure:1 Closed Loop Response

Figure 21  Closed Loop Response

This is the closed loop response. The signal is injected with a transformer. 15 degrees of phase margin at 450Hz. Well, looks like the real problem is the controller. The result does not match the results of the model!

Back to Basics

So at this point the code supports a PID, dither, slope control, but there is no phase margin, and there is semi-sinusoidal waveforms on the output. So we have to dig deeper.

Back on the bench, I put the plant back in open loop mode and get access to the Venable. This time around, I have a dither when making gain/phase measurements. As it turns out, a dither adds delay. This may impact stability as well as prevent limit cycles.

To measure the delay, I setup a simple two pole with delay MATLAB model and compare it to gain phase plots from the Venable. The cost of the two bit DAC dither is 1.75 clocks, or 7uS. Therefore the total delay is 7uS, plus 4uS to 8uS depending on the calculation time.

I also looked at the calculation time by having the interrupts toggle an IO pin. This revealed that the interrupts where not aligned with the clock. When the interrupt processing time was longer than the time to the next interrupt, the interrupts became irregular. After a day of work, the code was tweaked until the code was fast enough to complete before the next interrupt. This was a matter of using bit fields instead of variables for fault logic, putting all soft start code within one conditional, etc.

Figure:1 Calculation Time

Figure 23  Calculation Time

The first pulse is about 4.5uS. This is the PID interrupt. The two small pulses divide the calculation into three parts. Getting the ADC values and setting up the MACs, running the MACs, and updating the DAC and recalculating constants. The second pulse is about 1uS. This is the interrupt that handles all the fault conditions such as UVLO, OV, etc.

Once all this was cleaned up, I remeasured the gain/phase, and created new models. This time I ignored the RHPZs and the high frequency poles and used a simple model. Then I hand placed the resulting poles, and cranked out new PID constants. This time the response looked like this:

Figure:1 Final Phase Margin

Figure 22  Final Phase Margin

Now there is about 50 degrees of phase margin.

Figure:1 Rise Time

Figure 24  Rise Time

The response to a reference change is clean. Matches the bandwidth.

Let’s see how clean the output is.

Figure:1 Low Frequency Spectrum

Figure 25  Low Frequency Spectrum

Figure:1 High Frequency Spectrum

Figure 26  High Frequency Spectrum

We still have spurs. The one at 750Hz is near the cross over frequency. The one at 8.75Hhz is still a mystery.

The output filter has a corner at 10Khz with peaking, so it is possible there is some resonance in the output filter and load. The final experiment is to add a 47uF on the output and set the corner closer to 2Khz.

Figure:1 Spectrum with extra 47uF

Figure 27  Spectrum with extra 47uF

Figure:1 Spectrum with extra 47uF

Figure 28  Spectrum with extra 47uF

The spurs are gone. This means the filter needs to be redesigned to prevent resonance or live with less bandwidth and a slower response. Final result, -65dBV ish.

Review

What looks easy, is not always easy. But here are some takeaways.

  1. Converting from frequency to time domain is not perfect
  2. Delays make the controller design more difficult
  3. RHPZs can be modeled as delays
  4. A dither increases resolution but adds delay
  5. A PID is a simple controller that may not be a match for the job
  6. Use the MAC instructions so you have rounding instead of truncation
  7. Pre-calculate as much as you can
  8. Pay attention to resolution mismatch and limit cycling
  9. Watch your calculation time and the impact on delay and stability
  10. Wear onion goggles and don’t celebrate too soon

Comments on this post:

There are currently no comments.

Login or Register to post comments.
 
Click Here