Brewery Programming

The forum for discussing all kinds of brewing paraphernalia.
JabbA

Brewery Programming

Post by JabbA » Tue Apr 03, 2012 4:04 pm

Following my posts about my Arduino controlled HERMS upgrade (equipment post and 1st brewday post) it was suggested that thread be created to share and discuss code from other users. So here it is!

I should mention the idea of building a computer controlled brewery came from Howard's excellent post here. If it wasn't for his very well documented write up of his set-up I would have thought both the software and hardware were way beyond me, cheers Howard :beer:

Here is the code I have cobbled together:

Code: Select all

/*-------------------------------------------------------------

Release 1 03/4/2012

Semi-automated HERMS (Heat Exchange Recirculating Mash System)
brewery and sous vide cooker. 

Developed with Arduino IDE release 0023 and associated libraries

All code published as open source- do with it what you may!

by Jamie Abbott (j.abbott(at)dsl.pipex.com) 2011-12

--------------------------------------------------------------*/

#include <LiquidCrystal.h>
#include <PID_v1.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Bounce.h>
#include <avr/pgmspace.h>

#define ONE_WIRE_BUS 8               //One-Wire bus for 3 DS18b20 tempreature sensors on pin 8
OneWire oneWire(ONE_WIRE_BUS);       //setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire); //pass oneWire reference to Dallas Temperature.

float HLTtemp, Mash_CookTemp, BoilTemp,value;  //define names of temp. sensors

LiquidCrystal lcd (2,3,4,5,6,7); //setup LCD display pins

const boolean PotPin=A0;  //pump speed potentiometer 
                                                                                                                                                                                                     
const boolean button_pin1=A5;    //
const boolean button_pin2=A4;    //buttons on analogue pins
const boolean button_pin3=A3;    //

Bounce button1 = Bounce(button_pin1,5); //
Bounce button2 = Bounce(button_pin2,5); //setup de-bouncing on buttons  
Bounce button3 = Bounce(button_pin3,5); //

const boolean HLTrelay=11;       //
const boolean Mash_CookRelay=12; //solid State Relays controlling heating elements on analouge pins
const boolean BoilerRelay=13;    //

const byte buzzer=9;            //buzzer alarm to alert when a brew process has finished
const byte HERMSpumpFET=10;     //MOSFET to control solar pump that re-circs wort around HERMS kettle control

unsigned short HLTtarget=78;    //default mash liqour target temp. (hot enough to pre-heat mash tun) 
unsigned short MashTarget=66;   //default target mash temp.
unsigned short BoilTarget=100;  //default boil target temp.
unsigned short SpargeTarget=75; //default sparge liqour target temp.
unsigned short MashTime=90;     //default mash duration
unsigned short BoilTime=90;     //default boil duration
unsigned short ChillTarget=25;  //default target cool temp.

unsigned short PotValue;        //to hold value from pump potentiometer
unsigned short PumpDisplay;     //value as % of pump speed

unsigned short CookTarget=57;   //default target cook temp.
unsigned long CookTime;         //to store cooking duration
unsigned long CookHrs=2;        //default cook time hours
unsigned long CookMins=30;      //default cook time minutes

long time;

unsigned long MashStartTime, BrewEndTime, BoilStartTime, BrewStartTime, BrewDuration, CookStartTime;  //brewday finish time
unsigned long lastPIDCalculation = 0;

boolean HLTon = true;
boolean BoilOff = true;
boolean ChillOn = true;
boolean set = true;
boolean CookTargetSet = true;
boolean CookHrsSet = true;
boolean CookMinsSet = true;
boolean state = true;
boolean BoilPause = false;

#define COOK 1   //menu mode for cooking sous vide
#define BREW 2   //menu mode for brewing
byte mode=0;     //initalise menu mode 
byte CookMenu=1; //initalise brew menu 
byte BrewMenu=1; //initalise brew menu

double Setpoint, Input, Output;                      //
PID myPID(&Input, &Output, &Setpoint,5,2,1, DIRECT); //Specifies the PID links and initial tuning parameters
int WindowSize=2500;                                 //
unsigned long windowStartTime;                       //

byte CustomChar[8]={ //
B00010,              //
B01110,              //
B10001,              //Creates letter 'é' 
B11111,              //
B10000,              //
B01110};             //
int eAcute;          //                                           //

//Text strings below stored in flash memory to save sapce in SRAM

const prog_uchar s01[] PROGMEM ="      Welcome!";
const prog_uchar s02[] PROGMEM ="  what do you want";
const prog_uchar s03[] PROGMEM ="       to do?";
const prog_uchar s04[] PROGMEM ="COOK S-V        BREW";

const prog_uchar s05[] PROGMEM =" Welcome to Jamie's";
const prog_uchar s06[] PROGMEM ="      Brewery"     ;
const prog_uchar s07[] PROGMEM ="  Press any button";
const prog_uchar s08[] PROGMEM ="    to go forward  ";

const prog_uchar s09[] PROGMEM =" Welcome to Jamie's ";
const prog_uchar s10[] PROGMEM ="  Sous Vide Cooker! ";
const prog_uchar s11[] PROGMEM ="  Press any button  ";
const prog_uchar s12[] PROGMEM ="    to go forward   ";

const prog_uchar s13[] PROGMEM ="      HLT: ";
const prog_uchar s14[] PROGMEM ="       MT: ";
const prog_uchar s15[] PROGMEM ="   Boiler: ";
const prog_uchar s16[] PROGMEM ="  PRESS ANY BUTTON";

const prog_uchar s17[] PROGMEM ="Ready to start mash!";
const prog_uchar s18[] PROGMEM ="Prime pump&fill HLT ";
const prog_uchar s19[] PROGMEM =" for sparge liquor  ";
const prog_uchar s20[] PROGMEM ="                    ";

const prog_uchar s21[] PROGMEM ="     HLT Setup ";
const prog_uchar s22[] PROGMEM ="Current Temp:";
const prog_uchar s23[] PROGMEM =" UP     SET     DOWN";

const prog_uchar s24[] PROGMEM ="     Mash Setup ";

const prog_uchar s25[] PROGMEM ="     Boil Setup ";
const prog_uchar s26[] PROGMEM ="Current Time:";

const prog_uchar s27[] PROGMEM ="   Setup compleate";
const prog_uchar s28[] PROGMEM ="   Ready to brew!";
const prog_uchar s29[] PROGMEM ="   Is HLT Filled? ";
const prog_uchar s30[] PROGMEM ="BACK           START";

const prog_uchar s31[] PROGMEM =" Mash liquor heating";
const prog_uchar s32[] PROGMEM ="Mash liquor ready!!!";
const prog_uchar s33[] PROGMEM ="   HLT temp:";
const prog_uchar s34[] PROGMEM ="     Target:";
const prog_uchar s35[] PROGMEM ="CHANGE    OFF   MASH";
const prog_uchar s36[] PROGMEM ="CHANGE    ON    MASH";

const prog_uchar s37[] PROGMEM ="CHANGE    ON     END";
const prog_uchar s38[] PROGMEM ="CHANGE    OFF    END";

const prog_uchar s39[] PROGMEM ="  Mash temp:";
const prog_uchar s40[] PROGMEM ="  Time left:";
const prog_uchar s41[] PROGMEM ="BACK   CHANGE   STOP"; 

