In trying to implement a battery charge algorithm I needed to use some form of control to maintain a voltage set-point.
To do this with a good step-change response and stability is harder than you might think. I decided to implement a relatively simple Proportional and Integral controller to perform this task, as I have used this form of control before, but wanted to document it here in more detail.
This blog post is my collected notes on implementing a PID controller programmed onto an Arduino microcontroller.
What is PID control?
PID stands for Proportional, Integral and Derivative control. It was seen to be implemented by sailors as they captained ships. They did not only need to correct for the current situation, they also needed to factor in the past situation and also compensate for the rate of change. This was converted into a mathematical method of approaching control systems. As usual, the wikipedia article on this is a great starting point.
The basic theory is that you take your real signal, compare it to a set-point and this gives you an error value.
The error value is then given:
- Proportional gain (if the error is big then quickly change the output)
- Integral gain (if the error is small then still need to integrate that small offset to remove it)
- Derivative gain (this relates to how quickly the error has changed)
A diagram of that can be seen here:
There is a good explanation and images of this here: http://radhesh.wordpress.com/2008/05/11/pid-controller-simplified/
In practice the derivative gain is not always used, as it can complicate the solution and create instability. As I wanted to keep it simple and implement it easily on a micro controller, I decided to not implement the derivative component.
PID control is a standard control methodology, hence there are already a number of examples of PID control code for the arduino.
This was a bit too comprehensive for this task and also took up too much code space on the limited ATTiny45/85 I wanted to program.
There is also a write up of PID and Arduino here.
In the end I decided to implement my own simplified code.
Proportional and Integral Control
The final code was very simple.This was designed as a voltage limit controller, hence I use Vact = the actual measured voltage and Vsetpoint = the voltage set-point. The gains Kp and Ki are discussed in the next section.
This code runs in the main loop after the Vact has been calculated (using an averaging routine). Hence this updates around 20 times per second.
I wanted to only use integers for this routine. This saves program space and computation time as doubles and floats are not used. The problem with this is that the output will not be as smooth. This does not matter too much as the actual output will be an int between 0 and 255, hence has quite low resolution. But we need to be careful that we do not have problems with data resolution.
The error term is (Vact-Vsetpoint). This is used to calculate the integral and proportional terms.
// OUTPUT control
// This is the shunt regulation to control FET as a PWM
// This uses a PI (Proportional/Integral) control loop
// The error signal is the difference between the values (+ or -ve)
// The proportional gain adds this error difference multiplied by some factor
// The integral gain adds this error difference on to an integral term
// ************** ERROR*********************
error = (Vact - Vsetpoint);
// ************** PROPORTIONAL ****************
proportional = error*Kd;
// ************** INTEGRAL ****************
integral = integral + error*Ki;
// We want to limit the Integral.
integral = 255;
integral = -254;
// ************** OUTPUT ****************
FETPWM = integral + proportional;
// PWM LIMIT the value to between 0 and 255
FETPWM = 0;
integral = 0;
FETPWM = 255;
analogWrite(FETdriver, FETPWM); // Update the FET PWM %
There are a couple of limits and resets applied. These are:
This is required as the PWM output can only be set with 255 levels, hence if a number greater than 255 or lower than 0 is sent the this will cause problems with the value rolling over. In these cases we limit the values.
The integral term works by integrating the error over time. This is the same as adding on the error term (multiplied by the integral gain, Ki) each time the routine is called. This could be improved by using a definite sample time (rather than a rough sample time I am using). This could be implemented using interrupts.
The problem with the integral term is that it can become very large very quickly. This means it could ‘roll-over’, when the number gets larger than the container it is in (in this case an integer, with a max value of 32000(ish)). Also we do not want it to become very large as this can cause large output swings of the PWM.
Alongside the integral limit, we also need to reset the integral term in certain situations. For example, if the actual voltage is lower than the set-point then the PWM will be zero (ie OFF). But at the same time the integral term will be going lower and lower (as a negative value). If the actual voltage then goes over the set-point then there will be a delay before the integral term goes positive (as adding a small positive value to a large negative value will still give a negative value). In order to stop this I use a simple integral reset whenever the output is OFF. So when the PWM hits zero, we also set the integral term to zero. This provides much better response.
Tuning the gains
The final step this PID control is to tune the gains for the particular situation. Even though there are only three terms (in this example only two gains), this is actually a very difficult problem, usually with no single solution.
Typically this is done through manual tuning, which is what I have used here. I needed the unit to have fast response to changes, but also be stable and not oscillate. After playing with the values for quite a while, I came to the conclusion of using Kp = 1 and Ki = 1. This seems to work well, but this might need work. One idea is to use larger number for the integral and proportional terms and then divide them down to give the PWM output value.
You can also auto-tune the parameters, using some form of tuning algorithm, as shown here.
There are also computational methods to solve the problem for the range of situations the controller will be utilised in. Check the Wiki page on PID control for details of the tuning methods.