Trigger Wheel sim for DIY Car ignition system - appraisal required

Please excuse the newbie approach

I’m looking to use a mega 2560 as the basis of a car ignition system - the target vehicle is a 1972 Triumph Spitfire 1,300 cc - 4 cylinder engine

I’m intending to fit a standard 36-1 trigger wheel which is the type typically used by older Ford petrol cars
The ignition system will control the spark through a couple of MOSFET drivers and a standard Ford coil pack using wasted spark - a spark for each cylinder every revolution. In it’s simplest form, this will be a 2D system with no throttle pot or MAP sensor - just advance vs engine speed.

Step one has been to create an input signal simulator on a nano using timer1, largely based on a Nick Gammon tutorial (thank you Nick)

// analogue pot on anaglog pin0
// this represents routput of 1,000 to 8,000 RPM
#define ledPin 10
volatile int toothCount = 0;
volatile int missingTick = 0;

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
// initialize timer1
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 13333;            // compare match register 16MHz/256/2Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS10) | (0 << CS11) | (0 << CS12);    // 1 prescaler
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  if (toothCount <= 69) // 0 thru 69 is 70 toggles or 35 pulses per rev.
  {
    digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
    toothCount++;
  }
  else {
    missingTick++;  // 2 toggles - 1 missing pulse per revolution
    if (missingTick == 2) {
      toothCount = 0;
      missingTick = 0;
    }
  }
}

void loop()
{
  // your program here...
  OCR1A = map(analogRead(A0), 0, 1023, 1667, 13333);
  delay(250);
}

I would really appreciate any tips or corrections to the code as I’m a novice with this stuff

Thanks in advance

I’ve created a sketch which takes the output from the 36-1 trigger wheel simulator and detects the missing pulse and after a delay fires a 2ms pulse to the coil packs, one being 180 degrees after the first.

The position of the trigger wheel sensor is 90 degrees before TDC, to spark with an advance of 10 degrees BTDC, there needs to be a delay of 80 degrees before triggering the spark

I’ve used the mega as it has more that one 16 bit timer - one timer for cylinders 1&4 the other for cylinders 2&3. On the version shown below, I’ve not used a map table of advance vs RPM - that is my next step - I’ve just used a pot to enable me to adjust the timing and visualise the output on a scope

Again I’ve lifted most of the code from Nick Gammon and because I’ve modified it, I’d appreciate any input

// read squarewave on pin 2 and calc ignition timing
// 
#include <digitalWriteFast.h>
const byte interruptPin = 2;
const byte SPARKPLUG_1_4 = 10;
const byte SPARKPLUG_2_3 = 11;
// volatile byte state = LOW;
volatile unsigned long timeSinceMissingTooth = 0;
volatile unsigned long timeToFire = 0;
volatile unsigned long toothGap = 0;
volatile unsigned long lastTime = 0;
volatile unsigned long lastGap = 0 ;
volatile byte missingTooth = 0 ;
volatile float timePerDegree = 0;
byte signalToothOffSet = 90; // in degrees - the sensor being fitted 90 degrees before TDC
byte defaultAdvance = 10;    // in degrees
// float frequency = 0;
volatile unsigned int sparkDelayTime_1_4 = 10;   // microseconds
volatile unsigned int sparkDelayTime_2_3 = 10;   // microseconds

volatile unsigned int sparkOnTime = 2000;     // microseconds

// allow for time taken to enter ISR (determine empirically)
const unsigned int isrDelayFactor = 200;        // microseconds

// is spark currently on?
volatile boolean sparkOn_1_4;
volatile boolean sparkOn_2_3;
void setup()
{
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), ISR_missing_Tooth, FALLING);
  noInterrupts();           // disable all interrupts 
  TCCR1A = 0;  //  Timer 1 normal mode
  TCCR1B = 0;  //  Timer 1 stop timer
  TIMSK1 = 0;  //  Timer 1 cancel timer interrupt

  TCCR3A = 0;  //  Timer 3 normal mode
  TCCR3B = 0;  //  Timer 3 stop timer
  TIMSK3 = 0;  //  Timer 3 cancel timer interrupt
  
  interrupts();           // enable all interrupts
  pinMode (SPARKPLUG_1_4, OUTPUT);
  pinMode (SPARKPLUG_2_3, OUTPUT);  
}

