Archive for the ‘Food’ Category

Precision Fermentation: Arduino-Controlled Crock Pot Yogurt Maker

Friday, April 9th, 2010

What

Plug the Crockpot into the Relay, the Relay into a Power Source

Arduino-Controlled Crock Pot Yogurt Maker.

A homemade thermostat attachment for a crock pot (or other electric heating device). It can be used to precisely control the temperature of the crock pot for things like yogurt fermentation.

Why

  • Making yogurt is fun. It is also way cheaper than buying it.
  • You can make yogurt on the stove or in an oven, however it is fairly temperature-sensitive.
  • I am lazy. I don’t like waiting around for the milk to sterilize; I usually end up burning it.
  • I wanted to experiment with Arduino microcontroller programming (and Fritzing) to give myself a high degree of control over my fermentation process.

Why Not

  • Electric yogurt makers already exist.
    • True, but many yogurt makers only incubate; the heating/sterilization has to be done by you on the stovetop.
  • You can also buy crock pots with thermostats.
    • I know, wanna buy me one?
  • You can buy yogurt in the store.
    • Yup. This is something I make for fun anyway. I figure I may as well combine it with other fun things I like.
  • This is boring.

How

The crock pot is attached to a relay that can switch it on or off. The relay is toggled by a microcontroller based on readings from a temperature sensor (thermistor) placed inside the crock pot.

Here’s the basic yogurt recipe (and the detailed version):

  1. Mix 1/2 gallon milk with 1 package of dry powdered milk. (This is optional, but it adds nutritional value and makes the yogurt thicker).
  2. Heat the milk to 185°F (85°C). This kills off microbes to make way for our yogurt cultures, and denatures enzymes in the milk that may interfere with yogurt culture growth.
  3. Cool the milk to 110°F (43°C). Add 2 tablespoons of already-made yogurt with active cultures, or yogurt starter.
  4. In a sealed container, ferment the yogurt for 7+ hours by keeping it as close to 100°F (38°C) as possible.

Doing things the old-fashioned way, I’d be using a stovetop and candy thermometer for steps 2 and 3, then a warm oven or a radiator for step 4. That takes a lot of attention, and uses more containers than I care to wash later. Many commercially-sold yogurt makers still require you to perform step 2 yourself.

With the Arduino setup, I use a few canning jars in a water bath inside the crock pot to ensure even heating. I submerge  the temperature sensor in the water. A voltage divider circuit is used to indirectly measure the resistance of the thermistor. In the code, I make use of the Steinhart-Hart Thermistor Equation to translate the thermistor’s resistance into temperature. This gives a pretty good idea of the temperature inside the crock pot.

In addition to the thermistor’s resistance at a given time, the Steinhart-Hart equation needs to be fed three coefficients which can be calculated from information on the  manufacturer’s data sheet based on predetermined resistances at different temperatures. Since we’ll be measuring a range between 100°F (38°C) and 185°F (85°C), I used resistance values measured at 86°F (30°C), 140°F (60°C) and 194°F (90°C) to calculate my coefficients. Here’s a simple calculator to calculate your coefficients from a given temp/resistance range, and here’s a more in-depth explanation of the math involved.

Construction

Parts list:

Thermistor Preparation

Since the temperature readings will be taken in the water bath, we need a way to keep the thermistor from getting wet. Here’s a good reference for constructing a waterproof sensor.

Waterproof Thermistor.

Waterproof Thermistor.

I constructed my waterproof thermistor by first soldering the thermistor onto two long (~3′) lead wires, then wrapping the exposed wires with heat-shrink tubing. I slid a short length of aluminum tubing over the sensor, then used moisture-resistant shrink tubing to seal both ends. You could also use epoxy.

Relay Construction

Sparkfun.com has a great tutorial for constructing a 120V relay outlet specifically for this relay board. I recommend reading over their instructions.

Yogurt Maker Relay Assembly

Yogurt Maker Relay Assembly.

I deviated slightly by using a female connector instead of a GFCI outlet. Use the extension cord’s male end with about 12″-14″ of cord attached. Expose the three extension cord wires in the middle of the cord’s length. Cut the black wire and solder the ends to the relay board’s Load 1 and Load 2 connections. This is where the line voltage toggles on and off.

Relay Assembly: Black Wire is Soldered to Relay Board

