2

I am trying to implement a PI controller in C that outputs / controls a PWM's duty cycle on a microcontroller. The duty cycle that I can write to the certain PWM control register is limited to 10bit (values 0 - 1023 correspond to 0% - 100% duty cycle). My controllers output "Stell", that should correspond to a duty cycle, exceeds this bit limit due to calculations of the controlled values. Here is a simplified version of my controller function that is called regularely with a base clock:

void controller(void)
{
    int32_t stell_1, stell_2, stell; 
    int32_t Integral; 
    int16_t delta_e; 
    uint32_t DZSoll, DZIst; 
    uint16_t duty_cycle; 
    int32_t temp_duty_cycle; 
     
    //Error = Setpoint - actual_value
    delta_e = DZSoll - DZIst; 

    //PI controller
    stell_1 = delta_e * Kp; 
    Integral += delta_e; 
    stell_2 = Ki * Integral; 
    stell = stell_1 + stell_2; 

    //Max values 
    Max_S1 = Max_delta_e * Kp; 
    Max_S2 = (Max_delta_e + Integral) * Ki; 
    Max_Stell = Max_S1 + Max_S2; 

    //Scaling of the controller output to a 10bit duty cycle 
    temp_duty_cycle = stell<<10; //*1023
    duty_cycle = temp_duty_cycle / Max_Stell; 
    PWMUpdate(duty_cycle); 

    //...
}

Now, I thought that I would need to scale the controller output "Stell" to the desired 10bit "duty cycle" by setting them into a relation: duty_cycle/2^10 = Stell/Max_Stell (basically to receive the scaling factor). Nevertheless, this only works for positive values of "stell" as my duty cycle by definition has to be a positive value (uint16_t). In the case that my actual value "DZIst" is bigger that the setpoint "DZSoll" (for instance on a overshoot of the integral part of the PI controller), this results in a negative "Stell" which is then incorrectly calculated into a duty cycle. This is especially a problem on lower setpoints as the Integral value cannot compensate the negative control error.

So, my question is: Am I scaling "Stell" correctly to the 10bit duty cycle? Could I compensate the negative "Stell" values with an offset (and how?)? I would appreciate every help a lot, thank you in advance!

Alles Klar
  • 31
  • 4
  • Just to understand, since usually you have to deal with real world: let's suppose your system is stable with delta_e at zero and also its integral part: do you expect the duty cycle to be 50%? – Simone-Cu Dec 16 '22 at 10:56
  • Your `duty_cycle` is a 16-bit unsigned var. You shift 32-bit int 10 times then save into an unsigned 16 bit here `duty_cycle = stell<<10;` resulting in 10 bit worth value loss. You haven't specified neither where do you get the set point and feedback values from nor the max and min ranges of both the set point and feedbacks. But since you use 32bit (which I think it might very big for this purpose but depends on how many value you accumulate), you have to make convertions as lossless as possible using functions like linear inteerpolation. – Kozmotronik Dec 16 '22 at 13:08
  • @Kozmotronik you are right, this is a mistake in my documentation. I have cut a lot of the fixed point arithmetic from the code to try to keep it simple. In my actual code, the shift is done in a 32bit help variable: int32_t temp_duty_cycle; temp_duty_cycle = stell<<10; duty_cycle = temp_duty_cycle / (MaxStell); – Alles Klar Dec 16 '22 at 15:02
  • @AllesKlar Oh I see, however remember that your minimal example preferably should be reproducible. Please also specify the ranges of the input values so that we can give you better ideas. – Kozmotronik Dec 16 '22 at 15:26
  • @Kozmotronik My setpoint and my actual value range from 0 - 3500, they are the rotation numbers of a electrical engine that I am trying to control with the PI controller. So, my max_delta_e = 3500. As the values are determined by the rotation number of my specific engine, I don't know if I can provide a code that is gonna be fully reproducable. Generally, in my question, I was just interested if I could convert a +- int32_t Stell into a +uint16_t duty_cycle --> if I can scale a +- value range into a only positive 10bit range – Alles Klar Dec 16 '22 at 15:57
  • @Simone-Cu I am sorry but I couldn't completely follow your thought. I guess that if my delta_e is 0, the system is controlled correctly, and the duty cycle in that situation can range from 0 - 1023 (100%), corresponding to how high my setpoint rotation number is – Alles Klar Dec 16 '22 at 16:00
  • *I was just interested if I could convert a +- int32_t Stell into a +uint16_t duty_cycle --> if I can scale a +- value range into a only positive 10bit range* As I said earlier ofcourse you can @AllesKlar. But this implies some heavy calculations on which can be expensive for the micro you use, especially if it hasn't a multiplying hardware. Well perhaps a interpolation example might give you better understanding. – Kozmotronik Dec 16 '22 at 19:30
  • How much is your PB? If you wont to calculate the output in range form 0..1023 than your output is in PB range. – GJ. Dec 19 '22 at 08:48

