## sous-vide desplay

For other supported Arduino products from Adafruit: Shields, accessories, etc.

Please be positive and constructive with your questions and comments.
fuceye

Posts: 25
Joined: Sun Nov 25, 2012 5:43 pm

### sous-vide desplay

hi all

quick question
the sous-vid display when in run mode.
the # on the display with the % sign,what does this # represent?

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

That is the duty cycle of the time proportional output from the PID. When warming up, it will be at or near 100%. As it reaches the setpoint, the output will be reduced to just enough power to maintain a steady temperature (typically around 5% on the rice-cooker used for the prototype).

fuceye

Posts: 25
Joined: Sun Nov 25, 2012 5:43 pm

### Re: sous-vide desplay

is there and easer way to tune? i've read all the material but its mostly just cloudy with a chance of meatballs... i kinda get the premis but fear it will be mostly hit and miss,at the rate of hit and miss i should have it tuned in the year 2027...

Franklin97355

Posts: 23605
Joined: Mon Apr 21, 2008 2:33 pm

### Re: sous-vide desplay

mostly hit and miss
Yes, there is a lot of magic involved in tuning PID. There are so many variables that the formulas are not absolute, you can come close but unless you can quantify all the parameters it's still a trial and error proposition.

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

is there and easer way to tune?
You can start with the auto-tune. Than should get you in the ballpark.

Then start tuning the proportional term. Try to get it so that it comes up to temperature reasonably quick without continuing to oscillate.
At this point, it will probably settle in to a temp slightly below setpoint. You can start gently bumping up the Integral term to correct that offset.
For most cookers, you don't need to mess with the derivative term much.

fuceye

Posts: 25
Joined: Sun Nov 25, 2012 5:43 pm

### Re: sous-vide desplay

is there and easer way to tune?
You can start with the auto-tune. Than should get you in the ballpark.

Then start tuning the proportional term. Try to get it so that it comes up to temperature reasonably quick without continuing to oscillate.
At this point, it will probably settle in to a temp slightly below setpoint. You can start gently bumping up the Integral term to correct that offset.
For most cookers, you don't need to mess with the derivative term much.

well sir i do thank you very much...

went back and looked at the tempter before heating i think t was 33c.i then set the sp to33.5 and hit the auto tune...that got me in the ball park.then i rased the sp to 59c and let it do its thing...i tinkered with it as you suggested above^^^ and got it to remain steady at 59.6-59.13

i threw a tenderloine in for 1.5 hours and then scorched it on the hot bbq for 30 seconds per side....dude i be sous vidden now...and i saved a load of scrilla
by doing it this way instead of buying one off the shelf.....

this was a great project that i know i will use often....ok got to go do some more steaks the lads or on there way with the beer....lol

fuceye

Posts: 25
Joined: Sun Nov 25, 2012 5:43 pm

### Re: sous-vide desplay

oh, by the way, my duty cycle turned out to be around 19.5% at a stable temp. its a large slow cooker...again than for the help...

sp=59c
kp=1603
ki=.30
kd=.1

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

oh, by the way, my duty cycle turned out to be around 19.5% at a stable temp. its a large slow cooker...again than for the help...

sp=59c
kp=1603
ki=.30
kd=.1
Nice looking build! Thanks for the feedback. I haven't tried the controller on a cooker of that size yet.

dbick

Posts: 3
Joined: Fri Nov 22, 2013 6:42 pm

### Re: sous-vide desplay

is that kp 1603 or 16.03? I have a similar cooker and after autotune it set the kp to 12.05. It is still overshooting by around 5 degrees so I trying to figure out what to change.
Thanks

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

What is your Ki? Some overshoot on initial startup is typical if you have a non-zero Ki. I've been experimenting with a pre-heat phase to keep the integral term from causing overshoot at startup. The code is here if you want to try it:

Code: Select all

``````//-------------------------------------------------------------------
//
// Sous Vide Controller
// Bill Earl - for Adafruit Industries
//
// Based on the Arduino PID and PID AutoTune Libraries
// by Brett Beauregard
//------------------------------------------------------------------

// PID Library
#include <PID_v1.h>
#include <PID_AutoTune_v0.h>

// Libraries for the Adafruit RGB/LCD Shield
#include <Wire.h>

// Libraries for the DS18B20 Temperature Sensor
#include <OneWire.h>
#include <DallasTemperature.h>

// So we can save and retrieve settings
#include <EEPROM.h>

// ************************************************
// Pin definitions
// ************************************************

