If you search for "diy sous vide" on google, you get a lot of results stating how cheap they managed to get their sous vide setup vs commercial home use version costing a few hundred US dollars at least. Ever since dad saw this proto-typing electronic board called Arduino 1 year ago, he's been saying he would set one up. Finally got round to it last week and cooked onsen eggs with the setup. For details about how to cook eggs to perfection, just google "egg sous vide" for a myriad of detail discussions on the optimal temperature and timing for cooking egg. For dad's setup, he used 64-65C temperature and about 60 minutes, it all depends on the consistency of the egg yolk you want.
|
Yummy!!! |
|
Onsen egg just cooked with a few drops of prime soy sauce |
|
Arduino with LCD shield with 2 pins connected, a relay controlling on/off of the heating element and a water-proof temperature sensor based on DS18B20
|
Excluding the stainless steel pot, the total cost of the electronics and heating element is less than US$20 and this isn't even optimised with dedicated PCBs for micro-controllers.
The code can be modified to include cooking time, total energy consumed etc at no extra costs.
If you by chance come to this blog on the back of this, do let us know by leaving a comment:)
Below are the codes for the controls, which are based on 2 main sources of libraries:
LCD4Bit_mod.h to control LCD Keypad shield
http://www.dfrobot.com/wiki/index.php?title=Arduino_LCD_KeyPad_Shield_(SKU:_DFR0009)
Events.h etc from M Romani
http://arduino.cc/forum/index.php/topic,37650.0.html
#include <Events.h>
#include <EventQueue.h>
#include <EventDispatcher.h>
#include <LCD4Bit_mod.h>
#include <stdio.h>
//create object to control an LCD.
//number of lines in display=1
LCD4Bit_mod lcd = LCD4Bit_mod(2);
int getCurrentTemp(char *temp);
void convIntString(char *temp, int tempnum);
//Analog values of key presses
int adc_key_val[5] ={30, 150, 360, 535, 760 };
char temp_string[10];
#define NUM_KEYS 5
#define KEY_RIGHT 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_LEFT 3
#define KEY_SELECT 4
#define TEMP_PIN 3 //DS18B20 data pin
#define RELAY_PIN 11 //relay pin this is the pin next to pin3
int adc_key_in;
int key=-1;
int oldkey=-1;
int temp_target=60; //target temperature of the sous vide bath
int counter=1;
// the event queue
EventQueue q;
// the event dispatcher
EventDispatcher disp(&q);
// use this analog channel
#define AN_CHAN 0
// generate an event when the analog
// channel value changes this much
// increase value for noisy sources
//#define AN_DELTA 5
// time event handler
void timeHandler(int event, int param) {
int curr_temp;
curr_temp=getCurrentTemp(temp_string);
//call getCurrentTemp will return current temperature both in integer and string
lcd.cursorTo(2, 0);
lcd.printIn(temp_string);
convIntString(temp_string, temp_target);
lcd.cursorTo(1,5);
lcd.printIn(temp_string);
if (curr_temp < temp_target) {
digitalWrite(RELAY_PIN, HIGH);
lcd.cursorTo(1, 7);
lcd.printIn("HeaterOn ");
}
else {
digitalWrite(RELAY_PIN, LOW);
lcd.cursorTo(1, 7);
lcd.printIn("HeaterOff");
}
}
// analog event handler
void analogkeyHandler(int event, int param) {
lcd.clear();
switch (param) {
case KEY_SELECT: //Not Implemented
lcd.cursorTo(1, 0);
lcd.printIn("SELECT NA");
break;
case KEY_RIGHT: //Not Implemented
lcd.cursorTo(1, 0);
lcd.printIn("Right NA");
break;
case KEY_UP: // Increase target temperature by 1C
lcd.cursorTo(1, 0);
lcd.printIn("Up ");
if (temp_target < 84)
temp_target = temp_target + 1;
break;
case KEY_DOWN: // Decrease target temperature by 1C
lcd.cursorTo(1, 0);
lcd.printIn("Down ");
if (temp_target > 40)
temp_target = temp_target - 1;
break;
case KEY_LEFT: //Not Implemented
lcd.cursorTo(1, 0);
lcd.printIn("Left NA");
break;
}
}
// this function generates an EV_TIME event
// each 1000 ms
// If you don't want the relay to jump back and forth too often
// at around the target temperature
// you can change the timing intervals
void timeManager() {
static unsigned long prevMillis = 0;
unsigned long currMillis;
currMillis = millis();
if (currMillis - prevMillis >= 1000) {
prevMillis = currMillis;
q.enqueueEvent(Events::EV_TIME, 0); // param is not used here
}
}
// this function generates an EV_ANALOG event
// whenever the analog channel AN_CHAN changes
void analogkeyManager() {
adc_key_in = analogRead(AN_CHAN); // read the value from AN_CHAN
key = get_key(adc_key_in); // convert into key press
if (key != oldkey) // if keypress is detected
{
delay(50); // wait for debounce time
adc_key_in = analogRead(AN_CHAN); // read the value from AN_CHAN **AGAIN**
key = get_key(adc_key_in); // convert into key press
if (key != oldkey)
{
oldkey = key;
if (key >=0) {
q.enqueueEvent(Events::EV_ANALOG0, key); // use param to pass key value to event handler
}
}
}
}
// Convert ADC value to key number
int get_key(unsigned int input)
{
int k;
for (k = 0; k < NUM_KEYS; k++)
{
if (input < adc_key_val[k])
{
return k;
}
}
if (k >= NUM_KEYS)
k = -1; // No valid key pressed
return k;
}
void lcdprint_interval (char *minsec, int interval)
{
char temp_string[10];
lcd.cursorTo(2, 9);
lcd.printIn(minsec);
convIntString(temp_string, interval);
lcd.cursorTo(2, 5);
lcd.printIn(temp_string);
Serial.println(interval);
}
// routine for getting temperature
// standard for the OneWire DS18B20
void OneWireReset(int Pin) // reset. Should improve to act as a presence pulse
{
digitalWrite(Pin, LOW);
pinMode(Pin, OUTPUT); // bring low for 500 us
delayMicroseconds(500);
pinMode(Pin, INPUT);
delayMicroseconds(500);
}
void OneWireOutByte(int Pin, byte d) // output byte d (least sig bit first).
{
byte n;
for(n=8; n!=0; n--)
{
if ((d & 0x01) == 1) // test least sig bit
{
digitalWrite(Pin, LOW);
pinMode(Pin, OUTPUT);
delayMicroseconds(5);
pinMode(Pin, INPUT);
delayMicroseconds(60);
}
else
{
digitalWrite(Pin, LOW);
pinMode(Pin, OUTPUT);
delayMicroseconds(60);
pinMode(Pin, INPUT);
}
d=d>>1; // now the next bit is in the least sig bit position.
}
}
byte OneWireInByte(int Pin) // read byte, least sig byte first
{
byte d, n, b;
for (n=0; n<8; n++)
{
digitalWrite(Pin, LOW);
pinMode(Pin, OUTPUT);
delayMicroseconds(5);
pinMode(Pin, INPUT);
delayMicroseconds(5);
b = digitalRead(Pin);
delayMicroseconds(50);
d = (d >> 1) | (b<<7); // shift d to right and insert b in most sig bit position
}
return(d);
}
int getCurrentTemp(char *temp)
{
int HighByte, LowByte, TReading, Tc_100, sign, whole, fract;
OneWireReset(TEMP_PIN);
OneWireOutByte(TEMP_PIN, 0xcc);
OneWireOutByte(TEMP_PIN, 0x44); // perform temperature conversion, strong pullup for one sec
OneWireReset(TEMP_PIN);
OneWireOutByte(TEMP_PIN, 0xcc);
OneWireOutByte(TEMP_PIN, 0xbe);
LowByte = OneWireInByte(TEMP_PIN);
HighByte = OneWireInByte(TEMP_PIN);
TReading = (HighByte << 8) + LowByte;
sign = TReading & 0x8000; // test most sig bit
if (sign) // negative
{
TReading = (TReading ^ 0xffff) + 1; // 2's comp
}
Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25
whole = Tc_100 / 100; // separate off the whole and fractional portions
fract = Tc_100 % 100;
/*
if(sign) temp[0]='-';
else temp[0]='+';
temp[1]= whole%100+'0';
temp[2]= (whole-100*temp[1])%10 +'0' ;
temp[3]= whole-100*temp[1]-10*temp[2] +'0';
temp[4]='.';
temp[5]=fract%10 +'0';
temp[6]=fract-temp[5]*10 +'0';
temp[7] = '\0';
*/
sprintf(temp, "%c%3d%c%2d", (sign==0)?'+':'-', whole, '.', fract);
return whole;
}
// End of Getting temperature routine
void convIntString(char *temp, int tempnum)
{
sprintf(temp, "%2d", tempnum);
}
// program setup
void setup() {
// Serial.begin(115200); // Serial Port can be used for future debugging
// initialize DS18B20 datapin
digitalWrite(TEMP_PIN, LOW);
pinMode(TEMP_PIN, INPUT); // sets the digital pin as input (logic 1)
pinMode(RELAY_PIN, OUTPUT); // sets Relay pin to output
lcd.init();
//optionally, now set up our application-specific display settings,
//overriding whatever the lcd did in lcd.init()
//lcd.commandWrite(0x0F);//cursor on, display on, blink on. (nasty!)
lcd.clear();
disp.addEventListener(Events::EV_TIME, timeHandler);
disp.addEventListener(Events::EV_ANALOG0, analogkeyHandler);
}
// loop
void loop() {
// call the event generating functions
timeManager();
analogkeyManager();
// get events from the queue and call the
// registered function(s)
disp.run();
}