Untitled diff

Created Diff never expires
35 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
718 lines
35 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
716 lines
// aArtisanQ_PID.ino
// aArtisanQ_PID.ino
// ------------
// ------------


// prh version
// Written to support the Artisan roasting scope //http://code.google.com/p/artisan/
// Written to support the Artisan roasting scope //http://code.google.com/p/artisan/


// Heater is controlled from OT1 using a zero cross SSR (integral pulse control)
// Heater is controlled from OT1 using a zero cross SSR (integral pulse control)
// AC fan is controlled from OT2 using a random fire SSR (phase angle control)
// AC fan is controlled from OT2 using a random fire SSR (phase angle control)
// zero cross detector (true on logic low) is connected to I/O3
// zero cross detector (true on logic low) is connected to I/O3


// *** BSD License ***
// *** BSD License ***
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Copyright (c) 2011, MLG Properties, LLC
// Copyright (c) 2011, MLG Properties, LLC
// All rights reserved.
// All rights reserved.
//
//
// Contributor: Jim Gallt
// Contributor: Jim Gallt
//
//
// Redistribution and use in source and binary forms, with or without modification, are
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
// permitted provided that the following conditions are met:
//
//
// Redistributions of source code must retain the above copyright notice, this list of
// Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// conditions and the following disclaimer.
//
//
// Redistributions in binary form must reproduce the above copyright notice, this list
// Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
// provided with the distribution.
//
//
// Neither the name of the copyright holder(s) nor the names of its contributors may be
// Neither the name of the copyright holder(s) nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific prior
// used to endorse or promote products derived from this software without specific prior
// written permission.
// written permission.
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------


#define BANNER_ARTISAN "aArtisanQ_PID 5_3"
#define BANNER_ARTISAN "aArtisanQ_PID 5_3"


// Revision history:
// Revision history:
// 20110408 Created.
// 20110408 Created.
// 20110409 Reversed the BT and ET values in the output stream.
// 20110409 Reversed the BT and ET values in the output stream.
// Shortened the banner display time to avoid timing issues with Artisan
// Shortened the banner display time to avoid timing issues with Artisan
// Echo all commands to the LCD
// Echo all commands to the LCD
// 20110413 Added support for Artisan 0.4.1
// 20110413 Added support for Artisan 0.4.1
// 20110414 Reduced filtering levels on BT, ET
// 20110414 Reduced filtering levels on BT, ET
// Improved robustness of checkSerial() for stops/starts by Artisan
// Improved robustness of checkSerial() for stops/starts by Artisan
// Revised command format to include newline character for Artisan 0.4.x
// Revised command format to include newline character for Artisan 0.4.x
// 20110528 New command language added (major revision)
// 20110528 New command language added (major revision)
// Use READ command to poll the device for up to 4 temperature channels
// Use READ command to poll the device for up to 4 temperature channels
// Change temperature scale using UNITS command
// Change temperature scale using UNITS command
// Map physical channels on ADC to logical channels using the CHAN command
// Map physical channels on ADC to logical channels using the CHAN command
// Select SSR output duty cycle with OT1 and OT2 commands
// Select SSR output duty cycle with OT1 and OT2 commands
// Select PWM logic level output on I/O3 using IO3 command
// Select PWM logic level output on I/O3 using IO3 command
// Directly control digital pins using DPIN command (WARNING -- this might not be smart)
// Directly control digital pins using DPIN command (WARNING -- this might not be smart)
// Directly control analog pins using APIN command (WARNING -- this might not be smart)
// Directly control analog pins using APIN command (WARNING -- this might not be smart)
// 20110601 Major rewrite to use cmndproc.h library
// 20110601 Major rewrite to use cmndproc.h library
// RF2000 and RC2000 set channel mapping to 1200
// RF2000 and RC2000 set channel mapping to 1200
// 20110602 Added ACKS_ON to control verbose output
// 20110602 Added ACKS_ON to control verbose output
// ----------- Version 1.10
// ----------- Version 1.10
// 20111011 Added support for type J and type T thermocouples
// 20111011 Added support for type J and type T thermocouples
// Better error checking on EEPROM reads
// Better error checking on EEPROM reads
// ----------- aArtsianQ version 0.xx
// ----------- aArtsianQ version 0.xx
// 20111031 Created.
// 20111031 Created.
// ----------- aArtisanQ beta1
// ----------- aArtisanQ beta1
// 20111101 Beta 1 release
// 20111101 Beta 1 release




