// Software to control a 2 Channel temperature controller.
// Sensing can be either done by Thermistor or temperature sensor e.g. DHT types.
// One channel could be for heating and the second for cooling
// Opto isolated MOSFETS driven by PWM can accurately control temp. by means of a PID loop
//
// User interface by means of OLED display
// Possible to expand to use network interface (this could be USB Serial attached device, XBee or WizNet.
// with the optional bus connected XBEE, or Ethernet module.
// Alternative is to connect a SD card module and store results on the card
//
// Written for PCB v 4.1.x
// Search code for Modify here
// and adapt for either Thermistor or DHT
//
// Credits
// For Info: - Kudos to the following:
// http://disipio.wordpress.com/2009/07/17/temperature-measurement-using-arduino-and-a-thermistor/
// http://playground.arduino.cc//Code/PIDLibraryRelayCh1_PWM_OutputExample
// Visual Micro Debugger - http://www.visualmicro.com/
// The open source community - where one can learn so much
//Calculating Steinhart-Hart coefficients...using table from NTC thermistor supplier and java app from
// http://thermistor.sourceforge.net/
// to calculate....
// for 100K glass thermistor
//a[0] = 9.256546881300105e-004
//a[1] = 1.466485154162096e-004
//a[2] = 6.994684259553801e-006
//a[3] = -1.221152290793335e-007
// for 100R bead
//a[0] = 1.938673445820002e-003
//a[1] = 2.989388530840342e-004
//a[2] = 1.355307994901136e-006
//a[3] = 9.684853535359814e-008
//-----------------------------------------------------------------------------------------------------
//Includes - Libraries needed
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <Wire.h>
#include <PID_v1.h>
#include <SPI.h>
#include <math.h>
#include <avr/wdt.h>
#include <TimeAlarms.h>
#include <TimeLib.h>
//-----------------------------------------------------------------------
// Modify here
// if using DHTs
#include <dht.h> //if using DHT sensors
dht DHT_Ch1;
dht DHT_Ch2;
// Modify here
// Display Hardware connected - OLED Display
#define OLED_CS 7
#define OLED_DC 11
#define OLED_CLK 13
#define OLED_DATA 12
#define OLED_RESET -1
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
//#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
//Hows your Heating hardware connected?
//using thermistor
//#define Ch1_IN_PIN A4 //A4 or A5 if thermistor
//#define Ch2_IN_PIN A5 //A4 or A5 if thermistor
//using DHT
#define Ch1_IN_PIN A0 //A0 or A1 if DHT
#define Ch2_IN_PIN A1 //A0 or A1 if DHT
//you may have to swap these if rotating display, may need to swap rotation direction
#define Pot_Ch1_PIN A2 //R4 on PCB
#define Pot_Ch2_PIN A3 //R5 on PCB
#define Ch1_PWM_OUT_PIN 9 //(PWM on 3,5,6,9,10,11)
#define Ch2_PWM_OUT_PIN 6 //(PWM on 3,5,6,9,10,11)
#define btnSTAT_RESET 8 // Digital In to reset stats
//#define btnAUX_RESET 8 // Digital In
//#define led 12 //visual alarm or buzzer output - future?
//-----------------------------------------------------------------------
//Adafruit_SSD1306 OLED_display(OLED_DATA, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
Adafruit_SSD1306 OLED_display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//Other parameters
#define oneminute 60000 // 60 000 milliseconds per minute
//-----------------------------------------------------------------------
// Modify here
//Calibration - actual measurements, test your actual values and enter them here
double R_Ch1 = 99.8; // in Ohms... for a 100K resistance put in parallel
double R_Ch2 = 99.9; // in Ohms... for a 100K resistance put in parallel
double V_Supply = 5.46; // Your supply voltage to the micro controller
// nice to know but not essential
double Ch1_initial_Startpoint = 20.0; // approx room temperature
double Ch1_heating_rate = 120; // degree per minute
double Ch2_initial_Startpoint = 20.0; // approx room temperature
double Ch2_heating_rate = 120; // degree per minute
int TMIN_Ch1 = -50; // min temp (C) we want to on the Pot
int TMAX_Ch1 = 90; // max temp (C) we want to on the Pot
int Toffset_Ch1 = 0; // if we need an offset
int TMIN_Ch2 = 0; // min temp (C) we want to on the Pot
int TMAX_Ch2 = 200; // max temp (C) we want to on the Pot
int Toffset_Ch2 = 0; // if we need an offset
//These are my calculated Steinhart coefficients for the thermistor(s) I am using...
//(please duplicate for Ch2 if needed)
// for 100K
// double A1 = 9.256546881300105e-004;
// double B1 = 1.466485154162096e-004;
// double C1 = 6.994684259553801e-006;
// double D1 = -1.221152290793335e-007;
// for 100R
double shA1 = 1.938673445820002e-003;
double shB1 = 2.989388530840342e-004;
double shC1 = 1.355307994901136e-006;
double shD1 = 9.684853535359814e-008;
// mW/dec C dissipation factor
//double K = 9.5 //for 100K
double shK1 = 7.5;
//-----------------------------------------------------------------------
//Variables we will use
//Generic Variables
unsigned long initialTime = 0;
int WindowSize = 255; //used by PID, PWM
unsigned long windowStartTime;
double celsius; // for converting Kelvin to Celsius
boolean display_Ch1; // true for Ch1, false for Ch2 - used as a toggle via timer
char spin; //Used as a spinning wheel indicator on the screen
int l; //spin counter
//crude Layout of screen, start of column for a 64x128 OLED
int col0 = 0;
int col1 = 16;
int col2 = 32;
int col3 = 48;
int col4 = 64;
//-----------------------------------------------------------------------
// Ch1 variables
double Ch1_target_Setpoint = Ch1_initial_Startpoint; // target temperature Ch1
double Ch1_interim_Setpoint; // current temperature Ch1
double tempTC_Ch1;
char PID_Gear_Ch1; // This is used to display in which stage of the PID curve we are...
double humidity_Ch1;
double temperature_Ch1;
double Ch1_reading; // variables holding measured temperatures averaged
double Ch1_Record_Max,Ch1_Record_Min ;
int Ch1_Pot_raw, Ch1_Pot_Adj;
volatile double readings_Ch1[5] = {0,0,0,0,0}; //averaging last 5 readings_Ch1s
//Ch1 PID Config - Define the aggressive, conservative and slow Tuning Parameters
//Aggressive (agg) - when we are still far away from the target
double use_agg_percentage_Ch1 = 75; //use until more than this percentage close target
double use_agg_band_Ch1; //will be recalculated
double aggKp_Ch1=800.0, aggKi_Ch1=4.0, aggKd_Ch1=0.0; // aggressive PID values to use
// use constants when between agg(ressive) and slow
double consKp_Ch1=100.0, consKi_Ch1=2.0, consKd_Ch1=100.0;
// Use Slow when we are on or very near target temperature
double use_slow_percentage_Ch1 = 5; //within this percentage on target
double use_slow_band_Ch1; //will be recalulated
double slowKp_Ch1=10.0, slowKi_Ch1=1.0, slowKd_Ch1=800.0;
double PID_Ch1_Input, Ch1_PWM_Output;
//-----------------------------------------------------------------------
//Ch2 variables
double Ch2_target_Setpoint = Ch2_initial_Startpoint; // target temperature Ch2
double Ch2_interim_Setpoint; // current temperature Ch2
double tempTC_Ch2;
char PID_Gear_Ch2; // This is used to display in which stage of the PID curve we are...
double humidity_Ch2;
double temperature_Ch2;
double Ch2_reading; // variables holding measured temperatures averaged
double Ch2_Record_Max, Ch2_Record_Min ;
int Ch2_Pot_raw, Ch2_Pot_Adj;
volatile double readings_Ch2[5] = {0,0,0,0,0};
//averaging last 5 readings_Ch2s
//Ch2 PID Config - Define the aggressive, conservative and slow Tuning Parameters
//Aggressive (agg) - when we are still far away from the target
double use_agg_percentage_Ch2 = 75; //use until more than this percentage close target
double use_agg_band_Ch2; //will be recalculated
double aggKp_Ch2=800.0, aggKi_Ch2=4.0, aggKd_Ch2=0.0; // aggressive PID values to use
// use constants when between agg(ressive) and slow
double consKp_Ch2=100.0, consKi_Ch2=2.0, consKd_Ch2=100.0;
// Use Slow when we are on or very near target temperature
double use_slow_percentage_Ch2 = 5; //within this percentage on target
double use_slow_band_Ch2; //will be recalulated
double slowKp_Ch2=10.0, slowKi_Ch2=1.0, slowKd_Ch2=800.0;
double PID_Ch2_Input, Ch2_PWM_Output;
//---------------------------------------------------------------
//Create PID Objects
PID myCh1_PID(&PID_Ch1_Input, &Ch1_PWM_Output, &Ch1_interim_Setpoint, aggKp_Ch1, aggKi_Ch1, aggKd_Ch1, DIRECT);
PID myCh2_PID(&PID_Ch2_Input, &Ch2_PWM_Output, &Ch2_interim_Setpoint, aggKp_Ch2, aggKi_Ch2, aggKd_Ch2, DIRECT);
//------------------------------------------------------------------
void setup()
{
Serial.begin(19200);
// Boot info on serial
Serial.println("PID Temperature Controller");
Serial.print("LIBRARY VERSION: ");
Serial.println(DHT_LIB_VERSION);
wdt_enable(WDTO_8S);
setTime(0,0,0,1,1,12); //timer determines how often you get a status update on the Serial port or flip between Ch info on OLED
//Pin Init
pinMode(btnSTAT_RESET, INPUT);
pinMode(OLED_RESET, OUTPUT);
pinMode(OLED_CS, OUTPUT);
pinMode(Ch1_PWM_OUT_PIN, OUTPUT);
pinMode(Ch2_PWM_OUT_PIN, OUTPUT);
// OLED Init
digitalWrite(OLED_RESET, LOW);
Alarm.delay(2);
digitalWrite(OLED_RESET, HIGH);
OLED_display.begin(SSD1306_SWITCHCAPVCC);
//OLED_display.setRotation(2); //If your box requires the OLED display flipped around, also check the Ch allocation and swap Pot min/max
OLED_display.display();
// Serial.println();
// Serial.println("Type,\tstatus,\tHumidity (%),\tTemperature (C)");
// Lets Begin...
windowStartTime = millis();
//--------------------------------------------------------
// Modify here
//Init PID Ch1
//lets read the thermistors each x times to get an average room temp
// for (int i=0;i<5;i++)
{
// readings_Ch1[0] = readings_Ch1[1];
// readings_Ch1[1] = readings_Ch1[2];
// readings_Ch1[2] = readings_Ch1[3];
// readings_Ch1[3] = readings_Ch1[4];
// readings_Ch1[4] = read_Temperature_Thermistor(Ch1_IN_PIN);
// Ch1_reading = (readings_Ch1[0] + readings_Ch1[1] + readings_Ch1[2] + readings_Ch1[3] + readings_Ch1[4]) / 5;
}
//or if using DHT
Ch1_reading = DHT_Ch1.temperature;
Ch1_initial_Startpoint = Ch1_reading;
myCh1_PID.SetOutputLimits(0, WindowSize); //tell the PID to range between 0 and the full window size
myCh1_PID.SetSampleTime(2000); //SampleTime: How often, in milliseconds, the PID will be evaluated. (int>0)
myCh1_PID.SetMode(AUTOMATIC); //turn the PID on
//--------------------------------------------------------
// Modify here
//Init PID Ch2
//lets read the thermistors each x times to get an average room temp
// for (int i=0;i<5;i++)
{
// readings_Ch2[0] = readings_Ch2[1];
// readings_Ch2[1] = readings_Ch2[2];
// readings_Ch2[2] = readings_Ch2[3];
// readings_Ch2[3] = readings_Ch2[4];
// readings_Ch2[4] = read_Temperature_Thermistor(Ch2_IN_PIN);
// Ch2_reading = (readings_Ch2[0] + readings_Ch2[1] + readings_Ch2[2] + readings_Ch2[3] + readings_Ch2[4]) / 5;
}
//or if using DHT
Ch2_reading = DHT_Ch2.temperature;
Ch2_initial_Startpoint = Ch2_reading;
myCh2_PID.SetOutputLimits(0, WindowSize); //tell the PID to range between 0 and the full window size
myCh2_PID.SetSampleTime(2000); //SampleTime: How often, in milliseconds, the PID will be evaluated. (int>0)
myCh2_PID.SetMode(AUTOMATIC); //turn the PID on
//--------------------------------------------------------
//for debugging, enable some or all of these for serial feedback:
//routines only exisit for Ch1 but you can duplicate them for Ch2 changing variables
// Alarm.timerRepeat(5, Serial_Report_Temperature_Ch1);
// Alarm.timerRepeat(20, Serial_Report_MaxMin_Values_Ch1);
// Alarm.timerRepeat(20, Serial_Report_Pot_Ch1);
// Alarm.timerRepeat(5, Serial_Report_PID_Values_Ch1);
Alarm.timerRepeat(1, OLED_Activity); // The spinning wheel on the screen goes round and round
//If using DHT
Alarm.timerRepeat(2,read_DHT_Ch1); //wait at least 2 sec between DHT readings
Alarm.timerRepeat(2,read_DHT_Ch2); //wait at least 2 sec between DHT readings
Alarm.timerRepeat(5, Alternate_Display); //interrupt and call Alternate display to display other channel approx every 5 seconds
display_Ch1 = true; // Start display with Ch1 then alternate Ch2
Serial.println("Ready to go!");
OLED_Welcome();
Alarm.delay(1000);
Ch1_Record_Max = -999.0;
Ch1_Record_Min = 999.0;
Ch2_Record_Max = -999.0;
Ch2_Record_Min = 999.0;
}
void loop()
{
Service_Ch1();
Service_Ch2();
if(display_Ch1 == true)
{
OLED_Report_Temperature_Ch1();
}
else
{
OLED_Report_Temperature_Ch2();
}
// check if the pushbutton is pressed. If pressed, the reset statistics:
if ((digitalRead(btnSTAT_RESET)) == HIGH)
{
Ch1_Record_Max = -999.9;
Ch1_Record_Min = 999.9;
Ch2_Record_Max = -999.9;
Ch2_Record_Min = 999.9;
}
Alarm.delay(500); // wait a bit
wdt_reset(); // watchdog timer set back to zero
}
void Service_Ch1()
{
// Modify here
// if using thermistor
//Channel 1
//If using Thermistor, we avg the last 5 readings
// readings_Ch1[0] = readings_Ch1[1];
// readings_Ch1[1] = readings_Ch1[2];
// readings_Ch1[2] = readings_Ch1[3];
// readings_Ch1[3] = readings_Ch1[4];
// readings_Ch1[4] = read_Temperature_Thermistor(Ch1_IN_PIN) + Toffset_Ch1;
// Ch1_reading = (readings_Ch1[0] + readings_Ch1[1] + readings_Ch1[2] + readings_Ch1[3] + readings_Ch1[4]) / 5;
//or using DHT
// report_DHT_Ch1(); //Should be updated via TimeRepeat since we should wait 2 sec between readings
Ch1_reading = DHT_Ch1.temperature; //rem out if using thermistor
PID_Ch1_Input = Ch1_reading; //Will use the ave of the last 5 readings_Ch1 for input to the PID or the DHT reading
if (Ch1_reading >= Ch1_Record_Max)
{
Ch1_Record_Max = Ch1_reading;
}
if (Ch1_reading <= Ch1_Record_Min)
{
Ch1_Record_Min = Ch1_reading;
}
PID_Ch1();
read_Ch1_Pot();
}
void Service_Ch2()
{
// Modify here
// if using thermistor
//Channel 2
//If using Thermistor, we avg the last 5 readings
readings_Ch2[0] = readings_Ch2[1];
readings_Ch2[1] = readings_Ch2[2];
readings_Ch2[2] = readings_Ch2[3];
readings_Ch2[3] = readings_Ch2[4];
readings_Ch2[4] = read_Temperature_Thermistor(Ch2_IN_PIN) + Toffset_Ch2;
Ch2_reading = (readings_Ch2[0] + readings_Ch2[1] + readings_Ch2[2] + readings_Ch2[3] + readings_Ch2[4]) / 5;
//or using DHT
// report_DHT_Ch2(); //Should be updated via TimeRepeat since we should wait 2 sec between readings
Ch2_reading = DHT_Ch2.temperature; //rem out if using thermistor
PID_Ch2_Input = Ch2_reading; //Will use the ave of the last 5 readings_Ch2 for input to the PID or the DHT reading
//Set new records?
if (Ch2_reading >= Ch2_Record_Max)
{
Ch2_Record_Max = Ch2_reading;
}
if (Ch2_reading <= Ch2_Record_Min)
{
Ch2_Record_Min = Ch2_reading;
}
PID_Ch2();
read_Ch2_Pot();
}
void Alternate_Display()
{
if (display_Ch1 == true)
{
display_Ch1 = false;
}
else
{
display_Ch1 = true;
}
}
void OLED_Activity()
{
l = l + 1;
switch (l) {
case 1:
spin = '\\';
break;
case 2:
spin = '|';
break;
case 3:
spin = '/';
break;
case 4:
spin = '-';
break;
default:
l=0;
}
}
void read_Ch1_Pot()
{
Ch1_Pot_raw = analogRead(Pot_Ch1_PIN);
// apply the calibration to the sensor readings_Ch1
//use one of these two line depending on LCD orientation
// Ch1_Pot_raw = map(Ch1_Pot_raw, 0, 1023, TMIN_Ch1, TMAX_Ch1);
Ch1_Pot_raw = map(Ch1_Pot_raw, 1023, 0, TMIN_Ch1, TMAX_Ch1);
// in case the sensor value is outside the range seen during calibration
Ch1_Pot_Adj = constrain(Ch1_Pot_raw, TMIN_Ch1, TMAX_Ch1);
Ch1_target_Setpoint = Ch1_Pot_Adj;
//re-Set the gearbox!
use_agg_band_Ch1 = use_agg_percentage_Ch1 * (Ch1_target_Setpoint / 100);
use_slow_band_Ch1 = use_slow_percentage_Ch1 * (Ch1_target_Setpoint / 100);
}
void read_Ch2_Pot()
{
Ch2_Pot_raw = analogRead(Pot_Ch2_PIN);
// apply the calibration to the sensor readings_Ch1
//use one of these two line depending on LCD orientation
// Ch2_Pot_raw = map(Ch2_Pot_raw, 0, 1023, TMIN_Ch2, TMAX_Ch2);
Ch2_Pot_raw = map(Ch2_Pot_raw, 1023, 0, TMIN_Ch2, TMAX_Ch2);
// in case the sensor value is outside the range seen during calibration
Ch2_Pot_Adj = constrain(Ch2_Pot_raw, TMIN_Ch2, TMAX_Ch2);
Ch2_target_Setpoint = Ch2_Pot_Adj;
//re-Set the gearbox!
use_agg_band_Ch2 = use_agg_percentage_Ch2 * (Ch2_target_Setpoint / 100);
use_slow_band_Ch2 = use_slow_percentage_Ch2 * (Ch2_target_Setpoint / 100);
}
double read_Temperature_Thermistor(int This_Thermistor_ChX)
{
double celcius;
double adc_raw = analogRead(This_Thermistor_ChX);
//For Debugging, enable this
// Serial.print(" ADC = ");
// Serial.print(adc_raw,0);
double V = (adc_raw / 1024) * V_Supply;
double Rtherm;
//For Debugging, enable this
// Serial.print(" V =");
// Serial.print(V);
//Serial.print(" V ");
//Calculate Thermistor Resistance
if (This_Thermistor_ChX = 1)
{
Rtherm = (R_Ch1 * V ) / (V_Supply - V);
}
else
{
Rtherm = (R_Ch2 * V ) / (V_Supply - V);
}
//For Debugging, enable this
// Serial.print(" R");
// Serial.print(This_Thermistor_ChX);
// Serial.print(" = ");
// Serial.print(Rtherm);
// Serial.print(" Ohm ");
// double kelvin = SteinhartHart(Rtherm) - V * V / (K * Rtherm);
double kelvin =(SteinhartHart(Rtherm)) - (V * (V / (shK1 * Rtherm)));
// Serial.println(a,10);
// Serial.println(b,10);
// Serial.println(c,10);
// Serial.println(d,10);
// test = (1/test) ; //- 273.15;
// Serial.print("Result=");
// Serial.print(test);
// Serial.print("K ");
// test = test - 273.15;
// Serial.print("");
// Serial.println(test);
// Serial.println("");
// Serial.print(" T= ");
// return result in celcius
celcius = kelvin - 273.15;
return celcius;
}
double SteinhartHart(double R)
{
// calculate temperature
double logR1 = log(R);
double logR2 = logR1 * logR1;
double logR3 = logR1 * logR1 * logR1;
return 1.0 / ( shA1 + (shB1 * logR1) + (shC1 * logR2) + (shD1 * logR3));
}
void PID_Ch1()
{
unsigned long time_elapsed = millis() - initialTime;
Ch1_interim_Setpoint = Ch1_initial_Startpoint + double(time_elapsed) * Ch1_heating_rate / oneminute;
if (Ch1_interim_Setpoint > Ch1_target_Setpoint)
{
Ch1_interim_Setpoint = Ch1_target_Setpoint;
}
double gap = (Ch1_reading - Ch1_interim_Setpoint); //distance away from setpoint
if (Ch1_reading > (Ch1_target_Setpoint + 1.0))
{
PID_Gear_Ch1 = 'N';
}
else
{
if((gap) > use_agg_band_Ch1 )
{
//we're far from setpoint, use aggressive tuning parameters
myCh1_PID.SetTunings(aggKp_Ch1, aggKi_Ch1, aggKd_Ch1);
PID_Gear_Ch1 = '1';
}
if((gap) < use_slow_band_Ch1)
{
myCh1_PID.SetTunings(slowKp_Ch1, slowKi_Ch1, slowKd_Ch1);
PID_Gear_Ch1 = '3';
}
if((gap) > use_slow_band_Ch1 & abs(gap) < use_agg_band_Ch1)
{
//we're close to setpoint, use conservative tuning parameters
myCh1_PID.SetTunings(consKp_Ch1, consKi_Ch1, consKd_Ch1);
PID_Gear_Ch1 = '2';
}
}
myCh1_PID.Compute();
if (Ch1_reading < Ch1_target_Setpoint)
{
// analogWrite(Ch2_PWM_OUT_PIN, 0); //Switch of Ch2 if used as cooling
// Serial.print("Heating up. PWM Ch1_PWM_Output: ");
// Serial.println(Ch1_PWM_Output);
analogWrite(Ch1_PWM_OUT_PIN,Ch1_PWM_Output);
}
else
{
// Serial.print("Cooling Down Output: ");
// Serial.print(Ch1_reading - Ch1_target_Setpoint);
// Serial.print("---");
analogWrite(Ch1_PWM_OUT_PIN,0);
//Fan_PWM_Output = map((Ch1_reading - Ch1_target_Setpoint), 0.20, 2, 0, 255);
//Fan_PWM_Output = constrain(Ch2_PWM_Output, 0, 255);
//analogWrite(Ch2_PWM_OUT_PIN, Ch2_PWM_Output);
// Serial.println(Ch2_PWM_Output);
//Serial.println("Cooling Down Ch2 ON... ");
//digitalWrite (Ch2_PWM_OUT_PIN, true);
}
}
void PID_Ch2()
{
unsigned long time_elapsed = millis() - initialTime;
Ch2_interim_Setpoint = Ch2_initial_Startpoint + double(time_elapsed) * Ch2_heating_rate / oneminute;
if (Ch2_interim_Setpoint > Ch2_target_Setpoint)
{
Ch2_interim_Setpoint = Ch2_target_Setpoint;
}
double gap = (Ch2_reading - Ch2_interim_Setpoint); //distance away from setpoint
if (Ch2_reading > (Ch2_target_Setpoint + 1.0))
{
PID_Gear_Ch2 = 'N';
}
else
{
if((gap) > use_agg_band_Ch2 )
{
//we're far from setpoint, use aggressive tuning parameters
myCh2_PID.SetTunings(aggKp_Ch2, aggKi_Ch2, aggKd_Ch2);
PID_Gear_Ch2 = '1';
}
if((gap) < use_slow_band_Ch2)
{
myCh2_PID.SetTunings(slowKp_Ch2, slowKi_Ch2, slowKd_Ch2);
PID_Gear_Ch2 = '3';
}
if((gap) > use_slow_band_Ch2 & abs(gap) < use_agg_band_Ch2)
{
//we're close to setpoint, use conservative tuning parameters
myCh2_PID.SetTunings(consKp_Ch2, consKi_Ch2, consKd_Ch2);
PID_Gear_Ch2 = '2';
}
}
myCh2_PID.Compute();
if (Ch2_reading < Ch2_target_Setpoint)
{
// analogWrite(Ch1_PWM_OUT_PIN, 0); //Switch of Ch1 if used as cooling
// Serial.print("Heating up. PWM Ch2_PWM_Output: ");
// Serial.println(Ch2_PWM_Output);
analogWrite(Ch2_PWM_OUT_PIN,Ch2_PWM_Output);
}
else
{
// Serial.print("Cooling Down Output: ");
// Serial.print(Ch2_reading - Ch2_target_Setpoint);
// Serial.print("---");
analogWrite(Ch2_PWM_OUT_PIN,0);
//Fan_PWM_Output = map((Ch2_reading - Ch2_target_Setpoint), 0.20, 2, 0, 255);
//Fan_PWM_Output = constrain(Ch1_PWM_Output, 0, 255);
//analogWrite(Ch1_PWM_OUT_PIN, Ch1_PWM_Output);
// Serial.println(Ch1_PWM_Output);
//Serial.println("Cooling Down Ch1 ON... ");
//digitalWrite (Ch1_PWM_OUT_PIN, true);
}
}
void OLED_Report_Temperature_Ch1()
{
//Delete previous readings_Ch x ...
OLED_display.clearDisplay();
OLED_display.setTextSize(1);
OLED_display.setCursor(0,0);
OLED_display.print(spin);
OLED_display.print(" Ch1:");
OLED_display.setCursor(110, 0);
OLED_display.print("[");
OLED_display.print(PID_Gear_Ch1);
OLED_display.print("]");
OLED_display.setTextSize(3);
OLED_display.setCursor(0,8);
OLED_display.print(Ch1_reading,2);
OLED_display.setTextSize(1);
OLED_display.print("C");
OLED_display.setTextSize(1);
OLED_display.setCursor(80, 32);
//If using DHT
OLED_display.print(DHT_Ch1.humidity);
OLED_display.print("%");
OLED_display.setTextSize(1);
OLED_display.setCursor(0, 40);
OLED_display.print("Target:");
OLED_display.setCursor(48, 40);
OLED_display.print(Ch1_target_Setpoint,1);
OLED_display.setCursor(80, 40);
OLED_display.print("[");
OLED_display.print(Ch1_reading - Ch1_target_Setpoint,1);
OLED_display.print("]");
OLED_display.setTextSize(1);
OLED_display.setCursor(0, 48);
OLED_display.print("Low:");
OLED_display.setCursor(64, 48);
OLED_display.print("High:");
OLED_display.setCursor(0, 56);
OLED_display.print(Ch1_Record_Min,2);
OLED_display.setCursor(64, 56);
OLED_display.print(Ch1_Record_Max,2);
OLED_display.display();
}
void OLED_Report_Temperature_Ch2()
{
//Delete previous readings_Ch x ...
OLED_display.clearDisplay();
OLED_display.setTextSize(1);
OLED_display.setCursor(0,0);
OLED_display.print(spin);
OLED_display.print(" Ch2:");
OLED_display.setCursor(110, 0);
OLED_display.print("[");
OLED_display.print(PID_Gear_Ch2);
OLED_display.print("]");
OLED_display.setTextSize(3);
OLED_display.setCursor(0,8);
OLED_display.print(Ch2_reading,2);
OLED_display.setTextSize(1);
OLED_display.print("C");
OLED_display.setTextSize(1);
OLED_display.setCursor(80, 32);
//If using DHT
OLED_display.print(DHT_Ch2.humidity);
OLED_display.print("%");
OLED_display.setTextSize(1);
OLED_display.setCursor(0, 40);
OLED_display.print("Target:");
OLED_display.setCursor(48, 40);
OLED_display.print(Ch2_target_Setpoint,1);
OLED_display.setCursor(80, 40);
OLED_display.print("[");
OLED_display.print(Ch2_reading - Ch2_target_Setpoint,1);
OLED_display.print("]");
OLED_display.setTextSize(1);
OLED_display.setCursor(0, 48);
OLED_display.print("Low:");
OLED_display.setCursor(64, 48);
OLED_display.print("High:");
OLED_display.setCursor(0, 56);
OLED_display.print(Ch2_Record_Min,2);
OLED_display.setCursor(64, 56);
OLED_display.print(Ch2_Record_Max,2);
OLED_display.display();
}
void Serial_Report_Pot_Ch1()
{
Serial.print("--> Ch1 Pot raw = ");
Serial.print(Ch1_Pot_Adj);
Serial.print(" Agg until = ");
Serial.print(use_agg_band_Ch1);
Serial.print(" Slow within = ");
Serial.print(use_slow_band_Ch1);
Serial.println("");
}
void Serial_Report_MaxMin_Values_Ch1()
{
//for debugging
Serial.print("Min/Max Report ");
Serial.print(" Ch1:");
Serial.print(Ch1_Record_Min);
Serial.print("|");
Serial.print(Ch1_Record_Max);
Serial.print(" [");
Serial.print(Ch1_Record_Min - Ch1_target_Setpoint);
Serial.print("|+");
Serial.print( Ch1_Record_Max - Ch1_target_Setpoint);
Serial.println("] ");
}
void Serial_Report_PID_Values_Ch1()
{
// for debugging
Serial.print(" --->Ch1 Input:");
Serial.print( PID_Ch1_Input);
Serial.print(" Ch1_PWM_Output:");
Serial.print( Ch1_PWM_Output);
Serial.print(" Setpoint:");
Serial.print( Ch1_interim_Setpoint);
Serial.print(" Kp:");
Serial.print(myCh1_PID.GetKp(),2);
Serial.print(" Ki:");
Serial.print(myCh1_PID.GetKi(),2);
Serial.print(" Kd:");
Serial.print(myCh1_PID.GetKd(),2);
Serial.print(" Mode:");
Serial.print(myCh1_PID.GetMode());
Serial.print(" Dir:");
Serial.print(myCh1_PID.GetDirection());
Serial.println("");
}
void Serial_Report_Temperature_Ch1()
{
//for debugging
Serial.print(" Ch1 Reading:");
Serial.print(Ch1_reading);
Serial.print(" Ch1 Target: ");
Serial.print(Ch1_target_Setpoint);
// Serial.print(" K ");
// celsius = Mat - 273.15;
// Serial.print(celsius);
// Serial.print(" C ");
Serial.print(" Ch1 Delta Temp:");
Serial.print(Ch1_target_Setpoint - Ch1_reading);
Serial.print(" Ch1 Interim:");
Serial.print(Ch1_interim_Setpoint);
Serial.print(" Ch1 PID Out:");
Serial.print(Ch1_PWM_Output);
Serial.println("");
}
void read_DHT_Ch1()
{
// Serial.print("DHT22, \t");
int chk = DHT_Ch1.read22(Ch1_IN_PIN);
switch (chk)
{
case DHTLIB_OK:
// Serial.print("OK,\t");
break;
case DHTLIB_ERROR_CHECKSUM:
// Serial.print("Checksum error,\t");
break;
case DHTLIB_ERROR_TIMEOUT:
// Serial.print("Time out error,\t");
break;
default:
// Serial.print("Unknown error,\t");
break;
}
// DISPLAY DATA
humidity_Ch1 = (DHT_Ch1.humidity);
// Serial.print(DHT_Ch1.humidity);
// Serial.print(",\t");
temperature_Ch1 = (DHT_Ch1.temperature, 1);
// Serial.println(DHT_Ch1.temperature, 1);
Ch1_reading = DHT_Ch1.temperature;
}
void read_DHT_Ch2()
{
// Serial.print("DHT22, \t");
int chk = DHT_Ch2.read22(Ch2_IN_PIN);
switch (chk)
{https://www.elektormagazine.com/labs/3d-printer-head-and-mat-temperature-controller-using-arduino-130500-i
case DHTLIB_OK:
// Serial.print("OK,\t");
break;
case DHTLIB_ERROR_CHECKSUM:
// Serial.print("Checksum error,\t");
break;
case DHTLIB_ERROR_TIMEOUT:
// Serial.print("Time out error,\t");
break;
default:
// Serial.print("Unknown error,\t");
break;
}
// Set DISPLAY DATA
humidity_Ch2 = (DHT_Ch2.humidity);
// Serial.print(DHT_Ch2.humidity);
// Serial.print(",\t");
temperature_Ch2 = (DHT_Ch2.temperature, 1);
//Serial.println(DHT_Ch2.tempehttps://www.elektormagazine.com/labs/3d-printer-head-and-mat-temperature-controller-using-arduino-130500-irature, 1);
Ch2_reading = DHT_Ch2.temperature;
}
void OLED_Welcome()
{
// OLED_display.OLED_display(); // show splashscreen
OLED_display.clearDisplay();
OLED_display.setTextColor(WHITE);
OLED_display.setTextSize(1);
OLED_display.setCursor(0,0);
OLED_display.print("Temperature");
OLED_display.setCursor(0,8);
OLED_display.print("Controller");
OLED_display.setCursor(0,25);
OLED_display.print("A Jordaan");
OLED_display.setTextSize(1);
OLED_display.setCursor(0,40);
OLED_display.print("Ver 4.1.1");
OLED_display.setCursor(0,48);
OLED_display.print("elektor");
OLED_display.display();
Alarm.delay(2000);
OLED_display.clearDisplay();
//OLED_display.display();
}