Solar engine controller code listing

Here is an outline of the functions performed by the program for the engine controller:
1. Measure heat collector and heat sink (cold plate) temperatures and display them.
2. Measure crankshaft speed and display in Hz and rpm.
3. Compute absolute temperature ratio and Th – Tc and display.
4. Initiate engine start if the following conditions are met:
a. Temp ratio (Th/Tc) is sufficient (1.065)
b. The engine is not turning (hz = 0);
c. Increases temp ratio if the start fails.
5. Display the total engine run time since power on.
6. Display the total number of engine starts.
7. Display the total number of failed starts. A failed start is one where the engine runs for 2 minutes or less.
8. Displays the current starting temperature ratio.
9. Displays a log of the engine temperature ratio, Hz, percent of total run time. The temperature ratio ranges are displayed for example as: T106, 1.50, 08
where:
T106 = 1.060 to 1.070 for Th/Tc
1.50 = average engine speed in cycles/sec (hertz)
08 = 8% of total engine running time was spent at 1.060<= Th/Tc < 1.070 You'll notice the program needs to display a lot of information on a 2 line x 16 character display. This is done by using a fixed display on the first line that continuously displays Th (heat collector temperature), Tc (heat sink temperature), and engine speed (hertz). These are the most important data and I want them continuously available. The second line cycles through the other information at approximately a 2 second cycle rate per display. The LCD display was selected before I realized how much data I would eventually want to display. Any character LCD using the HD44780 controller should be compatible. A 4 line x 20 character display would have been a better choice. Code listing
Note: Some of the line comments are too long for the display line. To see them you’ll need to scroll to the side.

/*
09/14/11
Adding data collection for Hz vs Th/Tc with display of values
Controller and display sketch for the LT-2 engine. 
Features:
1. Measures the hot and cold plate temperatures and displays them on the LCD
   along with absolute temperature ratio (Th/Tc) and Th-Tc.
   All display values in degrees F
2. Measures the crankshaft speed and displays it in hertz and RPM. 
3. Initiates an engine start if the following conditions are Met:
    a. Temp ratio (Th/Tc) is sufficient (1.065)
    b. The engine is not turning (hz = 0);
    c. Increases temp ratio if the start fails.
    d. A wait time on a failed start before trying again. If the engine stops rotating quickly then
       a 5 minute delay is used. If the engine fails after a longer period a one minute delay is used 
       before checking the temperature ratio for a restart. 
    f. A long period delay should be used if the start fails at the maximum temperature ratio, indicating a 
       possible mechanical problem. 
    g. Display the total engine run time since power on.
    h. The total number of engine starts.
    i. The total number of failed starts. A failed start is one where the engine runs for 2 minutes or less
    j. Displays the current starting temperature ratio.
    k. Displays a log of the engine temperature ratio, Hz, percent of total time. The temperature ratio ranges
       are displayed for example as: T106, 1.50, 08
       where T106 = 1.060 to 1.070 for Th/Tc
       1.50 = engine speed in cycles/sec (hertz)
       08 = 8% of total engine running time was spent at 1.060<= Th/Tc < 1.070
    l. Logging appropriate data to NV memory and displaying it. TBD    

      
  The circuit:
 * LCD RS pin to digital pin 17 (A3) 
 * LCD Enable pin to digital pin 18 (A4) 
 * LCD D4 pin to digital pin 9
 * LCD D5 pin to digital pin 10
 * LCD D6 pin to digital pin 11
 * LCD D7 pin to digital pin 19 (A5)  
 * LCD R/W pin to ground
 * 10K resistor:
 * ends to +5V and ground
 * wiper to LCD VO pin (pin 3)

The arduino with motor shield to drive the stepper motor for staring will require many 
of the available controller pins. This is a list the pin assignments:
Analog pins (can also be used as digital pins)
Analog 0 or digital 14		Temp sensor Th (heat source)
Analog 1 or digital 15		Temp sensor Tc (cold sink)
Analog 2 or digital 16		Not used
Analog 3 or digital 17		LCD (RS)
Analog 4 or digital 18		LCD (EN)
Analog 5 or digital 19		LCD (DB7)

Not used by motor shield:
The current design uses stepper motor #2

digital pin 2			Hall effect sensor input pin, interrupt on H-L transition
digital pin 3			Other interrupt pin, save for possible second sensor

The following are used only if the noted devices are used:
Digital pin 11: DC Motor #1 / Stepper #1 (activation/speed control)	        LCD (DB6)
Digital pin 3: DC Motor #2 / Stepper #1 (activation/speed control)		save for interrupt

Stepper #2 is being used so these are not available:
Digital pin 5: DC Motor #3 / Stepper #2 (activation/speed control)		Not Available
Digital pin 6: DC Motor #4 / Stepper #2 (activation/speed control)		Not Available

The following pins are used only if that particular servo is in use:
Digitals pin 9: Servo #1 control						LCD (DB4)
Digital pin 10: Servo #2 control						LCD (DB5)
The following are used if any DC motor or stepper is used:
Digital pin 4, 7, 8 and 12 are used to drive the DC/Stepper motors via the 74HC595 
serial-to-parallel latch
 */

