This article has the purpose to explain how Fuelino calculates the control signal which is output to the fuel injector. In order to better understand how this simple control works, I decided to create a scheme using Matlab Simulink. The signal flow goes from left to right.
First of all, in order to work, Fuelino needs to receive, as input, the injection command that the original ECU would like to send to the fuel injector. This is the most important input signal, because Fuelino manipulates this signal and outputs a modified injector signal to control the physical injector. In other words, Fuelino, in a similar way to Power Commander and other ECU piggyback units, acts as a "man in the middle" between the original ECU and the injector.
By measuring the "original ECU injector command" signal, Fuelino is capable of calculating 2 important info:
- Original fuel injection time [us, microseconds]
- Time between 2 injections [us, microseconds], which basically corresponds to 2 engine rotations. By manipulating this measured time, it is possible to estimate the engine rotation speed [rpm].
The calculated engine speed, together with the TPS (Throttle Position Sensor) signal, are fed to two 1-D calibration maps, called "incrementi_rpm" and "incrementi_thr". These maps define the fuel injection time increment percentage ("perc_inc"), which is then converted into a time, in microseconds, "extension_time_ticks": this is the extension time, for the injector command. This logic allows the injection command pulse-width to be extended, of a certain amount, depending on engine rotation speed and throttle position.
The complete explanation of the algorithm is inside the source code file "INJmgr.cpp", which is available on GitHub, together with the complete Fuelino open source software. The function below runs at each injector input signal change interrupt (coming from the original ECU). Once the original ECU wants to turn ON the injector, Fuelino does it. On opposite, when the original ECU wants to turn OFF the injector, Fuelino says "wait!": first, it calculates the injection command extension time, and, if it is different than zero, the Arduino ATmega328P "Timer1" is set. After Timer1 expires, the injector is turned OFF.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
// Interrupt pin status changes (Original ECU injector command pin) ISR(PCINT2_vect){ // For execution time measurement uint16_t INJ_exec_time_start = INJmgr.Timer0_tick_counts(); // 1 tick = 4us INJ_signal_status = !digitalRead(IN_INJ_PIN); // Original ECU is enabling injector (Injector valve will open and gasoline flows) if (INJ_signal_status) { digitalWrite(OUT_INJ_PIN, HIGH); // activates injector delta_time_tick = INJ_exec_time_start - inj_start_tick_last; // calculates Timer0 ticks (4us) between 2 consecutive injections (2 rpm) perc_inc = INJmgr.interpolate_map(incrementi_rpm_brkpts, true, incrementi_rpm, INJ_INCR_RPM_MAPS_SIZE, delta_time_tick); // returns the interpolated increment based on rpm inj_start_tick_last = INJ_exec_time_start; // saves old time stamp injection_counter++; // increases the combustion cycles } // Original ECU is disabling injector (Injector valve will close and gasoline stops, after Timer1 expires) else{ delta_inj_tick = INJ_exec_time_start - inj_start_tick_last; // delta injetion time, in Timer0 tiks (4 us) bool extension_timer_activated = false; if ((delta_inj_tick >= INJ_TIME_TICKS_MIN) && (delta_inj_tick <= INJ_TIME_TICKS_MAX)) { // injection range check // Remove the offset (delay and opening time from measured ticks) if (delta_inj_tick > INJ_OPENING_TIME_TICKS) { // remove the opening time from the time measured delta_inj_tick -= INJ_OPENING_TIME_TICKS; // real injection time (removed the opening time) } else { delta_inj_tick = 0; // if time is lower than estimated injector opening time, real injection time is considered 0 } // Calculate final percentage increment uint8_t index_thr_tmp = (uint8_t)((throttle >> 6)) & 0x0F; // 0x0F -> to make sure that the index is not over 15 (INJ_INCR_THR_MAPS_SIZE - 1) perc_inc+=incrementi_thr[index_thr_tmp]; if (perc_inc > MAX_PERCENTAGE_INJ) perc_inc = MAX_PERCENTAGE_INJ; // saturates the maximum injection percentage, for safety extension_time_ticks = (uint16_t)(((uint32_t)delta_inj_tick * (uint32_t)perc_inc) >> (8 + 1 - 3)); // calculates extension time, in Timer1 ticks (0.5us) // Checks if the extension time ticks (Timer1) is acceptable if ((extension_time_ticks >= MIN_EXTENSION_TIME_TICKS) && (extension_time_ticks <= MAX_EXTENSION_TIME_TICKS)){ // Removes the calculation time requested if (extension_time_ticks > INJ_CALCULATION_TIME_TICKS) { // remove the calculation time extension_time_ticks -= INJ_CALCULATION_TIME_TICKS; // real extension time } else { extension_time_ticks = 0; // if time is lower, extension becomes 0 } // Sets the timer (Timer1) Timer1.setPeriod(extension_time_ticks); // setta il timer Timer1.attachInterrupt(deactivate_inj); // attacca l'interrupt Timer1.start(); // fa partire il timer extension_timer_activated = true; // timer has been activated } } // In case the previous calculaton stopped somewhere before activating the timer, do the following if (extension_timer_activated == false){ // timer was not activated, so need to disable the output now digitalWrite(OUT_INJ_PIN, LOW); // shuts down injector INJ_safety_inj_turned_off = true; // Injector turned OFF (this flag is used for Safety check) INJmgr.update_info_for_logger(); } } } |
In order to increase the stability, and safety of the system, I also introduced the concept of "Safety" into Fuelino's software. Can you imagine what happens if, for some reason, the injector is continuously driven "ON", and never deactivated? This is a security issue: not because the motorcycle can produce torque (if you release the gas, no air flows into the throttle body, and the engine does not produce any huge torque), but because a stuck open injector causes a continuous loss of gasoline, that you can stop just by turning OFF the ignition switch (key). This situation is not good. In order to avoid such possibly dangerous (?) situation, there is a "Safety" task which runs inside the Main Program (Arduino "Loop"), and makes sure that the injector is turned OFF. The name of this function is "INJmgr.safety_check()". This supervisory function makes sure that, even in case Fuelino does turn OFF the injector (for example due to a missing OFF interrupt), after a specific number of Loop iterations, the injector will be automatically turned OFF for safety.