Wednesday, July 29, 2020

Building an Engine Controller: The microprocessor

Obviously to run the whole thing I need a microprocessor. There are some great products out there to help in a build like this. Most of the work I have done in the past has been using PIC products. This required working in Assembly which I don’t particularly care for. I don’t know what the current state of affairs is with PIC these days so I really can’t say. Fortunately there are a number of suites to build a system using C++ as the programming language. I’ve used both the ST Micro STM32 system and the Arduino based system in various builds. I prefer the Arduino system because a lot of work has been done by other people and various routines are freely available. I also like the Arduino IDE suite. One other important factor is the footprint required. I chose to use two Arduino Nano boards for this build.


 

The two Nano boards are shown on the left with a Mega mini board in the center and an STM32F board on the right. I chose to use two Nano boards so I could split the responsibilities up between the boards. One board controls the injectors and spark plugs while watching the intake temperature and pressure along with the oxygen sensor. The other board monitors all of the sensors and turns various relays on and off at the appropriate times. This board also prints out via the serial interface the engine conditions and acts as my dashboard. The two boards are synched in time via the CYL signal from the NCV1124. A pinout diagram of the boards is shown below with pin connections tabulated. I chose to use two boards as it simplifies the programming immensely.


Pin Out Table
Board# Firmware Board# Firmware Board# Firmware Board# Firmware
1 Engine 1 Engine 2 Monitor 2 Monitor
Pin 1 NC Pin 16 NC Pin 1 NC Pin 16 NC
Pin 2 NC Pin 17 NC Pin 2 NC Pin 17 NC
Pin3 NC Pin 18 NC Pin 3 NC Pin 18 NC
Pin 4 GND Pin 19 MAP In Pin 4 GND Pin 19 MAP In
Pin 5 CYL In Pin 20 IAT In Pin 5 CYL In Pin 20 IAT In
Pin 6 TDC In Pin 21 OXY In Pin 6 Relay 1 Out Pin 21 OXY In
Pin 7 CPS In Pin 22 NC Pin 7 Relay 2 Out Pin 22 Fuel In
Pin 8 Spark Plug 1 Pin 23 NC Pin 8 Relay 3 Out Pin 23 Oil In
Pin 9 Spark Plug 2 Pin 24 NC Pin 9 Relay 4 Out Pin 24 ECT In
Pin 10 Spark Plug 3 Pin 25 NC Pin 10 Relay 5 Out Pin 25 Idle Control
Pin 11 Spark Plug 4 Pin 26 NC Pin 11 Relay 6 Out Pin 26 Idle Set
Pin 12 Injector 1 Pin 27 NC Pin 12 Relay 7 Out Pin 27 NC
Pin 13 Injector 2 Pin 28 NC Pin 8 Relay 8 Out Pin 28 NC
Pin 14 Injector 3 Pin 29 GND Pin 14 Relay 9 Out Pin 29 GND
Pin 15 Injector 4 Pin 30 +5V In Pin 15 Relay 10 Out Pin 30 +5V In

The code for the firmware for both boards is below:

