Category Archives: Impreza Diesel

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-2017)   https://subdiesel.wordpress.com

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

using namespace std::chrono_literals;

/**
 * @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)
 *
 */
const constexpr auto interval {50ms};
/**
 * @brief stock period for flashing mode is 800 ms,
 *        does not have to match stock here
 *
 */
const constexpr auto dpfLightPeriod {800ms};
/**
 * @brief defines DPF light output over time when active regeneration is on
 *
 */
const constexpr std::array<bool, dpfLightPeriod / interval> dpfLightCustomPattern
{
    1, 1, 0, 0,  1, 1, 0, 0,
    0, 0, 0, 0,  0, 0, 0, 0
};
// tested alternative: bitset; storage-efficient but much more lookup code
// const std::bitset<dpfLightPeriod / interval> bits {"1100110000000000"};

/**
 * @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 SubaruDieselCrew (2011-2017)   https://subdiesel.wordpress.com
/*
	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);
…


SuperH disassembly using objdump which is part of GNU binutils. Binary had been generated by GCC.


void calc_DPFLight_continue()
c:  91 1d   mov.w   0x4a,r1 ! 9c1f
e:  60 10   mov.b   @r1,r0
10:  88 01   cmp/eq  #1,r0
12:  8d 17   bt.s    0x44
14:  71 ff   add     #-1,r1
16:  91 19   mov.w   0x4c,r1 ! b222
18:  61 10   mov.b   @r1,r1
1a:  21 18   tst     r1,r1
1c:  8d 0e   bt.s    0x3c
1e:  62 13   mov     r1,r2
20:  91 15   mov.w   0x4e,r1 ! 9c53
22:  e2 0f   mov     #15,r2
24:  61 10   mov.b   @r1,r1
26:  71 01   add     #1,r1
28:  61 1c   extu.b  r1,r1
2a:  31 26   cmp/hi  r2,r1
2c:  8f 02   bf.s    0x34
2e:  60 13   mov     r1,r0
30:  e0 00   mov     #0,r0
32:  e1 00   mov     #0,r1
34:  92 0b   mov.w   0x4e,r2 ! 9c53
36:  22 10   mov.b   r1,@r2
38:  d1 06   mov.l   0x54,r1 ! 945c0
3a:  02 1c   mov.b   @(r0,r1),r2
3c:  91 08   mov.w   0x50,r1 ! 9c1e
3e:  21 20   mov.b   r2,@r1
40:  00 0b   rts
42:  00 09   nop
44:  21 00   mov.b   r0,@r1
46:  00 0b   rts
48:  00 09   nop
4a:  9c 1f
4c:  b2 22
4e:  9c 53
50:  9c 1e
54:  00 09
56:  45 c0

5c0:  01 01   .word 0x0101
5c2:  00 00   .word 0x0000
5c4:  01 01   .word 0x0101
5c6:  00 00   .word 0x0000
5c8:  00 00   .word 0x0000
5ca:  00 00   .word 0x0000
5cc:  00 00   .word 0x0000
5ce:  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). Apart from ROM specific variable addresses the same code will work for all known Euro 4/5/6 models.

Resultant binary data is meant 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

  • 2017-04: minor update, actual disassembly
  • 2016-11: updated source to C++14
  • 2016-10: updated source to C++11 with Doxygen documentation
  • 2016-01: added disassembly

ECU Coding: Ports v2

The engine control unit supports uploading code into RAM because that’s part of standard engine management software (ROM reflash) update procedure. I also use the transfer via CAN method as it is quite fast.
Actually, the ECUs stock firmware does not care about the uploaded bytes as long as generic conditions are met (max 12 KiB total size on diesels, checksum, …). That way you can abuse the system and do what you want. Did some relay/actuator testing lately, confirming some ports…

Tested ports

Valid for MY 2009/2010 Impreza Diesel Euro 4 only. Euro 5 models differ!

Port Type Function Comment
Port E
PE02 Out Radiator Fans both, low power
PE03 Out Radiator Fan left one only, high power
PE02 & PE03 Out Radiator Fans both, high power
PE11 Out Sub Fuel Pump noise originating from fuel tank area
PE12 Out A/C Compressor Clutch loud click noise, looking at pulley one can see clutch part move
PE14 Out MIL MIL
Port L
PL06 In Brake SW Almost same trigger point (pedal position) as stop light switch.
PL07 In Stop Light SW

I already knew those output ports from ROM analysis so it was rather safe for me to try them. Just reading any port is supposed to be safe.
Many are reversed, then bit ‘0‘ means ON and ‘1‘ is OFF.
The MIL is what I often use for debugging ECU code – e.g. flashing it to indicate some condition. Unlike most dashboard indications you don’t operate it using CAN messages (much more complicated).
However, ports can be model specific so one must be careful – i.e. might hit the starter with transmission not in neutral.

C/C++ Code snippet to test and operate a port


// Port E Data Register, from Renesas manual
#define PE_DR_w (uword*)0xFFFFF754
// Port L Data Register
#define PL_DR_w (uword*)0xFFFFF75E

// port bitmasks
const uword PE14_MIL = 1 << 14; // 0x4000
const uword PL06_BrakeSW = 1 << 6; // 0x40

void OperatePorts()
{
  for(;;)  // infinite loop
  {
    ToggleBits(PE_DR_w, PE14_MIL);  // toggle MIL
    uword pldr = *PL_DR_w;  // read Port L Data Register
    if ((pldr & PL06_BrakeSW) == 0)  // test bit
      Wait(100);  // Brake ON -> fast flashing of MIL
    else
      Wait(1000); // Brake OFF -> slow flashing, 1000 ms delay

    /* alternative, set (true) or clear bits (false):
    AdjustBits(PE_DR_w, PE14_MIL, true); // OFF
    Wait();
    AdjustBits(PE_DR_w, PE14_MIL, false); // ON
    Wait();
    */
  }
}

