DIY Fermentation Controller

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

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

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

LordUlrich

Well-Known Member
Joined
Dec 10, 2010
Messages
542
Reaction score
13
Location
Rochester
I figured being as I have finally finished my DIY fermentation temperature controller, I might as well share what I have done.

This is solely what I have one, I make no assurance or warranty or even suggest anyone else try this (but I will answer any questions anyone has). I also get no kick backs or profit on anything I have posted here.

dsc001421-61372.jpg


I designed my own circuit board and had it manufactured using the OSHpark service (great service, 3 boards made in about a week, at $5 per sq in)
http://oshpark.com/shared_projects/GSwiuFKu

The PCB parts are all available from Digi-Key with a few extra parts still needed (such as enclosure, outlet power cord, power supply (or transformer). Total Dig-key parts are about $30, although it can be brought down by buying in quantity (10 or 100 of a part is cheaper than 8 sometimes)

The board acts as an Arduino UNO, although there is no USB interface on board. The program I have, while it has some bugs, lets me set target temperature, allowable temp range and a delay between changes in state. In theory the settings are saved to the EEPROM, but that seems to not work yet. The code is here:

Code:
//*********************Libraries********************************
#include <EEPROM.h>
#include <SevSeg.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//*******************Operational Paramaters**********************

//Min Setpoint
float TargetMin=40.0;
//Max Setpoint
float TargetMax=95.0;
//Max allowable Range
float RangeMax=9.9;
//Max allowable Wait Time
unsigned int waitMax=90;
//Debounceing time
byte udLimit=20;
//Delay to reduce display flashing
byte DispDelay=200;


//EEPROM addresses
byte setAdd=0;
byte rangeAdd=2;
byte timeAdd=4;



//*******************Pin Declerations****************************
//setup display
SevSeg Display;
int displayType = COMMON_ANODE; //Your display is either common cathode or common anode
int numberOfDigits = 4; //Do you have a 1, 2 or 4 digit display?
//Declare what pins are connected to the digits (anode)
int digit1 = 9; //Pin 12 on my 4 digit display
int digit2 = 11; //Pin 9 on my 4 digit display
int digit3 = 17; //Pin 8 on my 4 digit display
int digit4 = 15; //Pin 6 on my 4 digit display
//Declare what pins are connected to the segments
int segA = 3; //Pin 11 on my 4 digit display
int segB = 10; //Pin 7 on my 4 digit display
int segC =   2; //Pin 4 on my 4 digit display
int segD = 12; //Pin 2 on my 4 digit display
int segE = 14; //Pin 1 on my 4 digit display
int segF = 18; //Pin 10 on my 4 digit display
int segG = 13; //Pin 5 on my 4 digit display
int segDP= 16; //Pin 3 on my 4 digit display

//Input Pins
int enter_pin=5;
int up_pin=4;
int dn_pin=6;

// one wire sensor pin
#define ONE_WIRE_BUS A5

//Output Pins
#define HeatPin 7
#define CoolPin 8

//***********************Global Variables********************

// Current Temp
float tempF;
// Target Temp
float TargetFloat=69.0;
// Allowable Temp Range
float RangeFloat=0.5;
// Time Between Control Changes
unsigned int waitTime=1;
//Control States
boolean heat_state=false;
boolean cool_state=false;
//Display mode
byte dispMode = 0;
//Counter to reduce display flash (delays temp read)
byte DispCount=0;
// Counter for debounce
int udTime=0;
// variable for debounce
boolean enter_state=false;
//Time control state can be changed
unsigned int time_clear=0;

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// arrays to hold device address
DeviceAddress DSAddress;





//**************************Functions**************************

void startOneWire(){
  //setup one wire
  sensors.begin();
  sensors.getAddress(DSAddress, 0);
  sensors.setWaitForConversion(false);
  sensors.requestTemperatures(); // Send the command to get temperatures
}

float ReadEEPROM(byte Add){
  word wrd;
  byte byteH, byteL;
  float floatvalue;
  byteH=EEPROM.read(Add);
  byteL=EEPROM.read(Add+1); 
  wrd=word(byteH,byteL);
  floatvalue=wrd;
  return floatvalue;
}

void WriteEEPROM(float value, byte Add){
        word wrd;
        wrd=word(value);
        EEPROM.write( highByte(wrd), Add);
        EEPROM.write( lowByte(wrd), Add+1);
}

void SetPins(){
  
  pinMode(HeatPin, OUTPUT);
  pinMode(CoolPin, OUTPUT);
  digitalWrite(HeatPin, LOW);
  digitalWrite(CoolPin, LOW);
  pinMode(enter_pin, INPUT);
  pinMode(up_pin, INPUT);
  pinMode(dn_pin, INPUT);
}

void StartDisplay(){
  Display.Begin(displayType, numberOfDigits, digit1, digit2, digit3, digit4, segA, segB, segC, segD, segE, segF, segG, segDP);
  Display.SetBrightness(50); //Set the display brightness level
  Display.DisplayString("8888", 0);
}

float GetTemp(){
  float temp;
  temp=sensors.getTempF(DSAddress); //get first temp
  sensors.requestTemperatures(); // Send the command to get temperatures
  return temp;
}

void printFloat10(float value, char leading){
    char dispStr[5];
    float dispFlt=value*10;
    int dispInt=int(dispFlt);
    String tempStr = String(dispInt);
    if (value < 10){
      tempStr="0"+tempStr;
    }
    if (value <1){
        tempStr="0"+tempStr;
    }
    tempStr=leading + tempStr;
    tempStr.toCharArray(dispStr,5);
    Display.DisplayString(dispStr, 3);
}

void printFloat0 (float value, char leading){
    char dispStr[5];
    int dispInt=int(value);
    String tempStr = String(dispInt);
        if (value < 100){
      tempStr="0"+tempStr;
    }
    if (value <10){
        tempStr="0"+tempStr;
    }
    tempStr=leading + tempStr;
    tempStr.toCharArray(dispStr,5);
    Display.DisplayString(dispStr, 0);
}

float adjFloat(float input, float mn, float mx, int factor, float inc){
  float output;
  output=input + inc * float(factor);
  output=max(output, mn);
  output=min(output, mx);
  return output;
}





//*************************Operating System***********************

void setup(){
  
  SetPins();
  StartDisplay();
  startOneWire();
  //Get values from EEPROM
//  TargetFloat=ReadEEPROM(setAdd)/10.0;
//  RangeFloat=ReadEEPROM(rangeAdd)/10.0;
//  waitTime=ReadEEPROM(timeAdd);
  
  //check for temp conversion
  while (sensors.isConversionAvailable(DSAddress)==false){
    Display.DisplayString("8888", 0);
  }
  tempF=GetTemp();
}

void loop(){
  
  //Update current temp
  if (DispCount >= DispDelay){
    tempF=GetTemp();
    DispCount=0;
     if (millis()>=time_clear){
      if (heat_state) {
        if (tempF > TargetFloat +( RangeFloat/2.0)){
          digitalWrite(HeatPin,LOW);
          heat_state=false;
          time_clear=millis()+(waitTime*60000);
        }
      }
      else if (cool_state){
        if (tempF < TargetFloat -( RangeFloat/2.0)){
          digitalWrite(CoolPin,LOW);
          cool_state=false;
          time_clear=millis()+(waitTime*60000);
        }
      }
      else {
          if (tempF < TargetFloat -( RangeFloat/2.0)){
          digitalWrite(HeatPin,HIGH);
          heat_state=true;
          time_clear=millis()+(waitTime*60000);
          }
          else if (tempF > TargetFloat +( RangeFloat/2.0)){
          digitalWrite(CoolPin,HIGH);
          cool_state=true;
          time_clear=millis()+(waitTime*60000);
          }
      }        
  }
  }
  DispCount++;
  
  //read "ENTER" button
  if (digitalRead(enter_pin)==HIGH){
    if (enter_state==false){
      enter_state=true;
      switch (dispMode){
        case 0:
          // displaying current temp- change to setpoint
          dispMode=1;
          break;
        case 1:
          //change to range
          dispMode=2;
//          WriteEEPROM(TargetFloat*10, setAdd);
          break;
        case 2:
          //change to delay
          dispMode=3;
//          WriteEEPROM(RangeFloat*10, rangeAdd);
          break;
        case 3:
          // change to current temp
          dispMode=0;
//          WriteEEPROM(waitTime, timeAdd);
          break;
      }
    }
  }
    else {
      int factor=0;
        enter_state=false;
       //read up or down press       
      if (digitalRead(up_pin)==HIGH){
        udTime++;
        if (udTime == udLimit){
          factor=1;
          udTime=0;
        }        
      }
      else if (digitalRead(dn_pin)==HIGH){
        udTime--;
         if (udTime <= (-1*udLimit)){
          factor=-1;
          udTime=0;
        }
      }
      else {
        udTime=0;
      }

      switch (dispMode){
        case 1:
          //adj target
          TargetFloat=adjFloat(TargetFloat, TargetMin, TargetMax, factor, 0.5);
          break;      
        case 2:
          //adj range
          RangeFloat=adjFloat(RangeFloat, 0.5 , RangeMax, factor, 0.5);
          break; 
        case 3:
          //adj wait
          waitTime=adjFloat(waitTime, 0, waitMax, factor, 1);
          break;          
      
     } 
      
  }
    switch (dispMode){
      case 0:
      //display temp

        printFloat10(tempF, 32);
      break;
      
      case 1:
         printFloat10(TargetFloat,116);
      break;
      case 2:
         printFloat10(RangeFloat, 114);
      break;
      case 3:
         printFloat0(waitTime, 100);
      break;
    }
  
}

So there you go
 
Pretty awesome, I like it! Way to go all out!

I'm new to all this though and I was wondering, how come you didn't just buy:

Arduino 25$
Digital thermometer 2$
Relays ~5$

And wire it all up for your plugs?

Trust me... I could easily be missing something.
 
The real reason is because I can, and wanted to learn, and I figured I could do it cheaper ( excluding time), which I think I did if you exclude the 3 revisions in the garbage ( one from a stupid error). Then because I am stubborn.

An Arduino and some relays is where this started. But then I wanted a display, which adds a few bucks and some more wires to run around (the board has those wires as traces). The relays take too much current to drive directly so transistors were needed, more wires to run around. I think that using a manufactured board just is easier as it gives me nice pads and solder mask which is a huge step up from perfboard, you pay a few bucks for convenience, but not that much when you have to troubleshoot a bad joint.
While I could have picked cheaper relays (there are 2) the ones I used are about $5 each, but are 30 amp rated and have spade terminals and screw tabs making them easy to mount and change if they fail. The relays are the second biggest cost part (boards are a bit more at $10 each or so)

So really the answer is because I can, but buying an Arduino board works too.
 
Back
Top