It ain't pretty but it's mine: DIY arduino mash temp controller

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
Joined
Feb 24, 2013
Messages
1,857
Reaction score
2,202
Location
New Brighton
Well, it took me the better part of the weekend to get it all together and write the program, but I managed to get it working. No small feat for me since I'm not much of an electrical engineer or programmer!

For the prototype I've just screwed everything to a board to test it out. Eventually I'll get it all put into an enclosure.

It is running on an Arduino Uno. Eventually I'll move it to a Nano to fit it into an enclosure. It uses two DS18b20 temperature probes on an I2C wire. One monitors the temp at the RIMS tube. The second probe is directly in the mash. It includes a rotary encoder knob to set the mash temp as well as turn the element on. There is a push button to run the pump as well. Parts are all off of eBay and Amazon.

The control algorithm is pretty simple. If the RIMS temp and the mash temp are both below setpoint, and the pump is on, it will energize the element. It's stupid simple, but with my 5 gallon water test it keeps mash temp to within .3F of setpoint. I had tried a complicated PID setup on my last try, but it seemed to way overthink things and tended to overshoot. This system uses the immediate response of the RIMS tube measurement to dampen the heating, and the thermal mass of the kettle to dampen the overshooting. I can't wait to see how it works with an actual mash!

IMG_1933.jpg
 
Hi, I am working on a similar but simpler project, with two DS18B20 sensors both in the mash wort. I am using gas, not electricity. Would you like to share the sketch?
 
Hi, I am working on a similar but simpler project, with two DS18B20 sensors both in the mash wort. I am using gas, not electricity. Would you like to share the sketch?

I'd be happy to share it. As I said, I'm pretty inexperienced, so use it at your own risk!

I still have some debug lines in here. I haven't taken any time to clean things up yet.

/*
* Mash temperature controller V1.0
* By Paul Aplikowski
* Designed for a RIMS system.
*
*/


//Various Libraries to include
#include <ClickEncoder.h>
#include <TimerOne.h>

#include <OneWire.h> //temp probe communication
#include <DallasTemperature.h> //temp probe communication
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

//LCD Setup
#define I2C_ADDR 0x27
#define BACKLIGHT_PIN 3
#define LCD_RS 0
#define LCD_RW 1
#define LCD_EN 2
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7

#define LCD_CHARS 20
#define LCD_LINES 4

//Pin Definitions
// Note that encoder pins are set at creation of ClickEncoder below
#define ONE_WIRE_BUS 2 //data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_PWR 3
#define ONE_WIRE_GND 4

#define PUMP_SWITCH 8 //the switch to control the pump
#define PUMP_POWER 9 //the relay to control the pump (HIGH means off)

