Tag Archives: Euro4

Log Graphs 1

Specification

Car: 2009 Impreza 2.0 Turbo Diesel, 110 kW / 148 hp / 150 PS, European domestic market, Euro 4 spec.

Important: Newer Boxer Diesel generations (Euro 5/6) may show different behaviour!

ECU firmware: patched ROM for unlimited logging, otherwise stock.
Protocol: SSM2 via CAN
More than 120 items had been logged, plenty of RAM variables (*) plus standard SSM2 items at roughly 170 ms interval.

Graphs

I prepared two graph images plotting some interesting parameters, click picture for full resolution:


Image 1/2

loggraphs1_p1


Image 2/2

loggraphs1_p2

Additional details

… at specific time positions:

#1: State before active regeneration

Time @ ~ 33 ½ min
Cruise Control: active
Vehicle Speed*: 110 km/h
Engine Speed: 2300 rpm
Gear: 5
Coolant Temperature: 93 °C
Injections: 2 (pre + main)
Soot Accumulation Ratio: 64 %
DPF Pressure Difference: ~ 5 kPa
Exhaust Gas Temperature (EGT) Catalyst Inlet: 345 °C
EGT DPF Inlet: 370 °C
Intake Air Amount: 430 mg/cyl
Mass Air Flow: 33.8 g/s
Manifold Absolute Pressure (MAP)*: 126 kPa
Inlet Air Temperature: 25 °C
Manifold Air Temperature: 48 °C
Fuel Temperature: 58 °C
Throttle Opening Angle: 79 deg
EGR Valve Opening Angle: 38 deg
Final Oil Dilution Rate*: -1.9 mg/s (evaporation)
Oil Dilution Amount: 282.0 g (4.6 %)

#2: Soot 65%, preparing for DPF regeneration

@ 33 ¾ min
Soot Accumulation Ratio* reaches 65%, this triggers active regeneration preparations. Note: I am referring to the actual RAM value, diagnostic parameter may indicate 65% earlier due to rounding.
Apparently ECU now does 3 injections (pre + main + after) when power demand is high enough, no post injections yet

#3: Active Regen ON

@ 34:04; = 20 seconds after #2, DPF Regeneration Switch turns ON
EGR valve closes instantly, 0 deg
Manifold Air Temperature dropping
Boost Control opens VGT immediately, from 52 to 25%
Manifold Absolute Pressure* dropping
pilot-injection kicks in, 2 injections (pilot C + main)
post-injections begin to fade in but not active yet

#4: Post-Injections

@ 33 seconds after #2, post-injections A + B become operational (injection amount > 0)
oil dilution rising
EGTs climbing
Manifold Absolute Pressure*: 75 kPa (~ 25 below ambient), varying, stays below ambient most of the time during regen at low power demand
EGT Catalyst Inlet: 340 °C
EGT DPF Inlet: 320 °C
injections: 4 (pilot C + main + post A + post B)

#5: Coasting Fuel Cut-Off During Regen

Example @ 35:12
EGR valve opens instantaneously, 70 deg; EGR behaves rather digitally during regen – either fully closed (0 deg, for max EGT) or max opened (70 deg, less fresh air)
Throttle Opening Angle: rising up to 31 deg
injections: 0
Fuel Consumption*: 0 mm³/s
oil dilution going down slowly due to estimated evaporation
EGT Catalyst Inlet: decreases fast, EGT at DPF inlet (> 600 °C) follows with a delay
Engine Speed: gear change from 5th to 6th to reduce engine braking effect

#6: Idling with Active Regen ON

@ 45:30
Engine Speed: 800 rpm
Post A Injection Amount*: 0
Post B Injection Amount*: ~ 8 mm³/st
Apparently while idling the ECU prefers the 2nd post-injection (“B”), otherwise during driving it’s rather mixed
Final Oil Dilution Rate: ~ 20 mg/s (medium)
Boost Control: 25 % (VGT fully open)
Manifold Absolute Pressure*: 74 kPa
Throttle Opening Angle: 5.5 deg
EGR Valve Opening Angle: 0 deg
Intake Air Amount: 370 mg/cyl
Mass Air Flow: 9.8 g/s

#7: DPF Regeneration OFF