// ----------- aArtisanQ_PID (Brad Collins)
// ----------- aArtisanQ_PID (Brad Collins)
// 20120915 Created.
// 20120915 Created.
// Added PID Library
// Added PID Library
// Added code for analogue inputs
// Added code for analogue inputs
// 20120916 Added PID command allowing PID control to be activated and deactivated from Artisan
// 20120916 Added PID command allowing PID control to be activated and deactivated from Artisan
// Added roast clock. Can be reset with PID;TIME command
// Added roast clock. Can be reset with PID;TIME command
// Removed LCD ambient temp display and added roast clock display
// Removed LCD ambient temp display and added roast clock display
// 20120918 Added code to read profile from EEPROM and interpolate to calculate setpoint. Time/Temp profiles
// 20120918 Added code to read profile from EEPROM and interpolate to calculate setpoint. Time/Temp profiles
// Added code to end PID control when end of profile is reached
// Added code to end PID control when end of profile is reached
// Added additional PID command allowing roast profile to be selected
// Added additional PID command allowing roast profile to be selected
// Added additional PID command allowing PID tunings to be adjusted on the fly (required MAX_TOKENS 5 in cmndproc.h library)
// Added additional PID command allowing PID tunings to be adjusted on the fly (required MAX_TOKENS 5 in cmndproc.h library)
// 20120920 Added RoR calcs
// 20120920 Added RoR calcs
// 20120921 Updated RoR calcs to better handle first loop issue (RoR not calculated in first loop)
// 20120921 Updated RoR calcs to better handle first loop issue (RoR not calculated in first loop)
// Stopped ANLG1 being read during PID control
// Stopped ANLG1 being read during PID control
// Added code to convert PID Setpoint temps to correct units. Added temperature units data to profile format
// Added code to convert PID Setpoint temps to correct units. Added temperature units data to profile format
// Serial command echo to LCD now optional
// Serial command echo to LCD now optional
// 20120922 Added support for LCDapter buttons and LEDs (button 1 currently activates or deactivates PID control if enabled)
// 20120922 Added support for LCDapter buttons and LEDs (button 1 currently activates or deactivates PID control if enabled)
// Added code to allow power to OT1 to be cut if OT2 is below OT1_CUTOFF percentage as defined in user.h. For heater protection if required. Required modification to phase_ctrl.cpp
// Added code to allow power to OT1 to be cut if OT2 is below OT1_CUTOFF percentage as defined in user.h. For heater protection if required. Required modification to phase_ctrl.cpp
// Added code to allow OT2 to range between custom min and max percentages (defined in user.h)
// Added code to allow OT2 to range between custom min and max percentages (defined in user.h)
// 20121007 Fixed PID tuning command so it handles doubles
// 20121007 Fixed PID tuning command so it handles doubles
// Added inital PID tuning parameters in user.h
// Added inital PID tuning parameters in user.h
// 20121013 Added code to allow Artisan plotting of levelOT1 and levelOT2 if PLOT_POWER is defined in user.h
// 20121013 Added code to allow Artisan plotting of levelOT1 and levelOT2 if PLOT_POWER is defined in user.h
// Swapped location of T1 and T2 on LCD display and renamed to ET and BT
// Swapped location of T1 and T2 on LCD display and renamed to ET and BT
// 20121014 Enhanced LCD display code and added support for 4x20 LCDs. Define LCD_4x20 in user.h
// 20121014 Enhanced LCD display code and added support for 4x20 LCDs. Define LCD_4x20 in user.h
// 20121021 Added optional limits for Analogue1
// 20121021 Added optional limits for Analogue1
// 20121120 Added support for pBourbon logging
// 20121120 Added support for pBourbon logging
// 20121213 Added UP and DOWN parameters for OT1 and OT2 commands. Increments or decrements power levels by 5%
// 20121213 Added UP and DOWN parameters for OT1 and OT2 commands. Increments or decrements power levels by 5%
// 20130116 Added user adjustable analogue input rounding (ANALOGUE_STEP) in user.h
// 20130116 Added user adjustable analogue input rounding (ANALOGUE_STEP) in user.h
// 20130119 aArtisanQ_PID release 3_5
// 20130119 aArtisanQ_PID release 3_5
// Added code to allow for additional LCD display modes
// Added code to allow for additional LCD display modes
// 20130120 Added ability to change roast profile using LCD and buttons
// 20130120 Added ability to change roast profile using LCD and buttons
// 20130121 Tidied up button press code
// 20130121 Tidied up button press code
// 20130203 Permits use of different TC types on individual channels as in aArtisan 2.10
// 20130203 Permits use of different TC types on individual channels as in aArtisan 2.10
// Updated temperature sample filtering to match aArtisan 2.10
// Updated temperature sample filtering to match aArtisan 2.10
// 20130406 Added GO and STOP commands to use with Artisan 'Charge' and 'End' buttons
// 20130406 Added GO and STOP commands to use with Artisan 'Charge' and 'End' buttons
// 20140127 aArtisanQ_PID release 4_0 created
// 20140127 aArtisanQ_PID release 4_0 created
// Added support for Roastlogger roasting software (responds to LOAD, POWER and FAN commands. Sends rorT1=, T1=, rorT2=, T2= and power levels to roastlogger)
// Added support for Roastlogger roasting software (responds to LOAD, POWER and FAN commands. Sends rorT1=, T1=, rorT2=, T2= and power levels to roastlogger)
// 20140128 Improved handling of heater and fan power limits
// 20140128 Improved handling of heater and fan power limits
// 20140213 aArtisanQ_PID release 4_2
// 20140213 aArtisanQ_PID release 4_2
// 20140214 Added option in user.h to define software mode (Artisan, Roastlogger or pBourbon)
// 20140214 Added option in user.h to define software mode (Artisan, Roastlogger or pBourbon)
// Fixed? bug causing crashes when receiving READ commands
// Fixed? bug causing crashes when receiving READ commands
// 20140214 aArtisanQ_PID release 4_3
// 20140214 aArtisanQ_PID release 4_3
// 20150328 Replaced pBourbon option with Android option
// 20150328 Replaced pBourbon option with Android option
// Bug fix in LCD menu code. Menu code disabled when not roasting stand alone
// Bug fix in LCD menu code. Menu code disabled when not roasting stand alone
// Changed default PID channel to 0
// Changed default PID channel to 0
// Added SV to values sent to Android app
// Added SV to values sent to Android app
// 20150328 aArtisanQ_PID release 5_0
// 20150328 aArtisanQ_PID release 5_0
// 20150403 Added PID;SV command from aArtisan
// 20150403 Added PID;SV command from aArtisan
// Changed default PID roast profile to 0 when using logging software
// Changed default PID roast profile to 0 when using logging software
// Reduced SRAM usage
// Reduced SRAM usage
// 20150404 Added PID;CHAN PID;CT and FILT commands
// 20150404 Added PID;CHAN PID;CT and FILT commands
// Added Heater, Fan and SV values to Artisan Logger if PID active
// Added Heater, Fan and SV values to Artisan Logger if PID active
// Removed PLOT_POWER option. Send power levels and SV if PID is active for Artisan. Send all by default for Android
// Removed PLOT_POWER option. Send power levels and SV if PID is active for Artisan. Send all by default for Android
// 20150409 Made loop time variable. Default = 1000ms (1s) but changes to 2000ms (2s) if needed for reading 4 temperature channels
// 20150409 Made loop time variable. Default = 1000ms (1s) but changes to 2000ms (2s) if needed for reading 4 temperature channels
// Adjusted Read command to match aArtisan. Runs logger() from command to provide immediate response to Artisan
// Adjusted Read command to match aArtisan. Runs logger() from command to provide immediate response to Artisan
// 20150426 Removed io3, rf2000 and rc2000 commands to save memory
// 20150426 Removed io3, rf2000 and rc2000 commands to save memory
// Other small compile changes to save memory
// Other small compile changes to save memory
// Compile directive change to ensure output levels are displayed when analogue pots are not active
// Compile directive change to ensure output levels are displayed when analogue pots are not active


