Home Brewing control system

Hi all, I am new to Arduino, and looking to use the Arduino to control my home brewery.

I am writing a program to do the following:

  1. Power a pump, heater, temp sensor, and LED

  2. when temp sensor reads 150F:
    a) start a timer (do not restart timer each time temp 150 is hit, just the first time it is hit)
    b) turn on an LED for 5 sec

  3. If temp sensor reads > 150F, turn off heater. If <150F, turn heater back on

  4. When timer = 60 min
    a) turn pump and heater off
    b) turn on LED

I have built the circuit for this and am able to do all these tasks independently. The issue is combining them all into one independent program. I am able to control the heater on and off based on input from the temp sensor (step 3 above), so that is a start. I know it will take a combo of if, else, while, switch statements to best accomplish this, but maybe there is another function that can better do this?

Any recommendations? Any input is appreciated, thanks!

Why do you think whatever you are doing needs "a whole bunch of" if, else and while statements? (I don't think you'll need switch at all)
If you are able to do each step then -except for step 3- everything else is simply sequential. Sure. You will need some if statements to check which "step" you are at and what your current time interval is. And then some control logic to execute the next increment of that step in your loop(). If you have programmed the individual steps though, I don't think sequencing them would be that hard.

BUT,
the step 3 is not as simple as you have written. The issue is with the lag/latency of your heater. Even if you shut if off as soon as you hit 150, the temp may continue to rise to say 156 that will kill your enzymes before you come back to 150. So you have to anticipate the temp going up and maybe shutoff before it reaches there and so on. This whole algorithm is called "PID control" and you'll find tons of resources on the web for that. So this algorithm along with some empirical parameters by will allow you to stick your temp very close to your required 150 for the entire 60 minutes. But coding PID is not trivial if you are new. If you search, somebody may have ready made code for Arduino that implements PID.

OR,
your other option is to buy a ready made PID hardware controller and hook it up to your own heated container. No need to do any programming. Search for "Sous-vide PID" and you'll find tons of resources on that.

HOWEVER,
Doing it yourself with Arduino will allow you some nicer customization though. You can create your own mash profile like 125 for 15 mins then 142 for 15 mins and 150 for 30 mins etc.

Hope this gives you enough stuff to brewed -I mean- brood upon. cheers!

Thanks for all the info and the great pun at the end!

I am aware of the hysteresis issue with the temperature control. I was planning on conquering that a little later, but now you have solved that for me, so thanks!.. I will explore the PID controllers.

I have made some progress since I posted this. I did similar to what you described, and it verifies, I just need to actually test it.

I have not yet put in the timer. How do I do a timer that starts the first time 150deg is hit, but doesn't reset each time 150 is hit? I am afraid that if I do a timer that starts at tempsensor value = 150, it will reset each time 150 is hit, and will never accomplish its mission. Even with the PID controller, I will go slightly above/below 150 and constantly reset the timer, as I see it.

Thanks!

You need a boolean variable to keep track of whether you've hit 150. Might as well make it global. It will be initialized to false. Call it something like ReachedBrewingTemp. When you read the temperature, if it is >= 150 and ReachedBrewingTemp is false, note the time using millis and set ReachedBrewingTemp to true. That start time will be used to compute your 60 minute period later.

wildbill,

I am back on to this and still struggling with the time. I think you may be able to help me understand what will work for me. I used the boolean variable and set it equal to true when my desired temp range is reached. I then say if that boolean is true, set mash start time = millis().

Here is the portion of my code I am having trouble with:

  if(MASHTANKTEMP >= (MASHTEMP - 2) && MASHTANKTEMP <= (MASHTEMP + 2)){ 
    boolean Timer1 = true; 
    analogWrite(LED, HIGH);
    Serial.print("MASH TANK temperature is: ");
    Serial.print(MASHTANKTEMP);
    Serial.print("F");
    Serial.print(" Mash Temp Reached, Mash Timer is On");
    Serial.print("\n\r"); 
   }  
  
  if (boolean Timer1 = true);{ 
    MashStartTime = millis();    
      }
     
