Arduino coffee roaster fan control

I designed a coffee roaster and was using artisan to control. Then I decided to simplify and just use time and temperature controls. There is a fan I am using to push hot air around the beans, I need to be able to adjust the speed in arduino code. I made the physical product and wiring but had someone make the code, I haven't been able to get in touch with him so I am trying to figure it out myself (sort of). I figured the command would be in the user. h or the main code but I cant find just the fan speed. It is supposed to be controlled by IO3 (fan) and digital pin 3 on Uno. In the main code I have tried changing the IO3= to different numbers and the fan still runs at 100 percent. Below is the main code- any help would be appreciated.

/*
  Notes LMMS:
  1) The libraries with Suffix LMMS in the Arduino folder libraries were modified for LMMS
  2) If it is printed on the console and the encoders are used, they work badly
*/

// aArtisanQ_PID.ino
// ------------

// 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)
//   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

// *** BSD License ***
// ------------------------------------------------------------------------------------------
// Copyright (c) 2011, MLG Properties, LLC
// All rights reserved.
//
// Contributor:  Jim Gallt
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//   Redistributions of source code must retain the above copyright notice, this list of
//   conditions and the following disclaimer.
//
//   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
//   provided with the distribution.
//
//   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
//   written permission.
//
// 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
// 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,
// 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)
// 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
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ------------------------------------------------------------------------------------------

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


// ----------- aArtisanQ_PID (Brad Collins)
// 20120915 Created.
//          Added PID Library
//          Added code for analogue inputs
// 20120916 Added PID command allowing PID control to be activated and deactivated from Artisan
//          Added roast clock. Can be reset with PID;TIME command
//          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
//          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 PID tunings to be adjusted on the fly (required MAX_TOKENS 5 in cmndproc.h library)
// 20120920 Added RoR calcs
// 20120921 Updated RoR calcs to better handle first loop issue (RoR not calculated in first loop)
//          Stopped ANLG1 being read during PID control
//          Added code to convert PID Setpoint temps to correct units.  Added temperature units data to profile format
//          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)
//          Added code to allow power to OT1 to be cut if OT2 is below HTR_CUTOFF_FAN_VAL 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)
// 20121007 Fixed PID tuning command so it handles doubles
//          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
//          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
// 20121021 Added optional limits for Analogue1
// 20121120 Added support for pBourbon logging
// 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 (DUTY_STEP) in user.h
// 20130119 aArtisanQ_PID release 3_5
//          Added code to allow for additional LCD display modes
// 20130120 Added ability to change roast profile using LCD and buttons
// 20130121 Tidied up button press code
// 20130203 Permits use of different TC types on individual channels as in 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
// 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)
// 20140128 Improved handling of heater and fan power limits
// 20140213 aArtisanQ_PID release 4_2
// 20140214 Added option in user.h to define software mode (Artisan, Roastlogger or pBourbon)
//          Fixed? bug causing crashes when receiving READ commands
// 20140214 aArtisanQ_PID release 4_3
// 20150328 Replaced pBourbon option with Android option
//          Bug fix in LCD menu code.  Menu code disabled when not roasting stand alone
//          Changed default PID channel to 0
//          Added SV to values sent to Android app
// 20150328 aArtisanQ_PID release 5_0
// 20150403 Added PID;SV command from aArtisan
//          Changed default PID roast profile to 0 when using logging software
//          Reduced SRAM usage
// 20150404 Added PID;CHAN PID;CT and FILT commands
//          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
// 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
// 20150426 Removed io3, rf2000 and rc2000 commands 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
// 20160416 Added fast PWM (3.922kHz) available in Phase Angle Control Mode on IO3. Enable using IO3_HTR_PAC in user.h.
//          Moved default zero-cross interrupt to IO2 to allow PWM out on IO3.
//          Also does 3.922kHz PWM for DC fan on IO3 in PWM mode.
//          Added Min and Max for IO3 output
//          aArtisanQ_PID version 6_2 released for testing
// 20161214 Added some compile directives to disable the pwmio3 variable when IO3_HTR_PAC is not defined (JGG)
//          This was causing the pullup on IO3 to be disabled and intefered with use of IO3 as the ZCD input
// 20161216 Changes to user.h (and others) to implement pre-defined configurations
// 20171004 Changed definition of PID_CHAN from logical channel to physical channel
//          This was causing issues with Artisan Roasting Scope logic when doing background follow with TC4 PID and logical channels set using CHAN;2100
//          Artisan Roasting Scope is assuming X in PID;CHAN;X command is a physical channel
//          Adjusted ROR_CHAN code to match physical channel approach
//          Adjusted command.txt to suit above changes

#define BANNER_ARTISAN "aArtisanQ_PID 6_3"

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

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

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

#ifdef MEMORY_CHK
// debugging memory problems
#include "MemoryFree.h"
#endif
#ifdef PHASE_ANGLE_CONTROL
  // code for integral cycle control and phase angle control
  #include "phase_ctrl.h"
#endif

#include <PWM16.h> // for SSR output

// these "contributed" libraries must be installed in your sketchbook's arduino/libraries folder
#include <cmndproc.h> // for command interpreter
#include <thermocouple.h> // type K, type J, and type T thermocouple support
#include <cADC.h> // MCP3424

#ifdef LCD
#include <cLCD.h> // required only if LCD is used
#endif

#if defined KORRY
  // -LMMS 1Abr18-
  // Libraries for Digital Display and Encoders
  #include "TM1637.h"
  #include <CommonBusEncoders.h>
  // END LMMS
#endif

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

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

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

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

int levelOT1, levelOT2;                            // parameters to control output levels
#if !(defined PHASE_ANGLE_CONTROL && (INT_PIN == 3) )
  int levelIO3;
#endif

#ifdef MEMORY_CHK
  uint32_t checktime;
#endif

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

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

#ifdef PID_CONTROL
  #include <PID_v1.h>

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

  //Specify the links and initial tuning parameters
  PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
  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_ptr;                                 // EEPROM pointer for profile data
  char profile_name[40];
  char profile_description[80];
  int profile_number_new;                          // used when switching between profiles

  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

#endif

uint32_t counter;                                  // second counter
uint32_t next_loop_time;                           //
boolean first;
uint16_t looptime = 1000;

// class objects
cADC adc( A_ADC );                                 // MCP3424
ambSensor amb( A_AMB );                            // MCP9800
filterRC fT[NC];                                   // filter for logged ET, BT
filterRC fRise[NC];                                // heavily filtered for calculating RoR
filterRC fRoR[NC];                                 // post-filtering on RoR values
#ifndef PHASE_ANGLE_CONTROL
PWM16 ssr;                                         // object for SSR output on OT1, OT2
#endif
// -----------------------------------------
// revised 14-Dec-2016 by JGG to disable constructor of pwmio3 when IO3_HTR_PAC not used
// revised 24-Sep-2017 by Brad changed from #ifdef IO3_HTR_PAC to #ifndef CONFIG_PAC3
#ifndef CONFIG_PAC3
  PWM_IO3 pwmio3;
#endif

// -LMMS 1Abr18-
#if defined KORRY
  // Objects and variables for Digital Displays
  // Timer
  #define CLK_1 A1                                 // Pins timer display
  #define DIO_1 A0
  TM1637 digitalDisplayTime(CLK_1,DIO_1);          // Timer display object
  #define TIME_TIMER_SET_FLASHED 5000              // Time roasting to flash preset
  long timeTimerSetFlashed;                        // Time roasting preset flashed
  // Temperature
  #define CLK_2 A3                                 // Pins temperature display
  #define DIO_2 A2
  TM1637 digitalDisplayTemperature(CLK_2,DIO_2);   // Temperature display object
  #define TEMPERATURE_TIME_FLASHED_BT 5000         // Time the BT tmperature must be flashed
  boolean flagCelsiusDegrees=false;                // Temperature in celsius degrees?

  // E N C O D E R S
  // Objects and variables for encoders
  int16_t previousValueEncoderTime;

  // Both
  CommonBusEncoders encoders(4, 5, 6, 2);

  // Temperature
  #define MAXIMUM_ROAST_TIME 30                    // Maximum roasting time
  #define TEMPERATURE_BUTTON_HOLD_CHANGE_UNITS 3000   // Time encoder button pressed to change units (°F, °C)
  long timeTemperatureBTFlashed=millis();
  boolean flagSettingRoastTemperature=false;       // Is the roast temperature setting?
  boolean flagTemperatureSetting = false;          // Has the encoder moved?

  // Both
  #define TIME_WAIT_ENCODER_MORE_ACTIVITY 5000     // Time wait more encoder activity
  boolean flagMovingEncoder=false;                 // Has the encoder moved?
  long timeEncoderMoved;

  // General variables
  int temperatureET,temperatureBT;
