Of ECU's and Electronics

Tuesday, July 2, 2019 - 11:45

Welcome to DiscoTd5.com, a site dedicated to the Land Rover Td5 Engine. The DiscoTd5.com website springs from the interests and experiences of my ownership of a Td5 Discovery which I purchased in May 2011. Since I first set up the site, my interests have become heavily focused on reverse engineering the Td5 ECU firmware, and I'd expect this focus to remain for the foreseeable future.

Donated but didn't get an account?

ECU Read and Save

Friday, September 3, 2021 - 19:00

This is a short demo of the ECU read and file save functionality of an app I'm currently working on.

The app is designed with Td5 maps in mind.
By default downloads target the exact length of the stock tune and optionally variant.
This behaviour can be overridden by using the Read Full Blocks option which forces reads of entire flash content for both tune and variant maps.
In most cases a minimal read of the tune is all that is required.

The progress bars adjust depending on selected options.
Progress reflects the percentage read of the total download.

One of the features is the ability read a 16kb tune and save a full 262kb bin with correct boot map and variant. Programming record is update with the current date and metadata for the loaded tune.

Injector Codes - programming

Thursday, March 11, 2021 - 16:45

Time for some gratuitous video....

This is basically a test GUI for Injector programming code I've been working on over the last month.

My code had been put on the back burner when I realised I needed access to Rovacom, Nanocom and a tool made by Omitec (Britpart Lynx or Hawkeye) to get enough points of reference to be confident of correct handling.

Fortunately Nicky @ Rovertech has access to all three and was kind enough to send me pics of how all three devices handled two specific test cases.

The main things happening in the video are:

  • injector codes being read from an NNN ECU running an 15P/EU3 map
  • codes validated and highlighted in red if incorrect on enter/return
  • codes converted to numeric using A =0 for 10P, M=8 and U=0 for 15P.
  • codes programmed when "Update" pressed
  • codes read from ECU after update and used to refresh display

U=0 is a made-up "uncoded" code reflecting the fact that 0 is the default state for new ECU's and a substitute value for corrupted idle codes. I think explicitly flagging this is more informative than leaving the code blank as the Hawkeye does.

Obviously there needs to be some kind of feedback that the codes have been programmed...

Td5 Injector Idle Codes

Saturday, March 6, 2021 - 11:30

Given my recent posts about bugs in the Nanocom's handling of injector idle codes I thought I'd pull together some background information.


The documentation on injector codes indicates that the injector idle letter codes map to numeric values. These numeric values are 0-based indexes for idle adjust tables which are located in the fuel map. Each idle code maps to an adjustment ranging from -0.075ms to +0.075ms in EU2 maps, and -0.047ms to +0.047ms in EU3 maps.

ECU Hardware

At the most basic level the MSB and NNN have different limitations on the value that can be saved.

The MSB masks the values sent by the diagnostic tool by performing the equivalent of modulo 4 of the value sent by the diagnostic tool. The effect is that if you send an idle code of 5 to an MSB it saves 1 to eeprom.

The NNN masks values with the equivalent of modulo 16, meaning it can save values In the range of 0-15 to eeprom.

10P maps and NNN

Regardless of whether running on MSB or NNN hardware 10P (EU2) maps mask the injector code to modulo 4.

This snippet in python demonstrates the effect. % is the modulo operator.

mask = 4
for i in range(9):
    print(f"{i} modulo {mask} = {i % mask}")
0 modulo 4 = 0
1 modulo 4 = 1
2 modulo 4 = 2
3 modulo 4 = 3
4 modulo 4 = 0
5 modulo 4 = 1
6 modulo 4 = 2
7 modulo 4 = 3
8 modulo 4 = 0

So while the diagnostic code will allow programming of 15P idle codes to an NNN running a 10P map, the map will use the 0-3 10P coding.

The effect is that the 10P coding is repeated for the higher 15P codes...
15P: 1, 2, 3, 4, 5, 6, 7, 8
10P: 1, 2, 3, 0, 1, 2, 3, 0

Code for the map, not the ECU

It should be clear from the above that injector codes are interpreted based on the map being run rather than "earlier" or "later" ECU's. The ideal here is to use the correct map for the injector type and engine, and set the idle codes accordingly.

Idle adjust tables