const prog_uchar s42[] PROGMEM ="    Mash Complete!  ";
const prog_uchar s43[] PROGMEM =" BACK  HLT ON   BOIL";
const prog_uchar s44[] PROGMEM ="              START ";
const prog_uchar s45[] PROGMEM ="BACK          SPARGE";
const prog_uchar s46[] PROGMEM ="     Sparge  in  ";
const prog_uchar s47[] PROGMEM ="      Progress ";
const prog_uchar s48[] PROGMEM ="               START";
const prog_uchar s49[] PROGMEM =" BACK  HLT OFF  BOIL";

const prog_uchar s50[] PROGMEM ="  Boil temp:";
const prog_uchar s51[] PROGMEM ="BACK           CHILL";
const prog_uchar s52[] PROGMEM ="BACK     OFF     END";
const prog_uchar s53[] PROGMEM ="BACK     ON     END";

const prog_uchar s54[] PROGMEM ="   Boil finished!";
const prog_uchar s55[] PROGMEM ="CHANGE   ON    CHILL";
const prog_uchar s56[] PROGMEM ="CHANGE   OFF   CHILL";

const prog_uchar s57[] PROGMEM ="      Chilling      ";
const prog_uchar s58[] PROGMEM =" Chilling Finished! ";
const prog_uchar s59[] PROGMEM ="   Wort temp:";
const prog_uchar s60[] PROGMEM ="   Boil Finished!   ";

const prog_uchar s61[] PROGMEM =" BREWDAY  FINISHED! ";
const prog_uchar s62[] PROGMEM ="   Time:";   
const prog_uchar s63[] PROGMEM ="  Here's to a good  ";
const prog_uchar s64[] PROGMEM ="       Beer!        ";

const prog_uchar s65[] PROGMEM ="   Sparge Liquor ";
const prog_uchar s66[] PROGMEM ="      Temp:";
const prog_uchar s67[] PROGMEM ="BACK";

const prog_uchar s68[] PROGMEM =" UP    HOURS    DOWN";
const prog_uchar s69[] PROGMEM =" UP    MINS     DOWN";
const prog_uchar s70[] PROGMEM =" Cooking  Complete  ";

const prog_uchar s71[] PROGMEM ="  Cook Temp:";
const prog_uchar s72[] PROGMEM ="  Cook Time:";
const prog_uchar s73[] PROGMEM ="    Change  mins";
const prog_uchar s74[] PROGMEM ="BACK   CHANGE    OFF";
const prog_uchar s75[] PROGMEM ="BACK   CHANGE     ON"; 
const prog_uchar s76[] PROGMEM ="Pump";
const prog_uchar s77[] PROGMEM ="    Bon app tit!    ";

void PrintText (const prog_uchar str[]){ //
  char c;                                // 
  if(!str) return;                       //print text stored in ProgMem
  while((c = pgm_read_byte(str++)))      //
  lcd.print(c,BYTE);}                    //

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

void StartScreen(){

/*          Welcome!
       what do you want
            to do?
    COOK S-V        BREW */
    
  lcd.clear();
  PrintText (s01);
  lcd.setCursor (0,1);
  PrintText (s02);
  lcd.setCursor (0,2);
  PrintText (s03);
  lcd.setCursor (0,3);
  PrintText (s04);
  
  while (mode==0){                 //
    if (button1.update()) {        //Initiates Sous Vied cooking mode
      if (button1.read()==HIGH) {  //
          mode=COOK;               //
          }
         }
    if (button3.update()) {
      if (button3.read()==HIGH) {
          mode=BREW;         
          }
         }
        }
       }
       
//-------------------------------------//

void CookWelcome(){

/*    Welcome to Jamie's
      Sous Vide Cooker!
      Press any button  
        to go forward   */

  lcd.clear();
 while (CookMenu==1){
  PrintText (s09);
  lcd.setCursor(0,1);
  PrintText(s10);
  lcd.setCursor(0,2);
  PrintText (s11);
  lcd.setCursor(0,3);
  PrintText (s12);
  
   if (button1.update()||button2.update()||button3.update()) {
     if ((button1.read()||button2.read()||button3.read())==HIGH){
        CookMenu++;
        CookStartTime=millis();
        }
       }
      }
     }
     
//-------------------------------------//

void Cooking(){
  
/*    Cook Temp: 56.2°C
         Target: 56.0°C
      Time Left: 1h 38m
    BACK   CHANGE    OFF  */

  lcd.clear();
  float h,m;
  unsigned long over;
  CookTime=(CookHrs*3600000)+(CookMins*60000);
  CookHrsSet=false;
  CookMinsSet=false;
while (CookMenu==2){  
  if (sensors.getDeviceCount()==0){      //
     lcd.setCursor(0,0);                 //
     PrintText (s20);                    //
     lcd.setCursor(0,1);                 // Display warning
     lcd.print(" *temprature probe* ");  // if temp probe
     lcd.setCursor(0,2);                 // not attached  
     lcd.print("   *not attached!*  ");  // 
     lcd.setCursor(0,3);                 //
     PrintText (s20);                    //
   }else{
   time = CookTime - (millis()-CookStartTime)/60000;
   h=int(time/3600000);
   over=time%3600000;
   m=int(over/60000);
   sensors.requestTemperatures();
   Mash_CookTemp=sensors.getTempCByIndex(2);
   Input = Mash_CookTemp;
   Setpoint = CookTarget;
   myPID.Compute();
   if(millis() - windowStartTime>WindowSize){
   windowStartTime += WindowSize;}
   if(Output > millis() - windowStartTime) digitalWrite(Mash_CookRelay,HIGH);
   else digitalWrite(Mash_CookRelay,LOW);
   PrintText(s71);
   lcd.print(Mash_CookTemp);
   lcd.setCursor(16,0);
   degC();
   lcd.setCursor(0,1);
   PrintText(s34);
   lcd.print(CookTarget);
   lcd.setCursor(0,2);
   PrintText(s40);
   lcd.print(h,0);
   lcd.print("h ");
   lcd.print(m,0);
   lcd.print("m ");
   lcd.setCursor(0,3);
   PrintText(s74);
      
   PumpControl();    //HERMS pump used to recirculate water to maintain uniform water temp.
   CookMenuButons(); //back - forward buttons
   
    if (button2.update()){        //
      if (button2.read()==HIGH){  //Change target cooking temp.
         CookTargetSet=false;     //
      }
    }  

    /* Cook Temp: 56.2°C
          Target: 58.0°C
       Time Left: 2h 38m
      UP    HOURS    DOWN  */ 
    
   while (!CookTargetSet){
       lcd.setCursor(0,3);
       PrintText(s68);
    if (button1.update()) {
      if (button1.read()) {
         CookTarget++;
         }
        }  
   if (button3.update()) {
     if (button3.read()) {
         CookTarget--;
         }
        }
   lcd.setCursor(12,1);
   lcd.print(CookTarget);
   degCTarget();
  if (CookTarget > 100){
      CookTarget=0;}
  if (CookTarget < 100){
      lcd.setCursor(14,1);
      degCTarget();}
  if (CookTarget < 10){
      lcd.setCursor(13,1);
      degCTarget();}
  if (button2.update()) {
   if (button2.read()){
     lcd.setCursor(0,3);
     PrintText(s68);
     CookTargetSet=true;
     CookMenu++;
     }
    }
   } 
   if (time<60000)CookMenu=5;
  }
 }
}
//-------------------------------------//