#endif
// -END LMMS 1Abr18-

// --------------------------------------
CmndInterp ci( DELIM );                            // command interpreter object

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

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

// T1, T2 = temperatures x 1000
// t1, t2 = time marks, milliseconds

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

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

// ----------------------------------
void checkStatus( uint32_t ms ) {                  // this is an active delay loop
  uint32_t tod = millis();
  while( millis() < tod + ms ) {
    checkSerial();
    #ifndef PHASE_ANGLE_CONTROL
      dcfan.slew_fan();                            // keep the fan smoothly increasing in speed
    #endif
    #ifdef LCDAPTER
      #if not ( defined ROASTLOGGER || defined ARTISAN || defined ANDROID )
        checkButtons();
      #endif
    #endif
  }
}

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

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

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

    }
  }

  Serial.print(F(","));
  if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {            // send 0 if OT1 has been cut off
      Serial.print( 0 );
  }
  else {
    Serial.print( HEATER_DUTY );
  }
  Serial.print(F(","));
  Serial.print( FAN_DUTY );
  Serial.print(F(","));
  if( myPID.GetMode() != MANUAL ) {                // If PID in AUTOMATIC mode
    Serial.print( Setpoint );
  } else {
    Serial.print( 0 );                             // send 0 if PID is off
  }

  Serial.println();

#endif

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


#ifdef ANDROID

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

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

  //#ifdef PLOT_POWER
  Serial.print(F(","));
  if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {            // send 0 if OT1 has been cut off
    Serial.print( 0 );
  }
  else {
    Serial.print( HEATER_DUTY );
  }
  Serial.print(F(","));
  Serial.print( FAN_DUTY );
  //#endif

  #ifdef PID_CONTROL
  Serial.print(F(","));
  Serial.print( Setpoint );

  #endif

  Serial.println();

#endif

}

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

  uint16_t dly = amb.getConvTime();                // use delay based on slowest conversion
  uint16_t dADC = adc.getConvTime();
  dly = dly > dADC ? dly : dADC;

  for( uint8_t jj = 0; jj < NC; jj++ ) {           // one-shot conversions on both chips

    // -LMMS 1Abr18-
    #if defined KORRY
      // Check encoders activity
      checkEncoders();
      if(flagMovingEncoder)
          return;
    #endif
  // -END LMMS-

    uint8_t k = actv[jj];                          // map logical channels to physical ADC channels
    if( k > 0 ) {
      --k;
      tc = tcp[k];                                 // each channel may have its own TC type
      adc.nextConversion( k );                     // start ADC conversion on physical channel k
      amb.nextConversion();                        // start ambient sensor conversion
      checkStatus( dly );                          // give the chips time to perform the conversions

      if( !first ) {                               // on first loop dont save zero values
        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[k] = millis();                        // record timestamp for RoR calculations

      amb.readSensor();                            // retrieve value from ambient temp register
      v = adc.readuV();                            // retrieve microvolt sample from MCP3424
      tempF = tc->Temp_F( 0.001 * v, amb.getAmbF() ); // convert uV to Celsius

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

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

      if ( !first ) {                              // on first loop dont calc RoR
        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
      }
    }
  }
  first = false;
};

#ifdef LCD
// --------------------------------------------
void updateLCD() {

  if( LCD_mode == 0 ) {                            // Display normal LCD screen

    lcd.setCursor(0,0);
    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?? :)
    if(counter - (counter/60)*60 < 10) lcd.print(F("0")); lcd.print(counter - (counter/60)*60);

  #ifdef LCD_4x20

  #ifdef COMMAND_ECHO
    lcd.print(F(" "));                             // overwrite artisan commands
  #endif

    // display the first 2 active channels encountered, normally BT and ET
    int it01;
    uint8_t jj,j;
    uint8_t k;
    for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
      k = actv[jj];
      if( k != 0 ) {
        ++j;
        it01 = round( convertUnits( T[k-1] ) );
        if( it01 > 999 )
          it01 = 999;
        else
          if( it01 < -999 ) it01 = -999;
        sprintf( st1, "%4d", it01 );
        if( j == 1 ) {
          lcd.setCursor( 13, 0 );
          lcd.print(F("ET:"));
        }
        else {
          lcd.setCursor( 13, 1 );
          lcd.print( F("BT:") );
        }
        lcd.print(st1);
      }
    }

  // AT
    it01 = round( convertUnits( AT ) );
    if( it01 > 999 )
      it01 = 999;
    else
      if( it01 < -999 ) it01 = -999;
    sprintf( st1, "%3d", it01 );
    lcd.setCursor( 6, 0 );
    lcd.print(F("AT:"));
    lcd.print(st1);

  #ifdef PID_CONTROL
    if( myPID.GetMode() != MANUAL ) {              // if PID is on then display PID: nnn% instead of OT1:
      lcd.setCursor( 0, 2 );
      lcd.print( F("PID:") );
      if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {        // display 0% if OT1 has been cut off
      sprintf( st1, "%4d", (int)0 );
      }
      else {
        sprintf( st1, "%4d", (int)HEATER_DUTY );
      }
      lcd.print( st1 ); lcd.print(F("%"));

      lcd.setCursor( 13, 2 );                      // display setpoint if PID is on
      lcd.print( F("SP:") );
      sprintf( st1, "%4d", (int)Setpoint );
      lcd.print( st1 );
    }
    else {
  //#ifdef ANALOGUE1
      lcd.setCursor( 13, 2 );
      lcd.print(F("       "));                     // blank out SP: nnn if PID is off
  //#else
  //    lcd.setCursor( 0, 2 );
  //    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 PID_CONTROL

    // RoR
    lcd.setCursor( 0, 1 );
    lcd.print( F("RoR:"));
    sprintf( st1, "%4d", (int)RoR[ROR_CHAN - 1] ); // adjust ROR_CHAN for 0-based array index
    lcd.print( st1 );


  //#ifdef ANALOGUE1
  #ifdef PID_CONTROL
    if( myPID.GetMode() == MANUAL ) {              // only display OT2: nnn% if PID is off so PID display isn't overwriten
      lcd.setCursor( 0, 2 );
      lcd.print(F("HTR:"));
      if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {        // display 0% if OT1 has been cut off
      sprintf( st1, "%4d", (int)0 );
      }
      else {
        sprintf( st1, "%4d", (int)HEATER_DUTY );
      }
      lcd.print( st1 ); lcd.print(F("%"));
    }

  #else                                            // if PID_CONTROL isn't defined then always display OT1: nnn%
      lcd.setCursor( 0, 2 );
      lcd.print(F("HTR:"));
      if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {        // display 0% if OT1 has been cut off
      sprintf( st1, "%4d", (int)0 );
      }
      else {
        sprintf( st1, "%4d", (int)HEATER_DUTY );
      }
      lcd.print( st1 ); lcd.print(F("%"));
  #endif // end ifdef PID_CONTROL
  //#endif // end ifdef ANALOGUE1

  //#ifdef ANALOGUE2
    lcd.setCursor( 0, 3 );
    lcd.print(F("FAN:"));
    sprintf( st1, "%4d", (int)FAN_DUTY );
    lcd.print( st1 ); lcd.print(F("%"));
  //#endif

  #else                                            // if not def LCD_4x20 ie if using a standard 2x16 LCD

  #ifdef COMMAND_ECHO
    lcd.print(F("    "));                          // overwrite artisan commands
  #endif

    // display the first 2 active channels encountered, normally BT and ET
    int it01;
    uint8_t jj,j;
    uint8_t k;
    for( jj = 0, j = 0; jj < NC && j < 2; ++jj ) {
      k = actv[jj];
      if( k != 0 ) {
        ++j;
        it01 = round( convertUnits( T[k-1] ) );
        if( it01 > 999 )
          it01 = 999;
        else
          if( it01 < -999 ) it01 = -999;
        sprintf( st1, "%4d", it01 );
        if( j == 1 ) {
          lcd.setCursor( 9, 0 );
          lcd.print(F("ET:"));
        }
        else {
          lcd.setCursor( 9, 1 );
          lcd.print( F("BT:") );
        }
        lcd.print(st1);
      }
    }

  #ifdef PID_CONTROL
    if( myPID.GetMode() != MANUAL ) {
      lcd.setCursor( 0, 1 );
      if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {        // display 0% if OT1 has been cut off
        lcd.print( F("  0") );
      }
      else {
        sprintf( st1, "%3d", (int)HEATER_DUTY );
        lcd.print( st1 );
      }
      lcd.print(F("%"));
      sprintf( st1, "%4d", (int)Setpoint );
      lcd.print(st1);
    }
    else {
      lcd.setCursor( 0, 1 );
      lcd.print( F("RoR:"));
      sprintf( st1, "%4d", (int)RoR[ROR_CHAN - 1] ); // adjust ROR_CHAN for 0-based array index
      lcd.print( st1 );
    }
  #else
      lcd.setCursor( 0, 1 );
      lcd.print( F("RoR:"));
      sprintf( st1, "%4d", (int)RoR[ROR_CHAN - 1] ); // adjust ROR_CHAN for 0-based array index
      lcd.print( st1 );
  #endif // end ifdef PID_CONTROL

  #ifdef ANALOGUE1
    if( analogue1_changed == true ) {              // overwrite RoR or PID values
      lcd.setCursor( 0, 1 );
      lcd.print(F("OT1:     "));
      lcd.setCursor( 4, 1 );
      if( FAN_DUTY < HTR_CUTOFF_FAN_VAL ) {        // display 0% if OT1 has been cut off
        sprintf( st1, "%3d", (int)0 );
      }
      else {
        sprintf( st1, "%3d", (int)HEATER_DUTY );
      }
      lcd.print( st1 ); lcd.print(F("%"));
    }
  #endif //ifdef ANALOGUE1
  #ifdef ANALOGUE2
    if( analogue2_changed == true ) {              // overwrite RoR or PID values
      lcd.setCursor( 0, 1 );
      lcd.print(F("OT2:     "));
      lcd.setCursor( 4, 1 );
      sprintf( st1, "%3d", (int)FAN_DUTY );
      lcd.print( st1 ); lcd.print(F("%"));
    }
  #endif // end ifdef ANALOGUE2

  #endif // end of ifdef LCD_4x20

  }

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

  #ifdef PID_CONTROL
  #ifdef LCD_4x20
    lcd.setCursor( 0, 0 );
    for( int i = 0; i < 20; i++ ) {
      if( profile_name[i] != 0 ) lcd.print( profile_name[i] );
    }
    lcd.setCursor( 0, 1 );
    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() ); lcd.print(F(",")); lcd.print( myPID.GetKd() );

  #else                                            // if not def LCD_4x20 ie if using a standard 2x16 LCD
    lcd.setCursor( 0, 0 );
    for( int i = 0; i < 20; i++ ) {
      if( profile_name[i] != 0 ) lcd.print( profile_name[i] );
    }
    lcd.setCursor( 0, 1 );
    lcd.print(F("P:")); lcd.print( myPID.GetKp() ); lcd.print(F(",")); lcd.print( myPID.GetKi() ); lcd.print(F(",")); lcd.print( myPID.GetKd() );
  #endif // end ifdef LCD_4x20
  #endif // end ifdef PID_CONTROL
  }

}                                                  // end of updateLCD()
#endif // end ifdef LCD

