Electronic ignition system EIS feedback

Hello.
I am building an electronic ignition system for my VW aircooled beetle engine with carburator. I have tried it on my wasted spark coilpack and it seems to work. Read my attached report. Could anyone please take a look at my code and give me some feedback on improvements and ideas to make it more realiable?
Edit: The last uppdated code is further down in this thread!

The breadboard demonstration is here:- YouTube

#include <Avr/interrupt.h>
#include <Avr/io.h>

//Pin definitions:
const byte HallPin1 = 2; // Pin nr 2 is conected to Hall sensor DO
const byte HallPin2 = 3; // Pin nr 3 is also conected to Hall sensor DO
const int Ign1Pin = 8;  // (PORTB,0)
const int Ign2Pin = 11; // (PORTB,3)

volatile byte milliseconds;              // The millisecondcounter value.
volatile unsigned int cranktime;         // Time from 3 degrees to where the coil should start to load.
volatile unsigned int cranktimeStart;    // Variable to measure real cranktime.
volatile unsigned int cranktimeStop;     // Variable to measure real cranktime.
volatile unsigned int realcranktime;     // Measured real cranktime.
volatile byte IgnSystem;                 // Statusword for active ign system.
volatile byte dwellTime;                 // The time the coil should charge (default is 0,004 seconds.

// RPM variables:
volatile byte half_revolutions;
volatile unsigned int rpm;
volatile unsigned int timeold;

/************************************/
void setup() {
  
  dwellTime = 3;               // Runningdwell: 3ms. (Crankingdwell: 4ms.)
  half_revolutions = 0;        
  rpm = 0;                     
  timeold = 0;                
  IgnSystem = 0;               // No ignition system is active.

  pinMode(Ign1Pin, OUTPUT);    // Initialize the Ignition 1 pin as an output.
  pinMode(Ign2Pin, OUTPUT);    // Initialize the Ignition 2 pin as an output.
  bitClear(PORTB, 3);          // (digitalWrite(Ign2Pin, LOW);)  Turn the ignition off in case it's on
  bitClear(PORTB, 0);          // (digitalWrite(Ign2Pin, LOW);)  Turn the ignition off in case it's

  attachInterrupt(digitalPinToInterrupt(HallPin1), SensorOn, RISING);   //Hall sensor DO for Ignition 1
  attachInterrupt(digitalPinToInterrupt(HallPin2), SensorOff, FALLING); //Hall sensor DO for Ignition 2

  /*************** Setup timer2******************/
  noInterrupts();
  TCCR2A = 0;               // Turn off Control register for waveform generation
  TCCR2B = 0;               // Turn off noise cancelling, turn off edge select, waveform gen mode 0, no clock source
  TCCR2A |= (1 << WGM21);   // Turn on CTC mode (so it will start again) automatically
  TIMSK2 |= (1 << OCIE2A);  // Set interrupt on compare match.
  OCR2A = 249;              // ((0, 001 / 0.0000000625) / 64 ) - 1; //(0,001s=249)
  TCNT2  = 0;               // Reset timer counter to 0
  milliseconds = 0;         // Reset the ms counter variable.
  interrupts();
}
//===============
/* The interrupt action for magnet 1: The Timer starts to count up 1 ms at a time.
***************/
void SensorOn () {

  if ((rpm < 300) & (IgnSystem == 0)) {   // When cranking (starting), the coil nr 2 will charge at once.
    bitSet(PORTB, 3); //Turn on coil 1 charging immediately, Cranking dwell will then be 1ms longer than normal.
    IgnSystem = 2;    // Statusword to know witch system is to be hot.
  }
  if ((rpm >= 300) & (IgnSystem == 2) ) {           // When running (not starting), the coil nr 1 will charge after cranktime in the ISR below.
    IgnSystem = 1;             //Statusword to know witch system is to be hot.
  }

  TCCR2B |= (1 << CS22);     // Load 64 prescaler / And this starts the timer2!
  cranktimeStart = micros(); // For monitoring.

  /********* RPM calculations: **********/
  half_revolutions++;        //For the RPM calculations
  if (half_revolutions >= 2) {
    rpm = 60 * 1000000 / (micros() - timeold) * half_revolutions;
    timeold = micros();
    half_revolutions = 0;
    realcranktime = (cranktimeStop - cranktimeStart);
  }
}
/*================
 The interrupt action for magnet 2: The Timer starts to count up 1 ms at a time.
********************/
void SensorOff () {

  if ((rpm < 300) & (IgnSystem == 0))  {     // When cranking (starting), the coil nr 1 will charge at once.
    bitSet(PORTB, 0);  //Turn on coil 2 charging immediately,  Cranking dwell will then be 1ms longer than normal.
    IgnSystem = 1;     //Statusword to know witch system is to be hot.
  }
  if ((rpm >= 300) & (IgnSystem == 1) ) {            // When running (not starting), the coil nr 2 will charge after cranktime in the ISR below.
    IgnSystem = 2;             //Statusword to know witch system is to be hot.
  }
  TCCR2B |= (1 << CS22);     // Load 64 prescaler / And this starts the timer2!
  cranktimeStart = micros(); //For diagnostics
  
   /********* RPM calculations: **********/
  half_revolutions++;        //For the RPM calculations
  if (half_revolutions >= 2) {
    rpm = 60 * 1000000 / (micros() - timeold) * half_revolutions;
    timeold = micros();
    half_revolutions = 0;
    realcranktime = (cranktimeStop - cranktimeStart);
  }
}
/*====================
 The Interrupt Service Routine for Timer2 that will be executed each time the timer reach the compare match register (1ms).
*********Milliseconds***********/