// ----------- aArtisanQ_PID (prh mods/hacks to achieve PWM PID output)
// 20151201


// this library included with the arduino distribution
// this library included with the arduino distribution
#include <Wire.h>
#include <Wire.h>


// The user.h file contains user-definable and some other global compiler options
// The user.h file contains user-definable and some other global compiler options
// It must be located in the same folder as aArtisan.pde
// It must be located in the same folder as aArtisan.pde
#include "user.h"
#include "user.h"


// command processor declarations -- must be in same folder as aArtisan
// command processor declarations -- must be in same folder as aArtisan
#include "cmndreader.h"
#include "cmndreader.h"


#ifdef MEMORY_CHK
#ifdef MEMORY_CHK
// debugging memory problems
// debugging memory problems
#include "MemoryFree.h"
#include "MemoryFree.h"
#endif
#endif


// code for integral cycle control and phase angle control
// code for integral cycle control and phase angle control
#include "phase_ctrl.h"
//#include "phase_ctrl.h"


// these "contributed" libraries must be installed in your sketchbook's arduino/libraries folder
// these "contributed" libraries must be installed in your sketchbook's arduino/libraries folder
#include <cmndproc.h> // for command interpreter
#include <cmndproc.h> // for command interpreter
#include <thermocouple.h> // type K, type J, and type T thermocouple support
#include <thermocouple.h> // type K, type J, and type T thermocouple support
#include <cADC.h> // MCP3424
#include <cADC.h> // MCP3424
//#include <PWM16.h> // for SSR output
#include <PWM16.h> // for PWM output
#ifdef LCD
#ifdef LCD
#include <cLCD.h> // required only if LCD is used
#include <cLCD.h> // required only if LCD is used
#endif
#endif


// ------------------------ other compile directives
// ------------------------ other compile directives
#define MIN_DELAY 300 // ms between ADC samples (tested OK at 270)
#define MIN_DELAY 300 // ms between ADC samples (tested OK at 270)
#define DP 1 // decimal places for output on serial port
#define DP 1 // decimal places for output on serial port
#define D_MULT 0.001 // multiplier to convert temperatures from int to float
#define D_MULT 0.001 // multiplier to convert temperatures from int to float
#define DELIM "; ,=" // command line parameter delimiters
#define DELIM "; ,=" // command line parameter delimiters


#include <mcEEPROM.h>
#include <mcEEPROM.h>
mcEEPROM eeprom;
mcEEPROM eeprom;
calBlock caldata;
calBlock caldata;


float AT; // ambient temp
float AT; // ambient temp
float T[NC]; // final output values referenced to physical channels 0-3
float T[NC]; // final output values referenced to physical channels 0-3
int32_t ftemps[NC]; // heavily filtered temps
int32_t ftemps[NC]; // heavily filtered temps
int32_t ftimes[NC]; // filtered sample timestamps
int32_t ftimes[NC]; // filtered sample timestamps
int32_t ftemps_old[NC]; // for calculating derivative
int32_t ftemps_old[NC]; // for calculating derivative
int32_t ftimes_old[NC]; // for calculating derivative
int32_t ftimes_old[NC]; // for calculating derivative
float RoR[NC]; // final RoR values
float RoR[NC]; // final RoR values
uint8_t actv[NC]; // identifies channel status, 0 = inactive, n = physical channel + 1
uint8_t actv[NC]; // identifies channel status, 0 = inactive, n = physical channel + 1


#ifdef CELSIUS // only affects startup conditions
#ifdef CELSIUS // only affects startup conditions
boolean Cscale = true;
boolean Cscale = true;
#else
#else
boolean Cscale = false;
boolean Cscale = false;
#endif
#endif


int levelOT1, levelOT2; // parameters to control output levels
int levelIO3, levelOT1, levelOT2; // parameters to control output levels prh added levelIO3
#ifdef MEMORY_CHK
#ifdef MEMORY_CHK
uint32_t checktime;
uint32_t checktime;
#endif
#endif


#ifdef ANALOGUE1
#ifdef ANALOGUE1
uint8_t anlg1 = 0; // analog input pins
uint8_t anlg1 = 0; // analog input pins
int32_t old_reading_anlg1; // previous analogue reading
int32_t old_reading_anlg1; // previous analogue reading
boolean analogue1_changed;
boolean analogue1_changed;
#endif
#endif


#ifdef ANALOGUE2
#ifdef ANALOGUE2
uint8_t anlg2 = 1; // analog input pins
uint8_t anlg2 = 1; // analog input pins
int32_t old_reading_anlg2; // previous analogue reading
int32_t old_reading_anlg2; // previous analogue reading
boolean analogue2_changed;
boolean analogue2_changed;
#endif
#endif


#ifdef PID_CONTROL
#ifdef PID_CONTROL
#include <PID_v1.h>
#include <PID_v1.h>


//Define PID Variables we'll be connecting to
//Define PID Variables we'll be connecting to
double Setpoint, Input, Output, SV; // SV is for roasting software override of Setpoint
double Setpoint, Input, Output, SV; // SV is for roasting software override of Setpoint


//Specify the links and initial tuning parameters
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
uint8_t pid_chan = PID_CHAN; // identify PV and set default value from user.h
uint8_t pid_chan = PID_CHAN; // identify PV and set default value from user.h