#if defined ANALOGUE1 || defined ANALOGUE2
// -------------------------------- reads analog value and maps it to 0 to 100
// -------------------------------- rounded to the nearest DUTY_STEP value
int32_t getAnalogValue( uint8_t port ) {
  int32_t mod, trial, min_anlg1, max_anlg1, min_anlg2, max_anlg2;
#ifdef PHASE_ANGLE_CONTROL
#ifdef IO3_HTR_PAC
  min_anlg1 = MIN_IO3;
  max_anlg1 = MAX_IO3;
  min_anlg2 = MIN_OT2;
  max_anlg2 = MAX_OT2;
#else
  min_anlg1 = MIN_OT1;
  max_anlg1 = MAX_OT1;
  min_anlg2 = MIN_OT2;
  max_anlg2 = MAX_OT2;
#endif
#else // PWM Mode
  min_anlg1 = MIN_OT1;
  max_anlg1 = MAX_OT1;
  min_anlg2 = MIN_IO3;
  max_anlg2 = MAX_IO3;
#endif
  float aval;
  aval = analogRead( port );
  #ifdef ANALOGUE1
    if( port == anlg1 ) {
      aval = min_anlg1 * 10.24 + ( aval / 1024 ) * 10.24 * ( max_anlg1 - min_anlg1 ) ; // scale analogue value to new range
      if ( aval == ( min_anlg1 * 10.24 ) ) aval = 0; // still allow OT1 to be switched off at minimum value. NOT SURE IF THIS FEATURE IS GOOD???????
      mod = min_anlg1;
    }
  #endif
  #ifdef ANALOGUE2
    if( port == anlg2 ) {
      aval = min_anlg2 * 10.24 + ( aval / 1024 ) * 10.24 * ( max_anlg2 - min_anlg2 ) ; // scale analogue value to new range
      if ( aval == ( min_anlg2 * 10.24 ) ) aval = 0; // still allow OT2 to be switched off at minimum value. NOT SURE IF THIS FEATURE IS GOOD???????
      mod = min_anlg2;
    }
  #endif
  trial = ( aval + 0.001 ) * 100;                  // to fix weird rounding error from previous calcs?????
  trial /= 1023;
  trial = ( trial / DUTY_STEP ) * DUTY_STEP;       // truncate to multiple of DUTY_STEP
  if( trial < mod ) trial = 0;
//  mod = trial % DUTY_STEP;
//  trial = ( trial / DUTY_STEP ) * DUTY_STEP;     // truncate to multiple of DUTY_STEP
//  if( mod >= DUTY_STEP / 2 )
//    trial += DUTY_STEP;
  return trial;
}
#endif // end if defined ANALOGUE1 || defined ANALOGUE2

#ifdef ANALOGUE1
// ---------------------------------
void readAnlg1() {                                 // read analog port 1 and adjust OT1 output
  char pstr[5];
  int32_t reading;
  reading = getAnalogValue( anlg1 );
  if( reading <= 100 && reading != old_reading_anlg1 ) { // did it change?
    analogue1_changed = true;
    old_reading_anlg1 = reading;                   // save reading for next time
#ifdef PHASE_ANGLE_CONTROL
#ifdef IO3_HTR_PAC
    levelIO3 = reading;
    outIO3();
#else
    levelOT1 = reading;
    outOT1();
#endif
#else                                              // PWM Mode
    levelOT1 = reading;
    outOT1();
#endif
  }
  else {
    analogue1_changed = false;
  }
}
#endif                                             // end ifdef ANALOGUE1

#ifdef ANALOGUE2
// ---------------------------------
void readAnlg2() {                                 // read analog port 2 and adjust OT2 output
  char pstr[5];
  int32_t reading;
  reading = getAnalogValue( anlg2 );
  if( reading <= 100 && reading != old_reading_anlg2 ) { // did it change?
    analogue2_changed = true;
    old_reading_anlg2 = reading;                   // save reading for next time
#ifdef PHASE_ANGLE_CONTROL
    levelOT2 = reading;
    outOT2();                                      // update fan output on OT2
#else // PWM Mode
    levelIO3 = reading;
    outIO3();                                      // update fan output on IO3
#endif
  }
  else {
    analogue2_changed = false;
  }
}
#endif // end ifdef ANALOGUE2


#ifdef PID_CONTROL
// ---------------------------------
void updateSetpoint() {                            //read profile data from EEPROM and calculate new setpoint

  if(profile_number > 0 ){
    while( counter < times[0] || counter >= times[1] ) { // if current time outside currently loaded interval then adjust profile pointer before reading new interval data from EEPROM
      if( counter < times[0] ) {
        profile_ptr = profile_ptr - 2;             // two bytes per int
      }
      else {
        profile_ptr = profile_ptr + 2;             // two bytes per int
      }

      eeprom.read( profile_ptr, (uint8_t*)&times, sizeof(times) ); // read two profile times
      eeprom.read( profile_ptr + 100, (uint8_t*)&temps, sizeof(temps) ); // read two profile temps.  100 = size of time data

      if( times[1] == 0 ) {
        Setpoint = 0;
        myPID.SetMode(MANUAL);                     // deactivate PID control
        Output = 0; // set PID output to 0
        break;
      }
    }

    float x = (float)( counter - times[0] ) / (float)( times[1] - times[0] ); // can probably be tidied up?? Calcs proportion of time through current profile interval
    Setpoint = temps[0] + x * ( temps[1] - temps[0] );  // then applies the proportion to the temps
    if( profile_CorF == 'F' && Cscale ) {          // make setpoint units match current units
      Setpoint = convertUnits( Setpoint );         // convert F to C
    }
    else if( profile_CorF == 'C' & !Cscale) {      // make setpoint units match current units
      Setpoint = Setpoint * 9 / 5 + 32; // convert C to F
    }
  }
  else {
    Setpoint = SV;
  }
}


