Old Siemens ECU (MC68HC11)

Disassembly, Reassembly, Tools and devleopment. Going deep with Hardware and Software.
Tobi_gNT
Posts: 13
Joined: Sat Aug 15, 2026 7:12 am
cars: Golf 3 16V ABF Digifant3.2
BMW 335i N54 MSD80

Old Siemens ECU (MC68HC11)

Post by Tobi_gNT »

Hello,

I have a Golf 3 2.0L ABF with a Siemens Digifant 3.2 ECU, part number 5WP4 304 037906024 BE.

MCU: Motorola MC68HC11G5FN1 (1C28M / ZQZOE9508)
EPROM: 27C256

While Digifant 1 (also HC11-based) is well documented and largely decrypted, Digifant 3.2 is still mostly unexplored.

I have read out the 27C256 EPROM and identified some tables:

0x117 – 16x16 Fuel MAP?
0x90E – 16x16 ??
0x362 – 4x16 ??
0x3A2 – 4x16 ??
0x5C2 – 16x16 Ignition MAP [(210-x)/4] – tested!
0x4C2 – 16x16 ??
0xD20 – 16-bit rev limiter – tested!

However, there is still a lot of code and many tables in the 27C256 that I don't understand or know the purpose of.

I have searched the entire internet, but I haven't been able to find a truly good .xdf file for this ECU. I have attached the only one I managed to find, but unfortunately, it isn't very informative.

Since I am still fairly new to reverse engineering, I wanted to ask if you could possibly help me with this.

I have attached the following files:

Pictures of the ECU
Original 27C256 EPROM dump

Any help or guidance would be greatly appreciated

Best regards,
Tobi
You do not have the required permissions to view the files attached to this post.
Last edited by Tobi_gNT on Sat Aug 15, 2026 4:47 pm, edited 1 time in total.
Tobi_gNT
Posts: 13
Joined: Sat Aug 15, 2026 7:12 am
cars: Golf 3 16V ABF Digifant3.2
BMW 335i N54 MSD80

Re: Old Siemens ECU (HC11)

Post by Tobi_gNT »

with Ghidra i dont found MC68HC11 processor
...i´m I on the wrong track?
geep
Posts: 47
Joined: Sat Feb 02, 2019 3:43 pm
cars: 60 ford falcon 07 grand prix 94 chevy swb ls swaped

Re: Old Siemens ECU (MC68HC11)

Post by geep »

https://www.nxp.com/docs/en/data-sheet/M68HC11E.pdf
not sure if it will help but it has some code examples in it
User avatar
antus
Site Admin
Posts: 10076
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

Re: Old Siemens ECU (MC68HC11)

Post by antus »

Try hc11 or hcs11. Post a bin of the similar well defined ecm and well defined xdf and it might be possible with some help from AI to port most of it across if theyre that close.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396
Tobi_gNT
Posts: 13
Joined: Sat Aug 15, 2026 7:12 am
cars: Golf 3 16V ABF Digifant3.2
BMW 335i N54 MSD80

Re: Old Siemens ECU (MC68HC11)

Post by Tobi_gNT »

i have found a MC68HC11 for Ghidra:
https://github.com/GaryOderNichts/ghidra-hc11-lang

But it's not exactly the same: the Digifant 3.2 ECU uses the MC68HC11G5FN1 chip. i have loaded (M68HC11RM/D)
I added the HC11 dataset in Ghidra, but when opening the project, and analysed. I only get this far—see the image. I suspect it's the wrong processor definition?


@Antus
There is already a post about the Digifant 1 HC11 here in the forum.
viewtopic.php?p=54719&hilit=digifant#p54719

At the very bottom of the last post,it is the .xdf and original .bin files for the Digifant 1.

I manually compared the .xdf file for the Digifant 1 and examined the tables and conversion formulas, but I don't think much of it is applicable to the Digifant 3.2—except for the ignition table and RPM conversions.
You do not have the required permissions to view the files attached to this post.
User avatar
antus
Site Admin
Posts: 10076
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

Re: Old Siemens ECU (MC68HC11)

Post by antus »