ISR(TIMER2_COMPA_vect) {

  /*********Counting milliseconds******************/
  milliseconds++;  // Increases the variable "milliseconds" by 1 at each ms (every time the ISR is executed).

  /************ coil charging**********/
  if (milliseconds >= cranktime) {  //When the timer reaches the cranktime, then do this:

    if (IgnSystem == 1) {  //If ignitionsystem 1 is selected and not on, then:
      bitSet(PORTB, 0);                                   //Turn on coil 1 charging.
    } if (IgnSystem == 2) { //If ignitionsystem 2 is selected and not on, then:
      bitSet(PORTB, 3);                                      //Turn on coil 2 charging.
    }
    cranktimeStop = micros();  //Stop recording the crank time
  }
  /***********Discharge coilspark********/
  //If the milliseconds has reached the cranktime and dwelltime, then:
  if (milliseconds >= (cranktime + dwellTime)) {
    milliseconds = 0;           // reset the ms counter variable
    //Turn off timer.
    TCCR2B  &= ~ (1 << CS22);   //clear the prescaler .
    TCNT2 = 0;                  // Reset the timer count to 0

    bitClear(PORTB, 0); //Stop charging coil 1. (Gives spark)
    bitClear(PORTB, 3); //Stop charging coil 2. (Gives spark)
    }
    
    // If the engine has stopped or are still cranking, the IgnSystem is set to starting advance degrees.
    if (rpm < 300) {
      IgnSystem = 0;
    }
  }

