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
}

