Published on

Standby-Generator

Authors

As part of the power supply concept for servers etc., I extended a generator with electric start. Originally it's an ordinary generator with a manual cold start lever and a pull cord. Additionally, it now has a key switch to operate the electric starter. The cold start remains manual.

An Arduino now handles the entire starting process.

The hardware setup looks something like this:

  • Arduino Micro (clone)
  • Serial connection to a Raspberry Pi which will send the start command
  • Relay that operates the electric starter motor
  • Relay that connects the spark plug to ground to shut off the engine
  • DC from the generator (before the diode) via voltage divider/Zener to the Arduino's ADC
  • Servo that operates the cold start (now replaced by an electromagnet)

The Arduino handles the following functions:

  1. Regular reporting of the battery status (from the ADC) via Serial
  2. Waiting for commands
  3. Upon command, pulling the cold start
  4. Releasing the shutdown relay
  5. Activating the starter relay for a certain duration
  6. Waiting, then measuring the RPM (via ADC, which now shows battery voltage plus rhythmic diode voltage)
  7. If the engine is not running, a new startup process, now 5 seconds longer
  8. If the engine is running, releasing the cold start
  9. Regular reporting and monitoring

All timings are configurable

Code here, this is old... I will show a newer version soon

https://youtu.be/HBANZNmgIr8

/*
 *GeneratorGovernor to autostart a cheap generator with electric start
 * Mine e.g. is a Fullex FG 8500XE
 * 
 * 
 * 
 * 
 */
#include <EEPROM.h>
#include <Servo.h>
Servo jock;
#include <MsTimer2.h>
//#include <SoftwareSerial.h>
//SoftwareSerial mySerial(1, 0); // RX, TX



//Set Verison (changes reset EEPROM to default!)

const byte versionnr = 2;

//Set Pins

const byte IOStatusLed = LED_BUILTIN;
const byte IOVBat = 2;
const byte IOStarter = 4;
const byte IOStop = 3;
const byte IORPM = 14;
const byte IOVforRPM = 15;
const byte IO0forRPM = 9;

const byte IOJock = 10;
const byte JOCKOPEN =45;
const byte JOCKCLOSED = 90;

//Set Config Adresses

const byte EEversionnr = 0;
const byte EEinitfiretime = 1;
const byte EEstepupfiretime = 2;
const byte EEmaxretry = 3;
const byte EEmaxretrytest = 4;
const byte EEwaittime = 5;
const byte EEheatuptime = 6;
const byte EEtestduration = 7;
const byte EEvbatdivider = 8;
const byte EEstep = 9;


// init config values
byte initfiretime = 25;    //initial try to start in 100ms
byte stepupfiretime = 10;  //increase startertime, in 100ms
byte maxretry = 6;         //maximum retries, including first try
byte maxretrytest = 3;     //maximum retries on tests, including first try
byte waittime = 10;        //time between tries
byte heatuptime = 10;      //time before opening Jock
byte testduration = 90;    //time before stopping test
byte vbatdivider = 40;     //Batterydivider (A/D 10Bit to Volt)

//init global vars
byte volatile procstatus = 0;       //processor
byte retriesdone = 0;
byte firetime = 0;
int counter = 0;           //Timing-counter, increased every 1/10 sec
int volatile nexttrig = 10;
byte RPMtimer = 0;        //same, to find sec
byte steplocation=20;
byte engineStatBuff = 0;


String inputString = "";    // a string to hold incoming data

//init Statusvalues
String EngineStatus = "OFF";
float VBat = 14;


 void setup() {
  //set pindirections
      pinMode(IOStarter, OUTPUT); digitalWrite(IOStarter,HIGH);
      pinMode(IOStop, OUTPUT); digitalWrite(IOStop,HIGH);
      jock.attach(IOJock);
      pinMode(LED_BUILTIN, OUTPUT);
      pinMode(IOVforRPM, OUTPUT); digitalWrite(IOVforRPM,HIGH);   
      pinMode(IO0forRPM, OUTPUT); digitalWrite(IO0forRPM,LOW);   

       
  //init serial on USB
      Serial.begin(9600);
      Serial1.begin(9600);
      inputString.reserve(200);
      //mySerial.begin(9600);
      Serial1.println("Boot");
      initConf();
  //setup timer
      MsTimer2::set(100,processor);
      MsTimer2::start();
  //set Jock
      jock.write(JOCKOPEN);

}