1 Answers1

1

In the comments you've mentioned that the value ranges would be between 0 - 3500 for set point as well as for the Max_Stell, fix me if I got it wrong.
Anyhow, what you want to do can be achived by using linear interpolation since;

it is used to calculate data points within the range of a discrete set of known data points.

Let's say you wanna interpolate a range from -3500 to 3500 and the range of 10 bit value which is from 0 to 1023. Here is a graph from wiki to visualize the interpolation between 2 range of values: x0 - x1 and y0 - y1.

Linear Interpolation graph from wiki

Well, it will make more sense when we assign values for Xs and Ys which would be as following:

y0 = -3500
y1 = 3500

x0 = 0
x1 = 1023

Let's assume that you calculated the Max_Stell as -1750 which corresponds the y point in the graph and we need to interpolate it to the 0 - 1023 range. Here we will calculate for x point value in order to find the corresponding value in the 0 - 1023 scale, so the formula is as following:
x = x0 + (y - y0) * (x1 - x0) / (y1 - y0)

Let's put the values and calculate what the corresponding value would be for -1750...

x = 0 + (-1750 - (-3500)) * (1023 - 0) / (3500 - (-3500))
x = 1750 * 1023 / 7000
x = 1,790,250 / 7000
x = 255.75

Now let's calculate it for the max value of y which is 3500 to see if it will correspond to max x value...

x = 0 + (3500 - (-3500)) * (1023 - 0) / (3500 - (-3500))
x = 7000 * 1023 / 7000
x = 7,161,000 / 7000
x = 1023 // Here we have it!

As you can see, you can convert any range of data points to another range using the handy interpolation. You can also interpolate vice versa using the following formula:
y = y0 + (x - x0) * (y1 - y0) / (x1 - x0)

Let's do it for the max value of duty cycle to prove it. x is 1023 in this case and we calculate for the corresponding y value...

y = -3500 + (1023 - 0) * (3500 - (-3500)) / (1023 - 0)
y = -3500 + 1023 * 7000 / 1023
y = -3500 + 7,161,000 / 1023
y = -3500 + 7000
y = 3500

Now that you have the idea on how to convert any range of values, you can translate it to the language of your specific microcontroller. But I wanna warn you about something:
If you will interpolate very frequently, this calculations will consume too many CPU cycles especially if you use an 8 bit PIC16 micro which has neither hardware multiplier nor hardware divider.

You should also choose between precision and speed when you implement the interpolator function. If you choose precision you need to calculate values with float type variables and trade-off the speed or vice versa.

Kozmotronik
  • 2,080
  • 3
  • 10
  • 25
  • Even though this is a late answer from my part, thank you very much for taking the time to provide a detailed answer! I actually had to shift my work away from this project, so I didn't get to try the changes out. Nevertheless, the conversion via interpolation sounds reasonable. I do fear that this will consume too many CPU cycles though, but I will try out. Thank you again! – Alles Klar Jan 08 '23 at 18:56