Category Archives: Cooking

Arduino Sous Vide, Version 2.0

I previously built a sous vide machine based off Seattle Food Geek’s plans but with an arduino + relay in place of the PID. It ran off a shoddy script I wrote in Python to be used with Pyfirmata. The recommended Norpro heating devices in the Seattle Food Geek blog failed within 10 uses and the Pyfirmata script required a computer to be plugged into the arduino. It worked but was hardly optimal. This new sketch is written in C and doesn’t require Pyfirmata or an additional computer to be plugged into the arduino.

Very simple, works great!

The Sketch

// A simple arduino sous vide machine sketch

void setup() {

Serial.begin(9600);
}
void loop() {

// set pins and mode
// set relay pin and relay status to off
const int relay_pin = 2; 
int relay_status = LOW;
pinMode(relay_pin, OUTPUT);

int analogValue = analogRead(A0); // read the raw value for thermistor via analog pin0

// assign variables
float temperature; // the current temperature
float upper;       // the upper limit at which the relay & heat source shut off
float setpoint;    // the desired setpoint cooking temperature
float lower;       // opposite of upper
float steinhartA;  // steinhart-hart equation coefficients
float steinhartB;
float steinhartC;
float rt;          // thermistor resistance

setpoint = 60.0;   // desired cooking temperature in degrees C 
lower = setpoint - 1.0; // can manually edit cutoff values to affect hysteresis curve
upper = setpoint + 1.0;
steinhartA = 1.137699953e-3; // replace with your thermistor's unique steinhart-hart coefficients
steinhartB = 2.325339915e-4;
steinhartC = 0.9547893220e-7;

rt = 9910*(1023/float(analogValue)-1.0);
temperature = steinhartA + steinhartB*log(rt) + steinhartC*(log(rt)*log(rt)*log(rt));
temperature = (1.0/temperature)-273.15; // convert from K

Serial.println(temperature); // debug, print temp to serial

if (temperature >= upper)
{
relay_status = LOW; 
digitalWrite(relay_pin, relay_status);
Serial.println("RELAY OFF"); // debug, print status to serial
}

else if (temperature <= lower)
{
relay_status = HIGH;
digitalWrite(relay_pin, relay_status);
Serial.println("RELAY ON");
}

delay(30000);        //  30s delay between temperature reads
}

How to use

Use it with a thermistor, relay, and a heating device of your choice. Edit the setpoint temperature to your desired temp and be sure to edit the Steinhart-Hart values to reflect those of your thermistor. If you wish to change the temperature, you will need to upload an updated sketch (for now….).

My current setup

A few things have changed since my previous posts. Since my immersion heaters have failed, I had to find a new heating source. A commenter recommended a turkey roaster which sounds like a fantastic idea (and they look very similar to lab water baths which is a plus). I was averse to spending any more money on this project though so I scavenged around the house and am currently using a hot plate with a large stockpot on top. The stockpot holds an excess of water which helps maintain a more constant temperature and the pot + large volume of water retains heat better than the thin sterilite container used previously. Its greater size allows me to fit larger cuts of meat in it also which is a big plus. Getting heat up to the setpoint temperature no longer takes a century with the hotplate which is also very nice.

I am using a 10k ohm thermistor that I had laying around as I misplaced the old 4.7k ohm thermistor. I only mention it in case someone looks at any old posts and is left scratching their head over the sudden change in equation values.

I am using the same relay setup as described previously.

Future

I am waiting on an Amazon shipment to come with an LCD screen. Once that arrives (hopefully within another 3 weeks… I live in a remote location!), I will feature creep this sketch to include a live temperature readout and the ability to configure setpoint temperature without needing to modify the sketch and reupload.

Further resources:

My other posts on arduino sous vide

How to use a relay with an arduino

Seattle Food Geek sous vide machine

Everything you would ever want to know about using a thermistor with an arduino

Thermistor calculator

 

Sous Vide Update, 5/2014

I was about to get started on the code overhaul for the Arduino sous vide machine when I discovered that my immersion heaters no longer work. A word to the wise, you shouldn’t store them in freezing temperatures in a humid garage. The heaters I chose had mediocre reviews on Amazon, but were still the best reviewed and most affordable. I’m going to look into a few alternatives to find something heartier, but equally affordable.

The (sort of) Return of Good Eats

The Situation

