The joys of taking stuff apart

Main menu

Arduino PID Temperature Controller

I recently implemented a PID routine for controlling temperature settings. The goal was to maintain a fixed temperature rate for a specified ramp up period, hold the temperature for a specified soak time, and then cool down at a specified rate. This could be useful for solder reflow cycles, food baking, beer brewing, or anything that needs a controlled timing scheme for temperature control.

PID stands for proportional-integral-derivative and is a very popular algorithm in control systems. In very simple terms, the temperature controller takes readings of the current state of the system and uses that data to determine whether to keep the heating element on. I won’t get into the details of the equation or the coefficients in this post; I am merely showing an Arduino implementation. My PID controller library on Github is here. I based my Arduino code off of this library on Github. This library is very good with handling the PID equation, but I felt like it could use some more description and clarification in applying the equation to ordinary Arduino peripherals, which is why I’m writing this post.

Power Switch for switching heating element, activated by digital pin. Solid State Relays can work too.

The MAX31855 is controlled through a SPI interface. I wrote a library for communicating with this chip on Github. The MISO and SCLK pin positions are dictated by the type of Arduino board you are using (see this). Any digital pin can be used for CSB. I don’t normally use the built-in SPI CSB pin (pin 10 on Uno) just because I normally am using multiple SPI devices at the same time that can’t all use pin 10.

I also just randomly picked a digital pin to control the SSR.

I have posted the full code at the bottom of the page, but I will break down some of the main functions now.

First, the function to run the PID equation. As parameters, this function takes in the 3 PID coefficient values (Kp, Ki, Kd); WindowSize, which is the maximum value the response could potentially be; and time_interval, the amount of time before the PID equation is re-evaluated. After setting the properties of the myPID object (part of the borrowed PID class), the output response is calculated based on the current temperature reading input and the desired temperature set point. I had issues getting the routine to start if the set point was too low, so the init_read section kick-starts the routine for the first time_interval. I calculated a ratio of the output response divided by the maximum response to determine how long the heating element should be on and how long it should be off in a given time_interval.

Next is the section that loops through the multiple time intervals. Depending on the system state (ramp up, soak, cool down), the function first determines how long that state will run based on the beginning and ending temperatures, calculates the number of time intervals are in that state time, and then loops through those iterations. In ramp up, the function determines the initial temperature reading and subtracts from the soak temperature to get the temperature delta, which along with the ramp rate can be used to find the total ramp time. In the soak state, the length of time is already specified. In cool down, the reverse of the ramp up is used.

Thanks for checking out my blog. I’ve written an update that should simplify my code and make it easier to run the example. For your exact issue, I just noticed that on line 74, it should be “thermo.thermocouple_temp” instead of “thermo[0].thermocouple_temp”.

Hey. It happens. It isn’t easy to make things simple and clear right off the bat. When we are developing something we are concerned about how to get it done and not how it will be read by someone later.

I had once written a postscript program for a programmer. It was prison admission form that was to be computerized. He was doing the computer interface and he was going to feed the parameters and the postscript programming to the printer.

I was writing routines for spacing and I had started in points, done some in tenths of an inch, then sixteenths. I think I had four different units of measurements by the time I was done. As I got further a different unit became more appropriate. In the end it all worked perfectly but if someone had to look at the code they would thing I was schizophrenic.

Anyway. I appreciate the work you have done here. I just have to get some of the language rules to sink in.

The problem was there was no SPI.begin(); executed. It is commented out in the above piece of code. It got hung up and I don’t know what it was doing. I put it into my initialization (setup) and then I was getting all my indicators. (LED & serial out)

I had put in some serial print routines in the run_PID routine to show me the time, setpoint, temp, error, and output. It runs pretty slick. I’m playing around with different PID settings now. I have a thermistor taped to a power resistor. My ramp rate is 5 degrees per minute. My soak temp is 250 degrees.

