Introduction: Dr. Fermentor V9 - Cooling and Heating Fermentation by an Arduino Controller

Objective:

I built a system to control the fermentation temperature of my brew (beer, wine, whisky) by circulating hot or cold water through the fermentation vessel by means of a heat exchanger (a stainless steel wort chiller) which is located inside the fermentation vessel. The temperature is controlled by an Arduino controller, which controls (1) the heating elements of an electric kettle (2) a compressor's cooling coils that were retrofitted into the electric kettle and (3) a pump also located inside the kettle. Digital thermometers signal the arduino which activates the kettle, compressor and pump via a Relay Switch therefore allowing for full temperature control of your fermentor.

The aim was to build a system that can control the temperature on a large fermentor that will not fit inside a refrigerator. Here, I demonstrate fermentation with a standard 23 liter plastic bucket fermenter but I plan to use my 70 liter brew pot in the future as the fermentor (the wort chiller will be retrofitted into the lid and silicon tubing will be used as a gasket clamped down with locking pliers).

Note: The cooling coils/compressor system I got from a used water bubbler/fountain (like those in high school hallways), but any refrigerator or window air conditioning compressor/cooling coils will work.

Generally, this is a big project and this was my first time attempting to build electronics and program code. All the code is open source and can be found both on this site and a simplified code at the Autodesk Circuits site here.

Note: The code on the Autodesk is an older version of what is supplied here since their emulator does not support SD card, Adafruit RGB LCD and DS18B20 temperature sensors.

Step 1: Parts Lists

Electronics from Adafruit ~$130

  1. x1 Arduino Uno R3 - https://www.adafruit.com/products/50
  2. x1 AdaFruit RGB LCD - https://www.adafruit.com/products/714
  3. x1 Adafruit Data Logger - https://www.adafruit.com/products/1141
  4. x1 Simple TMP36 for ambient temp - https://www.adafruit.com/products/165
  5. x2 Waterproof DS18B20 - https://www.adafruit.com/products/381
    1. Note: amazon sells for cheaper
  6. x2 16mm Panel Mount Momentary Pushbutton https://www.adafruit.com/products/1505
  7. Shield stacking headers for Arduino (R3 Compatible) https://www.adafruit.com/products/85
  8. x1 Half-size breadboard (FOR PROTOTYPING) https://www.adafruit.com/product/64


Note: You can get a 3rd push button to use a a 'select/enter' button, in my program i push both the up and down together to 'select/enter'. I also had an SD card lying around.


Electronics from Sainsmart ~$15

  1. 4 channel Relay - http://www.sainsmart.com/4-channel-5v-relay-modul...
  2. Electronic Parts Pack Kit for Arduino Component Resistors Switch Button HM http://www.sainsmart.com/electronic-parts-pack-ki...
  3. 9V 1A AC/DC Plug Power Supply http://www.sainsmart.com/9v-1a-ac-dc-plug-power-s...
  4. Female-Female Jumper wires http://www.sainsmart.com/40pcs-dupont-wire-jumper...
  5. Male-Male Jumper wires http://www.sainsmart.com/40pcs-dupont-wire-jumper...


Online Purchased Parts ~$151

  1. X1 Stainless Steel Wort Chiller - http://www.nybrewsupply.com/stainless-steel-wort-...
  2. X2 - 1/2" NPT x 3/8" Tube Compression adapter SS http://www.bargainfittings.com/index.php?route=pr...
  3. x4 O-RING 1/2 NPT - SILICONE http://www.bargainfittings.com/index.php?route=pro...
  4. x4 1/2" WASHER 304 SS http://www.bargainfittings.com/index.php?route=pro...
  5. x4 1/2" NPS LOCKNUT STAINLESS http://www.bargainfittings.com/index.php?route=pro...
  6. X2 - 1/2" SS Camlock D style fitting Quick Disconnect http://www.bargainfittings.com/index.php?route=pr...
  7. X2 - 1/2" SS Camlock F style fitting Quick Disconnect http://www.bargainfittings.com/index.php?route=pr...
  8. X2 - 1/2" NPT FEMALE x 1/2" hose barb http://www.bargainfittings.com/index.php?route=pr...
  9. X1 - 1/2" NPT MALE X 1/2" HOSE BARB http://www.bargainfittings.com/index.php?route=pro...
  10. X1 - SILICONE TUBING 1/2" ID X 3/4" OD http://www.bargainfittings.com/index.php?route=pr...
  11. X1 Aquarium Water Pump Submersible Fountain 45W 2500L/H
  12. x4 #4 SS CLAMP - FITS 5/8 OUTER DIAMTER TUBING http://www.bargainfittings.com/index.php?route=pro...
  13. 1/2" NPT 30cm/11.8" 304 Stainless Steel Thermowell Probe for Temperature Sensors https://www.amazon.com/gp/product/B00JSYNN80/ref=o...

  14. uxcell® 20 Pcs Remote Control Female to Male Servo Extension Cable Lead https://www.amazon.com/gp/product/B00EZE8YN2/ref=o...

  15. 50pcs Premium Brass 18-22 (RED) Gauge Male-Female Solderless Crimp Bullet Plug Connectors https://www.amazon.com/gp/product/B009638QJY/ref=o...

Notes:

  • The silicone tubing is amazing stuff and I would get more if I were you. I used it for making a seal on a 70 liter distillation vessel that I made (70 liter pot, silicone tubing between pot and lid held by clamps – an Instructable to follow at some point).
  • I bought an aquarium water pump from Chinese ebay store and it came busted up so I’m not recommending a product, it cost about $20 (included in the $110 mentioned above).

Locally Sourced/Free Hardware Parts

  1. Electric Cable Connectors like - http://imagehost.vendio.com/a/28304091/aview/smj_...

  2. Fermentation Vessel – Plastic Bucket from local brew shop.

  3. Garden hose
  4. Bracket from washing machine hose to prevent kinking of hose. http://www.partmaster.co.uk/washing-machine/c510w...
  5. Used Hot water kettle
  6. Used Water bubbler fountain (for compressor cooling coils etc)
  7. Used electric Cable - to extend the length of the Waterproof DS18B20 temperature sensor
  8. Home Depot 10 Gallon Water Cooler -45$


Skills and Tools

  1. Soldering Iron and Lead
  2. x2 TEFLON TAPEhttp://www.bargainfittings.com/index.php?route=pro...
  3. x1 Solder Sucker- desoldering pump https://www.amazon.com/gp/product/B0002KRAAG/ref=o...
  4. x1 STEP BIT 1-3/8" http://www.bargainfittings.com/index.php?route=pr...
  5. Elenco Solid Hook-Up Wire Kit 6 Colors https://www.amazon.com/gp/product/B008L3QJAS/ref=o...

  6. 48 pc HEAT SHRINK TUBING WRAP SLEEVES ASSORTED SIZES https://www.amazon.com/gp/product/B005W42SW2/ref=o...

  7. Wire Strippers

  8. Angle Grinder to cut apart the metal water bubbler to encase the system. With a refrigerator unit this probably won't be necessary.

  9. A propane blow torch – I needed this to disconnect the cooling coils from the water bubbler’s water tank. But you won't need it from refrigerator system.
  10. A computer – to program the arduino on.
  11. Time for the project

Step 2: Constructing the Fermentation Vessel

The Fermentation Vessel consists of a plastic fermentation bucket, a heat exchanger (wort chiller) and thermowell for the temperature probe.