int profile_number; // number of the profile for PID control
int profile_number; // number of the profile for PID control
int profile_ptr; // EEPROM pointer for profile data
int profile_ptr; // EEPROM pointer for profile data
char profile_name[40];
char profile_name[40];
char profile_description[80];
char profile_description[80];
int profile_number_new; // used when switching between profiles
int profile_number_new; // used when switching between profiles
int times[2], temps[2]; // time and temp values read from EEPROM for setpoint calculation
int times[2], temps[2]; // time and temp values read from EEPROM for setpoint calculation
char profile_CorF; // profile temps stored as Centigrade or Fahrenheit
char profile_CorF; // profile temps stored as Centigrade or Fahrenheit


#endif
#endif


//boolean artisan_logger = false;
//boolean artisan_logger = false;
//boolean ANDROID = false; // set initial state for ANDROID flag
//boolean ANDROID = false; // set initial state for ANDROID flag
//boolean roastlogger = false; // set initial state for roastlogger flag
//boolean roastlogger = false; // set initial state for roastlogger flag
uint32_t counter; // second counter
uint32_t counter; // second counter
uint32_t next_loop_time; //
uint32_t next_loop_time; //
boolean first;
boolean first;
uint16_t looptime = 1000;
uint16_t looptime = 1000;


// class objects
// class objects
cADC adc( A_ADC ); // MCP3424
cADC adc( A_ADC ); // MCP3424
ambSensor amb( A_AMB ); // MCP9800
ambSensor amb( A_AMB ); // MCP9800
filterRC fT[NC]; // filter for logged ET, BT
filterRC fT[NC]; // filter for logged ET, BT
filterRC fRise[NC]; // heavily filtered for calculating RoR
filterRC fRise[NC]; // heavily filtered for calculating RoR
filterRC fRoR[NC]; // post-filtering on RoR values
filterRC fRoR[NC]; // post-filtering on RoR values
//PWM16 ssr; // object for SSR output on OT1, OT2
PWM16 ssr; // object for SSR output on OT1, OT2 - prh reinstated this line
CmndInterp ci( DELIM ); // command interpreter object
CmndInterp ci( DELIM ); // command interpreter object


// array of thermocouple types
// array of thermocouple types
tcBase * tcp[4];
tcBase * tcp[4];
TC_TYPE1 tc1;
TC_TYPE1 tc1;
TC_TYPE2 tc2;
TC_TYPE2 tc2;
TC_TYPE3 tc3;
TC_TYPE3 tc3;
TC_TYPE4 tc4;
TC_TYPE4 tc4;


// ---------------------------------- LCD interface definition
// ---------------------------------- LCD interface definition
#ifdef LCD
#ifdef LCD
// LCD output strings
// LCD output strings
char st1[6],st2[6];
char st1[6],st2[6];
int LCD_mode = 0;
int LCD_mode = 0;
#ifdef LCDAPTER
#ifdef LCDAPTER
#include <cButton.h>
#include <cButton.h>
cButtonPE16 buttons; // class object to manage button presses
cButtonPE16 buttons; // class object to manage button presses
#define BACKLIGHT lcd.backlight();
#define BACKLIGHT lcd.backlight();
cLCD lcd; // I2C LCD interface
cLCD lcd; // I2C LCD interface
#else // parallel interface, standard LiquidCrystal
#else // parallel interface, standard LiquidCrystal
#define BACKLIGHT ;
#define BACKLIGHT ;
#define RS 2
#define RS 2
#define ENABLE 4
#define ENABLE 4
#define D4 7
#define D4 7
#define D5 8
#define D5 8
#define D6 12
#define D6 12
#define D7 13
#define D7 13
LiquidCrystal lcd( RS, ENABLE, D4, D5, D6, D7 ); // standard 4-bit parallel interface
LiquidCrystal lcd( RS, ENABLE, D4, D5, D6, D7 ); // standard 4-bit parallel interface
#endif
#endif
#endif
#endif
// --------------------------------------------- end LCD interface
// --------------------------------------------- end LCD interface


// T1, T2 = temperatures x 1000
// T1, T2 = temperatures x 1000
// t1, t2 = time marks, milliseconds
// t1, t2 = time marks, milliseconds
// ---------------------------------------------------
// ---------------------------------------------------
float calcRise( int32_t T1, int32_t T2, int32_t t1, int32_t t2 ) {
float calcRise( int32_t T1, int32_t T2, int32_t t1, int32_t t2 ) {
int32_t dt = t2 - t1;
int32_t dt = t2 - t1;
if( dt == 0 ) return 0.0; // fixme -- throw an exception here?
if( dt == 0 ) return 0.0; // fixme -- throw an exception here?
float dT = ( convertUnits( T2 ) - convertUnits( T1 ) ) * D_MULT;
float dT = ( convertUnits( T2 ) - convertUnits( T1 ) ) * D_MULT;
float dS = dt * 0.001; // convert from milli-seconds to seconds
float dS = dt * 0.001; // convert from milli-seconds to seconds
return ( dT / dS ) * 60.0; // rise per minute
return ( dT / dS ) * 60.0; // rise per minute
}
}




// ------------- wrapper for the command interpreter's serial line reader
// ------------- wrapper for the command interpreter's serial line reader
void checkSerial() {
void checkSerial() {
const char* result = ci.checkSerial();
const char* result = ci.checkSerial();
if( result != NULL ) { // some things we might want to do after a command is executed
if( result != NULL ) { // some things we might want to do after a command is executed
#if defined LCD && defined COMMAND_ECHO
#if defined LCD && defined COMMAND_ECHO
lcd.setCursor( 0, 0 ); // echo all commands to the LCD
lcd.setCursor( 0, 0 ); // echo all commands to the LCD
lcd.print( result );
lcd.print( result );
#endif
#endif
#ifdef MEMORY_CHK
#ifdef MEMORY_CHK
Serial.print(F("# freeMemory()="));
Serial.print(F("# freeMemory()="));
Serial.print(freeMemory());
Serial.print(freeMemory());
Serial.print(F(" , "));
Serial.print(F(" , "));
Serial.println( result );
Serial.println( result );
#endif
#endif
}
}
}
}


