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

Re: Old Siemens ECU (MC68HC11)

Post by Tobi_gNT »

Thanks for your confirmation.

Phew, okay, that’s a lot of input.
I’m still really struggling to understand and follow the code and how everything works in Ghidra. I’ve never done anything like this before.

What I know for sure is that 0x5C2 is the ignition table. 16x16
The RPM scale also matches.

The AI identified 0x4C2 as an ignition table as well. Maybe there is a second map? Part-load, failsafe, or something like that?
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 »

probably is the case. could be high / low octane or cold start or any number of things. there is another thread about ghidra that might help but its about much newer cpus. I dont have any ghidra myself. There might also be some youtube tutorials. Following the code is hard and time consuming, thats why the shift to AI (combined with acombination of skill and feel until sure) is such a strong combination now. If that can get mostly right ram addresses and table axis then you can label themin ghidra and that will help.

viewtopic.php?t=9310
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 »

So I'm back, I've now looked into this intensively, but as a beginner it's not that easy to understand.

I've gotten help from some AIs like ChatGPT to be able to analyze the code better. But whenever I give the AI a task, all sorts of different solutions come out.

I wanted to set the selective cylinder deactivation (rev limiter) to hard — meaning that the cylinders are no longer selectively deactivated and the injection keeps running. But every time I get different solutions, which unfortunately don't work either. In ChatGPT I can only ever have parts of the code analyzed, which is certainly not particularly advantageous.

Is there perhaps someone here who uses the AI Claude and the Ghidra MCP server? That would allow the entire code to be analyzed, not just blocks.
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 would, but also consider that in such an old PCM, likely the injection and ignition are under hardware control, which can be tricky. A group of people on here have been trying to implement spark cut on our VT V6 PCM, which is also HC11 based, but delco flavour, and due to the hardware ignition have had lots of problems getting it to work properly. You are probably dealing with undocumented hardware. That's not to say it's impossible, and you don't know if you don't try. But it might be a longer journey than a simple code patch.
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
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 »

Claude makes this of it:

VW Digifant 3.2 - 037 906 024 BE - the rev limiter is a fuel cut, and the ROM already contains a hard cut you can switch on

Follow-up to the earlier 3.1 teardown. OP with a Digifant 3.2 XDF asked for a hard cut, so I went back into the disassembly of 037906024_BE_2121_8570 to see what the limiter actually does.

Two findings, and neither of them needs a code patch:
  • The stock rev limiter is a progressive fuel cut - it drops injectors one at a time across three rpm thresholds. It never touches spark.
  • The ROM contains a complete single-stage hard cut routine that this calibration never reaches. One config bit switches to it.
Everything below is derived from the code. CPU address = file offset + 0x8000, as before.


1. What the limiter actually cuts

byte_11F (RAM 0x011F) is a cylinder cut mask. It is applied once per crank trigger event, in the scheduler block at 0xDA24:

Code: Select all

DA24  B6 01 1F     ldaa  $011F          ; the cut mask
DA27  43           coma
DA28  18 A4 00     anda  0,y            ; y = $D954 + cylinder index
DA2B  BA 10 20     oraa  TCTL1
DA2E  B7 10 20     staa  TCTL1
The table at 0xD954 is four bytes:

Code: Select all

D954: 03 30 0C C0
Those are TCTL1 OM/OL bit pairs, one per output compare channel:

Code: Select all

0x03 = OM5:OL5  -> OC5
0x30 = OM3:OL3  -> OC3
0x0C = OM4:OL4  -> OC4
0xC0 = OM2:OL2  -> OC2
And OC2 through OC5 are the four injector channels - isr_TOC2, isr_TOC3, isr_TOC4 and isr_TOC5 all call injector_pulse_compute and write the resulting pulse width into TOC2/3/4/5.

So a bit set in byte_11F means that cylinder's injector output action never gets armed for the upcoming event, and that injector does not fire. byte_11F = 0xFF is all four cylinders dead.

The limiter is a fuel cut. There is no spark cut in it anywhere. Ignition on this ECU is driven by an external device mapped at 0x1073/0x1075/0x1079, which the limiter code never writes to. Building a hard spark cut would mean writing new code against that peripheral - and on a port-injected engine it would be the worse choice anyway, since you would be pumping fuel into a cylinder that has no spark.