Engine Monitor
/*version 0714
Engine monitor for readout and rpm control
created 2020
by Allen J. Lindfors
*/
#include <digitalWriteFast.h>
//pin assignments
//cycle control
const byte inCYL = 2; // the number of the pin for the input CYL signal
//analog inputs
const int inMAP = A0;
const int inIAT = A1;
const int inOXY = A2;
const int inFuel = A3;
const int inOIL = A4;
const int inECT = A5;
const int idleControl = A6;
const byte idleSetPoint = A7;
//fuel control
const byte fuelOut = 3;
//fast idle start up
const byte outIACV = 4;
//evap canister purge control
const byte purgeOut = 5;
const byte motorCon1 = 6;
const byte motorCon2 = 7;
const byte motorCon3 = 8;
const byte motorCon4 = 9;
//fan control
const byte coolingFans = 10;
bool setEnable, setIACVEnable;
//integer variables for controls
int pulseCYL = LOW; //initialize
int stateCYL = LOW; //initialize
int lastCYL = LOW; //initialize
int countCYL = 0; //initialize
//timing and claculation variables
//timing and sensor values
//Sensor fits variable = C + S*(value)
//IAT
float C1 = 164, S1 = -0.15;
//ECT
float C2 = 164, S2 = -0.15;
//MAP
float C3 = 0, S3 = 0.302;
//single cylinder volume
float cylVolume = 400.0;
//fuel values
float rho = 0.72; //for gasoline
//other densities propane=0.58, ethanol = 0.78, methane = 0.42, methanol = 0.78,
//fuel air value
float fuelAirRatio = 14.7; //for gasoline
//other fuel/air value propane=15.5, ethanol = 9.0, methane = 17.2, methanol = 6.4
//injector flow rate
float flowRate = 225; //injector flow rate in cc/min
//injection calculation values
float massAir, massFuel, injectionTime;
//time values
unsigned long injTime;
unsigned long intervalCYL, startCYLTime, stopCYLTime;
unsigned long currentMillis, previousMillis;
unsigned long interval = 2000;
//read usage variables
float engFuel, fuelPressure, fuelAir, oilPressure;
float airPressure, airTemperature, coolantTemperature;
float engMAP, engIAT, engOXY, engECT, engRPM, engOIL, totalRPM, averageRPM;
void setup() {
// put your setup code here, to run once:
pinModeFast(inCYL, INPUT);
pinModeFast(fuelOut, OUTPUT);
pinModeFast(outIACV, OUTPUT);
pinModeFast(purgeOut, OUTPUT);
pinModeFast(motorCon1, OUTPUT);
pinModeFast(motorCon2, OUTPUT);
pinModeFast(motorCon3, OUTPUT);
pinModeFast(motorCon4, OUTPUT);
pinModeFast(coolingFans, OUTPUT);
Serial.begin(9600);
digitalWriteFast(fuelOut, HIGH);
digitalWriteFast(outIACV, HIGH);
digitalWriteFast(purgeOut, HIGH);
digitalWriteFast(motorCon1, HIGH);
countCYL = 0;
}
void initial_calculations() {
//make the calculations
airTemperature = 273 + C1 + engIAT * S1;
airPressure = C3 + engMAP * S3;
massAir = cylVolume * 0.0289 / 8.314; //mass air without P & T
massFuel = massAir / fuelAirRatio; //mass fuel without P & T
injectionTime = 1000 * massFuel / (rho * (flowRate / 60)); //in microseconds 00;
}
void loop() {
//do the initial calculations
initial_calculations();
injTime = 1000 * injectionTime * airPressure / airTemperature;
// put your main code here, to run repeatedly:
//get the fuel pressure and turn the pump on if necessary
engFuel = analogRead(inFuel);
fuelPressure = (engFuel * .0921) - 8.569;
if (fuelPressure <= 36.0)digitalWriteFast(fuelOut, LOW);
if (fuelPressure >= 40.0)digitalWriteFast(fuelOut, HIGH);
//enable/disable the IACV
if (setEnable == true)digitalWriteFast(outIACV, LOW);
if (setEnable == false)digitalWriteFast(outIACV, HIGH);
//check the coolant temperature and turn on fans if needed
engECT = analogRead(inECT);
coolantTemperature = 164 - (engECT * 0.150);
engMAP = analogRead(inMAP);
airPressure = engMAP * 0.302;
engIAT = analogRead(inIAT);
airTemperature = 273 + (164 - (0.15 * engIAT));
engOXY = analogRead(inOXY);
fuelAir = 15.11 - (0.004 * engOXY);
engOIL = analogRead(inOIL);
oilPressure = (engOIL / 10.0) - 9.0;
currentMillis = millis();
//print at 2 second intervals
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
Serial.println(" ");
Serial.print("RPM = ");
Serial.println(round(averageRPM));
Serial.print("Manifold Pressure = ");
Serial.print(round(airPressure));
Serial.println(" kPa ");
Serial.print("Intake Temperature = ");
Serial.print(round(airTemperature));
Serial.println(" K ");
Serial.print("Coolant Temperature = ");
Serial.print(round(coolantTemperature));
Serial.println(" C ");
Serial.print("Fuel Pressure = ");
Serial.print(fuelPressure);
Serial.println(" PSI ");
Serial.print("Oil Pressure = ");
Serial.print(oilPressure);
Serial.println(" PSI ");
Serial.print("Fuel Air Ratio = ");
Serial.println(fuelAir);
Serial.print("Injection Time = ");
Serial.print(injTime);
Serial.println(" us ");
}
stateCYL = digitalReadFast(inCYL);
if (stateCYL != lastCYL) {
if (stateCYL == LOW) {
startCYLTime = micros();
intervalCYL = startCYLTime - stopCYLTime;
pulseCYL++;
countCYL++;
engRPM = 120000000 / intervalCYL;
totalRPM = totalRPM + engRPM;
if (countCYL == 10) {
averageRPM = totalRPM / 10;
countCYL = 0;
totalRPM = 0;
}
if (pulseCYL == 1)setEnable = true;
if (pulseCYL == 100)setEnable = false;
}
lastCYL = stateCYL;
stopCYLTime = startCYLTime;
rpm_control();
}
}
void rpm_control() {
/*if (engRPM >= 1850) {
digitalWriteFast(motorCon1, HIGH);
digitalWriteFast(motorCon2, HIGH);
digitalWriteFast(motorCon3, LOW);
digitalWriteFast(motorCon4, LOW);
}
if (engRPM <= 1750) {
digitalWriteFast(motorCon1, LOW);
digitalWriteFast(motorCon2, LOW);
digitalWriteFast(motorCon3, HIGH);
digitalWriteFast(motorCon4, HIGH);
}
else {
digitalWriteFast(motorCon1, LOW);
digitalWriteFast(motorCon2, LOW);
digitalWriteFast(motorCon3, LOW);
digitalWriteFast(motorCon4, LOW);
}*/
}

