Programmable Ignition

Well I'll start by saying Im totally new to arduino. Still havnt got it in the mail yet, so while i was waiting I thought I would start writing a program.

Not that i have a old car to put it in, but one day i will. Assuming its going to be a Holden 308. so the idea is to replace the dizzy with one off a VN as ive seen on another website. that way its just a hall effect sensor, with no mecanical or vaccume advance. Timing advance would be done by the program. (havnt got that far yet anyway). Another idea is to ditch the dizzy all together and run seperate coils for each spark plug. that would also mean running a crank sensor which would require either another sensor input to indicate when cylinder 1 reaches TDC, or an extra pin. Im thinking id go for running with a dizzy first. (but i put the pulsetime variables in with the idea of using a crank sensor one day.)

so this is definetly a work in progress. If ive done something obviously wrong or inefficent, please let me know! :slight_smile:

int InPin = 3; //Pin connected to trigger from hall effect sensor
int OutPin = 12; //Pin conected to coil driving circuit
volatile unsigned long pulsetime1; //Time delay of latest pulse
volatile unsigned long pulsetime2; // time delay of pulse before that
volatile unsigned long pulsetime3; // time delay of pulse before that
volatile unsigned long pulsetime4; // time delay of pulse before that
volatile unsigned long halltimenew;
volatile unsigned long halltimeold;
int rpm;
int cylinders = 8; //number of cylynder of engine
long rpmcalc;
unsigned long advance = 14; // this is the advance time.  will be calculated eventually from rpm/map etc 
unsigned long firetime;     // the time to fire next spark compared to micro()
int dwell = 250; // dwell time in uS


void setup()
{
  rpmcalc = 180000000/cylinders; // put this hear to save doing it every loop. shortened this: 10000000/(cylinders/2)*60 to go from uS to rpm. also * 15 as part of the wma calculation (8+4+2+1)
  pinMode(InPin, INPUT);
  pinMode(OutPin, OUTPUT);
  attachInterrupt(1, halltrigger, RISING );  //interrupt on hall effect input going high. the 1 means that it will use pin 3
}//END OF SETUP

void loop()
{
  //RPM calc. uses average of pulse times, logarithmic weighted moving average (newest time is twice as imprtant as time before etc) 
  rpm = rpmcalc / ((pulsetime1 * 8)+(pulsetime2 * 4) +(pulsetime3 * 2)+ pulsetime4);
  // no use for rpm yet
  
  firetime=halltimenew+pulsetime1-advance;
 if (firetime >= micros()) ;
   {
    digitalWrite (OutPin, HIGH);
    delayMicroseconds (dwell);
    digitalWrite (OutPin, LOW);
    firetime=0;
    }
}//end of loop


void halltrigger(){
unsigned long halltimenew;
unsigned long halltimeold;
  // micros is a unsigned long with max value of 4,294,967,295  will need to work out what to do when it rolls over
  halltimeold = halltimenew;
  halltimenew = micros(); //get time of HALL GOING HIGH
  pulsetime4 = pulsetime3;
  pulsetime3 = pulsetime2;
  pulsetime2 = pulsetime1;
  pulsetime1 = halltimenew - halltimeold; //calclulate latest pulse time
}//end of halltrigger

Programable_ignition.ino (2.05 KB)

In the absence of directives to the contrary, numeric literals are interpreted as ints.

  rpmcalc = 180000000/cylinders; // put this hear to save doing it every loop. shortened this: 10000000/(cylinders/2)*60 to go from uS to rpm. also * 15 as part of the wma calculation (8+4+2+1)

Do you see any "directives to the contrary"? I don't. Do you think that 180000000 will fit in an int? I don't. Tacking a UL on the end (a directive to the contrary) would be useful.

However, I think that without an Arduino to test this on, or a car to test it with, that you are wasting your time.

As an exercise in programming, it is fine. So, I'll continue with suggestions. Adopt them or not, as you see fit.

int InPin = 3; //Pin connected to trigger from hall effect sensor
int OutPin = 12; //Pin conected to coil driving circuit

I read comments that precede a block of code, since they generally explain a block of code that performs something that is not intuitive. I rarely read comments that follow a line of code, since they often state the obvious. Why do I point this out? Because I don't generally read the comments, I have no idea what inPin and outPin are for. Even if I had read the comments, I wouldn't remember later what inPin and outPin are for. And, why should I? A name like hallPin, instead of inPin, and a name like coilPin, instead of outPin, would tell me, when I was reading the rest of the code, what the pins are for when they are referenced later.

int cylinders = 8; //number of cylynder of engine

See? This is why I don't read inline comments. Even here, though, a name like numCylinders would be better, in my opinion.