void CookTimeSetHours(){

/*    Cook Temp: 56.2°C
         Target: 56.0°C
      Time Left: 3h 8m
    UP     MINS     DOWN  */ 

  while (CookMenu==3){
       lcd.setCursor(12,2);
       lcd.print(CookHrs);
       if (CookHrs > 72){
    CookHrs=0;}
     if (CookHrs < 10){
      lcd.setCursor(11,2);}
     if (CookHrs < 0){
      CookHrs = 72;}
      lcd.setCursor(0,3);
      PrintText(s69);
    if (button1.update()) {
     if (button1.read()) {
        CookHrs++;
        }  
       }
     if (button3.update()) {
      if (button3.read()) {
        CookHrs--;
        }
       }
 if (button2.update()) {
   if (button2.read()){
     lcd.setCursor(0,3);
     PrintText(s69);
     CookMenu++;
   }
  }
 }   
}

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

void CookTimeSetMins(){

/*    Cook Temp: 56.2°C
         Target: 56.0°C
      Time Left: 3h 15m
    UP      SET     DOWN  */ 

  while (CookMenu==4){
     lcd.setCursor(15,2);
     lcd.print(CookMins);
     if (CookMins > 59){
        CookMins=0;}
     if (CookMins < 1){
        CookMins=59;}
     if (CookHrs < 10){
      lcd.setCursor(14,2);
      degCTarget();}
       lcd.setCursor(0,3);
       PrintText(s23);
    if (button1.update()) {
     if (button1.read()) {
        CookMins++;
        }  
       }
   if (button3.update()) {
     if (button3.read()) {
       CookMins--;
       }
      }
   if (button2.update()) {
     if (button2.read()){
        CookMenu=2;
   }
  }
 }
}

//-------------------------------------//
 
void CookFinished(){
 
/*    Cooking  Complete

         Bon appétit!    */

  lcd.clear();
  digitalWrite(Mash_CookRelay,LOW);
 while (CookMenu==5){
  PrintText(s70);
  lcd.setCursor(0,1);
  lcd.print("                    ");
  lcd.setCursor(0,3);
  PrintText(s77);
  lcd.setCursor(0,3);
  lcd.setCursor(10,3);
  lcd.write(eAcute);
 }
}

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

void BrewWelcome(){

/*  Welcome to Jamie's
        Brewery
    Press any button
     to go forward    */

  lcd.clear();
  PrintText(s05);
  lcd.setCursor(0,1);
  PrintText(s06);
  lcd.setCursor(0,2);
  PrintText(s07);
  lcd.setCursor(0,3);
  PrintText(s08);
  
 while (BrewMenu==1){
   if (button1.update()||button2.update()||button3.update()) {
      if ((button1.read()||button2.read()||button3.read())==HIGH){
        BrewMenu++;
    }
   }
  }
 }

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

void BrewStart(){

/*      HLT: 22.5°C
         MT: 21.8°C
     Boiler: 22.1°C
    PRESS ANY BUTTON   */

  BrewStartTime = millis(); 
  lcd.clear();
  lcd.setCursor(0,0);
  PrintText(s13);  
  lcd.setCursor(0,1);
  PrintText(s14);  
  lcd.setCursor(0,2);
  PrintText(s15); 
  lcd.setCursor(0,3);
  PrintText(s16);
  
 while (BrewMenu==2){
   if (sensors.getDeviceCount()<3){      //
     lcd.setCursor(0,0);                 //
     PrintText (s20);                    //
     lcd.setCursor(0,1);                 // Display warning if
     lcd.print("*temprature  probes*");  // all 3 temp probes 
     lcd.setCursor(0,2);                 // are not attached  
     lcd.print("  * not attached!*  ");  // 
     lcd.setCursor(0,3);                 //
     PrintText (s20);                    //
   }else{
    sensors.requestTemperatures();
    HLTtemp=sensors.getTempCByIndex(1);
    Mash_CookTemp=sensors.getTempCByIndex(2);
    BoilTemp=sensors.getTempCByIndex(0);
    lcd.setCursor(11,0);
    lcd.print (HLTtemp);
   if (HLTtemp<10){
      lcd.setCursor(14,0);
      degC();
   }else{
    lcd.setCursor(15,0);
    degC();}    
    lcd.setCursor(11,1);
    lcd.print(Mash_CookTemp);
   if (Mash_CookTemp<10){
      lcd.setCursor(14,1);
      degC();
   }else{
    lcd.setCursor(15,1);
    degC();}
    lcd.setCursor(11,2);
    lcd.print(BoilTemp);
   if (BoilTemp<10){
      lcd.setCursor(14,2);
      degC();
   }else{
    lcd.setCursor(15,2);
    degC();}
   if (button1.update()||button2.update()||button3.update()) {
      if ((button1.read()||button2.read()||button3.read())==HIGH){
        BrewMenu++;
    }
   }
  }
 }
}

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

void MashLiquorHeating(){ 

  /* Mash liquor heating
     HLT temp:62.7°C
       Target:78.0°C
   CHANGE    OFF   MASH  */
   
  digitalWrite(Mash_CookRelay,LOW);
  digitalWrite(BoilerRelay,LOW);
  analogWrite (HERMSpumpFET,0);
  lcd.clear();
 while (BrewMenu == 3){
   sensors.requestTemperatures();
   HLTtemp=sensors.getTempCByIndex(1);
   lcd.setCursor(0,1);
   PrintText(s33);
   lcd.print(HLTtemp);
   if (HLTtemp<10){
      lcd.setCursor(15,1);
      degC();
   }else{
    lcd.setCursor(16,1);
    degC();}
   lcd.setCursor(0,2);
   PrintText(s34);
   lcd.print(HLTtarget);
   degCTarget();
     
   if (button3.update()) {
    if (button3.read() == HIGH) {
     BrewMenu++; 
    }
   }
  if (button1.update()) {
    if (button1.read() == HIGH) {
     set=false;
     }
    }
     while (!set){
       lcd.setCursor(0,3);
       PrintText(s23);
         if (button1.update()) {
    if (button1.read()) {
      HLTtarget++;
      }
     }  
   if (button3.update()) {
    if (button3.read()) {
      HLTtarget--;
      }
     }
   lcd.setCursor(12,2);
   lcd.print(HLTtarget);
  if (HLTtarget<10){
    lcd.setCursor(14,2);
    degCTarget();
   }else{
    lcd.setCursor(15,2);
    degCTarget();}
  if (HLTtarget > 100){
      HLTtarget=0;}
  if (HLTtarget < 100){
      lcd.setCursor(14,2);
      degCTarget();}
  if (HLTtarget < 10){
      lcd.setCursor(13,2);
      degCTarget();}
    
  if (button2.update()) {
   if (button2.read()){
      set=true;
      }
     }  
    }
   if (button2.update()) {
    if (button2.read() == HIGH) {
     if (!HLTon) HLTon=true;
     else HLTon=false;
     }
    }
     HTLcontrol();
    }
   }

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

void HTLcontrol(){

/*  Mash liquor ready!!!
       HLT temp:78.1°C
         Target:78.0°C
     CHANGE    ON   MASH  */

   lcd.setCursor(0,3);
   analogWrite (HERMSpumpFET,0);
  if (!HLTon){
   PrintText(s36);
   digitalWrite(HLTrelay,LOW);
  }else{ 
   PrintText(s35);
  if (HLTtemp > HLTtarget){
    digitalWrite(buzzer,HIGH);
    digitalWrite(HLTrelay,LOW);
    lcd.setCursor(0,0);
    PrintText(s32);
  }else{ 
    digitalWrite(HLTrelay,HIGH);
    digitalWrite(buzzer,LOW);
    lcd.setCursor(0,0);
    PrintText(s31);
    }
   }
  }
  
//-------------------------------------//

void PumpControl(){    // Controls pump speed via MOSFET by reading potentiometer value
  PotValue = analogRead(PotPin);
  PotValue = map(PotValue, 0, 1023, 0, 255);
  analogWrite (HERMSpumpFET,PotValue); 
}

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

