Project showcase
Smokerduino

Smokerduino © Apache-2.0

An Arduino based smoker controller.

  • 8,849 views
  • 2 comments
  • 22 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

I had built a Sous Vide controller using the Adafruit plans and parts. I soon revised it using a hand wired PCB. Since I had used a large solid state relay I thought it might work well for my smoker too.

All I really had to do was change the sensor form a Dallas 18B20 to a thermocouple and add a little more capacity to the SSR output. . The Dallas sensor tops out at about 250 degrees Fahrenheit.

I used a MAX6675 thermocouple module that connects to ta couple of binding posts on the back panel.

My particular smoker is electric and draws about 1500 watts but the controller will work on charcoal fired smokers too. You just use a smaller relay relay to run a squirrel cage fan and motorized damper instead of the big heating element.

I chose pin 5 for the relay output. You can change that in the code.

Step 1: Assembly

I had an old Serial Port Switch box lying around. I didn't bother to repaint the front panel and make it look nice like I usually do. I was probably too distracted form thinking about the smoked ribs I was gonna have this weekend.

I 3D printed a Bezel for the LCD display like on my other projects. It helps if you are a bit off on your cutouts.

Just download the Adafruit RGB LCD schematic and follow it or better yet, just buy the LCD Shield and Uno. I bought 3 of these RCB shields and they make quick work of prototyping.

I've made so many projects with this combo I finally broke down and made a couple of dozen PCB's.

Since their LCD has an RGB backlight and mine didn't I just put three LEDs on the front panel. Then I added another LED across the SSR.

Step 2: BIG Solid State Relay

I have a bunch of these lying around from my mainframe service days. Same relay I used in the last 3 Sous Vide Controllers I built. The only difference is this time it had to drive a 120 Volt 12.5A heating element. I grabbed a server CPU heat sink from an old chassis and used that. I also used a standard duplex outlet.

That pretty much filled the back panel.

Step 3: Arduino Compatible Controller

I made a custom Atmega 328P / MC 23017 controller board for my second generation Sous Vide controllers. I used the same board here. I just used a wall wart clamped down for the power supply.

If you don't want to make your own controller the software is still compatible with the Adafruit RGB LCD Backpack mounted to an Arduino or Genuino Uno. if you are doing a charcoal smoker and you don't need the high current relay the Adafruit SSR will work too.

Step 4: Something to Smoke

RIBS. That's all I really wanted. I added a Turkey Breast to distract others while I gobble up the Ribs.

Use whatever you like and season to taste. I like to limit the amount of seasoning. I used a simple dry rub of brown sugar and spices but they really don't even need that.

The Turkey got a Bourbon & Butter injection and a little of the leftover dry rub.

Step 5: The Hardest Part

The hardest part is the waiting. Just set the thermostat on the factory controller to Max and the SmokerDuino does the rest.

After 3 hours I did open the box to refill the tray with more Apple Juice and bourbon.

I made a little Turkey gravy too. I'm still amazed at how long that blazed for.

Step 6: Need Better Airflow

One of the many complaints about my smoker I read on the forums is it has poor airflow. I went to the local True Value and found a stove damper really cheap.

I could have just used a piece of 3 in stove pipe but I had these old side pipes on the scrap pile. Just had to slice em up enough to to get the nice chromed tip off.

The damper was a perfect fit. I drilled a hole for the shaft in the side of the pipe and then used a punch to locate the far side hole. That way I know it won't bind up.

Just have to drill a hole in the top and stuff it in.

Step 7: What's Next?

I will try the Charcoal smoker with this controller too. Just have to find a suitable fan to use.

I also want to try to make Bacon. There are some good resources out there on how to make Bacon at home. I just got some Prague powder at Chef's.

Central in Jersey and now I need a pork belly.

Code