unsigned long advance = 14; // this is the advance time.  will be calculated eventually from rpm/map etc

What are the absolute minimum and maximum limits on advance? I can't imagine a value greater than 65535 degrees being meaningful. Really, I can't imagine a value greater than 255 degrees being meaningful.

The point? unsigned long is way larger than needed to store the values that will ever be stored in this variable. Why does this matter? Because you have very little memory in which to store variables. Using 4 bytes, when 1 is sufficient is a waste of 75%.

unsigned long halltimenew;
unsigned long halltimeold;
  // micros is a unsigned long with max value of 4,294,967,295  will need to work out what to do when it rolls over
  halltimeold = halltimenew;

halltimenew and halltimeold are local variables. Unlike global variables, local variables are NOT initialized. So, halltimenew will contain random junk, which you just copied into halltimeold.

As an aside, hallTimeOld and hallTimeNew are much easier to read.

  pulsetime1 = halltimenew - halltimeold; //calclulate latest pulse time

Now, you use that random junk and "now" to compute what you hope will be a meaningful value. It won't.

There are no comments to describe what you are trying to do here, especially why there are 4 values that are shuffled around like the various pulsetimex values are. (pulseTimeX would be easier to read.)

PaulS:
In the absence of directives to the contrary, numeric literals are interpreted as ints.

Actually, a decimal literal is interpreted as having type 'int' if it fits in one, or 'long int' if it fits in one of those. If it doesn't fit in either, the type is undefined according to the 2003 C++ standard, although many compilers support a 'long long int' type.

You should be looking at a wasted spark setup. This means you fire the sparks in pairs so you only need four coil drivers and four double-ended coils (or eight coil-on-plug coils) for a V8, and you don't need an engine phase/cam sensor.

You're going to need to buy/make coil drivers and buffers to go between your Arduino and the various sensors connected to it.

The overall solution will presumably need to be reliable, and it's going to be in quite a hot dirty noisy environment. So you will need to pay attention to wiring (joint quality and environmental sealing), electrical protection and mechanical (vibration) isolation.

Although you're planning to use a static dizzy as a crank position sensor, you might consider putting a trigger wheel on the crank and using an MVR sensor instead. This will give you much more accurate timing information (calculating the crank angle on the fly just based on ignition events is quite tricky since the crank speed fluctuates quite a bit especially at low RPM).

Fitting a trigger wheel+sensor also opens up the possibility of using an EDIS ignition module which will deal with all the HT stuff and dwell angle/timing and so on, and leave you just to set the advance angle based on the load/RPM calculations. Best of all, an EDIS module would continue working (albeit at a fixed default advance) if your controller packs up, so you have a good chance of getting home even if your home-made controller develops a problem.

You will need either a MAP sensor or a throttle pot input, for load. A MAP sensor is easiest to deal with electrically and is likely to give the best results. Since you're only dealing with ignition timing and not fuel, your load sensing doesn't need to be particularly accurate.

You will also need to work out engine RPM which means you'll need to have your crank signal at the controller. If you have gone for EDIS, you can daisy-chain the trigger wheel signal from EDIS up to the Arduino.

Thanks for the input!

I didnt know about directives to the contrary at all

I first had the advance variable has a time in micro seconds. should have changed it to an integer when i changed to degrees.

does setting halltimeold and halltimenew as global variables cause any other problems? I saw things set as local variables in others code that i was looking at, so assumed thats how it had to be.

Good points about the commenting I put in. I keep thinking that one day im going to be looking at this code again trying to work out what I was doing.

pulsetimex.. dont know what im doing with that yet. If i end up using a crack sensor with a missing or extra pin to detect TDC I will need to detect that. so thats half an idea on how to go about it, comparing the latest time pulse with older ones.

I did think about a wasted spark setup, but have not seen any double ended coils readily available. Although I wasnt aware about not needing a phase/cam sensor, so that makes it alot easier in that regard. I agree that a crank sensor would be more accurate to determine crank position. The idea i had was that using a dizzy and single coil would be a lot easier to set up and test, and prove that it actually works. EDIS module could be an option. especially when it comes to driving the coils. as the whole point of this is to improve performace and reliabitity. The points would go out of tune in under a week in my old car.

I was thinking that calulating rpm based on ignition events wouldnt be accurate enough. especially in a fast reving engine. Might be able to get away with it in an old V8 though.

coil drivers/ sensor filtering and mounting are all part of the fun!

sprocket1597:
I did think about a wasted spark setup, but have not seen any double ended coils readily available.

These days almost all cars are either wasted spark or coil-on-plug instead of using a mechanical distributor. Just about any recent Ford 4-pot would have a coil pack with a pair of coils giving four spark outputs. Two of those does the job nicely for a V8.