Relay Assembly: Black Wire is Soldered to Relay Board.

The three wires on the end of the cord attach to the female connector. The extension cord’s green/blue wire attaches to the green screw terminal. This is the ground wire. The black and white wires attach to the other two screw terminals. Use a connectivity tester to make sure that the larger slot receptacle is connected to the larger prong on the plug.

Female Connector Assembly: Green/Blue Wire Attaches to Green/Ground Screw.

Female Connector Assembly: Green/Blue Wire Attaches to Green/Ground Screw.

Be careful when working with line voltage, as it can kill you. The solder points on the relay are basically live exposed wires, so use a project enclosure of some sort to keep the relay from accidentally being touched. I used a discarded plastic petroleum jelly container.

Circuit Building

Diagrams and schematics for the Arduino-controlled crockpot yogurt maker are shown below. If you haven’t yet, I’d also recommend checking out Fritzing, an open-source, cross-platform development a tool that allows users to document and share their electronics prototypes.

fritzing_icon

Yogurt Maker Fritzing Diagram & Schematic.

Fritzing offers a visual mode that allows circuits to be documented as they look in real life. This is a great feature for those of us who aren’t electrical engineers, or are just visual thinkers. The best part is that the visual mode is linked to a schematic drawn with traditional electronics symbols, which can really help electronics newbs to see the translation between visual circuit layout and schematic layout.

yogurt_maker_breadboard

Yogurt Maker Circuitry Layout.

Yogurt Maker Schematic.

Yogurt Maker Circuitry Schematic.

