More efficient comparisons of temp differentials to control heater

Hello,

I have an experimental setup where I am trying to heat an aluminum plate at a controlled and specified rate by applying a silicon heating pad to the bottom of the plate and temperature sensor to the top. Each minute I specify a new set temperature and the heat turns on and off to maintain the aluminum block at that set temperature. The heating occurs in constant temperature, cold water. The heat ramp for the aluminum block starts at 13C and goes to 30C over the course of several hours, but the water it is sitting in is always at 13C.

I tried to use PID control and the PID library to achieve good control in this system, but after at least a week of continuous tuning I was unable to get a suitable ramp when the aluminum plate was immersed in the water (the PID did seem to work well on my desk, however).

So in a desperate move to get this project finished I resorted to a brute force method (see below). This involves calculating the difference between the actual temperature and the set temperature and then turning the heater on for different amounts of time depending on how large the differential is. What I also find is that I need to change how aggressive the heater is as I get to higher temperatures because the aluminum plate temperature can change rapidly when the differential between the aluminum plate temperature and the water it is immersed in are large. In the code below the “aggressiveness” changes above 19C, and again above 25C.

This heater function is run on a timer repeating every 5000 ms. Thus having the heater on for 4000ms (delay(4000)) means it is on for most of each cycle. However, as everyone knows using delays here is really not good, because nothing else in the code can take place during the delay.

All in all this code is not ideal and I was hoping someone with better coding experience could provide me with some pointers for how I might make this shorter, involving no (or at least fewer) delays. I have only included the heater function here, but I can provide the entire code if that seems helpful. Note there is also an indicator LED to show when the heater is on.

Thanks so much for any advice,

Nate

//---------------------------------------------------------
//heater function. Specifies when (at what temperature differential)
//to turn on the heater and for how long to get a consistent ramp
//---------------------------------------------------------