// Output Relay
#define RelayPin 7

// One-Wire Temperature Sensor
// (Use GPIO pins for power/ground to simplify the wiring)
#define ONE_WIRE_BUS 2
#define ONE_WIRE_PWR 3
#define ONE_WIRE_GND 4

// ************************************************
// PID Variables and constants
// ************************************************

//Define Variables we'll be connecting to
double Setpoint;
double Input;
double Output;

volatile long onTime = 0;

// pid tuning parameters
double Kp;
double Ki;
double Kd;

// EEPROM addresses for persisted data
const int SpAddress = 0;
const int KpAddress = 8;
const int KiAddress = 16;
const int KdAddress = 24;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

// 10 second Time Proportional Output window
int WindowSize = 10000;
unsigned long windowStartTime;

// ************************************************
// Auto Tune Variables and constants
// ************************************************
byte ATuneModeRemember=2;

double aTuneStep=500;
double aTuneNoise=1;
unsigned int aTuneLookBack=20;

boolean tuning = false;
boolean preheat = false;

PID_ATune aTune(&Input, &Output);

// ************************************************
// DiSplay Variables and constants
// ************************************************

// These #defines make it easy to set the backlight color
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7

#define BUTTON_SHIFT BUTTON_SELECT

unsigned long lastInput = 0; // last button press

byte degree[8] = // define the degree symbol
{
B00110,
B01001,
B01001,
B00110,
B00000,
B00000,
B00000,
B00000
};

const int logInterval = 10000; // log every 10 seconds
long lastLogTime = 0;

// ************************************************
// States for state machine
// ************************************************
enum operatingState { OFF = 0, SETP, RUN, TUNE_P, TUNE_I, TUNE_D, AUTO};
operatingState opState = OFF;

// ************************************************
// Sensor Variables and constants
// Data wire is plugged into port 2 on the Arduino

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// arrays to hold device address

// ************************************************
// Setup and diSplay initial screen
// ************************************************
void setup()
{
Serial.begin(9600);

// Initialize Relay Control:

pinMode(RelayPin, OUTPUT);    // Output mode to drive relay
digitalWrite(RelayPin, LOW);  // make sure it is off to start

// Set up Ground & Power for the sensor from GPIO pins

pinMode(ONE_WIRE_GND, OUTPUT);
digitalWrite(ONE_WIRE_GND, LOW);

pinMode(ONE_WIRE_PWR, OUTPUT);
digitalWrite(ONE_WIRE_PWR, HIGH);

// Initialize LCD DiSplay

lcd.begin(16, 2);
lcd.createChar(1, degree); // create degree symbol from the binary

lcd.setBacklight(VIOLET);
lcd.setCursor(0, 1);
lcd.print(F("   Sous Vide!"));

// Start up the DS18B20 One Wire Temperature Sensor

sensors.begin();
{
lcd.setCursor(0, 1);
lcd.print(F("Sensor Error"));
}
sensors.setResolution(tempSensor, 12);
sensors.setWaitForConversion(false);

delay(3000);  // Splash screen

// Initialize the PID and related variables
myPID.SetTunings(Kp,Ki,Kd);

myPID.SetSampleTime(1000);
myPID.SetOutputLimits(0, WindowSize);

// Run timer2 interrupt every 15 ms
TCCR2A = 0;
TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

//Timer2 Overflow Interrupt Enable
TIMSK2 |= 1<<TOIE2;
}

// ************************************************
// Timer Interrupt Handler
// ************************************************
SIGNAL(TIMER2_OVF_vect)
{
if (opState == OFF)
{
digitalWrite(RelayPin, LOW);  // make sure relay is off
}
else
{
DriveOutput();
}
}

// ************************************************
// Main Control Loop
//
// All state changes pass through here
// ************************************************
void loop()
{
// wait for button release before changing state
while(ReadButtons() != 0) {}

lcd.clear();

switch (opState)
{
case OFF:
Off();
break;
case SETP:
break;
case RUN:
Run();
break;
case TUNE_P:
TuneP();
break;
case TUNE_I:
TuneI();
break;
case TUNE_D:
TuneD();
break;
}
}

// ************************************************
// Initial State - press RIGHT to start
// ************************************************
void Off()
{
myPID.SetMode(MANUAL);
lcd.setBacklight(0);
digitalWrite(RelayPin, LOW);  // make sure it is off
lcd.setCursor(0, 1);
lcd.print(F("   Sous Vide!"));
uint8_t buttons = 0;

while(!(buttons & (BUTTON_RIGHT)))
{
}
// Prepare to transition to the RUN state
sensors.requestTemperatures(); // Start an asynchronous temperature reading

//turn the PID on
myPID.SetMode(AUTOMATIC);
windowStartTime = millis();
opState = RUN; // start control
}