The host and creator of the Food Network series Good Eats, Alton Brown, has been busy these last few years. From continuing his job as host of Iron Chef America and mentor on The Next Food Network Star, publishing periodical podcasts  and interviews with notable chefs and authors (Alton Browncasts), starting a new series Cutthroat Kitchen, continuing to bemuse his fans with his ridiculous post it note twitter account, and going on a nation wide food & music tour. You can say he’s been very busy and this is all well and good, but none of it compares to the pure bliss of Good Eats. It was a Bill Nye meets Julia Child mashup where the foundational science of cooking had an equal footing and airtime with the process and techniques involved in producing the episode’s dish. On a channel starved of any programs that actually taught cooking or offered any intellectual stimulation, Good Eats was king.

It was something unlike any other cooking show on the Food Network or elsewhere on television. It had no reality show theatrics and it taught you more than how to replicate a recipe, it taught you about why that recipe works and how to think about food. As someone who can assuredly say they learned how to cook nearly everything from the show, I really miss Good Eats.

Brown with his (in)famous yeast puppets
Brown with his (in)famous yeast puppets

The Rebirth of “Good Eats”

You can imagine my excitement when I stumbled across two new videos under the moniker ‘Cook Smart’ on the Alton Brown YouTube page. Looks like this is the Good Eats-esque project Brown has been hinting about for the last year. So far there are only two short web clips, one on cooking eggs in the oven and another on refrigerator organization.

Worth keeping an eye on and periodically checking for updates if you’re a ‘food fan’. Who knows, maybe even the yeast puppets will return for a clip.

 

Arduino Sous Vide Machine: Part 2

Note: For an updated version with simpler code and important information about hardware issues see this new post: Arduino Sous Vide, Version 2

Darwin Award Disclaimer: Playing with electricity around big containers of water can be dangerous!

sous vide_fritz
The wiring scheme

After several successful cooks with the sous vide machine and ironing out a few software bugs, I decided it’s good enough to post. Like my other entries, it’s hardly pythonic, but gets the job done and it’s not all that bad for two months of self taught lackadaisical python study.

Photos to follow this post

I will upload some photos when I get a chance in the coming weeks.

The Assembly

You will need the parts listed in Part 1. The machine is assembled relatively easily from here. The immersion heaters plug into the GFCI relay box. The relay should be connected to the arduino with M to F jumper wires as pictured in the first post.

The thermistor should be enclosed in something that will lend additional waterproofing (partially sealing it in a piece of vacuum bag is a good idea or any old ziploc). The thermistor is connected to the arduino as depicted in the image above.  Don’t bother moving on unless you know the resistance of your thermistor at 25C and have determined your specific resistor’s Steinhart-Hart equation coefficients as described in this post. When you have these values, you will need to add them to the code below. (My values will work fine for the 4.7k ohm epoxy thermistor linked in Part 1).

The binder clips mentioned in Part 1 are useful for holding the Thermistor / waterproofing in place and keeping food filled vacuum bags in place when needed. The coat hanger / safety wire is used to hold the immersion heaters at the proper place in the container. I used two and have the heaters pinched in between.  It’s important to not submerge them completely, but leave some space between the water and the start of the electrical cord. The aquarium pump goes at the bottom along a wall of the container to ensure constant heat redistribution. The container lid is not necessary, but can be placed on top once you’ve started.

sousvideconsole2
This console output will refresh every 30 seconds with an updated temperature, time remaining, and whether the heater is on or off.

The Code

Code available for download. Hack it, improve it, MIT license yada yada yada…

# ARDUINO SOUS VIDE 
# v 0.1
# Simple arduino sous vide program
# Controls relay based on thermistor readings

import time
import os
import pyfirmata
from math import log

status = 'off'

def slp():
  '''
  INSERTS 5 SECOND DELAY 
  '''
  time.sleep(5)

def cls():
  '''
  CLEARS CONSOLE BETWEEN READINGS AND USER ENTRIES
  '''
  os.system('clear')