Think i will have to go see what i can find at the wreckers when the time comes!

Just in case anybody else was wondering, I guess a "dizzy" is a distributor; must be an aussie thing...

cr0sh:
Just in case anybody else was wondering, I guess a "dizzy" is a distributor; must be an aussie thing...

Yes it is. sorry about that, bit of aussie slang there. same as tacho, speedo, mozzie, barbie, and probably a heap more i cant remember at this time of morning.

So Ive used this to look up the timing and vacuum advance
http://arduino.cc/playground/Main/MultiMap

Im thinking the advance need to be a float? so maybe i need to use FmultiMap down the bottom of that post. Not sure on how that effects the calculations i have in there. will the values being calculated need to be floats too?

Added a basic hard rev limiter. I dont know how a soft rev limiter works. does it retard the ignition?

I also found a use for pulseTimeX... adjusting the calculated time the next cylinder would be at tdc if the rpm is increasing or decreasing.

Also need to do some research on dwell angle.

// These vaules to be changed accoriding to engine
int cylinders = 8; 
int staticAdv = 15; // could be set with distributor anyway. 
int dwell = 250; // dwell time in uS
int revLimit = 5800;
int tableMechAdv[] = { 0, 5, 6, 10, 15, 20, 21 };  
int tableRpm[]  = {0,1000,2000,3000,4000,5000,6000};
int tableVacAdv[] = { 5, 5, 5, 5, 4, 3, 1, 0, 0, 0, 0 };  
int tableVac[]  = {0,10,20,30,40,50,60,70,80,90,100};
  
volatile unsigned long pulseTime1; //Time delay of latest pulse
volatile unsigned long pulseTime2; // time delay of pulse before that
volatile unsigned long pulseTime3; // time delay of pulse before that
volatile unsigned long pulseTime4; // time delay of pulse before that
volatile unsigned long HallTimeNew;
volatile unsigned long HallTimeOld;
int rpm;
long rpmcalc;
long advTime ; // advance time. in uS 
unsigned long fireTime;     // the time to fire next spark compared to micro()
int totalAdv;  //should advance be a float?
int vacIn;
int mechAdv;
int vacAdv;
int multiMap(int val, int* _in, int* _out, uint8_t size) // taken from interweb
{
  // take care the value is within range
  // val = constrain(val, _in[0], _in[size-1]);
  if (val <= _in[0]) return _out[0];
  if (val >= _in[size-1]) return _out[size-1];
  // search right interval
  uint8_t pos = 1;  // _in[0] allready tested
  while(val > _in[pos]) pos++;
  // this will handle all exact "points" in the _in array
  if (val == _in[pos]) return _out[pos];
  // interpolate in the right segment for the rest
  return map(val, _in[pos-1], _in[pos], _out[pos-1], _out[pos]);
}

void setup()
{
  rpmcalc = 180000000UL/cylinders; // shortened this: 10000000/(cylinders/2)*60 to go from uS to rpm. also * 15 as part of wma calculation (8+4+2+1)
  pinMode(3, INPUT);
  pinMode(12, OUTPUT);
  attachInterrupt(1, halltrigger, RISING );  //the 1 means that it will use pin 3
  digitalWrite (3, LOW); // as not to power the coil until the engine starts cranking
}


void loop()
{
 //input from MAP sensor... adjusted to 0-100kpa
  vacIn = analogRead(A0);
  vacIn = map(vacIn, 0, 1023, 0, 100);
  
  
  //RPM calc. uses average of pulse times, logarithmic weighted moving average (newest time is twice as imprtant as time before etc) 
  rpm = rpmcalc / ((pulseTime1 * 8)+(pulseTime2 * 4) +(pulseTime3 * 2)+ pulseTime4);

  // advance calc
  vacAdv = multiMap(vacIn, tableVac, tableVacAdv, 11);
  mechAdv = multiMap(rpm, tableRpm, tableMechAdv, 7); 
  totalAdv= staticAdv + vacAdv + mechAdv;
  advTime = (totalAdv/360) * (cylinders/2) * pulseTime1;

//firing and rev limiter
//uses pulseTime2 to compensate for acceleration or deceleration
  fireTime = HallTimeNew + pulseTime1 + (pulseTime1 - pulseTime2) - advTime; 
  if (fireTime >= micros() && rpm < revLimit ) ;
  {
    digitalWrite (12, LOW);
    delayMicroseconds (dwell);
    digitalWrite (12, HIGH);
    fireTime=0;
  }
}


void halltrigger()
{
    HallTimeOld = HallTimeNew;
  HallTimeNew = micros(); 
  pulseTime4 = pulseTime3; //keeping track of last 4 signal times
  pulseTime3 = pulseTime2;
  pulseTime2 = pulseTime1;
  pulseTime1 = HallTimeNew - HallTimeOld;
}