Worth knowing that byte_11F is also how overrun (deceleration) cut works - the same 0xFF-into-byte_11F path, driven by byte_6F bit 7 and byte_72 bit 1. So "all four injectors off" is a state this ECU enters every single time you lift off the throttle. That matters later.


2. The dispatcher

sub_DC71 is the limiter entry point, called once per trigger event from ignition_scheduler at 0xD964:

Code: Select all

DC71  96 BE        ldaa  <ram_crank_period_hi
DC73  27 01        beq   loc_DC76
DC75  39           rts                       ; very low rpm, bail
loc_DC76:
DC76  B6 8C A6     ldaa  $8CA6               ; config byte, file 0x0CA6
DC79  84 04        anda  #4
DC7B  26 04        bne   loc_DC81            ; bit 2 SET -> staged soft cut
DC7D  BD DE 37     jsr   sub_DE37            ; bit 2 CLEAR -> HARD CUT
DC80  39           rts
byte_8CA6 is 0x47 in this calibration, so bit 2 is set and the staged path runs.

I checked every other reader of 0x8CA6. There are exactly four, and they each test a different bit - bit 0 at 0xA057, bit 1 at 0xCACD, bit 7 at 0x9958, bit 2 at 0xDC76. It is a feature-enable byte, and bit 2 is used at 0xDC76 and nowhere else. Nothing else changes if you clear it.

sub_DE37 is a complete, finished, single-stage hard cut. It is simply dead code in this calibration.


3. The stock staged (soft) cut

sub_DD93 cuts, sub_DCF6 restores. Thresholds are crank periods, and the conversion is the one the 3.2 XDF already uses:

Code: Select all

rpm = 15000000 / period
which falls straight out of a 4 us timer tick over one crank revolution (60 / 4e-6 = 15,000,000).

Code: Select all

file    CPU     value   rpm    role
0x0D20  $8D20   0x089E  6800   stage 1 - cut 1 injector
0x0D1E  $8D1E   0x087E  6900   stage 2 - cut 2 injectors
0x0D1C  $8D1C   0x085E  7003   stage 3 - cut all 4
0x0D24  $8D24   0x0040   --    release hysteresis, added to each period
0x0D22  $8D22   0x0973  6201   NOT a limiter - see section 6
I re-implemented the three routines faithfully in Python and swept them 6600 -> 7200 -> 6600 rpm. Cylinders cut:

Code: Select all

going up          coming back down
6600   0          7025   4
6800   1          6800   2
6900   2          6700   1
7003   4          6600   0
That is your soft limiter. It also sets byte_7B bit 5 while engaged, which the fuel path reads: fuel_main at 0xA2FB swaps the normal corrections for a fixed adder word_8D27 (0xFFB8 = -72), sub_A291 at 0xA291 zeroes an enrichment term, and sub_A46F at 0xA4DD skips sub_F205.


4. Option A - switch on the ROM's own hard cut (1 byte)

Clear bit 2 of the config byte:

Code: Select all

file 0x0CA6:  0x47 -> 0x43
sub_DC71 now dispatches to sub_DE37, which uses its own two calibration items:

Code: Select all

file    CPU     value      meaning
0x0027  $8027   0x085E     hard cut rpm = 7003 (same 15000000/x)
0x0029  $8029   0x10 = 16  hold count, in trigger events

Code: Select all

DE37  DC BF        ldd   <ram_crank_period
DE39  1A B3 80 27  cpd   $8027
DE3D  24 0B        bcc   loc_DE4A        ; rpm at or below limit
DE3F  F6 80 29     ldab  $8029
DE42  D7 FB        stab  <$FB            ; load the hold counter
DE44  14 7B 10     bset  <$7B $10
DE47  0D           sec                   ; -> byte_11F = 0xFF, all four off
Above 7003 rpm all four injectors go off. When rpm falls back, the cut is held for another 16 trigger events before releasing. So the hysteresis is in the time domain rather than the rpm domain - that is what gives a hard cut its characteristic bounce.