void setProfile() {                                // set profile pointer and read initial profile data

  if( profile_number > 0 ) {
    profile_ptr = 1024 + ( 400 * ( profile_number - 1 ) ) + 4; // 1024 = start of profile storage in EEPROM. 400 = size of each profile. 4 = location of profile C or F data
    eeprom.read( profile_ptr, (uint8_t*)&profile_CorF, sizeof(profile_CorF) ); // read profile temp type

    getProfileDescription(profile_number);         // read profile name and description data from eeprom for active profile number

    profile_ptr = 1024 + ( 400 * ( profile_number - 1 ) ) + 125; // 1024 = start of profile storage in EEPROM. 400 = size of each profile. 125 = size of profile header data
    eeprom.read( profile_ptr, (uint8_t*)&times, sizeof(times) ); // read 1st two profile times
    eeprom.read( profile_ptr + 100, (uint8_t*)&temps, sizeof(temps) ); // read 1st two profile temps.  100 = size of time data

    // profile_ptr is left set for profile temp/time reads
  }
  //else //do something?
}

void getProfileDescription(int pn) {               // read profile name and description data from eeprom

  if( profile_number > 0 ) {
    int pp = 1024 + ( 400 * ( pn - 1 ) ) + 5; // 1024 = start of profile storage in EEPROM. 400 = size of each profile. 5 = location of profile name
    eeprom.read( pp, (uint8_t*)&profile_name, sizeof(profile_name) ); // read profile name

    pp = 1024 + ( 400 * ( pn - 1 ) ) + 45; // 1024 = start of profile storage in EEPROM. 400 = size of each profile. 45 = location of profile description
    eeprom.read( pp, (uint8_t*)&profile_description, sizeof(profile_description) ); // read profile name
  }
   //else //do something?
}
#endif // end ifdef PID_CONTROL

#ifdef LCDAPTER
// ----------------------------------
void checkButtons() {                              // take action if a button is pressed
  if( buttons.readButtons() ) {

    switch (LCD_mode) {

      case 0: // Main LCD display

        if( buttons.keyPressed( 0 ) && buttons.keyChanged( 0 ) ) { // button 1 - PID on/off - PREVIOUS PROFILE
          #ifdef PID_CONTROL
          if( myPID.GetMode() == MANUAL ) {
            myPID.SetMode( AUTOMATIC );
          }
          else {
            myPID.SetMode( MANUAL );
          }
          #endif
        }
        else if( buttons.keyPressed( 1 ) && buttons.keyChanged( 1 ) ) { // button 2 - RESET TIMER - NEXT PROFILE
          counter = 0;
        }
        else if( buttons.keyPressed( 2 ) && buttons.keyChanged( 2 ) ) { // button 3 - ENTER BUTTON
          // do something
        }
        else if( buttons.keyPressed( 3 ) && buttons.keyChanged( 3 ) ) { // button 4 - CHANGE LCD MODE
          lcd.clear();
          LCD_mode++; // change mode
          #ifndef PID_CONTROL
          if( LCD_mode == 1 ) LCD_mode++;          // deactivate LCD mode 1 if PID control is disabled
          #endif
          if( LCD_mode > 1 ) LCD_mode = 0;         // loop at limit of modes
          delay(5);
        }
        break;

      case 1: // Profile Selection and PID parameter LCD display

        if( buttons.keyPressed( 0 ) && buttons.keyChanged( 0 ) ) { // button 1 - PID on/off - PREVIOUS PROFILE
          #ifdef PID_CONTROL
          profile_number_new--;
          if( profile_number_new == 0 ) profile_number_new = NUM_PROFILES; // loop profile_number to end
          getProfileDescription(profile_number_new);
          #endif
        }
        else if( buttons.keyPressed( 1 ) && buttons.keyChanged( 1 ) ) { // button 2 - RESET TIMER - NEXT PROFILE
          #ifdef PID_CONTROL
          profile_number_new++;
          if( profile_number_new > NUM_PROFILES ) profile_number_new = 1; // loop profile_number to start
          getProfileDescription(profile_number_new);
          #endif
        }
        else if( buttons.keyPressed( 2 ) && buttons.keyChanged( 2 ) ) { // button 3 - ENTER BUTTON
          #ifdef PID_CONTROL
          profile_number = profile_number_new;     // change profile_number to new selection
          setProfile();                            // call setProfile to load the profile selected
          lcd.clear();
          LCD_mode = 0;                            // jump back to main LCD display mode
          #endif
        }
        else if( buttons.keyPressed( 3 ) && buttons.keyChanged( 3 ) ) { // button 4 - CHANGE LCD MODE
          lcd.clear();
          #ifdef PID_CONTROL
          profile_number_new = profile_number;     // reset profile_number_new if profile wasn't changed
          setProfile();                            // or getProfileDescription()?????????
          #endif
          LCD_mode++;                              // change mode
          if( LCD_mode > 1 ) LCD_mode = 0;         // loop at limit of modes
          delay(5);
        }
        break;
    } //end of switch
  } // end of if( buttons.readButtons() )
} // end of void checkButtons()
#endif // end ifdef LCDAPTER


// ----------------------------------
void outOT1() { // update output for OT1
  uint8_t new_levelot1;
#ifdef PHASE_ANGLE_CONTROL
#ifdef IO3_HTR_PAC                                 // OT1 not cutoff by fan duty in IO3_HTR_PAC mode
  new_levelot1 = levelOT1;
#else
  if ( levelOT2 < HTR_CUTOFF_FAN_VAL ) {
    new_levelot1 = 0;
  }
  else {
    new_levelot1 = levelOT1;
  }
#endif
  output_level_icc( new_levelot1 );
#else // PWM Mode
  if ( levelIO3 < HTR_CUTOFF_FAN_VAL ) {
    new_levelot1 = 0;
  }
  else {
    new_levelot1 = levelOT1;
  }
  ssr.Out( new_levelot1, levelOT2 );
#endif

}

// ----------------------------------
void outOT2() {                                    // update output for OT2

  #ifdef PHASE_ANGLE_CONTROL
    #ifdef IO3_HTR_PAC
      outIO3();                                    // update IO3 output to cut or reinstate power to heater if required
    #else
      outOT1();                                    // update OT1 output to cut or reinstate power to heater if required
    #endif
    output_level_pac( levelOT2 );
  #else                                            // PWM Mode
    if( levelIO3 < HTR_CUTOFF_FAN_VAL ) {          // if levelIO3 < cutoff value then turn off heater
      ssr.Out( 0, levelOT2 );
    }
    else {                                         // turn OT1 and OT2 back on again if levelIO3 is above cutoff value.
      ssr.Out( levelOT1, levelOT2 );
    }
  #endif
}

// ----------------------------------
void outIO3() {                                    // update output for IO3

  float pow;

  #ifdef PHASE_ANGLE_CONTROL
    #ifdef IO3_HTR_PAC
      uint8_t new_levelio3;
      new_levelio3 = levelIO3;
      if( levelOT2 < HTR_CUTOFF_FAN_VAL ) {        // if levelIO3 < cutoff value then turn off heater on IO3
        new_levelio3 = 0;
      }
      pow = 2.55 * new_levelio3;
      pwmio3.Out( round(pow) );
    #endif // IO3_HTR_PAC
  #else // PWM Mode, fan on IO3
    if( levelIO3 < HTR_CUTOFF_FAN_VAL ) {          // if levelIO3 < cutoff value then turn off heater on OT1
      ssr.Out( 0, levelOT2 );
    }
    else {                                         // turn OT1 and OT2 back on again if levelIO3 is above cutoff value.
      ssr.Out( levelOT1, levelOT2 );
    }
    pow = 2.55 * levelIO3;
    pwmio3.Out( round(pow) );
  #endif // PWM Mode, fan on IO3
}