void MashStart(){

/*  Ready to start mash!
    Prime pump&fill HLT
     for sparge liquor
      PRESS ANY BUTTON    */
      
  lcd.clear();
   digitalWrite(buzzer,LOW);
   digitalWrite(HLTrelay,LOW);
  while (BrewMenu==4){
    PrintText(s17);
    lcd.setCursor(0,1);
    PrintText(s18);
    lcd.setCursor(0,2);
    PrintText(s19);
    lcd.setCursor(0,3);
    PrintText(s16);
    
    if (button1.update()||button2.update()||button3.update()) {
      if ((button1.read()||button2.read()||button3.read())==HIGH){
        BrewMenu++;
      }
    }
  }
}

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

void Mashing(){

/*    Mash temp:65.8°C
         Target:66.0°C
      Time left:38mins
    BACK   CHANGE   STOP  */
    
   lcd.clear();
   MashStartTime=millis();
  while (BrewMenu==5){ 
   sensors.requestTemperatures();
   Mash_CookTemp=sensors.getTempCByIndex(2);
   HLTtemp=sensors.getTempCByIndex(1);
   Input = Mash_CookTemp;
   Setpoint = MashTarget;
   myPID.Compute();
   if(millis() - windowStartTime>WindowSize){
   windowStartTime += WindowSize;}
   if(Output > millis() - windowStartTime) digitalWrite(Mash_CookRelay,HIGH);
  else digitalWrite(Mash_CookRelay,LOW);
   time = MashTime - (millis()-MashStartTime)/60000;
   PrintText(s39);
   lcd.setCursor(12,0);
   lcd.print(Mash_CookTemp);
   lcd.setCursor(16,0);
   degC();
   lcd.setCursor(0,1);
   PrintText(s34);
   lcd.print(MashTarget);
   degCTarget();
   lcd.setCursor(0,2);
   PrintText(s40);
   lcd.print (time);
   lcd.print("mins ");
   lcd.setCursor(0,3);
   PrintText(s41);
   
   SpargeLiquorHeating();  //heat sarge liquor whilst mashing
   PumpControl();
   
 if (time<1)BrewMenu++, analogWrite (HERMSpumpFET,0);
 
   if (button1.update()) {
    if (button1.read()) {
      digitalWrite (Mash_CookRelay, LOW);
      analogWrite (HERMSpumpFET,0);
      BrewMenu--;
      }
     }
 if (button3.update()) {
   if (button3.read()) {
     digitalWrite (Mash_CookRelay, LOW);
     analogWrite (HERMSpumpFET,0);
     BrewMenu++;
     }
    } 
 if (button2.update()) {
   if (button2.read() == HIGH) {
     set=false;
 while (!set){
     lcd.setCursor(0,3);
     PrintText(s23);
  if (button1.update()) {
    if (button1.read()) {
      MashTarget++;
     }
    }  
  if (button3.update()) {
   if (button3.read()) {
     MashTarget--;
    }
   }
   lcd.setCursor(12,1);
   lcd.print(MashTarget);
   degCTarget();
  if (MashTarget > 100){
    MashTarget=0;}
  if (MashTarget < 100){
    lcd.setCursor(14,1);
    degCTarget();
   }
  if (MashTarget < 10){
    lcd.setCursor(13,1);
    degCTarget();
   }
  if (button2.update()) {
    if (button2.read()){
    lcd.setCursor(0,0);
    set=true;
      }
     }
    }
   }
  }
 }
}

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

void SpargeLiquorHeating(){ 
  digitalWrite(BoilerRelay,LOW);
  sensors.requestTemperatures();
  HLTtemp=sensors.getTempCByIndex(1);
if (HLTtemp < SpargeTarget){
  digitalWrite (HLTrelay, HIGH);}
if (HLTtemp > SpargeTarget+5){
  digitalWrite (HLTrelay, LOW);}
}

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

void MashFinished(){

/*  Mash Complete!

            START
  BACK      SPARGE */

  digitalWrite(buzzer,HIGH);
  digitalWrite(Mash_CookRelay,LOW);
  digitalWrite(HLTrelay,LOW);
  lcd.clear();
 while (BrewMenu==6){
   PrintText(s42);
   lcd.setCursor(0,2);
   PrintText(s44);
   lcd.setCursor(0,3);
   PrintText(s45);
   BrewMenuButons();
 }
}

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

void Sparging(){ 

/*   Sparge  in
      Progress
               START
 BACK  HLT OFF  BOIL  */

 digitalWrite(buzzer,LOW);
 lcd.clear(); 
 while (BrewMenu==7){
   SpargeLiquorHeating();
   PrintText(s46);
   lcd.setCursor(0,1);
   PrintText(s47);
   lcd.setCursor(0,2);
   PrintText(s48);
   
   BrewMenuButons(); 
   SpargeLiquorHeating();
  
   if (button2.update()) {
    if (button2.read() == HIGH);{
      digitalWrite(HLTrelay, HIGH);
      lcd.setCursor(0,3);  
      PrintText(s49);}
    }else{
      digitalWrite(HLTrelay, LOW);
      lcd.setCursor(0,3);  
      PrintText(s43);}
 }
}

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

void Boiling(){
  
/*   Boil temp:88.0°C
        Target:100.0°C
     Time left:89mins
   BACK   CHANGE   STOP    */

   digitalWrite(HLTrelay,LOW);  
   digitalWrite(Mash_CookRelay,LOW);
   digitalWrite(buzzer,LOW);
   lcd.clear();
   
 while (BrewMenu==8){
   digitalWrite(HLTrelay,LOW);  
   digitalWrite(Mash_CookRelay,LOW);
   sensors.requestTemperatures();
   BoilTemp=sensors.getTempCByIndex(0);
  if (BoilTemp > 95){
   BoilStartTime=millis();}   
   time = BoilTime - (millis()-BoilStartTime)/60000;
   PrintText(s50);
   lcd.setCursor(12,0);
   lcd.print(BoilTemp);
  if (BoilTemp >= 100){lcd.setCursor(17,0);
   degC();
  }else{ 
   lcd.setCursor(16,0);
   degC();}
   lcd.setCursor(0,1);
   PrintText(s34);
   lcd.setCursor(12,1);
   lcd.print(BoilTarget);
   degCTarget();
   lcd.setCursor(0,2);
   PrintText(s40);
   lcd.setCursor(12,2);
   lcd.print(time);
   lcd.print("mins ");
   lcd.setCursor(0,3);
   PrintText(s41);
   if (time<1)BrewMenu++;
   if (BoilTarget>BoilTemp){
    digitalWrite(BoilerRelay, HIGH);
   }else{
    digitalWrite(BoilerRelay,LOW);}
  if (button3.update()) {
   if (button3.read()) {
    BrewMenu++;
  }
 }
   if (button1.update()) {
    if (button1.read() == HIGH) {
     digitalWrite (BoilerRelay,LOW);
     BrewMenu--; 
    }
   }   
   if (button2.update()) {
    if (button2.read() == HIGH) {
     set=false;
    while (!set){
       lcd.setCursor(0,3);
       PrintText(s23);
   if (button1.update()) {
    if (button1.read()) {
      BoilTarget++;
   }
  }  
   if (button3.update()) {
    if (button3.read()) {
      BoilTarget--;
    }
   }
   lcd.setCursor(12,1);
   lcd.print(BoilTarget);
   degCTarget();
  if (BoilTarget > 100){
    BoilTarget=0;}
  if (BoilTarget < 100){
    lcd.setCursor(14,1);
    degCTarget();}
  if (BoilTarget < 10){
      lcd.setCursor(13,1);
      degCTarget();}
  if (button2.update()){
    if (button2.read()){
      lcd.setCursor(0,0);
      set=true;
     }
    }
   }
  }
 }
 }
}

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

