Monday, February 4, 2013

Slow cook on the cheap using Arduino 温泉蛋

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();
}
 

3 comments:

  1. Hello,

    I try to post your circuit. Only I always get the following message when uploading.

    " fatal error: Events.h: No such file or directorycompilation terminated."

    Do you have any idea what I can do here?
    greeting Ernst

    ReplyDelete
    Replies
    1. Hi

      Thanks for reading this blog. In order for the above arduino sketch to run you need to have the relevant header files (.h files) installed in your library.

      Go to this github and copy all the files
      EventDispatcher.cpp
      EventDispatcher.h
      EventQueue.cpp
      EventQueue.h
      Events.h

      into the "libraries" folder of your Arduino folder.

      Then when you compile the sketch your arduino will know where to get all the relevant files.

      Hope this help.

      Thanks again.


      Delete
    2. this github = https://github.com/kennedyshead/ArduinoEventSystem

      Delete