See if this gets you started. I used AI to look at the early bin and XDF and locate RPM in RAM. Then asked it to look at the shape of the functions in the new bin, being a later revision of the same platform, and attempt to identify fuel and spark tables. And gave it a direction of where we would be going. It identified some key routines and RAM, but then discarded the plan and went its own way, lol. This is a first cut from one session. I have not looked at checksums, and don't have any way to validate this. I would expect different HC11s still use the same bytecode, and should disassemble. Just onboard registers or eeprom or RAM sizes and things like that may be different. In your case, I think it may not have detected the reset vector and not started converting in to code. Perhaps the load address of the bin is wrong. For HC11 the reset vector is the last 2 bytes at FFFF so you may need to offset the whole file to load top justified ending at FFFF.

AI Warning, looks accurate but could have errors. See if this helps you get started:

VW Digifant 3.1 - 037 906 024 BE - full ROM teardown and a working XDF

This is a reverse engineering write-up of the Digifant 3.1 ECU 037 906 024 BE (software version 2121). No public XDF existed for this ECU, so it was built from the ROM itself rather than ported by address from the well-known Corrado G60 definition.

Short version of why that matters: the 3.1 image shares only 4 percent of its bytes with the G60 3-map ROM and is twice the size. It is a rewrite, not a recompile, so shifting the old XDF by a fixed offset does not work. What does carry across is the structure - the same engineers solving the same problem with the same arithmetic.

Everything below is derived from the code, not guessed from data shapes.


1. CPU and load geometry

CPU is a Motorola 68HC11. Confirmed by an exact 21-entry HC11 vector table at the top of the image, plus 0x18 (the Y prebyte) and 0xBD (JSR extended) being among the most frequent bytes in the file.

Code: Select all

CPU address = file offset + 0x8000
ROM occupies CPU 0x8000-0xFFFF, mapped 1:1 from file 0x0000-0x7FFF. Vectors sit at file 0x7FD6-0x7FFF (= CPU 0xFFD6-0xFFFF) and every vector target lands inside the ROM window, so there is no ambiguity.

Note this is different from the earlier G60 ECU, which aliases: on the G60 the ROM is only 16 KB and A15 is not decoded, so it appears at both 0x4000 and 0xC000, and the stored vector targets have bit 15 clear. The 3.1 does none of that.

IDA load parameters:

Code: Select all

Processor                  6811 (Motorola 68HC11)
Loading offset / ROM base  0x8000
File offset                0x0
Size                       0x8000
Entry point                0x9200
Memory map, confirmed from the reset code (it writes INIT = 0x01, and clears RAM up to 0x01FF):

Code: Select all

RAM    0x0000-0x01FF   internal RAM, 512 bytes
REGS   0x1000-0x105F   HC11 register block
ROM    0x8000-0xFFFF   the image
Reset also does ldy #$1000, so most register access in this ROM is indexed as offset,y rather than extended. If you do not account for that, the register reads disassemble as meaningless absolute loads.


2. Identification block

At file 0x7FC0 (CPU 0xFFC0), immediately below the vector table:

Code: Select all

7FC0: 21 21 00 37 90 60 24 42 45 00 00 41
      ^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^
      2121  0037906024 (BCD)  "BE"

21 21            BCD 2121 - software / calibration version
00 37 90 60 24   BCD 0037906024 - VW part number 037 906 024
42 45            ASCII "BE" - variant suffix
00 00 41         unresolved
So a filename like 037906024_BE_2121_8570 is fully accounted for except the trailing 8570, which is not in this block. That is almost certainly a checksum stored elsewhere. See the warning at the end.


3. Interrupt vectors

Standard HC11 layout. These are the best seed entry points - every one should end in RTI.

Code: Select all

CPU vec  Target  File    Interrupt
FFD6     B562    0x3562  SCI (serial - diagnostics / K-line)
FFD8     EE4C    0x6E4C  SPI
FFDA     EE4D    0x6E4D  Pulse Accumulator Input Edge
FFDC     EE19    0x6E19  Pulse Accumulator Overflow
FFDE     ED7D    0x6D7D  Timer Overflow
FFE0     E743    0x6743  Output Compare 5
FFE2     E802    0x6802  Output Compare 4
FFE4     E8C1    0x68C1  Output Compare 3
FFE6     E980    0x6980  Output Compare 2
FFE8     EA3F    0x6A3F  Output Compare 1
FFEA     E6AF    0x66AF  Input Capture 3
FFEC     EE33    0x6E33  Input Capture 2
FFEE     EE1B    0x6E1B  Input Capture 1
FFF0     ED00    0x6D00  Real Time Interrupt
FFF2     EE10    0x6E10  IRQ
FFF4     EE4E    0x6E4E  XIRQ
FFF6     EE1A    0x6E1A  SWI
FFF8     9200    0x1200  Illegal Opcode -> reset
FFFA     9200    0x1200  COP failure -> reset
FFFC     9200    0x1200  Clock Monitor -> reset
FFFE     9200    0x1200  RESET
The OC handlers sit at evenly spaced addresses (E743, E802, E8C1, E980, EA3F - spacing 0xBF each). Near-identical handler sizes strongly suggest four injector drivers plus one ignition channel.