void ToggleBits(uword* address, uword bitmask)
{
  *address ^= bitmask; // XOR
}

Compilation

Questions: What software can I use to compile SuperH code? How much $$$?
Answer: Linux and utilities, all open source and free!

Free open source GNU compiler collection (GCC) can generate binaries for Renesas SH-2E, the microcontroller’s (e.g. SH7058S) CPU inside the engine control unit.
The beauty of GNU binutils plus GCC is, you can use the same toolchain to produce code for tons of different platforms1.
So same stuff I use for producing Intel/AMD PC x86/x64 software plus some platform specific command line options will do it. GCC is very powerful, supports multiple source code languages. (Personally, I even compile small Windows tools directly on Linux using winelib.)
Uploading a binary via CAN is another story but once it is automated, you don’t have to think about it, e.g. just run a make command…

1) Compiling binutils and GCC from source with target platform support enabled might be necessary. Default Linux packages usually have not been compiled with such special platform support enabled.

Display a list showing all architectures and object formats available for specification with -b or -m:
objdump --info

Power Graphs and Calculations v2

Updated 2011-01-16: corrected data, added details

Official graphs

These are official Boxer Diesel power graphs from Subaru press info, dated 2008:

Digitizing with Inkscape, zoomed in, bitmap layer in background:

Saved as SVG (Inkscape’s default format) coordinates are plain readable. Some conversion had to be done – SVG origin is top left, scaling, relative coordinates etc.

Digitized torque data


Engine Speed [1/min],Torque [Nm]
1193.55,202.578
1313.36,249.619
1443.78,298.872
1472.12,307.111
1518.49,317.562
1555.84,326.697
1575.2,334.011
1617.03,340.603
1669.37,345.07
1736.66,347.988
1808.8,349.379
2383.36,349.662
2421.46,349.484
2461.55,348.549
2522.98,346.099
2650.91,340.182
2798.88,332.502
2994.03,324.842
3300.6,312.663
3478.52,304.245
3661.72,288.783
3787.83,275.768
3830.03,268.687
3878.16,261.977
4129.99,232.604
4232.87,220.772
4372.64,202.82
4419.5,195.276

Function plot, linear interpolation:

Digitized power data


Engine Speed [1/min],Power [W]
1201.24,25496.7
1419.95,43784.2
1565.12,55739.5
2025.77,73824.5
2310.73,84411.4
2376.1,86717.
2436.11,88110.1
2548.37,91706.8
2941.28,100369.
3125.55,104148.
3278.06,107158.
3442.58,109944.
3514.2,110805.
3589.68,110856.
3690.33,110450.
3835.49,108829.
3947.75,106904.
4058.08,104219.
4149.05,100927.
4261.31,95607.5
4347.44,91681.5
4396.8,89908.4