I mentioned earlier that the idle codes are index values. So let's look at what that means in practice.

10P idle

I briefly had the hidden 10P idle map defined in the XDF's but removed as it was causing significant confusion.

For reference this is what the idle adjust table looks like:

The x-axis is rpm, and y-axis is the numerical idle code for black/blue top injectors. What we can see from this is that idle codes 0 and 3 are both 0ms adjustment across the board. So the equivalence of A = 0,3 makes perfect sense. Index 1 is a positive adjustment, ranging from 0.075ms at 300rpm, 0.030ms at 800rpm and 0ms at 1000rpm. Index 2 is a negative adjustment which is the mirror of Index 1: - 0.075ms at 300rpm, -0.030ms at 800rpm and 0ms at 1000rpm.

Interpolating the values indicates that the adjustment at 760rpm is +/- 0.033 ms depending on code 1 or 2.

You'll notice that In 10P maps idle adjustment has no effect above 1000rpm.

15P idle

The 15P maps have the idle adjust map defined as a standard table.

This is obviously very different to the 10P map. The rpm axis is gone and there are now 10 columns with indexes in the range of 0-9. Looking at the time adjustments in each column it becomes apparent that 0 and 9 are the equivalent of rows 0 and 3 in the 10P map.

With this in mind it should be fairly obvious why the equivalence of M = 0, 8 looked highly questionable. It's simply not a valid statement to say 0ms is the same as -0.047ms.

The idle codes with values roughly follow the arrangement of the 10P table. Columns 1-4 contain positive adjustments, 5-8 contain negative adjustments which mirror the first 4 values.

10P codes 1 and 2 roughly align with 15P codes 2 and 7 respectively.
This should give a clue how it would be possible to code injectors when running a 15P map on 10P hardware:
- "B" is equivalent to "F"
- "C" is equivalent to "L"

I'll update further when I have some time, but this should give some basis for understanding the issues behind the injector coding bugs I've described.

Tuner Pro: odds and ends

Wednesday, March 3, 2021 - 21:30

MEMS Checksum plugin

To verify the plugin is correctly installed:

  • open the XDF Header [ XDF >> View/Edit XDF Header Info ].
  • click the Checksum tab.
  • select one of the MEMS checksum, then "Edit Selected".

If the plugin is installed you'll see "Td5 MEMS Checksum Plug-In" as shown below.

If the plugin is present and configured the MEMS checksums will be updated on save.

Note that two verification words are set to 0xFFFF during save to enable secure programming. Please be aware that once the map is saved in this way edits done in other apps will corrupt the MEMS checksums. You'll need to correct them by resaving in TP with the plugin enabled or use something like the MEMS Flasher to upload.

And if you want or need to revert to "classic" behaviour delete the two MEMS checksums from this tab.

"Brick Proof" programming tool update

Saturday, August 1, 2020 - 17:30

As I posted previously, I've been working on a tool to enable "brick proof" programming with the Nanocom.

To quickly recap the NNN ECU's have internal protection against failed uploads.

This relies to two things:

  • the checksum for variant and fuel maps have to be correct
  • two verification words need to be set to 0xFFFF.

When the verification routine is run after programming the ECU verifies the checksum(s). If the checksum is correct the ECU programs the verification words in both maps to a preset value.

When the ECU boots it checks that the verification words are programmed. If they are present and contain the correct value the ECU assumes the maps are correctly programmed and boots. If either of the verification words are not correct the ECU drops into programming mode and you can connect to the ECU with a programming tool.

While I've seen a one or two .maps with what look like corrected checksums, all have the verification words set to the "successfully programmed" value. This means if the upload fails the ECU reads these values and proceeds as if the ECU has been correctly programmed. If the upload fails at any time after the fuel map verification word has been programmed you are guaranteed a bricked ECU.

Fortunately the Nanocom runs the verification routine after each part of the ,map is programmed.

To take advantage of the "brick proof" programming all we need to do is update the checksums and set the verification words to 0xFFFF.

Td5 MEMS Checksum plugin

After a bit of messing around I've ended up porting my Python checksum code across to a Tuner Pro plugin.

This was fairly quick, although I wasted most of today messing around trying to work out why the variant checksum wasn't correct. I finally created a fresh map from Nanocom map wizard and the checksum calculated perfectly.