void heater() {
  float deltaT = Tset - temp;                                  //define variable calculating temperature differential
  float invertDeltaT = temp - Tset;                            //define an inverse differential

  if (Tset < 19) {
    if (deltaT < 0.05 && invertDeltaT < 0.1) {                   //use invert differential to make sure heating continues
      digitalWrite(Heater_Relay, HIGH);                          //even when temp is just above Tset (a little overshoot keeps the mean at
      digitalWrite(Heater_LED, HIGH);                             //closer to Tset)
      delay(500);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else if (deltaT >= 0.05 && deltaT < 0.09) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(1500);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else if (deltaT >= 0.09 && deltaT < 0.18) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(2000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    }  else if (deltaT >= 0.24) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(4000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else {
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    }
  } else if (Tset >= 19 && Tset < 25) {                        //change parameters at temps above 19 and below 25C
    if (deltaT < 0.05 && invertDeltaT < 0.1 ) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(1000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else if (deltaT >= 0.05 && deltaT < 0.18) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(2000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else if (deltaT >= 0.18) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(4000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    }
  } else if (Tset >= 25) {                                   //change parameters again above 25C (make heating more aggressive)
    if (deltaT < 0.05 && invertDeltaT < 0.1) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(2000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    } else if (deltaT >= 0.05) {
      digitalWrite(Heater_Relay, HIGH);
      digitalWrite(Heater_LED, HIGH);
      delay(4000);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);
    }
  }
}

//---------------------------------------------------------

Remove all delay()s from your program, change to the principle shown in the BlinkWithoutDelay example in the IDE and refactor the code to make it into a state machine.

When in each state where you need a 'delay' use the BWD principle. Save the value of millis() when the system enters the state then check each time through loop() check whether the required period has elapsed and act on it, probably by changing state. If not then do other things such as reading inputs and act on them.

These lines appear over & over:

      digitalWrite(Heater_Relay, HIGH);                          //even when temp is just above Tset (a little overshoot keeps the mean at
      digitalWrite(Heater_LED, HIGH);                             //closer to Tset)
      delay(500);
      digitalWrite(Heater_Relay, LOW);
      digitalWrite(Heater_LED, LOW);

Consider refactoring them into a function which takes a number of milliseconds for the delay as a parameter.

However, I’m not sure what advantage this method gives you - what’s the issue with turning the heater on when the temp is too low & turn it off when it reaches the desired level? It’ll need some hysteresis to avoid wearing the relay out of course and perhaps you need to turn off early because of residual heating effects of the pad. Simpler though.

to heat an aluminum plate at a controlled and specified rate

I don't see any rate control here. Is that really what you are trying to do?

I don't understand why you run the heater for a specified amount of time when what you need to achieve is a specified temperature.

Why not run the heater while regularly checking the temperature. Then stop it when the temperature reaches the desired level. And start it again when the temperature has fallen below the low threshold.

...R

Thanks for the good comments.

I guess I was coming from the mindset that I wanted to "titrate" the heater to try to avoid large under and overshooting of the set point. Thus, if the temperature was just a little bit away from the set point, pulse the heater (on/off quickly), reassess the temperature, and potentially give another pulse. If the temperature was further from the set point, use a longer pulse.

I actually don't have any evidence that the simple "if temp is below set temp, turn on heater until temp is equal to the set temp" code wouldn't work. I think there are two concerns.

One would be overshoot at low set temperatures, where the differential between the environment and the set temp is small and the heater is powerful.

A second is that at high set temperatures the heater is working against a pretty significant environmental gradient. Turning the heater off [u]at[/u] the set point will probably result in the plate fluctuating but being consistently cooler than the set temperature most of the time (it cools quickly when the heater is off, there is limited residual/carryover heating, and there is a lag before the sensor detects plate heating even after the heater is turned on). I suspect only the peaks of the temperature fluctuations will reach the set temp and the rest of the time the plate will be below that point. When the differential is quite high (high set temp in the face of cold environment) even when the heater is turned on it may take 20-30 seconds for the sensor to detect any heating, and during that period, even though the heater is on, the plate may continue to cool another 0.3-0.4degC.

I'm trying to maintain a +/- 0.5C accuracy around the set point (I'd like it even tighter). I have been able to do that with the code I have included here (even with a 17C difference between environmental and set temperatures)...however, its obviously ugly, brute force, and limiting.

I will look into how to set this up as a state machine. At this point I am not clear on what that would look like or where to look for example code to try to implement that. I have used code similar to the BlinkWithoutDelay elsewhere in my sketch so I can utilize that within the state machine once I figure it out....I guess I was wary of using that technique because it could be a whole lot of timers to keep track of...but obviously I have a whole lot of delays, so that's no excuse and delays are far more troublesome that timers.

The ramping rate is specified by incrementally increasing the set temperature every minute, but that code is not within this function. It could be.

Thanks for your comments and advice. Any additional thoughts?

Nate

On/off control is rarely optimal. Controlling the heater power would be better than on/off. Have you tried modeling this? If you knew the heat flow rate out of the plate, you could attempt to apply the same amount of heat with the heater to maintain plate temperature. (and less or more heat if you need to lower or raise the plate temp).

Tuning the PID does seem like the best solution - the PID algorithm is the best way to solve this problem since it should do exactly what you want. However, we can’t tune it for you.

That being the case, you could look at a simpler hard-coded control algorithm. Have you got a data log showing the thermal response of the system at different temperatures as you turn the heater on and off? That would give us an idea of the sort of control frequency you will need, and how much thermal inertia you have to deal with.

natemiller77: Turning the heater off [u]at[/u] the set point will probably result in the plate fluctuating but being consistently cooler than the set temperature most of the time

So figure out what is the appropriate temperature to switch off at so that the average between the OFF and ON is where you want to be. Id didn't intend my comment to be taken literally to 4 decimal places.

I agree with the suggestion that you modulate the energy flow in the heater - but that can go in parallel with temperature targets rather than time targets. You can probably figure out an algorithm that automatically (?) adjusts the energy flow to minimize the frequency of exceeding the too-high and too-low thresholds.

...R

Hi all,

I have been working with the memory constraints and have managed to reapply the PID for the heater control in my sketch. I realize that it is not possible to tune the PID control remotely, but I was hoping someone in the group could offer some basic advice given the current output I have seen (attached figure with PID values of 300, 100, 50). The temperature does track the set temperature nicely, but there is more oscillation than is currently acceptable. I am currently only concerned with the ramp portion of the plot.
Can anyone offer some general advice on where I should change things? My feeling is that I need to increase the D from 50 to try to damp the oscillations. This will probably play into the P which will then need to be adjusted. Any general advice? Is increasing the D the right way to go? In general, if I increase the D and have to then adjust the P, would I expect to decrease or increase P? I’ve spent a lot of time over the past month trying to compile as much advice on how to best adjust the PID values and I’m hoping that I can get some basic direction given the current plot of controller behavior.

Each of these heat ramps is rather slow (6 hours with increases of 0.05C per min) so tuning takes a very long time and as I’m up against a pretty hard deadline I’m trying to speed along the tuning in any way I can.

Thanks for any help or advice,
Nate

Forum PID Plot.pdf (18.5 KB)

Since this is moving so slow, you probably don't need any D at all (and it may be part of the oscillation problem, i.e. your controller is responding to noise). Try using PI control. If you are really just interested in the ramp rate, you could probably lose the I as well and go with a P controller. Oscillation is frequently the result of too much gain, trying backing off P.

Or you could take an analytical approach to tuning. The process model will tell you what your PID parameters need to be.

Do you need any PID at all?

Perhaps you are starting with an overly-complex concept of how the problem needs to be attacked?

Just because complex solutions are available doesn't mean they are necessary or even better than simple ones.

...R