SmokerDuino.inoArduino
//****************************************************************
//*  Name    : SmokerDuino PID Controller                        *
//*  Author  : Robert Joseph Korn                                *
//*  Notice  : Copyright (c) 2015 Open Valley Consulting Corp    *
//*  Date    : 10/18/15                                          *
//*  Version : 1.1                                               *
//*  Notes   :                                                   *
//*          :                                                   *
//****************************************************************
//-------------------------------------------------------------------
// Based on Sous Videno
// by Brady Doll
//
// Which was based on the Sous Vide Controller
// by Bill Earl - for Adafruit Industries
// https://github.com/adafruit/Sous_Viduino
// 
// which is in turn was
// Based on the Arduino PID and PID AutoTune Libraries
// by Brett Beauregard
// http://playground.arduino.cc/Code/PIDLibrary
// http://playground.arduino.cc//Code/PIDAutotuneLibrary
//
// Uses the Adafruit RGB LCD Shield library
// https://github.com/adafruit/Adafruit-RGB-LCD-Shield-Library
// 
//-----------
// Large LCD font based on Instructable:
// Custom Large Font for 16x2 LCDs by mpilchfamily
// http://www.instructables.com/id/Custom-Large-Font-For-16x2-LCDs/
//------------------------------------------------------------------
// ************************************************
// Compilation options
// 
// Comment/Uncomment to disable/enable options
// ************************************************

// LCD Options
#define CHARS 16		// Screen characters, Supports 16 or 20

// Support color LCD backlight
#define COLOR true		// Comment for monochrome backlight

// Enable serial logging
#define LOGGING true	// Comment to disable logging

// Default setpoint
#define SETPOINT_DEF 64.5

// Default Kp, Ki, & Kd values
#define KP_DEF 850
#define KI_DEF 0.5
#define KD_DEF 0.1


// ************************************************
// Libraries
// ************************************************

// Libraries for Adafruit LCD shield
#include <Wire.h>
//#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

// Libraries for the max6675 Thermocouple
#include "max6675.h"

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

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


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

//Define Variables we'll be connecting to
double Setpoint = SETPOINT_DEF;
double Input;
double Output;
// Output relay on time
volatile long onTime = 0;

// pid tuning parameters
double Kp = KP_DEF;
double Ki = KI_DEF;
double Kd = KD_DEF;

// EEPROM addresses for persisted data
const int SpAddress		= 0;
const int KpAddress		= 8;
const int KiAddress		= 16;
const int KdAddress		= 24;
const int configAddress	= 32;	// Bitmask, Unit settings, pump settings

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

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


// ************************************************
// Auto Tune Variables and constants
// ************************************************

byte ATuneModeRemember = 2;

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

// Tuning mode
boolean tuning = false;
// Auto Tuner
PID_ATune aTune(&Input, &Output);


// ************************************************
// Display variables and constants
// ************************************************

// Backlight colors
#if defined(COLOR)
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#endif
#define WHITE 0x7

// Large font segments
const int TOP_LEFT       = 0;
const int TOP_BAR        = 1;
const int TOP_RIGHT      = 2;
const int LOWER_LEFT     = 3;
const int LOWER_BAR      = 4;
const int LOWER_RIGHT    = 5;
const int TOP_MIDDLE_BAR = 6;
const int DEGREE         = 7;
const int BLANK          = 254;
const int SOLID          = 255;

// Define custom characters that make up segmetns
// Top left
uint8_t tl[8] = { B00111, B01111, B11111, B11111, B11111, B11111, B11111, B11111, };
// Top bar
uint8_t ub[8] = { B11111, B11111, B11111, B00000, B00000, B00000, B00000, B00000, };
// Top right
uint8_t tr[8] = { B11100, B11110, B11111, B11111, B11111, B11111, B11111, B11111, };
// Bottom left
uint8_t ll[8] = { B11111, B11111, B11111, B11111, B11111, B11111, B01111, B00111, };
// Bottom bar
uint8_t lb[8] = { B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111 };
// Bottom right
uint8_t lr[8] = { B11111, B11111, B11111, B11111, B11111, B11111, B11110, B11100 };
// Upper middle bar
uint8_t umb[8] = { B11111, B11111, B11111, B00000, B00000, B00000, B11111, B11111 };
// Degree
uint8_t deg[8] = { B01110, B11111, B11011, B11111, B01110, B00000, B00000, B00000 };