// ------------------------------------------------------------------------
// MAIN
//
void setup()
{
  delay(100);
  Wire.begin();
  //Serial.begin(BAUD);
  Serial.begin(9600);
  amb.init( AMB_FILTER );                          // initialize ambient temp filtering

  #ifdef LCD
    #ifdef LCD_4x20
      lcd.begin(20, 4);
    #else
      lcd.begin(16, 2);
    #endif // LCD_4x20
    BACKLIGHT;
    lcd.setCursor( 0, 0 );
    lcd.print( BANNER_ARTISAN );                   // display version banner
    lcd.setCursor( 0, 1 );
    #ifdef ANDROID
      lcd.print( F("ANDROID") );                   // display version banner
    #endif // ANDROID
    #ifdef ARTISAN
      lcd.print( F("ARTISAN") );                   // display version banner
    #endif // ARTISAN
    #ifdef ROASTLOGGER
      lcd.print( F("ROASTLOGGER") );               // display version banner
    #endif // ROASTLOGGER

  #endif // LCD

  #ifdef LCDAPTER
    buttons.begin( 4 );
    buttons.readButtons();
    buttons.ledAllOff();
  #endif

  #ifdef MEMORY_CHK
    Serial.print(F("# freeMemory()="));
    Serial.println(freeMemory());
  #endif

  adc.setCal( CAL_GAIN, UV_OFFSET );
  amb.setOffset( AMB_OFFSET );

  // read calibration and identification data from eeprom
  if( readCalBlock( eeprom, caldata ) ) {
    adc.setCal( caldata.cal_gain, caldata.cal_offset );
    amb.setOffset( caldata.K_offset );
  }
  else {                                           // if there was a problem with EEPROM read, then use default values
    adc.setCal( CAL_GAIN, UV_OFFSET );
    amb.setOffset( AMB_OFFSET );
  }

  // initialize filters on all channels
  fT[0].init( ET_FILTER );                         // digital filtering on ET
  fT[1].init( BT_FILTER );                         // digital filtering on BT
  fT[2].init( ET_FILTER);
  fT[3].init( ET_FILTER);
  fRise[0].init( RISE_FILTER );                    // digital filtering for RoR calculation
  fRise[1].init( RISE_FILTER );                    // digital filtering for RoR calculation
  fRoR[0].init( ROR_FILTER );                      // post-filtering on RoR values
  fRoR[1].init( ROR_FILTER );                      // post-filtering on RoR values

  // set up output on OT1 and OT2 and IO3
  levelOT1 = levelOT2 = 0;
  #if !(defined PHASE_ANGLE_CONTROL && (INT_PIN == 3) )
    levelIO3 = 0;
  #endif
  #ifndef PHASE_ANGLE_CONTROL
    ssr.Setup( TIME_BASE );
  #else
    init_control();
  #endif
  // --------------------------
  // modifed 14-Dec-2016 by JGG
  // revised 22-Mar-2017 by Brad changed from #ifdef IO3_HTR_PAC to #ifndef CONFIG_PAC3
  #ifndef CONFIG_PAC3
    pwmio3.Setup( IO3_PCORPWM, IO3_PRESCALE_8 );   // setup pmw frequency ion IO3
  #endif
  // ----------------------------

  #ifdef ANALOGUE1
    old_reading_anlg1 = getAnalogValue( anlg1 );   // initialize old_reading with initial analogue value
  #endif
  #ifdef ANALOGUE2
    old_reading_anlg2 = getAnalogValue( anlg2 );   // initialize old_reading with initial analogue value
  #endif

  // initialize the active channels to default values
  actv[0] = 2;                                     // ET on TC1
  actv[1] = 1;                                     // BT on TC2
  actv[2] = 0;                                     // default inactive
  actv[3] = 0;                                     // default inactive

  // assign thermocouple types
  tcp[0] = &tc1;
  tcp[1] = &tc2;
  tcp[2] = &tc3;
  tcp[3] = &tc4;

// add active commands to the linked list in the command interpreter object
  ci.addCommand( &dwriter );
  ci.addCommand( &awriter );
  ci.addCommand( &units );
  ci.addCommand( &chan );
  #if ( !defined( PHASE_ANGLE_CONTROL ) ) || ( INT_PIN != 3 ) // disable when PAC active and pin 3 reads the ZCD
    ci.addCommand( &io3 );
    ci.addCommand( &dcfan );
  #endif
  ci.addCommand( &ot2 );
  ci.addCommand( &ot1 );
  ci.addCommand( &reader );
  ci.addCommand( &pid );
  ci.addCommand( &reset );
  #ifdef ROASTLOGGER
    ci.addCommand( &load );
    ci.addCommand( &power );
    ci.addCommand( &fan );
  #endif
  ci.addCommand( &filt );

  #if ( !defined( PHASE_ANGLE_CONTROL ) ) || ( INT_PIN != 3 ) // disable when PAC active and pin 3 reads the ZCD
    dcfan.init(); // initialize conditions for dcfan
  #endif

  pinMode( LED_PIN, OUTPUT );

  #ifdef LCD
    delay( 500 );
    lcd.clear();
  #endif
  #ifdef MEMORY_CHK
    checktime = millis();
  #endif

  #ifdef PID_CONTROL
    myPID.SetSampleTime(CT);                       // set sample time to 1 second
    #ifdef IO3_HTR_PAC
      myPID.SetOutputLimits(MIN_IO3, MAX_IO3);     // set output limits to user defined limits
    #else
      myPID.SetOutputLimits(MIN_OT1, MAX_OT1);     // set output limits to user defined limits
    #endif
    myPID.SetControllerDirection(DIRECT);          // set PID to be direct acting mode. Increase in output leads to increase in input
    myPID.SetTunings(PRO, INT, DER);               // set initial PID tuning values
    myPID.SetMode(MANUAL); // start with PID control off
    #if not ( defined ROASTLOGGER || defined ARTISAN || defined ANDROID )
      // -180514 LMMS original line- profile_number = 1; // set default profile, 0 is for override by roasting software
      profile_number = 0; // -180514 line added for LMMS (for avoid use profile in EEPROM)-
    #else
      profile_number = 0;                          // set default profile, 0 is for override by roasting software
    #endif
    profile_number_new = profile_number;
    setProfile();                                  // read profile description initial time/temp data from eeprom and set profile_pointer
  #endif

  first = true;
  counter = 3;                                     // start counter at 3 to match with Artisan. Probably a better way to sync with Artisan???
  next_loop_time = millis() + looptime;            // needed??

  // -LMMS 1Abr18-
  #if defined KORRY
    // Setup Digital Displays
    digitalDisplayTime.set(BRIGHT_TYPICAL);
    digitalDisplayTime.init(D4056A);
    digitalDisplayTemperature.set(BRIGHT_TYPICAL);   // BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
    digitalDisplayTemperature.init(D4056A);          // D4056A is the type of the module
    // Setup Encoders
    encoders.setDebounce(16);
    encoders.resetChronoAfter(1000);
    encoders.addEncoder(1, 4, 7, 1, 11, 21);         // Encoder timer
    encoders.addEncoder(2, 4, 8, 1, 13, 23);         // Encoder temperature
    previousValueEncoderTime = -1;
    timeEncoderMoved = millis();
  #endif
  // END LMMS
}