OC = pulse output (injector open time, coil dwell/fire). IC = pulse input (crank/cam sensing). Start any RPM work at IC1/IC3.

Open question: five more pointer-shaped words sit below 0xFFD6, at 0xFFCC-0xFFD4: E690, E60C, ED83, E6DC, E735. They all land in ROM and look like code pointers, which would make this an extended-vector HC11 derivative rather than a plain A/E series. The G60 ECU has plain filler in that space, so this is a real difference, not a mis-read. If anyone knows which HC11 variant Siemens used here, that would settle it.


4. The lookup primitives - this is the key

Every calibration on this platform is read through a tiny library of lookup routines. Find those, cross-reference every caller, and the entire table inventory enumerates itself. You do not have to hunt for tables by eye.

In the 3.1 that library sits at 0xF150-0xF3C2:

Code: Select all

Addr    Name                      G60 equivalent  Reads
0xF1A7  interp_kernel_T16         0x53FC          core interpolation
0xF19C  lookup_T16_interp         0x5759          55 tables, 35 callers
0xF1F4  lookup_T16_noninterp      -               14 tables, no interpolation
0xF150  lookup_map16x16_bilinear  0x5187          6 maps, row stride 16
0xF292  search_breakpoints16_u16  0x520B          2 rpm axis tables
0xF280  interp_blend              -               2-D blend helper
lookup_T16_interp is literally:

Code: Select all

bsr  interp_kernel_T16
addd #8
lsrd
lsrd
lsrd
lsrd
which is exactly (t*(16-p) + t[i+1]*p + 8) / 16, where i is the high nibble of the index and p the low nibble. That is the same formula documented years ago in convertParams.py for the G60. The platform convention survived the rewrite intact.

search_breakpoints16_u16 (0xF292) is instruction-for-instruction identical to the G60's sub_520B.

Table width - do not assume 17. Because the kernel reads t[i+1], an interpolated table can be 17 bytes. But many bases here are packed exactly 16 apart, and in that case the overrun simply lands on the next table's first byte, which is harmless because at index 0xF0 the fraction is zero. The reliable method is to take each table's width from the gap to the next known table base. Tables read by lookup_T16_noninterp never touch t[i+1] and are always 16.


5. The engine speed anchor

This is the single most useful thing to find in any of these ROMs. Anchor on the RAM variable, not on code layout - a RAM address survives a rewrite far better than any byte signature.

calc_engine_speed_index (sub_985A) is the direct analogue of the G60's sub_51D5:

Code: Select all

ldd  <ram_crank_period      ; RAM 0xBF
tst  ram_crank_period_hi    ; RAM 0xBE
ldab #$F0                   ; clamp - same as the G60
ldx  #$8EA7                 ; the RPM breakpoint table
jsr  search_breakpoints16_u16
stab <ram_rpm_index_raw     ; RAM 0xA1
addb <ram_rpm_index / rorb  ; (new + old) / 2
stab <ram_rpm_index         ; RAM 0xA2
RAM map that falls out of it:

Code: Select all

0x80        ram_load_index       load (MAP) axis
0xA2        ram_rpm_index        ENGINE SPEED INDEX 0x00-0xF0  <-- the big one
0xA1 / 0xA3 raw / previous       pre-filter and previous sample
0xBE-0xC0   ram_crank_period     RPM = 15000000 / period
0xC5        ram_ignition_angle   ignition advance output
0xA0, 0xAE  fuel outputs         results of the two fuel maps
RAM 0xA2 has 2 writers and 58 readers, and cross-referencing it turns up around 30 distinct calibration tables. Every single one of the six 16x16 maps takes it as an axis. That is exactly what you would expect, and it is strong independent confirmation that the identification is right.


6. The rpm axis tables, decoded

Both are 16 ascending 16-bit big-endian crank periods. RPM = 15000000 / value.

0x8EA7 - main RPM axis
Raw: 085E 0955 0A78 0BD2 0D72 0F6C 11DB 14E5 18BE 1DB2 2431 2CE6 38B8 4926 600E 7F60