// Define large characters
uint8_t zero[6]  = { TOP_LEFT,       TOP_BAR,        TOP_RIGHT,      LOWER_LEFT, LOWER_BAR,  LOWER_RIGHT };
uint8_t one[6]   = { TOP_BAR,        TOP_RIGHT,      BLANK,          LOWER_BAR,  SOLID,      LOWER_BAR   };
uint8_t two[6]   = { TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, TOP_RIGHT,      LOWER_LEFT, LOWER_BAR,  LOWER_BAR   };
uint8_t three[6] = { TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, TOP_RIGHT,      LOWER_BAR,  LOWER_BAR,  LOWER_RIGHT };
uint8_t four[6]  = { LOWER_LEFT,     LOWER_BAR,      SOLID,          BLANK,      BLANK,      SOLID       };
uint8_t five[6]  = { LOWER_LEFT,     TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, LOWER_BAR,  LOWER_BAR,  LOWER_RIGHT };
uint8_t six[6]   = { TOP_LEFT,       TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, LOWER_LEFT, LOWER_BAR,  LOWER_RIGHT };
uint8_t seven[6] = { TOP_BAR,        TOP_BAR,        TOP_RIGHT,      BLANK,      BLANK,      SOLID       };
uint8_t eight[6] = { TOP_LEFT,       TOP_MIDDLE_BAR, TOP_RIGHT,      LOWER_LEFT, LOWER_BAR,  LOWER_RIGHT };
uint8_t nine[6]  = { TOP_LEFT,       TOP_MIDDLE_BAR, TOP_RIGHT,      BLANK,      BLANK,      SOLID       };
uint8_t c[6]     = { TOP_LEFT,       TOP_BAR,        TOP_BAR,        LOWER_LEFT, LOWER_BAR,  LOWER_BAR   };
uint8_t f[6]     = { TOP_LEFT,       TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, SOLID,      BLANK,      BLANK       };
uint8_t s[6]     = { TOP_LEFT,       TOP_MIDDLE_BAR, TOP_MIDDLE_BAR, LOWER_BAR,  LOWER_BAR,  LOWER_RIGHT };
uint8_t v[6]     = { LOWER_LEFT,     BLANK,          BLANK,          BLANK,      LOWER_LEFT, LOWER_RIGHT };
uint8_t blank[6] = { BLANK, BLANK, BLANK, BLANK, BLANK, BLANK };

// Initalize the Adafruit LCD shield
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// Flash state
unsigned long previousFlash = 0;
const int flashInterval = 500;
bool flashed = false;
int8_t prevFlash = -1;

// Current display offset
uint8_t x = 0;


// ************************************************
// Relay control
// ************************************************

// Output Relay
#define RelayPin 5



// ************************************************
// Logging
// ************************************************

// Optional logging support
#if defined(LOGGING)
const int logInterval = 10000; // log every 10 seconds
unsigned long lastLogTime = 0;
#endif


// ************************************************
// Mode variables and constants
// ************************************************

// Maximum number of menu items
const int MENU_MAX = 4;
const int SETTINGS_MENU_MAX = 6;
const int TUNE_MENU_MAX = 11;

// Menu timeout
const long menuTimeout = 20000; // 20s

enum mode {
	MENU = 0,
	RUN,			// --> begin Main menu
	SET_TEMP,
	DISPLAY_TEMP,
	SETTINGS,		// <-- end Main menu
	CHOOSE_UNITS,	// --> begin Settings menu
	TUNING,			// <-- end Settings menu
	TUNE_KP,		// --> begin Tuning menu
	TUNE_KI,
	TUNE_KD,
	AUTOTUNE		// <-- end Tuning menu
};
int8_t currentMode = MENU;
uint8_t previousMode = MENU;
unsigned long menuReturn = 0;