@ 46:15, decision is based on elapsed time from #2, achieved soot level does not matter (!)
DPF Regeneration SW had been ON for 12.2 minutes
all post-injections off
Oil Dilution Amount*: 299.6 g (4.9 %) = + 0.3 % during regeneration
EGR back to normal operation
Boost Control: 65 % instantly (max speed, spooling up turbo)
MAP rising
EGT Catalyst Inlet: 270 °C
EGT DPF Inlet: 490 °C

DPF Light Patch

This is meant for programmers or at least folks who understand coding in general. Here I am going to show how I implemented the “DPF light patch”, part of Diesel ECU Patch v1, on my former (Euro 4) car.
DPF Light Patch - Active Regeneration In Progress


Actual source code, updated to C++14:


// Copyright SubaruDieselCrew (2011-2016)	https://subdiesel.wordpress.com
// c++14

#include <array>
#include <chrono>
#include "sh.h"
#include "JZ2F401A.h"

using namespace std::chrono;

/**
 * @brief DPF light flashing modes (stock)
 *
 */
enum class DPFLightMode {
    off = 0,
    /**
     * @brief soot-high warning aka vehicle speed request
     *
     */
    on_steady = 1,
    /**
     * @brief error
     *
     * (multiple causes: compulsory regeneration required, oil dilution critical, ash overfill, DPF limp-home mode;
     * see https://subdiesel.wordpress.com/2011/03/21/dpf-light/ )	 *
     */
    flashing = 2,
};

/**
 * @brief time resolution (= CAN frame ID 0x600 interval)
 *
 */
constexpr auto interval {50ms};
/**
 * @brief stock period for flashing mode is 800 ms,
 *        does not have to match stock here
 *
 */
constexpr auto dpfLightPeriod {800ms};
/**
 * @brief defines DPF light output over time when active regeneration is on
 *
 */
constexpr std::array<bool, dpfLightPeriod / interval> dpfLightCustomPattern
{   {   1, 1, 0, 0,  1, 1, 0, 0,
        0, 0, 0, 0,  0, 0, 0, 0
    }
};

/**
 * @brief Implement custom flashing mode.
 *
 * Called every 50 ms (CAN-ID 0x600 interval)
 * from patched stock function "calcDpfLight".
 * Standard error flashing mode already handled by untouched
 * stock subroutine portion and this case this function won't get called.
 */
void calc_DPFLight_continue()
{
    // needed as original functionality has been overwritten for hook instructions
    if (DPFLightMode(*DPFLightModeEnum_b) == DPFLightMode::on_steady)
    {
        *DPFLight_bool = true;
        return;
    }

    // at this point DPFLightMode == DPFLightMode::off
    if (!*DPF_Regeneration_bool_SSM)
    {
        *DPFLight_bool = false;
        return;
    }

    // at this point active DPF regeneration is ON, do custom flashing
    // reusing DPF light counter var is safe
    auto counter = *DPFLightCounter_b;
    if (++counter >= dpfLightCustomPattern.size())
        counter = 0;
    *DPFLightCounter_b = counter;
    *DPFLight_bool = dpfLightCustomPattern.at(counter);
}



// Copyright (c) 2011 SubaruDieselCrew

/*
	For stock ROM:
	Model	2009/2010 Impreza Turbo Diesel 2.0 6MT EDM 110 kW / 150 PS
	ROMID	6644D87207
	CID		JZ2F401A
	CVN		F5AD7142 FB841734
	PAK		22611AP283
*/

#ifndef JZ2F401A_H
#define JZ2F401A_H

#include "diesel_rom.h"

// RAM vars
static auto const DPFLight_bool = reinterpret_cast<volatile bool*>(0xFFFF9C1E);
static auto const DPFLightModeEnum_b = reinterpret_cast<volatile int8_t*>(0xFFFF9C1F);
static auto const DPFLightCounter_b = reinterpret_cast<volatile uint8_t*>(0xFFFF9C53);
static auto const DPF_Regeneration_bool_SSM = reinterpret_cast<volatile bool*>(0xFFFFB222);
…


Disassembly using objdump which is part of GNU binutils. Binary had been generated by GCC. I added quite verbose comments for those who don’t know SH (SuperH) disassembly well enough.