If you have the 3.2 XDF, you already have this scalar - it is the entry titled "Rev Limiter found test" at 0x0027. It is not a duplicate of Rev Limiter1. It is the hard cut threshold, and it does nothing at all until bit 2 is cleared. That is why it reads the same value.

One caveat. sub_DE37 does not set byte_7B bit 5, so the fuel-path limiter trims in section 3 do not run. That sounds worse than it is: the overrun cut described in section 1 also drives byte_11F to 0xFF with bit 5 clear, on every throttle lift, so this exact machine state is exercised constantly in normal driving.


5. Option B - collapse the three thresholds onto one rpm (2 bytes)

Leave the mode bit alone and set all three staged thresholds equal:

Code: Select all

file 0x0D1F:  0x7E -> 0x5E
file 0x0D21:  0x9E -> 0x5E
so that 0x0D1C = 0x0D1E = 0x0D20 = 0x085E. In TunerPro that is just typing the same rpm into Rev Limiter 1, 2 and 3.

Now all three stages of sub_DD93 satisfy their compare on the same pass, so the very first trigger event above the threshold cuts all four injectors. Same sweep as before:

Code: Select all

going up          coming back down
6600   0          7025   4
7003   4          6800   0
Release point is threshold_period + 0x0D24. At the stock 0x0040 that is about 6800 rpm, so roughly a 200 rpm band. Edit 0x0D24 to change it - a larger raw value means a longer period, which means a lower re-enable rpm and a wider band.

Option B keeps byte_7B bit 5 and therefore every bit of the stock fuel-path limiter behaviour.

Which one

I would take Option B. It is a pure threshold change inside the code path that is already road-tested on these cars, it keeps the rest of the ECU informed that the limiter is engaged, and the hysteresis band is adjustable in rpm rather than in events.

Take Option A if you want the sharper time-based bounce, or if you would rather the limiter rpm lived in one scalar that cannot accidentally end up out of order.

Both of them cut fuel. Neither cuts spark, and on this ECU that is the right answer.


6. While I was in there - 0x0D22 is not a rev limiter

The 3.2 XDF has an entry called "Rev Limiter4" at 0x0D22, converting to 6201 rpm. It is not a limiter. It is read at exactly one place, 0xDCA7:

Code: Select all

DCA2  7F 01 1F     clr   $011F
DCA5  DC BF        ldd   <ram_crank_period
DCA7  1A B3 8D 22  cpd   $8D22
DCAB  25 12        bcs   loc_DCBF
DCAD  13 7B 10 0E  brclr <$7B $10 loc_DCBF
DCB1  7C 00 6C     inc   $6C              ; over-rev event counter
DCB4  26 03        bne   loc_DCB9
DCB6  7A 00 6C     dec   $6C              ; saturate at 0xFF
It is the rpm the engine has to fall back below before an over-rev occurrence gets logged into the fault counter byte_6C. Changing it does not move any limiter. Worth relabelling in your definition so nobody chases it.


7. Flashing

This ROM has no checksum - established in the 3.1 teardown, and nothing here changes that. Both edits can be burned straight in.

Neither has been run on an engine. Verify on a stand, or start with a conservative threshold, before you go looking for the limiter at 7000 rpm on a public road.
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
ProfessWRX
Posts: 185
Joined: Mon Oct 02, 2023 11:33 pm
cars: 04 Monte SSSC
04 Tahoe
92 Trans Am
Location: AZ USA

Re: Old Siemens ECU (MC68HC11)

Post by ProfessWRX »

I actively work with the 6811. It’s a very easy to follow ecu code to learn on. The third gen f bodies use them.

As far as trying to figure out things I’d suggest using whatever ghidra calls graph mode. A visual representation of the code flow. Mark your variables and find all references to that ram variable to trace back to other tables.

In the gm ecu the spark and fuel control are off loaded and handled elsewhere after table lookups/calculations are done.