void loop()
{
  #if defined KORRY
    if(flagMovingEncoder&&(millis()-timeEncoderMoved>TIME_WAIT_ENCODER_MORE_ACTIVITY))
    {
      flagMovingEncoder = false;
      flagSettingRoastTemperature = false;
    }
  #endif

  #ifdef PHASE_ANGLE_CONTROL
    if( ACdetect() ) {
      digitalWrite( LED_PIN, HIGH ); // illuminate the Arduino IDE if ZCD is sending a signal
    }
    else {
      digitalWrite( LED_PIN, LOW );
    }
  #endif

  #ifdef MEMORY_CHK
    uint32_t now = millis();
    if( now - checktime > 1000 ) {
      Serial.print(F("# freeMemory()="));
      Serial.println(freeMemory());
      checktime = now;
    }
  #endif

  #if not defined KORRY
    // Has a command been received?
    checkSerial();
    // Read temperatures
    get_samples();
  #else
    if(!flagMovingEncoder)
    {
      // Has a command been received?
      checkSerial();
      // Read temperatures
      get_samples();
    }
  #endif

  // Read analogue POT values if defined
  #ifdef ANALOGUE1
    #ifdef PID_CONTROL
      if( myPID.GetMode() == MANUAL ) readAnlg1(); // if PID is off allow ANLG1 read
    #else
      readAnlg1(); // if PID_CONTROL is not defined always allow ANLG1 read
    #endif // PID_CONTROL
  #endif // ANALOGUE1
  #ifdef ANALOGUE2
    readAnlg2();
  #endif

  // Run PID if defined and active
  #ifdef PID_CONTROL
    #if defined KORRY
      if(!flagMovingEncoder)
      {
    #endif
        if( myPID.GetMode() != MANUAL ) { // If PID in AUTOMATIC mode calc new output and assign to OT1
          updateSetpoint(); // read profile data from EEPROM and calculate new setpoint
          uint8_t k = pid_chan;  // k = physical channel
          if( k != 0 ) --k; // adjust for 0-based array index
            // Input is the SV for the PID algorithm
          Input = convertUnits( T[k] );
          myPID.Compute();  // do PID calcs
          #ifdef IO3_HTR_PAC
            levelIO3 = Output; // update levelOT1 based on PID optput
            outIO3();
          #else
            levelOT1 = Output; // update levelOT1 based on PID optput
            outOT1();
          #endif
          #ifdef ACKS_ON
            Serial.print(F("# PID input = " )); Serial.print( Input ); Serial.print(F("  "));
            Serial.print(F("# PID output = " )); Serial.println( levelOT1 );
          #endif
        }
    #if defined KORRY
      }
    #endif
  #endif

  // Update LCD if defined
  #ifdef LCD
    updateLCD();
  #endif

  // Send data to Roastlogger if defined
  #if defined ROASTLOGGER
    logger(); // send data every second to Roastlogger every loop (looptime)
  #endif

  // -LMMS 1Abr18-
  #if defined KORRY
    if(!flagMovingEncoder)
    {
  #endif
  // -END LMMS-
      // check if temp reads has taken longer than looptime. If so add 1 to counter + increase next looptime
      // Serial.println( next_loop_time - millis() ); // how much time spare in loop. approx 350ms
      if ( millis() > next_loop_time ) {
        counter = counter + ( looptime / 1000 ); if( counter > 3599 ) counter = 3599;
        next_loop_time = next_loop_time + looptime; // add time until next loop
      }

      // wait until looptiom is expired. Check serial and buttons while waiting
      while( millis() < next_loop_time ) {
        // -LMMS 1Abr18-
        checkEncoders();
        if(flagMovingEncoder)
          break;
       checkSerial();  // Has a command been received?
        #ifdef LCDAPTER
          #if not ( defined ROASTLOGGER || defined ARTISAN || defined ANDROID ) // Stops buttons being read unless in standalone mode. Added to fix crash (due to low memory?).
            checkButtons();
          #endif
        #endif
      }

      // Set next loop time and increment counter
      next_loop_time = next_loop_time + looptime; // add time until next loop
      counter = counter + ( looptime / 1000 ); if( counter > 3599 ) counter = 3599;
  // -LMMS 1Abr18-
  #if defined KORRY
    }
  #endif
  // -END LMMS-

   // -LMMS 1Abr18-
  #if defined KORRY
    // Display time and temperature and encoders activity
    checkEncoders();
    displayTime();
    displayTemperature();
    handleToasterFan();
    coolToast();
  #endif
  // -END LMMS-
}

// -LMMS 1Abr18-
#if defined KORRY

  int16_t indexEncoder;
  int16_t valueEncoderTime=0,valueEncoderTemperature;
  long timePreviousClick=millis();
  #define DELAY_DOUBLE_CLICK 1500                    // Delay for identify doble click

  long timeButtonTemperatureStartPressed=0;          // Time button encoder temperature start pressed

  int16_t timeRoastSet,timeRemainingEndRoast;
  long timeStartToast,timeStartCool;
  int16_t temperatureToastingFahrenheitSet=100,temperatureToastingCelsiusSet=50; // Temperature preset initial values
  int16_t temperatureToastingSet;
  boolean flagToasting=false,flagSettingRoastTime=false,flagToastTimeSet=false,flagToasterPowerON=false,
          flagFanPowerON=false,flagTemperatureRising=true,flagTimeRunning=false,flagCoolToast=false;