#define ELEMENT_POWER 7 //the SSR to control the element

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire); //pass our oneWire reference to Dallas Temperature
LiquidCrystal_I2C lcd(I2C_ADDR,LCD_EN, LCD_RW, LCD_RS, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

//Variables
ClickEncoder *encoder;
int16_t last, setP = 125;
float rTemp;
float mTemp;
bool pumpState = HIGH; //setting to turn pump on and off
String pumpText = "OFF";
bool demandHeat = LOW; //flag to determine if heating is in demand
String demandHeatText = "REST";
bool elementArmed = LOW; //setting to arm heating element
String elementArmedText = "ARMED";
void timerIsr() {
encoder->service();
}
//pump button variables
int reading = 0;
int buttonState;
int lastButtonState;
long lastDebounceTime = 0;
long debounceDelay = 50;

/*void displayAccelerationStatus() {
lcd.setCursor(0, 1);
lcd.print("Acceleration ");
lcd.print(encoder->getAccelerationEnabled() ? "on " : "off");
}*/

void setup() {

//temp probe setup
sensors.begin();
pinMode(ONE_WIRE_PWR, OUTPUT);
digitalWrite(ONE_WIRE_PWR, HIGH);
pinMode(ONE_WIRE_GND, OUTPUT);
digitalWrite(ONE_WIRE_GND, LOW);
pinMode(ONE_WIRE_BUS, INPUT);

//initialize the pump switch and relay pin
pinMode(PUMP_SWITCH, INPUT);
pinMode(PUMP_POWER, OUTPUT);
digitalWrite(PUMP_POWER, HIGH); //assumes sainsmart type relay where HIGH means off

//initialize the element pin
pinMode(ELEMENT_POWER, OUTPUT);
digitalWrite(ELEMENT_POWER, LOW);


// Switch on the backlight
lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
lcd.setBacklight(HIGH);
lcd.home (); // go home
Serial.begin(9600);
encoder = new ClickEncoder(A1, A0, A2);

//startup screen
lcd.begin(LCD_CHARS, LCD_LINES);
lcd.clear();
lcd.print(" RedShirt Brewing");
lcd.setCursor(0,1);
lcd.print(" RIMS Mash Temp");
lcd.setCursor(0,2);
lcd.print(" Controller");
lcd.setCursor(0,3);
lcd.print(" v1.0");
delay(3000);

screenInitialize();

Timer1.initialize(1000);
Timer1.attachInterrupt(timerIsr);

last = -1;

sensors.requestTemperatures(); //request temperatures early in order to initialize the starting temperature
}

void loop() {
//check rotary encoder and set setpoint
setP += encoder->getValue();
if (setP != last) {
last = setP;
Serial.print("Setpoint: ");
Serial.println(setP);
}

//Update temperatures
sensors.requestTemperatures();
rTemp = sensors.getTempCByIndex(0) * 1.8 + 32;
mTemp = sensors.getTempCByIndex(1) * 1.8 + 32;
//Serial.print(rTemp);
//Serial.print(mTemp);

//check the pump switch
reading = digitalRead(PUMP_SWITCH);
//
if(reading != lastButtonState){
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay){
if (reading != buttonState){
buttonState = reading;
//only toggle the pump if the new button state is HIGH
if(buttonState == HIGH){
pumpState = !pumpState;
}
}
}
lastButtonState = reading;
digitalWrite(PUMP_POWER, pumpState);

//check the encoder button
ClickEncoder::Button b = encoder->getButton();
if (b != ClickEncoder::Open) {

if (ClickEncoder::pressed){
if (elementArmed == LOW)
elementArmed = HIGH;
else if (elementArmed == HIGH)
elementArmed =LOW;
}

}
screenDraw();
controlSystems();
}

/* int SetPoint(){
Serial.print("Held!");
ClickEncoder::Button b = encoder->getButton();
while(b != ClickEncoder::Held){
//Serial.print("ClickEncoder");
value += encoder->getValue();

if (value != last) {
last = value;
Serial.print("Encoder Value: ");
Serial.println(value);

lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(value);
}
}
} */
void screenInitialize(){
lcd.clear();
lcd.print("Stat: Pump:");
lcd.setCursor(0,1);
lcd.print(" :Setpoint");
lcd.setCursor(0,2);
lcd.print(" :RIMS Temp");
lcd.setCursor(0,3);
lcd.print(" :Mash Temp");
}
void screenDraw(){
if (demandHeat == LOW)
demandHeatText = "REST";
else demandHeatText = "HEAT";
if (pumpState == HIGH) pumpText = "OFF";
else pumpText = " ON";
if (elementArmed == LOW) elementArmedText = " OFF";
else elementArmedText = "ARMED";
lcd.setCursor(5,0);
lcd.print(" ");
lcd.setCursor(5,0);
lcd.print(demandHeatText);
lcd.setCursor(17,0);
lcd.print(" ");
lcd.setCursor(17,0);
lcd.print(pumpText);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(15,1);
lcd.print(elementArmedText);
lcd.setCursor(0,1);
lcd.print(setP);
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print(rTemp,1);
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print(mTemp,1);
}

/*int pumpToggle(int buttonPin){
//variables
int reading = 0;
int buttonState;
int lastButtonState;
long lastDebounceTime = 0;
long debounceDelay = 50;

reading = digitalRead(buttonPin);
//
if(reading != lastButtonState){
Serial.print(reading);
Serial.print(lastButtonState);
lastDebounceTime = millis();
}

if ((millis() - lastDebounceTime) > debounceDelay){
if (reading != buttonState){
buttonState = reading;
//only toggle the pump if the new button state is HIGH
if(buttonState == HIGH){
pumpState = !pumpState;
}
}
}
lastButtonState = reading;
//Serial.print(pumpState);
return pumpState;
}
*/

void controlSystems(){
if (pumpState == LOW)
digitalWrite(PUMP_POWER, LOW);
if (rTemp<setP && mTemp<setP && elementArmed && !pumpState)
demandHeat = HIGH;
else demandHeat = LOW;
digitalWrite(ELEMENT_POWER, demandHeat);

}
 
Got a like on this thread and thought I'd update on how it's been working.

I've done maybe a dozen brews on this system now. It has been working quite well. I can keep the mash to within about 1/2 degree F of setpoint. It took me a while to figure out how to keep the recirc going. At first I had a tendency to get the pump running on full and then turn down the flow. I was having a lot of trouble with stuck mashes. It took me a few brews to realize that the full flow at the beginning resulted in stuck mashes a while into the mash. Since I've started turning the recirc way down to begin with I've had much better luck.

One issue that could use improvement is that the RIMS tube does seem to overshoot temp. While the thermal mass of the mash stays very consistent, the RIMS tube will fire up, take a minute or so until the mash picks up it's .2 degrees or so to get back to set point. But what happens then is that the RIMS tube is heated up. So it continues to heat the mash for +/- 30 seconds as the element cools down. This will often put the RIMS temp 3-4 degrees over set point. I have not seen any real negative affects of this. My attenuation have been within a point or two of what Beersmith predicted.

Still the bounce bothers me. I have thought of a few solutions, but have not found the time to try to implement them yet. The simplest I think would be to install a rheostat in the heating element circuit. By turning that down until the element stays cooler but runs for a longer duty cycle I think I would avoid the bounce. This is basically what I did when I was still using a hot plate below the tun. I would run it on about #2 and it would stay very close to temp.

The other idea I had was to either try to program in PID for the RIMS tube temp only, or possibly use PWM in the program to keep the element cooler in a manner similar to what the rheostat would do. These are on my to do list with no particular deadline...

Also, still all screwed down to the board. I keep it all in a small clear Rubbermaid container. Somehow I'm more interested in actually brewing than finishing this project up! I'll get to it one of these days...
 

Latest posts

Back
Top