// include the library code **************************************
#include <LiquidCrystal.h>
#include <AFMotor.h>

// initialize the display library with the numbers of the interface pins
LiquidCrystal lcd(17, 18, 9, 10, 11, 19);

//Hardware Pin assignments ************************************

/*
Arduino with motor shield pin assignments
*/


//Old assignments for display using boardino 
int TempCpin = 1; // Analog input pin for temp 1 sensor A5
int TempHpin = 0;
int speedPin = 2;
//int initStartPin = 11;

//LCD character assignments **************************************
byte delta_char[8] = {
	B00000,
	B00000,
	B00000,
	B00100,
	B01010,
	B10001,
	B11111,
	B00000
};
byte degF_char[8] = {
	B01000,
	B10100,
	B01000,
	B00111,
	B00100,
	B00110,
	B00100,
	B00100
};
byte TempRatio[8] = {
	B11100,
	B01001,
	B01010,
	B00100,
	B01000,
	B10111,
	B00010,
	B00010
};
byte hz_char[8] = {
	B10010,
	B10010,
	B11110,
	B10010,
	B10111,
	B00001,
	B00010,
	B00111
};
byte TH_char[8] = {
	B11100,
	B01000,
	B01000,
	B01000,
	B00101,
	B00111,
	B00101,
	B00101
};
byte TC_char[8] = {
	B11100,
	B01000,
	B01000,
	B01000,
	B00111,
	B00100,
	B00100,
	B00111
};



//Variable declarations ***************************************

boolean startDelay = false;// true means a delay, false means no delay
int j=0; // variable for case
int data = 105; //case count for displaying data
int readingC;
int readingH;
float tRatio; //Th/Tc absolute temp ratio
const float start_tRatio_min = 1.065;
float start_tRatio = start_tRatio_min;
const float start_tRatio_max = 1.090;
const float Roffset = 459.7; //conversion for degrees F to degrees Rankine (R = F + 459.7)
const float ThOffset = 1.3; //experimental offset for temperature sensor at room temp
const float TcOffset = 2.4; //errors at elevated temperatures have not been checked

int startNum = 0; //#number of attempted starts
int failedStart = 0; //#number of failed starts
//The program is always in one of these four states
const byte stopped = 0;
const byte start = 1;
const byte running = 2;
const byte evalStop = 3;
byte state = stopped; // variable to hold the state;
#define aref_voltage 5.0         // we tie 3.3V to ARef and measure it with a multimeter!
unsigned long time;
unsigned long oldTime =  0;
unsigned long startTime = 0;//holds the time when the engine should start/does start
unsigned long totalTime = 0;
unsigned long thisRunTime = 0;
unsigned long priorRunTime = 0;
unsigned int N106 = 0; //number of readings 1.060<= Th/Tc < 1.070
unsigned int N107 = 0;
unsigned int N108 = 0;
unsigned int N109 = 0;
unsigned int N110 = 0;
unsigned long Ntotal = 1; //Total N including outside buckets. Initialize =1 to avoid divide by zero
float T106 = 0;// summation of Hz for 1.070<= Th/Tc < 1.070
float T107 = 0;
float T108 = 0;
float T109 = 0;
float T110 = 0;
float Hz;

//Function definitions *********************************************


//This function is called for engine speed by an interrupt from the hall effect sensor
void hertz(){ //Interrupt routine to track Hz or RPM
  time = (millis());
  Hz = 1000/float(time -oldTime);
 // Serial.print("delta time = ");
  //Serial.print(deltaTime); Serial.print(" Hz = "); Serial.print(Hz);
  oldTime = time;
}

//converts temp analog input to temp degF with corrections for tmp36 sensor 
//inputs: analog reading, analog ref voltage, offset correction
int tempDegF(int analogIn, float aRef, float offset){
  float tempC = float(((analogIn * aRef/1024) -0.5) * 100);
  return(int((tempC + offset)*(9.0/5.0)+ 32.0));
}

// For a stepper motor with 48 steps per revolution (7.5 degree)
// to motor port #2 (M3 and M4)
AF_Stepper motor(48, 2);
int value=0;

