As we saw in the last post (CoffeePID: the build) the project integration was quite successful. Unfortunately the resulting temperature curve is not what I was hoping for. But to be able to fix this issue, we first need to understand the problem a bit better. The target temperature was set to 97°C, so the heater stopped at that limit. So lets start taking a closer look at that first measurement:
So what can we take away from this?
It seems that the heater is quite powerful, as it quickly manages reheating the boiler at falling temperatures. This means it should be possible to keep the temperature pretty stable, or at least keep the “underswing” of the temperature at a minimum.
The system is very inert. After only 2 seconds of heating the measured temperature is still increasing for more than 5 seconds. This means that we have to be more careful with (re-)heating phases and that it may be a good idea to add some delay after heating, before we take the next measurement and react on it.
Fun fact: We can clearly see that the frequency of the oscillation decreases over time, as the heat takes longer to dissipate and therefore increasing the cool down duration. So we basically know the state of the system (all components cold or heated up) by just taking a look at that frequency.
The last thing to state is, that the current heating strategy is crap. After reaching a “stable” system the temperature was still swinging between -2°C and +7°C around the target value.
Note: This curve is also exactly what we would expect from the static thermostat originally used. It just switches the heater at a “fixed” temperature. At this point I just digitized the original problem. Yay!
Let’s fix this
At this point I already tried at least 3 different strategies to optimize the temperature regulation and I am somehow sure that I’m not done. But this was the first real promising attempt:
Based on the measured temperature we need to calculate the duration to heat up to the target temperature. This will be a proportional factor, increasing the heating duration proportional to the temperature delta. For example heating double as long to heat up +20°C compared to +10°C. But this part is not enough, as it would never heat at or above the target temperature, but this may be a good idea on a quickly falling temperature. So we need a history of the temperature readings and calculate the incline. If the temperature is rapidly falling we need to heat longer to achieve the same heating effect as if the temperature was raising. We will later only have to find the constants needed to incorporate the formula to describe this system. So the formula simply looks like:
t = A * ΔT + B * a
A and B are the constants and a is the incline of the temperature curve, which is measured over the last 5 values in history. We can even simplify this by directly using the temperature delta as the incline. So the formula reads: the time to heat up to a specific temperature is equal to the sum of a constant times the temperature delta to overcome and a constant times the temperature delta of the last 5 values. It is obvious that B has to be a negative value here, as a temperature decrease (negative delta) should lead to a larger heating duration. I tested this a lot with my Gaggia Classic on my desk close to my computer and I came to these magic values:
A = 620, B = -8600
I’m sorry that those values most likely won’t work for you out of the box, as these are very system specific and timing critical. Even adding or removing Serial.println()-statements in the code will change the timing and therefore you have to optimize these values again. As I said, I spent some time doing this. But the values are actually not that magical, as we need a duration in milli seconds the heater should run. So A is just the time the heater runs to heat up the water 1°C. Whereas B is a more abstract factor which is important for the differential part. The factor can be substantial, e.g. when the current temperature is very close to the target temperature but rapidly falling. The first factor will then be close to zero, so the heating duration has to come from this part. So with my current values, if the temperature fell 0.25°C in the past, we would add 2150 ms to the heating duration. Reading these values the first time in written form, I have to say that it sounds really plausible. And the results were also pretty promising:
We can still see some minor oscillation in the curve, but if you check the y scaling, you will see that the temperature oscillation is less than 1°C in total amplitude.
One more thing
After I finished tweaking the values I was quite happy with the result. But that only lasted till the next day, when I made my first coffee. I realized that the values didn’t work as great in a cold-start scenario. The machine initially needed many heating phases to come close the target temperature although it was designed to “shoot for the target” with every heating phase. This is also plausible in retrospective, as the machine itself needs more “energy” on the first start to heat up all internal components and materials. The simple solution was to change the magic values a bit (A = 1000, B = -9000). This reduced the duration to reach a stable target temperature a lot and after around 10 minutes the system was swung in to less than ±0.3°C.
Conclusion
After using my Gaggia Classic with CoffeePID for some time now, I’m still satisfied. The machine obviously still takes about the same time to heat up completely, but it then manages to keep the temperature very stable. Even after pulling a shot of espresso it recovers pretty quickly. From this point of view I would declare this project successfully finished. Yay!
Outlook
It took me some time to write all these findings down and while I was working silently, a tweet and a github pull request reached me. A reader of this blog, built a CoffeePID himself and made quite some additions to the source code. This is so great and it promises us many features that I wanted to add in the future, namely:
Over-The-Air updating
a “real” PID-control
a PID-autotuner
I am currently in the state of checking this pull request and hope to come back to you pretty soon. Thanks for reading till the end, if you did ;) Stay tuned.