Tag Archives: light

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;

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

    // 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);

Disassembly made by objdump:

       c:	91 26       	mov.w	0x5c,r1	! 9c1f
       e:	61 10       	mov.b	@r1,r1
      10:	60 1c       	extu.b	r1,r0
      12:	88 01       	cmp/eq	#1,r0
      14:	89 14       	bt	0x40
      16:	91 22       	mov.w	0x5e,r1	! b222
      18:	61 10       	mov.b	@r1,r1
      1a:	62 1c       	extu.b	r1,r2
      1c:	22 28       	tst	r2,r2
      1e:	8d 0f       	bt.s	0x40
      20:	e2 0f       	mov	#15,r2
      22:	91 1d       	mov.w	0x60,r1	! 9c53
      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:	8d 0c       	bt.s	0x48
      2e:	60 13       	mov	r1,r0
      30:	92 16       	mov.w	0x60,r2	! 9c53
      32:	22 10       	mov.b	r1,@r2
      34:	d1 0b       	mov.l	0x64,r1	! 945a8
      36:	02 1c       	mov.b	@(r0,r1),r2
      38:	91 13       	mov.w	0x62,r1	! 9c1e
      3a:	21 20       	mov.b	r2,@r1
      3c:	00 0b       	rts	
      3e:	00 09       	nop	
      40:	92 0f       	mov.w	0x62,r2	! 9c1e
      42:	22 10       	mov.b	r1,@r2
      44:	00 0b       	rts	
      46:	00 09       	nop	
      48:	92 0a       	mov.w	0x60,r2	! 9c53
      4a:	e1 00       	mov	#0,r1
      4c:	e0 00       	mov	#0,r0
      4e:	22 10       	mov.b	r1,@r2
      50:	d1 04       	mov.l	0x64,r1	! 945a8
      52:	02 1c       	mov.b	@(r0,r1),r2
      54:	91 05       	mov.w	0x62,r1	! 9c1e
      56:	21 20       	mov.b	r2,@r1
      58:	00 0b       	rts	
      5a:	00 09       	nop	
      5c:	9c 1f       	mov.w	0x9e,r12	! 600c
      5e:	b2 22       	bsr	0x4a6
      60:	9c 53       	mov.w	0x10a,r12	! e000
      62:	9c 1e       	mov.w	0xa2,r12	! e0ff
      64:	00 09       	nop	
      66:	45 a8       	.word 0x45a8

     5a8:	01 01       	.word 0x0101
     5aa:	00 00       	.word 0x0000
     5ac:	01 01       	.word 0x0101
     5ae:	00 00       	.word 0x0000
     5b0:	00 00       	.word 0x0000
     5b2:	00 00       	.word 0x0000
     5b4:	00 00       	.word 0x0000
     5b6:	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.


  • 2017-10: update code, syntax-highlighted disassembly, C++17
  • 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

DPF Management

Some parts of Denso’s complex Diesel Particulate Filter (DPF) algorithms are somewhat known by now. Will update with new results from time to time.
Link to page

Coolant Temperature

Like all or most engine related sensors, coolant temperature is being digitised by ECU. There’s a 2-dimensional table to convert measured sensor voltage to temperature result inside ROM:

Calibration curve is typical for a NTC (Negative Temperature Coefficient) thermistor.
Moving on to combination meter, coolant temperature lights are driven by ECU CAN output, precisely CAN-ID 0x600 byte 3. That byte contains coolant temperature, conversion formula is “x-40 [°C]“.
There are no additional flags for the dashboard lights. Meaning combination meter computer parses coolant temperature from CAN frame and decides on its own.

EXACT conditions, confirmed by testing: 1

Light Coolant
Boxer Diesel combination meter light when coolant is cold. < 50 °C (122 °F)
≥ 112 °C (234 °F)

A possible way to change stock thresholds is to modify ECU ROM resulting in faked 0x600 coolant temperature value output. This has already been tested working. However, other control units then read wrong temperature, too, resulting in possible side effects – especially if the offset is significant.

Coolant temperature certainly has effect on:

  • A/C (can hear actuator move depending on temperature)
  • BIU (copies data onto low speed bus)
  • Combination Meter

Didn’t have time to investigate low-speed CAN bus and BIU (Body Integrated Unit) yet. In theory, BIU could transform data from high-speed (ECU, VDC/ABS etc.) to low-speed bus instead of just copying it. Anyone knows? BIU is the gateway, the only control unit connected to both CAN busses. Combination meter is on low speed bus for sure.