void start_engine(){
  Serial.println( "running start");
  motor.setSpeed(10);  // 10 rpm   
  //Flip motor over to engage for start
  motor.step(60, BACKWARD, MICROSTEP); 
  //Acceleration profile with approximately constant acceleration.
  //Constant acceleration profile using MICROSTEP
  motor.setSpeed(4);
  motor.step(1 ,FORWARD,MICROSTEP);
  motor.setSpeed(8);
  motor.step(3 ,FORWARD,MICROSTEP);
  motor.setSpeed(11);
  motor.step(4 ,FORWARD,MICROSTEP);
  motor.setSpeed(15);
  motor.step(7 ,FORWARD,MICROSTEP);
  motor.setSpeed(19);
  motor.step(8 ,FORWARD,MICROSTEP);
  motor.setSpeed(22);
  motor.step(11 ,FORWARD,MICROSTEP);
  motor.setSpeed(26);
  motor.step(12 ,FORWARD,MICROSTEP);
  motor.setSpeed(30);
  motor.step(14 ,FORWARD,MICROSTEP);

  motor.release(); //de-energizes the coils for zero power to motor
}


//processing for runtime, starts, failed starts, deltaT for starts
// determine #st, #Fst, deltaTS
//processing for engine performce data
//logging data to NV memory

// begin setup function *********************************

void setup() {
//Serial.begin(9600); //Start the serial connection to view results for development only
  
// Hardware intitialization
   pinMode(speedPin, INPUT); // Hall effect sensor, is used as an interrupt
   attachInterrupt(0, hertz, FALLING); // calls the function to use on the interrupt   

   // Loads custom characters for display
        lcd.createChar(0, delta_char);
        lcd.createChar(1, degF_char);
        lcd.createChar(2, TempRatio);
        lcd.createChar(3, hz_char);
        lcd.createChar(4, TH_char);
        lcd.createChar(5, TC_char);
        
// initialize display        
	lcd.begin(16,2);
        
// If you want to set the aref to something other than 5v
//  analogReference(EXTERNAL); 
  
//initialize the motor   
  motor.setSpeed(0);    
  motor.release();

}

// Begin loop function ***************************************