void calc_DPFLight_continue()
  c: 91 1b  mov.w   0x46,r1 ! 9c1f   // r1 = &DPFLightModeEnum_b = 0xFFFF9C1F (sign extension of value 0x9c1f)
  e: 60 10  mov.b   @r1,r0           // r0 = DPFLightModeEnum_b
 10: 88 01  cmp/eq  #1,r0            // DPFLightModeEnum_b == ONsteady ?
 12: 8f 02  bf.s    0x1a             // if not --> jump
 14: 71 ff  add     #-1,r1           // r1 = 0xFFFF9C1E = &DPFLight_bool
 16: 00 0b  rts                      // return (after following instruction)
 18: 21 00  mov.b   r0,@r1           // DPFLight_bool = true

 1a: 92 15  mov.w   0x48,r2 ! b222   // r2 = &DPF_Regeneration_bool_SSM = 0xFFFFB222
 1c: 62 20  mov.b   @r2,r2           // r2 = DPF_Regeneration_bool_SSM
 1e: 63 2c  extu.b  r2,r3            // r3 = (uint8)DPF_Regeneration_bool_SSM
 20: 23 38  tst     r3,r3            // DPF_Regeneration_bool_SSM == false ?
 22: 8d 0e  bt.s    0x42             // is false --> DPFLight_bool = false; return
 24: 00 09  nop
 26: 91 10  mov.w   0x4a,r1 ! 9c53   // r1 = &DPFLightCounter_b = 0xFFFF9C53
 28: e2 0e  mov     #14,r2           // r2 = CounterMax - 2 = 14
 2a: 60 10  mov.b   @r1,r0           // r0 = DPFLightCounter_b
 2c: 60 0c  extu.b  r0,r0            // r0 = counter = (uint8)DPFLightCounter_b
 2e: 30 26  cmp/hi  r2,r0            // counter > 14 ?
 30: 8d 02  bt.s    0x38             // if yes --> DPFLightCounter_b = 0
 32: 70 01  add     #1,r0            // ++counter
 34: a0 01  bra     0x3a             // --> DPFLightCounter_b = counter
 36: 60 0c  extu.b  r0,r0
 38: e0 00  mov     #0,r0            // r0 = 0
 3a: 21 00  mov.b   r0,@r1           // DPFLightCounter_b = r0
 3c: d1 04  mov.l   0x50,r1 ! 943c0  // r1 = &table[0]
 3e: 02 1c  mov.b   @(r0,r1),r2      // r2 = table[counter]
 40: 91 04  mov.w   0x4c,r1 ! 9c1e
 42: 00 0b  rts
 44: 21 20  mov.b   r2,@r1           // DPFLight_bool = r2; return
 46: 9c 1f                  // 0x9c1f
 48: b2 22                  // 0xb222
 4a: 9c 53                  // 0x9c53
 4c: 9c 1e                  // 0x9c1e
 4e: 00 09  nop
 50: 00 09                  // 0x943c0 --> blob position 3c0
 52: 43 c0

3c0: 01 01  .word 0x0101    // table[16] = { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
3c2: 00 00  .word 0x0000
3c4: 01 01  .word 0x0101
3c6: 00 00  .word 0x0000
3c8: 00 00  .word 0x0000
3ca: 00 00  .word 0x0000
3cc: 00 00  .word 0x0000
3ce: 00 00  .word 0x0000


As you can tell there is not much code required. Much more work, orders of magnitude (!), is necessary to reverse-engineer the related stock ROM portions in the first place, defining functions, disassembling machine instructions, naming local and global variables etc.

Usually, ROM and RAM addresses depend on the actual ROM version used. Above definitions work for outdated CID JZ2F401A (dated 2009-Sep). Same code should also work for all current Euro 4, 5 and 6 models.

Compiled binary generated from above source code is to be inserted into a free unused ROM region. On Renesas SH microprocessors (i.e. SH7058S) free ROM space is rather easy to find – just look for big chunks of continuous FF-bytes. This is because those chips erase bytes to value 0xFF. Others, e.g. Infineon TriCore series, erase their internal flash ROM to zeroes instead.

To actually make use of the added logic, I had to modify (patch) a few bytes in the original calc_DPF_light subroutine, so that after doing some of its work it will call my own function, knowing its start address (0x9400C). Usually there is no free space in between stock functions, therefore we have to apply clever patching tricks to make room for a few new instructions and/or divert execution flow.

