You are on page 1of 6


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

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
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

Página 1
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

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


if (minutes < 10)



if (seconds < 10)

Página 2

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");
Página 3

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


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
digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphram

Página 4
//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);
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.print("Stage 1 (Sterilize) Complete at ");
Serial.println("Push button to advance.");
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)
Página 5
Serial.print("Stage 2 (Cool) Complete at ");
Serial.println("Add yogurt/culture and seal containers.");

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.print("Stage 3 (Incubate) Complete at ");
Serial.println("Push button to stop buzzer.");
// buzz(600);

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

Página 6