// Run PID
bool runPID = false;

// ************************************************
// Button variables and constants
// ************************************************

#define BUTTON_MENU BUTTON_SELECT

// Current selected button(s)
uint8_t buttons = 0;
// Delay interval for button registration
const unsigned long selectInterval = 90;
// Previous states of buttons
bool buttonStates[5] = {
	false,	// BUTTON_LEFT
	false,	// BUTTON_RIGHT
	false,	// BUTTON_UP
	false,	// BUTTON_DOWN
	false	// SELECT
};




// ************************************************
// Temperature interface constants

int tcDO = 6;
int tcCS = 7;
int tcCLK = 8;

// Load temperature sensor
MAX6675 tc(tcCLK, tcCS, tcDO);

// Temperature (for viewing)
float currentTemp;
// Change temperature
float changeTemp;
// Selected digit
int8_t selected = 0;

// Units
enum units { C = 0, F };
// Current unit
uint8_t currentUnits = C;
// Selected unit
uint8_t selectedUnits = currentUnits;

enum cycle {
	SHOW_TEMP = 0,
	SETP_TEMP,
	DUTY,
	DETAILED
};
uint8_t cycleDisplay = 0;
const int cycleInterval = 5000;


// Current time
unsigned long currentMillis = 0;
//// Misc Interval
unsigned long previousInterval = 0;
// Last selected item
int8_t lastSelect;
float changeValue = 0.0;
float prevValue = 0.0;




// ************************************************
// Setup
// ************************************************
void setup()
{
	// Initialize the LCD
	lcd.begin(CHARS, 2);

	lcd.clear();
#if defined(COLOR)
	lcd.setBacklight(GREEN);
#endif

	// Create custom characters segements
	lcd.createChar(TOP_LEFT, tl);
	lcd.createChar(TOP_BAR, ub);
	lcd.createChar(TOP_RIGHT, tr);
	lcd.createChar(LOWER_LEFT, ll);
	lcd.createChar(LOWER_BAR, lb);
	lcd.createChar(LOWER_RIGHT, lr);
	lcd.createChar(TOP_MIDDLE_BAR, umb);
	lcd.createChar(DEGREE, deg);

	// Display splash screen
	x = 1;
	customChar(s);
	lcd.print(F("moker"));
	x += 5;
        lcd.setCursor(x, 0); 
        lcd.write(255);  
        lcd.write(1); 
        lcd.write(2);
        lcd.setCursor(x, 1); 
        lcd.write(255);  
        lcd.write(4);  
        lcd.write(5);
 	lcd.print(F("uino"));
 
#if defined(LOGGING)
	Serial.begin(9600);
#endif

	// Initialize Output Relay Control
	pinMode(RelayPin, OUTPUT);    // Output mode to drive relay
	digitalWrite(RelayPin, LOW);  // Make sure it is off to start
	
	// Initialize the PID and related variables
	LoadParameters();
	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;

	myPID.SetMode(MANUAL);

	// Delay for splash screen
	delay(3000);
	lcd.clear();
	// Load menu
	changeMode(MENU);
}

// ************************************************
// Main Loop
// ************************************************
void loop()
{
	currentMillis = millis();
	buttons = lcd.readButtons();

	// Determine the current mode
	switch (currentMode) {
		case MENU:
			displayMenu(MENU);
			break;
		case RUN:
			Run();
			break;
		case SET_TEMP:
			setValue(true, 1, true, MENU);
			break;
		case DISPLAY_TEMP:
			displayTemp();
			break;
		case SETTINGS:
			displayMenu(SETTINGS);
			break;
		case CHOOSE_UNITS:
			chooseUnits();
			break;
		case TUNING:
			displayMenu(TUNING);
			break;
		case TUNE_KP:
			setValue(false, 0, true, TUNING);
			break;
		case TUNE_KI:
		case TUNE_KD:
			setValue(false, 2, false, TUNING);
			break;
	}

	if (runPID) {
		doControl();
	}
}



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