// ************************************************
// Setpoint Entry State
// UP/DOWN to change setpoint
// RIGHT for tuning parameters
// LEFT for OFF
// SHIFT for 10x tuning
// ************************************************
{
lcd.setBacklight(TEAL);
lcd.print(F("Set Temperature:"));
uint8_t buttons = 0;
while(true)
{

float increment = 0.1;
if (buttons & BUTTON_SHIFT)
{
increment *= 10;
}
if (buttons & BUTTON_LEFT)
{
opState = RUN;
return;
}
if (buttons & BUTTON_RIGHT)
{
opState = TUNE_P;
return;
}
if (buttons & BUTTON_UP)
{
Setpoint += increment;
delay(200);
}
if (buttons & BUTTON_DOWN)
{
Setpoint -= increment;
delay(200);
}

if ((millis() - lastInput) > 3000)  // return to RUN after 3 seconds idle
{
opState = RUN;
return;
}
lcd.setCursor(0,1);
lcd.print(Setpoint);
lcd.print(" ");
DoControl();
}
}

// ************************************************
// Proportional Tuning State
// UP/DOWN to change Kp
// RIGHT for Ki
// LEFT for setpoint
// SHIFT for 10x tuning
// ************************************************
void TuneP()
{
lcd.setBacklight(TEAL);
lcd.print(F("Set Kp"));

uint8_t buttons = 0;
while(true)
{

float increment = 1.0;
if (buttons & BUTTON_SHIFT)
{
increment *= 10;
}
if (buttons & BUTTON_LEFT)
{
opState = SETP;
return;
}
if (buttons & BUTTON_RIGHT)
{
opState = TUNE_I;
return;
}
if (buttons & BUTTON_UP)
{
Kp += increment;
delay(200);
}
if (buttons & BUTTON_DOWN)
{
Kp -= increment;
delay(200);
}
if ((millis() - lastInput) > 3000)  // return to RUN after 3 seconds idle
{
opState = RUN;
return;
}
lcd.setCursor(0,1);
lcd.print(Kp);
lcd.print(" ");
DoControl();
}
}

// ************************************************
// Integral Tuning State
// UP/DOWN to change Ki
// RIGHT for Kd
// LEFT for Kp
// SHIFT for 10x tuning
// ************************************************
void TuneI()
{
lcd.setBacklight(TEAL);
lcd.print(F("Set Ki"));

uint8_t buttons = 0;
while(true)
{

float increment = 0.01;
if (buttons & BUTTON_SHIFT)
{
increment *= 10;
}
if (buttons & BUTTON_LEFT)
{
opState = TUNE_P;
return;
}
if (buttons & BUTTON_RIGHT)
{
opState = TUNE_D;
return;
}
if (buttons & BUTTON_UP)
{
Ki += increment;
delay(200);
}
if (buttons & BUTTON_DOWN)
{
Ki -= increment;
delay(200);
}
if ((millis() - lastInput) > 3000)  // return to RUN after 3 seconds idle
{
opState = RUN;
return;
}
lcd.setCursor(0,1);
lcd.print(Ki);
lcd.print(" ");
DoControl();
}
}

// ************************************************
// Derivative Tuning State
// UP/DOWN to change Kd
// RIGHT for setpoint
// LEFT for Ki
// SHIFT for 10x tuning
// ************************************************
void TuneD()
{
lcd.setBacklight(TEAL);
lcd.print(F("Set Kd"));

uint8_t buttons = 0;
while(true)
{
float increment = 0.01;
if (buttons & BUTTON_SHIFT)
{
increment *= 10;
}
if (buttons & BUTTON_LEFT)
{
opState = TUNE_I;
return;
}
if (buttons & BUTTON_RIGHT)
{
opState = RUN;
return;
}
if (buttons & BUTTON_UP)
{
Kd += increment;
delay(200);
}
if (buttons & BUTTON_DOWN)
{
Kd -= increment;
delay(200);
}
if ((millis() - lastInput) > 3000)  // return to RUN after 3 seconds idle
{
opState = RUN;
return;
}
lcd.setCursor(0,1);
lcd.print(Kd);
lcd.print(" ");
DoControl();
}
}

