jump to navigation

2JZDuino Pin Assignments 2010/05/31

Posted by Michael in 2JZduino.
add a comment

For anyone interested, I’ve posted below a PDF of the Arduino Mega pins in use for 2JZDuino. Further detailed explanation isn’t warranted just yet, but someone carefully following along may find this useful.
2JZDuino Pin Assignments


2JZduino Installation 2010/05/01

Posted by Michael in 2JZduino.

Just posting to show a few pictures of the 2JZduino box installed in the engine bay, and the LCD in the interior. After significant struggles with wire management I found a way to re-arrange connectors in the stock ECU enclosure so that most of the 2JZduino box fits inside. Some of the injection moulded box was removed (I’m not proud of this, but it does fit).

The LCD screen on the interior is wired to the engine bay via 12-conductor 20 gauge shielded cable.

More details on the wiring connections in a future post.

Fuel Injector Logic 2010/05/01

Posted by Michael in 2JZduino.
add a comment

As one of the primary functions of 2JZduino, the fuel injector logic is required to lengthen or shorten the injector pulse from the stock ECU to either richen or lean the combustion mixture. With the Arduino I’ve set this up as a RPM/MAP lookup table to apply a percent scale to the injector pulse. More details below. Here also is a screenshot of a Windows application written to program the 2JZduino fuel injector scale map (early Alpha version).

ECU Injector Signals:

All injector on/off events arriving from the stock ECU are wired to External Interrupt pins of the Arduino (ports D and E for those that have read the ATmega1280 datasheet). The interrupt handlers are setup to handle both ON and OFF events, so the first step is to read the state of the injector signal that triggered the interrupt and store it in InjectorState. This “state” needs to be inverted, since the ECU is active LOW. In the firmware I track injectors as active HIGH.

Scale Calculations:

Next, the amount of scaling to apply to the injector signal is identified based on a value stored in InjectorScaleTable[][] (read from the Arduino Mega EEPROM at startup). This follows one of two paths. If additional fuel is requested the OFF signal is delayed, and if less fuel is requested the ON signal is delayed. Delaying the OFF signal is straightforward. When the OFF signal arrives, the time since the injector was turned ON is measured (InterruptTime – InjectorLastOnTime) and the offset time is calculated. Here the calculation is based on 1/128ths of the total injector pulse instead of % simply because binary math for dividing by 128 is *much* faster than dividing by 100.

Delaying the ON signal is a little more difficult. Imagine, you can’t delay the start of a pulse by 10% if you don’t first know what the pulse length is. I believe there is a reasonable approximation to be calculated based on the manifold air pressure. It seems to me the manifold air pressure is a direct measurement of the mass of air that is (or will be) in the combustion chamber. If the manifold pressure is 1 standard atmosphere, there are 3L of standard air in the 2JZ-GE engine for a single combustion cycle. Likewise if the manifold pressure is 0.5 standard atmospheres, there is only 1.5L of standard air in the engine for the combustion cycle. Knowing the stoichiometric amount of fuel required for the mass of air and also the flow rate of the fuel injectors, we can arrive at the expected pulse length of the fuel injector signal. And from this we can estimate how long 10% of that pulse would be. For 420 cc/min fuel injectors (the fuel injector size selected for this project) I’ve calculated there is approximately 0.4ms of injector pulse required for each 5 kPa of absolute air pressure in the combustion chamber. Again, for efficiency on the ATmega1280 processor the final relation for a 1% shorter injector pulse is approximated as ManifoldAirPressure / 4.

Event Scheduling:

Having calculated the delay for either the ON or OFF injector signal, the final step in the logic is to either execute or schedule the event. Scheduled events are placed into the Output Compare Interrupts on Timers 1 and 3 of the ATmega1280. These interrupts are generated when the value of the Timer matches a set value in one of 3 output compare registers (OCR A, B, and C). Each fuel injector has a dedicated Timer/OCR pair to handle it’s delayed events.

When a delayed event is scheduled, the time of the event is set (InjectorOCR), any pending interrupts are cleared (InjectorTIFR), and the interrupt is enabled (InjectorTIMSK).