void BoilFinished(){

/*   Boil Finished!  


  BACK           CHILL  */

  lcd.clear();
 while (BrewMenu==9){
   digitalWrite(buzzer,HIGH);
   digitalWrite(HLTrelay,LOW);  
   digitalWrite(Mash_CookRelay,LOW);
   digitalWrite(BoilerRelay,LOW);
   PrintText(s60);
   lcd.setCursor(0,3);
   PrintText(s51);
   BrewMenuButons();
  }
}

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

void Chilling(){
  
  /*     Chilling    
      Wort temp:28.2°C
         Target:25.0°C
    BACK   CHANGE   STOP    */
   
  digitalWrite(buzzer,LOW);
    lcd.clear();
  while (BrewMenu==10){
    digitalWrite(HLTrelay,LOW);  
    digitalWrite(Mash_CookRelay,LOW);
    digitalWrite(BoilerRelay,LOW);
    sensors.requestTemperatures();
    BoilTemp=sensors.getTempCByIndex(0);
    PrintText(s57);
    lcd.setCursor(0,1);
    PrintText(s59);
    lcd.print(BoilTemp);
    lcd.setCursor(17,1);
    degC();
    lcd.setCursor(0,2);
    PrintText(s34);
    lcd.print(ChillTarget);
    degCTarget();
    lcd.setCursor(0,3);
    PrintText(s41);

  if (button3.update()) {
    if (button3.read() == HIGH) {
     BrewMenu++; 
     }
   }
  if (button1.update()) {
    if (button1.read() == HIGH) {
     BrewMenu--; 
    }
  }   
  if (button2.update()) {
    if (button2.read() == HIGH) {
     set=false;
   while (!set){
       lcd.setCursor(0,3);
       PrintText(s23);
   if (button1.update()) {
    if (button1.read()) {
      ChillTarget++;
    }
  }  
   if (button3.update()) {
    if (button3.read()) {
      ChillTarget++;
     }
   }
   lcd.setCursor(12,2);
   lcd.print(ChillTarget);
   degCTarget();
  if (ChillTarget > 100){
    ChillTarget=0;}
    if (ChillTarget < 100){
      lcd.setCursor(14,2);
      degCTarget();
    }
    if (ChillTarget < 10){
      lcd.setCursor(13,2);
      degCTarget();
    }
if (button2.update()) {
      if (button2.read()){
      set=true;
    }
  }  
}
   if (button2.update()) {
    if (button2.read() == HIGH) {
     if (!ChillOn) ChillOn=true;
     else ChillOn=false;
     }
    }
   }     
  }
 }
}

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

void ChillingControl(){
   lcd.setCursor(0,3);
  if (!ChillOn){
   PrintText(s36);
   digitalWrite(HLTrelay,LOW);
  }else{ 
   PrintText(s35);
  if (BoilTemp > ChillTarget){
    digitalWrite(buzzer,HIGH);
    lcd.setCursor(0,0);
    PrintText(s32);
  }else{
    digitalWrite(buzzer,LOW);
    lcd.setCursor(0,0);
    PrintText(s31);
    }
   }
  }
  
//-------------------------------------//

void BrewdayFinished(){
  
  /*  BREWDAY  FINISHED!
       Time:6hrs 35mins
       Here's to a good
           Beer!     */
 
  float h,m;
  unsigned long over;
  BrewEndTime=millis();
  BrewDuration=BrewEndTime-BrewStartTime;
  h=int(BrewDuration/3600000);
  over=BrewDuration%3600000;
  m=int(over/60000);
  lcd.clear();
  while (BrewMenu==11){
    PrintText(s61);
    lcd.setCursor(0,1);
    PrintText(s62);
    lcd.print(h,0);
    lcd.print("hrs ");
    lcd.print(m,0);
    lcd.print("mins"); 
    lcd.setCursor(0,2);
    PrintText(s63);
    lcd.setCursor(0,3);
    PrintText(s64);
      }
    }

void CookMenuButons(){             //Back and forward cook menu buttons
  if (button1.update()){           
    if (button1.read() == HIGH){   
      if (CookMenu>1) CookMenu--;  
      }                            
    }                               
    if (button3.update()){         
      if (button3.read()==HIGH){   
         CookMenu++;                
    }                              
  }
}

void BrewMenuButons(){            //Back and forward brew menu buttons
  if (button1.update()){      
    if (button1.read()==HIGH){
      if (BrewMenu>1) BrewMenu--;   
      }
    }
    if (button3.update()){
      if (button3.read()==HIGH){
         BrewMenu++;
    }
  }
}

void degC(){            //prints '°C' and masks the two  
  lcd.print((char)223); //least signigicant figures from
  lcd.print("C  ");     //temp sensor reading
}
void degCTarget(){      //
  lcd.print(".0");      //prints '0°C'
  lcd.print((char)223); //
  lcd.print("C ");      //
}
   
void setup(){
  lcd.begin(20,4);
  
  pinMode (Mash_CookRelay,OUTPUT);
  pinMode(HLTrelay,OUTPUT);
  pinMode (BoilerRelay,OUTPUT); 
  
  windowStartTime = millis();           //
  myPID.SetOutputLimits(0, WindowSize); //PID set-up
  myPID.SetMode(AUTOMATIC);             //
  
  lcd.createChar(eAcute,CustomChar);//initialse custom letter 'é'
}

void loop(){    
    
 switch (mode) {
    case 0:
     StartScreen();
    break;
    case 1:
     switch (CookMenu){
       case 1:
        CookWelcome ();
       break;
       case 2:
        Cooking ();
       break;
       case 3:
       CookTimeSetHours();
       break;
       case 4:
       CookTimeSetMins();
       break;
       case 5:
       CookFinished();
       break;
   }
    break;
    case 2:
      switch (BrewMenu){
        case 1:
         BrewWelcome ();
        break;
        case 2:
         BrewStart();
        case 3:
         MashLiquorHeating();
        break;
        case 4:
         MashStart();
        break;
        case 5:
         Mashing();
        break;
        case 6:
         MashFinished();
        break;
        case 7:
         Sparging();
        break;
         case 8:
         Boiling();
        break;
        case 9:
         BoilFinished();
        break;
        case 10:
         Chilling();
        break;
        case 11:
         BrewdayFinished();
        break;
      }
      break;
   }
}
Not being a programmer, in any way, shape, or form (I did a little Turbo Pascal at school many years ago!) it's a bit rough and ready and is mainly chucks copied from other examples on the net. But it does work, well the brewing side of things works- I've not had chance to properly test the sous vide mode as yet. If anyone has any criticism or ideas of what else I could include I'd be most grateful.

The major bits of hardware are the 20x4 line HD44780 display and Maxim DS18B20 temp sensors. Both have code libraries freely available to download and use from the Arduino website. I also used a potentiometer and a MOSFET to control the speed of the solar pump and SSRs to switch the heating elements in the HLT, HERMS unit and boiler both are easy to interface with with the software.

Cheers,
Jamie

Fil
Telling imaginary friend stories
Posts: 5229
Joined: Sun Oct 16, 2011 1:49 pm
Location: Cowley, Oxford

Re: Brewery Programming

Post by Fil » Tue Apr 03, 2012 6:03 pm