Float seems a bit over the top - you won't need very much precision and could easily get away with just using fixed point numbers i.e. scaling the advance value up and storing as an integer value.

A soft rev limiter is usually a rev limiter that comes in progressively as the revs increase rather than going straight from 0% cut to 100% cut.

A 'hard cut' causes the engine revs to cycle up and down with violent changes in power whereas a soft cut progressively reduces the power as the revs approach the threshold.

Yes thats what I did this morning, after reading hows floats are time/processor intensive. so Ive multiplied advance values by 10. I figure that 0.1 degree is enough resolution. (also had to shift some arithmetic around to avoid the need for floats)

A soft limiter is definetly needed. What about having it missfire 1 in 9 at the soft limit, and then ramping up the frequency of missfires as the hard rev limit is reached?

Having your soft cut based on a cut pattern which is not an integer multiple of factor of the number of cylinders is a good idea - it means that the soft cut will affect all cylinders equally rather than preferentially hitting specific cylinders. A 9-cylinder pattern is a good choice for a V8.

The scheme would be the the number of cut cylinders in your pattern will increase as the revs increase. E.g 1 cyl cut at the soft limit, 2 cyls cut at soft limit + 50 rpm, 3 cyls cut at soft limit + 100 rpm and so on.

How are you with electronics? I'm planning my first car electronics project, so I've been doing some research on how to build a proper PSU to power a 5v device. It's definitely not as simple as slapping a 7805 in front of the chip. At least, not if you want it to live more than a week. (So I'm told.) If the point is a permanently installed device, I'm inclined to over-build a little.

So far, I've come up with some ideas based on a combination of forum posts and app notes that covered specific parts, but never an entire 12v-input to 5v-output schematic. This seems like a relevant place to put it. Hope it helps. Ideas appreciated.

12v in goes through a fuse, then a 10-ohm, 1-watt resistor, then a diode. This will protect against reverse connection, and limit current in case something in the protection circuitry fails open. A short to ground in the PSU would pull around 20W, so the fuse would have to be fast-action. I'm still not sure if the resistor would survive, though that's potentially better than other modes of failure.

From there, a TVS to ground. I'm thinking 20v cutoff would make sense. This gives the PSU protection against high-voltage load dumps from the alternator, up to 1.5kW for a few ms. Connection to a 24v jump-start battery would likely blow the fuse, so it might be worth considering a higher value, though there are arguments against that as well. Maybe the best advice is "don't do that." I might do some destructive mock-ups to find out what else would fail, just to know.

I've seen some designs that use a zener in the input stage to protect against reverse polarity from downstream -- like if there were any connections from I/O pins that could see negative transients. I don't know if this would be necessary or not. I/O protection might be better off using the standard diode-to-each-rail method.

Next up, a switching regulator to drop 7-20v to 5v. I found a TI part last night (MC33063) that looks easy enough. Just needs a couple caps, couple output setting resistors, and a 220uH and (optional) 1uH inductor. 1A-capable inductors look cheap (less than $2), and with the optional filter, the output is quoted at less than 40mV ripple.

I hear the MegaSquirt project releases their schematics as open-source, so I might try to find their PSU and see what they came up with. If there's one thing you don't want to fail, it's the ECU. I would imagine they engineered their PSU accordingly.

Well I got my Arduino Uno today. It does 2760 loops a second. not fast enough to even idle.

Calculated that wrong 2760 loops a second is good up to 41000rpm

As for the power supply circuit, I hadnt put much thought into it yet, other than a 7805 and a snubbing diode because that work the last time i put an embedded pc in a car. But it dose definetly need to be engineer o handle everything. When I was installing car alarms Ive seen the back emf from the cars horn kock out the alarm module, and imobilise the car.

I started a thread for a community PSU design:

http://arduino.cc/forum/index.php/topic,106068.0.html

If I don't any more input on this soon, I'm just going to order the parts, build it, and strap it to my Ninja. The bike has a pulsed DC system that has got to be the worst electrical system one could ever find. So, if an AVR can survive that, a car should be cake.

Cool. Will be keeping an eye on that.

Have been contemplating using my laptops sound card as a pulse generator. Cant find any thing on what circuit is needed, everything ive found about using it as an audio input. what is the voltage level needed to switch a digital input? 5V? Im thinking I need a DC offset circuit too. Or is it easier just to buy a signal generator off ebay?

Well, if you happened to have a spare microcontroller lying around I suppose you could use that as a programmable signal generator. :wink:

Haha yes, that was my first thought. but I dont have one so, I thought how could I improvise till I get one