/****************************************************/
void loop() {

  //Calculate the cranktime dependent on the rpm. (cranktime= advance time - dwellTime)
  /********Cranktime calculations ***
       RPM  Advance        Degrees             Cranktime     Time change
            -degrees BTDC  from Sensor                        for each RPM (ms)
       300    -3           180                 100              -100
       400     9           171                 71,25            -0,2875
       600     8           172                 47,77777778      -0,117361111
       1000    7           173                 28,83333333      -0,047361111
       1700    17          163                 15,98039216      -0,018361345
       2800    26          154                 9,166666667      -0,006194296
       3100    28          152                 8,172043011      -0,003315412
       4000    29          151                 6,291666667      -0,002089307
       4100    0           180                 7,317073171       0,010254065
  */
  if (rpm <= 300) {
    cranktime = 0;
  } if ((rpm > 300) && (rpm <= 400)) {
    cranktime = (100 - (rpm - 300) * 0.288) - dwellTime;
  } if ((rpm > 400) && (rpm <= 600)) {
    cranktime = (71.25 - (rpm - 400) * 0.117) - dwellTime;
  } if ((rpm > 600) && (rpm <= 1000)) {
    cranktime = (47.778 - (rpm - 600) * 0.047) - dwellTime;
  } if ((rpm > 1000) && (rpm <= 1700)) {
    cranktime = (28.833 - (rpm - 1000) * 0.018) - dwellTime;
  } if ((rpm > 1700) && (rpm <= 2800)) {
    cranktime = (15.980 - (rpm - 1700) * 0.006) - dwellTime;
  } if ((rpm > 2800) && (rpm <= 3100)) {
    cranktime = (9.167 - (rpm - 2800) * 0.003) - dwellTime;
  } if ((rpm > 3100) && (rpm <= 4000)) {
    cranktime = (8.172 - (rpm - 3100) * 0.002) - dwellTime;
  }
}

Anders Arduino EIS 151204.pdf (599 KB)

Arduino_EIS_151202.ino (9.35 KB)

volatile float milliseconds;      // The millisecondcounter value.

Nowhere in your code is there any need for milliseconds to be a float.

Serial IO has no place in an interrupt service routine.

Ok. Thank you PaulS! I will change it to Integer.
Does the Serial communication slow down the code alot? I would like to be able to run this in Arduino-Oscilloscope-poor-mans-Oscilloscope if it is possible.

Does the Serial communication slow down the code alot?

Maybe not. But, if the outgoing serial buffer ever gets full, Serial.print() will block, waiting for room to be made in the buffer. Room is made in the buffer by interrupts shifting data out. Those interrupts don't happen while your interrupt service routine is running, so, once the buffer gets full, the Arduino will simply lock up.

By "working" you mean it makes a spark? What maximum RPM do you expect this system to support? Multiply by the number of cylinders and work out how much time you have to do all that calculation for each spark.

volatile int IgnSystem; // Statusword for active ign system.

You know an int is two bytes? Every time that your interrupt functions try to read this variable, two bytes have to be loaded from memory and every time you write, two bytes have to be saved. How many ignition systems do you expect this will support in the future? If it's less than 255, make this a byte variable.

Not to mention that it should be declared as volatile although it's only accessed from the three interrupt functions so maybe the compiler will do the right thing.

[multiple float variables]

Floating point calculations on any Arduino are S. L. O. W. Run the program, work out where you need it to be fastest (the 3ms dwell period would be my guess) and see if it is meeting that deadline. If not, you need to re-think and change some of those variables to integers so that your calculations use integer maths. Also, integer divisions are quite slow, so if there's a way to replace a divide operation with a multiplication, that will make a big difference.

I won't say that you must remove all floats. They are useful. Just do the profiling and work out what the deadlines are to fire the sparks before randomly trying to improve stuff.

Thank you Paul for teaching me this! I will get rid of the serial prints. I don't need them in the final code anyway.

Morgan, thank you also so much, I have no one else to help me. I have only programmed PLC before and I have been teaching my self arduino for 5 months now, still have a lot to learn.

Yes it does gives spark, and it seems to give it at the right time/angle. But I don't exactly know how to measure the timing. My calculations shows that there is time enough for everything. Max rpm is 3500 for this system, but I believe it would be possible go up to 6000rpm also.

I will change to Byte where it is possible, thank you for straighting me out :slight_smile:

There is 4 cylinders, but I use an wasted spark system that fires two cyllinder at the same time (one cylinder is on exaust phase). That's why there is only 2 coils. So that gives me minimum 8 ms total. So after subtractING the dwell time of 3ms I have 5 ms left for calculations. Exept for cranking where the ignition fires as soon as a magnet pass and the calculations are done (about 3° ATDC).