boolean flagRoastTemperatureSet=true;

  void checkEncoders()
  {
    timeButtonTemperatureStartPressed = millis();
    indexEncoder = encoders.readAll();
    if(indexEncoder!=0)
    {
      if(flagToasting&&(indexEncoder==11||indexEncoder==12))
        return;
      flagMovingEncoder = true;
//Serial.println(indexEncoder);
      switch (indexEncoder)
      {
        case 11:                                     // Time CW
          if(!flagSettingRoastTime)
            return;
          valueEncoderTime++;
          if (valueEncoderTime>MAXIMUM_ROAST_TIME)
            valueEncoderTime = MAXIMUM_ROAST_TIME;
          digitalDisplayTime.display(valueEncoderTime);
          break;
        case 12:                                     // Time CCW
          if(!flagSettingRoastTime)
            return;
          valueEncoderTime--;
          if (valueEncoderTime<0)
            valueEncoderTime = 0;
          digitalDisplayTime.display(valueEncoderTime);
          break;
        case 21:                                       // Time click
          if(millis()-timePreviousClick<DELAY_DOUBLE_CLICK)   // Double click
          {
//Serial.println("Double click");
            if(!flagSettingRoastTime)
            {
              ci.setCmndStr("OT1;0");
              //ci.setCmndStr("OT2;0");
              levelIO3 = 0;
              outIO3();
              flagSettingRoastTemperature = false;
              flagToastTimeSet = false;
              flagSettingRoastTime = true;
              digitalDisplayTime.point(false);
            }
            else
            {
              timeRoastSet = valueEncoderTime;
              flagToastTimeSet = true;
              flagSettingRoastTime = false;
            }
            timePreviousClick = millis() - DELAY_DOUBLE_CLICK;
          }
          else                                         // Click
          {
            if(flagToastTimeSet&&flagRoastTemperatureSet)
            {
              if(!flagToasting)
              {
                ci.setCmndStr("OT1;100");
                //ci.setCmndStr("OT2;87");
                levelIO3 = 87;
                outIO3();
                digitalDisplayTime.point(true);
                digitalDisplayTime.display(timeRoastSet*100);
                //timeStartToast = millis() / 1000;
                timeRemainingEndRoast = timeRoastSet;
                flagToasterPowerON = true;
                flagTemperatureRising = true;
                flagTimeRunning = false;
                flagToasting = true;
                if(flagCelsiusDegrees)
                  temperatureToastingSet = temperatureToastingCelsiusSet;
                else
                  temperatureToastingSet = temperatureToastingFahrenheitSet;
              }
              else
              {
                ci.setCmndStr("OT1;0");
                //ci.setCmndStr("OT2;0");
                levelIO3 = 0;
                outIO3();
                timeRemainingEndRoast = 0;
                flagToasterPowerON = false;
                flagToasting = false;
                flagCoolToast = false;
              }
            }
            timePreviousClick = millis();
          }
          break;
        case 13:                                       // Temperature CW
          if(!flagSettingRoastTemperature)
            return;
          if(flagCelsiusDegrees)
          {
            //temperatureToastingCelsiusSet++;
            temperatureToastingCelsiusSet += 5;
            digitalDisplayTemperature.display(temperatureToastingCelsiusSet);
          }
          else
          {
            //temperatureToastingFahrenheitSet++;
            temperatureToastingFahrenheitSet += 5;
            digitalDisplayTemperature.display(temperatureToastingFahrenheitSet);
          }
          break;
        case 14:                                       // Temperature CCW
          if(!flagSettingRoastTemperature)
            return;
          if(flagCelsiusDegrees)
          {
            //temperatureToastingCelsiusSet--;
            temperatureToastingCelsiusSet =- 5;
            if (temperatureToastingCelsiusSet<0)
              temperatureToastingCelsiusSet = 0;
            digitalDisplayTemperature.display(temperatureToastingCelsiusSet);
          }
          else
          {
            //temperatureToastingFahrenheitSet--;
            temperatureToastingFahrenheitSet -= 5;
            if (temperatureToastingFahrenheitSet<0)
              temperatureToastingFahrenheitSet = 0;
            digitalDisplayTemperature.display(temperatureToastingFahrenheitSet);
          }
          break;
        case 23:
          if(millis()-timePreviousClick<DELAY_DOUBLE_CLICK)   // Double click
          {
//Serial.println("Double click temperature");
            if(!flagSettingRoastTemperature)
            {
              flagSettingRoastTime = false;
              flagRoastTemperatureSet = false;
              flagSettingRoastTemperature = true;
            }
            else
            {
              // Patch
              if(flagCelsiusDegrees)
                temperatureToastingSet = temperatureToastingCelsiusSet;
              else
                temperatureToastingSet = temperatureToastingFahrenheitSet;
              // End patch
              flagRoastTemperatureSet = true;
              flagSettingRoastTemperature = false;
            }
            timePreviousClick = millis() - DELAY_DOUBLE_CLICK;
          }
          else                                         // Long click
          {
            if(millis()-timeButtonTemperatureStartPressed>TEMPERATURE_BUTTON_HOLD_CHANGE_UNITS)
            {
              if(!flagCelsiusDegrees)
                flagCelsiusDegrees = true;
              else
                flagCelsiusDegrees = false;
//Serial.println("Long click temperature");
            }
            timePreviousClick = millis();
          }
          break;
      }
      previousValueEncoderTime = valueEncoderTime;
      timeEncoderMoved = millis();
      flagMovingEncoder = true;
    }
  }

  int tempVariable;
  void displayTime()
  {
    if(flagMovingEncoder||!flagToasting||!flagTimeRunning)
      return;

    tempVariable = timeRoastSet * 60 - (millis() / 1000 - timeStartToast);
    timeRemainingEndRoast = tempVariable / 60 * 100 + tempVariable % 60;
    if(timeRemainingEndRoast<0) {                      // It doesn't come out here. Come out from handleToasterFan()
      digitalDisplayTime.display(timeRoastSet*100);
      return;
    }
    digitalDisplayTime.display(timeRemainingEndRoast);
    if(millis()-timeTimerSetFlashed>TIME_TIMER_SET_FLASHED)
    {
      digitalDisplayTime.display(timeRoastSet*100);
      timeTimerSetFlashed = millis();
    }
  }

  int it01;
  void displayTemperature()
  {
    if(flagMovingEncoder)
      return;

    if(millis()-timeTemperatureBTFlashed<TEMPERATURE_TIME_FLASHED_BT)
    {
      // Display ET temperature
      it01 = round( convertUnits( T[1] ) );
      if( it01 > 999 )
        it01 = 999;
      else if( it01 < -999 )
        it01 = -999;
      if(flagCelsiusDegrees)
        it01 = (it01 - 32) / 1.8;
      temperatureET = it01;
      digitalDisplayTemperature.display(temperatureET);
    }
    else
    {
      // Display BT temperature
      it01 = round( convertUnits( T[0] ) );
      if( it01 > 999 )
        it01 = 999;
      else if( it01 < -999 )
        it01 = -999;
      if(flagCelsiusDegrees)
        it01 = (it01 - 32) / 1.8;
      temperatureBT = it01;

      digitalDisplayTemperature.clearDisplay();
      checkEncodersFlash(150);
      if(flagMovingEncoder)
        return;
      digitalDisplayTemperature.display(temperatureBT);
      checkEncodersFlash(1000);
      if(flagMovingEncoder)
        return;
      digitalDisplayTemperature.clearDisplay();
      checkEncodersFlash(150);
      if(flagMovingEncoder)
        return;
      digitalDisplayTemperature.display(temperatureET);

      timeTemperatureBTFlashed = millis();
    }
  }
  boolean checkEncodersFlash(int flashDelay)
  {
    long timeStart=millis();
    while(millis()-timeStart<flashDelay)
    {
      checkEncoders();
      if(flagMovingEncoder)
      {
        digitalDisplayTemperature.display(temperatureET);
        timeTemperatureBTFlashed = millis();
        return true;
      }
    }
    return false;
  }

  void handleToasterFan()
  {
    if(!flagToasting)
      return;

    if(!flagTimeRunning&&flagTemperatureRising&&(temperatureBT>temperatureToastingSet||temperatureET>temperatureToastingSet))
    {
      digitalDisplayTime.point(true);
      digitalDisplayTime.display(timeRoastSet*100);
      timeStartToast = millis() / 1000;
      timeRemainingEndRoast = timeRoastSet;
      flagTimeRunning = true;
    }

    if(timeRemainingEndRoast==0)
    {
      ci.setCmndStr("OT1;0");
      //ci.setCmndStr("OT2;100");
      levelIO3 = 100;
      outIO3();
      timeStartCool = millis();
      flagCoolToast = true;
      flagToasting = false;
      digitalDisplayTime.display(timeRoastSet*100);
      return;
    }

    // Roaster
    if(flagTemperatureRising)                         // Temperature rising
    {
//Serial.print("Rising   ");
//Serial.print(temperatureBT);
//Serial.print("   ");
//Serial.print(temperatureET);
//Serial.print("   ");
//Serial.println(temperatureToastingSet);
      if(temperatureBT>temperatureToastingSet||temperatureET>temperatureToastingSet)
      {
        ci.setCmndStr("OT1;00");
        flagTemperatureRising = false;
        flagToasterPowerON = false;
        //ci.setCmndStr("OT2;00");
      }
    }
    else                                              // Temperature falling
    {
      int temperatureReference=temperatureToastingSet*0.97;
      // int temperatureReference=temperatureToastingSet*(1-0.1*timeRemainingEndRoast/(timeRoastSet*60));
//Serial.print("Falling ");
//Serial.print(temperatureToastingSet);
//Serial.print("   ");
//Serial.print(timeRoastSet);
//Serial.print("   ");
//Serial.print(timeRemainingEndRoast);
//Serial.print("   ");
//Serial.print(temperatureReference);
//Serial.print("   ");
//Serial.print(temperatureBT);
//Serial.print("   ");
//Serial.println(temperatureET);
      if(temperatureBT<temperatureReference&&temperatureET<temperatureReference)
      {
        char ot1Command[8],otiLevelS[4];
        strcpy(ot1Command,"OT1;");
        // Provisional
        int ot1Level = 100;
        //int ot1Level = timeRemainingEndRoast * 100 / (timeRoastSet * 60);
        // End provisional
        itoa(ot1Level,otiLevelS,10);
        strcat(ot1Command,otiLevelS);
//Serial.print("   ");
//Serial.println(ot1Command);
        ci.setCmndStr(ot1Command);
        flagTemperatureRising = true;
        flagToasterPowerON = true;
      }
    }
  }

  void coolToast()
  {
    if(!flagCoolToast)
      return;

    if(millis()-timeStartCool<600000L)                // 10 min
      return;
    //ci.setCmndStr("OT2;0");
    levelIO3 = 0;
    outIO3();
    flagCoolToast = false;
  }
 #endif

@korryh - Search on this: HTR_CUTOFF_FAN_VAL in code (14 occurrences, 10 - 14 around line 1066 - the rest are print statements)

HTR_CUTOFF_FAN_VAL percentage as defined in user.h. For heater protection if required. Required modification to phase_ctrl.cpp

Note: comments say artisan is on code.google, google points to github...

Post an annotated schematic showing how you propose to wire this and if it involved mains power do you have experience in that area or somebody to help you that does.

Yes, this code was supposed to run artisan from a computer and stand alone controls on board.
The artisan doesnt work when plugged in to usb on board but the on board controls work, now I need to control the speed of the fan to make heat adjustments. Here is the User.h

// user.h
//---------
// This file contains user definable compiler directives for aArtisanQ_PID

// *************************************************************************************
// NOTE TO USERS: the following parameters should be
// be reviewed to suit your preferences and hardware setup.
// First, load and edit this sketch in the Arduino IDE.
// Next compile the sketch and upload it to the Arduino.

#ifndef USER_H
#define USER_H

////////////////////
// Roasting software
// Comment out all if using TC4 stand alone
//#define ROASTLOGGER
#define ARTISAN
//#define ANDROID
#define KORRY

////////////////////
// Base configurations (leave only one uncommented)
#define CONFIG_PWM // slow PWM on OT1 (heater); fast PWM output (3.922kHz) on IO3 (DC fan); ZCD not required
//#define CONFIG_PAC2 // phase angle control on OT1 (heater) and OT2 (fan); IO2 used to read the ZCD; IO3 undefined
//#define CONFIG_PAC2_IO3HTR // phase angle control on OT1 (heater) and OT2 (fan); IO2 reads the req'd ZCD; IO3 reserved for fast PWM output for heater
//#define CONFIG_PAC3 // phase angle control on OT1 (heater) and OT2 (fan); IO3 reads the req'd ZCD; IO3 not available for output
// end of base configurations
/////////////////////////////

////////////////////
// Temperature Unit
//#define CELSIUS // controls only the initial conditions.  Comment out for F.

////////////////////
// LCD Options
// Comment out non required features
//#define LCD // if output on an LCD screen is desired
//#define LCDAPTER // if the I2C LCDapter board is to be used
//#define LCD_4x20 // if using a 4x20 LCD instead of a 2x16

// AC Power Options
// Needed for PHASE_ANGLE_CONTROL option
#define FREQ60 // 60Hz
//#define FREQ50 // 50Hz

////////////////////
// Thermocouple Input Options
// TC type is selectable by input channel
// permissable options:  typeT, typeK, typeJ
#define TC_TYPE1 typeK  // thermocouple on TC1
#define TC_TYPE2 typeK  // thermocouple on TC2
#define TC_TYPE3 typeK  // thermocouple on TC3
#define TC_TYPE4 typeK  // thermocouple on TC4

////////////////////
// BAUD Rate for serial communication
//#define BAUD 19200 // useful for BT links
#define BAUD 115200  // default