inspiring, a great resource. Im very impressed almost makes me want to follow in your steps,
ive only had a quick scan of the code in notepad, i was looking for the ssr output control,
im interested as ive just started playing with a pid controller, which i find much more baffling than your code..

I see your sending either HIGH or LOW to various relays, is this on/off? 100% output to the heating element and 0 output,
is the ssr acting like a normal relay on,,,, and off,,, or is proportionate heating effected by the program cycle frequency? everything seems to drop out neatly
ist update for months n months..
Fermnting: not a lot..
Conditioning: nowt
Maturing: Challenger smash, and a kit lager
Drinking: dry one minikeg left in the store
Coming Soon Lots planned for the near future nowt for the immediate :(

User avatar
alexe
Steady Drinker
Posts: 93
Joined: Sun Oct 26, 2008 8:27 pm
Location: Zug, Switzerland

Re: Brewery Programming

Post by alexe » Tue Apr 03, 2012 6:27 pm

Here is my code.

There are 5 control modes:
1. Display - purely displays the temperatures of all the probes.
2. HLT prep - gets the HLT up to temperature. Eventually want it to get the HLT up to boiling to clean up any chlorine, etc
3. HERMS - gets the heat exchanger up to temp.
4. Mash - controls the mash temperature by adjusting the HE temp.
5. Sparge - Same as HLT at the moment

The controller manages to SSRs, and since it is connected to a standard plug there is a mechanism to ensure only one SSR is on at any one time.

It sends data from the temperature probes, setpoints and SSR status to the USB port for the PC app. at regular intervals.

Code: Select all

//**************************************************************************************
//
//  BrewMaster
//
//  Home brewery controller
//  Slave control software for Arduino
//
//  Version : 3.0
//  Last updated : 29 Aug 2011
//
//*************************************************************************************

// #include ***************************************************************************
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//  Constants *************************************************************************

// General
#define  OFF           0
#define  ON            1

// Control modes
#define  DISPLAY_ONLY  1
#define  HLT_PREP      2
#define  HERMS_PREP    3
#define  MASH          4
#define  SPARGE        5
#define  BOIL          6

// LCD Pin Numbers
#define LCD_RS_PIN            12
#define LCD_ENABLE_PIN        11
#define LCD_D4_PIN            13
#define LCD_D5_PIN            10
#define LCD_D6_PIN             9
#define LCD_D7_PIN             8

// SSR Pin Numbers
#define SSR_HLT_PIN            4
#define SSR_HERMS_PIN          2

// Temperature probe bus pin numbers
#define ONE_WIRE_BUS_PIN       7

// Global variables *******************************************************************
int     control_mode;          // current mode of controlling brewery
int     usb_baud;              // Baud rate for USB communications
int     send_data;             // Flag to indicate status of data trasmission
int     hlt_probe = 1;         // Address of HLT temp. probe
int     herms_probe = 2;       // Address of HERMS temp. probe
int     mash_probe = 0;        // Address of mash-tun temp. probe
//int     boiler_probe = 3;    // Address of boiler temp. probe
float   hlt_setpoint;          // Temperature to hold HLT at
float   herms_setpoint;        // Temperature to hold HERMS at
float   hlt_temp;              // HLT temperature
float   last_hlt_temp;         // Previous HLT temperature reading
float   herms_temp;            // Temperature of HERMS unit
float   last_herms_temp;       // Previous temperature reading of HERMS unit
float   mash_temp;             // Mash-tun temperature
float   mash_setpoint;         // Temperature to hold mash at
float   boiler_temp;           // Boiler temperrature
float   herms_delta;           // Delta to calulate HERMS SP
long    last_time;             // Last control cycle
int     hlt_on_ctr, hlt_off_ctr;     // Counters to smooth on-off cycles
int     herms_on_ctr, herms_off_ctr;     // Counters to smooth on-off cycles
int     hlt_element;           // HLT element control state
int     herms_element;         // HERMS element control state
DeviceAddress temp_probe[4];   // Data structure for one-wire temperature probes

int    char_count = -1;
char   command;
char   buffer[10];

// Global class initialisation
LiquidCrystal lcd(LCD_RS_PIN, LCD_ENABLE_PIN, LCD_D4_PIN, LCD_D5_PIN, LCD_D6_PIN, LCD_D7_PIN);

OneWire oneWire(ONE_WIRE_BUS_PIN);
DallasTemperature sensors(&oneWire);

//*************************************************************************************
// setup - initial setup routine called at startup
//*************************************************************************************

void setup() {

  int  i, r;
  
  // load settings from EEPROM
  
  // Initialise global variables
  control_mode = DISPLAY_ONLY;
  usb_baud = 9600;
  send_data = OFF;
  hlt_on_ctr = 0;
  hlt_off_ctr = 50;
  herms_on_ctr = 0;
  herms_off_ctr = 50;
  hlt_setpoint = 76.0;
  herms_setpoint = 70.0;
  mash_setpoint = 69.0;
  herms_delta = 1.0;

  // Initialise lcd
  lcd.begin(20, 4);
  
  lcd_print("                   ", 0, 0);
  lcd_print("  BrewMaster v3.0  ", 0, 1);
  lcd_print("  Initialising...  ", 0, 2);
  lcd_print("                   ", 0, 3);
  
  delay(1000);
  lcd_clear();
  
  // Initialise communications
  Serial.begin(usb_baud);
  
  // Initialise temperature probes
  sensors.begin();

  r = 1;
  for (i = 0; i < 3; ++i) {
    if (!sensors.getAddress(temp_probe[i], i)) {
        // error
        lcd_print("Temp Error - ", 0, r); 
        lcd_print_int(r + 1, 14, r);
        r = r + 1;
        
//        send_serial_message((char *)"ET", i, (char *)"Error initialising temperature probe");
    }
  }
  
  // Initialse digital pins
  pinMode(SSR_HLT_PIN, OUTPUT);
  pinMode(SSR_HERMS_PIN, OUTPUT);
  
  send_ready_signal();
}

//*************************************************************************************
// loop - main application routine
//*************************************************************************************

void loop() {
  
  long current_time;
  
  // check for serial messages
  read_serial();
  
  // control loop, called every 0.1 seconds
  current_time = (long)millis();
  if(current_time - last_time > 100) {
    
    last_time = current_time;
    
    // control routine
    control_loop();
 
    // update lcd display
    update_display();
  
    // send data back to PC 
    if(send_data)
      send_temp_data();
  }
}
 
//*************************************************************************************
// control_loop - Main control loop, called every second
//*************************************************************************************

void control_loop() {
  
  // read temperature probes
  sensors.requestTemperatures();
  hlt_temp = sensors.getTempC(temp_probe[hlt_probe]);
  herms_temp = sensors.getTempC(temp_probe[herms_probe]);
  mash_temp = sensors.getTempC(temp_probe[mash_probe]);
//  boiler_temp = sensors.getTempC(temp_probe[boiler_probe]);
  
  // main control loop
  switch (control_mode) {
    case HLT_PREP:
      control_hlt();
      break;
    case HERMS_PREP:
      control_herms();
      break;
    case MASH:
      control_hlt();
      control_herms();
      break;
    case SPARGE:
      break;
    case BOIL:
      break;
    case DISPLAY:
      hlt_element = OFF;
      herms_element = OFF;
      break;
  }
  
  // switch the elements
  control_signal();
}

//*************************************************************************************
// control_hlt - determines the hlt control signal
//*************************************************************************************

void control_hlt() {

  float  diff;
  float  error;
  float  control;
  
  diff = hlt_temp - last_hlt_temp;
  error = hlt_setpoint - hlt_temp;
  
  control = error - 10.0 * diff;
  ++hlt_off_ctr;
  ++hlt_on_ctr;
  
  if(control > 0.1 && error > 0.1 && hlt_off_ctr > 10 && herms_element == OFF) {
      hlt_element = ON;
      hlt_on_ctr = 0;
  }
  else if(control < 0.1 && hlt_on_ctr > 10) {
    hlt_element = OFF;
    hlt_off_ctr = 0;
  }
  
  last_hlt_temp = hlt_temp;
}

//*************************************************************************************
// control_herms - determines the HERMS control signal
//*************************************************************************************

void control_herms() {

  float  diff;
  float  error;
  float  control;
  
  if(mash_temp > mash_setpoint)
    herms_delta = 0.5;
  else if(mash_temp > mash_setpoint - 1.0)
    herms_delta = 1.0;
  else 
    herms_delta = 2.0;
    
  herms_setpoint = mash_setpoint + herms_delta;
  
  diff = herms_temp - last_herms_temp;
  error = herms_setpoint - herms_temp;
  
  control = error - 10.0 * diff;
  ++herms_off_ctr;
  ++herms_on_ctr;
  
  if(control > 0.1 && error > 0.1 && herms_off_ctr > 10) {
    herms_element = ON;
    hlt_element = OFF;
    herms_on_ctr = 0;
  }
  else if(control < 0.1 && herms_on_ctr > 10) {
    herms_element = OFF;
    herms_off_ctr = 0;
  }
  
  last_herms_temp = herms_temp;
}

//*************************************************************************************
// control_signal - outputs the control signal
//*************************************************************************************

void control_signal() {
  
  if(herms_element == ON) {
    digitalWrite(SSR_HLT_PIN, LOW);
    digitalWrite(SSR_HERMS_PIN, HIGH);
  }
  else if(hlt_element == ON) {
    digitalWrite(SSR_HERMS_PIN, LOW);
    digitalWrite(SSR_HLT_PIN, HIGH);
  }
  else {
    digitalWrite(SSR_HERMS_PIN, LOW);
    digitalWrite(SSR_HLT_PIN, LOW);
  }
}

//*************************************************************************************
// update_display - writes output to the LCD display
//*************************************************************************************

void update_display()
{
  switch (control_mode) {
    case HLT_PREP:
      lcd_print("Mode: HLT Prep      ", 0, 0);
      break;
    case HERMS_PREP:
       lcd_print("Mode: HERMS Prep     ", 0, 0);
      break;
    case MASH:
      lcd_print("Mode: Mash          ", 0, 0);
      break;
    case SPARGE:
      lcd_print("Mode: Sparge        ", 0, 0);
      break;
    case BOIL:
     lcd_print("Mode: Boil          ", 0, 0);
      break;
    default:
      lcd_print("Mode: Display       ", 0, 0);
      break;
  }
  
  
  lcd_print("HLT T=     SP=      ", 0, 1);
  lcd_print("HE  T=     SP=      ", 0, 2);
  lcd_print("MT  T=     SP=      ", 0, 3);
  
  lcd_print_float(hlt_temp, 6, 1);
  lcd_print_float(hlt_setpoint, 14, 1);
  lcd_print_float(herms_temp, 6, 2);
  lcd_print_float(herms_setpoint, 14, 2);
  lcd_print_float(mash_temp, 6, 3);
  lcd_print_float(mash_setpoint, 14, 3);

  if(hlt_element == ON)
    lcd_print("*", 19, 1);
  if(herms_element == ON)
    lcd_print("*", 19, 2);
}

//*************************************************************************************
// read_serial - Checks serial port for any incoming communications and processes 
// accordingly
//*************************************************************************************

void read_serial() {
  
  int    incoming_byte = 0;
  
  while(Serial.available()) {

    incoming_byte = Serial.read(); 

    // Read characters until 'end of command'
    if(char_count == -1) {
      command = (char)incoming_byte;     
    }
    else if(incoming_byte == 'Z') {
      buffer[char_count] = 0;
      
      // Process command
      process_serial_command(command, buffer);  
      
      char_count = -2;
      buffer[0] = 0;
    }
    else {
      buffer[char_count] = (char)incoming_byte;  
    } 
    ++char_count; 
  } 
}

//*************************************************************************************
// process_serial_command - Processes command received by 'read_serial' function
//*************************************************************************************

int process_serial_command(char command, char *value) {
  
  float   float_value;
  int     int_value;
  
  switch (command) {
    case 'B':    // Start sending data to PC
      send_data = ON;
      break;
      
    case 'Q':    // Stop sending data to PC
      send_data = OFF;
      break;
      
    case 'H':
      hlt_setpoint = atof(value);
      break;
      
    case 'M':
      mash_setpoint = atof(value);
      break;
      
    case 'C':    // Set control mode
      if (value) {
        int_value = atoi(value);
        if(int_value < DISPLAY_ONLY || int_value > BOIL) {
          return_serial_error(&command, value, "Invalid parameter");
        }
        control_mode = int_value;
      }
      else {        
        return_serial_error(&command, value, "Missing parameter");
      }
      break;
  }
}

//*************************************************************************************
// return_serial_error - Returns an error message if the incoming serial message is faulty
//*************************************************************************************

int return_serial_error(char *command, char *value, char *error) {
  
  Serial.print("E");
  Serial.print(command);
  Serial.print(",");
  Serial.print(value);
  Serial.print(",");
  Serial.print(error);
  Serial.println("Z");
}

int send_temp_data()
{
  Serial.print("D");
  Serial.print(last_time, DEC);
  Serial.print(",");
  Serial.print(hlt_temp, DEC);
  Serial.print(",");
  Serial.print(hlt_setpoint, DEC);
  Serial.print(",");
  Serial.print(hlt_element, DEC);
  Serial.print(",");
  Serial.print(herms_temp, DEC);
  Serial.print(",");
  Serial.print(herms_setpoint, DEC);
  Serial.print(",");
  Serial.print(herms_element, DEC);
  Serial.print(",");
  Serial.print(mash_temp, DEC);
  Serial.print(",");
  Serial.print(mash_setpoint, DEC);
  Serial.print(",");
  Serial.print(control_mode, DEC);
  Serial.println("Z");
}

int send_ready_signal() {
  Serial.print("R");
  Serial.println("Z");
}

//*************************************************************************************
// LCD print functions
//*************************************************************************************

void lcd_print(char *string, int x, int y)
{
  lcd.setCursor(x,y);
  lcd.print(string);
}

void lcd_print_int(int i, int x, int y)
{  
  lcd.setCursor(x,y);
  lcd.print(i);
}

void lcd_print_float(float f, int x, int y)
{  
  int temp;
  
  lcd.setCursor(x,y);
  
  if(f < 0.1) {
    lcd.print ("--.-");
    return;
  }
  
  if (f < 10) lcd.print (" ");
  
  lcd.print((int)f);
  lcd.print(".");
  
  temp = (f - (int)f) * 10;
  
  lcd.print(abs(temp));
}

void lcd_clear()
{
  lcd_print("                   ", 0, 0);
  lcd_print("                   ", 0, 1);
  lcd_print("                   ", 0, 2);
  lcd_print("                   ", 0, 3);

}

User avatar
alexe
Steady Drinker
Posts: 93
Joined: Sun Oct 26, 2008 8:27 pm
Location: Zug, Switzerland

Re: Brewery Programming

Post by alexe » Tue Apr 03, 2012 6:34 pm

Jamie,
Forgive my ignorance, but can you describe (preferably with a circuit diagram) how you control the pump speed...
Thanks,
Alex

JabbA

Re: Brewery Programming

Post by JabbA » Tue Apr 03, 2012 7:14 pm

Fil wrote:I see your sending either HIGH or LOW to various relays, is this on/off? 100% output to the heating element and 0 output
Hi Fil,

Yep, got in in one! Setting a HIGH state sends a logic-level +5v to an output, enough to switch on an SSR and LOW = 0v, switching an SSR off.

The code below takes a reading from the "Mash_CookTemp" sensor and the PID library calculates and sets the output linked to the SSR to HIGH for a length of time as calculated, or LOW.

Code: Select all

   sensors.requestTemperatures();
   Mash_CookTemp=sensors.getTempCByIndex(2);
   HLTtemp=sensors.getTempCByIndex(1);
   Input = Mash_CookTemp;
   Setpoint = MashTarget;
   myPID.Compute();
   if(millis() - windowStartTime>WindowSize){
   windowStartTime += WindowSize;}
   if(Output > millis() - windowStartTime) digitalWrite(Mash_CookRelay,HIGH);
  else digitalWrite(Mash_CookRelay,LOW
Hope that helps!

Cheers,
Jamie

JabbA

Re: Brewery Programming

Post by JabbA » Tue Apr 03, 2012 7:40 pm

Hi Alex,

I've only hag a quick scan through your code and I must admit a lot of it is a bit over my head until I have a chance to go through it a but more thoroughly, but I do like:

Code: Select all

void lcd_print(char *string, int x, int y)
{
  lcd.setCursor(x,y);
  lcd.print(string);
}

lcd_print("                   ", 0, 0);
Much more elegant than setting the cursor then printing a string.

The pump is controlled by reading an analogue value of the Pot (0-1023) on pin A0 and mapping into to a PWM value (0-255) on pin 10 which is sent to the MOSFET transistor which provideds the variable voltage to the pump. Here's a schematic of a similar NPN transistor that is wired exactly the same as the MOSFET:

Image
The output pin being PWM enabled pin 10. I hope that makes sense?!?

Cheers,
Jamie

User avatar
alexe
Steady Drinker
Posts: 93
Joined: Sun Oct 26, 2008 8:27 pm
Location: Zug, Switzerland

Re: Brewery Programming

Post by alexe » Tue Apr 03, 2012 7:45 pm

Thanks! Glad my code was of some use. Also haven't had a chance to look at your code either yet...

Fil
Telling imaginary friend stories
Posts: 5229
Joined: Sun Oct 16, 2011 1:49 pm
Location: Cowley, Oxford

Re: Brewery Programming

Post by Fil » Tue Apr 03, 2012 8:19 pm

="JabbA"
The code below takes a reading from the "Mash_CookTemp" sensor and the PID library calculates and sets the output linked to the SSR to HIGH for a length of time as calculated, or LOW.
Hope that helps!

Jamie
yeah i missed the pid.compute call.. my excuse being i was reading in notepad, no context sensitive ide to colour code function/lib calls etc..

ive downloaded the ide and just gotta squeeze it onto the ssd of me notebook and if i can have a proper read/run thru ansd sus the variables, so you are effecting proportional power output, relay set high for x out of y cycles/millisecs... great :) courtesy of the pid lib sweet :)
ist update for months n months..
Fermnting: not a lot..
Conditioning: nowt
Maturing: Challenger smash, and a kit lager
Drinking: dry one minikeg left in the store
Coming Soon Lots planned for the near future nowt for the immediate :(

JabbA

Re: Brewery Programming

Post by JabbA » Tue Apr 03, 2012 8:25 pm

Fil wrote: so you are effecting proportional power output, relay set high for x out of y cycles/millisecs... great :) courtesy of the pid lib sweet :)
That's the jobbie! All the hard work is done in for you in the PID libary, all you have to worry about is setting the P,I and D parameters!

