Home Brewing Fementer Temperature Control & Monitoring using Arduino & emoncms

I recently decided to have a go at home brewing with a friend. Having read "Brew" by James Morton, I realised that careful control of fermentation temperatures is important for avoiding nasty beer. A few years ago, I discovered OpenEnergyMonitor, and as well as developing a system for monitoring energy use in my house, I also learned how to use Arduino-based devices for controlling stuff. So it was clear that I should bring this knowledge to home brewing, and particularly to the fermentation process.

This project involves;



  • Hacking a fridge to act as the fermentation chamber
  • Control of fridge cooling and heating to maintain a constant beer temperature using Arduino-based device and programming.
  • Use of openenergymonitor setup and emoncms to monitor beer and fridge conditions during the fermentation period.
Let me know what you think! I'm not an expert in any of these areas, so suggestions for improvements welcome.


Hardware

Fridge

We picked up a second hand fridge (£50) to act as the fermentation chamber. By adding a relay on the electrical supply to the fridge, the temperature in the fridge can be controlled (see BrewPi's guide to fridge hacking). A ceramic heater is mounted at the bottom of the fridge to enable the temperature to be raised if necessary (if the beer is too cold, the yeast activity may be slow). This is activated via a second relay.

Within the fridge are two DS18B20 temperature sensors. One measures the fridge temperature, the other measures the beer temperature.The fermenter bucket was fitted with a thermowell to house the second temperature sensor. A 3.5mm jack socket embedded within the thermwell enables the sensor to be plugged in to the arduino as required (enabling the bucket to be moved easily).

Computer fans were attached to the shelf as a late addition to improve air circulation. 

It turned out that the shelf struggled to hold the weight of the bucket when full, so some old wall tiles were used to prop it up! Will be replaced with something better before the next brew.


Fridge 'hacked' for use as fermenter chamber. 


Arduino Control


The fridge cooling and heating is controlled by an Arduino Uno, which I built myself from components. There are guides on the web on how to do this. My device is also fitted with an RFM12B radio transceiver, allowing packets of data to send back to the monitoring system (I'll explain this shortly).

The Arduino Uno receives data from the two temperature sensors, and outputs to two relays- one for the fridge, and the other for the heater.

The difference in the temperature of the fridge and the beer is essentially the 'gain' for controlling the beer temperature. I have opted for simple proportional control. The further away the beer is from its setpoint, the colder (or warmer) the fridge target temperature will be. So if the beer is exactly at the required temperature, the fridge target temperature will the same as the beer.

The fridge target temperature follows this equation;


Fridge Temp Setpoint = Beer Temp Sepoint - Kp*(Beer Temp - Beer Temp Sepoint)


Where Kp is the proportional control parameter. I set this to 5 initially (not sure where I got this from), which means that if the beer temperature is 1 degC higher than it should be, the fridge will be cooled to 5degC lower than the beer. The idea of this is to encourage a fast response, although clearly there is a danger of overshoot if Kp is too high. 

I thought about using PID control, and adding Ki and Kd parameters, but as will be seen, this isn't necessary.

The cooling and heating are activated on a hysteresis loop. So, for example, the fridge is turned on if the fridge setpoint is exceeded by 1degC, but only turned off when the temperature falls below the setpoint. This 1degC deadband stops the fridge being turned quickly on and off around a single temperature, which could damage the compressor. This means that the fridge temperature is maintained within 1degC of its setpoint. Also, I made a 'deadband' between the heating and cooling, so that they would never be on at the same time.

I'll post some code at the bottom of this post.



Arduino-based controller and relay box for fridge/heater control, and RF data transmission. I initially labelled the fridge and heater sockets the wrong way round, very confusing!


OpenEnergyMonitor & Emoncms


I set up an OpenEnergyMonitor system in my house, consisting of;
  • Raspeberry Pi fitted with RFM12Pi radio transceiver 
  • Raspberry Pi runs OpenEnergyMonitor web sever with emoncms, and logs data from fermenter controller on an SD card.
  • Data also sent to emoncms.org to allow fermenter to be monitored across the web (not just on my home network).
Thanks to the guys at OpenEnergyMonitor who have helped me get this system going!


Snapshot from emoncms dashboard. The big drop in temperature is caused by unplugging a temperature sensor (which gives an error code of -127).

Results


Amazingly, the beer temperature was held within 0.2degC of the target temperature (20degC) for the entire 2-week fermentation period. The proportional control algorithm worked perfectly- the fridge temperature oscillated through the 1degC deadband about its (moving) setpoint. The thermal inertia of the fermenter meant that the fluctuations in the fridge temperature had minimal effect on the beer temperature.

One issue I did find was that the fridge cooling and heating would often overshoot its setpoint. Particularly the heating. The heater would come on to raise the fridge temperature, but the temperature would rise too much, and so immediately cooling would be needed. I suspect this is because the ceramic heater actually stores quite a bit of heat within it, and keeps outputting when the power goes off. The issue might be improved by using a smaller heating element (I used 100W).


Snapshot from emoncms dashboard. The beer temperature (red line) stays within 0.2deg of the setpoint (20degC), while the fridge temperature oscillates about its moving setpoint on a hysteresis loop.


Snapshot of emoncms showing cooling (blue) and heating (red) status. 100 = "ON", 0 = "OFF". Every time the heat is activated, this is followed, after a short pause, by cooling. This suggests that the ceramic lamp continues to emit heat after it is turned off.


Future Improvements

  1. Add support brackets for shelf to take weight of fermenter bucket.
  2. Add real time clock to enable more complex fermentation regimes to be used.
  3. Make case for electronics.
  4. Get a smaller heating lamp (40W?)

Arduino Code


/*
 Fermenter controller based on emonTX v2 code.

 Version 6, 9th April 2017
 Libraries in the standard arduino libraries folder:
- JeeLib https://github.com/jcw/jeelib
- EmonLib https://github.com/openenergymonitor/EmonLib.git
- OneWire library http://www.pjrc.com/teensy/td_libs_OneWire.html
- DallasTemperature http://download.milesburton.com/Arduino/MaximTemperature

 Other files in project directory (should appear in the arduino tabs above)
- emontx_lib.ino
*/
#define RF69_COMPAT 0 // SET TO 1 IF RFM69
#define RF_freq RF12_868MHZ                                                // Frequency of RF12B module can be RF12_433MHZ, RF12_868MHZ or RF12_915MHZ. You should use the one matching the module you have.
const int nodeID = 20;                                                  // emonTx RFM12B node ID
const int networkGroup = 210;                                           // emonTx RFM12B wireless network group - needs to be same as emonBase and emonGLCD

const int UNO = 1;                                                      // Set to 0 if your not using the UNO bootloader (i.e using Duemilanove) - All Atmega's shipped from OpenEnergyMonitor come with Arduino Uno bootloader
#include <avr/wdt.h>                                                     

#define RF69_COMPAT 0 // set to 1 to use RFM69CW 
#include <JeeLib.h>   // make sure V12 (latest) is used if using RFM69CW
ISR(WDT_vect) { Sleepy::watchdogEvent(); }                              // Attached JeeLib sleep function to Atmega328 watchdog -enables MCU to be put into sleep mode inbetween readings to reduce power consumption 

#include "EmonLib.h"
EnergyMonitor ct1;                                                      // Create  instances for each CT channel

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

#define ONE_WIRE1 8                                                  // Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE2 9                                                 // Second bus in digital 9
OneWire oneWire1(ONE_WIRE1);                                          // Setup a oneWire instances to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire2(ONE_WIRE2);               
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors1(&oneWire1); 
DallasTemperature sensors2(&oneWire2);
// By using direct addressing its possible to make sure that as you add temperature sensors
// the temperature sensor to variable mapping will not change.
// To find the addresses of your temperature sensors use the: **temperature_search sketch**

//CHANGE THESE ADDRESSES 
DeviceAddress address_T15 = { 0x28, 0xF9, 0x6B, 0x5D, 0x05, 0x00, 0x00, 0xEB }; //Beer temperature sensor
DeviceAddress address_T16 = { 0x28, 0xF5, 0x5B, 0x44, 0x05, 0x00, 0x00, 0xB9 }; //Fridge Temperature
DeviceAddress address_T11 ={ 0x28, 0x8A, 0x29, 0xE4, 0x04, 0x00, 0x00, 0xE9 }; //Ambient temperature (onboard sensor)


typedef struct {
  int Input1; //Beer Temperature                                        
 int Input2; //Fridge Temperature
    int Input3; //Beer Setpoint                          
 int Input4; //Fridge Setpoint                                        
 int Input5; //Fridge Switch (0=OFF, 1=ON)                           
    int Input6; //Heater Switch (0=OFF, 1=ON)
    int Input7; //Ambient temperature from onboard sensor.
} Payload;
Payload emontx;

const int LEDpin = 4;


//-------------Fridge Control Setup--------------------------

//Define output pins
const int Fridge_Pin=5; //Fridge relay pin output
const int Heater_Pin=6; //Heater relay pin output

//Define temperature variables
  float T_Beer;
  float T_Fridge;
  float T_Ambient;
                         
//Define variables for control
const float Kp=5; //Proportional control variable
const float Deadband=1;
int Fridge_Switch;
int Heater_Switch;

float T_Set_Fridge;      //Set this to a temperature. Will have to be adjusted.
const float T_Set_Beer=18.0; //Beer Temperature setpoint. To be changed to suit the beer being brewed.

const float MinTemp=14; //Set a minimim fridge temperature in case control algorithm doesnt work.

const float MaxTemp=T_Set_Beer +2; //Set a max fridge temperature in case control algorithm doesnt work.

//Compressor Protection: compressorust be given time to settle after it is turned off. 3 mins may be about right. check manufacturer literature.
//long Comp_Counter;// Use to measure time.
//const long Comp_Off_Limit=3*60*1000;// Compressor off minimum time. Not currently in use.



void setup() {
  pinMode(Heater_Pin,OUTPUT);
  pinMode(Fridge_Pin,OUTPUT);
  digitalWrite(Heater_Pin,HIGH); //This will set relays as low.
  digitalWrite(Fridge_Pin,HIGH);
  pinMode(LEDpin, OUTPUT);   
  digitalWrite(LEDpin, HIGH);
        
  Serial.begin(9600);
  delay(2000);

  digitalWrite(LEDpin, LOW);
    
  Serial.println("Fermenter control program, V4. Includes proportional control of beer temperature, using fridge temperature as the gain variable"); 
  Serial.println("19th March 2017");
  Serial.print("Beer target temperature = ");
  Serial.println(T_Set_Beer);


  //Set up fridge initial conditions   

  //T_Set_Fridge=T_Set_Beer; //Initial setpoint of fridge
  Fridge_Switch=0; //Fridge Starts Off
  Heater_Switch=0; //Heater Starts Off
  
  //Comp_Counter=-30,000; //Start the counter behind millis so that the fridge can be started straight away.

  //Start temperature sensors 
  sensors1.begin();
  sensors2.begin();

  delay(2000);
  
  rf12_initialize(nodeID, RF_freq, networkGroup);                                       // initialize RF
  rf12_sleep(RF12_SLEEP);

  
  
  if (UNO) wdt_enable(WDTO_8S); 
  
}

void loop()
    sensors1.requestTemperatures(); // Send the command to get temperatures
    sensors2.requestTemperatures(); // Send the command to get temperatures
  
  //Get temperatures for control purposes****ADDRESSES MUST BE CORRECT

  T_Beer= sensors1.getTempC(address_T15);  
       
  T_Fridge = sensors1.getTempC(address_T16); 
    
  T_Ambient= sensors2.getTempC(address_T11);   


//Establish Fridge Setpoint based on the beer temperature.

T_Set_Fridge=constrain(T_Set_Beer-Kp*(T_Beer-T_Set_Beer),MinTemp,MaxTemp);
          

//ACTIVATE FRIDGE OR HEATER TO CONTROL BEER TEMPERATURE

if(T_Fridge>(T_Set_Fridge+Deadband)) Fridge_Switch=1;
else if(T_Fridge<T_Set_Fridge) Fridge_Switch=0;
if(T_Fridge<(T_Set_Fridge-Deadband)) Heater_Switch=1;
else if(T_Fridge>T_Set_Fridge) Heater_Switch=0;

digitalWrite(Fridge_Pin,1-Fridge_Switch); //Because relays are wired normally open
digitalWrite(Heater_Pin,1-Heater_Switch);


//PRINT INFORMATION TO SERIAL
Serial.print("Time=");
Serial.print(millis());
//Serial.print(" comp counter=");
//Serial.print(Comp_Counter);
Serial.print(" Beer Temp=");
Serial.print(T_Beer);
Serial.print(" Fridge Temp=");
Serial.print(T_Fridge);
Serial.print(" Beer Target Temperature=");
Serial.print(T_Set_Beer);
Serial.print(" Fridge Target Temperature=");
Serial.print(T_Set_Fridge);
Serial.print(" Ambient Temperature=");
Serial.print(T_Ambient);
Serial.print(" Deadband=");
Serial.print(Deadband);


if(Heater_Switch==1) Serial.print(" Heater=ON");
if(Heater_Switch==0) Serial.print(" Heater=OFF");
if(Fridge_Switch==1) Serial.println(" Fridge=ON");
if(Fridge_Switch==0) Serial.println(" Fridge=OFF");


//GIVE TEMPERATURES AND INFORMATION TO EMONTX READY FOR SENDING
  emontx.Input1 = T_Beer * 100;       // Multiple by 100 to make it an integer
  emontx.Input2 = T_Fridge * 100;    // Multiple by 100 to make it an integer
  emontx.Input3 = T_Set_Beer * 100; // Multiple by 100 to make it an integer
  emontx.Input4 = T_Set_Fridge * 100;      //Fridge setpoint variable. Setpoint varies according to beer temperature. This is the means by which beer temperature is controlled.
  emontx.Input5 = Fridge_Switch;      //Fridge on or off
  emontx.Input6 = Heater_Switch;      //Heater on or off
  emontx.Input7 = T_Ambient * 100; //Ambient temperature.
  
Serial.flush(); // Ensure serial has finished sending data.
    
//SEND RF DATA
  send_rf_data();                                                       // *SEND RF DATA* - see emontx_lib
 emontx_sleep(1);                                                     // sleep or delay in seconds - see emontx_lib
  digitalWrite(LEDpin, HIGH); delay(2); digitalWrite(LEDpin, LOW);      // flash LED
}










Comments

Popular posts from this blog

Custom BIAB Setup