Drill three 1/2" holes in the buckets lid for the Stainless Steel Wort Chiller and 1/2" NPT 30cm/11.8" 304 Stainless Steel Thermowell Probe for Temperature Sensors by using the STEP BIT 1-3/8". The thermowell whole should be in the center of the bucket. The two holes for the heat exchanger should be a bit off to the side so that the heat sits in the middle of the bucket with the thermowell in its center (place the holes in such a way that it doesn't warp the lid).

To install the thermowell:

Use TEFLON TAPE on the thread of the Thermowell (10 wrappings), add 1/2" WASHER 304 SS onto the thermowell and insert the thermowell into the drilled whole, slip on an O-RING from the bottom and secure with a a 1/2" NPS LOCKNUT STAINLESS.

To assemble the Wort Chiller:

Connect the 1/2" NPT x 3/8" Tube Compression adapter Stainless Steel (SS) to the SS Wort Chiller. After applying TEFLON TAPE (15 wrappings) on the thread connecting to the wort chiller you will need two heavy duty wrenches to compress the compression nut so that there is no leak between the wort chiller and the compression adapter. Watch video how to do it.

Place a 1/2" WASHER 304 SS on the compression adapted, then an O-RING ½ NPT SILCONE, then the buckets plastic lid, then apply Teflon tape to the thread of the compression adapter, use the 1/2" NPS LOCKNUT STAINLESS and/or only the 1/2" SS Camlock D style fitting Quick Disconnect (sometime there may not be space on the thread for the locknut but it feels more secure with it.).

To your garden hose add 1/2" NPT FEMALE x 1/2" hose barb and lock it down with a SS CLAMP - FITS 5/8 OUTER DIAMETER TUBING. Attach the 1/2" SS Camlock F style fitting Quick Disconnect to the hose barb, not forgetting to apply Teflon tape.

Add a Plastic Bracket to prevent kinking of the hose (link) - i found used ones for free.

NOTE: Although the garden hoses can be attached to either end of the Wort Chiller, in order to get an even distribution of temperature, it is important to realize that heat rises and cold sinks, so the bottom of the wort is the coldest and the top is the warmest. So if you are cooling off your wort (summer time) with the Compressor then the Cold water inlet into the Wort Chiller should be from the top of the spiral and the outlet should be from the bottom of the spiral. (If you are heating your wort in the winter have the Warm water inlet should be from the bottom and outlet from the top.)

Orange Water Cooler

The standard home brewing 23Liter Fermentation bucket fits nicely into the 10gallon water coolers, however it doesn't reach the bottom of the water cooler (the buckets get wider towards the top and will get stuck) so you want to find a piece of wood or something to place at the bottom of the orange water cooler so that the Fermentation bucket sits on the wood. Also you can retrofit the wort chiller directly to the lid of the orange bucket but adjustments will need to be made since the lid is much thicker than the fermentation bucket lid.

TEST

At this point your bucket should be read for testing. Run water at a slow rate through the system making sure you don’t have leakage anywhere. This part is tricky and may take some fiddling if you didn't add teflon tape or didn't tighten the joints correctly. You don’t want the water to run too fast since the pump that will be used has a MUCH weaker stream than your tap water.

Note:With the wort chiller inside the fermentation vessel, after boiling your wort you can pour it inside the fermentation bucket and then cool it down with the garden hoses prior to attaching to the this cooling system.

Step 3: Deconstructing a Water Cooler

For this step you want to find a used working appliance that can be stripped for its compressor, Evaporator (copper cooling coils), and Condenser (coils that get hot on outside of fridge). All three items are one piece DON’T take them apart from each other.

For this step I found a used water cooler, the advantage for me here was that the Condenser was small and had a fan and did not take up a lot of space. Taking the water cooler apart took some time and the cooling cooling coils were wrapped around and soldered to the stainless steel water tank. To disassemble it I needed to use a propane torch to melt the solder (if your taking apart a refrigerator the torch won't be necessary).

Since we will be using arduino controller to control the fermentation you won’t need the electronics from the water cooler. Although, if you find a water cooler in which you can digitally set the temperature then this project won’t need an arduino.

Once taken apart i rewrapped the copper cooling coils inside my secondary vessel, here i used an old electric water kettle. This way i will be able to heat the fermentation temperature in the winter and cool it in the summer and have complete control of the fermentation temperature. A bit of the lid from the water kettle needs to be cut away to allow the lid to close with the copper coils and garden hose leading in.

NOTE: When handling the cooling coils you have to be careful not to kink the copper tubing. Use caution and work slowly to rewrap it to the diameter that you need. Furthermore, there are two parts to the cooling coils, the thick tube and a very thin copper tube. You must be very careful not to break this thin copper tube, this is where the cooling effect comes from in the system (high pressure gas to low pressure gas = cooling effect).

In the picture you can see a lot of scrap metal from the water cooler. I actually used the Angle Grinder to cut down the walls of the water cooler so as to encase the compressor and heat exchanger. The water cooler had a shelf which housed the water tank. I kept the shelf and cut down the walls to that height.

Step 4: Electronics Hardware Assembly

My arduino controller is composed of 4 boards.

1. Arduino Uno R3

2. Adafruit RGB LCD

3. Adafruit Data Logger

4. Four channel Relay

The first three are stackable and the Relay is not. The Adafruit RGB LCD comes as a kit and needs to be assembled, this was my first electronics kit and it took some time, but there is a great tutorials on the Adafruit website. The data logger also comes as a kit and has a good tutorial. Remember to install Shield Stacking Headers for Arduino onto the data logger and not the regular ones (its in the bottom part of the tutorial)!

DataLogger

The Data logger has a breadboard design which will allow us to add various components so that we can add a few pushbuttons and temperature sensors. I used two pushbuttons, two digital ds18b20 waterproof temperature sensors (one for the fermentation bucket and one for the secondary vessel inside the kettle), and one analog TMP36 temp sensor for recording the room temperature.

Unfortunately, this data logger's bread board does not feature a Ground and Positive rail and since we will need to connect many things (buttons, resistors, temp senors, relay) to both the Ground and the power (5 Volt) pins I needed to add a copper wire to the underside of the breadboard that acted as a rail for all these connections. NOTE: If i had to do it again i would move those rail's to different locations on the board, see in the side view picture where they interfere with the electronics of the arduino (circled in yellow) [if the rail touches the arduino, the SD card doesn't work], in order to prevent this i had to tape it over.

In addition to the picture of my board, i'm providing a schematic of how my connections are laid out on the Datalogger, which wires and pins are connected to which buttons, temp sensors and relay.

In order to be able to easily disassemble the electronics (between DataLogger and buttons, temp sensors) i am connecting them using the Premium Brass 18-22 (RED) Gauge Male-Female Solderless Crimp Bullet Plug Connectors.

Relay

The Relay is a physical switch that is engaged or disengaged based on the command from the arduino. This is like a light switch when arduino tells it to turn ON the switch is flipped and the 120-220 volts is passed to your appliance. I provide another diagram which 5v pins on the relay are connected to which 5v pins on the arduino and also how to connect the 120-220V compressor, heater, pump to the relay.


Padding to Protect From Compressor Vibrations
Since the compressors gives off micro Vibrations that could damage the fragile electronic circuits i added some soft padding between the arduino and its housing which encases it.

NOTE: Before hard wiring try practicing laying out all the connections on a breadboard before soldering.

NOTE on TMP36: The analog digital senor reads out erratic readings. I recently read here , that this can be smoothed out by adding a (decoupling) capacitor ~0.1uF between the TMP36's VCC and ground. I have not yet done this but will once i get a hold of a capacitor.


Step 5: Wiring Buttons and Temp Probes

Push Buttons

Push buttons have two pins. One pin gets the Ground AND Signal wire , the second pin gets connected to the 5 volts. Solder and Shrink Tube Wrap. Then add Premium Brass 18-22 (RED) Gauge Male-Female Solderless Crimp Bullet Plug Connectors on the other side of the stripped wire..

In the picture you can see that i made a 3 prong connector with the 5 Volts, this is to further reduce the many wires that connect to the Datalogger's bread board. (this won't work for the signal, since each button goes to different Arduino pin, and each Ground from the pushbutton will need its own resistor so this won't work for Ground either)

DS18B20 Temperature Sensors

These have 3 leads, Ground, Signal, and 5 Volt. Instead of using the Crimp connections i use the Female to Male Servo Extension Cable Lead which have 3 wires already. Solder and Shrink Tube Wrap.

Cut 2 Servo Extension cables in half. One half will be attached to the Datalogger board, while the other half will be attached to the temperature sensors (two DS18B20's and one TMP36). Note: i used extra cables to extend the range of the DS18B20.

TMP36

I recommend using a Servo Extension Cable Lead for the TMP36, instead of what i did - 3 separate wires. Connect as in this picture, notice there is directionality. .

Step 6: Code Features and Notes

Before i started with hardware i programed a basic version on the https://circuits.io/ website. Please visit this demo here.

https://circuits.io/circuits/1211086-fermentation-...

Features

  1. Heats or Cools the fermentation vessel.
  2. Adafruit LCD changes colors depending on the state your program is in, (Initial, Monitor, Cool, Heat, Buttons, DataLogger, Sleep)
  3. Pushing Up and Down Button together takes you to the Buttons state where you choose your prefered temperature. You have 10 seconds to choose.

4. The Initial State prevents compressor being turned on and off in case of multiple power failures which can ruin a compressor, the program stays in the Initial state for 15 minutes. Note the temperature will revert to default, which is 20 C. To skip this state push the Up button once.

5. After the fermentation reaches your prefered temperature (setemp), the program goes to a Sleep mode so the compressor isn't cycled on and off. 15 minutes of sleeping

6. While in sleep state or monitor state the pump will cycle on and off as needed to keep your fermentation at desired temperature (compressor/heater will not turn on in sleep mode).

7. Since we don't want the pump inside the kettle/2ndary vessel to boil or freeze there is a default max 30 C and min 1.5 C temps.

8. Sometimes the SD18B20 sensors don't see the digital signal and read errors by showing -127 or +85, if this happens the program does a recheck.

9. Part of the code for conversion to Fahrenheit is inside the program for the analog temp sensor uncomment these lines and change the variables (remove the double forward slashes at the beginning of the lines // ). More code editing will be needed.

10. The program takes up 99% of the memory of the Arduino Uno. If you don't want Serial output for debugging in the below line change 1 to 0 and you will go down to 92% of the memory.

#define ECHO_TO_SERIAL 0

Note DS18B20

The DS18B20 sensors can all be located on one arduino pin. As such each has their unique digital address. To identify the address of each one of your DS18B20 probes see the tutorial on how to obtain these addresses:

// http://www.hacktronics.com/Tutorials/arduino-1-wi...

For example my addresses are

PumpTemp probe address 0x28, 0xEC, 0x33, 0x28, 0x07, 0x00, 0x00, 0x96

FerTemp probe address 0x28, 0xB5, 0x7B, 0x26, 0x07, 0x00, 0x00, 0xEC

Note Code Variable that you can change

Although you don't need to change anything if you do exactly as i did it is good to understand the code if something doesn't work!

  1. setpoint is the default fermentation temperature in celsius = 20.0
  2. minTemp is the minimum temperature that the LCD is allowed to go down to = 1.5 celsius
  3. maxTemp is the maximum temperature that the LCD is allowed to go down to = 30 celsius
  4. Heating element heats the secondary vessel very fast so to not overshoot the setpoint PumpTempOFF is 3 degrees above setpoint and is there to turn off the Heating element when PumpTemp reaches this temp.
  5. ButtonWait variable is now 10000 miliseconds (10 seconds) and can be adjusted to give you more or less time to decide how long to choose a temperature.
  6. blueLED is the compressor and is on Arduino pin 7
  7. redLED is the heating element and is on Arduino pin 5
  8. yellowLED is the pump in the 2ndary vessel/kettle and is on Arduino pin 6
  9. On 30/4/17 i uploaded a code with some more features
    1. if SD card missing program keeps running instead of killing the fermentation in the middle
    2. A hot wort doesn't cause over cooling and then overheating cycles (see line 1007 and 986 of code).

Download and Install the Arduino program to you computer

https://www.arduino.cc/en/Main/Software

Download and Install the libraries

To install a library in Arduino computer program go to Sketch->Include LIbrary-> Add .Zip Library

Adafruit_RGB_LCD_Shield_Library_master - https://github.com/adafruit/Adafruit-RGB-LCD-Shiel...

OneWire - https://github.com/PaulStoffregen/OneWire

RTClib-master (data logger) - https://github.com/adafruit/RTClib

Arduino-Temperature-Control-Library-master - https://github.com/milesburton/Arduino-Temperature...

Step 7: Copy Paste Code

/*--------------------------------------------------------------
Program: DR_Fermentor_V9_Instructables

Program History: Date: 15 April 2012 Author: W.A. Smith, http://startingelectronics.org

Adaptation: Brian Gaude (aka AtomicGecko) Date: 19 September 2015 Description: Adapted W.A. Smith's code to read the voltage from a TMP36 temperature sensor on pin A0 of the Arduino. Converts the voltage to a temperature and displays it on an LCD (16 character by two line). Added code to convert to Fahrenheit. Added code to display a temp setpoint on the LCD. Added code to adjust setpoint with buttons. Added code to compare setpoint value temp and turn on LED if below. set point value. This simulates activating an SSR to control a heating element which would control temperature. https://123d.circuits.io/circuits/1034184

Adaptation: Ilya Pittel Date: Sept 23 2016 Description:

I adapted the above code by Brian to control a beer fermentation vessle and additional code for SwitchCases was adapted from Adam, specifically to add a Sleeping state as detialed by -adam@adambyers.com https://adambyers.com/2014/11/refrigerator-controllerthermostat/

Using Terry's code i got multiple digital DallasTemp sensors to work http://arduino-info.wikispaces.com/Brick-Temperature-DS18B20#mult

Objective:

I will be constructing a fermentation vessel with a heat exchanger (wort chiller) inside it that will keep a steady fermentation temperature.

I will pump hot or cold water through the heat exchanger (wort chiller), depending on the current temperature of the fermenter.

The water will be heated or cooled in a separate secondary vessel by a heating element (symbolized in the sketch by a redLED here) or cooled by a cooling coil/compressor (a BlueLED) (the cooling coils/compressor i got from an old drinking fountain, but a fridge or AC unit will work too). This secondary vessle also has a simple submersible fishtank pump (represented by a yellow light in the scetch) that will pump and circulate the water through the heat exchanger (wort chiller) in the fermenting vessle.

I use an AdaFruit RGB LCD that only needs 4 active pins, but needs a special library. A standard LCD display can probably be used also but i did not explore this option in detail in real life, i did a basic sketch using this option here https://circuits.io/circuits/1211086-fermentation-temperature-control-by-arduino-heating-and-cooling-for-beer-wine-bread-yeast

I have two types of temperature sensors, the simple TMP36 and the waterproof DS18B20. The latter needs the Onewire and the DallasTemperature libraries. I am using 2 DS18B20 temps, one for the fermentation vessle, one for the secondary vessle in which the heating&cooling elements and pump is located (pump will melt if the water starts to boil!) and a TMP36 to measure ambient temp.

There are 7 SwitchCase States (aka states, cases or functions - i use the term interchangebly), Initial, Monitor, Buttons, Sleep, Heat, Cool and DataLogger. All the state show you the temps and set temp and the state that it is in.

To change the set temp (the temp at which you want the fermentation to take place) both buttons up&down must be pressed simoltaneouly. This is to prevent accidental changing of the set temp by a child, dog or enemy.

Each state has its own background color when using the AdaFruit RGB LCD (cooling=Blue, heating=Red, Datalogger=Green, Buttons=Yellow, Monitor=Teal). You have 10 seconds in Buttons state to change the temperature after it goes to the previous state that it was in.

The Initial State is there to prevent compressor burnout in case of a fire outage, to skip it press up&down buttons together. The program goes into sleep state after leaving the cooling state also to prevent the compressor from burning out by turning on an off too frequently. While in sleep state or monitor state the pump can be cycled on and off to keep your fermentation at desired temperature. I added a counting timer while sleeping in the lcd so you know when it will come out of sleep.

The code was tested and works in real life.

Parts list: Arduino Uno R3 - https://www.adafruit.com/products/50

AdaFruit RGB LCD - 2 ports (gnd, 5v, pin analog 4 and analog 5) https://www.adafruit.com/products/714

Adafruit Data Logger - https://www.adafruit.com/products/1141 (rtc clock uses analog pins 4 and 5

4 channel Relay - http://www.sainsmart.com/4-channel-5v-relay-module-for-pic-arm-avr-dsp-arduino-msp430-ttl-logic.html/ This relay works backwards, it is Active on LOW instead of Active on HIGH. So the code below was reversed. If i want a red light to turn on that pin needs to be LOW not High. if using another relay or non at all change back by replacing all the places where the OFFF variable is used with LOW and the ONN variable with HIGH

Temperature Sensors Simple TMP36 for testing - https://www.adafruit.com/products/165 waterproof DS18B20 - https://www.adafruit.com/products/381

Stainless Steel Wort Chiller - http://www.nybrewsupply.com/stainless-steel-wort-chiller-pre-chiller.html

Note: I have tried this in the real world and it works !

// Assign the addresses of your 1-Wire temp sensors. // See the tutorial on how to obtain these addresses: // http://www.hacktronics.com/Tutorials/arduino-1-wire-address-finder.html my PumpTemp address 0x28, 0xEC, 0x33, 0x28, 0x07, 0x00, 0x00, 0x96 Fermentation Vessel FerTemp address 0x28, 0xB5, 0x7B, 0x26, 0x07, 0x00, 0x00, 0xEC

--------------------------------------------------------------*

/for RGBAdafruit LCD include (only 4 pins needed) #include #include #include

// The adafruit RGB LCD shield uses the I2C SCL and SDA pins. On classic Arduinos // this is Analog 4 and 5 so you can't use those for analogRead() anymore // However, you can connect other I2C sensors to the I2C bus and share // the I2C bus. Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color #define RED 0x1 #define YELLOW 0x3 #define GREEN 0x2 #define TEAL 0x6 #define BLUE 0x4 //#define VIOLET 0x5 //#define WHITE 0x7

//for DS18B20 water resistant temp sensor #include #include // Data wire is plugged into pin 2 on the Arduino #define ONE_WIRE_BUS 2

// 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);

//Code for datalogger and RTC Start #include //#include #include "RTClib.h"

// A simple data logger for the Arduino analog pins

// how many milliseconds between grabbing data and logging it. 1000 ms is once a second #define LOG_INTERVAL 60000 // mills between entries (reduce to take more/faster data)

#define ECHO_TO_SERIAL 1 // echo data to serial port - if using datalogger and no attached computer than set to zero will free 10% on program storage and 10% on Gloval variable dynamic memory. #define ECHO_TO_DataLogger 1 //echo data to datalogger - if no datalogger than set to zero.

RTC_DS1307 RTC; // define the Real Time Clock object

// for the data logging shield, we use digital pin 10 for the SD cs line const int chipSelect = 10;

// the logging file File logfile;

void error(char *str) { Serial.print("error: "); Serial.println(str); lcd.setCursor(0, 1); lcd.print("SD Error "); lcd.setBacklight(RED); while (1); } //Code for datalogger and RTC END

//defining the different states #define S_Monitor 0 #define S_Sleep 1 #define S_Heat 2 #define S_Cool 3 #define S_DataLogger 4 #define S_Buttons 5 #define S_Initial 6

int state = S_Initial; // Initial state (welcome splash screen)tell user the filename and time now and in case of power outage defined to stay in this state for Sleeplength/3 to protect compressor. unsigned long Sleep = 0; unsigned long SleepLength = 900000; // (miliseconds)- How long we wait before we try to cool the fermentor again. Use 10 sec for testing should be changed to 900000 = 15 minutes.

int buttonupPin = 8; // the number of the pushbutton up pin int buttondnPin = 9; // the number of the pushbutton down pin int buttonupState = 0; // variable for reading the up pushbutton status int buttondnState = 0; // variable for reading the down pushbutton status

float setpoint = 20.0; // temperature setpoint default float minTemp = 1.5 ; // minimum temperature that the LCD is allowed to go down to - not to burn out the compressor float maxTemp = 30 ; // maximum temperature that the LCD is allowed to go down to - not to burn out the pump in the secondary vessle

int val ; //variable for reading BlueLed pin state, Blue LEd will be the compressor, cooler int blueLEDstate; //variable for keeping track of the blueLed/compressor being on or off

int blueLED = 7; // the compressor cooler will replace the blue Led via SSR relay. int redLED = 5; //the heating element int yellowLED = 6; //yellow is a pump that will ciruclate the heated or cooled water through an exchanger coil in the fermentor.

//So the pump should go on anytime the blueled (cooler) or redled (heater) go on. unsigned long LogTimer = 0; //variable to help regulate when to go to datalogger. float PumpTempOFF = setpoint+3 ; //turn off the Heating when PumpTemp reaches PumpTempOFF example 30 Celcius, or 3 degrees above setpoint.

//unsigned long ButtonTimer = 0 ; //this will keep track of when to go into the Buttons state to check for presses if time goes over ButtonsCheck . unsigned long ButtonWait = 10000 ; // this will give user 10 seconds to change the temperature in this state unsigned long ButtonWaitTimer = 0; // variabile to count the time till maximum of ButtonWait = 10 sec.

int logSend = 1 ; // This will track everytime there is a switch of states between initial, sleeping, monitor, heat and cool. if logSend = 1 it will go to datalogger, if = 0 it will not. // in Datalogger the logSend will recieve value of 0 [and stateTrack (next int) will send it back to the state it came from] and on the second time in the same state since logSend=0, // it will not send to datalogger. int stateTrack ; // variable to keep track of what state we are in so that from Datalogger we can go straight back to the same state without going through Monitor State.

//Multiple DS18B20 Temperature Sensors on 1 wire DeviceAddress PumpTempAddress = { 0x28, 0xEC, 0x33, 0x28, 0x07, 0x00, 0x00, 0x96 }; DeviceAddress FerTempAddress = { 0x28, 0xB5, 0x7B, 0x26, 0x07, 0x00, 0x00, 0xEC };

//Since the SainSmart relay works backwards and ACTIVE when LOW and NOT-Active when High i will make extra variables to make this easier to understand // if the SainSmart relay is not being used replace all OFFF with LOW and ONN with HIGH int OFFF = 1 ; //use these for the redLED, yellowLED, blueLED, int ONN = 0 ;//use these for the redLED, yellowLED, blueLED

void setup() { //Setup loop runs only one time.

Serial.begin(9600); //setup serial communication for debugging.

// initialize the LCD display lcd.begin(16, 2);

digitalWrite (redLED, OFFF); //this prevents the relay being triggered before its time (remember this relay is Active on LOW and not Active on HIGH). digitalWrite (blueLED, OFFF); //this prevents the relay being triggered before its time (remember this relay is Active on LOW and not Active on HIGH). digitalWrite (yellowLED, OFFF); //this prevents the relay being triggered before its time (remember this relay is Active on LOW and not Active on HIGH).

pinMode(redLED, OUTPUT); // heating element pinMode(blueLED, OUTPUT); //compressor for cooling pinMode(yellowLED, OUTPUT); // pump for circulating the water. // initialize the pushbutton pins as an input: pinMode(buttonupPin, INPUT); pinMode(buttondnPin, INPUT); blueLEDstate = digitalRead(blueLED);

// Multiple DS18B20 Temperature Sensors on 1 wire sensors.begin(); // DS18b20 temp sensor IC Default 9 bit. If you have troubles consider upping it 12. Ups the delay giving the IC more time to process the temperature measurement // set the resolution to 10 bit (Can be 9 to 12 bits .. lower is faster) sensors.setResolution(FerTempAddress, 11); sensors.setResolution(PumpTempAddress, 11);

#if ECHO_TO_DataLogger // DataLogger and RTC Code Start // initialize the SD card Serial.print(F("Initializing SD card...")); // make sure that the default chip select pin is set to // output, even if you don't use it: pinMode(10, OUTPUT);

// see if the card is present and can be initialized: if (!SD.begin(chipSelect)) { error("Card failed, or not present"); } Serial.println(F("card initialized."));

// create a new file //char filename[] = "LOG00.CSV"; char filename[] = "LOG00.CSV"; for (uint8_t j = 0; j < 100; j++) { filename[3] = j / 10 + '0'; filename[4] = j % 10 + '0'; if (! SD.exists(filename)) { // only open a new file if it doesn't exist logfile = SD.open(filename, FILE_WRITE); break; // leave the loop! } }

if (! logfile) { error("couldnt create file"); }

Serial.print(F("Logging to: ")); Serial.println(filename);

// connect to RTC Wire.begin(); if (!RTC.begin()) { logfile.println(F("RTC failed")); #if ECHO_TO_SERIAL Serial.println(F("RTC failed")); #endif //ECHO_TO_SERIAL }

logfile.println(F("Date, Time, Ambient Temp, Setpoint, FerTemp, PumpTemp, RedLedstate, BlueLedstate, YellowLedstate, StateTrack ")); #endif //ECHO_TO_DataLogger

} //end loop that runs only once.

uint8_t i = 0; // use buttons on the adafruit lcd display

void loop() { switch (state) { // Switches between states.

case S_Initial: F_Initial(); break;

case S_Monitor: F_Monitor(); // break;

case S_Sleep: // go to function sleep F_Sleep(); break;

case S_Heat: F_Heat(); break;

case S_Cool: F_Cool(); break;

#if ECHO_TO_DataLogger case S_DataLogger: F_DataLogger(); break; #endif //ECHO_TO_DataLogger

case S_Buttons: F_Buttons(); break ;

} }

void F_Initial() //Welcomes and shows date, if not correct then RTC battery dead and needs replacing, also prevent compressor burn out if power outage. { #if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered F_Initial")); Serial.print(F("setpoint is ")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

stateTrack = S_Initial; // send back from datalogger function back here to this state (function).

if (logSend == 1) // only on the first time that the state is changed datalogger should record the change of state, resets at datalogger state. { state = S_DataLogger; }

DateTime now ; now = RTC.now();

lcd.setCursor(0, 0); // print(filename);

if (millis() - Sleep > SleepLength) //here we see how long the program has been in sleep mode by taking the difference between the "Sleep" time { // from above (before going into sleep mode) and now, if it is longer than SleepLength, then come out of sleep and go to monitor.

logSend = 1 ; //prior to switching state logSend is 1 so that when goes to monitor it will log data one time. state = S_Monitor;

}

//lcd.setCursor(9, 0); lcd.setBacklight(TEAL); //adafruit LCDRGB display lcd.print(F("Welcome:")); lcd.print((millis() - Sleep) / 60000); // Count down timer (to change to minutes divide by 60,000 for minutes, for seconds divide by 1000) lcd.print(F(" m/")); lcd.print(SleepLength / 60000); // Counting to a total of SleepLength/3 minutes lcd.print(F("m"));

lcd.setCursor(0, 1); lcd.print(F("Time:")); lcd.setCursor(5, 1); // lcd.print(F("'")); lcd.print(now.year(), DEC); lcd.print(F("/")); lcd.print(now.month(), DEC); lcd.print(F("/")); lcd.print(now.day(), DEC); lcd.print(F(": "));

// read the state of the up pushbutton value: buttonupState = digitalRead(buttonupPin); buttondnState = digitalRead(buttondnPin);

//use buttons on the adafruit lcd display uint8_t buttons = lcd.readButtons(); //use buttons on the adafruit lcd display // check if the up pushbutton is pressed. // if it is, the buttonupState is HIGH: if ((buttonupState == HIGH ) || (buttons & BUTTON_UP )) { logSend = 1 ;//prior to switching state logSend is 1 so that when goes to monitor it will log data one time. state = S_Monitor; }

if ((buttonupState == HIGH & buttondnState == HIGH ) || (buttons & BUTTON_UP && buttons & BUTTON_DOWN)) { logSend = 1 ;//prior to switching state logSend is 1 so that when goes to monitor it will log data one time. state = S_Buttons; } }

void F_Monitor() {

#if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered F_monitor")); Serial.print(F("setpoint is ")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

stateTrack = S_Monitor; //from datalogger it can come back here.

if (logSend == 1) // only on the first time that the state is changed datalogger should record the change of state { state = S_DataLogger; }

buttonupState = digitalRead(buttonupPin); buttondnState = digitalRead(buttondnPin); if (buttonupState == HIGH & buttondnState == HIGH) //go to Button State if up and down buttons pressed together { ButtonWaitTimer = millis(); state = S_Buttons; }

float FerTemp = 0.0; // stores the calculated temperature, float ambTemp = 0.0; float PumpTemp = 0.0; //float temperaturef = 0.0; //temperature in Fahrenheit (if you want to use farenhight code was commented out below)

// Using a simple temperature sensor Tmp36 to measure the ambient temperature ambTemp = ((float)analogRead(A0) * 5.0 / 1024.0) - 0.5; //if you want to use 3.3 V i think it needs to be multiplied by 3.3 not 5 here.....convert A0 value to temperature ambTemp = ambTemp / 0.01; #if ECHO_TO_SERIAL Serial.print(F("Ambtemp: ")); Serial.println(ambTemp); #endif //ECHO_TO_SERIAL

// convert to Fahrenheit T(°F) = T(°C) × 9/5 + 32 //if you want farenhight uncomment these next two lines and use "temperaturef" in teh rest of the code. // temperaturef = ((temperature * 9)/5) + 32;

//DS18B20 Temp commands (if using simple tmp36 sensor comment out these next 3 lines. sensors.requestTemperatures(); // Send the command to get temperatures FerTemp = sensors.getTempC(FerTempAddress); PumpTemp = sensors.getTempC(PumpTempAddress) ;

#if ECHO_TO_SERIAL Serial.print(F("# of Temp Probes found on bus = ")); Serial.println(sensors.getDeviceCount()); Serial.print(F("FerTemp: ")); Serial.println(FerTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire Serial.print(F("PumpTemp: ")); Serial.println(PumpTemp); #endif //ECHO_TO_SERIAL

// display the temperature on the LCD lcd.setBacklight(TEAL); //adafruit LCDRGB display lcd.setCursor(0, 0); lcd.print(FerTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("F:")); lcd.setCursor(6, 0); lcd.print(ambTemp, 1); //the ",1" will print out a rounded number to the nearest tenth. lcd.print(F("A:")); lcd.print(PumpTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("P:")); lcd.setCursor(0, 1); lcd.print(F("Set=")); lcd.print(setpoint, 1); lcd.setCursor(8, 1); lcd.print(F(":Monitor "));

//Serial.println(millis());

// stateTrack = S_Monitor; // maynot need. if ((millis() - LogTimer) > LOG_INTERVAL ) //go to datalogger if greater time went by then the LOG_INTERVAL { state = S_DataLogger; } //else // compare temp with setpoint off by more than 1 degree then heat.

if (PumpTemp > 85 || PumpTemp < -65) //sometimes this sensor gives communication problems so its good to request a new reading. { PumpTemp = sensors.getTempC(PumpTempAddress) ; delay(3000); } if (FerTemp > 85 || FerTemp < -65) //sometimes this sensor gives communication problems so its good to request a new reading. { FerTemp = sensors.getTempC(FerTempAddress) ; delay(3000); } if (FerTemp <= setpoint - 1) { logSend = 1; //so that upon entering Heating it will go to log one time. state = S_Heat; }

// check temp.//only cool state sends to sleep mode, so in this case turning on pump won't affect if generally i'm heating. if (FerTemp > setpoint && PumpTemp < setpoint) //even in sleep the pump can be turned on to cool the fermentation temp. {digitalWrite(yellowLED, ONN);//turn on pump // lcd.setCursor(15, 0); // lcd.print("P"); // delay(300); // lcd.setCursor(15, 0); // lcd.print("-"); } //new change should turn off the pump if its temp goes over setpoint, or if Fermtemp goes equals or goes below setpoint) if (FerTemp < setpoint-0.3 || PumpTemp > setpoint) //in case the pump was enough to cool down the FerTemp digitalWrite(yellowLED, OFFF);//turn off pump if (FerTemp >= setpoint + 1 && PumpTemp > setpoint) // If TempC is more than 1 degree above setpoint turn on cooler/compressor, this can be chanegd to 0.5 or just any temp > setpoint. { logSend = 1; state = S_Cool; //so that upon entering cooling it will go to log one time. } }

void F_Buttons() //to enter this state both buttons must be pressed at once. This is so that a child could not accidentally change your set temp. {

#if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered F_Buttons")); #endif //ECHO_TO_SERIAL

lcd.setBacklight(YELLOW); //adafruit LCDRGB display lcd.setCursor(0, 0); lcd.print("Temp: Up or Down?"); //first line asks to raise or lower temp

lcd.setCursor(0, 1); lcd.print(F("Set= ")); lcd.print(setpoint, 1); //shows current set temp lcd.print(F(" ")); lcd.print((millis() - ButtonWaitTimer) / 1000); // Countdown Timer in seconds lcd.print(F("s/")); lcd.print(ButtonWait / 1000); // Counter, maximum lcd.print(F("s"));

//use buttons on the adafruit lcd display uint8_t buttons = lcd.readButtons(); //use buttons on the adafruit lcd display

buttonupState = digitalRead(buttonupPin); // check if the up pushbutton is pressed.

if ((buttons & BUTTON_UP && setpoint < maxTemp) || (buttonupState == HIGH && setpoint < maxTemp)) //checks Adafruit buttons OR external pushbuttons) { setpoint = setpoint + 0.5; //temp at which you want the beer to ferment at.

lcd.setCursor(0, 1); lcd.print(F("Set= ")); lcd.print(setpoint, 1); lcd.print(F(" "));

#if ECHO_TO_SERIAL Serial.print(F("Button Up, setpoint is ")); Serial.println(setpoint, 1); #endif //ECHO_TO_SERIAL

}

buttondnState = digitalRead(buttondnPin); // check if the down pushbutton is pressed.if it is, the buttondnState is HIGH:

if ((buttons & BUTTON_DOWN && setpoint > minTemp) || (buttondnState == HIGH && setpoint > minTemp)) //checks Adafruit buttons OR external pushbuttons)

{ // subtract -1 to setpoint: setpoint = setpoint - 0.5; lcd.setCursor(0, 1); lcd.print(F("Set= ")); lcd.print(setpoint, 1); lcd.print(F(" "));

#if ECHO_TO_SERIAL Serial.print(F("Button DOWN, setpoint is")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

}

// stateTrack = S_Monitor; // maynot need. if ((millis() - ButtonWaitTimer) > ButtonWait ) // leave ButtonState if longer then ButtonWait (10 sec) goes by { ButtonWaitTimer = millis(); //state = S_Monitor; state = stateTrack; } }

void F_Sleep() {

#if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered F_Sleep")); Serial.print(F("setpoint is ")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

stateTrack = S_Sleep; // maynot need.

digitalWrite(blueLED, OFFF); // Shut off the compressor blueLEDstate = OFFF ; digitalWrite(redLED, OFFF); //turn off heating element

if (logSend == 1) // only on the first time that the state is changed datalogger should record the change of state { state = S_DataLogger; }

float FerTemp = 0.0; // stores the calculated temperature float PumpTemp = 0.0; sensors.requestTemperatures(); // Send the command to get temperatures FerTemp = sensors.getTempC(FerTempAddress); PumpTemp = sensors.getTempC(PumpTempAddress) ;

// check temp.//only cool state sends to sleep mode, so in this case turning on pump won't affect if generally i'm heating. if (FerTemp > setpoint && PumpTemp < setpoint) //even in sleep the pump can be turned on to cool the fermentation temp. {digitalWrite(yellowLED, ONN);//turn on pump // lcd.setCursor(15, 0); // lcd.print("P"); // delay(300); // lcd.setCursor(15, 0); // lcd.print("-"); } if (FerTemp < setpoint-0.3 || PumpTemp > setpoint) //in case the pump was enough to cool down the FerTemp digitalWrite(yellowLED, OFFF);//turn off pump

buttonupState = digitalRead(buttonupPin); buttondnState = digitalRead(buttondnPin); if (buttonupState == HIGH & buttondnState == HIGH) //go to Button State when both up and down button pressed { ButtonWaitTimer = millis(); state = S_Buttons; }

// float ambTemp =0.0; // ambTemp = ((float)analogRead(A0) * 5.0 / 1024.0) - 0.5; //if you want to use 3.3 V i think it needs to be multiplied by 3.3 not 5 here.....convert A0 value to temperature // ambTemp = ambTemp / 0.01;

//use buttons on the adafruit lcd display uint8_t buttons = lcd.readButtons(); //use buttons on the adafruit lcd display // check if the up pushbutton is pressed. // if it is, the buttonupState is HIGH: if ((buttonupState == HIGH & buttondnState == HIGH ) || (buttons & BUTTON_UP && buttons & BUTTON_DOWN)) { ButtonWaitTimer = millis(); state = S_Buttons; }

// display the state and count down to monitor state on the LCD lcd.setBacklight(TEAL); //adafruit LCDRGB display lcd.setCursor(0, 0); lcd.print(F("Sleep: ")); lcd.print((millis() - Sleep) / 60000); //countdown timer (to change to minutes divide by 60,000 for minutes, for seconds divide by 1000 lcd.print(F(" m/")); lcd.print(SleepLength / 60000) ; // Counting out of Sleeplength minutes) lcd.print(F("m: ")); // change to minutse m/"SleepLength"m // display the temperature on the LCD lcd.setCursor(0, 1); lcd.print(FerTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("F::"));

lcd.setCursor(7, 1); lcd.print(F("Set=")); lcd.print(setpoint, 1); lcd.print(F(" "));

//{ #if ECHO_TO_SERIAL Serial.print(F("Sleeping ")); //change to minutes as per SleepLength Serial.print((millis() - Sleep) / 1000); Serial.println(F(" sec out of 10 seconds")); #endif //ECHO_TO_SERIAL

// }

//stateTrack = S_Sleep ; //maynot need

if ((millis() - LogTimer) > LOG_INTERVAL ) //go to datalogger if greater time went by then the LOG_INTERVAL { state = S_DataLogger; }

// Serial.println(millis()); //if you want to keep track of milliseconds passing since program started. if (millis() - Sleep > SleepLength) //here we see how long the program has been in sleep mode by taking the difference between the "Sleep" time { // from above (before going into sleep mode) and now, if it is longer than SleepLength, then come out of sleep and go to monitor. logSend = 1; state = S_Monitor; }

}

void F_Heat() { #if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered F_heat")); Serial.print(F("setpoint is ")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

digitalWrite(blueLED, OFFF); blueLEDstate = OFFF;

stateTrack = S_Heat; // maynot need.

buttonupState = digitalRead(buttonupPin); buttondnState = digitalRead(buttondnPin); if (buttonupState == HIGH & buttondnState == HIGH) //go to Button State when up and down buttons are pressed together { ButtonWaitTimer = millis(); state = S_Buttons; }

/* lcd.setCursor(15, 0); //blink a H at the top right corner of the LCD to indicate that the heating element is working. if (digitalRead(redLED)== ONN) //if heating element is on then flash the H, if not, i.e. only pump is on dont' flash it { lcd.print("H"); delay(300); lcd.setCursor(13, 0); lcd.print(" -"); } else lcd.setCursor(13, 0); lcd.print(F("OFF")); */ //use buttons on the adafruit lcd display uint8_t buttons = lcd.readButtons(); //use buttons on the adafruit lcd display // check if the up pushbutton is pressed. // if it is, the buttonupState is HIGH: if ((buttonupState == HIGH & buttondnState == HIGH ) || (buttons & BUTTON_UP && buttons & BUTTON_DOWN)) { ButtonWaitTimer = millis(); state = S_Buttons; } { #if ECHO_TO_SERIAL Serial.println(F("Blue Led OFF")); if (digitalRead(redLED) == ONN) { Serial.print(F("redLED ON")); //because fo the relay code this needs reversing. } if (digitalRead(redLED) == OFFF) { Serial.print(F("redLED OFF")); } // Serial.println(F("Red Led Turned On")); Serial.println(F("Pump on")); #endif //ECHO_TO_SERIAL

} float FerTemp = 0.0; // stores the calculated temperature, float ambTemp = 0.0; float PumpTemp = 0.0; //float temperaturef = 0.0; //temperature in Fahrenheit (if you want to use farenhight code was commented out below)

// Using a simple temperature sensor Tmp36 to measure the ambient temperature ambTemp = ((float)analogRead(A0) * 5.0 / 1024.0) - 0.5; //if you want to use 3.3 V i think it needs to be multiplied by 3.3 not 5 here.....convert A0 value to temperature ambTemp = ambTemp / 0.01; #if ECHO_TO_SERIAL Serial.print(F("Ambtemp: ")); Serial.println(ambTemp); #endif //ECHO_TO_SERIAL

// convert to Fahrenheit T(°F) = T(°C) × 9/5 + 32 //if you want farenhight uncomment these next two lines and use "temperaturef" in teh rest of the code. // temperaturef = ((temperature * 9)/5) + 32;

//DS18B20 Temp compands sensors.requestTemperatures(); // Send the command to get temperatures FerTemp = sensors.getTempC(FerTempAddress); PumpTemp = sensors.getTempC(PumpTempAddress) ;

#if ECHO_TO_SERIAL Serial.print(F("FerTemp: ")); Serial.println(FerTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire Serial.print(F("PumpTemp: ")); Serial.println(PumpTemp); #endif //ECHO_TO_SERIAL

// display the temperature on the LCD // display the temperature on the LCD lcd.setBacklight(RED); //adafruit command for RGB display lcd.setCursor(0, 0); lcd.print(FerTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("F:")); lcd.setCursor(6, 0); lcd.print(ambTemp, 1); //the ",1" will print out a rounded number to the nearest tenth. lcd.print(F("A:")); lcd.print(PumpTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("P:")); lcd.setCursor(0, 1); lcd.print(F("Set=")); lcd.print(setpoint, 1);

// stateTrack = S_Heat; // may not need

if ((millis() - LogTimer) > LOG_INTERVAL ) //go to datalogger if greater time went by then the LOG_INTERVAL { state = S_DataLogger; }

if (PumpTemp > PumpTempOFF) // If the PumpTemp: greater than the cut off it turns off the heating but keeps pumping. { digitalWrite(redLED, OFFF) ;// Turn heat off when reaches pumptempoff

if (FerTemp < setpoint) //if fermentation temp is smaller than setpoint then

digitalWrite(yellowLED, ONN); //start pumping lcd.setCursor(8, 1); lcd.print(F(":Pumping ")); }

if (FerTemp < setpoint && PumpTemp < PumpTempOFF-1.5) //the heating element will turn on and off rapidly if the pump is cooling off the pumptemp from the fermentor //and then it turns on again, then off since its a difference off 0.2C, so changed from PumpTempOFF to PumpTempOFF-1.5, this gives smooth transition { digitalWrite(redLED, ONN) ; // if tempC is 1 degree below setpoint turn on REDLED/heating element digitalWrite(yellowLED, ONN); //start pumping lcd.setCursor(8, 1); lcd.print(F(":Heating ")); if (logSend == 1) // only on the first time that the state is changed datalogger should record the change of state { state = S_DataLogger; } } if (FerTemp >= setpoint +0.5) { digitalWrite(redLED, OFFF) ;// Turn heat off when reaches temp digitalWrite(yellowLED, OFFF); //Turn off pump logSend = 1; state = S_Monitor; }

}

void F_Cool() { #if ECHO_TO_SERIAL //Serial.println(); Serial.println(F("entered F_Cool")); Serial.print(F("setpoint is ")); Serial.println(setpoint); #endif //ECHO_TO_SERIAL

digitalWrite(redLED, OFFF);

stateTrack = S_Cool; // maynot need.

buttonupState = digitalRead(buttonupPin); buttondnState = digitalRead(buttondnPin); if (buttonupState == HIGH & buttondnState == HIGH) //go to Button State if up and down buttons are pressed together { ButtonWaitTimer = millis(); state = S_Buttons; }

/* lcd.setCursor(15, 0); //blink a C at the top right corner of the LCD to indicate that the Cooling element is working. lcd.print("C"); delay(300); lcd.setCursor(15, 0); lcd.print("-"); */

//use buttons on the adafruit lcd display uint8_t buttons = lcd.readButtons(); //use buttons on the adafruit lcd display // check if the up pushbutton is pressed. // if it is, the buttonupState is HIGH: if ((buttonupState == HIGH & buttondnState == HIGH ) || (buttons & BUTTON_UP && buttons & BUTTON_DOWN)) { ButtonWaitTimer = millis(); state = S_Buttons; }

#if ECHO_TO_SERIAL Serial.println(F("Blue Led ON")); #endif //ECHO_TO_SERIAL

float FerTemp = 0.0; // stores the calculated temperature, float ambTemp = 0.0; float PumpTemp = 0.0; //float temperaturef = 0.0; //temperature in Fahrenheit (if you want to use farenhight code was commented out below)

// Using a simple temperature sensor Tmp36 to measure the ambient temperature ambTemp = ((float)analogRead(A0) * 5.0 / 1024.0) - 0.5; //if you want to use 3.3 V i think it needs to be multiplied by 3.3 not 5 here.....convert A0 value to temperature ambTemp = ambTemp / 0.01;

#if ECHO_TO_SERIAL Serial.print(F("Ambtemp: ")); Serial.println(ambTemp); #endif //ECHO_TO_SERIAL

// convert to Fahrenheit T(°F) = T(°C) × 9/5 + 32 //if you want farenhight uncomment these next two lines and use "temperaturef" in teh rest of the code. // temperaturef = ((temperature * 9)/5) + 32;

//DS18B20 Temp commands, if using simple tmp36 sensor comment out the next 3 lines. sensors.requestTemperatures(); // Send the command to get temperatures FerTemp = sensors.getTempC(FerTempAddress); PumpTemp = sensors.getTempC(PumpTempAddress) ;

#if ECHO_TO_SERIAL Serial.print(F("FerTemp: ")); Serial.println(FerTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire Serial.print(F("PumpTemp: ")); Serial.println(PumpTemp); #endif //ECHO_TO_SERIAL

// display the temperature on the LCD lcd.setBacklight(BLUE); //adafruit LCD RGB display lcd.setCursor(0, 0); lcd.print(FerTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("F:")); lcd.setCursor(6, 0); lcd.print(ambTemp, 1); //the ",1" will print out a rounded number to the nearest tenth. lcd.print(F("A:")); lcd.print(PumpTemp, 1); //to use the simple temp sensor change "sensors.getTempCByIndex(0)" to "temperature" lcd.print(F("P:")); lcd.setCursor(0, 1); lcd.print(F("Set=")); lcd.print(setpoint, 1); lcd.setCursor(8, 1); lcd.print(F(":Cooling "));

if ((millis() - LogTimer) > LOG_INTERVAL ) //go to datalogger if greater time went by then the LOG_INTERVAL { state = S_DataLogger; }

if (logSend == 1) // only on the first time that the state is changed datalogger should record the change of state { state = S_DataLogger; }

// check temp. if (FerTemp > setpoint && PumpTemp < setpoint) digitalWrite(yellowLED, ONN);//turn on pump

if (PumpTemp > setpoint) //before turning on the pump, drop the pumptemp below setpoit, so that Fertemp doesn't go up. { digitalWrite(blueLED, ONN); //if tempC is above setpoint+1 turn on BlueLED/cooling element blueLEDstate = ONN; } if (FerTemp > setpoint && PumpTemp < setpoint && PumpTemp > minTemp) //once the PumpTemp: below setpoint now you can begin cooling { digitalWrite(blueLED, ONN); //if tempC is above setpoint+1 turn on BlueLED/cooling element blueLEDstate = ONN; digitalWrite(yellowLED, ONN);//turn on pump }

if (FerTemp <= setpoint - 0.5) { digitalWrite(blueLED, OFFF); digitalWrite(yellowLED, OFFF); Sleep = millis(); logSend = 1; state = S_Sleep; } }

#if ECHO_TO_DataLogger void F_DataLogger () { DateTime now; #if ECHO_TO_SERIAL // Serial.println(); Serial.println(F("entered Datalogger")); #endif //ECHO_TO_SERIAL

lcd.setBacklight(GREEN); lcd.setCursor(0, 0); lcd.print(F("F-DataLogger ")); lcd.setCursor(0, 1); lcd.print(" "); //for a smooth visual transition. delay (500);

logSend = 0; //resets the logSend.

float FerTemp = 0.00; // stores the calculated temperature, float ambTemp = 0.00; float PumpTemp = 0.00; //float temperaturef = 0.00; //temperature in Fahrenheit (if you want to use farenhight code was commented out below)

// Using a simple temperature sensor Tmp36 to measure the ambient temperature ambTemp = ((float)analogRead(A0) * 5.0 / 1024.0) - 0.5; //if you want to use 3.3 V i think it needs to be multiplied by 3.3 not 5 here.....convert A0 value to temperature ambTemp = ambTemp / 0.01; #if ECHO_TO_SERIAL Serial.print(F("Ambtemp: ")); Serial.println(ambTemp); #endif //ECHO_TO_SERIAL

// convert to Fahrenheit T(°F) = T(°C) × 9/5 + 32 //if you want farenhight uncomment these next two lines and use "temperaturef" in teh rest of the code. // temperaturef = ((temperature * 9)/5) + 32;

//DS18B20 Temp commands sensors.requestTemperatures(); // Send the command to get temperatures FerTemp = sensors.getTempC(FerTempAddress); PumpTemp = sensors.getTempC(PumpTempAddress) ;

#if ECHO_TO_SERIAL Serial.print(F("FerTemp: ")); Serial.println(FerTemp); // Why "byIndex"? You can have more than one IC on the same bus. 0 refers to the first IC on the wire Serial.print(F("PumpTemp:")); Serial.println(PumpTemp); #endif //ECHO_TO_SERIAL

// fetch the time now = RTC.now(); //log time //logfile.print(now.unixtime()); // seconds since 1/1/1970 //logfile.print(F(", ")); logfile.print(now.year(), DEC); logfile.print(F("/")); logfile.print(now.month(), DEC); logfile.print(F("/")); logfile.print(now.day(), DEC); logfile.print(F(", ")); //comma after date logfile.print(now.hour(), DEC); logfile.print(F(":")); logfile.print(now.minute(), DEC); logfile.print(F(":")); logfile.print(now.second(), DEC);

#if ECHO_TO_SERIAL // Serial.print(now.unixtime()); // seconds since 1/1/1970 // Serial.print(F(", ")); Serial.println(F("datetime, Ambient Temp, Setpoint, FerTemp, PumpTemp, redLEDstate, BlueLedState, YellowLedstate ")); Serial.print(F("'")); Serial.print(now.year(), DEC); Serial.print(F("/")); Serial.print(now.month(), DEC); Serial.print(F("/")); Serial.print(now.day(), DEC); Serial.print(F(" ")); Serial.print(now.hour(), DEC); Serial.print(F(":")); Serial.print(now.minute(), DEC); Serial.print(F(":")); Serial.print(now.second(), DEC); Serial.print(F("'")); #endif //ECHO_TO_SERIAL

//"datetime, Ambient Temp, Setpoint, FerTemp, PumpTemp, redLEDstate, BlueLedstate, YellowLedstate "

logfile.print(F(", ")); //comma after dateTime logfile.print(ambTemp); logfile.print(F(", ")); logfile.print(setpoint); logfile.print(F(", ")); logfile.print(FerTemp); logfile.print(F(", ")); logfile.print(PumpTemp); logfile.print(F(", ")); if (digitalRead(redLED) == ONN) { logfile.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(redLED) == OFFF) { logfile.print(F("OFF")); } // logfile.print(digitalRead(redLED)); logfile.print(F(", ")); if (digitalRead(blueLED) == ONN) { logfile.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(blueLED) == OFFF) { logfile.print(F("OFF")); } // logfile.print(digitalRead(blueLED)); logfile.print(F(", ")); if (digitalRead(yellowLED) == ONN) { logfile.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(yellowLED) == OFFF) { logfile.print(F("OFF")); } // logfile.print(digitalRead(yellowLED)); logfile.print(F(", ")); logfile.print(stateTrack); logfile.println(); #if ECHO_TO_SERIAL Serial.print(F(", ")); //comma after dateTime Serial.print(ambTemp); Serial.print(F(", ")); Serial.print(setpoint); Serial.print(F(", ")); Serial.print(FerTemp); Serial.print(F(", ")); Serial.print(PumpTemp); Serial.print(F(", ")); if (digitalRead(redLED) == ONN) { Serial.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(redLED) == OFFF) { Serial.print(F("OFF")); } //Serial.print(digitalRead(redLED)); //reads if redLED is a ON (1) or OFF(0). Serial.print(F(", ")); if (digitalRead(blueLED) == ONN) { Serial.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(blueLED) == OFFF) { Serial.print(F("OFF")); } //Serial.print(digitalRead(blueLED)); //reads if blueLED is a ON (1) or OFF(0). Serial.print(F(", ")); if (digitalRead(yellowLED) == ONN) { Serial.print(F("ON")); //because fo the relay code this needs reversing. } if (digitalRead(yellowLED) == OFFF) { Serial.print(F("OFF")); } //Serial.print(digitalRead(yellowLED));//reads if YellowLED is a ON (1) or OFF(0). // Serial.println(); Serial.print(F(", ")); Serial.print(stateTrack); #endif // ECHO_TO_SERIAL

// blink LED to show we are syncing data to the card & updating FAT!

logfile.flush();

lcd.setCursor(0, 1); lcd.print(F("SD Sync:")); lcd.setCursor(8, 1); lcd.print(now.month(), DEC); lcd.print(F("/")); lcd.print(now.day(), DEC); lcd.print(F(" ")); /* lcd.print(F(" ")); lcd.print(now.hour(), DEC); lcd.print(F(":")); lcd.print(now.minute(), DEC); lcd.print(F(":")); lcd.print(now.second(), DEC); lcd.print(F(" ' "));

*/ delay(500); //clear the bottom of the screen lcd.setCursor(0, 1); lcd.print(F(" ")); // #if ECHO_TO_SERIAL // Serial.println(); // Serial.print("millis are: "); // #endif //ECHO_TO_SERIAL

LogTimer = millis();

state = stateTrack; } #endif //ECHO_TO_DataLogger

Step 8:

LCD Display

LCD Outputs the Temperature in Celsius

1st Line Reads: '46.0 degrees in the Fermentation Vessel : 25.7Ambient Temp : 37.0Pump Temperature Vessel/Electric Kettle'

2nd Line: 'Set temperature is 17 degrees and the unit is Cooling by having the compressor ON.

Note: The picture of the LCD was taken at start up of the system. After the wort was boiled, cooled in the bathtub with tap water to 46 C and hooked up to the system, which successfully cooled the wort to the target 17 C. Then yeast was pitched.

Results of datalogger

When the temp goes 1 Celsius above Setpoint the compressor and pump start working and bring the temp down to 0.5 C below setpoint and then turns off and goes into 15 minute sleep mode and from there to monitor mode. So the fermentation stays within 1.5 degrees of the target range.

Note: the Orange HomeDeport Water Cooler helps to keep the fermentation bucket insulated from ambient temperature swings and therefore the compressor only works 10% of the time. Without the insulation it would be significantly higher.