// ************************************************
// PID COntrol State
// SHIFT and RIGHT for autotune
// RIGHT - Setpoint
// LEFT - OFF
// ************************************************
void Run()
{
// set up the LCD's number of rows and columns:
lcd.print(F("Sp: "));
lcd.print(Setpoint);
lcd.write(1);
lcd.print(F("C : "));

SaveParameters();
myPID.SetTunings(Kp,Ki,Kd);

uint8_t buttons = 0;
while(true)
{
setBacklight();  // set backlight based on state

if ((buttons & BUTTON_SHIFT)
&& (buttons & BUTTON_RIGHT)
&& (abs(Input - Setpoint) < 0.5))  // Should be at steady-state
{
StartAutoTune();
}
else if (buttons & BUTTON_RIGHT)
{
opState = SETP;
return;
}
else if (buttons & BUTTON_LEFT)
{
opState = OFF;
return;
}

DoControl();

lcd.setCursor(0,1);
lcd.print(Input);
lcd.write(1);
lcd.print(F("C : "));

float pct = map(Output, 0, WindowSize, 0, 1000);
lcd.setCursor(10,1);
lcd.print(F("      "));
lcd.setCursor(10,1);
lcd.print(pct/10);
//lcd.print(Output);
lcd.print("%");

lcd.setCursor(15,0);
if (preheat)
{
lcd.print("P");
}
else if (tuning)
{
lcd.print("T");
}
else
{
lcd.print(" ");
}

// periodically log to serial port in csv format
if (millis() - lastLogTime > logInterval)
{
Serial.print(Input);
Serial.print(",");
Serial.println(Output);
}

delay(100);
}
}

// ************************************************
// Execute the control loop
// ************************************************
void DoControl()
{
// Read the input:
if (sensors.isConversionAvailable(0))
{
Input = sensors.getTempC(tempSensor);
sensors.requestTemperatures(); // prime the pump for the next one - but don't wait
}

if (tuning) // run the auto-tuner
{
if (aTune.Runtime()) // returns 'true' when done
{
FinishAutoTune();
}
}
else // Execute control algorithm
{
if (Setpoint - Input > 3)
{
preheat = true;            // suppress control until within range
Output = WindowSize;
}
else
{
preheat = false;
myPID.Compute();
}
}

// Time Proportional relay state is updated regularly via timer interrupt.
onTime = Output;
}

// ************************************************
// Called by ISR every 15ms to drive the output
// ************************************************
void DriveOutput()
{
long now = millis();
// Set the output
// "on time" is proportional to the PID output
if(now - windowStartTime>WindowSize)
{ //time to shift the Relay Window
windowStartTime += WindowSize;
}
if((onTime > 100) && (onTime > (now - windowStartTime)))
{
digitalWrite(RelayPin,HIGH);
}
else
{
digitalWrite(RelayPin,LOW);
}
}

// ************************************************
// Set Backlight based on the state of control
// ************************************************
void setBacklight()
{
if (tuning)
{
lcd.setBacklight(VIOLET); // Tuning Mode
}
else if (abs(Input - Setpoint) > 1.0)
{
lcd.setBacklight(RED);  // High Alarm - off by more than 1 degree
}
else if (abs(Input - Setpoint) > 0.2)
{
lcd.setBacklight(YELLOW);  // Low Alarm - off by more than 0.2 degrees
}
else
{
lcd.setBacklight(WHITE);  // We're on target!
}
}

// ************************************************
// Start the Auto-Tuning cycle
// ************************************************

void StartAutoTune()
{
// REmember the mode we were in
ATuneModeRemember = myPID.GetMode();

// set up the auto-tune parameters
aTune.SetNoiseBand(aTuneNoise);
aTune.SetOutputStep(aTuneStep);
aTune.SetLookbackSec((int)aTuneLookBack);
tuning = true;
}

// ************************************************
// ************************************************
void FinishAutoTune()
{
tuning = false;

// Extract the auto-tune calculated parameters
Kp = aTune.GetKp();
Ki = aTune.GetKi();
Kd = aTune.GetKd();

// Re-tune the PID and revert to normal control mode
myPID.SetTunings(Kp,Ki,Kd);
myPID.SetMode(ATuneModeRemember);

// Persist any changed parameters to EEPROM
SaveParameters();
}

// ************************************************
// Check buttons and time-stamp the last press
// ************************************************
{
uint8_t buttons = lcd.readButtons();
if (buttons != 0)
{
lastInput = millis();
}
return buttons;
}

// ************************************************
// Save any parameter changes to EEPROM
// ************************************************
void SaveParameters()
{
{
}
{
}
{
}
{
}
}