Code: Select all

7003 6279 5597 4957 4358 3799 3282 2804
2368 1973 1619 1305 1033  801  610  460   rpm
0x8EC7 - idle RPM axis (read by idle_speed_control, sub_CDAF)

Code: Select all

1600 1527 1453 1380 1307 1233 1160 1087
1013  940  867  793  720  647  573  500   rpm
Period ascending means rpm descending, so index 0 is the highest rpm. An XDF axis needs the reversed stride that the G60 definition already uses.

This also settles an old inconsistency in the published G60 XDF. That definition uses 15000000/X on its map axes (correct) but 30000000/X on its standalone "RPM Scalar" table (wrong - it would double every number). 15000000/period produces physically sane ladders on both ECUs; 30000000 would put this engine's redline at 14000 rpm. convertParams.py had it right.


7. Map roles

Code: Select all

Addr    Role                          Confidence
0x84C2  Ignition / spark advance      HIGH
0x8A3E  Fuel (-> RAM 0xA0)            medium-high
0x85C2  Fuel (-> RAM 0xAE)            medium-high
0x8117  unclassified                  low
0x890E  unclassified                  low
0x82E2  unclassified, 8 rows not 16   low
0x84C2 is the ignition map because ignition_advance_from_map (sub_DB16) wraps the lookup in angular arithmetic - it clamps at 0x77 and wraps modulo 0x78, i.e. 120 counts per angular segment - and its caller sub_D964 also invokes the routine that writes TCNT/TCTL1 to schedule the output compare. Nothing else in the ROM has that shape.

It also behaves correctly as a spark map: values fall left-to-right as rpm falls, and fall again with increasing load. Both are what advance should do.

0x8A3E and 0x85C2 share an identical but quite different signature - bilinear lookup, addd #$80 (round to nearest), store one byte. That is a fuelling quantity, not an angle.

0x82E2 turned out to be 8 rows, not 16: another table base sits only 128 bytes in. That is exactly why its data looked banded rather than smooth.


8. The XDF

92 tables, no overlapping regions, confidence marked per table:

Code: Select all

 1 x  Ignition
 2 x  Fuel
 2 x  Axes and Breakpoints (real rpm, verified)
45 x  1-D Tables (interpolated)
11 x  1-D Tables (stepped)
 3 x  Unclassified Maps
28 x  Unverified - inspection only
Notes on using it:
  • Map columns are rpm descending left to right (7003 down to 460), because index 0 is the shortest crank period. Rows are the load index 0-240.
  • Everything except the two rpm axes is shown as raw counts. I deliberately did not bake in an unverified degrees-per-bit for ignition. 0.75 deg/bit would give a very plausible 18-36 deg, but it is not derived yet, and a wrong ignition scale is the worst kind to publish.
  • Anything in Unclassified or Unverified is inspection only. The addresses and shapes are solid; the meanings are not.

9. Warnings and open items
The checksum algorithm for this ECU is not yet known. The 8570 in the filename is not in the ID block, so it is stored somewhere else or computed. Until that is solved, treat writing a modified bin as untested - it may be rejected or run incorrectly. Read and inspect freely.
Still open:
  1. Checksum algorithm and location. This blocks any actual flashing.
  2. Degrees per bit for the ignition map. Needs the code that converts ram_ignition_angle into an output compare count.
  3. Which physical output each of the four OC handlers drives. That would pin the fuel maps down properly.
  4. Whether this is an extended-vector HC11 variant (the 0xFFCC question above).
  5. I only have one calibration of this ECU. A second or third 037 906 024 variant would allow differential analysis - regions that differ between calibrations of the same code base are, by definition, the calibration tables. This is by far the highest-value thing anyone could contribute.
You do not have the required permissions to view the files attached to this post.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396
Tobi_gNT
Posts: 13
Joined: Sat Aug 15, 2026 7:12 am
cars: Golf 3 16V ABF Digifant3.2
BMW 335i N54 MSD80

Re: Old Siemens ECU (MC68HC11)

Post by Tobi_gNT »

Wow, at first glance, what the AI is producing looks very impressive. Unfortunately, though, I can’t really do anything with the tables. On top of that, they’re also incorrect — not all of them, but around 90%. The only thing that seems to be correct are the RMP ranges.

If Digifant 1 and Digifant 3.2 were identical, things would certainly be much easier. Unfortunately, they aren’t.