// ************************************************
// Mode Functions
// ************************************************

/// Handles mode changes
///
/// <param name="newMode"></param>
void changeMode( uint8_t newMode )
{

	// Reset the button states
	for (uint8_t i = 0; i < 5; i++) {
		buttonStates[i] = false;
	}

	bool celsius = (C == currentUnits);

	previousMode = currentMode;

	switch (previousMode) {
		case SET_TEMP:
			Setpoint = (celsius) ? changeValue : (changeValue - 32) * 0.555555555555556;
			SaveParameters();
			break;
		case TUNE_KP:
			Kp = changeValue;
			SaveParameters();
			myPID.SetTunings(Kp, Ki, Kd);
			break;
		case TUNE_KI:
			Ki = changeValue;
			SaveParameters();
			myPID.SetTunings(Kp, Ki, Kd);
			break;
		case TUNE_KD:
			Kd = changeValue;
			SaveParameters();
			myPID.SetTunings(Kp, Ki, Kd);
			break;
	}

	switch (newMode) {
		case MENU:
		case SETTINGS:
		case TUNING:
#if defined(COLOR)
			lcd.setBacklight(BLUE);
#endif
			lastSelect = -1;
			selected = currentMode;
			menuReturn = previousInterval = currentMillis;
			break;
		case RUN:
			if (!runPID) {
				myPID.SetMode(AUTOMATIC);
				windowStartTime = millis();
				runPID = true;
			}
			previousInterval = currentMillis;
			cycleDisplay = SHOW_TEMP;
			getTemp(celsius);
			break;
		case SET_TEMP:
#if defined(COLOR)
			lcd.setBacklight(TEAL);
#endif
			prevValue = changeValue = (celsius) ? Setpoint : (Setpoint * 1.8) + 32;
			selected = 2;
			lcd.clear();
			printTemp(changeValue, celsius, true, true, 1);
			prevFlash = -1;
			break;
		case DISPLAY_TEMP:
			if (runPID) {
				turnOff();
				changeMode(MENU);
				return;
			} else {
#if defined(COLOR)
				lcd.setBacklight(GREEN);
#endif
				getTemp(celsius);
			}
			break;
		case CHOOSE_UNITS:
#if defined(COLOR)
			lcd.setBacklight(TEAL);
#endif
			lcd.clear();
			
			lcd.setCursor(0, 0);
			lcd.print(F("Units"));

			selectedUnits = currentUnits;
			displayUnitChooser();
			break;
		case TUNE_KP:
#if defined(COLOR)
			lcd.setBacklight(TEAL);
#endif
			prevValue = changeValue = Kp;
			selected = 2;
			lcd.clear();
			x = (CHARS - 12) / 2;
			printLargeNumber(changeValue, 0, true);
			prevFlash = -1;
			break;
		case TUNE_KI:
#if defined(COLOR)
			lcd.setBacklight(TEAL);
#endif
			prevValue = changeValue = Ki;
			selected = 2;
			lcd.clear();
			x = (CHARS - 10) / 2;
			printLargeNumber(changeValue, 2, false);
			prevFlash = -1;
			break;
		case TUNE_KD:
#if defined(COLOR)
			lcd.setBacklight(TEAL);
#endif
			prevValue = changeValue= Kd;
			selected = 2;
			lcd.clear();
			x = (CHARS - 10) / 2;
			printLargeNumber(changeValue, 2, false);
			prevFlash = -1;
			break;
		case AUTOTUNE:
			if (abs(Input - Setpoint) < 0.5) {
				// Remember the mode we're in
				ATuneModeRemember = myPID.GetMode();

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

				// Switch to the RUN mode
				changeMode(RUN);
			} else {
				lcd.clear();
				lcd.setCursor(0, 0);
				lcd.print(F("The temperature must"));
				lcd.setCursor(0, 1);
				lcd.print(F("be within 0.5"));
				lcd.write(B11011111);
				lcd.print(F("C"));
				delay(cycleInterval);

				changeMode(SETTINGS);
			}
			return;
			break;
	}

	currentMode = newMode;
}

