Ever wonder how your oven keeps the temperature you set perfectly over time? PID ( Proportional, Integral, Derivative ) controllers can be used to regulate flow, temperature, pressure, level, and many other variables that need to maintain a value over time, with the least amount of error as possible. In this blog we will discuss how PID works, and how you can use microcontrollers to control a BBQ smoker temperature. A great way to develop this project would be using a Clicker 2, with a Thermo click and Fan click for receiving temperatures of up to 1372°C and controlling fan speeds efficiently.
Here is a great youtube video on some examples of PID.
PID Explained
PID is used for controlling a variable by using changes in proportional , integral and derivative variables. To make PID easier to understand, lets use a temperature controller as the example. All three variables will be used to determine the current fan speed to not only reach our desired fan speed, but make sure we do not go past the desired fan speed, and can maintain the fan speed when we reach it.
As you can see in the graph above, PID controllers use the three different factors Kp, Ki and Kd to maintain a smooth line up to the "reference signal" and keeps it there.
Just to be completely clear, PID is used to find use error correction over time, to keep a value steady. The P, I, and D are all proportional to the amount of error. The "P" is proportional to the amount of error, the "I" is proportional to the integral of the error, and the "D" is proportional to the derivative of the error. Let's begin.
The P, I and D
The Proportional variable would be considered the difference between the current temperature and the temperature we are trying to reach. If the temperature we are trying to reach is 70 degrees, and we are at 60 degrees, then the proportional error would be 70 - 60 = 10 and our proportional variable used would be what we then multiply 10 by. If we set the proportional variable to 5, then the fan speed would be set to 10 * 5 = 50%.
The Integral variable uses time to determine if the difference between our current temperature and destination temperature is being reached fast enough. Every one second the integral variable will increase by a set value. Say we have a temperature of around 60 degrees and our integral value is set to 1, then for every second that the temperature stays at 60 degrees, the integral variable would increase the fan speed by 1 percent, until reaching 61 degrees and resetting this loop.
Derivative is kind of like the look-out variable. It watches the temperature over time, to see how the other variables are doing at either maintaining the temperature at the desired temperature, or making sure it does not go too fast and pass the desired temperature, or too slow and drops below the desired temperature. You can set the derivative to check every 5 seconds ( depending on the type of environment ), to evaluate the changes in temperature over time. For example, if the temperature we are trying to maintain is 65 and has gone from 65 to 67 in 5 seconds, and our derivative is set to 3%, then the difference is 2 degrees in 5 seconds, and therefore the fan speed should be decreased by 3 * 2 = 6%. If the temperature is 67 and we are trying to reach 70 and the temperature has changed by +2 degrees in the last 5 seconds, then we would increase the fan speed by 3 * 2 = 6%.
Code Examples
Here is an example of finding the fan speed for your controller using simple variables like the current temperature, target temperature, time, and your user-defined variables for proportional, integral, and derivative "K" factors. Let's start with proportional.
int8_t pid_get_proportional( float curr_temp, float target_temp, float prop_val ) { float proportional = 0; int8_t percent = 0; proportional = target_temp - curr_temp; // Difference between target temperature and current temperature percent = prop_val * proportional; // Depends on Proportional(Kp) value, user-defined return percent; }
In this example, the function sends the current temperature of the smoker, target temperature, and the value for proportional "Kp" factor. This function simply takes the difference between the current and target temperatures, and scales the percentage. It then sends back the value for increasing or decreasing the fan speed, depending on the Kp factor or "prop_val" you send it. Using a super-loop, the user would put this function in as something that is called continuously, because it is not dependent on time. The only thing needed is to get the most recent temperature from the Thermo click. So that was the "P" in PID pretty simple right?
Let's move on to the I.
int8_t pid_get_integral( float int_val, uint8_t seconds, float curr_temp, float target_temp ) { int8_t percent = 0; if( curr_temp < target_temp ) // Fan Speed should be INCREASED { percent = int_val * seconds; } else if( curr_temp > target_temp ) // Fan Speed should be DECREASED { percent = int_val * seconds; percent = ( percent - ( percent * 2 ) ); } else if( curr_temp == target_temp ) // DO NOT increase or decrease percent = 0; return percent; }
The integral value is found by giving this function a integral value to scale the percent with. The amount of time that the temperature has remained the same. Outside of this function, we must remember that the time would be on a timer, and once the temperature is raised by 1, you must reset the "time" variable back to 0. Inside of this function, if the current temperature is less than the target temperature, the percent is scaled by the "Ki" factor, or "int_val" in this case. If the current temperature is more than the target temperature, we must decrease the percentage by multiplying the Ki factor by the amount of seconds that the temperature has stayed the same. This function could also be called in the super-loop along with the pid_get_proportional because all it uses is the most recent temperature, and an amount of time that has passed since the last temperature change using your timer. Last but not least, if we have reached our target temperature, the percentage must not increase or decrease as far as the integral error correction goes. Lastly, here is the derivative...
int8_t pid_get_derivative( float deriv_val, float change, float curr_temp, float target_temp ) { int8_t percent = 0; if( curr_temp < target_temp ) { percent = deriv_val * change; // Proportional to amount of change in user-defined time frame } else if( curr_temp > target_temp ) { percent = deriv_val * change; percent = ( percent - ( percent * 2 ) ); } else if( curr_temp == target_temp ) percent = 0; return percent; }
Derivative error correction uses the temperature change in the last user-defined amount of time, and multiplies that by the "deriv_val", or "Kd" factor. Note that this function does not have any timer inside, therefore it is just like the integral and things related to time must be dealt with outside this function. For example, the timer outside of the function can set a flag every 5 seconds and send the difference in time in the last 5 seconds to this function. Using a set amount of time to determine change in temperature, allows you to capture a pattern in temperature change, and compensate for it with the derivative value. This function would only be called every so many seconds, depending on what you define. This can be done with a flag being set within your timer after a set amount of time, and then being set back to 0 in the while loop after calling this function. Your timer function might look a little like this...
Timer Interrupt
void PID_Timer_Interrupt() isr IVT_INT_TIM2 { seconds++; // Must be set to 0 outside of this interrupt deriv_seconds++; if( deriv_seconds == deriv_time_frame ) // deriv_time_frame is user-defined { deriv_flag = 1; deriv_seconds = 0; } TIM2_SR.UIF = 0; //Reset timer flag }
This timer would be initialized to interrupt every 1 second. The "seconds" variable would be set to 0 after the temperature has changed outside of this interrupt, so that the integral function would know how long the temperature had not changed. The deriv_seconds would continue to increment until it reaches the user-defined time frame and sets a flag telling the super-loop to call the derivative function, along with setting the deriv_seconds back to 0 to restart the count.
Here is a great picture to help understand data flow a little bit better...
SP would be our "target temperature" and PV would be the "current temperature". After calculating the amount of error between the two, we can calculate the PID error correction, add up all three of them and output that variable to the controller or fan speed in our case. The only other variable that is missing from this diagram would be time, which is really dependent on your tuning and testing.
Summary
After putting all three of these percentages together and applying them to the fan percentage, you will have a very efficient and steady increase to the desired temperature, and automatic error correction. The only thing that you must determine are the three factors that you send to these functions. Testing will have to be done in order to see if your Kp, Ki and Kd factors are correct for the environment your "BBQ" is in, and will probably take some time to get perfect. When testing your PID controller make sure to compensate for the usual errors like spikes in your temperatures, "windup" or the time it takes to get things going and normal operation to begin, and also of course computation time depending on what else you are doing with your micro controller.
References
PID for Dummies http://www.csimn.com/CSI_pages/PIDforDummies.html 2015
Simple Examples of PID Control https://www.youtube.com/watch?v=XfAt6hNV8XM 2012
Cal Tech http://www.cds.caltech.edu/~murray/courses/cds101/fa02/caltech/astrom-ch6.pdf 2002