I will go through the code and see where I can use integers instead of float. This system is not supposed to be super accurate, only reliable and still better than the 009 distributor .

Does anyone have any tips on how to run and test the program to see if the spark occurs att the right time? I don't have an old engine yet to test it on.

Thanks again! Please read my system description also.

I have made some changes in the original code in the first post according to the advices from Paul and Morgan. I hope I did understand you correctly. There is no errors, and my simulation board seems to function correctly exept that I still don`t know for sure if the ignition timing is correct (because of internal delays or miscalculations/fuckups).

Here is a picture of the system. It still needs to be shielded properly. The ignition leads give high EMP and disturbed the arduino. I will put it in a metal box.

Hey Anders,

Got your message and had a quick look over the code. A few quick thoughts...

If I'm reading your timer config correctly, the ISR is firing every 1mS, making your timing accuracy 0.001s (1ms). If that is right then, and I hate to say this, your timing is going to be really quite bad. Even at your relatively low target speed of 3500, 1 crank degree takes about 47uS, so your 1ms (1000uS) timing check is only accurate to about 21 degrees. Having your spark 21 degrees out is going to make for a really poor running engine.
Your prescaler of 64 gives 4uS per tick and really you want accuracy to at least 32uS (1 degree at ~5100rpm) so you need to be setting your timer compare to be 8 (4uS * 8 = 32) instead of ~250. That said...

The only way you will get any benefit from this is if you change you crank timing from mS to uS. Unfortunately, accuracy to 1mS just isn't good enough for an ignition system, you'll need to set your target cranking times in uS to get any chance of a smooth running engine from this.

Also, really, really try to get rid of your floats. From what I can see there's no need for them at all. Eg: With a little reshuffling

cranktime = (71.25 - (rpm - 400) * 0.117) - dwellTime;

can be rewritten as

cranktime = 118 - (rpm * 10 / 86) - dwellTime

With very little loss of precision.
You don't have any benchmarking code in there to see how fast your code is running, but I can promise you that you are losing a huge amount of time with just those few floats that you have.

Hope that helps a little, and sorry to put a downer on things.

Thank you Noisymime so much! This is exactly what kind of feedback I am looking for! It has been so frustrating to not get any help from nowhere.
I guess I totalt miscalculated the hole time with milliseconds instead of microseconds. .. And my math is not the best so it is great to get rid of the floats. I will get into this and come back to you. Again, thank you so much!

Hi again!
I have now made the changes that you Noisymime suggested! (What is your name anyway?) Could you have a look at it and see if I got it right?

The changes I made is this:

volatile byte microseconds;              // The microsecondcounter value.
 OCR2A = 8; // Prescaler of 64 gives 4uS per tick, 4uS*8=32uS (32uS=1degree at ~5100rpm).
ISR(TIMER2_COMPA_vect) {

  /*********Counting microseconds*****************/
 // Increases the variable "microseconds" by 32 every time the ISR is executed).
  microseconds = microseconds + 32;
/************ coil charging*****************************/
  if (microseconds >= cranktime) {  //When the timer reaches the cranktime, then do this:
/***********Discharge coilspark*******************************************/
  //If the microseconds has reached the cranktime and dwelltime, then:
  if (microseconds >= (cranktime + dwellTime)) {
    microseconds = 0;           // reset the ms counter variabl

I changed the milliseconds to microseconds and roundet them to get rid of floats. And then simplyfied the formula so there will be less calculations to do. I hope this is accurate enough?

void loop() {

  //Calculate the cranktime dependent on the rpm. (cranktime= advance time - dwellTime)
  /********************Cranktime calculations ************
       RPM  Advance        Degrees             Cranktime     Time change
            -degrees BTDC  from Sensor           (uS)        for each RPM (uS)
       300    -3            180                100000           -100000
       400     9            171                71250            -288
       600     8            172                47778            -117
       1000    7            173                28833            -47
       1700    17           163                15980            -18
       2800    26           154                9167             -6
       3100    28           152                8172             -3
       4000    29           151                6292             -2
       
  */
  if (rpm <= 300) {
    cranktime = 0;
  } if ((rpm > 300) && (rpm <= 400)) {
    cranktime = (186400 - (rpm * 288)) - dwellTime;  // = (cranktime - (rpm - 300) * Time change for each RPM) - dwellTime;
  } if ((rpm > 400) && (rpm <= 600)) {
     cranktime = (118050 - (rpm * 117)) - dwellTime; // = (71250 - (rpm - 400) * 117) - dwellTime;
  } if ((rpm > 600) && (rpm <= 1000)) {
    cranktime = (75978 - (rpm * 47)) - dwellTime;  
  } if ((rpm > 1000) && (rpm <= 1700)) {
    cranktime = (46833 - (rpm * 18)) - dwellTime;
  } if ((rpm > 1700) && (rpm <= 2800)) {
    cranktime = (26180 - (rpm * 6)) - dwellTime;
  } if ((rpm > 2800) && (rpm <= 3100)) {
    cranktime = (10007 - (rpm * 3)) - dwellTime;
  } if ((rpm > 3100) && (rpm <= 4000)) {
    cranktime = (8732 - (rpm * 2)) - dwellTime;

The new code is in attachments.

I would really appreciate if you could help me further on this.

Arduino_EIS_160110.ino (7.53 KB)

By making microseconds a byte, you are declaring that you will never want to count more than 255 microseconds. I suspect your design needs longer periods than that. You may even need to use a long integer to count more than 65536 microseconds (65 milliseconds.)

That's why there are many types of variables available to you. Use the most appropriate size for the data you are going to put in it.

Thank you MorganS for notising this! I need to be able to count to 100 000 microseconds, so I will use an long Int like this:
volatile long int microseconds;              // The microsecondcounter value.

I would like the long int variable "microseconds" to start at -68. Do I need to put an L somewere?
I read this: "If doing math with integers, at least one of the numbers must be followed by an L, forcing it to be a long. "

 microseconds = 0;         // Reset the ms counter variable.
microseconds = microseconds - 68;      //Cranking dwell will then be 1ms longer than normal.
 microseconds = microseconds + 32;  // Increases the variable "microseconds" by 32 every time the ISR is executed).

If you are using the Arduino's micros() and millis() counters then you should be using unsigned long integers. The math just works out that way. Read http://www.gammon.com.au/millis to find out why.

But the maths is actually so friendly that your proposed solution of storing -68 will actually work. As a signed long integer, this is represented as 1111 1111 1111 1111 1111 1111 1011 1100, which can be represented in decimal as 4294967228 (unsigned.)

So by storing a small negative number in an unsigned integer, you wrap around the zero into the very large integers. This wraparound is exactly the stuff that Nick's discussion linked above talks about.

Or maybe you save yourself a whole lot of bits by using a "32ms" counter that counts up by 1 each 32ms. Then you need to be careful that you get the signed and unsigned arithmetic correct.

Thanks Morgan! I have studied the math some now and decided to go an other route. I wont be messing with the microsecounds because I found an easier way of getting what I wanted. Here is the updated code:

#include <Avr/interrupt.h>
#include <Avr/io.h>

//Pin definitions:
const byte HallPin1 = 2; // Pin nr 2  to Hall sensor
const byte HallPin2 = 3; // Pin nr 3 is also to Hall sensor
const int Ign1Pin = 8;  // (PORTB,0)
const int Ign2Pin = 11; // (PORTB,3)

volatile long int microseconds;          // The microsecondcounter value.
volatile unsigned int cranktime;         // Time from 3 degrees to where the coil should start to load.
volatile byte IgnSystem;                 // Statusword for active ign system.
volatile byte dwellTime;                 // The time the coil should charge (default is 0,004 seconds.

// RPM variables:
volatile byte half_revolutions;
volatile unsigned int rpm;
volatile unsigned int timeold;

/***********************************************************************/
void setup() {
  
  crankingDwellTime = 4000; //in uS
  runningDwelltime = 3000;  //in uS
  half_revolutions = 0;        
  rpm = 0;                     
  timeold = 0;                
  IgnSystem = 0;               // No ignition system is active.

  pinMode(Ign1Pin, OUTPUT);    // Initialize the Ignition 1 pin as an output.
  pinMode(Ign2Pin, OUTPUT);    // -"-
  digitalWrite(Ign2Pin, LOW);  // bitClear(PORTB, 3); Turn the ignition off in case it's on
  digitalWrite(Ign1Pin, LOW);  // bitClear(PORTB, 0);          //  -"-

  attachInterrupt(digitalPinToInterrupt(HallPin1), SensorOn, RISING);   //Hall sensor DI for Ignition 1
  attachInterrupt(digitalPinToInterrupt(HallPin2), SensorOff, FALLING); //-"- 2

  /********** Setup timer2*************/
  noInterrupts();
  TCCR2A = 0;               // Turn off Control register for waveform generation
  TCCR2B = 0;               // Turn off noise cancelling, turn off edge select, waveform gen mode 0, no clock source
  TCCR2A |= (1 << WGM21);   // Turn on CTC mode (so it will start again) automatically
  TIMSK2 |= (1 << OCIE2A);  // Set interrupt on compare match.
  OCR2A = 8;                // Prescaler of 64 gives 4uS per tick, 4uS * 8 = 32uS (32uS = 1 degree at ~5100rpm).
  TCNT2  = 0;               // Reset timer counter to 0
  microseconds = 0;         // Reset the ms counter variable.
  interrupts();
}
//========================================================================
/* The interrupt action for magnet 1: The Timer starts to count up 32 uS at a time.
**********************/
void SensorOn () {

  if ((rpm < 300) & (IgnSystem == 0)) {     // When cranking (starting), the coil nr 2 will charge at once.
    digitalWrite(Ign2Pin, HIGH);            // bitSet(PORTB, 3); (Turn on coil 2 charging immediately.)
    dwelltime = crankingDwelltime;          //Setting the dwelltime for cranking.
    IgnSystem = 2;                          // Statusword to know witch system is to be hot.
  }
  if ((rpm >= 300) & (IgnSystem == 2) ) {  // When running (not starting), the coil nr 1 will charge after cranktime in the ISR below.
    dwelltime = runningDwelltime;          //setting the dwelltime for running  
    IgnSystem = 1;                         //Statusword to know witch system is to be hot.
  }
  TCCR2B |= (1 << CS22);                   // Load 64 prescaler / And this starts the timer2!

  /********* RPM calculations: **********/
  half_revolutions++;                     
  if (half_revolutions >= 2) {
    rpm = 60 * 1000000 / (micros() - timeold) * half_revolutions;
    timeold = micros();
    half_revolutions = 0;
  }
}
/*========================================================================
 The interrupt action for magnet 2: The Timer starts to count up 32uS at a time.
********************************/
void SensorOff () {

  if ((rpm < 300) & (IgnSystem == 0))  {    // When cranking (starting), the coil nr 1 will charge at once.
    digitalWrite(Ign1Pin, HIGH);            // bitSet(PORTB, 0); (Turn on coil 1 charging immediately,)
   dwelltime = crankingDwelltime;           //setting the dwelltime for cranking  
    IgnSystem = 1;                          //Statusword to know witch system is to be hot.
    }
  if ((rpm >= 300) & (IgnSystem == 1) ) {   // When running (not starting), the coil nr 2 will charge after cranktime in the ISR below.
     dwelltime = runningDwelltime;          //setting the dwelltime for running   
    IgnSystem = 2;                          //Statusword to know witch system is to be hot.
  }
  TCCR2B |= (1 << CS22);                    // Load 64 prescaler / And this starts the timer2!
  
/********* RPM calculations: **********/
  half_revolutions++;       
  if (half_revolutions >= 2) {
    rpm = 60 * 1000000 / (micros() - timeold) * half_revolutions;
    timeold = micros();
    half_revolutions = 0;
  }
}
/*=============================================================================
 The Interrupt Service Routine for Timer2 that will be executed each time the timer reach the compare match register (1ms).
*****************Milliseconds****************/

ISR(TIMER2_COMPA_vect) {

  /*********Counting microseconds*****************/
  microseconds = microseconds + 32;  // Increases the variable "microseconds" by 32 every time the ISR is executed).

  /************ coil charging*****************************/
  if (microseconds >= cranktime) {  //When the microseconds reaches the cranktime, then do this:

    if (IgnSystem == 1) {   //If ignitionsystem 1 is selected and not on, then:
       digitalWrite(Ign1Pin, HIGH);  //bitSet(PORTB, 0);  (Turn on coil 1 charging.)
    } if (IgnSystem == 2) { // -"-
        digitalWrite(Ign2Pin, HIGH);  //bitSet(PORTB, 3); (Coil 2 charging.)
    }
  }
  /***********Discharge coilspark*******************************************/
  //When the microseconds has reached the cranktime and dwelltime, then:
  if (microseconds >= (cranktime + dwellTime)) {
    microseconds = 0;           // reset the uS counter variable
    //Turn off timer.
    TCCR2B  &= ~ (1 << CS22);   //clear the prescaler .
    TCNT2 = 0;                  // Reset the timer count to 0

    digitalWrite(Ign1Pin, LOW);  //bitClear(PORTB, 0); ( Stop charging coil 1. (Gives spark))
    digitalWrite(Ign2Pin, LOW);  //bitClear(PORTB, 3); (-"-)
    }
    
    // If the engine has stopped or are still cranking, the IgnSystem is set to starting advance degrees.
    if (rpm < 300) {
      IgnSystem = 0;
    }
  }

/***********************************************************/
void loop() {

  //Calculate the cranktime dependent on the rpm. (cranktime= advance time - dwellTime)
  /********************Cranktime calculations ************
       RPM  Advance        Degrees             Cranktime     Time change
            -degrees BTDC  from Sensor           (uS)        for each RPM (uS)
       300    -3            180                100000           -100000
       400     9            171                71250            -288
       600     8            172                47778            -117
       1000    7            173                28833            -47
       1700    17           163                15980            -18
       2800    26           154                9167             -6
       3100    28           152                8172             -3
       4000    29           151                6292             -2
       
  */
  if (rpm <= 300) {
    cranktime = 0;
  } if ((rpm > 300) && (rpm <= 400)) {
    cranktime = (186400 - (rpm * 288)) - dwellTime;  // = (cranktime - (rpm - 300) * Time change for each RPM) - dwellTime;
  } if ((rpm > 400) && (rpm <= 600)) {
     cranktime = (118050 - (rpm * 117)) - dwellTime; // = (71250 - (rpm - 400) * 117) - dwellTime;
  } if ((rpm > 600) && (rpm <= 1000)) {
    cranktime = (75978 - (rpm * 47)) - dwellTime;  
  } if ((rpm > 1000) && (rpm <= 1700)) {
    cranktime = (46833 - (rpm * 18)) - dwellTime;
  } if ((rpm > 1700) && (rpm <= 2800)) {
    cranktime = (26180 - (rpm * 6)) - dwellTime;
  } if ((rpm > 2800) && (rpm <= 3100)) {
    cranktime = (10007 - (rpm * 3)) - dwellTime;
  } if ((rpm > 3100) && (rpm <= 4000)) {
    cranktime = (8732 - (rpm * 2)) - dwellTime;
  }
}[code]

[/code]

volatile long int microseconds; // The microsecondcounter value.
volatile unsigned int cranktime; // Time from 3 degrees to where the coil should start to load.

This looks like you're going to be doing lots of signed-to-unsigned conversions in the future. Pick one and stay with it.
Read Gammon Forum : Electronics : Microprocessors : Timers and counters again to understand why it "just works" when you use only one type of variable.

if (microseconds >= cranktime) { //When the microseconds reaches the cranktime, then do this:

This is the kind of comparison that Nick argues against. Since you're in complete control over microseconds, it is less important to use the subtraction method. This may cause problems if you start cranking more than 32.767 seconds after turning the Arduino on.

But what is microseconds? Is it the time since TDC? Is it the time since the sensor measured some other angle of the crank? Why does it get reset to zero when the spark is started? Then you've just lost all that information about where the crank is right now.

I'm coming into this a little late.
Why do you convert crank time into RPM?
Figuring advance as a percentage is easier if you just leave
the value as time and not the inverse of time.
RPM is for people to read on a dial, not for engine control.
Dwight

I will do so, I will read and try to learn. Great to get advice from someone like you! I will change it so that both are long int. I have only programmed PLC before (Simatic, Mitsu and Omron) and I guess I didn`t learn all the basic arithmetic and digital technics that I should know. I am very happy to learn this, because it gives me a lot more overall understanding in automation.