// ----------------------------------
// ----------------------------------
void checkStatus( uint32_t ms ) { // this is an active delay loop
void checkStatus( uint32_t ms ) { // this is an active delay loop
uint32_t tod = millis();
uint32_t tod = millis();
while( millis() < tod + ms ) {
while( millis() < tod + ms ) {
checkSerial();
checkSerial();
#ifdef LCDAPTER
#ifdef LCDAPTER
#if not ( defined ROASTLOGGER || defined ARTISAN || defined ANDROID )
#if not ( defined ROASTLOGGER || defined ARTISAN || defined ANDROID )
checkButtons();
checkButtons();
#endif
#endif
#endif
#endif
}
}
}
}


// ----------------------------------------------------
// ----------------------------------------------------
float convertUnits ( float t ) {
float convertUnits ( float t ) {
if( Cscale ) return F_TO_C( t );
if( Cscale ) return F_TO_C( t );
else return t;
else return t;
}
}


// ------------------------------------------------------------------
// ------------------------------------------------------------------
void logger() {
void logger() {


#ifdef ARTISAN
#ifdef ARTISAN
// print ambient
// print ambient
Serial.print( convertUnits( AT ), DP );
Serial.print( convertUnits( AT ), DP );
// print active channels
// print active channels
for( uint8_t jj = 0; jj < NC; ++jj ) {
for( uint8_t jj = 0; jj < NC; ++jj ) {
uint8_t k = actv[jj];
uint8_t k = actv[jj];
if( k > 0 ) {
if( k > 0 ) {
--k;
--k;
Serial.print(F(","));
Serial.print(F(","));
Serial.print( convertUnits( T[k] ),DP );
Serial.print( convertUnits( T[k] ),DP );


}
}
}
}


// check to see if PID is running, and output additional values if true
// check to see if PID is running, and output additional values if true
if( myPID.GetMode() != MANUAL ) { // If PID in AUTOMATIC mode
if( myPID.GetMode() != MANUAL ) { // If PID in AUTOMATIC mode
Serial.print(F(","));
Serial.print(F(","));
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
Serial.print( 0 );
Serial.print( 0 );
}
}
else {
else {
Serial.print( levelOT1 );
Serial.print( levelIO3 );
}
}
Serial.print(F(","));
Serial.print(F(","));
Serial.print( levelOT2 );
Serial.print( levelOT2 );
Serial.print(F(","));
Serial.print(F(","));
Serial.print( Setpoint );
Serial.print( Setpoint );
}
}
Serial.println();
Serial.println();




/*
/*
#ifdef PLOT_POWER
#ifdef PLOT_POWER
Serial.print(F(","));
Serial.print(F(","));
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
Serial.print( 0 );
Serial.print( 0 );
}
}
else {
else {
Serial.print( levelOT1 );
Serial.print( levelIO3 );
}
}
Serial.print(F(","));
Serial.print(F(","));
Serial.print( levelOT2 );
Serial.print( levelOT2 );
#endif
#endif
Serial.println();
Serial.println();
*/
*/


#endif
#endif


#ifdef ROASTLOGGER
#ifdef ROASTLOGGER
for( uint8_t jj = 0; jj < NC; ++jj ) {
for( uint8_t jj = 0; jj < NC; ++jj ) {
uint8_t k = actv[jj];
uint8_t k = actv[jj];
if( k > 0 ) {
if( k > 0 ) {
--k;
--k;
Serial.print(F("rorT"));
Serial.print(F("rorT"));
Serial.print(k+1);
Serial.print(k+1);
Serial.print(F("="));
Serial.print(F("="));
Serial.println( RoR[k], DP );
Serial.println( RoR[k], DP );
Serial.print(F("T"));
Serial.print(F("T"));
Serial.print(k+1);
Serial.print(k+1);
Serial.print(F("="));
Serial.print(F("="));
Serial.println( convertUnits( T[k] ) );
Serial.println( convertUnits( T[k] ) );
}
}
}
}
Serial.print(F("Power%="));
Serial.print(F("Power%="));
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
Serial.println( 0 );
Serial.println( 0 );
}
}
else {
else {
Serial.println( levelOT1 );
Serial.println( levelIO3 );
}
}
Serial.print(F("Fan="));
Serial.print(F("Fan="));
Serial.println( levelOT2 );
Serial.println( levelOT2 );
#endif
#endif




#ifdef ANDROID
#ifdef ANDROID


// print counter
// print counter
Serial.print( counter );
Serial.print( counter );
Serial.print( F(",") );
Serial.print( F(",") );


// print ambient
// print ambient
Serial.print( convertUnits( AT ), DP );
Serial.print( convertUnits( AT ), DP );
// print active channels
// print active channels
for( uint8_t jj = 0; jj < NC; ++jj ) {
for( uint8_t jj = 0; jj < NC; ++jj ) {
uint8_t k = actv[jj];
uint8_t k = actv[jj];
if( k > 0 ) {
if( k > 0 ) {
--k;
--k;
Serial.print(F(","));
Serial.print(F(","));
Serial.print( convertUnits( T[k] ) );
Serial.print( convertUnits( T[k] ) );
Serial.print(F(","));
Serial.print(F(","));
Serial.print( RoR[k], DP );
Serial.print( RoR[k], DP );
}
}
}
}
//#ifdef PLOT_POWER
//#ifdef PLOT_POWER
Serial.print(F(","));
Serial.print(F(","));
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // send 0 if OT1 has been cut off
Serial.print( 0 );
Serial.print( 0 );
}
}
else {
else {
Serial.print( levelOT1 );
Serial.print( levelIO3 );
}
}
Serial.print(F(","));
Serial.print(F(","));
Serial.print( levelOT2 );
Serial.print( levelOT2 );
//#endif
//#endif
#ifdef PID_CONTROL
#ifdef PID_CONTROL
Serial.print(F(","));
Serial.print(F(","));
Serial.print( Setpoint );
Serial.print( Setpoint );