# SET COOKING PARAMETERS
def setup():
  ''' 
  SET UP COOKING PARAMETERS SUCH AS TEMP SCALE, TEMP, LENGTH OF TIME, PRECISION
  '''
  global cook_temp
  global cook_time
  global end_time
  global cook_temp_f
  global scale

  # CHOOSE TEMPERATURE SCALE AND COOKING TEMP
  n = True
  while n == True:
    cls()
    # SELECT TEMPERATURE SCALE
    scale = raw_input("Select temperature scale\n\n   1) Celsius \n\n   2) Fahrenheit \n\nEnter your selection: ")
    scale = scale.strip()
    if scale == '1':
      scale = [scale, 'Celsius', 1.0]			# scale list is temperature scale selection from menu, followed by name of scale, and last value is number of +/- degrees deviation allowed from cook_temp in cook() 
    elif scale == '2':
      scale = [scale, 'Fahrenheit', 2.66]
    else:
      print '\n\nERROR: You must enter 1 for Celsius or 2 for Fahrenheit\n\n'
      slp()
      continue
    cls()

    # SELECT COOKING TEMPERATURE; DON'T ALLOW NONSENSE ENTRIES SUCH AS < ROOM TEMP OR > BOILING POINT
    cook_temp = float(raw_input('Enter desired cooking temperature in degrees ' + scale[1] + ': '))
    if (scale[0] == '1' and cook_temp > 100) or (scale[0] == '2' and cook_temp > 212):
      print '\n\nERROR: Temperature may not be higher than boiling point of water (BP: 100 C, 212 F)\n\n'
      slp()
      continue

    elif (scale[0] == '1' and cook_temp < 25) or (scale[0] == '2' and cook_temp < 77):       print '\n\nERROR: Temperature may not be lower than room temperature (RT: 25 C, 77 F)\n\n'        slp()       continue          try:     # SET ALLOWED TEMPERATURE DEVIATION. DON'T ALLOW DEVIATION BEYOND +/- 5 DEGREES.        deviation = float((raw_input('Enter allowed +/- degrees deviation from setpoint. Press enter for default of ' +str(scale[2]) + ' degrees ' + scale[1] +': ')))       if deviation > 5:
	print '\n\nERROR: Deviation of ' +str(deviation) + ' degrees ' + scale[1] + ' is too great. Reduce your value to 5 degrees or lower for greater precision cooking.'
	print '\n\nExample: cook temp of 70 degrees C with 5 degrees deviation will allow a low temp of 65 C to be achieved before heaters are turned on and a high temp of 75 C before heaters are shut off'
	slp()
	continue
      scale[2] = deviation
    except:
      pass

    # SET COOKING TIME LENGTH IN MINUTES
    cook_time = raw_input("Enter desired cook time in minutes: ")
    cook_time = float(cook_time) * 60
    start_time = time.time()
    end_time = start_time + cook_time
    cls()

    # SETUP VERIFICATION / START
    print 'COOKING SETTINGS'
    print '\n\nSet for ' +str(cook_temp) +' degrees ' + scale[1] ,
    print 'for ' +str(cook_time / 60) +' minutes'
    print time.strftime('Cooking will complete at %a %I:%M %p', time.localtime(end_time)) #convert end_time seconds to readable day H:m format
    start = raw_input('\n\nDo you wish to continue with current settings? ')
    if start.lower() == 'n' or start.lower() == 'no':
      continue
    if start.lower() == 'y' or start.lower() == 'yes':
      n = False

def connection():
  '''
  CONNECT TO ARDUINO
  '''

  global pin0
  global pin4
  global board
  n = 0							# first port number to try ie: ttyACM0
  PORT = '/dev/ttyACM' + str(n)    			# device location, yours may very. if unable to connect, try /dev/USB or check dmesg
  connected = False 

  # ATTEMPT TO CONNECT TO ARDUINO. ITERATE THROUGH 1000 PORT NUMBERS UNTIL CONNECTED OR FAIL ON 1001.
  while connected == False:
    try:
      print 'Trying ' + PORT
      board = pyfirmata.Arduino(PORT)
      print 'Port found, connecting...'
      connected = True
      print "Connection established"
    except:
      n = n+1
      if n > 1000:
	raise Exception("Error connecting to arduino. Check to make sure it's connected and check dmesg for correct location. If other than /dev/ttyACMx, edit line #102 of sousvide.py to reflect your location.")
      PORT = '/dev/ttyACM' + str(n)
      pass

  pin0 = board.get_pin('a:0:i')				# edit to reflect your setup
  pin4 = board.get_pin('d:4:o')

  it = pyfirmata.util.Iterator(board)
  it.start()

  pin4.write(0)						# start with pin off
  pin0.enable_reporting()

  print 'Waiting on reading...'
  while pin0.read() is None:				# ignore input until pin is active
      pass

