// Generator control system for car alternator (Ford with PWM controlled charging voltage) and small engine
#define DEBUG_STATUS // Serial debug, comment to disable, downmost values
//#define DEBUG_MAIN //Serial debug, comment to disable, uppermost main function values
// System status variables (motorStatus)
#define WAITING 0
#define STARTING 1
#define STARTED 2
#define RAMP_UP 3
#define RUNNING 4
#define FAIL 5
#define STOPPING 6
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <neotimer.h> // Non-blocking 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(15000); // 15 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 continuous cranking
Neotimer crankWait = Neotimer(5000); // 5 second timer, if crank failed, wait 5 seconds before a new try
Neotimer voltageWait = Neotimer(2000); // 60 seconds timer, how long to wait with undervoltage & other assigned conditions, before the starting function executes
Neotimer serDelay = Neotimer(1000); // For serial print interval
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, sensor type: 3144
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, actual value
int current; // For current testing, serial input
//float sensVoltage; // Store sensed voltage, actual value
int sensVoltage = 13; // For voltage testing, serial input
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 (ramp up), 77 = ~30 %, you can add steps, if needed
int currentPwmStep = 0;
// Motor state, starting and shutting down
int motorStatus; // 0=waiting, for low voltage 1=starting, 2=started, 3=ramping up pwm, 4=running, 5=stopping, 6=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
int startInterval; // 0=not waiting after start attempt, 1=waiting after start attempt
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
int rpm = 0; //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_MAIN
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_MAIN
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_MAIN
Serial.println("Setup ok.");
#endif
}
void motorCrankState() { // Detection of undervoltage to trigger motor cranking function
if (sensVoltage < startVoltage) { // If motorStatus is WAITING and sensed voltage is less than start voltage, trigger cranking function
#ifdef DEBUG_MAIN
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 = STARTING;
digitalWrite(relayPin, HIGH); // Switch ignition relay on
voltageWait.stop();
voltageWait.reset(); // Reset timer
}
}
}
void crank() { // Engine starting function
if (((startTry >= 0) && (startTry <= 4)) && (startInterval == 0)) { // If conditions are met, start executing
if (rpm <= rpmTH) { // If motor runs slower than the threshold, switch starter relay on
digitalWrite(starterPin, HIGH);
if (!crankTime.started()) {
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, LOW); // Switch starter relay off
crankTime.stop();
crankTime.reset(); // Reset timer
startTry += 1; // Add one attempt
startInterval = 1; // Start waiting time function after starting attempt
}
}
} else if (rpm > rpmTH) { // If motor runs faster than the threshold, switch starter relay off
digitalWrite(starterPin, LOW); // Switch starter pin off
startTry = 0;
motorStatus = STARTED;
crankTime.stop();
crankTime.reset(); // Reset timer
}
}
void Waiter() { // Wait after starting attempt
if (((startTry >= 0) && (startTry <= 4)) && (startInterval == 1)) { // If conditions are met, start executing
if (!crankWait.started()) {
crankWait.start(); // Start the crankWait timer if it hasn't started yet
}
if (crankWait.done()) { // If timer is done
startInterval = 0; // revert to state 0 (waiting time after start attempt ready)
crankWait.stop();
crankWait.reset(); // reset timer
}
}
}
void startFail() { // If engine did not start put system status to state 5 (malfunction)
if (startTry == 5) {
motorStatus = FAIL;
digitalWrite(starterPin, LOW); // Starter relay off
digitalWrite(relayPin, LOW); // Ignition switch relay off
#ifdef DEBUG_MAIN
Serial.println("Kaynnistysvirhe!");
#endif
lcd.setCursor(0, 0); // On lcd print "Engine start failed"
lcd.print("K");
lcd.print((char)0xe1);
lcd.print("ynnistys ");
lcd.setCursor(0, 1);
lcd.print("ep");
lcd.print((char)0xe1);
lcd.print("onnistui ");
}
}
void overvoltage() { // If voltage is over max voltage put motor status to FAIL (malfuntion) and shut down the ignition relay
if (sensVoltage >= upVoltage) {
motorStatus = FAIL;
digitalWrite(relayPin, LOW);
#ifdef DEBUG_MAIN
Serial.println("Ylijannite!");
#endif
lcd.setCursor(0, 0); // On lcd print "Overvoltage"
lcd.print("Ylij");
lcd.print((char)0xe1);
lcd.print("nnite ");
lcd.setCursor(0, 1);
lcd.print(" ");
}
}
void motorRunning() { // Engine running detection
if ((rpm > rpmRun) || (digitalRead(motorPin == HIGH))) { // If engine runs faster than rpmRun value (revolution per minute) assume motor is running normally
#ifdef DEBUG_MAIN
Serial.println("Kayntitieto!");
#endif
if (!motorTime.started()) {
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 = RAMP_UP;
motorTime.stop(); // Stop the timer
motorTime.reset(); // Reset the timer
#ifdef DEBUG_MAIN
Serial.println("Kay.");
#endif
}
}
}
void rampUp() { // Ramping up PWM
//Ramp pwm up
if (stepTime.repeat()) {
analogWrite(pwmPin, pwmValues[currentPwmStep]);
currentPwmStep++;
#ifdef DEBUG_MAIN
Serial.println("Ramp up...");
#endif
if (currentPwmStep >= sizeof(pwmValues) / sizeof(pwmValues[0])) { //Checking the pwm step count
motorStatus = RUNNING; // Motor running on normal charging state
stepTime.stop();
stepTime.reset(); // Reset the timer
}
}
}
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)) {
// if (digitalRead(motorPin) == HIGH) { // Vibration sensor
digitalWrite(relayPin, LOW); // Switch relay off
analogWrite(pwmPin, 15); // Duty Cycle to 5 % before running down engine
if (!shutTime.started()) {
shutTime.start();
}
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Stopped Duty Cycle at 100 %, alternator electromagnet off
lcd.setCursor(0, 0);
lcd.print("Sammutus "); // Print on lcd "Shutdown"
lcd.setCursor(0, 1);
lcd.print(" ");
motorStatus = WAITING; // Motor has run, battery is full, go back to waiting voltage
shutTime.stop(); // Reset the timer
shutTime.reset(); // Reset the timer
#ifdef DEBUG_MAIN
Serial.println("Sammutettu.");
#endif
}
}
}
//}
void lcdDisplay() { // Show values on lcd only when
if (motorStatus == RUNNING) {
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_MAIN
Serial.println("Virta LCD.");
#endif
}
if (motorStatus == WAITING || motorStatus == STARTING) {
lcd.setCursor(0, 0); // On lcd print "Waiting"
lcd.print("Odotetaan... ");
lcd.setCursor(0, 1);
lcd.print("J");
lcd.print((char)0xe1);
lcd.print("nnite: ");
lcd.print((float)sensVoltage);
lcd.print(" V");
}
}
void loop() {
// Calculate current from sensor 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(1); //Wait for a millisecond 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 linear equation, correct with current calibration value if needed
// 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(1); // Wait for a millisecond 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
// }
// Handling the motor status
switch (motorStatus) {
case WAITING:
motorCrankState();
lcdDisplay();
startFail();
overvoltage();
break;
case STARTING:
crank();
Waiter();
lcdDisplay();
startFail();
overvoltage();
break;
case STARTED:
motorRunning();
startFail();
overvoltage();
break;
case RAMP_UP:
rampUp();
lcdDisplay();
startFail();
overvoltage();
break;
case RUNNING:
startFail();
overvoltage();
break;
}
//Serial key input test code, write to various variables
#ifdef DEBUG_STATUS
if (Serial.available() > 0) {
char input = Serial.read();
switch (input) {
case '1':
rpm = 0;
break;
case '2':
rpm = 500;
break;
case '3':
rpm = 2000;
break;
case 'a':
current = 2;
break;
case 'b':
current = 20;
break;
case 'c':
current = 80;
break;
case 'z':
sensVoltage = 11;
break;
case 'x':
sensVoltage = 14;
break;
case 'y':
sensVoltage = 16;
break;
default:
Serial.println("Invalid input.");
return;
}
}
#endif
//Debug
#ifdef DEBUG_STATUS
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();
Serial.print("Odotus: ");
Serial.print(startInterval);
Serial.println();
Serial.print("RPM: ");
Serial.print(rpm);
Serial.println();
#endif
}