/// Displays the menu
/// 
///
void displayMenu( uint8_t menu )
{
	bool change = false;

	if (buttons) {
		if ((currentMillis - menuReturn) > flashInterval ) {
			//if (checkButton(BUTTON_MENU)) {
			//	if ((currentMillis - menuReturn) > flashInterval ) {
			//		// Change back to the previous mode
			//		changeMode(previousMode);
			//	}
			//} else if (checkButton(BUTTON_UP)) {
			if (checkButton(BUTTON_UP)) {
				selected--;
			} else if(checkButton(BUTTON_DOWN)) {
				selected++;
			} else if (checkButton(BUTTON_RIGHT)) {
				changeMode(selected);
				return;
			} else if (MENU != menu && (checkButton(BUTTON_LEFT) || checkButton(BUTTON_MENU))) {
				if (SETTINGS == menu) {
					changeMode(MENU);
					selected = SETTINGS;
				} else if (TUNING == menu) {
					changeMode(SETTINGS);
					selected = TUNING;
				}
			}

			previousInterval = currentMillis;
		}
	} else {
		// Reset the button states
		for (uint8_t i = 0; i < 5; i++) {
			buttonStates[i] = false;
		}

		
		// Check if we have reached our timout interval
		if ((currentMillis - previousInterval) > menuTimeout) {
			if (runPID) {
				changeMode(RUN);
			} else {
				turnOff();
				// Turn off the display backlight
				lcd.setBacklight(0);

				// Wait until a button is pressed
				while (!buttons) {
					buttons = lcd.readButtons();
				}

				menuReturn = previousInterval = millis();

				// Turn the backlight back on
#if defined(COLOR)
				lcd.setBacklight(BLUE);
#else
				lcd.setBacklight(WHITE);
#endif
			}
		}
	}

	x = 0;

	uint8_t min;
	uint8_t max;

	// Get the item ranges for the selected menu
	if (MENU == menu) {
		min = 1;
		max = MENU_MAX;
	} else if (SETTINGS == menu) {
		min = MENU_MAX + 1;
		max = SETTINGS_MENU_MAX;
	} else if (TUNING == menu) {
		min = SETTINGS_MENU_MAX + 1;
		max = TUNE_MENU_MAX;
	}

	// Make sure the currently selected item is within the range
	if (min > selected) {
		selected = min;
	} else if (max < selected) {
		selected = max;
	}

	// Check if the selected item has changed
	if (selected != lastSelect) {
		bool celsius = (C == currentUnits);

		lcd.clear();

		int selected_num = selected;
		if (SETTINGS == menu) {
			selected_num -= MENU_MAX;
		} else if (TUNING == menu) {
			selected_num -= SETTINGS_MENU_MAX;
		}

		printLargeDigit(selected_num);
		x++;
		lcd.setCursor(x, 0);

		switch ( selected ) {
			case RUN:
				if (runPID) {
					if (tuning) {
						lcd.print(F("Tuning"));
					} else {
						lcd.print(F("Running"));
					}
				} else {
					lcd.print(F("Run"));
				}

				lcd.setCursor(( (celsius) ? CHARS - 9 : CHARS - 10 ), 1);
				lcd.print(F("SP:"));
				lcd.print( (celsius) ? Setpoint : (Setpoint * 1.8) + 32 );
				lcd.setCursor(CHARS - 2, 1);
				lcd.write(B11011111); // Degree symbol
				lcd.print( (celsius) ? 'C' : 'F' );
				break;
			case SET_TEMP:
				lcd.print(F("Set"));
				lcd.setCursor(x, 1);
				lcd.print(F("Temperature"));
				break;
			case DISPLAY_TEMP:
				if (runPID) {
					lcd.print(F("Stop"));
					lcd.setCursor(x, 1);
					lcd.print(F("Running"));
				} else {
					lcd.print(F("Display"));
					lcd.setCursor(x, 1);
					lcd.print(F("Temperature"));
				}
				break;
			case SETTINGS:
				lcd.print(F("Settings"));
				lcd.setCursor(x,1);
				break;
		// Settings Menu
			case CHOOSE_UNITS:
				lcd.print(F("Set"));
				lcd.setCursor(x, 1);
				lcd.print(F("Units"));
				lcd.setCursor(CHARS - 1, 1);
				lcd.print( (celsius) ? 'C' : 'F' );
				break;
			case TUNING:
				lcd.print(F("Configure"));
				lcd.setCursor(x, 1);
				lcd.print(F("Tuning"));
				break;
		// Tuning Menu
			case TUNE_KP:
				lcd.print(F("Tune"));
				lcd.setCursor(x, 1);
				lcd.print(F("Kp"));
				if (Kp < 100) {
					lcd.setCursor(CHARS - 5, 1);
				} else if (Kp < 1000) {
					lcd.setCursor(CHARS - 6, 1);
				} else {
					lcd.setCursor(CHARS - 7, 1);
				}
				lcd.print(Kp);
				break;
			case TUNE_KI:
				lcd.print(F("Tune"));
				lcd.setCursor(x, 1);
				lcd.print(F("Ki"));
				lcd.setCursor(CHARS - 4, 1);
				lcd.print(Ki);
				break;
			case TUNE_KD:
				lcd.print(F("Tune"));
				lcd.setCursor(x, 1);
				lcd.print(F("Kd"));
				lcd.setCursor(CHARS - 4, 1);
				lcd.print(Kd);
				break;
			case AUTOTUNE:
				lcd.print(F("Auto"));
				lcd.setCursor(x,1);
				lcd.print(F("Tuning"));
				break;
		}

		lcd.setCursor(CHARS - 1, 0);
		lcd.write(B01111110);

		lastSelect = selected;
	}
}