def temp_check():
  '''
  FUNCTION THAT POLLS ANALOG PIN FOR TEMPERATURE, USED EVERY 30 SECONDS
  '''

  global current_temperature
  current_temperature = ''				# reset temperature each time 

  while pin0.read() is None:				# ignore input until pin is active
      print 'waiting on thermistor reading...'
      pass

  analog_value = pin0.read()

  # VOLTAGE DIVIDER CALCULATION
  analog_value = analog_value  * 5			
  analog_value = 4700.0 * ((5.0/analog_value) -1.0) 	# make sure you use the correct number here reflecting your thermistor. ie: for 10k ohm resistor, replace 4700 with 10000

  # STEINHART-HART EQUATION 
  temp = (1 / (0.001308463361 + 0.0002344771590 * log(analog_value) + 0.0000001041772095 * log(analog_value)**3)) # substitute your thermistor's unique values for A, B, C. 

  # CONVERT TEMPS FROM KELVIN 

  # TEMP IN CELSIUS
  if scale[0] == '1':
    current_temperature = temp - 272.15

  # TEMP IN FAHRENHEIT
  if scale[0] == '2':
    current_temperature= 9/5.0*(temp-272.15) + 32

  # TIME / TEMPERATURE LOGGING
  f = open('/tmp/sousvide.log', 'a')
  f.write(str(time.time()) + ' ' + str(current_temperature) + '\n')
  f.close()

  # STATUS REPORTING TO CONSOLE
  cls()
  print 'ARDUINO SOUS VIDE v 0.1'
  print '-'*80+'\n\n'
  print ' '*5 + 'Set temperature: ' + str(cook_temp) + '\n\n'
  print ' '*5 + 'Current temperature: ' + str(current_temperature) + ' ' + scale[1] + '\n\n'
  print ' '*5 + 'Heating element(s): ' + status + '\n\n'
  print ' '*5 + 'Time remaining: ' + str((end_time - time.time())/60.0) + ' minutes\n\n\n\n'
  print  '-'*80
  slp()

def cook():
  while True:

    global status

    while time.time() < end_time: # continue cook function until time up
      temp_check()
      if current_temperature < (cook_temp - scale[2]): 	# start heating element every time temp drops X degrees below goal temp 	pin4.write(0)	# START HEAT 	board.pass_time(5) 	time.sleep(20) 	status = 'on'       elif current_temperature > (cook_temp + 0.4 * scale[2]): # kill heating element every time temp goes 0.5X degrees over goal temp
	pin4.write(1)	# KILL HEAT
	board.pass_time(5)
	time.sleep(20)
	status = 'off'
      else:  				      		# if temperature is within X*2 degrees (-X goal +X) of goal temp, continue with whatever is currently happening
	board.pass_time(5)
	time.sleep(20)
	pass 

    if time.time() > end_time or time.time() == end_time:
      pin4.write(1)
      cls()
      print "all finished, killing heat..."
      slp()
      board.exit()
      exit()

setup()
connection()
cook()

Features

The code offers the following features:

  • Fahrenheit and Celsius heating options
  • Status updates every 30 seconds
  • Control over hysteresis with modifiable temperature range from setpoint (the deviation option)
  • Automatic logging of temperature and time to /tmp/sousvide.log

What’s Next

Now to get cooking, there are some excellent resources online. Douglas Baldwin offers some of the most oft quoted resources. He has some good information on his website, including some carefully calculated pasteurization tables that if followed, let you cook food low and slow while still getting all the nasties.

For a more general overview, you can check out his presentations that he did for the American Chemical Society, Sous Vide Cooking and Chemistry and Giving Thanks for the Water Bath: Sous Vide Cooking for the Holidays.

Limitations

This set up and code is good to go as-is ‘out of the box’. FYI: timer starts immediately after accepting  cook settings. You must take into account preheat time (approximately one hour to obtain 60C from room temp water, see ‘future updates’ section below).

A little fine tuning may be helpful to get the most precise and near constant temperature. In order to avoid a constant on/off, a hysteresis curve is simulated in the cook() function. After about 6 cooks, I’ve gotten it calibrated pretty well. It originally was giving some wild peaks due to the fact that the heaters continue to heat after shutting off.

Case in point:

sous vide premodification

I found that reducing the deviation variable to 40% for the cut off temp and reducing the temperature check interval by 10s greatly improved the outcome. I may go one step further and reduce the polling interval by another 5s to try to get equidistant ‘peaks’ to ‘troughs’ from setpoint temp).

I cooked my chicken for more than 1 hour. Scale abbreviated to better match above graph.
I cooked my chicken for more than 1 hour. Scale abbreviated to better match above graph.