I use the ability to calculate the factory checksum using an unmodified map as the benchmark for testing. My theory is if I can recreate the factory checksums from scratch I'm doing ok.

Checksum Plugin UI

Most of the configuration is embedded into the plug-in and the only additional setup required is the data start and end addresses.
This has primarily been done to allow the XDF's to support "weird" formats like the MEMS3 Flasher dumps.

I've tested the plugin on Windows 10 and XP and seems to work on both.

The current XDFs for Nanocom .map formats come with the plugin preconfigured.

A (very) quick guide to Tuner Pro setup

Monday, July 6, 2020 - 17:15

The Donor XDF's take most of the grunt work out out of setting up Tuner Pro for editing Td5 map files. However after a query from a new donor I've realised that I've been assuming people will just figure it out in the end.

To remedy that I'm planning on making a few posts on which cover getting up and running with the XDF's. This first post looks at getting a .map, XDF and "compare bin" loaded into Tuner Pro.

Keep a clean copy to use as a master .map

It is a good idea to treat the .map files supplied with the XDF's as master copies which you don't edit.
Make a copy of the "master" .map and work from that instead.

Loading .maps

Load .map files using File >> Open Bin.
Change the "Files of type" pull down to "All Files" to see and load .map files.

If the version of the tune is not obvious from the file name, the best way to check is by using the built in hex editor.
This can be found under Tools >> Advanced >> Hex Editor. With the Hex Editor open scroll down to offset 0x19010. You should see the tune and variant identifiers in the ascii view at the right of the editor.

In this example the tune identifier is svdxr007. img

The XDF names reflect the tune they are intended to edit so the tune identifier will give you sufficient information to select the correct one for the current .map.

Load the appropriate XDF

With the tune identified its fairly straight forward to load the appropriate XDF from XDF >> Select XDF. Navigate to where you have the donor XDF saved and pick the XDF with same tune identifier as the map you are editing.

Load a Compare Bin

Compare bins are the main reason you need an unmodified copy of the .map or .bin you are editing. From the Compare >> Load Compare Bins menu item load an unmodified copy of .map into the first slot by clicking "browse".


Tick enable to make this compare bin active.

Table Listings

There are a few ways to view listings of defined parameters.

Parameter Ordered List

The default is the "Parameter Ordered List".
This seems to be a favourite of those who have used "Td5 Map Editor" in the past.


Parameter Category

The alternative way of listing tables is the "Parameter Category" view.
Each defined table is assigned to one or more categories based on function. The main demand related tables are all organised under the collective title of "System Demand", with driver demand, torque limiters and smoke limiter maps organised into sub-categories.

The result is that tables are arranged into functional groups which - in my view - makes it easier to access the tables.


The "Bad Land Rover Engineering" Myth and .map Programming

Tuesday, June 16, 2020 - 13:00

I'm sure you've heard that the reason NNN ECU's get bricked when uploads fail is due to some variation on the "Bad Land Rover Engineering" theme. The spiel on many LR forums is familiar: "NNN: Worst flash programming ever. Blame the incompetent Land Rover engineers."

It turns out that is a myth.

While doing the disassembly I'd found code than indicated that the ECU had routines to checksum uploaded variant and fuel map code and checked that specific bytes were set in both the variant and fuel maps. I didn't really dig any further with that, as Nanocom .map files always had the specific bytes configured.

Then a couple of weeks ago a long time supporter sent me a link to Andrew Revill's K-Series reverse engineering project. Andrew has done some fantastic work with the K-Series NNN, including an NNN flash programmer application that works with a dumb VAG interface.

Reading the description of his programmer triggered an "Ah ha" moment..

It's taken bit of messing around over the past couple of days but I've now managed to confirm that the Nanocom supports "brick proof" programming. The problem is that none of the maps produced for the Nanocom I've seen - from stock tunes produced by the MapWizard to .tun protected commercial remaps - are set up to use the functionality.

Rather than having "worst programming eva" the NNN has an extremely robust mechanism which only makes the ECU bootable after the fuel map has been completely programmed and the data has been verified. But only if the uploaded map ticks all the boxes.

The video shows the "ignition" being turned off after roughly 3KB of the fuel map has been programmed.
Under normal circumstances this would have guaranteed a bricked ECU.