#endif
#endif
Serial.println();
Serial.println();


#endif
#endif


}
}


// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void get_samples() // this function talks to the amb sensor and ADC via I2C
void get_samples() // this function talks to the amb sensor and ADC via I2C
{
{
int32_t v;
int32_t v;
tcBase * tc;
tcBase * tc;
float tempF;
float tempF;
int32_t itemp;
int32_t itemp;
float rx;
float rx;


uint16_t dly = amb.getConvTime(); // use delay based on slowest conversion
uint16_t dly = amb.getConvTime(); // use delay based on slowest conversion
uint16_t dADC = adc.getConvTime();
uint16_t dADC = adc.getConvTime();
dly = dly > dADC ? dly : dADC;
dly = dly > dADC ? dly : dADC;
for( uint8_t jj = 0; jj < NC; jj++ ) { // one-shot conversions on both chips
for( uint8_t jj = 0; jj < NC; jj++ ) { // one-shot conversions on both chips
uint8_t k = actv[jj]; // map logical channels to physical ADC channels
uint8_t k = actv[jj]; // map logical channels to physical ADC channels
if( k > 0 ) {
if( k > 0 ) {
--k;
--k;
tc = tcp[k]; // each channel may have its own TC type
tc = tcp[k]; // each channel may have its own TC type
adc.nextConversion( k ); // start ADC conversion on physical channel k
adc.nextConversion( k ); // start ADC conversion on physical channel k
amb.nextConversion(); // start ambient sensor conversion
amb.nextConversion(); // start ambient sensor conversion
checkStatus( dly ); // give the chips time to perform the conversions
checkStatus( dly ); // give the chips time to perform the conversions


if( !first ) { // on first loop dont save zero values
if( !first ) { // on first loop dont save zero values
ftemps_old[k] = ftemps[k]; // save old filtered temps for RoR calcs
ftemps_old[k] = ftemps[k]; // save old filtered temps for RoR calcs
ftimes_old[k] = ftimes[k]; // save old timestamps for filtered temps for RoR calcs
ftimes_old[k] = ftimes[k]; // save old timestamps for filtered temps for RoR calcs
}
}
ftimes[k] = millis(); // record timestamp for RoR calculations
ftimes[k] = millis(); // record timestamp for RoR calculations
amb.readSensor(); // retrieve value from ambient temp register
amb.readSensor(); // retrieve value from ambient temp register
v = adc.readuV(); // retrieve microvolt sample from MCP3424
v = adc.readuV(); // retrieve microvolt sample from MCP3424
tempF = tc->Temp_F( 0.001 * v, amb.getAmbF() ); // convert uV to Celsius
tempF = tc->Temp_F( 0.001 * v, amb.getAmbF() ); // convert uV to Celsius


// filter on direct ADC readings, not computed temperatures
// filter on direct ADC readings, not computed temperatures
v = fT[k].doFilter( v << 10 ); // multiply by 1024 to create some resolution for filter
v = fT[k].doFilter( v << 10 ); // multiply by 1024 to create some resolution for filter
v >>= 10;
v >>= 10;
AT = amb.getAmbF();
AT = amb.getAmbF();
T[k] = tc->Temp_F( 0.001 * v, AT ); // convert uV to Fahrenheit;
T[k] = tc->Temp_F( 0.001 * v, AT ); // convert uV to Fahrenheit;


ftemps[k] =fRise[k].doFilter( tempF * 1000 ); // heavier filtering for RoR
ftemps[k] =fRise[k].doFilter( tempF * 1000 ); // heavier filtering for RoR


if ( !first ) { // on first loop dont calc RoR
if ( !first ) { // on first loop dont calc RoR
rx = calcRise( ftemps_old[k], ftemps[k], ftimes_old[k], ftimes[k] );
rx = calcRise( ftemps_old[k], ftemps[k], ftimes_old[k], ftimes[k] );
RoR[k] = fRoR[k].doFilter( rx / D_MULT ) * D_MULT; // perform post-filtering on RoR values
RoR[k] = fRoR[k].doFilter( rx / D_MULT ) * D_MULT; // perform post-filtering on RoR values
}
}
}
}
}
}
first = false;
first = false;
};
};