Since power can be calculated from torque and angular velocity, this can be used to check the official power curve. Looks like someone put “lipstick” on the last part of the curve…

Drive force

After applying some math (torque curve, vehicle speed vs. RPM, gear ratios)…

View above graph as SVG (best quality).

Theoretical shift points

Calculated intersection points (drive force equality solutions), slightly rounded:

v [km/h] Gear1 RPM1 [1/min] Gear2 RPM2 [1/min]
75 2 4340 3 2630
116 3 4070 4 3010
151 4 3920 5 3170
181 5 3800 6 3340

ROM Differences JZ2F401A vs JP3F501A

Those MY2009/10 Impreza (110 kW) and Forester (108 kW) ROMs are almost identical.

Binary comparison result: Only 1292 bytes differ.
Spreadsheet (exported from OpenOffice.org Calc) can be downloaded here: Impreza_JZ2F401A_vs_Forester_JP3F501A.xls.zip. Rename to .zip and extract! Update: File position listed by cmp starts at one instead of zero. This hasn’t been corrected in the XLS file yet.

How-To

Difference list was created using standard Unix command cmp, then imported into Spreadsheet application.
Unix/Linux terminal commands:


file1='2009_2010_Impreza_2.0_Diesel_6MT_EDM_JZ2F401A.rom'
file2='2009_2010_Forester_2.0_Diesel_6MT_EDM_JP3F501A.rom'
cmp -l "$file1" "$file2" > Impreza_JZ2F401A_vs_Forester_JP3F501A.txt

Important: cmp outputs file position in decimal, starting at one, content bytes in octal!
Result looks like this, three columns: address (dec), byte1 (oct), byte2 (oct)

16389 132 120
16390 121 106
16391 62 63

1047482 127 114
1047483 66 203
1047484 244 11

Apps like OpenOffice.org Calc can import such text file. Then you might want to use available functions like DEC2HEX, OCT2HEX, CHAR, … for number conversions.

Updated RomRaider Logger Definitions

Added some more RAM values for Impreza Diesels. Link to page.

Injection Quantity Limit (Gear)

Note: Following 3D table only represents a small part of the calculations the firmware performs. Lots of code plus data determine target torque and injection quantity. We currently don’t have the resources in order to analyze the whole thing.

3D-map, valid for these Boxer Diesel models at least:

Submodel CID ROMID PAK
2009 Impreza JZ2F302A 66 44 D8 71 07 22611AP282
2009/2010 Impreza JZ2F401A 66 44 D8 72 07 22611AP283
2009/2010 Forester JP3F501A 61 44 D8 72 07 22611AP203

So far content is 100% identical in those. Data type is uint8.

2008 Legacy J2TF230A, ROMID 58 44 D8 71 07, has minor differences. Ex: f(850, 6). Data type is int16.

For XML file go to page RomRaider ECU Definitions

Gear Number OpenOffice Calc Macro

Derived from engine speed vs. vehicle speed math, here’s an OpenOffice.org Basic macro calculating gear numbers. Again values are for EDM Impreza 2.0 Diesel.

OpenOffice.org Calc using macro formula


' Usage: GearNum(Engine Speed [rpm], Vehicle Speed [kph])
' Does not require clutch and neutral input
' but yields invalid values in neutral and when clutch depressed!
Function GearNum(ByVal rpm, ByVal velocity)
  Const Invalid = ""
  If Not IsNumeric(velocity) Or velocity < 1 Or Not IsNumeric(rpm) then
    GearNum = Invalid
  Else
    Dim factor As Double
    factor = rpm / velocity
    Select Case factor
      ' range values calculated using 5% tolerance
      Case 108.669 To 120.108
	      GearNum = 1
      Case 55.0583 To 60.8539
	      GearNum = 2
      Case 33.4125 To 36.9296
	      GearNum = 3
      Case 24.6976 To 27.2973
	      GearNum = 4
      Case 19.9468 To 22.0465
	      GearNum = 5
      Case 17.5243 To 19.3689
	      GearNum = 6
      Case Else
	      ' neutral or clutch depressed
	      GearNum = Invalid
    End Select
  End If
End Function

If you always log clutch and neutral switches you could add those as inputs to the macro. Then check (if…then..) to prevent invalid output.