With the "brick proof" configuration of the .map, the ECU boots back into programming mode and the Nanocom connects without any problems.

As an aside I was having major issue with 1.34 firmware on the Nanocom. Yesterday it came up with a black screen and "cannot find NC image" error message. After installing the 1.35 update, then installing again and wiping the configuration after discovering the unlock codes were completely scrambled it is working far better.

A tool that will update .map files to the "brick proof" format is in the works...

Map Finder

Wednesday, April 15, 2020 - 17:45

The "Stay at Home" directive from local authorities has given me time to dig out a couple of projects I'd put on the backburner last year.

I'd run into problems building the "Map Finder" app for distribution when I was working on it previously. Today I dusted the project off and finally sorted out the build issues.

The app uses a cross platform gui for python called Kivy that theoretically allows an application to run on macOS, Windows, Linux, iOS and Android.

"Map Finder" uses a database back end to display and filter a listing of engine maps based on a set of criteria - Vehicle, Engine, Wheelbase or Transmission and finally Market. The idea was/is that this will end up as part of a cross platform tool for generating and manipulating Td5 engine map formats.

EU3 Specific Limiters

Friday, September 13, 2019 - 08:45

The 3.0 release of the XDFs includes two additional EU3 specific limiters. Both limiters use ambient air temperature (AAT) as part of the calculation of the parameter value. This makes them slightly problematic as Nanocom omits this value from display and logs despite reading the data from the ECU. Fortunately the calculations don’t seem overly sensitive to AAT so an estimation will likely suffice.
If you are estimating its worth understanding what AAT is...

AAT is not Ambient

While it’s been claimed in various places that the AAT sensor measures “ambient temperature” this somewhat misleading. The AAT sensor is measuring air temperature inside the lid of the airbox and can be significantly higher than external ambient temperature.
The airbox temperature is effectively "pre-heated" by under bonnet temperatures, so engine, radiator, turbo, and even sun on the bonnet have an impact.


You can see from the logged data above that from a cold start AAT is reading around 26°C. As the engine and coolant warms up under bonnet temperatures increase and “ambient” temperature rises. AAT peaks at 38°C when the vehicle is idling at the end of the log. So in this example there is a 12 degree differential between AAT when the engine is cold and when the engine is hot and idling.


The other factor to consider is that increased air flow reduces the AAT reading.
As can been seen in the zoomed section of the plot, there was a 2.8 degree drop in AAT when there is high flow through the airbox compared with the no load flow.

The take away is if you need to guesstimate assume AAT will be higher than ambient temperature by something like 10°C.

Over temp multiplier

This table is apparently used to limit fuelling to protect against excessive exhaust gas temperatures.

If you look at the table values you’ll see that the limiter has no influence below 3000rpm, nor when the Y axis parameter is at or below the minimum value.

The Y axis parameter is calculated from ambient pressure and temperature:
$$param = \frac{(AAT + 273.2) \cdot 50}{300} + \frac{25 \cdot 100}{AAP}$$

where AAT is °C and AAP is kPa.

If you were at 2000m where standard pressure is 81kPa, and you had an AAT reading at 35°C the limiter parameter would be: $$param = \frac{(35 + 273.2) \cdot 50}{300} + \frac{25 \cdot 100}{81}$$
$$param = 82 = 0.82$$

This value is high enough to trigger limiting above 3500rpm.

The parameter calculation is roughly twice as sensitive to decrease in AAP as it is to increase in AAT. You'd need extreme AAT values in the range of 70-80°C to cause limiting at sea level.

Where this may become a factor is on high road passes - Stelvio Pass is 2757m ASL for example. At this altitude standard pressure is around 74kPa, so you'd be seeing limiting creeping in above 300rpm with 40°C AAT.

It is likely that this limiter will only rarely need to be touched, if ever.

Turbo Overspeed

The Turbo Overspeed limiter is a bit of a silent killer in the EU3 maps.

The limiter begins reducing fuel once operating conditions reach the equivalent of 1.5 bar boost and 680kghr MAF at sea level.

The exact limiting threshold will change with ambient pressure and temperature, manifold pressure, and mass air flow.

Working with the values in kPa, kg/hr and °C the main parameter calculation for the overspeed limiter is: $$ param = \frac{MAP + 0.0424 \cdot MAF \cdot \sqrt{AAT + 273.2}}{AAP}$$

