// Simulation of a 4 stroke engine (injection and ignition)
// https://forum.arduino.cc/t/micros-counting-time-for-engine-simulation/1255668/13
// 2024-04-05 by noiasca
constexpr uint8_t throttlePin {A0};  // Analog Input
constexpr uint8_t cylinders = 3;
constexpr uint8_t ignitionPin[cylinders] {3, 5, 6};
constexpr uint8_t injectionPin[cylinders] {9, 10, 11};
// parameters from 0 .. 720 for a 4 stroke
constexpr int16_t injectionStart = 720 - 90;
constexpr int16_t injectionEnd = 720 - 45;
constexpr int16_t ignitionEnd = 720 - 10;
int16_t ignitionStart = ignitionEnd - 105; // dwell
constexpr uint16_t firingOffset[cylinders] = {0, 480, 240}; // can also be used for firing order or a Big-bang engine

uint16_t cycle = 0;     // counts from 0 to 720 for a 4 stroke

uint32_t lastToggleTimes[2][cylinders]; // on/off x cyls

void setup() {
  Serial.begin(115200);
  for (auto &i : ignitionPin) pinMode(i, OUTPUT);
  for (auto &i : injectionPin) pinMode(i, OUTPUT);
}

void loop() {
  static uint32_t previousMicros = 0;
  static uint16_t lastInterval = -1;
  uint32_t currentMicros = micros();
  uint16_t interval = map(analogRead(throttlePin), 0, 1023, 2, 720);
  if (interval != lastInterval) {
    lastInterval = interval;
    float rpm = 60.0 * 1000000 / (interval) / 720 * 2;
    Serial.print("Interval:");
    Serial.print(interval);
    Serial.print(" RPM:");
    Serial.print(rpm);
    Serial.print(" dwell_deg:");
    int dwellAngle = min(max(3000 / interval, 2), ignitionEnd - 2);
    Serial.print(dwellAngle);
    Serial.println();
    ignitionStart = ignitionEnd - dwellAngle;

  }
  if (currentMicros - previousMicros > interval) {
    previousMicros += interval;
    //if(currentMicros - previousMicros > interval)Serial.print('!');
    //  previousMicros = currentMicros;

    if (++cycle > 720) cycle = 0;
    for (int i = 0; i < cylinders; i++) {
      uint16_t thisCycle = (cycle + firingOffset[i]) % 720; // cylce angle for this cylinder
      if (thisCycle >= ignitionStart && thisCycle <= ignitionEnd) {
        if (digitalRead(ignitionPin[i]) == LOW) digitalWrite(ignitionPin[i], HIGH);
      }
      else if (digitalRead(ignitionPin[i]) == HIGH) digitalWrite(ignitionPin[i], LOW);
      if (thisCycle >= injectionStart && thisCycle <= injectionEnd) {
        if (digitalRead(injectionPin[i]) == LOW) digitalWrite(injectionPin[i], HIGH);
      }
      else if (digitalRead(injectionPin[i]) == HIGH) digitalWrite(injectionPin[i], LOW);
    }
  }
}