Cheers,
Jamie

Fil
Telling imaginary friend stories
Posts: 5229
Joined: Sun Oct 16, 2011 1:49 pm
Location: Cowley, Oxford

Re: Brewery Programming

Post by Fil » Wed Apr 04, 2012 4:52 am

Had a deeper look and see your setting the p i and d to 5,2,and 1 on creation of the mypid object?
how did you arrive at these values? dont they change for different tasks?
or did i miss the reassignment

with my dedicated pid controller the autotune function derives these values leaving me blissfully ignorant :)

I cant see a similar feature within the Pid lib source code?
ist update for months n months..
Fermnting: not a lot..
Conditioning: nowt
Maturing: Challenger smash, and a kit lager
Drinking: dry one minikeg left in the store
Coming Soon Lots planned for the near future nowt for the immediate :(

JabbA

Re: Brewery Programming

Post by JabbA » Wed Apr 04, 2012 9:40 am

Those values are kinda arbitrary, a PID autotune library has now been released for the Arduino but before using it I thought it best to have a play and find out how to tune it myself. There is a graphical front end available with the PID library and I need to get that working so as I can tweak the parameters more accurately.

Cheers,
Jamie

Fil
Telling imaginary friend stories
Posts: 5229
Joined: Sun Oct 16, 2011 1:49 pm
Location: Cowley, Oxford

Re: Brewery Programming

Post by Fil » Wed Apr 04, 2012 11:12 am

cheers Jamie,
im really considering buying a board starter kit, and am gonna lift your code if i do. so get crackin with that auto tune feature ;) I dont wnt to have to roll up my sleeves and get dirty at the codeface.. im lookin for a finished product here ;) and get a BT/serial lib included too ... come on, whats the hold up:)

