In the ignition timing signal discussion we examine how the dwell/spark signal is actually generated in the real time part of the DME code. But there, we assumed that the values used had been calculated somewhere. In this article, we’ll take a look at that somewhere! Specifically it starts at 1C24, which is called from the main loop. It’s mostly just a series of map lookups, and doesn’t contain anything very complicated.
The timing maps contain values that represent quarter-teeth, that is about 0.618 degrees. The actual timing signal itself has only half-tooth resolution, that is 1.363 degrees. Keeping the map values in querter teeth is probably to increase precision before the inevitable rounding happens.
The map values are also offset by +20, so that a value of 20 in any timing map really means zero. Values below 20 represent negative values. Since there’s a presumption that timing angles are before TDC, a final timing value that’s negative will fire the spark after TDC.
Other than that, the timing map lookup routine is pretty simple - certainly far simpler than the fuel enrichments logic. There’s a complication with timing that we won’t cover here, but I’ll mention it briefly: you might expect that after calculating the next timing value, it’ll get assigned to the variable that the real-time signal generation logic uses more or less directly. But it’s not that simple! Unlike fueling, timing is never allowed to change abruptly.
Instead, after the spark is fired, the timing value is allowed to advance a little bit in the direction of the new value. How little a bit are we talking about? Well it turns out that’s more complicated than you might think. It’s not a fixed speed. It varies according to some complicated rules. These rules are implemented by a set of flags, and the flags have to be finalized before it’s safe to assign the newly calculated timing value to the main variable.
So we will see the next target timing value being calculated here, but to see how it’s assigned and used, we’ll have to wait for a different article.
The steps here are roughly like this:
Timing maps contain absolute values, unlike fuel maps, so the way we combine them is additive. Because of the peculiar offset of 20 that the maps use, a helper routine (1CAE) is used throughout this code to accumulate the final value into r5. It just looks up the curret map (pointed to by r2 as usual), adds the value to r5, subtracts 20, and returns. Note that timing values in these maps are interpreted as 2’s complement signed numbers (after subtracting 20), so 128=-128, 129=-127 etc.
There are some noteworthy things about the corrections made in this routine.
Some map visualizations are always useful for context. Here are the O2/cat vs RoW maps for temperature based timing correction.
Idle:

And part throttle:

There’s a WOT warmup map, but there’s no alternative for O2 vs non-O2, and all values are zero anyway.
We might as well take a look at the main timing maps for each driving mode too.
Here’s idle:

Note the sudden increase at the low rpm end - this starts down around 760rpm. When the rpm drops below this, the sudden increase in spark advance helps to bring it back up again, providing a crude but very fast form of idle control. As idle speed increases beyond the normal target of 840rpm, the advance barely increases, which helps to stop it from getting too high.
Part throttle:

WOT:

