Brewery Programming

The forum for discussing all kinds of brewing paraphernalia.
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 » Tue Feb 12, 2013 6:33 pm

I will probadly have a little trunking left Fil if you need a few offcuts ;)
Hair of the dog, bacon, butty.
Hops, cider pips & hello.

Name the Movie + song :)

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 Feb 12, 2013 9:31 pm

ha ha I think your monster build trunking would dwarf the box leaving little space for anything barneey.. the spaghetti is just temp as i finalise the h/w config .. the board will probably piggyback the lcd and just have power, ssr control and probe wires bridging between lid and base. i will just need to fit another button for the reset.
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 :(

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

Re: Brewery Programming

Post by Fil » Thu Feb 14, 2013 1:36 am

found this for reading pt100/pt1000 rtd sensors with an arduino.. ends up using 2 analog outputs and one input and involves a bit of a circuit.. i priced up the components as £12 on ebay and have taken a punt, i may be able to put it together.. http://openenergymonitor.org/emon/build ... re-sensing,,
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 :(

Matho

Re: Brewery Programming

Post by Matho » Mon Jun 24, 2013 10:40 am

wrote up a bit of code to turn the brauduino shield into a fermentation controller

Image

Image

Code: Select all

/*
brauduino semi automated single vessel RIMS
 created by s.mathison
 using part code orginal developed  by Michael Pilcher  2/9/2010 
 Copyright (C) 2012  Stephen Mathison
 
 compiled on Arduino V1.0
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.*/



//libraries
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <avr/pgmspace.h>
OneWire ds(11);
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

// push buttons
const char Button_up = A3;
const char Button_dn = A2;
const char  Button_prev = A1;
const char  Button_nxt = A0;


// outputs
const int Heat = 9;
const int Cool = 8;
const int Buzz = 10;

//Global Variables

unsigned long convStartTime,displayTime,start,buttonPress;

float Temp_c,setTemp,hyst;

byte data[2];
byte firstNumber;
byte secondNumber;
byte remainder;
byte second;
byte minute;
byte hour;
byte day;
byte compDelay;
byte compTime;

const byte gBigFontShapeTable[] PROGMEM	=	{
  //*	LT[8] =
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  //*	UB[8] =
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  //*	RT[8] =
  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  //*	LL[8] =
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111,
  //*	LB[8] =
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  //*	LR[8] =
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100,
  //*	UMB[8] =
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  //*	LMB[8] =
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111


};

const byte gBigFontAsciiTable[] PROGMEM	=	{


  0,	1,	2,	3,	4,	5,		//	0x30	0
  1,	2,	32,	32,	255,	32,		//	0x31	1
  6,	6,	2,	3,	7,	7,		//	0x32	2
  6,	6,	2,	7,	7,	5,		//	0x33	3
  3,	4,	2,	32,	32,	5,		//	0x34	4
  255,  6,	6,	7,	7,	5,		//	0x35	5
  //	0,	6,	6,	7,	7,	5,		//	0x35	5
  0,	6,	6,	3,	7,	5,		//	0x36	6
  1,	1,	2,	32,	0,	32,		//	0x37	7
  0,	6,	2,	3,	7,	5,		//	0x38	8
  0,	6,	2,	32,	32,	255,		//	0x39	9


};


boolean Conv_start = false;
boolean run = false;
boolean cool_on = false;
boolean showlp = false;

//***********Start of the Functions***************

static void	BigNumber_SendCustomChars(void)
{
  uint8_t	customCharDef[10];
  uint8_t	ii;
  int		jj;

  for (ii=0; ii<8; ii++)
  {
    for (jj=0; jj<8; jj++)
    {
      customCharDef[jj]	=	pgm_read_byte_near(gBigFontShapeTable + (ii * 8) + jj);
    }
    lcd.createChar(ii, customCharDef);
  }
}

// 1 second button press
int Button_1sec_press (int Button_press){
  if (digitalRead(Button_press)==0){
    delay (1000);
    if (digitalRead(Button_press)==0){
      lcd.clear();
      while(digitalRead(Button_press)==0){
      }
      return 1;
    }
  }
  return 0;

}




// repeat button press
int Button_repeat (int Button_press){
  if (digitalRead(Button_press)==0){
    delay(200);
    return 1;
  }
  return 0;
}

// holds whilst button pressed 
int Button_hold_press (int Button_press){
  if (digitalRead (Button_press)==0){
    delay(50);
    while (digitalRead (Button_press)==0){
    }
    return 1;
  }
  return 0;
}

void display_lcd (int pos , int line ,const char* lable){
  lcd.setCursor(pos,line);
  lcd.print(lable);
}


// reads the DS18B20 temerature probe 
void Temperature(void){
  // start conversion and return
  if (!(Conv_start)){
    ds.reset();
    ds.skip();
    ds.write(0x44,0);
    Conv_start = true;
    convStartTime = millis();
    return;
  }
  // check for conversion if it isn't complete return if it is then convert to decimal
  if (Conv_start){
    if ((millis()-convStartTime)<1000){
      return;
    }
    ds.reset();
    ds.skip();
    ds.write(0xBE);  
    for ( int i = 0; i < 2; i++) {           // we need 2 bytes
      data[i] = ds.read();
    } 
    unsigned int raw = (data[1] << 8) + data[0];
    unsigned int whole = (raw >>4);
    unsigned int remain = (raw & 0X000f);
    Temp_c = (raw) * 0.0625;
    firstNumber = whole/10;
    secondNumber = whole%10;
    remainder = (remain * 10)/16;
    Conv_start = false;
    return;
  } 

}
//************************************************************************
//*	returns the width of the character
static int	DrawBigChar(byte theChar , int xLocation)
{
  int		offset;
  int		ii;
  byte	theByte;
  offset		= theChar *6; 
    lcd.setCursor(xLocation, 0);
  for (ii=0; ii<3; ii++)
  {
    theByte	=	pgm_read_byte_near(gBigFontAsciiTable + offset + ii);
    lcd.write(theByte);
  }

  lcd.setCursor(xLocation, 1);
  offset	+=	3;
  for (ii=0; ii<3; ii++)
  {
    theByte	=	pgm_read_byte_near(gBigFontAsciiTable + offset + ii);
    lcd.write(theByte);
  }

}
// displays large font numbers
void PrintDegC(void){

  lcd.setCursor(13,0);
  lcd.print("o");
  lcd.setCursor(8,1);
  lcd.write(4);
  lcd.setCursor(14,1);
  lcd.write(8);
  lcd.write(6);
}

//Prints current Temperature in large font
void displayBigTemp(){
  DrawBigChar(firstNumber,0);
  DrawBigChar(secondNumber,4);
  lcd.setCursor(8,1);
  lcd.write(4);
  DrawBigChar(remainder,10);
  PrintDegC();


}

//Timing of the fermentation 
void ferment_timing()
{
  if ((millis()-start)>1000){  // timing routine
    start = millis();
    second++;
    if (second>59){
      second = 0;
      minute++;
      if(minute>59){
        minute = 0;
        hour++;
        if(hour>23){
          hour=0;
          day++;
        }
      }
    }
  }

}

float change_temp(float& temp_change,int upper_limit,int lower_limit)
{
  // Increase set temp
  if (Button_repeat(Button_up)){  
    temp_change+=0.1;
    if (temp_change > upper_limit)temp_change = upper_limit;
  }
  // decrease temp
  if (Button_repeat(Button_dn)){
    temp_change-=0.1;
    if ( temp_change < lower_limit) temp_change = lower_limit;
  }

}
void heatOff(){
  digitalWrite(Heat,LOW); 
}

void heatOn(){
  digitalWrite(Heat,HIGH); 
}
void coolOff(){
  digitalWrite(Cool,LOW);
  if (cool_on){
    compTime = minute;
    cool_on =false;
  }
}
void coolOn(){
  if ((minute - compTime)>= compDelay){
    digitalWrite(Cool,HIGH);
    cool_on= true;
  }
}

void save_settings (int addr,int data)
{
  EEPROM.write(addr,highByte(data));
  EEPROM.write((addr+1),lowByte(data));

}  



void save_settings (int addr,byte data){

  EEPROM.write(addr,data);

}

int change_set(byte& set_change,int upper_limit,int lower_limit,int step_size)
{
  // Increase set temp
  if (Button_repeat(Button_up)){
    set_change+=step_size;
    display_lcd(0,1,"                ");
  }
  if (set_change > upper_limit)set_change = upper_limit;

  // decrease temp
  if (Button_repeat(Button_dn))
  {
    set_change-=step_size;
    display_lcd(0,1,"                ");    
  }
  if ( set_change < lower_limit) set_change = lower_limit;
}

// settings for the unit
void set(){
  for(int i=0;i<3;i++){
    boolean setLoop = true;
    byte setting;
    float settingFloat;
    int settingWord;
    if (i==0){
      setTemp= word(EEPROM.read(50),EEPROM.read(51));
      setTemp=setTemp/10.0;
      while (setLoop){
        display_lcd (0,0,"Set Temp = ");
        lcd.print(setTemp);
        change_temp(setTemp,35,2);
        if(Button_hold_press(Button_nxt)){
          setTemp=setTemp * 10;
          settingWord =  word(setTemp);
          save_settings(50,settingWord);
          setLoop = false;
        }
      }
    }
    if (i==1){
      setting = (EEPROM.read(52));
      display_lcd(0,0,"                ");
      while (setLoop){
        settingFloat =setting/10.0;
        display_lcd (0,0,"Hyst = ");
        lcd.print(settingFloat);
        change_set(setting,20,1,1);
        if(Button_hold_press(Button_nxt)){
          save_settings(52,setting);
          setLoop = false;
        }
      }

    }
    if (i==2){
      setting = EEPROM.read(53);
      display_lcd(0,0,"                ");
      while (setLoop){
        display_lcd (0,0,"Comp Delay = ");
        lcd.print(setting);
        change_set(setting,9,2,1);
        if(Button_hold_press(Button_nxt)){
          save_settings(53,setting);
          setLoop = false;
          setTemp = word(EEPROM.read(50),EEPROM.read(51));
          setTemp = setTemp/10.0;
          lcd.clear();
        }
      }

    }
  }
}
void loadSettings(){
  setTemp = word(EEPROM.read(50),EEPROM.read(51))/10.0;
  hyst = EEPROM.read(52)/10.0;
  compDelay = EEPROM.read(53); 
}

//control process
void control(){

  loadSettings();

  while (run){  
    Temperature();
    if (showlp){
      display_lcd(0,0,"Time=");
      lcd.print(day);
      lcd.print(":");
      lcd.print(hour);
      lcd.print(":");
      lcd.print(minute);
      lcd.print(":");
      if(second<10)lcd.print("0");
      lcd.print(second);
      display_lcd(0,1,"S/A=");
      lcd.print(setTemp);
      lcd.print("/");
      lcd.print(Temp_c);
      change_temp(setTemp,35,2);
      if (Button_hold_press(Button_nxt)){
        lcd.clear();
        showlp =false;
      }

    }
    else displayBigTemp();
    ferment_timing();
    if (Temp_c>=setTemp)heatOff();
    if (Temp_c<=setTemp)coolOff();
    if (Temp_c<=(setTemp-hyst))heatOn();
    if (Temp_c>=(setTemp-hyst))coolOn();
    if (Button_hold_press(Button_nxt)){
      showlp = true;
      lcd.clear();
    }
    if (Button_1sec_press(Button_prev))run= false;
  }
}
void setup(){
  lcd.begin(16,2);
  pinMode (Button_up,INPUT);
  pinMode (Button_dn,INPUT);
  pinMode (Button_prev,INPUT);
  pinMode (Button_nxt,INPUT);
  pinMode (Heat,OUTPUT);
  pinMode (Cool,OUTPUT);
  pinMode (Buzz,OUTPUT);
  // assignes each segment a write number

  BigNumber_SendCustomChars();
  setTemp = word(EEPROM.read(50),EEPROM.read(51));
  setTemp = setTemp/10.0;


}

void loop(){
  if (Button_1sec_press(Button_prev)){
    run = true;
    control();
  }
  if (Button_1sec_press(Button_nxt)){
    set();
  }

  digitalWrite(Heat,LOW);
  digitalWrite(Cool,LOW);
  second = 0;
  minute = 0;
  hour = 0 ;
  day = 0;
  display_lcd(1,0,"Ferment Control");
  display_lcd(1,1,"Set Temp = ");
  lcd.print(setTemp);

}








when I get a bit of time I might merge the 2 codes to make a 1v brewery controller and a ferment controller in one

Matho

Re: Brewery Programming

Post by Matho » Mon Jun 24, 2013 12:49 pm

oh also I had the LCD scramble on me when I tried to switch my fridge off with the unit, the board already has the snubber circuit connected across the relay contacts, so I decided to put a 25v 2200uF electrolytic cap across the 12v supply input and it has seemed the fix the problem.

cheers steve

darkonnis

Re: Brewery Programming

Post by darkonnis » Sat Jun 29, 2013 7:55 pm

Math, does this/could this control fermentation with steps in? (ie, 2 days at 12c - 20 days at 9c - 2 days at 18c - few months at 2c)

Its probably beyond me how I'd make it OR how I'd afford it with all the other toys I'm saving up for :D but Its a straight toss up between this and 2 STC-1000s. which are cheaper but require you to remember to do things and be close enough to do them.

Matho

Re: Brewery Programming

Post by Matho » Sun Jun 30, 2013 1:35 am

yeah I have been planning on doing that, in the code already is a timer and it wouldn't be hard to do, just making up a menu that is easy to set would take a bit of work. I have been thinking of maybe 3 profiles you can pick from and you can change those profiles. I will have to think about it a bit longer

cheers steve

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

Re: Brewery Programming

Post by Fil » Sun Jun 30, 2013 10:31 pm

Matho wrote:yeah I have been planning on doing that, in the code already is a timer and it wouldn't be hard to do, just making up a menu that is easy to set would take a bit of work. I have been thinking of maybe 3 profiles you can pick from and you can change those profiles. I will have to think about it a bit longer

cheers steve
why not just lift/use the staged mash profile setup and execution in the excellent code u already wrote?
a rtc might be useful for the longer timescales..
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 :(

Matho

Re: Brewery Programming

Post by Matho » Wed Jul 03, 2013 9:45 pm

Thanks Fil, I was thinking of implementing something like this

start: 'X' degs
hold 'X' days
ramp to 'x' degs over 'x' amount of days
hold 'x' days
and so on.
The timer should be good for about 7 weeks, if I want more I'll have to write a bit of code to handle the overflow, a RTC would be good too and I have one in my parts bin but I'm too lazy to figure it out.

cheers steve

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

Re: Brewery Programming

Post by Fil » Thu Jul 04, 2013 10:02 pm

sampled one to many to make sense this evening :) but was thinking a list of stages would define a fermentation each stage with its own ramping data, index, duration and target perhaps expected starting temp too for error checkin? could be coded into a byte or 2 per stage without too much hassle if space is a prob, might even get away with a recersive control function with a depth check of course...
but as i dont intend to do owt about it :) so u do it your way :)
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 :(

Post Reply