// Handles the run state
//
//
void Run() {
	bool celsius = (C == currentUnits);

	if (buttons) {
		if (checkButton(BUTTON_MENU)) {
			changeMode(MENU);
			return;
		} else if (buttons & BUTTON_LEFT && buttons & BUTTON_RIGHT) {
			if (DETAILED != cycleDisplay) {
				cycleDisplay = DETAILED;
				lcd.clear();
				lcd.setCursor(0, 0);
				lcd.print(F("Sp: "));
				lcd.print((celsius) ? Setpoint : (Setpoint * 1.8) + 32);
				lcd.write(B11011111);	// degree symbol
				lcd.print((celsius) ? 'C' : 'F');

				lcd.setCursor(CHARS - 1, 0);
				if (tuning) {
					lcd.print(F("T"));
				} else {
					lcd.write(BLANK);
				}
			}
		}
	} else {
		for (uint8_t i = 0; i < 5; i++) {
			buttonStates[i] = false;
		}
	}

	if (DETAILED != cycleDisplay) {
		if ((SHOW_TEMP == cycleDisplay && (currentMillis - previousInterval) > menuTimeout) || ((SHOW_TEMP != cycleDisplay) && (currentMillis - previousInterval) > cycleInterval)) {
			cycleDisplay++;

			if (cycleDisplay > 2) {
				cycleDisplay = 0;
			}

			if (SETP_TEMP == cycleDisplay) {
				float setpTemp = (celsius) ? Setpoint : (Setpoint * 1.8) + 32;
				printTemp(setpTemp, celsius, true, true, 1);
				lcd.setCursor(0, 0);
				lcd.print(F("S"));
				lcd.setCursor(0, 1);
				lcd.print(F("P"));
				
				lcd.setCursor(CHARS - 1, 0);
				if (tuning) {
					lcd.print(F("T"));
				} else {
					lcd.write(BLANK);
				}
			} else if (DUTY == cycleDisplay) {
				lcd.clear();
				lcd.setCursor(CHARS - 2, 0);
				lcd.print(F("%"));

				lcd.setCursor(CHARS - 1, 0);
				if (tuning) {
					lcd.print(F("T"));
				} else {
					lcd.write(BLANK);
				}
			}

			previousInterval = currentMillis;
		}
	}

	float pct;

	switch (cycleDisplay) {
		case SHOW_TEMP:
			printTemp(currentTemp, celsius, false, true, 2);
			
			lcd.setCursor(CHARS - 1, 0);
			if (tuning) {
				lcd.print(F("T"));
			} else {
				lcd.write(BLANK);
			}
			break;
		case DUTY:
			pct = (map(Output, 0, WindowSize, 0, 1000) / 10);
			x = (CHARS - 14) / 2;
			printLargeNumber(pct, 2, false);
			break;
		case DETAILED:
			lcd.setCursor(0, 1);
			lcd.print(currentTemp);
			lcd.write(B11011111);	// degree symbol
			if (celsius) {
				lcd.print(F("C :"));
			} else {
				lcd.print(F("F :"));
			}
			pct = (map(Output, 0, WindowSize, 0, 1000) / 10);
			if (pct < 100) {
				lcd.write(BLANK);
			}
			lcd.print(pct);
			lcd.print("%");
			break;
	}

#if defined(COLOR)
	setBacklight();
#endif
}