If your sous vide cooking relies on even more precise cooking that deviates less than 1C from setpoint, feel free to fiddle with the cook() function and/or set deviation to something very low (< 0.5 degrees).

Future Updates

I ran out of time in the short run to make any new updates to this. For my own satisfaction, I will likely add a preheat function from the data gleaned in the last post that will preheat the water bath and once preheated, start the timer from there. Should be a quick fix. As an exercise in dealing with formatting Python time strings, I may make the time input a little smarter with options other than minutes.

I’m also considering modifying the code to make use of PyPi and the Raspberry Pi via its GPIO pins.

Arduino Sous Vide Machine: Part 1

Note: For an updated version with simpler code and important information about hardware issues see this new post: Arduino Sous Vide, Version 2

Heavily inspired by an old blog post by the brilliant Seattle Food Geek, I set out to build an arduino powered sous vide / thermal immersion circulator machine.  Instead of a PID controller, it uses an arduino with a relay + waterproof thermistor. A little less sexy, but just as effective with the added bonus of being modular with reusable parts.

Materials

  1. Sterilite® Ultra Latch 17 L container $4
    • Made of polyproplyene, softens at ~155°C according to the Merck index, melts at 165°C; perfect for our application of temps < 100°C.
    • Avoid long term exposure to UV light as paranoid precaution against degradation
    • You can simply use a big pot instead of the plastic container. I Outlet boxhave an enamelled cast iron pot that is excellent for maintaining a steady temperature.
  2. Arduino (I’m using UNO rev3) $35
  3. Waterproof temperature sensor / thermistor (DS18b20 ) $2.03

    • This thermistor is waterproof but not rated for full immersion
    • I sealed mine in a small bit of plastic with the vacuum sealer. problem solved
    • See previous post for how to calibrate
  4. Immersion heater (2x) $13
  5. 3 prong power cable free if sacrificed, otherwise $6+
  6. 5V 1 channel relay  $1.36
    • This relay is really cheap and was starting to fall apart on me; I substituted it with another one I had laying around. YMMV
  7. Aquarium water pump 80GPH $6.88
  8. Ziploc V150 vacuum sealer system $30 (on clearance at Target)
  9. GFCI outlet $12.30
  10. Outlet faceplate $1 (optional)
  11. Outlet box $0.50 (link is to pack of 100, cheaper to buy in store)
  12. Wire coat hanger / safety wire
  13. Large binder clips (2x) $0.50
  14. Male to female (3x) and male to male jumper wires (the more the merrier) $2.00
  15. A 4.7 k Ω resistor for thermistor $0.05

Grand Total:  $114.62 if you buy everything. You can add an extra heater or two if desired. I found that two alone work pretty well. My units have been calculated to heat at about 0.01 C per second, taking an hour to get up to an average cooking temp of 65C from room temperature water.

norpro temperature graph

Step 1: Building the relay

The first step is building a functioning relay. The beauty of this sous vide machine is that it is comprised of parts that can be cannibalized and put to other uses when you’re not sous vide cooking. The relay is especially cool as it allows you to control high voltage devices from the measly 5V microcontroller of the arduino. The options from here are limited only by your creativity and/or depravity. (Lots of options of pairing it with motion!)

I followed a Sparkfun tutorial on how to build a Controllable Power Outlet. Quite frankly, there is no need to rehash something that is described better there. Check it out.

Here’s a diagram for the relay listed  in the parts list. You can figure it all out from this picture alone.

relay wiring

Step 2: Testing Relay

Controlling a relay with pyfirmata

Controlling the relay is incredibly easy with pyfirmata. It’s as simple as turning a pin on and off to open and close the relay. If you know how to switch a LED on, you can control a relay.

Here’s some example code to function test it.

import pyfirmata

port = '/dev/ttyACM0'

board = pyfirmata.Arduino(port)

it = pyfirmata.util.Iterator(board)
it.start()

pin4 = board.get_pin('d:4:o')

while True:
	ui = raw_input("Enter 'on' or 'off': ")
	if ui == 'on':
		pin4.write(1)
	elif ui == 'off':
		pin4.write(0)
	else:
		pass

Plug your relay into the arduino, plug your arduino into a PC and test it out by plugging in your relay controlled power outlet and lamp into the outlet.

That’s all there is to it. It’s only a small leap from here to put some devices online that you can control remotely. (Check out X10 for anything beyond a few devices).

Next…

Building a feedback loop with a thermistor and using thermostat-like hysteresis curves for controlling the immersion heaters.