Finally, after carefully verifying the changes applied to the stock ROM, you have to correct checksums. Flashing software usually does this anyway, perhaps asking first. Checksum correction and verification is actually very easy to do for such Denso firmware.

Providing SDC-modified ROMs is possible, however will not be free due to amount of labour involved. Contact us if you’re interested.

Updates

  • 2016-11: updated source to C++14
  • 2016-10: updated source to C++11 with Doxygen documentation
  • 2016-01: added disassembly

RomRaider Logger Status

Kudos to RomRaider for including diesel definitions!
Especially interesting for people driving both petrol and diesel cars, no need to use separate definition files.

Links to RR pages:
Latest app version: RomRaider 0.5.3b RC9
Latest definitions: (2011-04-04) Experimental Logger Definition Updates

The application can only do SSM2 via Serial, no CAN protocol(s) support for Euro5 diesels I’m afraid. TCU support has been added, too, but then again, diesels only have MT.

We’ve spotted some extended diesel parameter errors, easy to fix in XML, though. Still safe to use, won’t hurt ECU, just expect some weird data. Will upstream any corrections to RR source of course…

Injector Learning / Calibration

Applies to all known Boxer Diesel models, Euro 4/5/6.
Injector learning (micro quantity calibration) procedure can improve engine idling (vibrations, noise, fuel economy …). The ECU measures engine speed changes, derives learning values and uses them (for idling only?) from there on.

Tiny fuel injections, achieved through very short pulses, are especially sensitive to mechanical wear inside injectors. Remember, the ECU does not know how much fuel is actually being sprayed into the cylinders. However, by measuring acceleration (crankshaft speed changes) after at each cylinder’s power stroke, it can adjust injection pulses to achieve a balance across cylinders.

Normally, a power stroke consists of two or three individual injections, main injection always being the largest one. For micro quantity calibration, the software does things differently. It divides the necessary fuel to hold engine speed into more and evenly small injections so that only short pulses matter and differences in injected amounts can be compensated.
The entire procedure is done in two ways:

  1. Automatically by ECU when idling (~800 rpm), for example at traffic light stop. Takes roughly a minute to complete. Goes through first 4 pressure levels. ECU will try again if it could not finish.
  2. Compulsory using Subaru dealer software (open source solution is planned). In addition, this mode uses higher common rail pressure, revving the engine up to ~1,200 rpm at 5th pressure level which is not acceptable in auto-mode.

Injector Learning Chart

Either way, the operation causes distinct engine noise due to special injections, easy to recognize. Also altering common rail pressure to the next pressure level causes noise to change.

You can use logging (Subaru specific protocols) in order to monitor related parameters:

  • injector learning status (ON/OFF)
  • injection quantity learning values [ms] (4 cylinders times 5 pressure levels = 20 values; all zeroes means learning has not been completed successfully)
  • mileage after last learning [km]
  • mileage after injector replacement [km]

As usual, all relevant values are being saved into EEPROM.

Automatic Injector Learning Intervals

In theory, ECU wants to do automatic injector learning at these predefined intervals:

# Odometer
[km]
Interval
[km]
1 50 50
2 200 150
3 350 150
4 950 600
5 1,550 600
6 4,550 3,000
7 7,550 3,000

Confirmed by disassembly from Euro 4/5/6 software. As you can see, the interval is being increased up to 3,000 km.

Above odometer values are theoretical because:

  • For the ECU mileage after last learning matters, not any absolute odometer value.
  • If conditions are not met, learning process is being delayed, trying again at next occasion, increasing all further odometer numbers.
  • ECU internal odometer is not very precise, it will drift off from combination meter odometer over time.

Injector Learning Conditions

Basically, engine must be warm, around 80°C coolant temperature. Lots of parameters must be within pre-defined ranges (fuel temperature, accelerator not depressed, battery voltage 12..15 Volts, …).

Video Links

Courtesy of contributors posting their videos.

Automatic learning

Compulsory

Tested

  • Adjusting internal variable “mileage after last learning”, i.e. setting to 3000+ km triggers automatic calibration ASAP.
  • Adjusting ECU odometer, i.e. to re-synchronize with dashboard value.