This is a detailed code walkthrough of the main fuel adjustment routine for the 944T DME. For a gentler overview that explains the process without getting into the weeds of the code, see this overview article
The main fuel adjustments are done by the routine starting at 1D23 (note: the opendme annotations incorrectly call this routine “lookup_dwell_angle”.
We can summarize the adjustments made here like this:
These are by no means all the fuel calculations! They’re just the ones that are done in this routine. There is also acceleration enrichment, handled elsewhere, and various other adjustments that will need their own separate articles. But this is the closet thing to a “main” fuel adjustment routine, other than the base pulse calculation.
There’s a general pattern in this code roughly like this:
But there are exceptions! The cranking enrichment in particular is very tricky.
Throughout this process, the running total is stored as a 16-bit value in r7:r6, and at the end it’s stored in 4F:4E
Now there is one thing that can be hard to keep track of when reading fuel map related code, and I can’t avoid explaining this before we get into the code. The fuel adjustment values are generally fractions, most commonly using a denominator of 128, but occasionally other values. Now there’s no easy way to store fractions as individual values in a simple 8-bit system like the 8051. Instead, only the numerator is stored, and the division by 128 must happen in the code. But division is generally complicated in a system like this. Luckily, both division and multiplication by powers of 2 are relatively simple (for the same reason that powers of 10 are easy in our everyday decimal system). The way that division by 128 is typically achieved is by:
The reason that division by 256 is used for this is that it’s basically free. Let’s say we do something like this:
mul ab
mov r4, b
The first instruction multiplies 2 8-bit numbers, a and b, producing a 16-bit result with the high byte in b and the low byte in a. Conventionally I usually write this number as b:a. But next we store only b for later, effectively discarding a. This is effectively a rough integer division by 256. It’s subtle and easy to miss, because it’s really something we don’t do rather than something we do - we just don’t bother to keep track of a.
Having done this, all we need to do next is multiply by 2. This is very easy to do by shifting the bits to the left like this:
mov a, r4
rl a
Now we have effectively achieved division by 128 with a minimum of instructions. But these two steps can often be very far removed from each other in the code, making it really hard to keep track of things.
To complicate matters further, our fuel value is stored as a 16 bit number, so instead of using the mul instruction, we will often call the 8x16 multiply routine 04D9, which multiplies an 8-bit number with r7:r6, and produces a 24-bit result in r7:r6:r5 - and then we often later call it again, which effectively discards the low byte r5, again in a very subtle, easy-to-miss way. Think of it like this: if the routine takes r7:r6 as input, and returns r7:r6:r5 as output, then simply chaining multiple calls is going to result in a division by 256 each time!
In the fuel adjustment routine we’re looking at here, the left-shifts are tracked using the variable 50h. This location keeps track of the number of times we need to multiply by two to get the denominator to come out right. But there are complications with that too, which are explained later.
One more slight complication is that while most fuel adjustments are in this multiplier form, a few (including warmup enrichment) are represented as additive values. This doesn’t really complicate most of the routine though, because the logic of combining all the adjustments together is hidden away in two helper routines, 1DF0 and 1DE7. You can think of 1DF0 as a routine that handles multiplier values, and 1DE7 as an extension of 1DF0 that handles additive values. You’ll see one or the other of these routines being called after a map lookup throughout this routine, so knowing this in advance should help a bit. The actual details of these two routines are explained in the Appendix.
On to the routine itself - it all starts with this section:
X1d23:
mov dptr,#X112e
lcall X05cd
clr acc.2
X1d2b:
add a,#1bh
X1d2d:
movc a,@a+dptr
mov b,a
mov r2,#2ah
lcall X051d
mul ab
mov r7,b
mov r6,a
mov 50h,#2
We start by loading the FQS map location into dptr and then calling the map lookup routine 05CD. Bit 2 of the resulting value is cleared (that bit is used only for timing adjustments).
Here’s what this raw FQS map looks like:
| 0x17 | Value |
|---|---|
| 0 | 0 |
| 35 | 1 |
| 73 | 2 |
| 102 | 3 |
| 122 | 4 |
| 136 | 5 |
| 150 | 6 |
| 160 | 7 |
Let’s render it in a slightly more readable form:
| FQS voltage at ADC | 0 | 0.68 | 1.43 | 2.0 | 2.4 | 2.66 | 2.94 | 3.14 |
|---|---|---|---|---|---|---|---|---|
| FQS switch position | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
This map takes the FQS value in 17h (read by the ADC) as input and maps it to the range 0-7, indicating which of the eight possible positions the switch is in. I’ve included actual voltage measurements made at the ADC input Channel 7 in the Appendix section.
Clearing bit 2 has the effect of mapping the second four positions onto the first - in other words, for fueling purposes, positions 4-7 are identical to 0-3. (The only difference with these later 4 positions is that they also include an ignition timing change.)
Recall that the map lookup routine restores the dptr to it’s “home” value of 1160, so adding our FQS map value to 1B before the dptr lookup gets us to 1160 + 1B + fqs_value, that is, 117B+fqs_value. The locations starting at 117B contain:
| Offset (FQS Position) | Hex value | Decimal value | Effect on fuel |
|---|---|---|---|
| 0 | 80h | 128 | 0 |
| 1 | 84h | 132 | + 3.1% |
| 2 | 7Ch | 124 | - 3.1% |
| 3 | 88h | 136 | + 6.25% |
So now we have stored our FQS value in b, and next we look up Map 42 (2Ah). Map 42 is the air temperature correction map and its raw form is
| Air temperature (C) | Value |
|---|---|
| -34.61 | 144 |
| -13.55 | 138 |
| 16.71 | 128 |
| 55.53 | 128 |
The value from this is multiplied with our FQS value in b and we store the result in r7:r6.
Next, we check if 25h.7 is set. This bit being set indicates that the altitude sensor is active, which corresponds to an altitutde of > 1000M. If this is set, we do a dptr lookup for 1160+6B=11CB, which contains the value 78h, that is 120 decimal.
The call to 1DF0 handles combining this adjustment with r7:r6.
At this point, r7:r6 contains the product of the FQS, air temperature and altitude adjustments.
Next we have:
X1d44:
jnb 23h.2,X1da9
mov r2,#28h
mov a,#1fh
movc a,@a+dptr
cjne a,13h,X1d4f
X1d4f:
jnc X1d52
inc r2
X1d52:
lcall X051d
mov 3ch,a
The bit 23h.2 indicates that the engine is cranking; thus what follows is startup-specific code. There are 2 adjustment stages within this cranking-specific block: cranking enrichment, and post-start enrichment.
The post-start enrichment is handled first. The result will end up in 3C, which is decremented in the main loop. The result is that this kind of enrichment is phased out within around 30 seconds after startup regardless of temperature or rpm.
If the cranking bit is set, we load map 40 or 41 depending on the coolant temperature. The constant 1F points to location 117F, which contains A2h, or 162 in decimal. This corresponds to about 65C (using [value - 62.6]/1.52).
If 13h >= this value, c will be set and we’ll switch to map 41. Otherwise we stick with Map 40.
Here is Map 40:
| Engine Temperature (0x13) | Value |
|---|---|
| -13.55 | 90 |
| 16.71 | 45 |
| 35.79 | 51 |
| 55.53 | 0 |
And Map 41:
| 0x12 | Value |
|---|---|
| 41.05 | 0 |
| 55.53 | 39 |
| 65.39 | 39 |
| 75.26 | 39 |
Note that Map 40 uses 13h (NTC II, i.e. coolant temperature) while 41 uses 12h (NTC I, i.e. intake air temperature).
The switch over happens at 65C coolant temperature, but from the air temp values in the map we can see that Map 41 only makes corrections at very high intake air temps. This is therefore probably for heat-soak enrichment. So we can interpret this logic to mean that above 65C, no compensation is needed for the engine block’s temperature, but heat soak is a possibility, to be determined by air temperature. When the block is below 65C presumably there’s no risk of running lean from heat soak.
Also note that these maps are additive, so 1DE7 will be called when 3C is added to the main adjustment a little later. That’s all there is to the post-start enrichment, except for the logic that decrements 3C, which happens elsewhere.
Next we have the cranking adjustment, which is by far the most complicated part of this entire routine.
First we load Map 83, which looks like this:
| Engine Temperature (0x13) | Value |
|---|---|
| -31.97 | 182 |
| 16.71 | 28 |
| 36.45 | 18 |
| 58.16 | 12 |
These values are fractions with a denominator of 8 as we’ll see soon. The value from this map is the base cranking enrichment - but it can be modified.
There are really two paths through this part of the routine, a simple one and a complicated one. We’ll cover the simplest one first: this is where the we have had 12 or fewer rotations, and have not hit 600rpm yet.
mov r2,#53h
lcall X051d
mov r4,a
mov r3,#5
mov r1,#8
The value from Map 83 is stored in r4. Next we set two local variables. The first, r3, is used later as an exponent, so that the final cranking enrichment will be multiplied by 2^r3. Since r3=5, that means 32, which is why Map 83 values are really have a denominator of 8 (the usual division by 256 will happen later).
There’s a reason why the exponent of 5 isn’t hard coded as 5 though - we’ll see that very soon. Next, r1 represents the denominator of our cranking adjustment multiplier. This is used later to make sure that we don’t ever reduce fuel, i.e. if the numerator is less than r1, we’ll skip adjustment entirely. As with r3, you’ll soon see why this is a variable.
jb 21h.4,X1d78
lcall X051d
clr c
subb a,37h
jc X1d73
mov a,#1ah
movc a,@a+dptr
subb a,4dh
jnc X1d95
X1d73:
setb 21h.4
mov 4dh,#0
Initially, 21h.4 will be clear so we won’t jump to 1D78. The section immediately after that flag check can be read as
a = read_map(84) # input engine temp, output rpm/40
if rpm > a OR 4D > 12:
set 21h.4
4D = 0
start reducing cranking enrichment...
else:
apply the full cranking enrichment from Map 83/8
Thus 21h.4 just keeps track of whether the phasing out of this enrichment has been triggered or not. This is necessary because we might exceed the rpm threshold, but then dip back below it again. This flag ensures that the phase out will continue anyway.
The counter 4D gets incremeted in the real time part of the code, when the fuel injectors are fired.
Map 84 contains 15 (i.e. 600rpm) for all entries. You might think that if the rpm is this high, then the cranking flag 23h.2 wouldn’t be set, so we couldn’t even be in this section of the code at all (due to the 23h.2 check back at 1D44). But while 23h.2 is initially set when the rpm is measured at 160rpm, it’s not cleared until the rpm exceeds a much higher temperature dependent threshold, which is at least 720rpm (determined by Map 3).
So we’ll dig into that phase-out part next. It’s based on the combination of two maps, 85 and 86, both of which are straight lines that slope downwards with increasing inputs (injection count and rpm respectively).
X1d78:
mov r2,#55h
lcall X051d
mov b,a
lcall X051d
mul ab
Don’t forget that the map read routine 051D always leaves r2 incremented from its previous input, so the second map read here read map 86.
The mul ab instruction leaves the high byte in b and the low byte in a.
Next, recall that the value from Map 83 was stored in r4 earlier, and also that r3=5 and r1=8. Now, our Map 83 value is normalized and the exponent is scaled appropriately:
mov a,r4
X1d84:
jb acc.7,X1d8e
rl a
xch a,r1
rl a
xch a,r1
dec r3
sjmp X1d84
;
This is a hard loop to read, but what’s happening is that the base value is being scaled up (multiplied by 2) as many times as needed to get it into the range of >128, but simultaneously, r1 is scaled in exactly the same way, and r3 is decremented. The end result is that:
Map 83 * 2^r3
will come out the same as before, and r1 will represent the new denominator, used for making sure that the adjustment doesn’t ever fall below 1.
The purpose of all this is to get greater precision in the multiplication and division that come next.
Then
X1d8e:
mul ab
mov r4,b
jnb acc.7,X1d95
inc r4
X1d95:
mov a,r4
cjne a,rb0r1,X1d99
X1d99:
jc X1da6
Recall that b has the high byte of the product of Maps 85 and 86, which decrease with temperature. So the code above multiplies our cranking adjustment value by b and rounds up if the low byte is 128 or higher, then stores the high byte of the result in r4.
Next we skip making the adjustment if our adjustment is less than the adjusted denominator.
The final section:
lcall X04d9
mov a,50h
add a,r3
lcall X0509
X1da4:
mov 50h,a
X1da6:
ljmp X1ddd
The 04D9 routine is the generic 8x16 multiply routine. This applies our final cranking adjustment to the main adjustment value r7:r6.
Next, 0509 is the left-shit routine that doubles our value by the number indicated by a, if there is room on the left, and stores the number of pending shifts back in a if there isn’t room. The explanations of 1DF0 and 1DE7 in the Appendices cover this in more detail.
That’s it for the really complicated part! Everything since the test of 23h.2 up to this point applies only during cranking. In steady-state operation, the code will jump from that flag check to 1DA9, skipping all the cranking enrichment stuff, and now we’re at 1DA9. This is the warm-up enrichment section, and it’s also where the post-start enrichment we calcualted earlier (3C) is incoroporated.
The warm-up enrichment is a 2-map process. First the basic temperature adjustment is looked up from one map (43 or 45) based on engine temperature. But then this adjustment value is scaled by the value from another map based on rpm and load (47 or 48). Here are the details:
X1da9:
jb 25h.5,X1db0
acall X1ff2
ajmp X1db3
X1db0:
lcall X1d10
Here we check the flag 25h.5 which is a coolant temperature threshold flag that’s set to 1 if the coolant temperature is below around 15c.
Recall that the last map we looked up was 42. The map lookup routine uses r2 for the map number, and it generally incrememnts this location after a lookup. So right now r2=43.
If the 25h.5 flag is set, then we call 1D10 which just adds 2 to r2. That gets us map 45. Otherwise, we call 1FF2 which selects a map based on region coding - this will just be 43 for US cars, 44 for RoW, and 45 or 46 for certain specific countries.
These maps are very similar - they take engine temp (NTC II) as input and return fuel adjustment multipliers. Here’s Map 43 (location 1465h):
| Engine Temperature (C) | Value |
|---|---|
| -34.61 | 166 |
| -13.55 | 90 |
| -3.03 | 67 |
| 11.45 | 45 |
| 16.71 | 15 |
| 69.34 | 0 |
This map is a little different from the other fuel adjustments in that the values represent additive increases in fuel. That is, a value of 15 for example means “add 15/128”. This logic is handled in the 1DE7 routine, which is explained in more detail in the Appendix.
Next:
X1db3:
lcall X051d
mov b,a
mov r2,#2fh
jnb 23h.0,X1dbe
inc r2
X1dbe:
lcall X051d
mul ab
mov a,b
acall X1de7
Here we get the value for map 43 and then we load 47 (2Fh) into r2 for the next map lookup. But if the TPS is at the idle position, then we incremenent r2 so that we’ll be looking up map 48 instead.
These maps contain scaling factors for the temperature adjustments. Here’s what they look like:
Map 47 (part throttle and WOT, location 1473h):
| RPM (0x37) \ Load (0x49) | 40 | 80 | 140 |
|---|---|---|---|
| 1000 | 255 | 255 | 255 |
| 3000 | 255 | 255 | 192 |
| 5000 | 255 | 179 | 128 |
Map 48 (idle, location 1486h):
| RPM (0x37) | Value |
|---|---|
| 520 | 255 |
| 1520 | 255 |
These two follow a slightly different rule from the fuel adjustment maps we’ve seen so far: with these, the denominator is 256. So a value of 255 (close enough to 1) indicates no effect, and 128 will reduce the warm-up enrichment value from map 43 by half.
At this point we add the startup enrichment value 3C; this was calculated earlier, but it follows the same pattern as the warmup enrichment so it needs 1DE7 to be called:
mov a,3ch
acall X1de7
Next we handle the main driving mode maps: idle, part throttle, and WOT.
mov r2,#31h
jb 23h.0,X1dd6
mov r2,#4bh
jb 23h.1,X1dd6
mov r2,#4fh
X1dd6:
acall X1ff2
lcall X051d
acall X1df0
This is pretty straightforward. We initialize r2 (the map number variable) with map 49 (31h) which is the idle fuel map. Then we check if we’re at idle and if not, we replace it with 75 (4Bh) which is the WOT map. Then we check if we have the WOT condition (23h.1=1) and if not, we finally set r2 to 79 (4Fh) which is the part throttle fuel map.
Next we call the region-based map selection routine 1FF2 (discussed earlier), then the map lookup routine, and finally our accumulation routine 1DF0 which we discussed earlier.
Finally, we store our complete fuel adjustment values in 4Fh:4Eh.
X1ddd:
ljmp X1050 ; this ultimately just jumps to 1DE0 below
X1de0:
mov 4eh,r6
mov 4fh,r7
ljmp X1053
And that’s pretty much it for this particular fuel adjustment routine. But I think it’s helpful to jump away to another routine to see where these values actually get used. So, the code below is run just after the base pulse width is calculated, as discussed here.
What this code does, in brief is:
The routine at 0455 is a 16x16 bit multiply routine that produces a 24 bit result (the lowest byte is discarded).
The next part is a little tricky to understand. Recall that earlier we saw how 50h was used to keep track of how many times our fuel calculation needed to be multiplied by 2, and also that these multiplications were done by shifting the bits to the left. But the routine that handles that (0509, both here and in 1DF0) stops in its tracks if the value in the leftmost bit is a 1. Otherwise we’d lose information when we shift that bit out to the left. That can happen sometimes in 1DF0 because we’re working with 24 bit values. Here, the 16x16 multiply produces a 32 bit result, which opens up more space, so if there are any outstanding multiplies needed, 50h will have a record of that and we can do it here.
This is a pretty complicated way of doing it - why not leave all the bit shifting until this point? Honestly I don’t know, but that’s how it works.
The reason for the pop 50h instruction is that 50h was previously pushed onto the stack because the acceleration enrichment routine uses its own copy of 50h. We’ll cover that in another article though.
X040d:
mov r0,4eh
mov r1,4fh
acall X0455
mov a,50h
X0415:
acall X0509
pop 50h
lcall X1038
mov r2,#1ah
acall X051d
X0420:
mov 54h,a
X0422:
clr ea
mov 4bh,r7
mov 4ah,r6
clr ie0
setb ea
lcall X103b
jnb 23h.6,X043a
mov r1,#50h
Actual voltages measured at the FQS channel on the ADC:
| FQS Position | ADC input voltate |
|---|---|
| 0 | 0 |
| 1 | 1.143v |
| 2 | 1.754v |
| 3 | 2.281v |
| 4 | 2.516v |
| 5 | 2.839v |
| 6 | 3.048v |
| 7 | 3.254v |
The fuel adjustment routine basically looks up a series of maps and combines the resulting values. That’s what we discussed in details in the main article. But we glossed over how the values are combined, because it’s complicated and messy. Here we’ll discuss that code in detail.
There are 2 routines here, 1DF0 and 1DE7. Strictly speaking they are different entry points to a single routine, in the sense that if we call 1DE7, then 1DF0 will run also (see the code below).
The best way to understand these is to look at 1DF0 first.
X1de7:
jz X1dfb
add a,#80h
jnc X1df0
rrc a
inc 50h
X1df0:
lcall X04d9
mov a,50h
X1df5: inc a
lcall X0509
mov 50h,a
X1dfb: ret
Starting at 1DF0, first we call 04D9 which is the Motronic’s 8x16 multiply routine. This routine multiplies a by r7:r6, with a 24 bit result in r7:r6:r5.
Generally the code that calls 1DF0 puts the fuel adjustment value in a, and the previous adjustment value is already in r7:r6 (see the start of this article).
Next we increment 50h and call 0509 which is the left-shift routine. That routine shifts the bits of r7:r6:r5 to the left as many times as indicated by 50h. Crucially, this routine guards the case where the leftmost bit is 1. In that case, it stops shifting the bits, but leaves the number of outstanding shift steps in a. Here, after 0509 returns, we store that value back into 50h.
Let’s think about this for a minute. You know from earlier that the reason for shifting the bits to the left is to multiply by two, and this is needed because it’s part of the division by 128. Generally, one map lookup requires one multiplication by two and one division by 256 to get an overall division by 128. But sometimes multiple adjustments are combined without calling 1DF0 (for example, see the beginning of the adjustments earlier). In that case, 50h must be “primed” with the number of such lookups. Thus when 1DF0 is called, 50h could contain 3. But it’s quite possible that there isn’t enough space to shift the bits of the resulting value to the left without running into the end. In that case, 0509 will stop but it will return a record of the number of shift steps that were omitted in a, and now we’re storing that in 50h.
Later, as we saw in the main article, at 040D, the fuel value is turned into a 32 bit number, which opens up more empty space on the left. At that point we perform the remaining outstanding bit shift steps using the value in 50h.
This is a really confusing way of handling this. Why not just leave all the bit shifting until 040D? I really have no idea and it was pretty hard to figure out what was going on here. Maybe that was the idea!
Now let’s take a look at 1DE7. This routine is used for the warm-up enrichment (which we covered earlier) and acceleration enrichment maps (which we didn’t cover here). The difference with these maps is that the values they contain represent additive increases in fuel rather than multipliers.
X1de7:
jz X1dfb
add a,#80h
jnc X1df0
rrc a
inc 50h
X1df0:
...
X1dfb: ret
Firstly, if a is zero, we short-circuit and return.
Next we add 128 (80h) to the input value, which has the effect of biasing it to match the existing multiplicative fuel adjustments. For example if the input in a was 15, this step would turn that into 15+128, which is the correct value to multiply the existing fuel adjustment by. In other words it has the same effect as having the value 143 in one of the other maps. Why not just represent it as a multiplicative value of 143? Honestly I don’t know.
Anyway, if this addition doesn’t overflow, then we simply call 1DF0 and everything proceeds as before. If there is an overflow, we divide the result by 2 and increment 50h which will cause the final value to get doubled later when there’s more room.
This might seem strange, but when we rotate right, the carry bit (which is set from the overflow) rotates in from the left, leaving us with the overflow value plus 128. Thus the effect of this code is to leave us with (a+128)/2, which will be restored to the full value by doubling later. An example might make it clearer. Let’s say the intput is 166, the highest value from Map 32, corresponding to the lowest temperature. When we do
128 + 166 = 294
…we get an overflow, leaving us with a=38 and the carry bit set. Then the rrc instruction turns this into
128 + 19 = 147
…because the rrc divides 38 by 2 but also puts the carry bit on the left.
Now because we incremented 50h, we will eventually double our final value, which restores us to 294.
Everything the fuel adjustment routine (1D23) touches, along with the post-load section at 040D. See the memory map for the master list.
| Location | Purpose |
|---|---|
| 11h | system voltage, the axis for the injector dead-time map |
| 12h | intake air temperature (NTC I) |
| 13h | coolant temperature (NTC II) |
| 17h | FQS switch position, as read by the ADC |
| 37h | engine speed (rpm/40) |
| 49h | load |
| 3Ch | post-start enrichment, phased out by the software counter |
| 4Dh | injection event counter, used to phase out cranking enrichment |
| 4Ah | low byte of the final fuel pulse, in timer ticks |
| 4Bh | high byte of the final fuel pulse |
| 4Eh | low byte of the accumulated fuel adjustment |
| 4Fh | high byte of the accumulated fuel adjustment |
| 50h | count of outstanding left shifts (pending multiplications by 2) |
| 54h | injector dead-time |
| Flag | Purpose |
|---|---|
| 21h.4 | latch marking that the cranking enrichment phase-out has been triggered |
| 23h.0 | TPS at idle |
| 23h.1 | WOT |
| 23h.2 | cranking |
| 25h.5 | coolant temperature was below ~15C during cranking |
| 25h.7 | altitude above 1000M |
| Reference | Address | Purpose |
|---|---|---|
| FQS map | 112Eh | maps the ADC value in 17h to a switch position of 0-7 |
| 1160+1Bh+FQS | 117Bh-117Eh | fuel offset per FQS position (128, 132, 124, 136) |
| Map 42 (2Ah) | 145Bh | intake air temperature correction |
| 1160+6Bh | 11CBh | altitude correction applied when 25h.7 is set (78h, i.e. 120) |
| 1160+1Fh | 117Fh | coolant threshold selecting Map 40 vs 41 (A2h, i.e. ~65C) |
| Map 40 (28h) | 1566h | post-start enrichment below 65C, by coolant temperature |
| Map 41 (29h) | 1570h | heat soak enrichment above 65C, by intake air temperature |
| Map 83 (53h) | 157Ah | base cranking enrichment, by coolant temperature |
| Map 84 (54h) | 1584h | rpm threshold that starts the cranking phase-out (600rpm) |
| Map 85 (55h) | 158Ah | cranking phase-out scaling, first term |
| Map 86 (56h) | 1590h | cranking phase-out scaling, second term |
| 1160+1Ah | 117Ah | injection count threshold for the cranking phase-out (12) |
| Maps 43-46 | 1465h, 17E8h, 1B14h, 1A00h | warmup enrichment by coolant temperature; 43/44 by region, 45/46 for cold cranking |
| Map 47 (2Fh) | 1473h | warmup enrichment scaling, part throttle and WOT (by rpm and load) |
| Map 48 (30h) | 1486h | warmup enrichment scaling, idle (by rpm) |
| Map 49 (31h) | 1538h | idle fuel map |
| Map 75 (4Bh) | 1544h | WOT fuel map |
| Map 79 (4Fh) | 148Ch | part throttle fuel map |
| Map 26 (1Ah) | 1598h | injector dead-time by system voltage, stored in 54h |