void loop() {
  // put your main code here, to run repeatedly:
  if(Serial){
    if(Serial.available()){
      char inChar = (char)Serial.read();
      // add it to the inputString:
      inputString += inChar;
      // if the incoming character is a newline, set a flag
      // so the main loop can do something about it:
      if (inChar == '\n') {
        SerialResponse(inputString);
      }
    }
  }
  if (Serial1.available()) {
    char inChar = (char)Serial1.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      SerialResponse(inputString);
    }
  }
  
}

//void serialEvent() {
//  while (Serial.available()) {
//    // get the new byte:
//    char inChar = (char)Serial.read();
//    // add it to the inputString:
//    inputString += inChar;
//    // if the incoming character is a newline, set a flag
//    // so the main loop can do something about it:
//    if (inChar == '\n') {
//      SerialResponse(inputString);
//    }
//  }
//}

void serprintln(String msg){
  if(Serial){
    Serial.println(msg);
  }
  Serial1.println(msg);
}

void serprint(String msg){
  if(Serial){
    Serial.print(msg);
  }
  Serial1.print(msg);
}


void initConf(){    //only first time on new device
  if(EEPROM.read(EEversionnr) != versionnr){
    EEPROM.write(EEversionnr,versionnr);
    EEPROM.write(EEinitfiretime,initfiretime);
    EEPROM.write(EEstepupfiretime,stepupfiretime);
    EEPROM.write(EEmaxretry,maxretry);
    EEPROM.write(EEmaxretrytest,maxretrytest);
    EEPROM.write(EEwaittime,waittime);
    EEPROM.write(EEheatuptime,heatuptime);
    EEPROM.write(EEtestduration,testduration);
    EEPROM.write(EEvbatdivider,vbatdivider);
    EEPROM.write(EEstep, steplocation);

  }else{
    initfiretime = EEPROM.read(EEinitfiretime);
    stepupfiretime = EEPROM.read(EEstepupfiretime);
    maxretry = EEPROM.read(EEmaxretry);
    maxretrytest = EEPROM.read(EEmaxretrytest);
    waittime = EEPROM.read(EEwaittime);
    heatuptime = EEPROM.read(EEheatuptime);
    testduration = EEPROM.read(EEtestduration);
    vbatdivider = EEPROM.read(EEvbatdivider);
    
    steplocation = EEPROM.read(EEstep);
    procstatus = EEPROM.read(steplocation);
    steplocation =+ 1; if (steplocation>40){steplocation = 20;}
    EEPROM.write(EEstep,steplocation);
    EEPROM.write(steplocation,procstatus);
    
    
  }
}

void updateProc(byte procupdate){
  EEPROM.update(steplocation,procupdate);
  procstatus = procupdate;
}