1) No hardware has been harmed, we’ve used CAN injection/simulation.

Diesel ECU Patch v1

Finally, now that we are able to reflash diesel ECUs it makes sense to do some changes – or improvements as we think of it.
Couple of things already implemented and working perfectly:

  • Added SSM2 getter function Engine Load, using standard definition 0x000007, x*100/255 [%] – unused in stock diesel ROMs. As engine load value is being calculated internally anyway, also available as OBDII Mode 1 PID 0x04, it makes sense to provide this in SSM2 protocol, too. Very easy to do actually, just a single function pointer change – 4 bytes.
  • Added SSM2 getter function Gear, standard def 0x00004A, x+1 [-], unused in stock ROMs. Tiny function required because of +1 offset.

Btw, as with all SSM2 functions, the physical layer does not matter – any SSM2 function gets called via both serial (Euro4) and CAN connection. Diesel ECUs typically have SSM2 virtual address space 0x000000-0x00034F. That’s 848 function pointers, plus same amount in SSM2-write vector table. You could hook up a lot non-standard things as there are plenty of unused addresses…

  • Modified SSM2 init capability bits accordingly. That way above additional params do show up automatically on ALL SSM2 capable software (RomRaider, FreeSSM, SSM3, DashDAQ ?, …).
  • DPF light active regeneration flashing mode. Applies to closed-type DPF models only. Stock behavior (steady on and error flashing) is untouched, we managed to append own additional code. New code part checks if any stock DPF light mode is currently active. If not, it checks DPF regeneration flag, and only if true it performs this new flashing mode. It’s particularly useful at low engine speed – warning driver that additional turbo lag can occur. Remember, engine management reduces boost and manifold air pressure – sometimes a lot – when active regen is in progress.
    Update: got very positive feedback on this one. Even on a bright sunny day looking through the windshield, that blinking light on dashboard gets noticed immediatly. Whole thing is only a screen page of easy C++. Kudos to GCC, v4.6.x generated code is awesome – highly efficient.

Right now this is all internal, experimental, Linux-centric stuff. Availability of our services to the public will be discussed. It meant many months of full-time work to get this far, call for open source collaboration (less work for everyone) did not work…

DPF Light


  • Do consult your car manual! Unfortunately owner’s manuals lack useful details.
  • Please also read our DPF management page.
  • Same DPF light logic in Euro 4/5/6 ROMs.
  • DTC numbers differ across boxer diesel generations.

Applies To

All Boxer Diesel models fitted with closed-type Diesel Particulate Filter (managed DPF), emissions spec Euro 4, 5, 6.

Therefore does NOT apply to early diesel models equipped with open-type DPF (MY 2008/2009 Legacy/Outback). These do not have DPF management at all in software, no sensors, no DPF light, a lot less to worry about!


Constant light means “Soot High Warning“, also called “Vehicle Speed Request“.

According to manual, one should drive for at least 15 minutes at 60+ km/h, providing favorable conditions (exhaust gas temperature high enough) for active DPF regeneration to start (and ideally complete) in order to reduce soot.

Steady light is triggered by single condition:

  • Soot Accumulation Ratio > 85%

According to ROM logic, steady light will turn off when soot drops below 75% (hysteresis).

If there’s any error condition, see below, the error has higher priority, always resulting in flashing mode.


Flashing/blinking means “Error“, any of the four possible conditions can trigger this mode right away – without showing steady light before.
The manual usually states something like: go to Subaru dealership for inspection. Waiting too long might worsen the issue! Mileage driven with light ON can be recorded by ECU and read out with software!

  1. Oil Dilution Ratio ≥ 10% (also sets DTC P1468 Oil Dilution). Remember, as dilution is just a rough estimate, the value and therefore this error can easily be false! Might disappear when dilution drops below threshold due to calculated evaporation, see post Oil Dilution Graph. Dealership might have forgotten to reset oil dilution at oil change – seen this many times on forums.
  2. Compulsory regeneration required, also called “Dealer Visit Request“; e.g. Soot Accumulation ≥ 100%; ECU does not try active regen anymore unless soot drops to normal level
  3. Ash Accumulation Ratio ≥ 100% (also sets DTC Ash Overfill). Need to clean or replace DPF and tell the ECU to reset this value.
  4. DPF Limp-Home Mode (also sets DTC DPF Limp-Home Mode).

DPF Light Control

At ignition ON, the DPF light illuminates for two seconds exactly – light test –  and should stay off afterwards, indicating normal condition. Otherwise see above.