// ************************************************
// Load parameters from EEPROM
// ************************************************
{
// Load from EEPROM

// Use defaults if EEPROM values are invalid
if (isnan(Setpoint))
{
Setpoint = 60;
}
if (isnan(Kp))
{
Kp = 850;
}
if (isnan(Ki))
{
Ki = 0.5;
}
if (isnan(Kd))
{
Kd = 0.1;
}
}

// ************************************************
// Write floating point values to EEPROM
// ************************************************
void EEPROM_writeDouble(int address, double value)
{
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
}
}

// ************************************************
// Read floating point values from EEPROM
// ************************************************
{
double value = 0.0;
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
}
return value;
}
``````

dbick

Posts: 3
Joined: Fri Nov 22, 2013 6:42 pm

### Re: sous-vide desplay

I searched "souse vide" topics and saw you post on another thread that recomended setting Ki to Zero. I believe that is was around .3 or something before(from last nights Autotune) I changed it. I also saw where you recomended letting the cooker stabilize for a while at SP before trying autotune. That may be the problem as I did not do that last night on the systems first autotune attempt.I have been letting the cooker stabilize all day so I could attempt another Autotune. It started out very slow with the output below 20%. Once it neared the SP I let it sit to stabilize but it just kept getting hotter. I unplugged the unit and let it cool to below the SP then turned it back on. I watched it until it got to within .50 of setpoint and then started Autotune. Its been about an hour since then in autotune and it is still getting hotter. The SP is 64.5 and the current temp is 66.75 with 42.3% output. This is using a larger (6 Quart) slowcooker similar to the one in the OP.

Thank you for your speedy relpy and sorry to necro an old thread

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

Yea, autotune is not perfect. And with some of these cookers, the response time is so slow it is really hard to tell when you have truly reached equilibrium. The pre-heat cycle does seem to help in my preliminary tests. It keeps the integral term from building up too much during the preheat phase and causing serious overshoot.

dbick

Posts: 3
Joined: Fri Nov 22, 2013 6:42 pm

### Re: sous-vide desplay

Ok last night I let the system stabilize until it was near the SP for a while then tried AT once again. Even though the Temp was at or near the SP the power output was near 50% and the system just kept getting hotter. By the time AT was over, the system was at least 5 degrees over temp and the output was still near 50%. I turned it all off and loaded your new code that you posted above. I am not sure what you tweaked in that code but it sure seems to be working closer to what I would expect. After loading your code, the cooker was still a few degrees over SP but the power output was at 0%, which is more like what I would expect when it is over temp. After a few mins, the cooker cooled and then the power setting came on little by little, which is also closer to what I would expect. It kept the cooker at or very near the SP for over a hour so I decided to test it with two eggs. This is where I made a mistake as I added the eggs straight from the refrigerator and so they were cold. The cooker was at SP at 64.5C and after a minute dropped to around 62. The power output came up a bit but for the 2 hours that I let it cook, the cooker was never able to recover from the cooling and stayed around 63 or so. Pretty close I'd say. I expect that if I had at least let the eggs come to room temp I wouldn't have had this problem. Is that common practice for this type of cooking? Letting the food come to room temp before putting it in the cooker? In this situation, is there any manual change that I could have done if I see that the cooker is struggling to come back to SP?

Anyway, I really appreciate your help. It is much closer now to what I want and now I am going to test it out with a steak or two

Posts: 86938
Joined: Sat Feb 07, 2009 10:11 am

### Re: sous-vide desplay

Even though the Temp was at or near the SP the power output was near 50%
Being at or near setpoint is not the same as being at equilibrium. Temperatures change very slowly in these systems. It can take a long time for the oscillations to damp out. You may cross-over the setpoint temperature many times on the way up as well as on the way down. Autotune assumes that you are at equilibrium - meaning your power output is just enough to maintain the setpoint temperature.
I am not sure what you tweaked in that code but it sure seems to be working closer to what I would expect.
The new code waits until the system is close to the setpoint temperature before starting the PID algorithm. This prevents the integral term from winding up while the system is preheating.

fuceye

Posts: 25
Joined: Sun Nov 25, 2012 5:43 pm

### Re: sous-vide desplay

to save time i started my sp @ .5 deg from room temp before i did the auto tune...once i let it run through its cycle a bit i locked it in and reset the sp for desired temp for my cook...this was done without adding the extra code....not sure if that helps you but it sure helped me....

Please be positive and constructive with your questions and comments.