void SerialResponse(String inpStr){
  byte confvalue = inpStr.substring(2).toInt();
  serprintln("");
  switch (inpStr.charAt(0)){
    case '1':
      printStatus();
      break;
    case '2':
        //byte confvalue = inpStr.substring(2).toInt();
        if(confvalue>0){
          serprint("Update Key ");serprint(String(inpStr.charAt(1)));serprint(" to "); serprintln(String(confvalue));
          switch (inpStr.charAt(1)){
            case '1':
              initfiretime = confvalue; EEPROM.write(EEinitfiretime,initfiretime);
              break;
            case '2':
              stepupfiretime = confvalue; EEPROM.write(EEstepupfiretime,stepupfiretime);
              break;
            case '3':
              maxretry = confvalue; EEPROM.write(EEmaxretry,maxretry);
             break;
            case '4':
              maxretrytest = confvalue; EEPROM.write(EEmaxretrytest,maxretrytest);
              break;
            case '5':
              waittime = confvalue; EEPROM.write(EEwaittime,waittime);
              break;
            case '6':
              heatuptime = confvalue; EEPROM.write(EEheatuptime,heatuptime);
              break;
            case '7':
              testduration = confvalue; EEPROM.write(EEtestduration,testduration);
              break;
            case '8':
              vbatdivider = confvalue; EEPROM.write(EEvbatdivider,vbatdivider);
              break;
          }
        }
        serprintln("");
        serprintln("Config");
        serprint("1. InitFireTime (1/10s) = "); serprintln(String(initfiretime));
        serprint("2. StepUpTime (1/10s) = "); serprintln(String(stepupfiretime));
        serprint("3. Max Retry = "); serprintln(String(maxretry));
        serprint("4. Max Retry on Tests = "); serprintln(String(maxretrytest));
        serprint("5. WaitTime (s) = "); serprintln(String(waittime));
        serprint("6. HeatUpTime (s) = "); serprintln(String(heatuptime));
        serprint("7. Test Duration (s) = "); serprintln(String(testduration));
        serprint("8. VBat correction vector = "); serprintln(String(vbatdivider));
        serprintln("To change type 2<Nr><Value> (no spaces!)");
        serprintln("Each Value from 1 to 255");
      break;
    case '3':
      updateProc(10);
      nexttrig = 0;
      break;
    case '4':
      updateProc(90);
      nexttrig = 0;
      break;
    case '5':
      updateProc(20);
      nexttrig = 0;
      break;
    case '7':
      updateProc(0);
      nexttrig = 0;
    case '8':
      if(confvalue>0){
          serprint("Set Key ");serprint(String(inpStr.charAt(1)));serprint(" to "); serprintln(String(confvalue));
          switch (inpStr.charAt(1)){
            case '1':
              jock.write(confvalue);
              break;
          }
        }
        serprintln("");
        serprintln("Maintenance");
        serprintln("1. Relay Position");
        serprintln("To change type 2<Nr><Value> (no spaces!)");
        serprintln("Each Value from 1 to 255");
      break;
      
    default:
      serprintln("TwipSEA V0.1");
      serprintln("1. Status");
      serprintln("2. Config");
      serprintln("3. Start");
      serprintln("4. Stop");
      serprintln("5. Testrun");
      serprintln("");
      serprintln("7. Reset Errors");
      serprintln("8. Maintenance");
      serprintln("Press <Nr><ENTER>");
  }
  inputString = "";
  
}

void printStatus(){
  serprintln("");
  serprintln("Status");
  serprint("Processor = ");serprint(String(procstatus));serprint(" --> ");
  switch(procstatus){
    case 0: serprintln("Idle");break;
    case 1: serprintln("Running");break;
    case 10: serprintln("Starting, preparing Jock");break;
    case 11: serprintln("Starting, ignition");break;
    case 12: serprintln("Starting, wait for RPM");break;
    case 13: serprintln("Starting, evaluation");break;
    case 14: serprintln("Starting, warming up");break;
    case 15: serprintln("Starting, shut down Jock, wait");break;
    case 20: serprintln("Testing, preparing Jock");break;
    case 21: serprintln("Testing, ignition");break;
    case 22: serprintln("Testing, wait for RPM");break;
    case 23: serprintln("Testing, evaluation");break;
    case 24: serprintln("Testing, warming up");break;
    case 25: serprintln("Testing, shut down Jock, wait");break;
    case 26: serprintln("Testing, heating up a bit, then stop");break;
    case 90: serprintln("Stopping!!");break;
    case 91: serprintln("Stopping, measure RPM");break;
    case 92: serprintln("Stopped, returning to idle");break;
    case 101: serprintln("Critical Error: Motor is not shutting down!");break;
    case 102: serprintln("Critical Error: Motor is not starting up!");break;
    
  }
  serprint("Engine = "); serprintln(EngineStatus);
  serprint("VBat(v) = "); serprintln(String(VBat));


}