////////////////////
// Analogue inputs (optional)
// Comment out if not required
//#define ANALOGUE1 // if potentiometer connected on ANLG1
//#define ANALOGUE2 // if potentiometer connected on ANLG2

////////////////////
// Duty Cycle Adjustment Increment
// Used for rounding/increment for analogue inputs and power UP/DOWN commands
#define DUTY_STEP 1 // Use 1, 2, 4, 5, or 10.

////////////////////
// Physical input channel for RoR display on LCD
// Corresponds to Thermocouple inputs T1-T4
#define ROR_CHAN 1

////////////////////
// PID Control Options
#define PID_CONTROL
#define PID_CHAN 2 // physical channel for PID input (corresponding to thermocouple inputs T1-T4)
#define CT 1000 // default cycle time for the PID, in ms
#define PRO 5.00 // initial proportional parameter
#define INT 0.15 // initial integral parameter
#define DER 0.00 // initial derivative parameter

#define NUM_PROFILES 2 // number of profiles stored in EEPROM

////////////////////
// Heater and Fan Limits/Options
#define MIN_OT1 0 // Set output % for lower limit for OT1.  0% power will always be available
#define MAX_OT1 100 // Set output % for upper limit for OT1

#define MIN_OT2 0 // Set output % for lower limit for OT2.  0% power will always be available
#define MAX_OT2 100 // Set output % for upper limit for OT2

#define MIN_IO3 0 // Set output % for lower limit for IO3.  0% power will always be available
#define MAX_IO3 100  // Set output % for upper limit for IO3

// cut power to Heater if fan duty is less than HTR_CUTOFF_FAN_VAL (to protect heater in air roaster). Set to 0 for no cutoff
#define HTR_CUTOFF_FAN_VAL 0 // default
// #define HTR_CUTOFF_FAN_VAL 10  //optional value

#define FAN_AUTO_COOL 13 // Set fan output duty for auto cool when using PID;STOP command

////////////////////
// Command Echo
//#define COMMAND_ECHO // Echo all serial commands to LCD for debugging

////////////////////
// Temperature Reading Filters
#define BT_FILTER 10 // filtering level (percent) for BT
#define ET_FILTER 10 // filtering level (percent) for ET
#define AMB_FILTER 70 // 70% filtering on ambient sensor readings

// use RISE_FILTER to adjust the sensitivity of the RoR calculation
// higher values will give a smoother RoR trace, but will also create more
// lag in the RoR value.  A good starting point is 80%, but for air poppers
// or other roasters where BT might be jumpy, then a higher value of RISE_FILTER
// will be needed.  Theoretical max. is 99%, but watch out for the lag when
// you get above 95%.
#define RISE_FILTER 85 // heavy filtering on non-displayed BT for RoR calculations
#define ROR_FILTER 80 // post-filtering for the computed RoR values

// Thermocouple inputs
#define NC 4 // maximum number of physical channels on the TC4

////////////////////
// Calibration Values
// default values for systems without calibration values stored in EEPROM
#define CAL_GAIN 1.00 // you may substitute a known gain adjustment from calibration
#define UV_OFFSET 0 // you may subsitute a known value for uV offset in ADC
#define AMB_OFFSET 0.0 // you may substitute a known value for amb temp offset (Celsius)

////////////////////
// Time Base for slow PWM on OT1, OT2
// When NOT using PHASE_ANGLE_CONTROL option
// choose one of the following for the PWM time base for heater output on OT1 or OT2
//#define TIME_BASE pwmN4sec  // recommended for Hottop D which has mechanical relay
//#define TIME_BASE pwmN2sec
#define TIME_BASE pwmN1Hz  // recommended for most electric heaters controlled by standard SSR
//#define TIME_BASE pwmN2Hz
//#define TIME_BASE pwmN4Hz
//#define TIME_BASE pwmN8Hz
//#define TIME_BASE 15 // should result in around 977 Hz (TODO these need testing)
//#define TIME_BASE 7 // approx. 1.95kHz
//#define TIME_BASE 6 // approx. 2.2kHz
//#define TIME_BASE 3 // approx. 3.9kHz

////////////////////
// Debuging Options
// Useful for debugging only -- leave inactive otherwise
//#define MEMORY_CHK

// This turns on the "# xxxxxxx\n" acknowledgements after commands
//#define ACKS_ON

////////////////////
// Output Pin Setup
// phase angle control and integral cycle control outputs
#define OT1 9 // OT1 is on pin D9
#define OT2 10 // OT2 is on pin D10
#define OT_PAC OT2 // phase angle control on OT2 (AC fan, usually)
#define OT_ICC OT1 // integral cycle control on OT1 (AC heater, usually)
#define LED_PIN 13

//////////////////////////////////////////////////////
// Set up parameters for the base configurations
#ifdef CONFIG_PWM // nothing special to configure -- this is the default
#endif

#ifdef CONFIG_PAC2_IO3HTR
  #define PHASE_ANGLE_CONTROL // phase angle control for OT2(fan) and ICC control for OT1(heater)
  #define IO3_HTR_PAC // use PWM (3.922kHz) out on IO3 for heater in PHASE ANGLE CONTROL mode
  // zero cross detector (ZCD) connected to I/O2
  #define EXT_INT 0 // interrupt 0
  #define INT_PIN 2 // pin 2
#endif

#ifdef CONFIG_PAC2
  #define PHASE_ANGLE_CONTROL // phase angle control for OT2(fan) and ICC control for OT1(heater)
  // zero cross detector (ZCD) connected to I/O2
  #define EXT_INT 0 // interrupt 0
  #define INT_PIN 2 // pin 2
#endif

#ifdef CONFIG_PAC3
  #define PHASE_ANGLE_CONTROL // phase angle control for OT2(fan) and ICC control for OT1(heater)
  // zero cross detector (ZCD) connected to I/O3
  #define EXT_INT 1 // interrupt 1
  #define INT_PIN 3
#endif

////////////////////
// Heater and Fan Duty Dispay Options
// These should NOT need adjusting.  They control what gets streamed back to via serial
// These have no effect on operation and only affect what gets displayed/logged by Artisan
#ifdef PHASE_ANGLE_CONTROL
  #ifdef IO3_HTR_PAC // If using PWM on IO3 for a heater
    #define HEATER_DUTY levelIO3 // Heater output is assumed levelIO3 with heater connected to IO3
  #else // If using ICC control of a heater connected to OT1
    #define HEATER_DUTY levelOT1 // Heater output is assumed levelOT1 with heater connected to OT1
  #endif
  #define FAN_DUTY levelOT2 // Fan output is assumed levelOT2 for phase angle control mode on OT2
#else // PWM Mode
  #define HEATER_DUTY levelOT1 // Heater output is assumed levelOT1 with heatre connected to OT1
  #define FAN_DUTY levelIO3 // Fan output is assumed levelIO3 for PWM control of fan connected to IO3
#endif

////////////////////
// Phase Angle Control Options
// When using PHASE_ANGLE_CONTROL option
// Selct load type being switched by phase angle control (has no effect on the ICC output)
#define TRIAC_MOTOR // inductive loads need a longer pulse width to fire at 100%
//#define TRIAC_HEATER // enable this for resistive loads, like heaters

#endif

Here is what I used for planning

You lost me on that... but the constant HTR_CUTOFF_FAN_VAL seems to be compared to a few levels (OT1, OT2, OT3) to cycle heaters and fans, their functions are just before setup() in the outIOx() functions

What is the fuzzy blue blob that the wires connect to and do you have a schematic for it?

Sorry, explanation - I know the Artisan firmware for arduino/TC4 shield works and have used it many successful times roasting coffee. I made the machine that roasts coffee and wanted to add manual controls so I didnt have to hook it up to a computer all the time. I made it have just time and temp controls using push button encoders so you dial the time and push the button then dial the temp and push the button starting the machine. This started the fan blowing into the heater which heated the beans.
The person that made the code put the code for the encoders, 7 seg display, fan and heater in the original Artisan sketch. This was supposed to control the manual functions until you plugged in a usb and ran the artisan software to control the roast. It was supposed to have the best of both worlds, manual control and Artisan control (when you wanted it). The Artisan computer control lets you follow roasting profiles so you can make the same roast every time.

so - the manual controls work and the Artisan doesn't work when you plug in the USB. Now I need to slow down the fan so the heater can heat up faster.

The blue board is a TC4 plus arduino uno shield. I don't have a schematic for it. I made this illustration based on current components, I gave it to a friend of mine and he made a single board.
Here is a link to the quick start guide if it helps.

Reading about this... and adjusting fans...

found this, looks like it should be a speed control - where/what code do I enter if this is where I can control the DC fan speed.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.