On Impreza Euro 4 / 5, ECU operates DPF light using CAN message 0x600 byte 0 bit 1, bit = 1 means light ON.

Euro 6 just seems to use different CAN IDs and frame contents.

Like we did in above GIF animation, flash rate is 400 ms/400 ms on/off (8/8 CAN messages at 50 ms interval), respectively. There are no other stock flash modes. In the future we’d like to use DPF light for additional purposes e.g. indicating active regeneration by implementing a different flash rate. We already did a patch using DPF light to indicate active regeneration: Diesel ECU Patch v1. For source code see post DPF Light Patch.


  • 2016-10 incorporate Euro 6 findings

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
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
      Wait(1000); // Brake OFF -> slow flashing, 1000 ms delay

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

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


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

Engine Oil Change (DPF models)

Owners want to be able to change oil by themselves obviously.
On closed-type DPF models (all except MY 2008/2009 Legacy/Outback which are mated with open-type DPF) you’ll need to reset oil dilution parameter as it is calculated (not measured!) by the ECU. Basically active DPF regenerations in particular cause diesel fuel bypass into engine oil. Piston rings cannot seal perfectly.

No auto-detection! Since Subaru diesels, unlike Mazda for example, are not equipped with an oil pressure sensor, its software cannot detect an oil change nor deteriorated engine oil. Subaru’s simple on/off oil pressure switch (near oil filter) is connected to the dashboard. There’s also a low/high oil level switch available on Euro 5/6 models which won’t help here, too, although this one is evaluated by complex ECU algorithm.

Following info as seen in all managed-DPF-ROMs so far.

DTC P1468 Oil dilution (MIL remains off) is being triggered when oil dilution ratio ≥ 10 %. Results in DPF light flashing , there is no warning in advance! If oil dilution gets below 10 % (estimated evaporation) the light will also turn off.

The engine management software assumes 6.13 kg oil mass and this maintenance operation resets RAM variable “Oil Dilution Amount [kg]” to zero.
The ROM’s internal calculation steps are:

  1. update OilDilutionAmount [kg] according to CoolantTemp, Injection Quantities etc.
  2. OilDilutionRatio [%] = 100 * OilDilutionAmount [kg] / 6.13 [kg]
  3. EstimatedDistanceToOilChange [km], via LUT

Also see posts Estimated Distance to Oil Change as well as Oil Dilution Graph.

Oil Dilution Reset

We’ve traced related Euro 4/5/6 ECU-ROM machine code. Here are all possible options:

Euro 4 (IV)

  1. Diagnostic protocol (SSM2 via Serial only). See post Oil Dilution Reset (K-Line, Euro4). There is no alternative method on Euro 4 models! (In theory, we could patch Euro 4 ROMs adding the same Euro 5/6 manual procedure as below. Not a trivial task, though, talk about hours of development and testing…)

Euro 5 (V), Euro 6 (VI)

  1. Diagnostic protocol (Extended OBD-II only since advanced stuff is not implemented in SSM2 anymore unlike Euro 4)
  2. Manual procedure, should be printed in the owner’s manual. The following table is derived from actual ECU software, confirmed in Euro 5 gen2/gen3 and Euro 6 ROMs:
    Step Description
    Prerequisites Brake Light
    • Whole procedure must be done while parking.
    • Engine must be running.
    • Parking brake recommended, vehicle speed must be zero all the time.
    1 Depress and keep holding brake pedal. (Slight pressure is sufficient).
    2 (Rear) defogger ON
    3 Lights ON (stage 1 = parking/clearance/DRL lights is sufficient)
    From here you only have 4 seconds to accomplish each of the following steps and roughly half a minute total countdown!
    4 Defogger OFF
    5 Lights OFF
    6 Defogger ON (again)
    7 Lights ON (again)
    8 Defogger OFF
    9 Lights OFF
    10 Release brake pedal.
    Now the glow light (depicting yellow coil) will flash for 5 seconds indicating full success.
    Otherwise wait for half a minute at least, then try again, repeating the whole procedure.

    Many users have confirmed this procedure, e.g. Subaru Forester Owners Forum – Oil change on Diesel engine and ECU service reset.

    Wanted – please contribute:

    • dashboard video to confirm glow light flashing modes
    • picture of car owner’s manual page describing reset procedure

Other Brands

Mazda SkyActiv-D 2.2 (CX-5)

“Engine Oil Data Reset”, either via OBD or manual procedure (test terminal & accelerator pedal).

ECU software contains algorithms that can detect deteriorated engine oil via oil pressure sensor, there are several DTCs.