void processor(){
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); //WatchDog-Led
  counter++;

  
  RPMtimer++;
  if(RPMtimer>10) {
    RPMtimer=0;
    VBat = (analogRead(IOVBat)/(float)vbatdivider + 9*VBat)/10;
    //printStatus();
    if(digitalRead(IORPM)==1){
      EngineStatus="ON";
      engineStatBuff = 10;
    }else{
      if(engineStatBuff==0){
        EngineStatus="OFF";
      }else{
        engineStatBuff--;
      }
    }      
  }
  
  
  if(counter>nexttrig){
    counter = 0;
    switch(procstatus){
      case 0:
        nexttrig=30;
        printStatus();
        if(EngineStatus=="ON"){procstatus=1;}
        break;
      case 1:
        nexttrig=30;
        printStatus();
        if(EngineStatus=="OFF"){procstatus=0;}
        break;



        
      case 10:
        retriesdone = 0;
        firetime = initfiretime;
        jock.write(JOCKCLOSED);
        printStatus();
        updateProc(11);
        nexttrig = 20;
        break;
      case 11:
        digitalWrite(IOStarter,LOW);
        printStatus();
        updateProc(12);
        nexttrig = firetime;
        break;
      case 12:
        digitalWrite(IOStarter,HIGH);
        retriesdone++;
        printStatus();
        updateProc(13);
        nexttrig = waittime*10;
        break;
      case 13:
        if(EngineStatus=="OFF"){
          serprintln("");
          serprint("Tried to start generator, try ");serprint(String(retriesdone));serprintln(" failed");
          serprintln("");
          if(retriesdone>=maxretry){
            updateProc(102);
            nexttrig = 0;
            break;
          }else{
            serprintln("Next try, but longer");
            firetime = firetime + stepupfiretime;
            updateProc(11);
            nexttrig = 20;
            break;
          }
        }else{
          updateProc(14);
          nexttrig = 0;
          break; 
        }
      case 14:
        printStatus();
        updateProc(15);
        nexttrig = heatuptime*10;
        break;
      case 15:
        printStatus();
        jock.write(JOCKOPEN);
        updateProc(1);
        nexttrig = 10;
        break;




      case 20:
        retriesdone = 0;
        firetime = initfiretime;
        jock.write(JOCKCLOSED);
        printStatus();
        updateProc(21);
        nexttrig = 20;
        break;
      case 21:
        digitalWrite(IOStarter,LOW);
        printStatus();
        updateProc(22);
        nexttrig = firetime;
        break;
      case 22:
        digitalWrite(IOStarter,HIGH);
        retriesdone++;
        printStatus();
        updateProc(23);
        nexttrig = waittime*10;
        break;
      case 23:
        if(EngineStatus=="OFF"){
          serprintln("");
          serprint("Tried to start generator, try ");serprint(String(retriesdone));serprintln(" failed");
          serprintln("");
          if(retriesdone>=maxretrytest){
            updateProc(102);
            nexttrig = 0;
            break;
          }else{
            serprintln("Next try, but longer");
            firetime = firetime + stepupfiretime;
            updateProc(21);
            nexttrig = 20;
            break;
          }
        }else{
          updateProc(24);
          nexttrig = 0;
          break; 
        }
      case 24:
        printStatus();
        updateProc(25);
        nexttrig = heatuptime*10;
        break;
      case 25:
        printStatus();
        jock.write(JOCKOPEN);
        updateProc(26);
        nexttrig = 10;
        break;
      case 26:
        printStatus();
        updateProc(90);
        nexttrig = testduration*10;
        break;


        

      case 90:
        digitalWrite(IOStarter,HIGH);
        digitalWrite(IOStop,LOW);
        retriesdone =0;
        printStatus();
        updateProc(91);
        nexttrig = 100;
        break;
      case 91:
        printStatus();
        updateProc(92);
        nexttrig = 30;
      case 92:
        if(EngineStatus="OFF"){
          digitalWrite(IOStop,HIGH);
          printStatus();
          updateProc(0);
          nexttrig = 0;
          break;
        }else{
          updateProc(101);
          nexttrig = 0;
          break;
        }
       case 101:
          printStatus();
          nexttrig = 100;
          break;
       case 102:
          printStatus();
          nexttrig = 100;
          break;
    }
  }
  
}