Here is the code I am running for those interested. Knock yourselves out. A side note: you have to reprogram the stock boot loader on the arduino chips in order to run the wdt, and I have not done that yet. All the other bells and whistles are in there, though. I wouldn't suggest using this regularly until that safety is setup, however.
Lastly, I am running a graphical user interface with the PID controller that is built in processing (check on wiki if you have never heard of it. It is great.). This gui lets me change the tuning parameters and graph the output without rebooting the chip or interrupting the process. The nicest part is I can change the tuning parameters and watch the thing oscillate. This makes behavior like overshoot and oscillation stand out much quicker. Anyway here is a link:
http://brettbeauregard.com/blog/2009/07/pid-front-end-v02/
//Vape 0.7 On/Off
//libraries
#include <PID_Beta6.h>
#include <Bounce.h>
//pin definitions
#define YLED 2 //Yellow LED connected to pin 2
#define RLED 3 //Red component of LED connected to pwm pin 3
#define GLED 4 //Green component of LED connected to pwm pin 4
#define BLED 5 //Blue component of LED connected to pwm pin 5
#define OUT 6 //Heater output on pin 6
#define POW 7 //power button input on pin 7
#define BTN1 8 //Button 1 input on pin 8
#define BTN2 9 //Button 2 input on pin 9
#define BTN3 10 //Button 3 input on pin 10
#define CS 11 //
#define SO 12 //serial for MAX6675
#define SCK 13 //clock out for MAX6675
//MAX6675 temperature variables
float offset_tc = 0; //Temperature compensation error
int samples = 5;
int bad_tc = 0; //flag for dead thermocouple
//PID varibles
double input, output, setpoint;
int cycle_time = 250;
double default_setpoint = 430;
int highest_setpoint = 500;
int lowest_setpoint = 360;
PID pid(&input, &output, &setpoint, 20,285,2.5); //pid object
//button variables
int btnpress1 = 0; //temp variable for button press recognition
int btnpress2 = 0; //temp variable for button press recognition
int btnpress3 = 0; //temp variable for button press recognition
int powpress = 0; //temp variable for button press recognition
int button_flag = 0; //prevents multiple recognitions of a single button press (1 while button is pressed, 0 otherwise)
Bounce bouncer1 = Bounce(BTN1, 5); //debouncer object for button 1
Bounce bouncer2 = Bounce(BTN2, 5); //debouncer object for button 2
Bounce bouncer3 = Bounce(BTN3, 5); //debouncer object for button 3
Bounce bouncerpow = Bounce(POW, 5); //debouncer object for power button
//power on variables
int power_flag = 0; //determines if program runs in power on loop or power off loop
int start_flag = 0; //stores wether or not the startup LED test sequence has run
//time variables
unsigned long counter; //length of power button press
unsigned long start_time = 0; //time that current loop started
unsigned long serialTime; //this will help us know when to talk with processing
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup()
{
pinMode(RLED,OUTPUT); //sets the digital pin as output
pinMode(GLED,OUTPUT); //sets the digital pin as output
pinMode(BLED,OUTPUT); //sets the digital pin as output
pinMode(YLED,OUTPUT); //sets the ditital pin as output
pinMode(OUT,OUTPUT); //sets the digital pin as output
pinMode(POW,INPUT); //sets the digital pin as input (powers unit on and off)
pinMode(BTN1,INPUT); //sets the digital pin as input (adjusts setpoint -10 degrees F)
pinMode(BTN2,INPUT); //sets the digital pin as input (resets setpoint to default_setpoint)
pinMode(BTN3,INPUT); //sets the digital pin as input (adjusts setpoint +10 degrees F)
pinMode(CS,OUTPUT); //sets the digital pin as input (MAX6675)
pinMode(SO,INPUT); //sets the digital pin as output (MAX6675)
pinMode(SCK,OUTPUT); //sets the digital pin as input (MAX6675)
Serial.begin(9600); //starts serial communication with computer
//Initialize PID variables
input = 0;
output = 0;
setpoint = default_setpoint;
pid.SetOutputLimits(0,cycle_time); //sets output range
pid.SetInputLimits(0,1873); //sets input range
pid.SetSampleTime(250); //PID samples every 250 ms
pid.SetMode(AUTO); //turn on the PID
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop()
{
/*
/////////////////////////////////////////////////////////////Power Off Logic///////////////////////////////////////////////
//power on routine that waits for power a button press of a certain length
if(power_flag == 0) {
start_time = millis(); //set cycle start time
//turn LED's off
digitalWrite(YLED,LOW);
digitalWrite(RLED,LOW);
digitalWrite(GLED,LOW);
//check for power button press
bouncerpow.update();
powpress = bouncerpow.read();
if(powpress == HIGH) {
counter = millis();
while(powpress == HIGH) {
bouncerpow.update();
powpress = bouncerpow.read();
digitalWrite(YLED,HIGH);
digitalWrite(BLED,LOW);
if(millis() - counter > 3000) {
power_flag = 1;
digitalWrite(YLED,LOW);
powpress = LOW;
counter = 0;
}
}
} else {
counter = 0;
digitalWrite(YLED,LOW);
for(int i = 1; i < 255; i++) {
if(digitalRead(POW) == HIGH) {
break;
}
analogWrite(BLED, i);
delay(7);
}
for(int i = 255; i > 1; i--) {
if(digitalRead(POW) == HIGH) {
break;
}
analogWrite(BLED,i);
delay(7);
}
}
}
*/
////////////////////////////////////////////////////////Power On Logic////////////////////////////////////////////////////////////////
//power on routing that runs once the power button has been pressed for a defined period of time
//if(power_flag == 1) {
start_time = millis(); //set cycle start time
//*******************************************************Startup Routine*************************************************************
//startup sequence that executres if the unit has just been turned on
if(start_flag == 0)
{
digitalWrite(BLED, HIGH);
delay(1000);
digitalWrite(BLED, LOW);
digitalWrite(GLED, HIGH);
delay(1000);
digitalWrite(GLED, LOW);
digitalWrite(RLED, HIGH);
delay(1000);
digitalWrite(RLED, LOW);
digitalWrite(YLED, HIGH);
delay(500);
digitalWrite(YLED, LOW);
delay(500);
digitalWrite(YLED, HIGH);
delay(500);
digitalWrite(YLED, LOW);
delay(500);
digitalWrite(YLED, HIGH);
delay(500);
digitalWrite(YLED, LOW);
delay(500);
start_flag++;
}
//*****************************************************************Read Temperature from MAX6675***********************************************
input = 0; //clear input
//reading heater temp from MAX6675
for(int i=samples; i>0; i--) {
digitalWrite(CS,LOW); // Enable device
/* Cycle the clock for dummy bit 15 */
digitalWrite(SCK,HIGH);
digitalWrite(SCK,LOW);
/* Read bits 14-3 from MAX6675 for the Temp
Loop for each bit reading the value and
storing the final value in 'input'
*/
for (int i=11; i>=0; i--){
digitalWrite(SCK,HIGH); // Set Clock to HIGH
input += digitalRead(SO) << i; // Read data and add it to our variable
digitalWrite(SCK,LOW); // Set Clock to LOW
}
/* Read the TC Input inp to check for TC Errors */
digitalWrite(SCK,HIGH); // Set Clock to HIGH
bad_tc = digitalRead(SO); // Read data
digitalWrite(SCK,LOW); // Set Clock to LOW
digitalWrite(CS,HIGH); //Disable Device
}
input /= samples; // Divide the value by the number of samples to get the average
/*
Keep in mind that the temp that was just read is on the digital scale
from 0C to 1023.75C at a resolution of 2^12. We now need to convert
to an actual readable temperature. Now multiply by 0.25 for deg C.
The final value is converted to an int and returned at x10 power.
*/
input += offset_tc; // Insert the calibration error value
/* Output 999.9 if there is a TC error, otherwise convert to deg F and make temp equal input*/
if(bad_tc == 1) {
input = 999.9;
} else {
input = ((input*0.25) * (9.0/5.0)) + 32.0;;
}
//exectures for bad thermocouple
if(input > 999) {
digitalWrite(BLED,HIGH);
delay(1000);
digitalWrite(BLED,LOW);
digitalWrite(RLED,HIGH);
delay(1000);
digitalWrite(RLED,LOW);
digitalWrite(BLED,HIGH);
delay(1000);
digitalWrite(BLED,LOW);
digitalWrite(RLED,HIGH);
delay(1000);
digitalWrite(RLED,LOW);
power_flag = 0; //kicks back out to power off state
}
//****************************************************Temperature Indication Logic****************************************************
//executes if temp is low
if(input < setpoint - 5) {
digitalWrite(BLED,HIGH);
} else {
digitalWrite(BLED,LOW);
}
//executes if temp is within window
if(input >= setpoint -5 && input <= setpoint + 5) {
digitalWrite(GLED,HIGH);
} else {
digitalWrite(GLED,LOW);
}
//executes if temp is high
if(input > setpoint + 5) {
digitalWrite(RLED,HIGH);
} else {
digitalWrite(RLED,LOW);
}
//****************************************************Button Logic*****************************************************
//button debouncer updates
bouncer1.update();
bouncer2.update();
bouncer3.update();
bouncerpow.update();
//button press checks
btnpress1 = bouncer1.read();
btnpress2 = bouncer2.read();
btnpress3 = bouncer3.read();
powpress = bouncerpow.read();
//power off routine that waits for a power button press of a certain length
if(powpress == HIGH) {
counter = millis(); //counter is reset in the no button pressed routine below
while(powpress == HIGH) {
bouncerpow.update();
powpress = bouncerpow.read();
Serial.println(millis() - counter);
digitalWrite(YLED,HIGH);
if(millis() - counter > 3000) {
power_flag = 0;
digitalWrite(YLED,LOW);
powpress = LOW;
}
}
}
//executes when a button is first pressed, but bypasses once buttonflag is HIGH
if(button_flag == 0) {
//button 1 logic decreases setpoint
if(btnpress1 == HIGH) {
digitalWrite(YLED,HIGH);
setpoint -= 10;
button_flag = 1;
}
//button 2 logic resets setpoint to default, but also executres reset if setpoint is out of acceptable window
if(btnpress2 == HIGH || setpoint < lowest_setpoint || setpoint > highest_setpoint) {
digitalWrite(YLED,HIGH);
setpoint = default_setpoint;
button_flag = 1;
}
//button 3 logic increases setpoint
if(btnpress3 == HIGH) {
digitalWrite(YLED,HIGH);
setpoint += 10;
button_flag = 1;
}
}
//executes only if no button is pressed
if(btnpress1 + btnpress2 + btnpress3 + powpress == 0) {
digitalWrite(YLED,LOW);
button_flag = 0;
counter = 0;
}
//**************************************************PID Logic*******************************************************************
pid.Compute(); //give the PID the opportunity to compute if needed
//turn the output on for the portion of the cycle time specified by output
while(output > millis() - start_time) {
digitalWrite(OUT,HIGH);
}
//turn power off for the remainder of the cycle time not demanded by output
while(cycle_time > millis() - start_time) {
digitalWrite(OUT,LOW);
}
//*************************************************Processing********************************************************************
//send-receive with processing if it's time
if(millis()>serialTime)
{
SerialReceive();
SerialSend();
serialTime+=500;
}
//}
}
/********************************************
* Serial Communication functions / helpers
********************************************/
union { // This Data structure lets
byte asBytes[24]; // us take the byte array
float asFloat[6]; // sent from processing and
} // easily convert it to a
foo; // float array
// getting float values from processing into the arduino
// was no small task. the way this program does it is
// as follows:
// * a float takes up 4 bytes. in processing, convert
// the array of floats we want to send, into an array
// of bytes.
// * send the bytes to the arduino
// * use a data structure known as a union to convert
// the array of bytes back into an array of floats
// the bytes coming from the arduino follow the following
// format:
// 0: 0=Manual, 1=Auto, else = ? error ?
// 1-4: float setpoint
// 5-8: float input
// 9-12: float output
// 13-16: float P_Param
// 17-20: float I_Param
// 21-24: float D_Param
void SerialReceive()
{
// read the bytes sent from Processing
int index=0;
byte Auto_Man = -1;
while(Serial.available()&&index<25)
{
if(index==0) Auto_Man = Serial.read();
else foo.asBytes[index-1] = Serial.read();
index++;
}
// if the information we got was in the correct format,
// read it into the system
if(index==25 && (Auto_Man==0 || Auto_Man==1))
{
setpoint=double(foo.asFloat[0]);
//Input=double(foo.asFloat[1]); // * the user has the ability to send the
// value of "Input" in most cases (as
// in this one) this is not needed.
if(Auto_Man==0) // * only change the output if we are in
{ // manual mode. otherwise we'll get an
output=double(foo.asFloat[2]); // output blip, then the controller will
} // overwrite.
double p, i, d; // * read in and set the controller tunings
p = double(foo.asFloat[3]); //
i = double(foo.asFloat[4]); //
d = double(foo.asFloat[5]); //
pid.SetTunings(p, i, d); //
if(Auto_Man==0) pid.SetMode(MANUAL);// * set the controller mode
else pid.SetMode(AUTO); //
}
Serial.flush(); // * clear any random data from the serial buffer
}
// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings. much easier than getting
// floats from processing to here no?
void SerialSend()
{
Serial.print("PID ");
Serial.print(setpoint);
Serial.print(" ");
Serial.print(input);
Serial.print(" ");
Serial.print(output);
Serial.print(" ");
Serial.print(pid.GetP_Param());
Serial.print(" ");
Serial.print(pid.GetI_Param());
Serial.print(" ");
Serial.print(pid.GetD_Param());
Serial.print(" ");
if(pid.GetMode()==AUTO) Serial.println("Automatic");
else Serial.println("Manual");
}