The parameter calculation can be thought of having three main components:

  1. pressure ratio
  2. flow rate
  3. turbo speed constant

Pressure Ratio

This is a measure of input pressure (AAP) to output pressure (MAP). I’m ignoring the pressure drop caused by intercooler here, so:

$$pr =\frac{MAP}{AAP}$$ As an example lets look at pressure ratio required to produce 135kPa boost at AAP = 100kPa and 80kPa. $$pr = \frac{235}{100} = 2.35$$ $$pr = \frac{215}{80} = 2.69$$

You’ll see from the turbo map that turbine speed increases with pressure ratio. This means to produce the same output pressure turbine speed must increase as altitude increases.

Flow rate

The flow rate is determined as:

$$fr = \frac{MAF\times\sqrt{AAT K}}{AAP}$$

Assuming a temperature drop of 9.8°C per 1000m and 25°C ambient at sea level, and MAF = 500kg/hr.

At sea level: $$\frac{\sqrt(25+273.2)}{101} = \frac{17.27}{101} = 0.171$$ $$fr = 500 * 0.171 = 85.5$$

At 2000m ASL: $$\frac{\sqrt(5.4+273.2)}{101} = \frac{16.69}{80} = 0.209$$ $$fr = 500 * 0.209 = 104.5$$

A decrease in ambient pressure and turbo inlet temperature results in an increase in flow rate multiplier.


This is a scalar value set to 0.0424 in all fuel maps. This is incorrectly scaled in the 3.0 XDFs. The issue is fixed in 3.1 release.

Reassemble the components…

If we look at the parameter equation in terms of blocks we have this: $$param = pr + fr \times speedConst$$

So using the above values for sea level: $$param = 2.35 + 85.5 \times 0.0424 = 5.97$$ and 2000m: $$param = 2.69 + 104.5 \times 0.0424 = 7.13$$

The Overspeed limiter x-axis starts at 7.0, with no limiting applied below this parameter value.

Two it would appear that his calculation uses what are effectively the x and y axes of a turbo performance map to create a limiter that accounts for the effect of air density on turbine speed.

To further illustrate I’ve mapped the 7.0 parameter value for 680kg/hr MAF at sea level onto a GT2052S 52 trim performance map.

Annotated GT2052 map
Note the figures in red above the x-axis are MAF in kg/hr.

Because this is a calculated parameter the only way to fully assess whether this limiter is actually impacting is to calculate using the above formula from log data.

This can be done with a spreadsheet app or using a calculated field in MegaLogViewerHD.

As a final illustration of the effect of this limiter, I’ll include a small segement of a log from a site donor who was having some major issues with a hybrid turbo and map levels over 250kPa.

OS Limiter in action

The white trace is request IQ after Driver Demand and Torque/Smoke limiters.
The red trace is the IQ after application of the all system limiters including the Turbo Overspeed map.

The point at the cursor shows a 26% reduction in requested IQ corresponding to the 7.5 cell in the limiter map.

You can also see clearly that as the OS limiter parameter increases above 7.0 the IQ after all limiters reduces. MAP levels when limiting is occurring are around 250-260kPa.

The take away is simple:
If you are running boost above 250 kPa on an EU3 with a "new school" map this limiter is kicking your butt.

Adjusting the limiter

Rather than modifying the limiter table I would suggest altering the tb_speed_const.

  1. Work out max Pressure Ratio at sea level - this MAP divided by AAP $$275kPa / 100kPa = 2.75$$

  2. Subtract that from 7.0 to get your flow component. $$fc = 7.0 - 4.25$$

  3. Work out your maximum MAF multiply and multiply by 0.1738 to get flow rate $$720 \times 0.1738 = 125$$

  4. Divide the flow component by flow rate to find tb_speed_const: $$4.25 / 125 = 0.034$$

A note GT2052S turbo maps

The Garrett catalog and website list three different compressor trims for the stock GT2052S turbo: 48, 50 and 52. There are two maps available for the 52 trim version. The newest of these, which shows an extended range to a pressure ratio of 3.5, is on Garrett website. The others can be found in Garrett catalogs.

The problem here is that the Td5 uses the 54 trim compressor, and there is no publically avaliable map. The 52 trim will be the closest in performance but it will not be the same.