X1c24:
mov r5,#0
clr c
jnb 23h.2,X1c32 ; cranking
mov r2,#49h ; Map 73 (rpm)
acall X1cae
acall X1cae ; r2=Map 74, engine temp
ajmp X1c8c
This first section handles timing for cranking. it accumulates the values from Map 73 and 74 into r5. 1C8C ends up in the clamping routine, which is the end of the calculation for this condition.
;
X1c32:
jnb 23h.0,X1c47
mov r2,#4 ; Map 4 engine temp
jb 25h.4,X1c3e ;25h.4=1 means NTCII <= ~15C during cranking
acall X1ff2 ; region based alternate map selection
ajmp X1c41
;
X1c3e:
lcall X1d10; just adds 2 to r2, selecting +2 alternate map
X1c41:
acall X1cae
mov r2,#8
ajmp X1c88
;
If we’re at idle, this code looks up Map 4 (or 6 if we had a cold start, but Map 6 is identical to 4 anyway) and accumulates the result into r5, then intializes r2 with Map 8 which is the idle timing map, and jumps to 1C88, which is where the main idle/PT/WOT map lookup is done.
X1c47:
jnb 23h.1,X1c50
mov r2,#0ch ; Map 12, temp
acall X1cae
ajmp X1c69
;
X1c50:
mov r2,#12h ; Map 18, temp
jb 25h.4,X1c59 ; 24h.4=1 means we had a cold start (<15C)
acall X1ff2 ; select alternate map based on region coding
ajmp X1c5c
;
X1c59:
lcall X1d10 ; just adds 2 to r2, selecting +2 alternate map, but Map 20=Map 18
X1c5c:
acall X1cae
mov a,#20h
movc a,@a+dptr
subb a,49h
jc X1c69
mov r2,#5bh
ajmp X1c88
;
Here we handle WOT and part throttle cases. For WOT, r5 accumulates Map 12 and jumps away to 1C69 where we do the FQS and air temperature corrections.
For parth throttle, (1C50), we accumulate either Map 18 (or region alternate) or Map 20 (if we had a cold start). These maps are different.
Next (still part-throttle only) we look up 1180, which contains 50h/80 dec. and if load is > 80 then we jump to 1C69, same location we jump for WOT, where we perform FQS and temp correction. Otherwise we skip that and jump to 1C88, the main driving mode map lookup, with r2=91. Map 91 is the main PT map.
X1c69:
mov r2,#0dh ; Map 13, air temp 12h, all values 20 for the Turbo (i.e. 0)
acall X1cae
jb 23h.1,X1c72 ; WOT - r2 will have Map 14 from previous lookup
mov r2,#5bh ; Map 91, rpm/load - main PT timing map
X1c72:
mov dptr,#X112e ; FQS switch map
lcall X05cd ; simple map lookup (no interpolation)
jnb acc.2,X1c88 ; if bit 3 is clear, it's 0-3 which is fuel only
mov a,#21h
movc a,@a+dptr ; 1181h, contains 28h/40 dec., that is 1600rpm
clr c
subb a,37h
jnc X1c88 ; jump if rpm <=1600
mov a,#22h
movc a,@a+dptr ; 1182h, contains FCh/252 dec. (i.e. -4)
add a,r5
mov r5,a
The above section is where the FQS and air temp adjustments are made. This code was skipped earlier if we are at idle or part throttle with load < 80.
The Turbo air temp map 13 has all cells 20, which means zero since 1CAE always subtracts 20. The NA Map 13 does have a reduction in timing for increasing temps, above ~30C however (NA cars don’t have a knock sensor).
We only perform the FQS based adjustment if rpm > 1600. The adjustment is the same for all FQS positions with bit 3 set (4-7) and is -4 quarter teeth.
This code also leaves r2 with either 14 (main WOT timing map) or 91 (main PT timing map).
X1c88:
acall X1ff2
acall X1cae
X1c8c:
ljmp X104a ;this just jumps back to 1C8F (below)
;
At this point, r2 should have either
These are the main timing maps. Here we select alternates based on region and call the accumulation routine, so r5+= map_value - 20 as usual.
X1c8f:
mov r2,#48h ; Map 72 (rpm based)
acall X1cae ; r5+= Map 72 value - 20
mov r0,a ; r0 = new r5 value
mov c,acc.7 ; c = (r5 > 127)
mov a,#23h ; 35 decimal
jnc X1c9b ; jump if r5 <= 127
inc a
X1c9b:
movc a,@a+dptr ;23h if r5 <= 127 (contains 73 dec), 24h otherwise (contains 249 dec.)
mov r1,a
jnc X1ca0 ; c=1 still indicates r5 > 127, so this jumps if r5 <= 127
xch a,r0 ; r5 > 127, so r0 now gets 249 and a gets whatever r0 was, which was the same as r5
X1ca0:
clr c
subb a,r0 ; if r5<=127, then a=73 and r0=r5. Otherwise if r5>127,then a=r0=r5 and r0=249
jnc X1ca6 ; For r5<=127, jump if r5<=73. FOr r5>127, jump if r5>249
mov a,r1
mov r5,a ;
X1ca6:
mov a,r5
mov c,acc.7
rrc a ; divide signed integer by 2, c preserves the sign
mov r5,a
ljmp X104d
;
We add Map 72 to the accumulated value - this is an rpm based map that adjusts everything a bit for rpm, and also cancels out the fixed latency that the KLR introduces to the timing signal.
If the calculated r5 <= 127 (i.e positive), then it’s clamped at 73. If negative, it’s clamped at 249, that is -6. These are quarter-tooth values - multiplying by 0.618… they come out to ~50 and -4 degrees.
Finally we divide by 2, turning the final value into half-teeth.
Map 72
1-Axis Map (address 12c0, input variable Engine RPM (0x37))
| Engine RPM (0x37) | Value |
|---|---|
| 520 | 17 |
| 1480 | 20 |
| 3240 | 25 |
| 6120 | 33 |
X1cae:
lcall X051d ; map lookup routine, input in r2, output in a
add a,r5
clr c
subb a,#14h
mov r5,a
ret
This routine simply does:
r5 += map_lookup(r2) - 20
After this, the code jumps to the overload protection and then the logic that checks the flags I mentioned at the beginning. That stuff has to happen before the value we just calculated (in r5) can be assigned to the timing variable.
Map 8 (idle):
1-Axis Map (address 13ac, input variable Engine RPM (0x37))
| Engine RPM (0x37) | Value |
|---|---|
| 600 | 63 |
| 760 | 27 |
| 880 | 27 |
| 1440 | 28 |
| 1760 | 42 |
| 2400 | 42 |
| 3360 | 71 |
Map 91 (part throttle, O2/cat):
2-Axis Map (address 12ca, inputs Engine RPM (0x37), Load (0x49))
| Engine RPM (0x37) \ Load (0x49) | 21 | 26 | 37 | 42 | 48 | 53 | 63 | 79 | 90 | 100 | 122 | 142 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 800 | 35 | 38 | 44 | 47 | 49 | 49 | 49 | 49 | 49 | 49 | 49 | 49 |
| 960 | 35 | 38 | 44 | 47 | 49 | 49 | 49 | 49 | 49 | 49 | 49 | 49 |
| 1120 | 35 | 42 | 49 | 57 | 64 | 64 | 51 | 49 | 49 | 49 | 49 | 49 |
| 1440 | 50 | 57 | 64 | 64 | 64 | 65 | 52 | 49 | 45 | 45 | 45 | 45 |
| 1760 | 55 | 64 | 64 | 68 | 68 | 60 | 57 | 51 | 46 | 45 | 45 | 45 |
| 2080 | 60 | 64 | 64 | 67 | 68 | 65 | 59 | 54 | 52 | 45 | 41 | 41 |
| 2400 | 64 | 71 | 71 | 71 | 71 | 66 | 60 | 55 | 52 | 45 | 41 | 41 |
| 3360 | 70 | 75 | 79 | 79 | 79 | 72 | 69 | 64 | 57 | 49 | 46 | 45 |
| 4000 | 70 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 61 | 54 | 47 | 45 |
| 4640 | 70 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 61 | 54 | 47 | 45 |
| 5600 | 70 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 64 | 56 | 49 | 47 |
| 6240 | 70 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 63 | 55 | 48 | 46 |
Map 92 (part throttle, non O2/cat)
2-Axis Map (address 16fa, inputs Engine RPM (0x37), Load (0x49))
| Engine RPM (0x37) \ Load (0x49) | 21 | 26 | 37 | 42 | 48 | 53 | 63 | 79 | 90 | 100 | 122 | 142 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 800 | 35 | 38 | 44 | 47 | 49 | 49 | 49 | 49 | 49 | 49 | 49 | 49 |
| 960 | 35 | 38 | 44 | 47 | 49 | 49 | 49 | 49 | 49 | 49 | 49 | 49 |
| 1120 | 35 | 42 | 49 | 57 | 64 | 64 | 55 | 49 | 49 | 49 | 49 | 49 |
| 1440 | 50 | 57 | 68 | 69 | 72 | 66 | 55 | 49 | 49 | 49 | 49 | 49 |
| 1760 | 55 | 64 | 74 | 79 | 79 | 69 | 60 | 55 | 49 | 49 | 49 | 49 |
| 2080 | 60 | 67 | 74 | 79 | 79 | 72 | 66 | 59 | 53 | 49 | 45 | 45 |
| 2400 | 64 | 73 | 79 | 79 | 79 | 72 | 66 | 60 | 52 | 45 | 41 | 41 |
| 3360 | 71 | 75 | 79 | 79 | 79 | 72 | 69 | 66 | 57 | 50 | 46 | 45 |
| 4000 | 71 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 63 | 54 | 49 | 46 |
| 4640 | 71 | 75 | 79 | 79 | 79 | 72 | 72 | 70 | 67 | 59 | 51 | 47 |
| 5600 | 71 | 75 | 79 | 79 | 79 | 72 | 72 | 70 | 67 | 61 | 55 | 50 |
| 6240 | 71 | 75 | 79 | 79 | 79 | 72 | 72 | 69 | 66 | 60 | 54 | 50 |
Map 14 (WOT):
1-Axis Map (address 1376, input variable Engine RPM (0x37))
| Engine RPM (0x37) | Value |
|---|---|
| 1000 | 50 |
| 1480 | 50 |
| 2000 | 45 |
| 2120 | 39 |
| 2240 | 39 |
| 2520 | 39 |
| 3000 | 39 |
| 3280 | 42 |
| 3520 | 44 |
| 4000 | 44 |
| 4520 | 44 |
| 5000 | 45 |
| 5520 | 47 |
| 5800 | 46 |
| 6000 | 45 |
| 6240 | 45 |
Map 73 (cranking):
1-Axis Map (address 13cc, input variable Engine RPM (0x37))
| Engine RPM (0x37) | Value |
|---|---|
| 120 | 28 |
| 400 | 50 |
| 920 | 50 |
Map 4 (idle, O2/Cat equipped cars):
1-Axis Map (address 13bc, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| -34.61 | 38 |
| 16.71 | 13 |
| 75.26 | 20 |
Map 6 (idle, non O2/Cat)
1-Axis Map (address 17d8, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| -34.61 | 38 |
| 16.71 | 20 |
| 75.26 | 20 |
Map 18 (part throttle, O2/Cat)
1-Axis Map (address 13c4, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| 7.50 | 20 |
| 16.71 | 6 |
| 65.39 | 20 |
Map 19 (part throttle, non O2/Cat)
1-Axis Map (address 17e0, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| -34.61 | 20 |
| 41.05 | 20 |
| 65.39 | 20 |
Map 12 (WOT, all versions)
1-Axis Map (address 13a2, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| -3.03 | 20 |
| 11.45 | 20 |
| 21.32 | 20 |
| 75.26 | 20 |
Map 13 (air temperature, PT and WOT):
1-Axis Map (address 1398, input variable 0x12)
| 0x12 | Value |
|---|---|
| 77.89 | 20 |
| 81.84 | 20 |
| 84.47 | 20 |
| 85.79 | 20 |
Map 13 from the 944NA:
1-Axis Map (address 1398, input variable 0x12)
| 0x12 | Value |
|---|---|
| -3.03 | 20 |
| 16.71 | 20 |
| 31.18 | 19 |
| 41.05 | 16 |
Map 74 (cranking):
1-Axis Map (address 13d4, input variable Engine Temperature (0x13))
| Engine Temperature (0x13) | Value |
|---|---|
| -31.97 | 27 |
| 19.34 | 20 |
| 75.26 | 13 |