#define DEBUG // Serial debug, comment to disable
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <neotimer.h> // Timer from Neotimer library
Neotimer motorTime = Neotimer(10000); // 10 second timer for startup detection
Neotimer stepTime = Neotimer(1000); // 1 second timer for PWM step interval
Neotimer shutTime = Neotimer(10000); // 10 second timer for engine shutdown (wait before switching PWM back to 100 %, electromagnet off)
Neotimer crankTime = Neotimer(4000); // 4 second timer, if problems to start, try at most 4 seconds cranking
Neotimer crankWait = Neotimer(5000); // 5 second timer, if crack failed, wait 5 seconds before a new try
Neotimer voltageWait = Neotimer(2000); // 60 seconds timer, how long to wait with assigned conditions, before the starting function executes
LiquidCrystal_I2C lcd(0x27, 16, 2); // Create a new LiquidCrystal_I2C display object with the correct I2C address and display size
// Define the pins
const int currentPin = A0; // Information about current
const int voltagePin = A2; // Information about voltage
const int motorPin = 2; // Motor running pin (vibration sensor), HIGH = motor running, TEST purpose LOW
const int pwmPin = 9; // PWM output pin
const int relayPin = 4; // Shutdown relay control pin
const int rpmPin = 11; // RPM Hall Sensor input pin
const int starterPin = 3; // Starter relay control pinn
// Current and voltage calculation
float voltageFactor = 5.00 / 1023.00; // Factor to convert ADC reading to voltage
// Define the points for current calculation / the line equation
float x1 = 0.500; // volts
float y1 = 0; // amps
float x2 = 4.005; // volts
float y2 = 150; // amps
// Calculate the slope and intercept
float m = (y2 - y1) / (x2 - x1);
float b = y1 - m * x1;
float currentCal = 1; // Variable to shift the whole current level
float sensVfactor = 20.00 / 4.9248; // 4.9248 volts = 20 volts, factor to convert high voltage to 0-5 V
float current; // Store current value
float sensVoltage; // Store sensed voltage
const int N = 25; // Number of current readings to average
const int Y = 25; // Number of sensed voltage readings to average
int readings[N]; // Array to store the current readings
int readingsV[Y]; // Array to store the sensed voltage readings
// PWM
int pwmValues[] = { 24, 41, 58, 77 }; // PWM duty cycle steps, 77 = ~30 %
int currentPwmStep = 0;
// Motor state, starting and shutting down
int motorStatus; // 0=stopped, waiting for low voltage 1=not running, deary for cranking, 2=started, 3=ramping up pwm, 4=running, 5=system or engine malfuntion, stopped
int startTry; // 0=not tried to start yet, 1=first try done, 2=second, 3=third, 4=fourth, 5=fifth
float startVoltage = 11.9; // Volts, under what voltage the starting function will execute
float shutCurrent = 5; // Amperes, below what charging current to execute the shutdown function
float upVoltage = 15; // Volts, max allowed voltage
float rpmTH = 300; // Revolutions per minute, rpm threshold greater than this to stop cranking (assume motor is started rpms over the threshold)
float rpmRun = 1800; // Revolutions per minute, the rpm greater than this is accounted as motor normal running
//RPM Calculation
unsigned long rpm = 0; // Variable to store rpm value
unsigned long duration; // Sensor pulse in duration
// int lcdStep; //0=current display, 1=voltage display, not used at the time
void setup() {
Serial.begin(250000); // Start the serial communication
#ifdef DEBUG
Serial.println("Setup...");
#endif
Wire.begin();
// Check if the LCD is connected
Wire.beginTransmission(0x27);
if (Wire.endTransmission() == 0) {
// LCD is connected, proceed with initialization
delay(50);
lcd.begin(16, 2);
lcd.backlight();
lcd.clear();
} else {
// LCD is not connected, continue without initializing the LCD
#ifdef DEBUG
Serial.println("Ei LCD:ta. Ohitus.");
#endif
}
// Change timers on pins to change PWM freq to 122 Hz
// Pins D9 and D10 - 122 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000100; // x256 phase correct
// Make pins output/input
pinMode(currentPin, INPUT); // Information on charging current
pinMode(voltagePin, INPUT); // Information on voltage, for real use, delete _PULLUP
pinMode(motorPin, INPUT_PULLUP); // Vibration sensor in, for motor running detect, for real use, delete _PULLUP (it is for test purpose, pullup resistor)
pinMode(pwmPin, OUTPUT); // Alternator PWM output
pinMode(relayPin, OUTPUT); // Ignition relay pin
pinMode(rpmPin, INPUT_PULLUP); // RPM sensing pin
pinMode(starterPin, OUTPUT); // Engine starter relay pin
// Start Duty Cycle at 100 %, alternator electromagnet off, for starting the engine
analogWrite(pwmPin, 255);
#ifdef DEBUG
Serial.println("Setup ok.");
#endif
}
void motorCrankState() { // Detection of undervoltage to trigger motor cranking function
if (motorStatus == 0 && sensVoltage < startVoltage) { // If motorStatus is 0 and sensed voltage is less than start voltage, trigger cranking function
#ifdef DEBUG
Serial.println("Jannitekynnys kaynnistykseen!");
#endif
if (!voltageWait.started()) { // Start the voltageWait timer only, if it has not been started yet
voltageWait.start(); // Start the voltageWait timer if it hasn't started yet
}
if (voltageWait.done()) { // Wait for voltageWait to be done before moving to the cranking of the engine
motorStatus = 1;
voltageWait.reset(); // Reset timer
}
} else if (motorStatus == 0) {
voltageWait.stop(); // Else, stop timer
voltageWait.reset();
}
}
void Crank1() { // Engine start first attempt
if (motorStatus == 1 && startTry == 0 && rpm == 0) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, LOW);
if (!crankTime.started()) { // Start the crankTime timer only, if it has not been started yet
crankTime.start(); // Start the crankTime timer if it hasn't started yet
}
if (crankTime.done()) { // If motor did not start during the crankTime, stop starting
digitalWrite(starterPin, HIGH); // Switch starter relay off
crankTime.reset(); // Reset timer
// Waiting time between starting attempts
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) { // If waiting time full
startTry = 1; // attempt done
crankWait.reset(); // Reset timer
}
} else if (startTry == 0) { // If attempt not done yet
crankWait.stop(); // stop wait timer
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, HIGH);
crankTime.stop();
startTry = 1;
motorStatus = 2;
crankTime.reset(); // Reset timer
}
}
}
void Crank2() { // Engine start second attempt
if (motorStatus == 1 && startTry == 1 && rpm == 0) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, LOW);
if (!crankTime.started()) { // Start the crankTime timer only, if it has not been started yet
crankTime.start(); // Start the crankTime timer if it hasn't started yet
}
if (crankTime.done()) { // If motor did not start during the crankTime, stop starting
digitalWrite(starterPin, HIGH); // Switch starter relay off
crankTime.reset(); // Reset timer
// Waiting time between starting attempts
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) { // If waiting time full
startTry = 2; // attempt done
crankWait.reset(); // Reset timer
}
} else if (startTry == 1) { // If attempt not done yet
crankWait.stop(); // stop wait timer
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, HIGH);
crankTime.stop();
startTry = 2;
motorStatus = 2;
crankTime.reset(); // Reset timer
}
}
}
void Crank3() { // Engine start third attempt
if (motorStatus == 1 && startTry == 2 && rpm == 0) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, LOW);
if (!crankTime.started()) { // Start the crankTime timer only, if it has not been started yet
crankTime.start(); // Start the crankTime timer if it hasn't started yet
}
if (crankTime.done()) { // If motor did not start during the crankTime, stop starting
digitalWrite(starterPin, HIGH); // Switch starter relay off
crankTime.reset(); // Reset timer
// Waiting time between starting attempts
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) { // If waiting time full
startTry = 3; // attempt done
crankWait.reset(); // Reset timer
}
} else if (startTry == 2) { // If attempt not done yet
crankWait.stop(); // stop wait timer
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, HIGH);
crankTime.stop();
startTry = 3;
motorStatus = 2;
crankTime.reset(); // Reset timer
}
}
}
void Crank4() { // Engine start fourth attempt
if (motorStatus == 1 && startTry == 3 && rpm == 0) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, LOW);
if (!crankTime.started()) { // Start the crankTime timer only, if it has not been started yet
crankTime.start(); // Start the crankTime timer if it hasn't started yet
}
if (crankTime.done()) { // If motor did not start during the crankTime, stop starting
digitalWrite(starterPin, HIGH); // Switch starter relay off
crankTime.reset(); // Reset timer
// Waiting time between starting attempts
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) { // If waiting time full
startTry = 4; // attempt done
crankWait.reset(); // Reset timer
}
} else if (startTry == 3) { // If attempt not done yet
crankWait.stop(); // stop wait timer
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, HIGH);
crankTime.stop();
startTry = 4;
motorStatus = 2;
crankTime.reset(); // Reset timer
}
}
}
void Crank5() { // Engine start fifth attempt
if (motorStatus == 1 && startTry == 4 && rpm == 0) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, LOW);
if (!crankTime.started()) { // Start the crankTime timer only, if it has not been started yet
crankTime.start(); // Start the crankTime timer if it hasn't started yet
}
if (crankTime.done()) { // If motor did not start during the crankTime, stop starting
digitalWrite(starterPin, HIGH); // Switch starter relay off
motorStatus = 5; // Fault state
crankTime.reset(); // Reset timer
// Waiting time between starting attempts
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) { // If waiting time full
startTry = 5; // attempt done
crankWait.reset(); // Reset timer
}
} else if (startTry == 4) { // If attempt not done yet
crankWait.stop(); // stop wait timer
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, HIGH);
crankTime.stop();
startTry = 5;
motorStatus = 2;
crankTime.reset(); // Reset timer
}
}
}
void motorRunning() { // Engine running detection
if ((motorStatus == 2) && digitalRead(motorPin) == HIGH || rpm > rpmRun) { // If engine runs faster than rpmRun value (revolution per minute) assume motor is running normally
motorTime.reset(); // Reset the timer
#ifdef DEBUG
Serial.println("Kayntitieto!");
#endif
if (!motorTime.started()) { // Start the motorTime timer only, if it has not been started yet
motorTime.start(); // Start the motorTime timer if it hasn't started yet
}
if (motorTime.done()) { // Wait for motorTime to be done before moving to ramping up PWM
motorStatus = 3;
#ifdef DEBUG
Serial.println("Kay.");
#endif
}
} else if (motorStatus == 0 || motorStatus == 1) {
motorTime.stop(); // Stop the timer if motor is not running before ramping up state
#ifdef DEBUG
Serial.println();
Serial.println("Kaynnistys...");
#endif
lcd.setCursor(0, 0); // On lcd print Finnish to start the engine
lcd.print("K");
lcd.print((char)0xe1);
lcd.print("ynnist");
lcd.print((char)0xe1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("moottori");
}
}
void rampUp() { // Ramping up PWM
//Ramp pwm up
if (motorStatus == 3) {
stepTime.reset(); // Reset the timer
if (stepTime.repeat()) {
analogWrite(pwmPin, pwmValues[currentPwmStep]);
currentPwmStep++;
#ifdef DEBUG
Serial.println("Ramp up...");
#endif
if (currentPwmStep >= sizeof(pwmValues) / sizeof(pwmValues[0])) { //Checking the pwm step count
motorStatus = 4; // Motor running on normal charging state
}
}
}
}
void shutDown() { // Engine shutdown, execute relay
// If the charging current drops to 0 to shutCurrent and charging voltage is under upVoltage and motor is running
// start the shutdown function and turn off the relay
if (current >= 0 && current < shutCurrent && sensVoltage < upVoltage && digitalRead(motorPin) == HIGH && motorStatus == 4) {
shutTime.reset(); // Reset the timer
digitalWrite(relayPin, LOW);
analogWrite(pwmPin, 15); // Duty Cycle to 5 % before running down engine
if (!shutTime.started()) { // Wait for shutDelay time to engine have time to stall down
shutTime.start();
}
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Stopped Duty Cycle at 100 %, alternator electromagnet off
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sammutus");
motorStatus = 0; // Motor has run, battery is full, go back to waiting voltage
#ifdef DEBUG
Serial.println("Sammutettu.");
#endif
}
} else if (motorStatus == 4) {
motorTime.stop(); // Stop the timer if motor is in state 4 (normal running)
digitalWrite(relayPin, HIGH); // Else, keep relay on
#ifdef DEBUG
Serial.println("Kaynnissa.");
#endif
}
}
void showCurrent() { // Show current and voltage on lcd only when normal running state
if (motorStatus == 4) {
lcd.setCursor(0, 0);
lcd.print("Virta:");
lcd.setCursor(0, 1);
lcd.print((float)current);
lcd.print(" ");
lcd.setCursor(5, 1);
lcd.print(" A");
lcd.setCursor(8, 0);
lcd.print("J");
lcd.print((char)0xe1);
lcd.print("nnite:");
lcd.setCursor(8, 1);
lcd.print((float)sensVoltage);
lcd.print(" ");
lcd.setCursor(13, 1);
lcd.print(" V");
#ifdef DEBUG
Serial.println("Virta LCD.");
#endif
}
}
void loop() {
// Calculate current from shunt reading
// Calculate the charging current from the average and display it on the LCD screen.
// Take N readings and store them in the array
for (int i = 0; i < N; i++) {
readings[i] = analogRead(currentPin);
delay(5); //Wait for 5 milliseconds between readings
}
// Calculate the average of the N readings
float sum = 0;
for (int i = 0; i < N; i++) {
sum += readings[i];
}
float average = sum / N;
float voltage = average * voltageFactor; // Convert ADC reading to voltage
current = (m * voltage + b) * currentCal; // Convert voltage to current using the new linear equation
// Calculate high voltage from voltage divider input
// Calculate the charging voltage from the average and display it on the LCD screen.
// Take Y readings and store them in the array
for (int j = 0; j < Y; j++) {
readingsV[j] = analogRead(voltagePin);
delay(5); // Wait for 5 milliseconds between readings
}
// Calculate the average of the N readings
float sumV = 0;
for (int j = 0; j < Y; j++) {
sumV += readingsV[j];
}
float averageV = sumV / Y;
float voltageV = averageV * voltageFactor; // Convert ADC reading to voltage
sensVoltage = voltageV * sensVfactor; // Convert voltage to real voltage
// RPM Calculation from Hall Sensor input
{
duration = pulseIn(rpmPin, FALLING, 500000); // Times the amount of microseconds the motor is not timing IR, Times out after 100000 uS. Raise the timeout for slower RPM readings. .5 second
rpm = 60000.0 / duration * 1000; // See above
}
motorCrankState();
Crank1();
Crank2();
Crank3();
Crank4();
Crank5();
motorRunning();
rampUp();
shutDown();
showCurrent();
//Debug
#ifdef DEBUG
Serial.print("Jannite: ");
Serial.print((float)sensVoltage);
Serial.println();
Serial.print("Virta: ");
Serial.print((float)current);
Serial.println();
Serial.print("Tila: ");
Serial.print(motorStatus);
Serial.println();
Serial.print("Start tila: ");
Serial.print(startTry);
Serial.println();
#endif
}