///
///
///
void turnOff() {
	runPID = false;

	myPID.SetMode(MANUAL);
	// Turn off the relay
	digitalWrite(RelayPin, LOW); // make sure it is off
}

// ************************************************
// Execute the control loop
// ************************************************
void doControl() {
	getTemp(C == currentUnits);

	if (tuning) {
		if (aTune.Runtime()) {	// returns 'true' when done
			FinishAutoTune();
		}
	} else {
		// TODO: Add feed forward/quick heat mode
		myPID.Compute();
	}

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

#if defined(LOGGING)
	// Periodically log to the serial port in CSV format
	if (currentMillis - lastLogTime > logInterval) {
		Serial.print(Input);
		Serial.print(",");
		Serial.print(Output);
		Serial.print(",");
		Serial.print(Setpoint);

		lastLogTime = currentMillis;
	}
#endif
}

///
void displayTemp() {
	if (buttons) {
		if (checkButton(BUTTON_MENU)) {
			changeMode(MENU);
			return;
		}
	} else {
		for (int i = 0; i < 5; i++) {
			buttonStates[i] = false;
		}
	}

	bool celsius = (C == currentUnits);

	getTemp(celsius);

	printTemp(currentTemp, celsius, false, true, 2);

#if defined(LOGGING)
	// Periodically log to the serial port in CSV format
	if (currentMillis - lastLogTime > logInterval) {
		Serial.print(Input);
		Serial.println(",0,0");

		lastLogTime = currentMillis;
	}
#endif
}

///
///
///
...

This file has been truncated, please download it to see its full contents.

Schematics

Schematic
Check out learn.adafrui.com and follow their Sous Vide build. My only hardware changes are the massive SSR and the Thermocouple module
Smoker

Comments

Similar projects you might like

Weather Station v.1.0

Project showcase by derapados

  • 10,388 views
  • 3 comments
  • 36 respects

Temperature and Humidity Logger (Using Arduino)

Project showcase by lmsousa

  • 7,786 views
  • 3 comments
  • 34 respects

LIDAR Anywhere

Project showcase by truthkos

  • 6,869 views
  • 1 comment
  • 8 respects

Wifi Arduino-based Music Player

Project showcase by Vaughn Kottler

  • 3,970 views
  • 2 comments
  • 9 respects

Smoke Sensor

Project showcase by Jiss Anto

  • 4,863 views
  • 0 comments
  • 8 respects

Magic in the Bedroom

Project in progress by Randa El Ali and Jesse Laprad

  • 2,103 views
  • 0 comments
  • 3 respects
Add projectSign up / Login