Once again, thanks Joe for putting this up. I wish I would have found it earlier. It is the starting point I was looking for. I didn’t want to start with code with a display or keyboard. I plan to use serial to talk to the controller. But first I want to add in the SD card logging.

This has been interesting. Prior to this I played with the Blink program then a Logger test program. I’m surprised how much overhead some of the libraries consume. I thought there was plenty of memory but I now see this is a lot different than assembly language programming.

Hello,
Thanks for all informations, I want to make a pid control of temperature by a pic16F877.
I saw someone use a resistance 10 ohm 5 watt for Heating with 12volte command it by PWM signal of pic, but i don’t know how he links betewen the PWM and temperature.
I wish you help me with any info and thank you so much.

Hi Erick,
Yes, it would be possible to implement this with a MAX31850. I think the only real difference between these 2 thermocouple readers is the digital interface. The MAX31850 uses 1-wire whereas the MAX31855 uses SPI. You could see more on this on Adafruit’s site: https://www.adafruit.com/product/1727

Thank you so much for this great code. I am running it on some heating elements stuck inside of parts and it does a great job of ramping and soaking with PID values I give it. As of now I am just controlling one relay that looks at just one temp in a part.

I was wondering if there would be a way to control multiple relays with this code? This means multiple PID loops responding to multiple temperature readings from different areas of a part. I want to control different areas of a tool because controlling based off just one temperature in one area is not efficient for my application. I was struggling to figure this out on my own because the timing that goes into turning on and off a relay would mess up the timing for another relay.

Hi Tarik,
I really appreciate your comment. Running multiple PID loops should be very possible on an Arduino, but you are right that the timing would be messed up the way the code is now because I use the millis() function. If the code used a software counter as the timer instead of the “hardware” counter of the millis() function, this should work. Let me see what I can do. If I write you some code, do you mind trying it out and letting me know how it goes?

Great to hear it is possible! I was going to try and test the code on two different areas using the while loops for on and off right after each other for each area, though I assumed I would run into problems with one being completely off while the other was running through its loop. I basically just repeated the functions you made for one relay such as creating run_PID2 for the second relay.
Yes, if you have a better idea for the code that would be greatly appreciated and I will test it out! And if there is a certain counter chip needed to overcome the issue as well I would get that.

I’m concerned about the while loop that effectively waits for the time to expire before moving one. As you mentioned, the second relay won’t do anything while the first relay is waiting. Instead, I’m thinking about passing a time variable around so that timers for all relays could be evaluated almost concurrently, rather than serially (doing one and then the next).

I haven’t had a chance to acutally put code together, but I’ll get to it in the next couple of days. Thanks for the idea. I’ll write a post about it when it’s done.

Thank you very much for this great write up. I am implementing your project for my popcorn popper – coffee bean roaster. My setup is almost exactly the same, as I need to control the heat ramp rate on the roaster (via SSR) for a specified amount of time. However, the thermocouple that I am using is the MAX31856. My question is, how much of the source code would I need to modify in order to run it? So far, I have tried replacing the 31855 library with the 31856 files, as well as referencing the 31856 within the source code, but I keep getting errors. I am fairly new to Arduino, so any help would be much appreciated. Again, thank you very much for sharing this project, this is exactly what I’ve been searching for, just a little lost.

Hi Hans,
Most controlled temperature chambers (ovens, kilns, etc.) have a cycle in which the unit ramps up from room temperature to the desired set temperature, then holds at the set temperature for a certain amount of time, and then cools back down to room temperature. I see that I could be more clear about that in my post. I’ll try to add a diagram for that too.
Yes, I tried to make the code as modular as possible so that you could use any temp sensor that you want, just swap in new code.
Thanks for reading!

Hi Jo. Like your build. About time some one make arduino do something real. Not just blink bloody led’s. Any chanse of including a display skerch in there for us plebs who cant figure a the coding stuff. I started a small bronze foundry. Running a kiln to burnout molds and nead a thermometer to check metal temp before i cast. Now its bit hit and miss.
Expennsive on the mis part…..