Engine Control
/*version 0714
Engine controller for a generator.
created 2020
by Allen J. Lindfors

#include >digitalWriteFast.h<
//set the pins for nano board, change pin #s for mega
const byte inCYL = 2; // // the number of the pin for the input CYL signal
const byte inTDC = 3; // the number of the pin for the input TDC signal
const byte inCPS = 4; // the number of the pin for the input CPS signal
const byte inMAP = A0;
const byte inIAT = A1;
const byte inOXY = A2;
bool setEnable, setFlowRateEnable;
//outputs
//firing order 1 3 4 2 the pulseCPS and pulseTDC selection configures this
const int sp[4] = {5, 6, 7, 8};
const int fi[4] = {9, 10, 11, 12};
//integer variables for controls
int pulseCYL = 0; //initialize
int stateCYL = LOW; //initialize
int lastCYL = LOW; //initialize
int pulseTDC = 0; //initialize
int stateTDC = LOW; //initialize
int lastTDC = LOW; //initialize
int pulseCPS = 0; //initialize
int stateCPS = LOW; //initialize
int lastCPS = LOW; //initialize
//timing and sensor values
//Sensor fits variable = C + S*(value)
//IAT
float C1 = 164, S1 = -0.15;
//ECT float C2 = 164, S2 = -0.15;
//MAP
float C3 = 0, S3 = 0.302;
//injector flow rate in cc/min see startup below
float flowRate;
//single cylinder volume
float cylVolume = 400.0;
//fuel values
float rho = 0.72; //for gasoline
//other densities propane=0.58, ethanol = 0.78, methane = 0.42, methanol = 0.78,
//fuel air value
float fuelAirRatio = 14.7; //for gasoline
//other fuel/air value propane=15.5, ethanol = 9.0, methane = 17.2, methanol = 6.4
//injection calculation values
float massAir, massFuel, injectionTime;
//timing and claculation variables
unsigned long injTime, spTime, ignTime, timeTDC;
unsigned long injTime0, injTime1, injTime2, injTime3;
unsigned long sparkTime0, sparkTime1, sparkTime2, sparkTime3;
unsigned long intervalCYL, startCYLTime, stopCYLTime;
unsigned long intervalTDC, startTDCTime, stopTDCTime;
unsigned long intervalCPS, startCPSTime, stopCPSTime;
//read usage variables
float airPressure, airTemperature;
float engMAP, engIAT, engOXY, engRPM, totalRPM;
void setup() {
// put your setup code here, to run once:
//set the pins
pinModeFast(inCPS, INPUT);
pinModeFast(inTDC, INPUT);
pinModeFast(inCYL, INPUT);
pinModeFast(fi[0], OUTPUT);
pinModeFast(fi[1], OUTPUT);
pinModeFast(fi[2], OUTPUT);
pinModeFast(fi[3], OUTPUT);
pinModeFast(sp[0], OUTPUT);
pinModeFast(sp[1], OUTPUT);
pinModeFast(sp[2], OUTPUT);
pinModeFast(sp[3], OUTPUT);
setEnable = false;
setFlowRateEnable = false;
digitalWriteFast(fi[0], LOW);
digitalWriteFast(fi[1], LOW);
digitalWriteFast(fi[2], LOW);
digitalWriteFast(fi[3], LOW);
digitalWriteFast(sp[0], LOW);
digitalWriteFast(sp[1], LOW);
digitalWriteFast(sp[2], LOW);
digitalWriteFast(sp[3], LOW);
Serial.begin(9600);
}
//get timers and settings for fuel injectors and ignition coils
void initial_calculations() {
//make the calculations
airTemperature = 300;//273 + C1 + engIAT * S1;
airPressure = 75;//C3 + engMAP * S3;
massAir = cylVolume * 0.0289 / 8.314; //mass air without P and T
massFuel = massAir / fuelAirRatio; //mass fuel without P and T
injectionTime = 1000 * massFuel / (rho * (flowRate / 60)); //in microseconds 00;
spTime = 2000; //spark coil charge time in microseconds
}
//initial setup for cold start
void startup_sequence() {
if (setFlowRateEnable == true)flowRate = 210;
else if (setFlowRateEnable == false)flowRate = 225;
}
void loop() {
// get the timing
initial_calculations();
startup_sequence();
injTime = 1000 * injectionTime * airPressure / airTemperature;
//read in the timing from the cam sensors
stateCYL = digitalReadFast(inCYL);
stateCPS = digitalReadFast(inCPS);
stateTDC = digitalReadFast(inTDC);
//main detection, timing, and writing to outputs
//CYL Counter
if (stateCYL != lastCYL) {
if (stateCYL == LOW) {
pulseCPS = 0;
pulseTDC = 0;
pulseCYL++;
startCYLTime = micros();
intervalCYL = startCYLTime - stopCYLTime;
//set the number of cam shaft revolutions to ignore
if (pulseCYL == 3)setEnable = true;
//set the number of initial cam shaft revolutions for extra fuel
if (pulseCYL == 2)setFlowRateEnable = true;
if (pulseCYL == 50)setFlowRateEnable = false;
}
lastCYL = stateCYL;
stopCYLTime = startCYLTime;
//if (intervalCYL >= 200000)ignTime = 1100;
//works but off
ignTime = 700 + (0.03 * intervalCYL);
//ignTime = 2000;
}
//TDC Counter
if (stateTDC != lastTDC) {
if (stateTDC == HIGH) {
pulseTDC++;
startTDCTime = micros();
//set injector zero times
if (pulseTDC == 3)injTime0 = micros();
if (pulseTDC == 2)injTime1 = micros();
if (pulseTDC == 4)injTime2 = micros();
if (pulseTDC == 1)injTime3 = micros();
//set zero times for spark plugs
if (pulseTDC == 1)sparkTime0 = micros(); //spark cylinder 1
if (pulseTDC == 4)sparkTime1 = micros(); //spark cylinder 2
if (pulseTDC == 2)sparkTime2 = micros(); //spark cylinder 3
if (pulseTDC == 3)sparkTime3 = micros(); //spark cylinder 4
intervalTDC = startTDCTime - stopTDCTime;
}
lastTDC = stateTDC;
stopTDCTime = startTDCTime;
}
//CPS Counter
if (stateCPS != lastCPS) {
if (stateCPS == HIGH) {
pulseCPS++; //increment the counter
//read sensors in between pulses
if (pulseCPS == 3) {
engMAP = analogRead(inMAP);//read manifold pressure
airPressure = C3 + (S3 * engMAP);
}
if (pulseCPS == 7) {
engIAT = analogRead(inIAT);//read intake temperature
airTemperature = C1 + (S1 * engIAT);
}
if (pulseCPS == 11) {
engOXY = analogRead(inOXY);//read AFR
}
}
lastCPS = stateCPS;
stopCPSTime = startCPSTime;
}
if (setEnable == true) {
//write to the spark plug outputs
timeTDC = intervalCYL / 4;
//cylinder 1
if (micros() >= sparkTime0 + timeTDC - (ignTime + spTime) &&
micros() <= sparkTime0 + timeTDC - (ignTime + spTime) + 1000) {
digitalWriteFast(sp[0], HIGH);
}
if (micros() >= sparkTime0 + timeTDC - ignTime)digitalWriteFast(sp[0], LOW);
//cylinder 2
if (micros() >= sparkTime1 + timeTDC - (ignTime + spTime) &&
micros() <= sparkTime1 + timeTDC - (ignTime + spTime) + 1000) {
digitalWriteFast(sp[1], HIGH);
}
if (micros() >= sparkTime1 + timeTDC - ignTime)digitalWriteFast(sp[1], LOW);
//cylinder 3
if (micros() >= sparkTime2 + timeTDC - (ignTime + spTime) &&
micros() <= sparkTime2 + timeTDC - (ignTime + spTime) + 1000) {
digitalWriteFast(sp[2], HIGH);
}
if (micros() >= sparkTime2 + timeTDC - ignTime)digitalWriteFast(sp[2], LOW);
//cylinder 4
if (micros() >= sparkTime3 + timeTDC - (ignTime + spTime) &&
micros() <= sparkTime3 + timeTDC - (ignTime + spTime) + 1000) {
digitalWriteFast(sp[3], HIGH);
}
if (micros() >= sparkTime3 + timeTDC - ignTime)digitalWriteFast(sp[3], LOW);
//write to the fuel injector outputs
//injector 1
if (micros() >= injTime0 + 25 && micros() <= injTime0 + 1000) {
digitalWriteFast(fi[0], HIGH);
}
if (micros() >= injTime0 + injTime) {
digitalWriteFast(fi[0], LOW);
}
//injector 2
if (micros() >= injTime1 + 25 && micros() <= injTime1 + 1000) {
digitalWriteFast(fi[1], HIGH);
}
if (micros() >= injTime1 + injTime) {
digitalWriteFast(fi[1], LOW);
}
//injector 3
if (micros() >= injTime2 + 25 && micros() <= injTime2 + 1000) {
digitalWriteFast(fi[2], HIGH);
}
if (micros() >= injTime2 + injTime) {
digitalWriteFast(fi[2], LOW);
}
//injector 4
if (micros() >= injTime3 + 25 && micros() <= injTime3 + 1000) {
digitalWriteFast(fi[3], HIGH);
}
if (micros() >= injTime3 + injTime) {
digitalWriteFast(fi[3], LOW);
}
}
}

What still has to happen is that I need to incorporate the oxygen sensor into the fuel injection time and then build up a table of values for the system to pick from as input variables change.

No comments:

Post a Comment