#ifdef LCD
#ifdef LCD
// --------------------------------------------
// --------------------------------------------
void updateLCD() {
void updateLCD() {
if( LCD_mode == 0 ) { // Display normal LCD screen
if( LCD_mode == 0 ) { // Display normal LCD screen
lcd.setCursor(0,0);
lcd.setCursor(0,0);
if(counter/60 < 10) lcd.print(F("0")); lcd.print(counter/60); // Prob can do this better. Check aBourbon.
if(counter/60 < 10) lcd.print(F("0")); lcd.print(counter/60); // Prob can do this better. Check aBourbon.
lcd.print(F(":")); // make this blink?? :)
lcd.print(F(":")); // make this blink?? :)
if(counter - (counter/60)*60 < 10) lcd.print(F("0")); lcd.print(counter - (counter/60)*60);
if(counter - (counter/60)*60 < 10) lcd.print(F("0")); lcd.print(counter - (counter/60)*60);
#ifdef LCD_4x20
#ifdef LCD_4x20
#ifdef COMMAND_ECHO
#ifdef COMMAND_ECHO
lcd.print(F(" ")); // overwrite artisan commands
lcd.print(F(" ")); // overwrite artisan commands
#endif
#endif
// display the first 2 active channels encountered, normally BT and ET
// display the first 2 active channels encountered, normally BT and ET
int it01;
int it01;
uint8_t jj,j;
uint8_t jj,j;
uint8_t k;
uint8_t k;
for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
k = actv[jj];
k = actv[jj];
if( k != 0 ) {
if( k != 0 ) {
++j;
++j;
it01 = round( convertUnits( T[k-1] ) );
it01 = round( convertUnits( T[k-1] ) );
if( it01 > 999 )
if( it01 > 999 )
it01 = 999;
it01 = 999;
else
else
if( it01 < -999 ) it01 = -999;
if( it01 < -999 ) it01 = -999;
sprintf( st1, "%4d", it01 );
sprintf( st1, "%4d", it01 );
if( j == 1 ) {
if( j == 1 ) {
lcd.setCursor( 13, 0 );
lcd.setCursor( 13, 0 );
lcd.print(F("ET:"));
lcd.print(F("ET:"));
}
}
else {
else {
lcd.setCursor( 13, 1 );
lcd.setCursor( 13, 1 );
lcd.print( F("BT:") );
lcd.print( F("BT:") );
}
}
lcd.print(st1);
lcd.print(st1);
}
}
}
}
// AT
// AT
it01 = round( convertUnits( AT ) );
it01 = round( convertUnits( AT ) );
if( it01 > 999 )
if( it01 > 999 )
it01 = 999;
it01 = 999;
else
else
if( it01 < -999 ) it01 = -999;
if( it01 < -999 ) it01 = -999;
sprintf( st1, "%3d", it01 );
sprintf( st1, "%3d", it01 );
lcd.setCursor( 6, 0 );
lcd.setCursor( 6, 0 );
lcd.print(F("AT:"));
lcd.print(F("AT:"));
lcd.print(st1);
lcd.print(st1);
#ifdef PID_CONTROL
#ifdef PID_CONTROL
if( myPID.GetMode() != MANUAL ) { // if PID is on then display PID: nnn% instead of OT1:
if( myPID.GetMode() != MANUAL ) { // if PID is on then display PID: nnn% instead of OT1:
lcd.setCursor( 0, 2 );
lcd.setCursor( 0, 2 );
lcd.print( F("PID:") );
lcd.print( F("PID:") );
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
sprintf( st1, "%4d", (int)0 );
sprintf( st1, "%4d", (int)0 );
}
}
else {
else {
sprintf( st1, "%4d", (int)levelOT1 );
sprintf( st1, "%4d", (int)levelIO3 );
}
}
lcd.print( st1 ); lcd.print(F("%"));
lcd.print( st1 ); lcd.print(F("%"));
lcd.setCursor( 13, 2 ); // display setpoint if PID is on
lcd.setCursor( 13, 2 ); // display setpoint if PID is on
lcd.print( F("SP:") );
lcd.print( F("SP:") );
sprintf( st1, "%4d", (int)Setpoint );
sprintf( st1, "%4d", (int)Setpoint );
lcd.print( st1 );
lcd.print( st1 );
}
}
else {
else {
//#ifdef ANALOGUE1
#ifdef ANALOGUE1
lcd.setCursor( 13, 2 );
lcd.setCursor( 13, 2 );
lcd.print(F(" ")); // blank out SP: nnn if PID is off
lcd.print(F("MANUAL ")); // blank out SP: nnn if PID is off
//#else
#else
// lcd.setCursor( 0, 2 );
lcd.setCursor( 0, 2 );
// lcd.print(F(" ")); // blank out PID: nnn% and SP: nnn if PID is off and ANALOGUE1 isn't defined
// lcd.print(F(" ")); // blank out PID: nnn% and SP: nnn if PID is off and ANALOGUE1 isn't defined
//#endif // end ifdef ANALOGUE1
#endif // end ifdef ANALOGUE1
}
}
#endif // end ifdef PID_CONTROL
#endif // end ifdef PID_CONTROL
// RoR
// RoR
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
lcd.print( F("RoR:"));
lcd.print( F("RoR:"));
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
lcd.print( st1 );
lcd.print( st1 );
//#ifdef ANALOGUE1
#ifdef ANALOGUE1
#ifdef PID_CONTROL
#ifdef PID_CONTROL
if( myPID.GetMode() == MANUAL ) { // only display OT2: nnn% if PID is off so PID display isn't overwriten
if( myPID.GetMode() == MANUAL ) { // only display OT2: nnn% if PID is off so PID display isn't overwriten
lcd.setCursor( 0, 2 );
lcd.setCursor( 0, 2 );
lcd.print(F("OT1:"));
lcd.print(F("OUT:")); // prh changed "OT1" to "OUT"
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
sprintf( st1, "%4d", (int)0 );
sprintf( st1, "%4d", (int)0 );
}
}
else {
else {
sprintf( st1, "%4d", (int)levelOT1 );
sprintf( st1, "%4d", (int)levelIO3 );
}
}
lcd.print( st1 ); lcd.print(F("%"));
lcd.print( st1 ); lcd.print(F("%"));
}
}
#else // if PID_CONTROL isn't defined then always display OT1: nnn%
#else // if PID_CONTROL isn't defined then always display OT1: nnn%
lcd.setCursor( 0, 2 );
lcd.setCursor( 0, 2 );
lcd.print(F("OT1:"));
lcd.print(F("OUT:")); // prh changed "OT1" to "OUT"
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
sprintf( st1, "%4d", (int)0 );
sprintf( st1, "%4d", (int)0 );
}
}
else {
else {
sprintf( st1, "%4d", (int)levelOT1 );
sprintf( st1, "%4d", (int)levelIO3 );
}
}
lcd.print( st1 ); lcd.print(F("%"));
lcd.print( st1 ); lcd.print(F("%"));
#endif // end ifdef PID_CONTROL
#endif // end ifdef PID_CONTROL
//#endif // end ifdef ANALOGUE1
#endif // end ifdef ANALOGUE1
//#ifdef ANALOGUE2
//#ifdef ANALOGUE2
lcd.setCursor( 0, 3 );
lcd.setCursor( 0, 3 );
lcd.print(F("OT2:"));
lcd.print(F("Profile:")); // prh changed from "OT2:" to "Profile
sprintf( st1, "%4d", (int)levelOT2 );
lcd.setCursor( 10, 3 ); // prh added this line
lcd.print( st1 ); lcd.print(F("%"));
lcd.print(profile_number); // prh added this line
//lcd.print( st1 ); lcd.print(F("%"));
//#endif
//#endif
#else // if not def LCD_4x20 ie if using a standard 2x16 LCD
#else // if not def LCD_4x20 ie if using a standard 2x16 LCD
#ifdef COMMAND_ECHO
#ifdef COMMAND_ECHO
lcd.print(F(" ")); // overwrite artisan commands
lcd.print(F(" ")); // overwrite artisan commands
#endif
#endif
// display the first 2 active channels encountered, normally BT and ET
// display the first 2 active channels encountered, normally BT and ET
int it01;
int it01;
uint8_t jj,j;
uint8_t jj,j;
uint8_t k;
uint8_t k;
for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
k = actv[jj];
k = actv[jj];
if( k != 0 ) {
if( k != 0 ) {
++j;
++j;
it01 = round( convertUnits( T[k-1] ) );
it01 = round( convertUnits( T[k-1] ) );
if( it01 > 999 )
if( it01 > 999 )
it01 = 999;
it01 = 999;
else
else
if( it01 < -999 ) it01 = -999;
if( it01 < -999 ) it01 = -999;
sprintf( st1, "%4d", it01 );
sprintf( st1, "%4d", it01 );
if( j == 1 ) {
if( j == 1 ) {
lcd.setCursor( 9, 0 );
lcd.setCursor( 9, 0 );
lcd.print(F("ET:"));
lcd.print(F("ET:"));
}
}
else {
else {
lcd.setCursor( 9, 1 );
lcd.setCursor( 9, 1 );
lcd.print( F("BT:") );
lcd.print( F("BT:") );
}
}
lcd.print(st1);
lcd.print(st1);
}
}
}
}
#ifdef PID_CONTROL
#ifdef PID_CONTROL
if( myPID.GetMode() != MANUAL ) {
if( myPID.GetMode() != MANUAL ) {
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
lcd.print( F(" 0") );
lcd.print( F(" 0") );
}
}
else {
else {
sprintf( st1, "%3d", (int)levelOT1 );
sprintf( st1, "%3d", (int)levelIO3 );
lcd.print( st1 );
lcd.print( st1 );
}
}
lcd.print(F("%"));
lcd.print(F("%"));
sprintf( st1, "%4d", (int)Setpoint );
sprintf( st1, "%4d", (int)Setpoint );
lcd.print(st1);
lcd.print(st1);
}
}
else {
else {
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
lcd.print( F("RoR:"));
lcd.print( F("RoR:"));
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
lcd.print( st1 );
lcd.print( st1 );
}
}
#else
#else
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
lcd.print( F("RoR:"));
lcd.print( F("RoR:"));
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
sprintf( st1, "%4d", (int)RoR[ROR_CHAN] );
lcd.print( st1 );
lcd.print( st1 );
#endif // end ifdef PID_CONTROL
#endif // end ifdef PID_CONTROL
#ifdef ANALOGUE1
#ifdef ANALOGUE1
if( analogue1_changed == true ) { // overwrite RoR or PID values
if( analogue1_changed == true ) { // overwrite RoR or PID values
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
lcd.print(F("OT1: "));
lcd.print(F("OUT: ")); // prh changed "OT1" to "OUT"
lcd.setCursor( 4, 1 );
lcd.setCursor( 4, 1 );
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
if( levelOT2 < OT1_CUTOFF ) { // display 0% if OT1 has been cut off
sprintf( st1, "%3d", (int)0 );
sprintf( st1, "%3d", (int)0 );
}
}
else {
else {
sprintf( st1, "%3d", (int)levelOT1 );
sprintf( st1, "%3d", (int)levelIO3 );
}
}
lcd.print( st1 ); lcd.print(F("%"));
lcd.print( st1 ); lcd.print(F("%"));
}
}
#endif //ifdef ANALOGUE1
#endif //ifdef ANALOGUE1
#ifdef ANALOGUE2
#ifdef ANALOGUE2
if( analogue2_changed == true ) { // overwrite RoR or PID values
if( analogue2_changed == true ) { // overwrite RoR or PID values
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
lcd.print(F("OT2: "));
lcd.print(F("OT2: "));
lcd.setCursor( 4, 1 );
lcd.setCursor( 4, 1 );
sprintf( st1, "%3d", (int)levelOT2 );
sprintf( st1, "%3d", (int)levelOT2 );
lcd.print( st1 ); lcd.print(F("%"));
lcd.print( st1 ); lcd.print(F("%"));
}
}
#endif // end ifdef ANALOGUE2
#endif // end ifdef ANALOGUE2
#endif // end of ifdef LCD_4x20
#endif // end of ifdef LCD_4x20


}
}


else if( LCD_mode == 1 ) { // Display alternative 1 LCD display
else if( LCD_mode == 1 ) { // Display alternative 1 LCD display


#ifdef PID_CONTROL
#ifdef PID_CONTROL
#ifdef LCD_4x20
#ifdef LCD_4x20
lcd.setCursor( 0, 0 );
lcd.setCursor( 0, 0 );
for( int i = 0; i < 20; i++ ) {
for( int i = 0; i < 20; i++ ) {
if( profile_name[i] != 0 ) lcd.print( profile_name[i] );
if( profile_name[i] != 0 ) lcd.print( profile_name[i] );
}
}
lcd.setCursor( 0, 1 );
lcd.setCursor( 0, 1 );
for( int i = 0; i < 20; i++ ) {
for( int i = 0; i < 20; i++ )
if( profile_description[i] != 0 ) lcd.print( profile_description[i] );
}
lcd.setCursor( 0, 2 );
for( int i = 20; i < 40; i++ ) {
if( profile_description[i] != 0 ) lcd.print( profile_description[i] );
}
lcd.setCursor( 0, 3 );
lcd.print(F("PID: ")); lcd.print( myPID.GetKp() ); lcd.print(F(",")); lcd.print( myPID.GetKi() );