joking aside, fantastic stuff..
ist update for months n months..
Fermnting: not a lot..
Conditioning: nowt
Maturing: Challenger smash, and a kit lager
Drinking: dry one minikeg left in the store
Coming Soon Lots planned for the near future nowt for the immediate :(

User avatar
barneey
Telling imaginary friend stories
Posts: 5423
Joined: Mon Jul 25, 2011 10:42 pm
Location: East Kent

Re: Brewery Programming

Post by barneey » Wed Apr 04, 2012 3:54 pm

Well everyone is taking gobbledygook with the code thats been posted, so when I get my basic kit, I think I`ll be working through the Arduino Kit Manual first.

Baby steps first ;)
Hair of the dog, bacon, butty.
Hops, cider pips & hello.

Name the Movie + song :)

Matho

Re: Brewery Programming

Post by Matho » Mon Apr 09, 2012 10:52 am

I have been working on a shield and code for a single vessel RIMS ( think braumeister), the reason was that I was asked to make my controller for my braumiser but the PIC microcontroller i used required special hardware to program so I decided to use the Arduino. I have built a prototype and finished the code Ill post the code when I have finished testing. What I have programmed is a controller that controls the heating element and the pump, it uses a single DS18b20 to read the temperature, it has a buzzer to alert when there is a action required like 'remove malt'. There are up to 9 programmable mash steps and timed boil stage with up to 8 hop addition alarms. Apart from the Auto mode there is also a Manual mode which allows you to use the four buttons to increase or decrease the set temperature and turn the heat and the pump on and off. You set it up through the four buttons, all setting are saved to the EEPROM as well as the progress through the auto mode so if the power lost you can pick up where you left off.
Here is are some pics of the shield and the schematic

Image

Image

Image

cheers steve

User avatar
alexe
Steady Drinker
Posts: 93
Joined: Sun Oct 26, 2008 8:27 pm
Location: Zug, Switzerland

Re: Brewery Programming

Post by alexe » Mon Apr 09, 2012 5:34 pm

Looks interesting! Looking forward to seeing your code...

Post Reply