Untitled diff
718 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() );