Also I have found AI to be completely inept, wrong and misleading 98% of the time. I’ve used Gemini, ChatGPT and grok. They all suck. Don’t rely on them for anything.
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 would say this week (it changes fast!) claude opus 4.8+ (5 ideally) on very high thinking or chatgpt sol also on very high are your minimum. Most others cannot do it. And I am more sure of opus from my own use of it.
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
pasto
Posts: 6
Joined: Tue Dec 31, 2019 9:26 am
cars: Fiat coupé 20v turbo

Re: Old Siemens ECU (MC68HC11)

Post by pasto »

ProfessWRX wrote: Tue Aug 25, 2026 12:37 am I actively work with the 6811. It’s a very easy to follow ecu code to learn on. The third gen f bodies use them.

As far as trying to figure out things I’d suggest using whatever ghidra calls graph mode. A visual representation of the code flow. Mark your variables and find all references to that ram variable to trace back to other tables.

In the gm ecu the spark and fuel control are off loaded and handled elsewhere after table lookups/calculations are done.

Also I have found AI to be completely inept, wrong and misleading 98% of the time. I’ve used Gemini, ChatGPT and grok. They all suck. Don’t rely on them for anything.
I am also working with a 68HC11 mounted on a Marelli IAW16F. I use IDA Pro for disassembly.
I haven't used AI to help with this work yet.
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 to admit that reverse engineering is completely new territory for me. I’ve never worked with IDA Pro before and I’m still trying to understand how all of this actually fits together. I’ve played around with Ghidra quite a bit now, but its not so easy for me. Unfortunately, I'm not really making much progress.
This is a very complex topic when you’re starting from basically zero.

What I find especially interesting is what you said about the lookup routines and the fact that the same basic structure can survive between different HC11-based ECUs even if the actual code and table addresses are completely different. That makes me wonder if we could somehow standardise some of this between the different ECUs.

Since you guys are also working with 68HC11 ECUs, are there things we can identify and document in a common way? For example, the lookup/interpolation routines, RPM calculation, RAM variables, table structures, etc. If the underlying HC11 code and the way Siemens/VW implemented things are similar, perhaps we could use the well-understood ECUs as a reference when working on the less documented ones.

For me personally, the biggest problem is that I don't yet know what I should be looking for in Ghidra. I can find an address and see that something is being accessed, but I often don't know what the next step should be or how to determine what a function is actually doing.

The Digifant 1 information is useful as a reference, but as we already found, the Digifant 3.2 is not simply the same software with different addresses. A lot of the tables and functions are different, so I can't really just transfer the XDF.

And honestly, I don't think I have much of a chance of properly reverse engineering the Digifant 3.2 on my own. There are simply too many functions, tables and relationships that I don't understand yet. I can identify some things experimentally, like the ignition map at 0x5C2 and the RPM-related stuff, but understanding the complete table structure and, more importantly, how each table actually functions in the code is a completely different level.

So I’m wondering if it would make more sense to approach this as a collaborative HC11/Siemens reverse-engineering effort rather than trying to solve the Digifant 3.2 completely on its own.

If we can establish a common methodology for the 68HC11 ECUs, I’d be very happy to provide the Digifant 3.2 ROM and test/verify things on the actual ECU. That might be more useful than me trying to guess my way through the disassembly.

I’m still learning here, so any guidance on what I should concentrate on next in Ghidra would be greatly appreciated.
ProfessWRX
Posts: 185
Joined: Mon Oct 02, 2023 11:33 pm
cars: 04 Monte SSSC
04 Tahoe
92 Trans Am
Location: AZ USA

Re: Old Siemens ECU (MC68HC11)

Post by ProfessWRX »

The easiest way is to label every table you know. Also every 3d table lookup should be handled a certain way. You should have something loaded into register a. Something else loaded into register b. The table is likely loaded into register x. Then once all are loaded it will jsr to a different routine to do the actual table lookup.

Once you cross reference both a and b registers to other tables you can tell which one is say rpm. Or MAP sensor or any other axis lookup. So you can then label the ram address loaded to the register as RPM. Then you can lookup everywhere RPM is referenced. You can do this with every sensor or table lookup. When you find tables in the bin you already know that are is working on timing. Or fueling. Or whatever it is. Then you can look around there for inputs and outputs and other tables or multipliers or things like that to ultimately find whatever it is youre after.