From my couple of brief tests, I’ve identified a few problems which are preventing the engine from starting up and running for more than a few seconds.
- The ignition timing at very low speeds < 500 RPM is very erratic, and not at all on time.
- Under heavy acceleration the ignition timing is erratic, for example when pull starting the engine
- There are some occasional misfires
- The ignition timing is generally not massively stable – sometimes varying as much as 10 degrees(!)
I have now managed to fix all of these problems, and it seems to be down to the length of time that my program was spending in my interrupt routines. I knew that this would be a problem at some point, and was basically just pushing it to see how much I could put in them before needing to optimise. It turns out not a great deal (not really a surprise). So now I have optimised some calculations to avoid more floating point operations and have moved them out of the interrupt routine and into the main loop. I have also added a low pass filter on the crank angle sensor tooth measurement which gives a more stable ignition timing under steady state conditions.
The problem with the timing drifting under heavy accelerations was due to the way I was setting up the timer for starting the ignition pulse. The ignition pulse needs to switch off some time around 20 degrees before top dead centre (BTDC). Before the ignition can be switched off, it needs to be switched on and charged for a short period of time. To do this I was setting a timer up upon detecting a missing tooth. When the timer elapsed, it switched the ignition on, and then reset itself and loaded a new time for the coil charge time. Then when it elapsed again, it would switch off the ignition and disable the timer. This is the point at which a spark would occur.
The tricky part is calculating and triggering the initial switch on part. It should happen at TDC minus ~20 degrees minus the time to charge the coil (1500 uS).Hence, we have a component which is specified in crank angle degrees and one which is specified in absolute time. To convert between them, we have to use the engine speed measured from the missing tooth wheel and VRS sensor. We then can calculate either the absolute time for 1 crank angle degree, or the number of crank angles for 1500 uS. Then the ignition switch on could either be triggered by a timer or when a certain crank angle is reached (the crank angle is always known since it is reset after the missing teeth, and 3 degrees are added for every normal tooth). The difficulty with triggering on the crank angle is that it would not provide very accurate ignition timing (only 3 degree resolution). I guess there are tricks you could use to improve this though. The way I decided to do it was to use the timer to trigger switching the ignition on.
The problem with doing it this way was that under heavy acceleration, the timer would have been set up at the missing tooth and ready to trigger the ignition at the correct angle for the engine speed at that point. Imagine, for example, that at 600 RPM, it takes 80 ms until TDC happens. However, since the engine is accelerating, TDC will actually occur sooner than 80ms. Therefore, if the timer has been set up, it will elapse too late, and the ignition timing will effectively become retarded. Under heavy acceleration this effect becomes very pronounced and ignition can be skipped completely for a whole cycle, causing a misfire.
To overcome this, I have changed the code to continuously update the timer compare register for every tooth that occurs. This means that even as the engine accelerates, the timer should get updated with a more suitable value every 3 degrees as oppose to only every 360 degrees. This along with the other optimisations has made a huge improvement, and now the ignition is accurate to about 1 degree even under acceleration and deceleration.