In order to tell if we’re barking up the right tree with the Arduino Mini / Atmega328P, it needed to be tested on speed, precision and accuracy of its hardware and software.
Testing the Arduino Software and ATmega328P
The Arduino libraries are easy to use, and without a doubt lessen the learning curve. But the extra routines in libraries designed for ease of use, can also sometimes cost timing accuracy in an application.
So I set out writing the basic timing and calculation routines for the NanoEFI, to see if this project – at it’s heart – is actually feasible.
Test 1. Maximum Digital GPIO Pin Frequency
Result: PASSED (5μs)
This was easy, I just set one of these in the main loop:
The result was a hair over 5μs. Far better than my expectations, so I quickly moved on to the calculations test.
Test 2. RPM Calculation – Processor Time
Result: PASSED (35μs)
rpm = (60000000/usPerRotation);
usPerDegree = (usPerRotation/360);
timeShift = usPerDegree*(360);
According to the ‘scope, the Arduino Mini / ATmega328P takes 35μs to make these calculations, which equals to only 2.5° of rotation at maximum engine speed. That’s just 0.0035% of the time in one injection cycle.
I’ll elaborate: Injector cycles occur every two revolutions (720° apart) on 4-stroke engines. So we end up with an entire 717.5° of “time” to check sensors, lookup values in our fuel map table, and perhaps interpolate. Excellent.
Final Test: Event timing accuracy
For this test, the calculation sketch was filled out quite a bit more, with use of timers and about 2kb of logic.
attachInterrupt(0, update_uS_per_rotation, RISING);
Timer0 is used in hardware interrupt mode, tied to pin 2. This provides the information we need to determine RPM. Upon interrupt our ISR fires:
// This uses TIMER 0 (8-Bit)
// This interrupt fires when signal is received from the trigger.
// The crank is 25°BTDC at this time
// Each rotation, this interrupt function is run once.
usPerRotation = micros() - timeold;
timeold = micros();
fl_sync = !fl_sync;
bool “fl_sync” is used to keep track of the engine cycle and prevent the injector from firing at the end of the exhaust stroke.
int “timeShift” is the time in microseconds from the current moment to the future firing event.
- “usPerRotation” is calculated by subtracting time (in microseconds) between the rising edge of trigger pulses.
- “timeold” is updated for the next cycle
- If “fl_sync” = true (engine is in the compression stroke), schedule Timer1 to run the “fireSpark” function at the time value (in microseconds) of “timeShift”.
Final test results: A look at 7632rpm
After some hours of trial and error, this setup was able to consistently produce “fire” events every other rotation.
Yellow: Trigger signal
Blue: Our test “injector output” signal
Events timing accurate to ±1°
In these screenshots you’ll notice that the blue event proceeds it’s yellow signal from the trigger sensor. This is because I added a variable to set timing to a 10° advance and worked it into the calculations for “timeShift”.
At 7632rpm, the time per degree is 21.84μs.
The screenshot below shows our leading edge advance of 200μs on the ‘scope. A quick division of 200/21.84 reveals an advance of 9.16° (we were shooting for 10°). This surely meets our needs.
After watching the o’scope for a while, I determined that events typically fire within 1° (0.9° actually) with an occasional blip outside of the typical range.
There is some room for speed improvement in the logic, but I feel comfortable at this point continuing with the ATmega328P processor for this project.