// there are 5 other similar interrupt handlers for all 6 injectors
ISR(INT0_vect) { // Ext Interrupt 0 on D0

void h_InjectorChange(byte Inj_Index)
  unsigned int InterruptTime = *InjectorTCNT[Inj_Index];
  unsigned int EventDelay = 0;
  // Store the Injector signal value from ECU that triggered this Interrupt; signal is active LOW (@ GND when injector is ON)
  InjectorState[Inj_Index] = !(InjectorInputBitMasks[Inj_Index] & *InjectorInputPinRegisters[Inj_Index]);  // TRUE == ON, FALSE == OFF

  char TotalInjectorScale = InjectorScaleTable[EngineHzIndex][MAPindex];
  if (InjectorState[Inj_Index]) { // for ON events, perform Injector Pulse Contraction
    if (TotalInjectorScale >= 0) EventDelay = 0; // setup immediate execution of ON signals if there is no Contraction
    else {
      // Presuming that MAP (alone) is an effective measures the charge airmass in the combustion chamber...
      // For 420cc injectors on a 3L 6-cylinder engine, there is roughly 100 1/64_TimerTicks of fuel required for each 5 kPa of MAP (for a single injector)
      byte OnePercentTicks = (ManifoldAirPressure >> 2);  // Rough approximateion: OnePercentTicks = (MAP / 4 kPa)
      EventDelay = OnePercentTicks * abs(TotalInjectorScale); // estimate the # of TimerTicks to contract the pulse
    InjectorLastOnTime[Inj_Index] = InterruptTime + EventDelay;  // update the LastOnTime for this injector (either real or scheduled)
  else { // for OFF events, perform Injector Pulse Extension
    if (TotalInjectorScale <= 0) EventDelay = 0; // setup immediate execution of OFF signals if there is no Extension
    else EventDelay = ((InterruptTime - InjectorLastOnTime[Inj_Index]) >> 7) * TotalInjectorScale; // calculate TimerTicks to extend the pulse, using ">> 7" to approximate %age as x/128
  if (*InjectorTCNT[Inj_Index] - InterruptTime + MIN_OCR_OFFSET > EventDelay) h_ExecuteInjectorEvent(Inj_Index); // execute immediately if event is already/almost late
  else { // otherwise schedule the event for the future
    if (*InjectorTIMSK[Inj_Index] & InjectorOCRbitmask[Inj_Index]) GenerateErrorMessage("Inj Over-run!   ");  // check for OCR over-run
    // schedule Injector event on Timer 1/3, OCR A/B/C
    *InjectorOCR[Inj_Index] = InterruptTime + EventDelay;
    *InjectorTIFR[Inj_Index] = InjectorOCRbitmask[Inj_Index];
    *InjectorTIMSK[Inj_Index] |= InjectorOCRbitmask[Inj_Index];

Event Execution:

Executing the injector ON/OFF event is a matter of simply turning the output pin ON or OFF to control the gate of the 2JZduino MOSFETs. Note that the InjectorState value is referenced, which is stored in h_InjectorChange when the ECU signal first arrives. The effect is that the 2JZduino injector output is always set to the last value received by the stock ECU, and inherits some of the fail-safe features that might exist in Toyota’s firmware logic. If the stock ECU commands the injector OFF unexpectedly, the 2JZduino logic will set it’s output to the same, even if an intended ON event might be arriving in one of the timer OCRs (it will be interpreted as an OFF). For normal operation it is (reasonably) presumed that all ON events are fully processed before OFF events arrive. The function for controlling the injectors is shown below.

void h_ExecuteInjectorEvent(byte Inj_Index) // handles Timer 1/3 output compare A/B/C interrupts
      *InjectorTIMSK[Inj_Index] &= ~InjectorOCRbitmask[Inj_Index];  // disable Output Compare Match Interrupt
      *InjectorTIFR[Inj_Index] = InjectorOCRbitmask[Inj_Index];  // clear this specific interrupt flag
      if (!FuelCut && InjectorState[Inj_Index]) *InjectorOutputPorts[Inj_Index] |= InjectorOutputBitMasks[Inj_Index];  // turn injector ON if injector signal is ON (and FuelCut == FALSE)
      else {
        *InjectorOutputPorts[Inj_Index] &= ~InjectorOutputBitMasks[Inj_Index];  // otherwise turn injector OFF (normally because the Injector Signal is OFF)