DS18B20 PID Tuning on the warm side

By way of review, I have a 7CU chest type freezer I use for brewing and aging wine. I use a 125W heat lamp for warming and, obviously, the freezer for cooling. Colin Law graciously helped me tune his PID node for the cooling side last summer and it has been working flawlessly. (Thanks!)
It's winter and time to tune the warming side. :slight_smile:

Here is the cooling flow:

[{"id":"8aae4b77.5f52a8","type":"tab","label":"PID-BrewBucket","disabled":false,"info":""},{"id":"2805617b.3c39c6","type":"comment","z":"8aae4b77.5f52a8","name":"Temperature Control","info":"","x":220,"y":40,"wires":[]},{"id":"7fb90422.38a1cc","type":"link out","z":"8aae4b77.5f52a8","name":"BrewBox_Cool_PID","links":["2f2d49d.cd842b6","3eec1917.f77a7e","23f91614.cb3b52","5254a3d5.631ff4","33adeae3.377626"],"x":1135,"y":180,"wires":[]},{"id":"cd0aa371.e74d4","type":"comment","z":"8aae4b77.5f52a8","name":"BrewBox PID","info":"PID Control Node\n\nThis node has 2 outputs, the first is for \nheating control, the second is for cooling \ncontrol.\n\nBoth outputs provide a floating point value \nbetween 0 and 10.\n\nSettings\n\nThe node takes the following configuration values:\n\n    Gain: The % gain to apply to the difference between the setpoint and input value\n    Ti: The time in seconds over which to apply the gain value\n    Dead Band: The +/- each side of the setpoint to consider the setpoint reached\n    Recalculation Time: The time in seconds between each calculation of values\n    Setpoint Topic: the topic a message should have to set the setpoint\n    Fire Topic: the topic a message should have to enable/disable Fire mode\n    Fixed Value Topic: the topic a message should have to enable/disable fixed output mode\n    Fixed Value: the value to output when in fixed mode, +ve for heating -ve for cooling\n\nOperations\n\nBefore you can use the node you need to set \nthe setpoint, to do this you need send a \nmessage with the setpoint value as the payload \nand a topic set to the Setpoint Topic \nconfigured in the editor UI.\n\nFire mode sets all outputs to 0. \nFire mode is enabled by sending a message \nwith a payload of false to and a topic equal \nto the Fire Topic. To disable Fire mode send \na message with a payload of true and a topic \nequal to the Fire Topic","x":560,"y":40,"wires":[]},{"id":"a6666729.59d64","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBox_Stop","links":["3cf71be3.8491dc","bf31d5f2.1a3828"],"x":285,"y":120,"wires":[["e8716a19.58a768"]]},{"id":"631bb978.f50118","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBox_Start","links":["9281b062.7edd48","859b757a.2891d8","d025e0d6.f340d"],"x":285,"y":80,"wires":[["e8716a19.58a768"]]},{"id":"858fbfb3.29de7","type":"function","z":"8aae4b77.5f52a8","name":"Split Heat/Cool","func":"/* A function designed to be used with node-red-contrib-pid in applications where both\n * heating and cooling are available to control the system.\n * The node is given a power value in msg.payload in the range 0 to 1, such as is produced by \n * node-red-contrib-pid and splits this into a heat power (o/p 1) and cool power (o/p 2) where\n * each is in the range 0 to 1. These can then be fed directly into an output device, if this\n * is continuously variable, or they may be passed to node-red-contrib-timeprop nodes to generate\n * time proportioned on/off outputs.\n * There are two particular issues to be dealt with in a heat/cool application. Firstly is the fact\n * that the cooling device may be more or less powerful than the heating device. It is necessary\n * therefore to be able to adjust the gain of the system separately for heating and cooling. Secondly\n * is the highly non-linear response of some devices, notably refrigerant systems, that can have a\n * large effect initially, then this tails off. To compensate for this it is useful to have an \n * overlap range where both heat and cool are slightly on.\n *\n * To allow for these requirements two variables can be set below. The value of the power input value\n * where the heating starts to come on is determined by the variable heatMin. Above this value the\n * heating will rise till it is fully on with an input of 1.\n * The cooling is fully on when value of the power input is 0, and falls till the cooling is fully\n * off at an input of coolMin.\n *\n * If the heating and cooling systems are of similar power then set heatMin and coolMin both to 0.5\n * in which case input values of 0.5 to 1.0 will map to heating outputs of 0.0 to 1.0,\n * and 0.5 down to 0.0 will map to cooling 0.0 to 1.0.\n * If, for example, the cooling system is more powerful than heating then they can both be set\n * to something like 0.7 which increases the gain in the heating region and reduces it\n * in the cooling region, to compensate for the different powers in the heating/cooling systems.\n * If some overlap is desired (so that both heat and cool are on slightly near the crossover\n * point) then overlap the two settings so that, for example, heatMin might be 0.45 and coolMin\n * might be 0.55\n */\n\n// set these as described above\nvar heatMin = 0.5; // the value of input corresponding to 0 heat o/p\nvar coolMin = 0.5; // the value of input corresponding to 0 cool o/p\n \nvar power = msg.payload;\nvar heat = (power - heatMin)/(1 - heatMin);\n// limit to range 0 to 1\nheat = Math.min(Math.max(heat, 0), 1);\nvar cool = (coolMin - power) / coolMin;\n// limit to range 0 to 1\ncool = Math.min(Math.max(cool, 0), 1);\nreturn [{payload: heat}, {payload: cool}];\n","outputs":"2","noerr":0,"x":790,"y":90,"wires":[[],[]]},{"id":"e25eceb3.16d238","type":"link in","z":"8aae4b77.5f52a8","name":"BrewBucket_RX","links":["5ada9764.19b6a"],"x":285,"y":160,"wires":[["e8716a19.58a768"]]},{"id":"e8716a19.58a768","type":"PID","z":"8aae4b77.5f52a8","name":"BrewBucket.PID","setpoint":"18.333","pb":"5","ti":"57600","td":"0","integral_default":"1","smooth_factor":"0","max_interval":"600","enable":1,"disabled_op":"1","x":570,"y":160,"wires":[["c3edcaf2.c8de7"]]},{"id":"468cec3c.edc1d4","type":"function","z":"8aae4b77.5f52a8","name":"Fetch setpoint","func":"var s = global.get('BB_setpoint');\nmsg.topic = 'setpoint';\nmsg.payload = s;\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":220,"wires":[["e8716a19.58a768"]]},{"id":"bd9ca82e.9de228","type":"inject","z":"8aae4b77.5f52a8","name":"Setpoint Restart","topic":"setpoint","payload":"1","payloadType":"num","repeat":"","crontab":"","once":true,"onceDelay":"","x":270,"y":350,"wires":[["40d9fdb8.4f7b6c"]]},{"id":"d94c80ab.7550e8","type":"link in","z":"8aae4b77.5f52a8","name":"","links":["211309ae.9cc57e"],"x":135,"y":220,"wires":[["468cec3c.edc1d4"]]},{"id":"40d9fdb8.4f7b6c","type":"delay","z":"8aae4b77.5f52a8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":300,"y":290,"wires":[["468cec3c.edc1d4"]]},{"id":"7dccdaa1.132f14","type":"comment","z":"8aae4b77.5f52a8","name":"PID last","info":"pb = 4.575\nTi = 5746\nd = 0","x":540,"y":120,"wires":[]},{"id":"c3edcaf2.c8de7","type":"range","z":"8aae4b77.5f52a8","minin":"1","maxin":"0","minout":"1","maxout":"0.85","action":"scale","round":false,"property":"payload","name":"Power Scale","x":990,"y":180,"wires":[["7fb90422.38a1cc"]]}]```

I wanted to remind that further to the node-red-contrib-id Colin gave us an awesome guide on PID tuning.

http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html

1 Like

I chatted with Colin yesterday and we are starting a new thread to finish the temp controller. He's onboard. :slight_smile:

1 Like

Dang! I'm trying to get my temperature controller to split outputs from the PID to heat and cool my brew but am running into some hair pulling issues. I've attached a test flow to illustrate my problem.
I hope someone can see what I"m doing wrong.

  1. PV is alwasy 18C
  2. Setpoint can be injected
  3. Power scaling is to match freezer power with 125W heat lamp power
  4. the output is to GPIO pins on a raspberry pi 3B

When setpoint is 17C cooling should turn on but heat turns on.
When setpoint is 19C cooling turns on
When setpoint is 18C neither cool or heat should be on but heat is on!
When setpoint is 18.5C heat turns on as it should.

[{"id":"e146da78.fb048","type":"comment","z":"4b2ce38f.7cfe34","name":"Temperature Control","info":"","x":270,"y":160,"wires":[]},{"id":"44d9ee8c.a6905","type":"inject","z":"4b2ce38f.7cfe34","name":"Setpoint 17C","topic":"setpoint","payload":"17","payloadType":"num","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":280,"y":380,"wires":[["c126f24e.87acc","83c7c9d9.30dfb8"]]},{"id":"a34aa6a.066e758","type":"inject","z":"4b2ce38f.7cfe34","name":"setpoint 18.5C","topic":"setpoint","payload":"18.5","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":280,"y":520,"wires":[["c126f24e.87acc","83c7c9d9.30dfb8"]]},{"id":"f7fa5e38.a728b","type":"inject","z":"4b2ce38f.7cfe34","name":"Stop","topic":"enable","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":310,"y":240,"wires":[["c126f24e.87acc"]]},{"id":"e8cfe365.f68068","type":"inject","z":"4b2ce38f.7cfe34","name":"Start","topic":"enable","payload":"1","payloadType":"num","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":310,"y":200,"wires":[["c126f24e.87acc"]]},{"id":"39173cc1.de495c","type":"inject","z":"4b2ce38f.7cfe34","name":"pv = 18C","topic":"pv","payload":"18","payloadType":"num","repeat":"5","crontab":"","once":true,"onceDelay":"","x":290,"y":280,"wires":[["c126f24e.87acc","c42300c0.a1eec"]]},{"id":"c126f24e.87acc","type":"PID","z":"4b2ce38f.7cfe34","name":"BrewBucket.PID","setpoint":"18.0","pb":"5","ti":"57600","td":"0","integral_default":"1","smooth_factor":"0","max_interval":"600","enable":1,"disabled_op":"0.5","x":580,"y":280,"wires":[["65859772.a923b8","9f1736ac.df0ed"]]},{"id":"65859772.a923b8","type":"function","z":"4b2ce38f.7cfe34","name":"Split Heat/Cool","func":"/* A function designed to be used with node-red-contrib-pid in applications where both\n * heating and cooling are available to control the system.\n * The node is given a power value in msg.payload in the range 0 to 1, such as is produced by \n * node-red-contrib-pid and splits this into a heat power (o/p 1) and cool power (o/p 2) where\n * each is in the range 0 to 1. These can then be fed directly into an output device, if this\n * is continuously variable, or they may be passed to node-red-contrib-timeprop nodes to generate\n * time proportioned on/off outputs.\n * There are two particular issues to be dealt with in a heat/cool application. Firstly is the fact\n * that the cooling device may be more or less powerful than the heating device. It is necessary\n * therefore to be able to adjust the gain of the system separately for heating and cooling. Secondly\n * is the highly non-linear response of some devices, notably refrigerant systems, that can have a\n * large effect initially, then this tails off. To compensate for this it is useful to have an \n * overlap range where both heat and cool are slightly on.\n *\n * To allow for these requirements two variables can be set below. The value of the power input value\n * where the heating starts to come on is determined by the variable heatMin. Above this value the\n * heating will rise till it is fully on with an input of 1.\n * The cooling is fully on when value of the power input is 0, and falls till the cooling is fully\n * off at an input of coolMin.\n *\n * If the heating and cooling systems are of similar power then set heatMin and coolMin both to 0.5\n * in which case input values of 0.5 to 1.0 will map to heating outputs of 0.0 to 1.0,\n * and 0.5 down to 0.0 will map to cooling 0.0 to 1.0.\n * If, for example, the cooling system is more powerful than heating then they can both be set\n * to something like 0.7 which increases the gain in the heating region and reduces it\n * in the cooling region, to compensate for the different powers in the heating/cooling systems.\n * If some overlap is desired (so that both heat and cool are on slightly near the crossover\n * point) then overlap the two settings so that, for example, heatMin might be 0.45 and coolMin\n * might be 0.55\n */\n\n// set these as described above\nvar heatMin = 0.5; // the value of input corresponding to 0 heat o/p\nvar coolMin = 0.5; // the value of input corresponding to 0 cool o/p\n \nvar power = msg.payload;\nvar heat = (power - heatMin)/(1 - heatMin);\n// limit to range 0 to 1\nheat = Math.min(Math.max(heat, 0), 1);\nvar cool = (coolMin - power) / coolMin;\n// limit to range 0 to 1\ncool = Math.min(Math.max(cool, 0), 1);\nreturn [{payload: heat}, {payload: cool}];\n","outputs":"2","noerr":0,"x":780,"y":280,"wires":[["f7b0228.3246ce","71978d89.f96204"],["d607e050.abe4e8","bd956091.35deb"]]},{"id":"d607e050.abe4e8","type":"range","z":"4b2ce38f.7cfe34","minin":"0","maxin":"0.5","minout":"0","maxout":"0.15","action":"scale","round":false,"property":"payload","name":"Power Scale","x":970,"y":300,"wires":[["1ddd1570.e8e76b"]]},{"id":"1ddd1570.e8e76b","type":"debug","z":"4b2ce38f.7cfe34","name":"Cool","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":1170,"y":320,"wires":[]},{"id":"71978d89.f96204","type":"debug","z":"4b2ce38f.7cfe34","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":970,"y":200,"wires":[]},{"id":"bd956091.35deb","type":"debug","z":"4b2ce38f.7cfe34","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":970,"y":350,"wires":[]},{"id":"f7b0228.3246ce","type":"range","z":"4b2ce38f.7cfe34","minin":"0","maxin":"1","minout":"0","maxout":".5","action":"scale","round":false,"property":"payload","name":"Power Scale","x":970,"y":260,"wires":[["ec143908.8f3578"]]},{"id":"ec143908.8f3578","type":"debug","z":"4b2ce38f.7cfe34","name":"Heat","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":1170,"y":240,"wires":[]},{"id":"9f1736ac.df0ed","type":"debug","z":"4b2ce38f.7cfe34","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":970,"y":410,"wires":[]},{"id":"c42300c0.a1eec","type":"debug","z":"4b2ce38f.7cfe34","name":"PV","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":550,"y":360,"wires":[]},{"id":"4f8b1086.2b74d8","type":"inject","z":"4b2ce38f.7cfe34","name":"Setpoint 15C","topic":"setpoint","payload":"15","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":290,"y":420,"wires":[["c126f24e.87acc","83c7c9d9.30dfb8"]]},{"id":"670f16aa.1eb78","type":"inject","z":"4b2ce38f.7cfe34","name":"setpoint 22C","topic":"setpoint","payload":"22","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":290,"y":480,"wires":[["c126f24e.87acc","83c7c9d9.30dfb8"]]},{"id":"83c7c9d9.30dfb8","type":"debug","z":"4b2ce38f.7cfe34","name":"Setpoint","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":560,"y":420,"wires":[]},{"id":"d0f158d1.491b4","type":"inject","z":"4b2ce38f.7cfe34","name":"Setpoint 18C","topic":"setpoint","payload":"18","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":290,"y":340,"wires":[["c126f24e.87acc","83c7c9d9.30dfb8"]]},{"id":"d2c32e2a.ad1728","type":"comment","z":"4b2ce38f.7cfe34","name":"PID","info":"","x":550,"y":160,"wires":[]},{"id":"2df7aeeb.e5528a","type":"comment","z":"4b2ce38f.7cfe34","name":"Heat/Cool Split","info":"","x":780,"y":160,"wires":[]},{"id":"f506de42.eb6c38","type":"comment","z":"4b2ce38f.7cfe34","name":"Power Scaling","info":"","x":970,"y":160,"wires":[]},{"id":"17920388.644ee4","type":"comment","z":"4b2ce38f.7cfe34","name":"TO GPIO","info":"","x":1180,"y":160,"wires":[]}]

To check the splitter is working correctly you need to look at its input and outputs first, so repeat the tests and tell us for each setpoint what are the values of output from the PID node and the two outputs from the heat cool split node.

I've removed the automated temperature sensor and setpoint node and added the test inputs pictured below.
I enabled the PID for each test just enough time to copy the values then disabled the PID again.
My Brewbox temp was 13.6C and my Brewbucket temp was 12.6C.

Here are the values for each set temp while maintaining PV at 18C:
PID settings: v1.1.4
Setpoint: 18.0C
Proportional: 5
Integral: 57600
Derivative: 0
Initial Integral: 1
Max Sample: 600
Derivative Sm: 0
Enable: 1
Output PW: 0.5

Heat Scale:
0 to 1
0 to 0.5

Cool Scale:
0 to 0.5
1 to 0.85

PV Constant 18C
Setpoint variable

Test Setpoint 18.0C
PID Output: 1
Heat Split: 1
Cool Split: 0
Heat GPIO: 0.5
Cool GPIO: 1 (inverted)

Test Setpoint 18.5C
PID Output: 1
Heat Split: 1
Cool Split: 0
Heat GPIO: 0.5
Cool GPIO: 1 (inverted)

Test Setpoint 19.0C
PID Output: 1
Heat Split: 1
Cool Split: 0
Heat GPIO: 0.5
Cool GPIO: 1 (inverted)

Test Setpoint 17.5C
PID Output: 0.899956501736111
Heat Split: 0.79987821875
Cool Split: 0
Heat GPIO: 0.399912986
Cool GPIO: 1 (inverted)

Test Setpoint 17.0C
PID Output: 0.7998520225
Heat Split: 0.5996345243
Cool Split: 0
Heat GPIO: 0.299782456
Cool GPIO: 1 (inverted)

Test Setpoint 16.0C
PID Output: 0.5996730399
Heat Split: 0.1992147038
Cool Split: 0
Heat GPIO: 0.09950390
Cool GPIO: 1 (inverted)

Test Setpoint 15.0C
PID Output: 0.3992952
Heat Split: 0
Cool Split: 0.20161812
Heat GPIO: 0
Cool GPIO: 0.93945166

Many thanks for taking a look. I appreciate your help again. :slight_smile:
Charles

I don't see anything wrong with the heat/cool splitter. When the pid output is between 0.5 and 1.0 it is driving the heater between 0 and 1, and when it is between 0.5 and 0 it is driving the cooler between 0 and 1. So 0 pid o/p means full cool, 0.5 means both off, 1.0 means full heat.
Previously did you just have the cooler connected? If so you should double the Proportional Band as the range of cool output is now only over half of the range, whereas previously it was over the whole band. That way the operation during cooling should be the same as it was before, assuming you haven't changed anything else.

Also I guess you might find that the cooler is on when it should be off and vice versa, as before you were driving it from the pid output and interpreting 0 to mean full cool, whereas now the splitter inverts the cool for you and gives 0 to 1 meaning off to full cool.

Previously I we just worked with the cooler to avoid complications.
Our plan was to run the cooler part through the summer and when the temperatures got cool enough to tune the Heat.
The problem I'm seeing is that with a PV of 18 and a Setpoint of 18C the heater is on. I have to reduce setpoint to 15C to get the cooler to come on.
When the PV and Setpoint are equal the output from the PID is not 0.5, it's up around 0.99.

I am sure we went through this last time. You have to remember that the Integral term moves the proportional band up or down around the setpoint in order to stabilise the system. The situation you are seeing is just because you have been running it open loop (so the heat/cool the pid asks for is not being delivered). Consider what would happen if you closed the loop, keeping the setpoint at 18. The heat will come on and the temperature will rise so the algorithm will reduce the heat and if it doesn't come down it will keep reducing it and eventually (if necessary) it will start cooling and then, if the loop is well tuned will stabilise the system with a bit or heat or a bit of cool dependent on the ambient temperature. All you need worry about for the moment is that if the temperature is much too high then the cooling comes on and if it is much too low then the heat comes on. The next thing to do is to close the loop and see what happens. If you have already got it performing well when cooling then, as I said, double the PB to allow for the fact that the total cool range is half what it was, and try it. Since it is already tuned for the cooling region (which is usually the most difficult) then with luck all you will have to do is to adjust the crossover point in the heat/cool splitter to get the balance between the two systems correct.

OK, I'll hook it all back up and let-r-rip and see what happens. I have a six gallon carboy of water in the freezer so it should be a good test.
Charles

Am I right in remembering that you have Influx and Grafana setup so you get good charts of what happens? If so you need to be charting the setpoint, temperature, pid o/p, and the heat and cool from the splitter. To test the heating side you can set a setpoint higher than ambient and to test the cooler set the setpoint lower than ambient.

I do and I'm tracking Brewbox, Brewbucket, Setpoint, Heat power and Cool power.

Put the PID output in as well.

I have all the parameters above tracking in Grafana.
I started with a PV of 54.1625°F and a setpoint of 55°F and it has turned on heat to full power for an hour and 20 minutes. The PV is now 53.6375°F and climbing.
I'll let it go as long as I can. The last time the heat lamp took off on it's own it melted the inner lid of my freezer. :frowning:

If it started at 54 with full heat, why has it gone down?

Sorry, typo. It's 56.6375°F

I have just realised why it is starting at full heat, you have got initial integral set to 1 which means on restart it will output full heat if it is on the setpoint. If you set that to 0.5 and restart then the initial condition will be no heat/cool if it is on the setpoint. Of course that only applies on a deploy or node-red restart, but would at least let you get going from a more sensible point.

Is that Integral value (that's about 15 hours isn't it?) what we determined when tuning the cool? It is going to take a hell of a long time to wind the power down.

It immediately dropped to 0.76 and is dropping steadily. I'll just shut it off and equalize the temperatures again. It'll take awhile because the freezer temperature is almost 80°F

The Integral is 57600 = 16hrs.