I’ve also played around a bit more with Ghidra and used the base address 0x8000. Now I’m getting functions displayed, but at this point I’m completely lost... I also have no idea whether this is even correct.

Maybe someone here can point me in the right direction?
You do not have the required permissions to view the files attached to this post.
User avatar
antus
Site Admin
Posts: 10076
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

Re: Old Siemens ECU (MC68HC11)

Post by antus »

it looks correct from what we can see. the opcodes look valid and the code structure viable.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396
User avatar
pman92
Posts: 672
Joined: Thu May 03, 2012 12:50 pm
Location: Castlemaine, Vic

Re: Old Siemens ECU (MC68HC11)

Post by pman92 »

Tobi_gNT wrote: Sun Aug 16, 2026 5:52 pm Now I’m getting functions displayed, but at this point I’m completely lost... I also have no idea whether this is even correct.
Looks like real code to me. You'll know pretty quickly when it's not correct.

If you have a valid XDF or any known tables etc for this file that you've already found, start labelling them in ghidra. From there you will be able to find what references them and they should lead elsewhere
User avatar
antus
Site Admin
Posts: 10076
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

Re: Old Siemens ECU (MC68HC11)

Post by antus »

I still have the code open and asked it to find load and it progressed this far. Removed first XDF and added update here. Still experimental and not usable.

Code: Select all

Load measurement: it's MAP (speed-density)
RAM 0x80 is the load axis. Signal path: external converter channel 0x1040 → sub_9758 → 4-deep shift register 0x95–0x98 → averaged → 0x99 → second register 0x9A–0x9C → averaged → 0x80. Sampled synchronously with engine events and double-averaged, which is what you do to a pulsating manifold-pressure signal.

The decisive evidence is an absence: nothing in the ROM ever combines the load value with a time base. Any flow meter measures quantity per unit time, so it must be multiplied by cycle time to give charge per cycle. The set of functions reading the load variable and the set reading a period variable are disjoint. The only use of crank period in the injector path is sub_A88F, which is a duty-cycle limiter. That's speed-density.

Supporting: it's analogue via an external converter (not a frequency input, so not a digital MAF); the plausibility ceiling 0x8C1F is flat at 48 from 1033–7003 rpm, which is meaningless for a flow meter but natural for pressure; and the G60 sibling is unambiguously MAP. High confidence, not proven — the kPa-per-bit scaling is still underived, so the axis ships as the raw 0–240 index rather than a fake pressure scale.

The error the feedback was pointing at
My index attribution was wrong in method. I was looking forward from any read of a candidate RAM byte to the next table lookup — which mis-assigns whenever a function touches several variables before the lookup. The correct method walks backward from each lookup call to the last ldab, since B holds the index.

Redoing it properly:

Table	Was	Now
0x8A3E	"Fuel Map A"	Substitute load model — its output replaces the MAP signal when the sensor is flagged faulty
0x85C2	"Fuel Map B"	Unclassified — not in the main fuel chain; label was unsupported
0x8117	"Unclassified"	Fuel trim map, signed around 0x80
0x82E2	"Unclassified"	Fuel chain, 8 rows
So both tables I'd called fuel maps were wrong, and one I'd dismissed is a real fuel table. The actual fuel routine is sub_A2EA. RPM being right is consistent — it's the one axis I'd derived end-to-end and validated numerically.

Bonus finds along the way
RAM 0x91 is coolant temperature — sub_D61F is a textbook thermistor handler (open-circuit above 0xE5, short below 0x10, slew filter, substitute value on fault). It indexes 16 tables, the second most common axis after rpm.
The whole external converter bank at 0x1040–0x104F is mapped: load, battery, coolant, intake air, and a 16-bit channel that's probably throttle.
0x8106 is the injector dead-time vs battery table — curve 255 255 255 255 255 199 114 68 35 13 0…, near-identical in shape to the G60's 0x462E. That's the third POC target now located in the target ECU.
Updated
XDF regenerated — 48 of 92 tables now carry an identified physical axis (19 rpm, 14 coolant, 9 MAP load, 3 intake air, 2 battery, 1 throttle) instead of a generic index. The other 44 are honestly labelled as unidentified. The .i64 has all the new names and comments, and there's a new _index_map.txt with the per-table axis derivation.

If the OP can say which tables looked wrong rather than just "many", I can check them directly — but the axis relabelling above should account for a lot of it on its own.
You do not have the required permissions to view the files attached to this post.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396