I will try to explain the "microseconds". Maybe it is a bad name for it? I will try to make it extra clear just in case others are listening (I don`t believe you are stupid dough).
First of all there are two magnets on the flywheel, one for each ignition system (coil). One magnet is The flywheel turns 360 degrees and zero degrees for magnet north is when piston 1 and piston 4 are at top dead center, TDC, (piston 2 and 3 are then at bottom dead center, BDC) and vice versa for magnet south. Magnet north gives an rising interrupt trigger and south an falling interrupt trigger. The magnets are mounted on 3 degrees and 183 degrees after TDC on the flywheel.
When the engine is running, one magnet trigger the hall sensor and the code calculate the current rpm and the timer starts counting microseconds. The cranktime is calculated depending on the rpm, so if the engine is running at 2800 rpm, then I want the spark ignite at 26 degrees before next TDC, that is 154 degrees after the magnet has triggered the hall sensor, and that takes 9167uS for the crank to travel from 3 degrees. So when the timer has reached 9167uS, I start charging the coil. Now I need the timer to keep counting until the coil has charged for 3ms (dwell time), after that I reset the timer and wait for the nest magnet to come and trigger the other system to do the same ting. During the time between the interrupts, the loop is working on the rpm calculations.

So that was running mode, the N-magnet trigger the ignsystem1 and fires cylinder 1&4. When hand propping/cranking, I want the ignition to give spark at 3 degrees at the first pull. I don’t want to have to swing the prop a half turn before it will ignite. But when N-magnet trigger the hall sensor, the cylinder 2&3 are just after TDC so if I want to start the engine now, I need to use ignSystem2 instead of ignsys1! So when the rpm is below 300, the cranktime calculations gives 0 so the coil starts charging right away and after 4 ms (not 3ms) the coil will discharge and ignite the sparkplug. At that low speed, 4ms is not much and I think it will be just fine.

So the "microseconds" is another word for the time since the last sensor trigger. I could call it sensorTime or something like that...

Sorry for my over explaining, I guess I took the opportunity to write it down for my system description.

dwightthinker:
I'm coming into this a little late.
Why do you convert crank time into RPM?
Figuring advance as a percentage is easier if you just leave
the value as time and not the inverse of time.
RPM is for people to read on a dial, not for engine control.
Dwight

Hi Dwight. Thank you for helping :slight_smile:
I guess I just do whatever that comes to my mind and if it works, I go for it :slight_smile:
In my head I am using rpm to calculate time it takes to turn the crankshaft one degree.
I am sorry but I don`t quite understand what you are telling me about Figuring advance as a percentage and to leave the value as time and not the inverse of time. Could you explain some more detailed? I am a rookie at this, an dmy english is not the best.

I like to use numbers i feel confortable with when I am learning new stuff. Are you an engine engineer?