//THIS STARTS THE HANDOFF FROM MASHING TO BOILING (PROCESS #3)
   if ((millis()-MashStartTime) >= MASHTIME ){ 
     Serial.print("Mash Complete.");
     Serial.print("\n\r"); 
     analogWrite(LED, 0);
     analogWrite(HEATER1, 0);
     analogWrite(PUMP1, 0);
     analogWrite(PUMP2, 255);
     Serial.print("Pump#2 On. Wort to Boil Tank.");
     Serial.print("\n\r");

my (millis()-MashStartTime) is always = 0 the way it currently is. How do I fix that? Thanks!

Better to post all your code - it's hard to see what's going on with a snippet.

That said, there's several errors here:

  if (boolean Timer1 = true);{ 
    MashStartTime = millis();    
      }

= is assignment, you need == in the if statement for comparison. Get rid of the word boolean - it's declaring a new variable. Also, there's no reason to check a boolean against true, you can just say:

if(Timer1)

Then, you have an erroneous semicolon after the if, which means that the following statement will always be executed, irrespective of the result of the test.

Finally, that stuff shouldn't be there at all; you should note the time in the same section that announces the mash timer is on, but only if timer1 (terrible name by the way) is false.

I'd suggest you fix the issues in my last paragraph and if that doesn't solve things, post the whole sketch.

Thanks for the reply, wildbill. I made the changes you suggested except for putting the time in the section that announces mash timer on. I didn't do this because I wasn't able to see how how the boolean variable would then be linked to the MashStartTime variable.

Here is my full code:

#include <OneWire.h>
#include <DallasTemperature.h>
//#include <Time.h>

// Data wire (containing the Temp Sensors) is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 3
// Setup a oneWire instance to communicate with any OneWire device
OneWire oneWire(ONE_WIRE_BUS);
// Pass the oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// Assign the addresses of the 1-Wire DS18B20 Temp sensors.
DeviceAddress TEMPSENSOR2 = { 0x28, 0xC3, 0x14, 0xAC, 0x04, 0x00, 0x00, 0x9E };
DeviceAddress TEMPSENSOR3 = { 0x28, 0x57, 0xF9, 0xD0, 0x04, 0x00, 0x00, 0x7F };

//Assign floating variables for temperatures
float MASHTANKTEMP;
float BOILTANKTEMP;
  
//User Inputs:
//MASH TEMP
const int MASHTEMP = 82; // Desired Mash Temp(deg F)
//MASH TIME
const int MASHTIME = 10000; //Desired Mash Time(milli sec.)
//BOIL TIME
const int BOILTIME = 10000; //Desired Boil Time(milli sec.)

boolean MashTimer = false; //used for MASH TIME
long MashStartTime; //used for MASH TIME
boolean BoilTimer= false; //used for BOIL TIME
long BoilStartTime; //used for BOIL TIME

//Pin Inputs to Arduino:
//PUMP#1 (MASH TANK THROUGH HOT TANK)
const int PUMP1 = 9; // Pump to pin 9
//PUMP#2 (MASH TANK TO BOIL TANK)
const int PUMP2 = 10; // Pump to pin 10
//HEATER#1 (HOT TANK)
const int HEATER1 = 12; // Heater #1 to pin 12
//HEATER#2 (BOIL TANK)
const int HEATER2 = 13; // Heater #2 to pin 13
//LED
const int LED = 8; // LED to pin 8

//This establishes communication feedback with the Arduino
//and assigns functions to the components
void setup(void) 
{
  // start serial port to report back values and status
  Serial.begin(9600);
  // Start up the library
  sensors.begin();
  // set the resolution to 9 bit
  sensors.setResolution(TEMPSENSOR2, 9); //Resolution of 9 means 0.5degC increments.
  sensors.setResolution(TEMPSENSOR3, 9); //Resolution of 9 means 0.5degC increments.
  
  //initialize PUMP1 as the OUTPUT:
  pinMode(PUMP1, OUTPUT);
  //initialize PUMP2 as the OUTPUT:
  pinMode(PUMP2, OUTPUT);
  //initialize HEATER1 as the OUTPUT:
  pinMode(HEATER1, OUTPUT);
  //initialize HEATER2 as the OUTPUT:
  pinMode(HEATER2, OUTPUT);
 }

//The Control System script starts here:

void loop() 
{ 
  delay(2000); //give a reading every 2.0 sec
  sensors.requestTemperatures();
  
  MASHTANKTEMP = sensors.getTempF(TEMPSENSOR2);
  BOILTANKTEMP = sensors.getTempF(TEMPSENSOR3);
  
 
 
  //Mash Tank Temp is LESS THAN user input, turn Pump #1 and Heater #1 ON
  if (MASHTANKTEMP < MASHTEMP && MashTimer < MASHTIME) { // Constrained to only triger during mashing
    analogWrite(HEATER1, 255);
    analogWrite(PUMP1, 255);
    Serial.print("MASH TANK temperature is: ");
    Serial.print(MASHTANKTEMP);
    Serial.print("F");
    Serial.print(" Pump#1 and Heater#1 On");
    Serial.print("\n\r"); 
  }
  
 //Mash Tank Temp GREATER THAN user input, turn Pump#1 and Heater#1 OFF
    if (MASHTANKTEMP > MASHTEMP && MashTimer < MASHTIME) { // constrained to only trigger during mashing
    analogWrite(HEATER1, 0);
    analogWrite(PUMP1, 0);
    //Serial.print(" F: ");
    Serial.print("MASH TANK temperature is: ");
    Serial.print(MASHTANKTEMP);
    Serial.print("F");
    Serial.print(" Pump#1 and Heater#1 Off");
    Serial.print("\n\r"); 
  }
  
  //Otherwise, Temp is = MASHTEMP
  if(MASHTANKTEMP >= (MASHTEMP - 2.0) && MASHTANKTEMP <= (MASHTEMP + 2.0) && MashTimer < MASHTIME){ //Give range to ensure you trigger the timer and mash time is not reached
    MashTimer = true; //Only triggers the FIRST time this condition is met, not each time
    analogWrite(LED, HIGH);
    Serial.print("MASH TANK temperature is: ");
    Serial.print(MASHTANKTEMP);
    Serial.print("F");
    Serial.print(" Mash Temp Reached, Mash Timer is On");
    Serial.print("\n\r"); 
  }  
  
  if (MashTimer){ //Starts the Mash Timer if condition above is met (Mash Temp is reached, boolean MashTimer = true)
    MashStartTime = millis(); // assigns Timer1 as the time passed since the program started   
    
    //Test to see if timer is working correctly:
    Serial.print("MashStartTime - millis()=  ");
    Serial.print(MashStartTime-millis());
    Serial.print("\n\r"); 
  }
     
//THIS STARTS THE HANDOFF FROM MASHING TO BOILING (PROCESS #3)
   if ((millis()-MashStartTime) >= MASHTIME && BoilTimer == false){ //Make it GREATER THAN OR EQUAL TO so that it ensures the condition triggers. //BoilTimer false so it only runs before boil after Mash timer is complete
     Serial.print("Mash Complete.");
     Serial.print("\n\r"); 
     analogWrite(LED, 0);
     analogWrite(HEATER1, 0);
     analogWrite(PUMP1, 0);
     analogWrite(PUMP2, 255);
     analogWrite(HEATER2, 255);
     Serial.print("Pump#2 & Heater#2 On. Wort to Boil Tank.");
     Serial.print("\n\r"); 
     //delay (20000); //This should turn the pump off after 20 seconds. but will keep repeating until condition is no longer satisfied. Fix this.
     //analogWrite(PUMP2, 0);
     }
     
// THE FOLLOWING CONTINUES AND CONTROLS THE BOIL PROCESS (PROCESS #3)
  
  //while ((millis()-MashStartTime) > MASHTIME && BoilTimer == false);{ //Add this so boil only takes places after Mashing is complete
      //analogWrite(HEATER2, 255); //This starts heater #2 to begin the BOIL process, Process #3
      //Serial.print("Heater#2 On.");
      //Serial.print("\n\r");
     
  if ((millis()-MashStartTime) > MASHTIME && BOILTANKTEMP >= 85 && BOILTANKTEMP <= 95){ 
    // Triggers after mash time is complete and once temp range is reached. 
    BoilTimer = true; //SHOULD only trigger the FIRST time this condition is met, not each time
    analogWrite(HEATER2, 0);
    Serial.print("Boil Temp has been reached");
  }
  if (BoilTimer){ ////Starts the Boil Timer if condition above is met (Boil Temp is reached, boolean Boil Timer = true)
     BoilStartTime = millis();
  }
  
  if ((millis()-BoilStartTime) >= BOILTIME && BoilTimer == true){ //Greater than or equal to ensures that you will hit and trigger this function
    analogWrite(LED, HIGH);
    analogWrite(HEATER2, 0);
    Serial.print("Boil is complete");
    Serial.print("\n\r");
    }
  else
    analogWrite(HEATER2, 0);
}
//}

What happens now is that the timer is set to 10 sec and i read it every two seconds, I get 5 printouts on temp and then it says mash is complete, like it isn't even reading the temp. See serial printout:

MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
Mash Complete.
Pump#2 & Heater#2 On. Wort to Boil Tank.
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
Mash Complete.
Pump#2 & Heater#2 On. Wort to Boil Tank.
MASH TANK temperature is: 74.30F Pump#1 and Heater#1 On
Mash Complete.

Thanks again, I really appreciate your help!

Your immediate problem is this:

   if ((millis()-MashStartTime) >= MASHTIME && BoilTimer == false)

The fifth time round loop, a little over 10 seconds have passed. MashStartTime has not been set and as it's global it was initialized to zero. At this stage, the BoilTimer hasn't been touched either, so it still has it's initialized value of false.

So, irrespective of the temperature, ten seconds in, that if statement is true and the code then declares the mash complete. You need to check the MashTimer flag in that if too.

Your names are causing you problems though - you're mixing up your flags and times in several places e.g. here:

  if (MASHTANKTEMP < MASHTEMP && MashTimer < MASHTIME) { // Constrained to only triger during mashing

MashTimer is a boolean, but you're comparing it to a number of milliseconds. I'd suggest you give it a better name - MashCompleted perhaps.

By convention, anything in all caps is a constant so although the compiler doesn't care, anyone reading your code will be surprised by MASHTANKTEMP.

This is executed every time around loop:

  if (MashTimer){ //Starts the Mash Timer if condition above is met (Mash Temp is reached, boolean MashTimer = true)
    MashStartTime = millis(); // assigns Timer1 as the time passed since the program started

So once MashTimer becomes true MashStartTime will be reset on every iteration - once you fix the issue with comparing flags to time, it needs to be set after this:

 Serial.print(" Mash Temp Reached, Mash Timer is On");

If you sort out your names and test your flags appropriately, you should be able to get this working. Personally though, I'd restructure it as a state machine (q.v.)

Thanks very much wildbill. Your suggested changes make sense, it was just hard for me to find the errors. I will implement the changes and see how it works. Also, thanks for mentioning the state machine function, I didn't know it even existed. It looks like that would be a much better/cleaner format for what I am doing. I will get to that later! Thanks again.

ajauger:
Thanks very much wildbill. Your suggested changes make sense, it was just hard for me to find the errors. I will implement the changes and see how it works. Also, thanks for mentioning the state machine function, I didn't know it even existed. It looks like that would be a much better/cleaner format for what I am doing. I will get to that later! Thanks again.

Have a look in the playground - there is a State Machine library written by a guy from somewhere in Europe that takes a lot of the hard work out of setting up a state machine - perfect for what you are trying to do

Craig

Hi guys this is a tad off topic, but I am also a home brewer of about 120 successful brews. I cheat by using Coopers kits and don't go preparing mashes etc, however I have used a Picaxe system (built 2006 well before the lovely Arduino was thought of) to control the temperature of the fermentation and found it super helpful. In a very quick summary I use two 75W globes, in series behind a baffle, so they have a fair bit of power, but don't run too hot and can handle repeated switching , in a keg cabinet with the Picaxe reading a DS18B20 and controlling the 2 heating lights through a solid state switch . It drives a 2 line LCD display to show the set temp and the current temp setting. This is stored in EEPROM and means if there is a power failure (and we get quite a few) the brew thermostat restarts at the right temperature. A small computer fan on the baffle runs continuously and helps eliminate the temp gradients.

Getting a brew off to the right temp and knowing it stays there until ready (all sugars fermented=no exploding recycled stubbies) for bottling has been a huge help in being able to reliably whip up brews whenever I feel like it (currently trying to keep it to one per month maximum :)).

To cut a long story short, have you already applied what you have got from this project here to that phase of the brewing cycle as well?

I have bought an alcohol detector to maybe move up to an Arduino and program it to show when the fermentation has stopped ie alcohol % in vapours stabilises? And also try a lowering of temp over a period of time for lagers for example, ie start at 24+C to get things moving then after a few hours, smoothly lower it down to around 16-17C?

Cheers, Rob

robwlakes:
Hi guys this is a tad off topic, but I am also a home brewer of about 120 successful brews. I cheat by using Coopers kits and don't go preparing mashes etc, however I have used a Picaxe system (built 2006 well before the lovely Arduino was thought of) to control the temperature of the fermentation and found it super helpful. In a very quick summary I use two 75W globes, in series behind a baffle, so they have a fair bit of power, but don't run too hot and can handle repeated switching , in a keg cabinet with the Picaxe reading a DS18B20 and controlling the 2 heating lights through a solid state switch . It drives a 2 line LCD display to show the set temp and the current temp setting. This is stored in EEPROM and means if there is a power failure (and we get quite a few) the brew thermostat restarts at the right temperature. A small computer fan on the baffle runs continuously and helps eliminate the temp gradients.

Getting a brew off to the right temp and knowing it stays there until ready (all sugars fermented=no exploding recycled stubbies) for bottling has been a huge help in being able to reliably whip up brews whenever I feel like it (currently trying to keep it to one per month maximum :)).

To cut a long story short, have you already applied what you have got from this project here to that phase of the brewing cycle as well?

I have bought an alcohol detector to maybe move up to an Arduino and program it to show when the fermentation has stopped ie alcohol % in vapours stabilises? And also try a lowering of temp over a period of time for lagers for example, ie start at 24+C to get things moving then after a few hours, smoothly lower it down to around 16-17C?

Cheers, Rob

Nice Post - gotta love the picaxe and the reved forums for how helpful everyone is and how quickly you can knock a project together. It certainly takes a while before you hit the limits with the chips for these types of projects.

Craig

I am having a little bit of trouble understanding exactly how to use the State Machine function.

As I understand it, my “states” would be:

1 – Mash temp less than user input
2 - Mash temp within user input range
3 - Mash temp greater than user input
4 – Mash time complete
5 – Boil temp range reached
6 – Boil time complete

Then, for each state, I would write what needs to be executed if that state is reached (turn on pump, turn on timer, etc.)
I still see this giving me issues with my timer resetting each time that state (mash temp achieved and boil temp achieved) is reached. Maybe it would work if I implemented a PID control to ensure the temp stays within that desired range, but it still would not be completely robust.

I am still working to implement the changes wildbill suggested.

Thanks!

ajauger:
I am having a little bit of trouble understanding exactly how to use the State Machine function.

As I understand it, my “states” would be:

1 – Mash temp less than user input
2 - Mash temp within user input range
3 - Mash temp greater than user input
4 – Mash time complete
5 – Boil temp range reached
6 – Boil time complete

Then, for each state, I would write what needs to be executed if that state is reached (turn on pump, turn on timer, etc.)
I still see this giving me issues with my timer resetting each time that state (mash temp achieved and boil temp achieved) is reached. Maybe it would work if I implemented a PID control to ensure the temp stays within that desired range, but it still would not be completely robust.

I am still working to implement the changes wildbill suggested.

Thanks!

Did you look at this library ? it gives some good descriptions of how to use a state machine and the factors involved - he also has implemented a couple of functions that essentially run at the head of a state machine which would cover your timer based concerns.

http://playground.arduino.cc/Code/SMlib

Craig

Thanks for posting that link, not sure why I wasn't able to find that one. That is a much better one than the library I found (Arduino Playground - FiniteStateMachine Library). I will read through it and start working some examples.

No problem - let us know how you progress

Craig

All, great series of posts- can you say how it turned out? Also, the library posted had been removed. Any ideas on a substitute? I was successfull in deveoping a Frementation & Kegerator contoller, and want to tackle the brew side