void loop(){
  // The program is always in one of the following four states
  switch (state){
    case stopped:
      if(millis() >= startTime){
        //start delay is satisfied
        startDelay = false;
      }
      if(tRatio >= start_tRatio && startDelay == false){ //ready to start  
        state = start;
        startTime = millis(); 
      }
      if(Hz > 0){ // The engine was started manually
        state = running;
        startTime = millis(); 
        startDelay = false;
      }  
      break;
    case start:
      //run the start routine
      start_engine();
      startTime = millis(); //This is the actual start time
      //wait 10 sec for engine to turn
      if(millis() >= ((startTime + 10000) && Hz > 0)){
        startNum++; // increment the number of starts
        state = running;   
      }   
      break;
    case running:
      //Test to make sure the engine is running
      //log run time and stats
      //switch to evalStop if not running
      thisRunTime = millis() - startTime;
      totalTime = priorRunTime + thisRunTime;
      if(millis() - oldTime > 5000){ 
        Hz = 0; // The speed sensor has not responded for 5 sec. Engine is not running
        state = evalStop;
        priorRunTime = totalTime;
      }
      break;
    case evalStop:
      startDelay = true;
      if(thisRunTime < 10000){ //possibly a mechanical problem, wait 5 min
        //The problem could be condensation
        startTime = millis() + 300000; 
        failedStart++;
      } else if(thisRunTime < 60000){ // failed start raise tRatio
        startTime = millis() + 60000;
       start_tRatio = start_tRatio + .005;
        failedStart++;
        if(start_tRatio > start_tRatio_max) start_tRatio = start_tRatio_max; //Don't raise tRatio too high
      }else if(thisRunTime < 300000){ // short run raise tRatio a little
        startTime = millis() + 60000;
        tRatio = tRatio + .002;
        if(start_tRatio > start_tRatio_max) start_tRatio = start_tRatio_max; //Don't raise tRatio too high
      }else { // Normal run, reduce the start tRatio if it has been running for 30 min. Not below minimum.
        startTime = millis() + 60000;
        if(thisRunTime > 1800000) start_tRatio -= .002;
        if(start_tRatio < start_tRatio_min) start_tRatio = start_tRatio_min;
      }
      state = stopped;     
      break;
  }


/*
  Serial.print("case n = "); Serial.println(n); 
  Serial.print("k = ");  Serial.println(k);
  Serial.print("state = "); Serial.println(int(state));  
  Serial.print("tRatio= "); Serial.println(tRatio,3);
  Serial.print("Start Tratio = "); Serial.println(start_tRatio, 3);
*/

  //Average temperature readings. zero variables first.
  
  int i; // loop variable
  int aveTempC = 0;
  int aveTempH = 0;


  for (i=0; i < 4; i++){ 
     aveTempC += analogRead(TempCpin);
     aveTempH += analogRead(TempHpin);
     delay(10);
  }

  //finish average
  aveTempC /= 4;
  aveTempH /= 4;
  
int temperatureFC = tempDegF(aveTempC, aref_voltage, TcOffset);
int temperatureFH = tempDegF(aveTempH, aref_voltage, ThOffset);
 
 //The first line of the display shows Tc, Th, and hertz at all times 
 lcd.clear(); // clears the display and sets cursor to line 0, col 0.
 lcd.write(5);//TC
 lcd.write(1);//degF
 lcd.print(temperatureFC);
 lcd.setCursor(5,0);
 lcd.write(4); //TH
 lcd.write(1);//degF symbol
 lcd.print(temperatureFH);
 lcd.setCursor(11,0);
 lcd.write(3); //Hz symbol
 lcd.print(Hz);
 
 //The second line of the display cycles through different data 
 //Start line 2 of display
 lcd.setCursor(0,1);
  switch (j) {
  case 0:  //total runtime
     lcd.print("Run Hrs= ");
     lcd.print(float(totalTime/1000)/3600);// runtime in hrs
    break;
  case 1: //number of start attempts
     lcd.print("# Starts= ");
     lcd.print(startNum);// # start attempts
    break;
  case 2: // failed start attempts
     lcd.print("# St fails= ");
     lcd.print(failedStart);// # failed starts
    break;
  case 3: // start absolute temperature ratio
     lcd.print("st Th/Tc= ");
     lcd.print(start_tRatio, 3);
    break;
  case 4: // current absolute temperature ratio
     tRatio = float(temperatureFH + Roffset)/(temperatureFC + Roffset); //Roffset = ABS rankine
     lcd.print("Abs Th/Tc= ");// 
     lcd.print(tRatio, 3);
    break;
    //the following case is not displayed if the engine is running. 
  case 5: // start delay must be satisfied before start temp ratio is examined. 
     if(state == stopped && startDelay == true && (startTime-millis()) > 0){
      lcd.print("st delay= ");
      lcd.print(int((startTime - millis())/1000));
      lcd.setCursor(15,1);
      lcd.print("s");
     }
    break;
  case 6: // RPM
     lcd.print("RPM= ");
     lcd.print(Hz * 60);
    break;
  case 7: //log data
    switch(data){
      case 106:  // 1.060 <= Th/Tc < 1.070
        lcd.print("T106, ");
        lcd.print(T106/N106);
        lcd.print(", ");
        lcd.print(float(N106)/Ntotal); 
        break;
      case 107:
        lcd.print("T107, ");
        lcd.print(T107/N107);
        lcd.print(", ");
        lcd.print(float(N107)/Ntotal); 
        break;
      case 108:
        lcd.print("T108, ");
        lcd.print(T108/N108);
        lcd.print(", ");
        lcd.print(float(N108)/Ntotal); 
        break;
      case 109:
        lcd.print("T109, ");
        lcd.print(T109/N109);
        lcd.print(", ");
        lcd.print(float(N109)/Ntotal); 
        break;
      case 110:
        lcd.print("T110, ");
        lcd.print(T110/N110);
        lcd.print(", ");
        lcd.print(float(N110)/Ntotal); 
        data = 105; // reset to increment into first case
        break;
    }
    data++;//increment the case counter for log data
    break;
  case 8: //Th - Tc 
     lcd.print("Th-Tc= ");
     lcd.print(temperatureFH - temperatureFC);
     j=-1;//reset the case counter to count into the first case
    break;
  }
  
  if(state == running){ // when the engine is running, log the data
    // Put Hz into Th/Tc buckets and count 
    if(tRatio >=1.060 && tRatio < 1.070){
      T106 += Hz;
      N106++;
    }else if(tRatio >=1.070 && tRatio < 1.080){
      T107 += Hz;
      N107++;
    }else if(tRatio >=1.080 && tRatio < 1.090){
      T108 += Hz;
      N108++;
    }else if(tRatio >=1.090 && tRatio < 1.100){
      T109 += Hz;
      N109++;
    }else if(tRatio >=1.100){ //everything above Th/Tc >1.100
      T110 += Hz;
      N110++;
    }    
    Ntotal++; // sum of all N including any that fall outside buckets
  }  
 delay(1900);// wait before repeating loop so the viewer can read display
 j++; // increment display counter
}