Code

 /*
 Arduino-controlled crockpot thermostat, aka DIY yogurt maker.
 By Chris Reilly

http://www.rainbowlazer.com

 This program controls a relay that switches on an electric heating element (eg, crockpot) to control temperature for fermentation processes.
 The relay state is set based on readings from a thermistor which approximate the temperature of the heating element.
 The setup() function controls the stages of temperature for making yogurt.
 After adding powdered nonfat milk to liquid milk in glass canning containers, a water bath is set up in the heater, and the thermistor is placed in the bath.

 Stage 1 heats milk to 185F, to sterilize & denature enzymes in the milk.
 During this stage it's useful to cover the heating element with insulation such as towels to allow for faster/more efficient heating.
 The temp will hold at 185F for ten minutes, then stage one ends.
 At the end of stage one, the buzzer will signal for one minute.

Stage 2 cools the milk to 110F. During this stage it's useful to remove the cover and insulation from the heating element to allow for faster cooling.
As soon as the temp reaches 110F, the buzzer will signal. The temp will hold at 110F for ten minutes, then stage two ends.
Yogurt or starter culture is added and containers sealed at the end of stage 2.

Stage 3 incubates the yogurt at 100F for 8 hours. This time can be increased to taste and will result in more sour yogurt.
After holding, the heating element will be shut off. At the end of stage 3, the alarm will sound for 10 minutes, at which point the yogurt containers should be refrigerated.

 The serial monitor can be used for temperature readouts and feedback on what the program is doing.
 */

 #include <EEPROM.h>
 #include <math.h>

 // These constants won't change:
 const int sensorPin = 0;     // pin that the sensor is attached to
 const int relayPin = 13;    //pin that turns the relay on or off
 const int buzzerPin = 9;    //pin that activates the piezo buzzer
 const int buttonPin = 12;    //pin that activates the piezo buzzer

 //do a better job of getting temp
 double thermistor_read(int sensorVal) { 

     //Vout = Vin * (R2/(R1 + R2)) = analogread
     double R2 = 10000; //the other (non-thermistor) resistor in our voltage divider
     double R1;  //the resistance of\the thermistor (this will be calculated from the analog-to-digital conversion taken at the sensor pin)
     double temp;     //temp will be calculated using the Steinhart-Hart thermistor equation
     double Vin = 4.6;  //reference voltage that we get from the board

     double Vout = sensorVal * (Vin/1024); //convert the ADC reading from the analog pin into a voltage. We'll need this to calculate the thermistor's resistance next

     R1 = ((R2 * Vin) / (Vout)) - R2; //calculate resistance from the analogread value. See this page for more info: http://en.wikipedia.org/wiki/Voltage_divider

     temp = 1 / (0.0011690592 + 0.00023090243 * log(R1) + .000000074484724 * pow(log(R1), 3));
     //Steinhart-Hart thermistor equation, using coefficients calculated from the manufacturere's data sheet,
     //and the calculator found here: http://www.capgo.com/Resources/Temperature/Thermistor/ThermistorCalc.html
     //this gives us temperature in Kelvin

     temp = temp - 273.15;            // Convert Kelvin to Celcius
     temp = (temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit

     return temp;
 }

//convert millis to readable hours:mins:seconds
void timestamp(unsigned long milliseconds) {
   int seconds = milliseconds / 1000;
   int minutes = seconds / 60;
   int hours = minutes / 60;

   seconds = seconds % 60;
   minutes = minutes % 60;

   if (hours < 10)
     Serial.print("0");
   Serial.print(hours);
   Serial.print(":");

   if (minutes < 10)
     Serial.print("0");
   Serial.print(minutes);

   Serial.print(":");

   if (seconds < 10)      Serial.print("0");    Serial.print(seconds);  }  void printDouble(double val, byte precision) {   // prints val with number of decimal places determine by precision   // precision is a number from 0 to 6 indicating the desired decimal places   // example: printDouble(3.1415, 2); // prints 3.14 (two decimal places)   Serial.print (int(val));  //prints the int part   if( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac, mult = 1;
    byte padding = precision -1;
    while(precision--) mult *=10;
    if(val >= 0) frac = (val - int(val)) * mult; else frac = (int(val) - val) * mult;
    unsigned long frac1 = frac;
    while(frac1 /= 10) padding--;
    while(padding--) Serial.print("0");
    Serial.print(frac,DEC) ;
  }
}

 //get to a specified temperature and hold
 //go_to_temp(target temp in F, duration in seconds to hold target temp for, whether to beep during hold time)
 boolean go_to_temp(double targetTemp, int holdFor, boolean alarm) { 

     //these need to be reset each time go_to_temp is called
     boolean tempReached = 0; //whether the target temp has been reached
     unsigned long startTime = 0; //the time in millis when the target temp is first reached
     int sensorValue = 0;         // the sensor value

     //loop the temp checking/relay control function until the target temp is reached, then hold for the amount of time specified in holdFor
     //the while statement will loop forever, until the target temperature is reached
     //once that happens, millis are used to count from startTime to startTime plus the length of holdFor
     while (millis() * tempReached <= (startTime + holdFor * 1000) * tempReached) {
       sensorValue = analogRead(sensorPin); //get the resistance reading from the thermistor

       if ((int)millis() % 1000 == 0){ //test the temperature every five seconds
            timestamp(millis()); //print the time elapsed since starting
            Serial.print("\tTarget temp = ");
            Serial.print(targetTemp, DEC); //print the desired temperature in F
            Serial.print("\tApprox Temp = ");
            printDouble(thermistor_read(sensorValue), 2); //print the approximate temp. in F
            Serial.print(" F");

            if (thermistor_read(sensorValue) < targetTemp) { //If below target temp, turn the crock pot on               digitalWrite(relayPin, HIGH);               Serial.print("\tRelay is ON");             }             else if (thermistor_read(sensorValue) > targetTemp) { //If above target temp, turn the crock pot off
                digitalWrite(relayPin, LOW);
                Serial.print("\tRelay is OFF");
            }

            if (abs(thermistor_read(sensorValue) - targetTemp) < 1) { //If approx. temp is within range of desired temp, log the time                 if (tempReached == 0) { //the tempReached boolean ensures this start time log only happens once                    startTime = millis();                    tempReached = 1;                 }             }              if (tempReached){ //if the target temp has been reached                     Serial.print("\tTarget temp reached at ");                     timestamp(startTime);                     Serial.print("\tholding for ");                     timestamp((startTime + holdFor * 1000) - millis());                     if (alarm)                       if (buzz(1) == 1) //buzz the buzzer to alert                         return tempReached; //if the temp is reached, and the buzzer buzzes, and the button is pushed, return true              }              Serial.println();        }     }     if (millis() * tempReached > (startTime + holdFor * 1000) * tempReached)
        return tempReached;
}

//make the buzzer generate a tone
void tone(int targetPin, long frequency, long length) {
    long delayValue = 1000000/frequency/2;
    long numCycles = frequency * length/ 1000;

    for (long i=0; i < numCycles; i++){                   // for the calculated length of time...
        if (micros() % (delayValue * 2) < delayValue)
            digitalWrite(targetPin,HIGH);                     // write the buzzer pin high to push out the diaphram
        else
            digitalWrite(targetPin,LOW);                     // write the buzzer pin low to pull back the diaphram
        }
}

//cycle the tone on and off for a given duration, in seconds.
int buzz(long duration) {
   long buzzEnd = millis() + duration * 1000;
   int buttonState = digitalRead(buttonPin);

   while (millis() < buzzEnd) {
         if (buttonState == HIGH) {
              if (millis() % 700 < 125) {
                   tone(9, 1500, 75);
               }
               else if (175 < (millis() % 700) && (millis() % 700) < 300)
                 tone(9, 1500, 75);
               else if (400 < (millis() % 700) && (millis() % 700) < 600)
                  tone(9, 1000, 75);
               buttonState = digitalRead(buttonPin);
         }
         else
             return 1;
    }

    return 0;
}

//Pretty much everything is controlled from setup(), since we don't want the looping that happens in loop()
//
void setup() {
     Serial.begin(9600); //open communications over the serial port @ 9600 baud
     pinMode(buzzerPin, OUTPUT); // set a pin for buzzer output
     pinMode(12, INPUT); // set a pin for pushbutton input

     /*
     Each one of these if statements below is one stage in the fermentation.
     */

     if (go_to_temp(185 /*temp(F)*/, (60 * 10) /*hold(seconds)*/, 0 /*to beep or not to beep during hold time*/) == 1) {//heat to temp (F) and hold for hold time (seconds)
         Serial.println();
         Serial.print("Stage 1 (Sterilize) Complete at ");
         timestamp(millis());
         Serial.println();
         Serial.println("Push button to advance.");
         Serial.println();
         buzz(300); //sometimes we want the alarm to happen after the hold time is complete, like in this case.
     }

     if (go_to_temp(110 /*temp(F)*/, (60 * 20) /*hold(seconds)*/, 1 /*to beep or not to beep during hold time*/) == 1) {//heat to temp (F) and hold for hold time (seconds)
         Serial.println();
         Serial.print("Stage 2 (Cool) Complete at ");
         timestamp(millis());
         Serial.println();
         Serial.println("Add yogurt/culture and seal containers.");
         Serial.println();
     }

     if (go_to_temp(110 /*temp(F)*/, 25200 /*hold(seconds)*/, 0 /*to beep or not to beep during hold time*/) == 1) {//heat to temp (F) and hold for hold time (seconds)
         Serial.println();
         Serial.print("Stage 3 (Incubate) Complete at ");
         timestamp(millis());
         Serial.println();
         Serial.println("Push button to stop buzzer.");
         Serial.println();
        // buzz(600);
     }
}

 void loop() { } //all our looping happens in individual functions

Making Yogurt

Wash Everything!

Wash Everything! Before starting, gather up all the containers, lids and stirring utensils to be used and thoroughly wash with hot, soapy water.

Ingredients: 1/2 gallon milk, 1 package dry milk, yogurt or starter culture

Ingredients: 1/2 gallon milk, 1 package dry milk, yogurt or starter culture. Let the yogurt come to room temperature before using; make sure that it contains live cultures & isn't expired!

Add dry milk.

Add the Dry Milk to the Wet Milk. Stir Thoroughly.

Fill Containers and Place in Water Bath

Fill Containers and Place in Water Bath in Crock Pot.

Insert the Thermistor in the Water Bath (Loosely cover jars to prevent condensation from dripping in)

Insert the Thermistor in the Water Bath (Loosely cover jars to prevent condensation from dripping in).

Plug the Crockpot into the Relay, the Relay into a Power Source

Plug the Crockpot into the Relay, the Relay into a Power Source.

Cover the Crockpot and Insulate with Towels or Blankets

Cover the crockpot and insulate with towels or blankets to help the heating stage go faster. The milk will heat to 185° F.

Arduino Serial Monitor

Plug the Arduino into a Computer. The Serial Monitor will give Feedback and Instructions.

Add Starter to Cooled Milk

After the milk has cooled to 110° F, add starter or yogurt (about a tablespoon per container).

Incubate

Seal the lids tightly and incubate for 7 hours (or more) at 100°F.

It's Yogurt!

It's Yogurt! Refrigerate after opening.