Bike (honda cb450 twin cilinder 180°) ignition

He everyone here, (i'm totally new on posting so sorry 4 the mistakes).

As the subject says I'd like to design a ignition for my bike.
Ik know it's possible cause several people have already done it.

But the program i've seen are far to complicated, they use vacuum, and 4 or more cilinders ....

First of all i have to give a little bit of information about the engine. It's a 2 cilinder 4 stroke DOHC 450cc
engine with a 180° degree crank (2 pistons are always in opposite position).
The engine has 2 distributor points mounted at the cam shaft. (90° possition).

What i like to do replace the distributor points by 4 hall sensors
=> why 4 ?
->2 hall sensors for under +-1000 rpm the advance have to be stable at 8°, the rpm is not stable certainly when cranking, so fire immediatly when sensing these 2 sensors. Regulate the 8° stable pre advance by turning the sensor plate. 2 hall sensors at TDC (top death center)
->The other 2 at +-17,5° advance. the maximum advance of the engine is +- 35° (at the crank), that makes max 17,5 at the camshaft (1/2 distribution). => if more then XXX rpm fire immediatly.

the coils are going to be driven from a kit 'velleman'.

To be certain i would put a fet on the output from the ignition witch will control the velleman kit.
The kit is only a booster witch controls the current instead of the contact breakers.

So the program, i've been thinking. => measure rpm from 2 sensors witch are 90° separated.
Compare the value with the desired pre-advance,
if below 1000 => fire at trigger by hall sensor at tdc.
If more 1000 => look for delay in a table, trigger interrupt at hall sensor on 17,5° pre advance.
and delay the requested time.

All this should be done in the time between the two cilinders has fired. so in the time between 0° and 360° minus 2 times 20 ° (max fire advance) and minus 180° degrees beteen 2 cilinders. give 140° left of camshaft rotation to do what i want, rpms meaurement, pre advance calculation.
I understand that 140° says nothing but the max rpm of my bike is +- 10 000 rpm.
gives 5 000 at the camshaft, 5 000/60= 83.333 RPS, time 1/83.33333 = 0.012 S or 12 000µs per revolution. but we only have 140° for calc => 12 000 / 360 * 140 = 4 666 µS 'free' time.

I wonder if that would be enough.

The only thing i am still thinking about is the dwell time.
where to put that ? i know some ignitions have a dwell of 120° on the cam.

I do not have a program jet, starting to write when i have no structure is not really doable.

Thank you for reading this post.

1 Like

From a signal processing view, you get a trigger signal from an hall sensor, calculate a delay depending on the current RPM, wait that time to start ignition, (eventually) wait some more time to stop ignition.

That makes a total of 3 states for each cylinder: idle, wait_start_ignition, wait_stop_ignition, or 5 states in total with both idle states merged.

The hall sensors can be polled in loop(), or can cause an interrupt. In either case their signal changes the state into wait_start_ignition, with the current time and ignition delay time stored in global variables. When loop() reaches that time, ignition is started and the state is advanced to wait_stop_ignition, with the current and delay time again stored in the variables. See "blink without delay" for handling the timing.

The loop() now may look like this:

  unsigned long currentMilis = millis(); //or micros()
  if (currentMilis - PreviousMillis >= Interval) { //it's time to do something!
    switch (State) {
    wait_start_ignition1: /*do start ignition here...*/ State = wait_stop_ignition1; break;
    wait_stop_ignition1: /*do stop ignition here...*/ State = idle; break;
    //same for second cylinder
    }
    PreviousMillis = currentMillis; //base time for next event
  } else if (State=idle) {
      if (digitalRead(hall1) { /*prepare for ignition here...*/ State = wait_start_ignition1; }
      else if ... //same for second cylinder
      else {  /*calculate RPM or do other uncritical things here...*/ }
  }

According to C convention the state constants should be capitalized, i.e. IDLE...

For better operation you should take vacuum into account, but that can be added later.

olivierH:
But the program i've seen are far to complicated, they use vacuum, and 4 or more cilinders ....

Just to be really annoying for a moment, the reason they use MAP (in addition to RPM) is because ideally you need a load reference. On an engine that you want any decent sort of performance out of, you need to adjust ignition timing based on load as well as engine speed. It's the same reason most distributors have a vacuum reference as well.

You could do the same thing with a TPS sensor too...

...gives 5 000 at the camshaft, 5 000/60= 83.333 RPS, time 1/83.33333 = 0.012 S or 12 000µs per revolution. but we only have 140° for calc => 12 000 / 360 * 140 = 4 666 µS 'free' time.

I wonder if that would be enough.

Should be heaps if your code is well written. You're not really needing to do too much per loop, so a loop time of around 1ms (1000uS) at the slowest should be achievable fairly easily.

The only thing i am still thinking about is the dwell time.
where to put that ? i know some ignitions have a dwell of 120° on the cam.

Cam dwell != ignition dwell at all. You need to know the manufacturers recommended dwell time for the coil that you're using. Typically this will be between 2ms and 5ms. If this case of a 4ms coil dwell, you'd need to set the output high 4ms before you want it to spark, then drop the output low at spark time.

*About the vacuum, it is indeed thrue that it would give a better performance. But there is no info/data about the delay in function of the vacuum created. old bike ...
And it is a 450cc with 43 horsepower. Im happy with that :).

I could try to add it later to maybe reduce fuell costs.

*To reduce the loop i'd like to make a array with the RPM's in µS/rot and the related delay.
=> measure µs per rotation , look at the table with the corresponding delay.
add this delay with the dwell time and that would be the time to wait before sparking.
Maybe (because i do not have info about the coils) i can start with a dwell time from 5ms
and make the current visible on the scope and look if it is to long / short ?

also Look in the loop
rpm<800 => idle fire immediatly
800 rpm 4000 => look at array
rpm > 4000 advance is Always max.

DrDiettrich:
From a signal processing view, you get a trigger signal from an hall sensor, calculate a delay depending on the current RPM, wait that time to start ignition, (eventually) wait some more time to stop ignition.

That makes a total of 3 states for each cylinder: idle, wait_start_ignition, wait_stop_ignition, or 5 states in total with both idle states merged.

The hall sensors can be polled in loop(), or can cause an interrupt. In either case their signal changes the state into wait_start_ignition, with the current time and ignition delay time stored in global variables. When loop() reaches that time, ignition is started and the state is advanced to wait_stop_ignition, with the current and delay time again stored in the variables. See "blink without delay" for handling the timing.

The loop() now may look like this:

  unsigned long currentMilis = millis(); //or micros()

if (currentMilis - PreviousMillis >= Interval) { //it's time to do something!
    switch (State) {
    wait_start_ignition1: /do start ignition here.../ State = wait_stop_ignition1; break;
    wait_stop_ignition1: /do stop ignition here.../ State = idle; break;
    //same for second cylinder
    }
    PreviousMillis = currentMillis; //base time for next event
  } else if (State=idle) {
      if (digitalRead(hall1) { /prepare for ignition here.../ State = wait_start_ignition1; }
      else if ... //same for second cylinder
      else {  /calculate RPM or do other uncritical things here.../ }
  }



According to C convention the state constants should be capitalized, i.e. IDLE...

For better operation you should take vacuum into account, but that can be added later.

I want to work with interrupts. Because at the trigger point (by the hall sensor) the arduino should be doing , and nothing else, then waiting the delay to spark. And it may never miss a trigger.

for the seperated rpm we have indeed different states 0 (stop) (wich i haven't thought about to turn of the coils ... ), idle (0-800rpms fixed min advance), running (800-4000 array time delay) (4000-10000 rpms fixed max advance).

I have found loads of code ad you guys gave me quite good ideas (code and thinking area). This week i'll put them (the found ideas and code) together to a 'program' and then look further to tweek (remove the erros :p)

Ideally, you'll need to use 2 sets of interrupts.

The first is a hardware interrupt that triggers based on the input from your sensor. Use this to calculate the 2 minimum pieces of information you need for the timing: RPM and crank angle. Without those you can't hope to accurately do timing advance.

The 2nd is the timer based interrupts that are use to drive the coil high and then low. Triggering the high/low of the coil in this way will give you substantially more accurate and consistent timing than using delays/time comparisons etc.

Hey,

I found a code for a 180° engine but i want to tweek it to get it more accurate/ adaptable.

The code calculates the array with the delays (linear) but u want to make the array myself.

The following code only uses 2 hall sensors and the spark time for the second cilinder is calculated (the time for 180 °). I think this is not good because when the engine is ac/deccellerating very fast this will not be correct anymore.

Here you see the code with the text in witch i believe it works.

const int MIN_RPM = 800;
const int MAX_INDEX = 200;
const int DWELL = 1500;
const int COIL1 = 4;
const int COIL2 = 7;
const int SENSOR1 = 2; //idle sensor
const int SENSOR2 = 3; //cam sensor

bool triggered = false;
long lastDuration = 0;
unsigned short currentIndex = 0;

unsigned int RPMS[MAX_INDEX];
unsigned int HALVES[MAX_INDEX];
unsigned int DELAYS[MAX_INDEX];

///Build all of the values in the arrays based on our starting RPM and initial and final advance
///Redline is (MAX_INDEX-1) * 50 + MIN_RPM
void calculateArrayValues()
{
 double startRPM = MIN_RPM; //use a double for increased precision in the calculations
 int startAdvance = 18; //18 degrees of advance coming on at 800 rpm
 int endAdvance = 38; //total advance of 38 degrees
 int endRPM = 4000; //total advance to be achived at 4000 rpm
 int hallAdvance = 180; //assume sensor is 180 degrees advanced from TDC
 double slope = abs(endAdvance - startAdvance)/abs(endRPM - startRPM); //function to calculate linear advance is y = mx+b where m = (y1-y2)/(x1-x2) // hier word m berekend (tg van de alpha)
 int intercept = startAdvance - (startRPM * slope); //intercept is b from above formula // hier word de b berekend van boven de formule

 //build the arrays, 50 rpm at a time
 for(int i = 0; i < MAX_INDEX; i++)
        {
 double usPerDegree = 1000000 / (startRPM / 60) / 360;
 double advance = startRPM * slope + intercept; //use our function to calculate the advance
 int advanceDelay;
 
 if(startRPM >= endRPM) //if our current RPM is >= the to ending RPM, just use 38 degrees
 {
 advance = 38;
 }
 
 advanceDelay = ((hallAdvance - advance) * usPerDegree) - DWELL; //calculate the time we wait before picking up the sensor and starting the spark process on the coil(s)
 
 DELAYS[i] = advanceDelay;
 HALVES[i] = usPerDegree * 180; //we're on a 180 twin, so calculate the delay between the first coil (left) and second coil (right) firing.  This way, we only need one pickup.
 
 startRPM += 50; //increment the RPMs for the next iteration
 }
}

///Fires a coil by grounding the selected pin, waiting the dwell period, then ungrounding it
void dwellAndFire(int pin)
{
 digitalWrite(pin, HIGH); //ground the coil
 delayMicroseconds(DWELL);
 digitalWrite(pin, LOW); //trigger the coil
}

///Fires both coils with the currently mapped delay values
void fireWithDelays()
{
 delayMicroseconds(DELAYS[currentIndex]);
 dwellAndFire(COIL1);
 delayMicroseconds(HALVES[currentIndex]);
 dwellAndFire(COIL2);
}

///Interrupt for the idle sensor.  At RPMs less than RPMS[0], use this sensor to immediately trigger the coil(s) with no delay.
///This sensor will be located around 6 to 10 degrees BTDC
void idleSensor()
{
 if(lastDuration > RPMS[0])
 {
 int coil2Delay = (lastDuration / 4); //engine spinning slower than our map, calculate delay for second coil (divide by four is 90 degrees of rotation)
 
 dwellAndFire(COIL1); //fire left coil
 delayMicroseconds(coil2Delay); //wait 180 degrees
 dwellAndFire(COIL2); //fire right coil
 }
}

///Interrupt for the "running" sensor
void camSensor()
{
 triggered = true;
}

///Set the pins appropriately, calculate the values we'll be using, and then attach the interrupts
 void setup()
 {
 pinMode(COIL1, OUTPUT);
 pinMode(COIL2, OUTPUT);
 pinMode(SENSOR1, INPUT);
 pinMode(SENSOR2, INPUT);
 
 calculateArrayValues();
 
    attachInterrupt(1, camSensor, RISING); //cam sensor interrupt, als de camsensor een interupt geeft word de void camsensor uitgevoerd.
            attachInterrupt(0, idleSensor, RISING); //idle sensor interrupt, als de motor te traag draait < dan x aantal toeren word void idle uitgevoerd., MAAR de motor moet altijd eerst camsensor interupt passeren (deze staat vroeger) 
                                                 //dus deze volgende word sowieso altijd ook uitgevoed
                                                 // moet trouwens daar in de camsensor interupt de durations / toerntal bepaald word !!! 
 
 }

///If we pickup a trigger from the running sensor, adjust the timing map and then fire the coils
 void loop()
 {
 if(triggered)
 {
 noInterrupts();  //turn off interrupts
 triggered = false;
 lastDuration = (micros() - lastDuration); //number of microseconds since last rotation
 
 if(lastDuration > RPMS[MAX_INDEX]) //not past redline, OK to fire spark
 {
 if(lastDuration < RPMS[0]) //cam sensor (not idle sensor) handling spark check index
 {
                              
                             
 if(lastDuration < RPMS[currentIndex]) //engine speed increasing
  {
 currentIndex++; //jump to the next value in the array
  }
 else if (lastDuration > RPMS[currentIndex]) //engine speed decreasing
  {
 currentIndex--; //drop to the previous value in the array
  }
 
 fireWithDelays();
                             
 }
 }
 
 interrupts();  //turn interrupts back on
 }
 }

First we declare de min rpm and the maximum index for the array (the higher the more preciese).
Then the dwell time. Here 1,5 ms.

Set pin 4 to control coil one and pin 7 to control coil 2.

Set the inputs for the coils sensor 1 (advance sensor) pin 2 (interupt) sensor 2 (idle) pin 3 (interupt)

declare a state 'triggered' = false

set the duration on '0'
set the index (from the arrays) at 0

Declare the arrays
RPMS (used to store the engine speed )
HALVES (used to store the delay time between 1st en 2nd cilinder )

at the 'void calculatearrayvalues' we want to, as the name says, calculate the arrays one time.
This is math :p. Simple below 800 the advance is 18° between 800 and 4000 => lineair rising line
calculated, above 4000 fixed to 38°.

So the arrays get filled up from 0 (800rpm) to 200 (10 000) (max index) by delays and halvedelays.

Then we have a void dwell and fire.

this is the function where the cils just have to spark. Just wait the dwell time and fire. Called by another section of the program.

The next is void fire with delays

This is the function where we are above 800 rpm.
Wait the delay 'call' the dwell and fire funcion. wait a half rotation and do the same for the second cilinder.

Next is the void idel sensor.

Here the engine is >800rpm. calculate the delay for coil 2, Just dwell and fire coil 1, wait calculated delay and fire coil 2 (determined by the void dwell and fire section).

Next is the camsensor (180BTDC). Makes the 'triggered state' true.

Then we have the setup. (speeks for itselves.)
Maybe only a little bit info for the interupt. The camsensor always comes before the idle sensor, it is mechanically determined.

The last is the void loop.

=> if the state triggered is true (camsensor detection)
switch of interrups, make it false again for the second round.

Look if the motor is not running to fast. (do nothing motor protection, advance will be as at idle => way to late)
Look if it is running below 800 rpm (do nothing go to idle, dwell and fire)
Look if higher then 800 => advance from table dwell and fire.

I know the explanation is not as good on paper explained as i would like to tell it. But if you can't follow my well written in perfect English tekst => ASK, i will be as happy to help as i can.

I am also busy on making a model 'engine', just gears 1 to 2 ratio with timing disks mounted on them to get a view by without testing it and ....... :smiling_imp: my engine. It will also contain the ignition coils drivers. So i van test with the strobe.

Or would that be to much effort ?

Bad timing can be .... for the connections rods and pistons.