void loop()
{
   defaultAdvance = map(analogRead(A0), 0, 1023, 0, 45);
  if (missingTooth = 1) {
    // if timesincemissing tooth eq or greater than ignition and off set
    // fire sparkplug
     timeToFire = (signalToothOffSet - defaultAdvance) * timePerDegree;
    if (timeSinceMissingTooth >= timeToFire) {
      timeSinceMissingTooth = 0;      
      // fire 1,4 sparkplugs
     sparkDelayTime_1_4 = timeToFire;
     timeToFire = (signalToothOffSet - defaultAdvance + 180) * timePerDegree;
     sparkDelayTime_2_3 = timeToFire;     
     fireSparkPlugs_1_4(); // fire now on cylinders 1 and 4   
     fireSparkPlugs_2_3(); // fire now on cylinders 2 and 3  
    }
    timePerDegree = lastGap / 10; // lastGap period covers 10 degrees
//    frequency = 1/((timePerDegree / 1000000) * 360 ) * 60;   /// need to rename this RPM
    missingTooth = 0 ;
  }
}


void ISR_missing_Tooth() {

  toothGap = micros() - lastTime;
  if (toothGap > 1.8 * lastGap) {
  // missing tooth detected 
    timeSinceMissingTooth = micros(); 
    missingTooth = 1;
    }
  lastGap = toothGap;
  lastTime = micros();
}

void fireSparkPlugs_1_4() // pass parameter of offset time
 {
  sparkOn_1_4 = false;                  // make sure flag off just in case
  // set up Timer 1
  noInterrupts(); 
  TCCR1A = 0;                       // normal mode
  TCNT1 = 0;                        // count back to zero
  TCCR1B = bit(WGM12) | bit(CS11);  // CTC, scale to clock / 8
  // time before timer fires - zero relative
  // multiply by two because we are on a prescaler of 8
  OCR1A = (sparkDelayTime_1_4 * 2) - (isrDelayFactor * 2) - 1; 
  TIMSK1 = bit (OCIE1A);            // interrupt on Compare A Match
  interrupts(); 
  } 

  // interrupt for when time to turn spark on then off again
ISR (TIMER1_COMPA_vect)
  {
  // if currently on, turn off
  if (sparkOn_1_4)
    {
    digitalWriteFast (SPARKPLUG_1_4, LOW);  // spark off
    TCCR1B = 0;                         // stop timer
    TIMSK1 = 0;                         // cancel timer interrupt
    }
  else
    // hold-off time must be up
    {
    digitalWriteFast (SPARKPLUG_1_4, HIGH); // spark on
    TCCR1B = 0;                         // stop timer
    TCNT1 = 0;                          // count back to zero
    TCCR1B = bit(WGM12) | bit(CS11);    // CTC, scale to clock / 8
    // time before timer fires (zero relative)
    // multiply by two because we are on a prescaler of 8
    OCR1A = (sparkOnTime * 2) - (isrDelayFactor * 2) - 1;     
    }
    sparkOn_1_4 = !sparkOn_1_4;                  // toggle
  }  // end of TIMER1_COMPA_vect
//////////////////////////////////////////////////////////////////////////////////

  void fireSparkPlugs_2_3() // pass parameter of offset time
 {
 
  sparkOn_2_3 = false;                  // make sure flag off just in case
  // set up Timer 3
  noInterrupts(); 
  TCCR3A = 0;                       // normal mode
  TCNT3 = 0;                        // count back to zero
  TCCR3B = bit(WGM32) | bit(CS31);  // CTC, scale to clock / 8
  // time before timer fires - zero relative
  // multiply by two because we are on a prescaler of 8
  OCR3A = (sparkDelayTime_2_3 * 2) - (isrDelayFactor * 2) - 1; 
  TIMSK3 = bit (OCIE3A);            // interrupt on Compare A Match
  interrupts(); 
  } 

  // interrupt for when time to turn spark on then off again
ISR (TIMER3_COMPA_vect)
  {
  // if currently on, turn off
  if (sparkOn_2_3)
    {
    digitalWriteFast (SPARKPLUG_2_3, LOW);  // spark off
    TCCR3B = 0;                         // stop timer
    TIMSK3 = 0;                         // cancel timer interrupt
    }
  else
    // hold-off time must be up
    {
    digitalWriteFast (SPARKPLUG_2_3, HIGH); // spark on
    TCCR3B = 0;                         // stop timer
    TCNT3 = 0;                          // count back to zero
    TCCR3B = bit(WGM32) | bit(CS31);    // CTC, scale to clock / 8
    // time before timer fires (zero relative)
    // multiply by two because we are on a prescaler of 8
    OCR3A = (sparkOnTime * 2) - (isrDelayFactor * 2) - 1;     
    }
    sparkOn_2_3 = !sparkOn_2_3;                  // toggle
  }  // end of TIMER3_COMPA_vect

I’ve seen comments that an Arduino is not up to the job of ignition control but as this engine was designed for clockwork ignition, it doesn’t need or respond to timing to the fractions of a degree

Once running (or not) I’ll keep you updated on my progress

Thanks in advance