New research/ROAD_TEST_LOG.md: the 2026-09-15 flash of 20262-arrow-delete-lowthrottle-composed.hex was a major real-world success (stalling fully resolved, decel backfire much reduced but not eliminated). Captures the live diagnostic reasoning that followed: correcting an overly-lean-leaning framing mid-session (residual decel pop is more likely a rich-unburned-mixture-with-no-SAI-assist problem, not a lean one), a structural hypothesis that the Idle Fuel Trim (CO) curve likely applies whenever the throttle is closed at any RPM rather than only at a literal stop (only 2 of its 32 points are currently corrected, and the untouched middle range lines up with the residual pop's RPM window), and the reasoning for rejecting airbox-baffle removal as a fix for decel popping specifically (wrong mechanism -- closed throttle isn't airbox-limited -- and risks worsening the separately-flagged, still unaddressed main-VE-table leanness gap). Also updates TABLES.md with the CO-trim applicability open question, and brings TUNING_GUIDE.md's "local research assets" section up to date (it still said maps were gitignored/unflashable, both stale). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FP2GaxS9HkUdL5sLBnjKje
537 lines
31 KiB
Markdown
537 lines
31 KiB
Markdown
# Tuning the 2010 T100 — master reference
|
||
|
||
One place that answers "how do we actually tune this bike, any way we
|
||
want" — indexes everything scattered across this project's research files
|
||
into the actual decision tree: what's possible, what's proven, what's
|
||
still open. Read this first; follow the links for the technical depth
|
||
behind each claim.
|
||
|
||
**The bike:** 2010 Triumph Bonneville T100, 865cc air-cooled twin, Keihin
|
||
ECU (Renesas SH7054), **mechanical odometer** — this generation detail
|
||
matters constantly (see "the one recurring trap" below).
|
||
|
||
**Current real-world state:** SAI plumbing physically removed, solenoid
|
||
left electrically connected (clicking, harmless — [resistor fix
|
||
below](#stop-the-sai-solenoid-clicking)). Exhaust: Arrow 2-in-1 in hand,
|
||
not yet fitted. Airbox: still stock, removal planned for later. Fuel:
|
||
Canadian premium, ≤10% ethanol (E10). ECU not yet connected to
|
||
(`tunie info` never run) — ["Phase 1" in the project's own
|
||
`../STATUS.md`](../STATUS.md), still blocked on the Triumph diagnostic
|
||
connector cable.
|
||
|
||
## Two goals, and they sometimes pull in different directions
|
||
|
||
**Primary:** tune my own bike correctly for whatever mods actually go on
|
||
it — exhaust now, airbox later, whatever comes after that. Accuracy and
|
||
correctness win when this goal is in tension with the one below.
|
||
|
||
**Secondary:** make the tuning process non-invasive and simple enough that
|
||
this gets open-sourced and other riders can use these tools without
|
||
cutting/drilling/welding anything on their bike they can't easily undo.
|
||
|
||
These aren't always aligned, and it's worth being explicit about it rather
|
||
than letting decisions quietly optimize for one without saying so:
|
||
|
||
- The bung-reuse / device-flag-decoupling approach two sections down is a
|
||
case where both goals point the same way — no permanent modification
|
||
*and* it doesn't compromise the primary tune's correctness. When that's
|
||
possible, it's the obvious choice.
|
||
- Elsewhere they can conflict: e.g. two wideband sensors (one per
|
||
cylinder, pre-collector) gives better tuning accuracy (goal 1) but costs
|
||
more hardware and setup complexity than one sensor post-collector (goal
|
||
2). No blanket rule — flag the trade-off explicitly at each decision
|
||
point rather than silently picking one goal's answer.
|
||
- The open-sourcing goal also means the eventual tooling (Phase 0/4 in
|
||
`TUNING_IMPL_PLAN.md`) should stay agnostic to *which* hardware path a
|
||
given rider picked, rather than hard-coding this build's specific
|
||
choices — already the design intent there, worth keeping true as it
|
||
gets built out.
|
||
|
||
---
|
||
|
||
## Is the no-drilling ECU-streamed plan actually achievable? (Aug 2026 status check)
|
||
|
||
Short answer: **architecturally yes, nothing found so far says it can't
|
||
work — but it is not yet proven end-to-end, and it all sits behind the
|
||
one blocker this whole project has had since before this plan existed.**
|
||
Broken down honestly, piece by piece:
|
||
|
||
| Piece | Status |
|
||
|---|---|
|
||
| No drilling — reuse stock O2 bungs for wideband | **Solid.** M18x1.5 thread standard confirmed for both sensor types and this Triumph twin family specifically. |
|
||
| Wideband sensor + controller hardware | **Solid.** Off-the-shelf, well-understood (Innovate/AEM), just needs buying and wiring — execution risk only, no research risk. |
|
||
| ECU exposes RPM/TPS/coolant live | **Mechanism confirmed**, specific record IDs **not yet confirmed for this bike**. Recovered one candidate record array from the decompile (the Keihin-family "default" case), but which `Oe()` switch case actually applies to a Bonneville twin specifically hasn't been traced through the ECU-type-selection logic — it's a good candidate, not a verified one. |
|
||
| Wideband AFR into the onboard logger | **Solid**, standard ADC-reads-a-voltage problem, no open questions. |
|
||
| TuneECU's F Trim / Commit trims workflow | **Confirmed from documentation** (`android.html`), never operated hands-on — no bike has been connected yet, project-wide, per `../STATUS.md`. |
|
||
| Mapping log points to fuel-table cells | **Solid** — RPM/throttle axes already extracted and validated (`reference-maps/table_map.py`). |
|
||
| Polling-loop + onboard-unit integration code | **Not written yet.** Phase 0 of `TUNING_IMPL_PLAN.md` — straightforward extension of `tunie`'s existing transport, but zero lines of it exist right now. |
|
||
|
||
**The one hard blocker underneath all of it:** first ECU contact
|
||
(`tunie info`) has never happened — still stuck on the Triumph diagnostic
|
||
connector adapter, the same blocker `../STATUS.md` has tracked since
|
||
before this tuning plan existed. Record labeling, live-capture validation,
|
||
and confirming the F Trim workflow hands-on all require that connection to
|
||
exist first. Nothing about the plan is contradicted or looks unworkable —
|
||
it's just genuinely unverified past the paper-architecture stage until
|
||
that cable problem gets solved.
|
||
|
||
**So: achievable, yes. Achieved, not yet.** The path from here to "working
|
||
system" is: solve the cable → `tunie info` (resolves current stock map ID
|
||
as a side effect) → label the live records empirically → write the
|
||
polling-loop code (can actually start before the cable's solved, against
|
||
mocked data) → wire the wideband ADC → do a real capture → build the
|
||
first tune. No step in that chain currently looks like it won't work — but
|
||
none of them past "the cable exists" have actually been tried.
|
||
|
||
---
|
||
|
||
## The five ways to get a tune, ranked by effort/reliability
|
||
|
||
### 1. Pick an existing official TuneECU calibration (done, free, safest)
|
||
|
||
For Arrow 2-in-1 + E10 + mechanical odo, TuneECU's own catalogue already
|
||
has the validated answer: **map `20262` or `20313`** (same tune, two
|
||
catalogue reissues — see
|
||
[`reference-maps/PERMUTATIONS.md`](reference-maps/PERMUTATIONS.md) for the
|
||
proof they're identical bar one trivial byte). This is real engineering
|
||
data from Triumph/TuneECU, dyno-validated by them, zero DIY risk. Covers
|
||
exhaust + fuel type. **Does not cover SAI/O2 delete or airbox removal** —
|
||
TuneECU never ships those combined with an aftermarket exhaust, confirmed
|
||
by an exhaustive catalogue-wide keyword search (zero hits across all 1811
|
||
entries, any model) — see
|
||
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#existing-tunes-for-airbox-removal--2-1-exhaust-aug-2026).
|
||
|
||
### 2. Flip the SAI/O2 device flags on top of #1 (done, free, low risk)
|
||
|
||
`reference-maps/toggle_devices.py` flips the three device-enable bytes
|
||
(`0x53801` SAI, `0x53818`/`0x53819` O2×2 — found and independently
|
||
re-verified 3× across different reference maps, see
|
||
[`reference-maps/DEVICES.md`](reference-maps/DEVICES.md)) on any base map.
|
||
Already built: `reference-maps/derived/20262-noSAI-only.hex` (matches your
|
||
resistor-swap plan — see below) and `...-noSAI-noO2.hex`. Confirmed
|
||
byte-for-byte disjoint from the Arrow exhaust calibration, so this
|
||
composition is sound, not a guess
|
||
([`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#what-the-real-device-flag-file-tells-us-about-composing-arrow--delete)).
|
||
|
||
**Important, established this session:** this flag most likely only
|
||
**suppresses the fault-code/CEL logic** for a missing device — it does
|
||
*not* reliably change fueling behavior. Multiple forum sources describe it
|
||
this way, and TuneECU's own current Android UI still separately exposes
|
||
"Idle Fuel Trim (CO)" as *the* parameter specifically for "Triumph without
|
||
O²-Sensor" — i.e. the flag and the fueling correction are two different
|
||
things you both need, not one flag that does both.
|
||
|
||
### 3. Hand-compose a "good enough" trim correction (done, updated Aug 2026)
|
||
|
||
`reference-maps/compose_arrow_delete.py` sets **both** idle-trim bytes now
|
||
(`0x536AB` and `0x5369C`, part of the 32-entry "Idle Fuel Trim (CO)" table,
|
||
[`reference-maps/TABLES.md`](reference-maps/TABLES.md#fuelignition-trim-array--rpm-indexed-32-entries-0x53690-0x536af)) —
|
||
`0x5369C` was originally excluded as "exhaust-family-dependent," but that
|
||
was an artifact of reading the byte as unsigned; the table is signed, and
|
||
in signed space both bytes compose the same way (one saturates at `+127`).
|
||
**This composition is still scoped to the SAI/O2-delete case only — it is
|
||
explicitly NOT an airbox-removal tune.** A real dyno reference on the same
|
||
ECU family
|
||
(`20184dynoTuneSteveO2-Disable.hex`) showed a real airbox+open-pipe mod
|
||
rewrites nearly the *entire* 32-cell trim table plus a further ~50-byte
|
||
region we haven't mapped — see
|
||
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#major-find-a-real-dyno-tune-on-our-exact-ecu-generation-airbox-removed-aug-2026).
|
||
Output: `reference-maps/derived/20262-arrow-delete-composed.hex`.
|
||
|
||
### 4. Buy a matched commercial tune (real, currently blocked)
|
||
|
||
- **Triumph Twin Power (TTP)** had exact-match products (`Tune 11 2-1`:
|
||
airbox removal + 2-1 exhaust + O2/SAI removal) — but the storefront is
|
||
effectively wound down (founder retirement 2022, every SKU checked shows
|
||
"not in stock," hardware supply handed to a sister company). Don't count
|
||
on this.
|
||
- **DNK TuneWorks** ($275, mail-in or self-flash) — no airbox-specific
|
||
variant listed for the 865 air-cooled model, but they've built per-mod
|
||
variants off-catalogue before (per a customer review); worth emailing
|
||
directly. Watch for their *other*, wrong-model product page (liquid-cooled
|
||
2016+ T100) — easy to mix up.
|
||
- Full details: [`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#existing-tunes-for-airbox-removal--2-1-exhaust-aug-2026).
|
||
|
||
### 5. Road-tune it yourself, no dyno (documented, not yet tried, most promising for airbox)
|
||
|
||
A complete, concrete methodology exists and is **confirmed still usable in
|
||
the current TuneECU Android app** (not a dead Windows-only technique):
|
||
wideband O2 sensor + datalogger, drive fixed throttle positions through the
|
||
RPM range, log target-vs-measured AFR, feed the discrepancy into the **F
|
||
Trim** table (swipe right from the F/I table screen), **Commit trims**
|
||
(bakes correction into the permanent fuel table, resets trim to zero),
|
||
repeat 3-4 passes. A dyno session afterward becomes optional fine-tuning,
|
||
not a requirement. Full writeup, sourced:
|
||
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#major-find-a-documented-no-dyno-road-tuning-method-aug-2026).
|
||
|
||
**This is the actual answer to "how do we get airbox removal tuned
|
||
without paying a tuner."** It directly fixes what airbox removal breaks
|
||
(the fuel table no longer matching real airflow) using measured reality,
|
||
sidestepping the whole byte-composition problem in #3 entirely.
|
||
|
||
#### How it actually works, mechanically
|
||
|
||
The ECU's fuel table (`FuelMap`) stores intake air mass in mg for each
|
||
RPM/throttle-position cell. Hardware changes (exhaust, airbox) change the
|
||
real airflow at each cell without changing what's stored — the table goes
|
||
stale until someone re-measures it. A dyno does that by holding the bike at
|
||
load and reading a wideband sensor; this method does the same measurement
|
||
on the road.
|
||
|
||
1. **Get a truthful AFR reading.** The stock O2 sensor is narrowband — fine
|
||
for a 14.7:1 closed-loop target, useless for tuning richer WOT mixtures.
|
||
A wideband sensor + controller reads accurately across the whole range
|
||
actually being tuned (roughly 12.5-14.7:1).
|
||
2. **Flatten the baseline so it can't lie.** Before measuring, load a tune
|
||
with AF1/AF2 (target-AFR tables) both set flat to something safely rich
|
||
(13.0:1 in the original guide) and **F Trim** zeroed. This isolates "is
|
||
the fuel table wrong" from "was the target already weird here" — you
|
||
can't tell those apart if the target varies cell-to-cell too.
|
||
3. **Log real riding data.** Mark the throttle grip at the fuel table's
|
||
throttle breakpoints. On a quiet straight road, hold each mark steady,
|
||
sweep the RPM range, three passes per mark. Log RPM, table
|
||
row/cell, and measured-vs-target AFR at each point.
|
||
4. **Translate the log into corrections.** For each logged point, work out
|
||
the nearest fuel-table cell (interpolating between throttle
|
||
breakpoints) and how far off the AFR was — that error goes into the
|
||
**F Trim** table, a temporary zero-baseline overlay the same shape as
|
||
the main table.
|
||
5. **Commit and iterate.** **Commit trims** permanently bakes the F Trim
|
||
corrections into the real fuel table and zeroes F Trim again. Reflash,
|
||
repeat the same test. Converges in roughly 3-4 passes per the original
|
||
author.
|
||
6. **Finish it.** Once the base table is correct, replace the flat 13.0:1
|
||
placeholder with real AF1/AF2 targets (richer at full load for power,
|
||
leaner at part load for drivability/economy), one final commit.
|
||
7. **Dyno becomes optional** — at that point it's just fine-tuning via
|
||
F Trim on an already-correct base map, not the whole job.
|
||
|
||
#### Tools actually required — and why this isn't a simple process
|
||
|
||
Being honest about this rather than making it sound tidier than it is:
|
||
|
||
**Fabrication.** The wideband sensor needs a bung in the exhaust — either
|
||
weld one in (needs a welder, or a shop that will), or find/reuse an
|
||
existing bung (check whether Arrow's collector has a provision, or whether
|
||
the stock O2 bung is reusable). Not a bolt-on step.
|
||
|
||
**Electrical work.** Wideband controller needs switched 12V, ground, and
|
||
the sensor's connector wired in — same category of work as the SAI
|
||
resistor swap, just for a different, more sensitive sensor (wideband
|
||
sensors are also consumables that die if run rich/unheated wrong, adding a
|
||
"don't fry the $80 sensor" constraint the resistor job didn't have).
|
||
|
||
**A real integration gap, not just extra steps.** The German guide's slick
|
||
workflow — a single log row giving you table position *and* measured AFR
|
||
together, entered with one keypress — depended on a **TuneBoy-specific
|
||
cable with a built-in analog input**, a different product from TuneECU.
|
||
**It is not confirmed that TuneECU's cable has an equivalent.** Without it,
|
||
this becomes correlating *two separate logs* (the wideband's own
|
||
recording, and whatever position/RPM reference you have) after the fact,
|
||
by eye/timestamp — meaningfully messier and more error-prone than the
|
||
streamlined description implies. This is the single biggest unresolved
|
||
question before treating this as a smooth process: does TuneECU support
|
||
synced logging at all, or does every pass require manual correlation.
|
||
|
||
**A second software tool.** The wideband controller has its own logging
|
||
software/app (Innovate LogWorks, AEM's app, or onboard SD), separate from
|
||
TuneECU. Plus a spreadsheet to do the cell-correlation math by hand.
|
||
|
||
**Riding logistics, not just equipment.** Steady, repeatable throttle
|
||
positions at speed, on a quiet legal road, while a logger runs — solo,
|
||
that's watching a gauge/throttle marks/traffic/road all at once, which is
|
||
a real safety concern, not a paperwork one. A passenger to run the logger,
|
||
or a fully autonomous SD-logging setup that needs zero mid-ride
|
||
interaction, is the practical answer, not "just wear the gauge and go."
|
||
|
||
**Time.** Multiple ride-then-reflash cycles (3-4 minimum, per the guide),
|
||
each needing the bike, the logger, a suitable road, and a laptop/Android
|
||
device to reflash between passes. Realistically spread across more than
|
||
one outing, not an afternoon.
|
||
|
||
**Net honest assessment:** this is a real DIY-tuning project — fabrication
|
||
+ wiring + a second logging tool + an unresolved software-integration
|
||
question + genuine riding-safety logistics + several iteration cycles —
|
||
not a "flash a file and go" task. It's the most *correct* path (measures
|
||
your actual bike, not someone else's), but it's the highest-effort of the
|
||
five options in this document, not the easy one. Worth weighing again
|
||
against just paying DNK once/if they confirm an airbox variant (#4) —
|
||
that trades money for skipping all of the above.
|
||
|
||
---
|
||
|
||
## Better idea (Aug 2026): stream live data straight off the ECU with `tunie`
|
||
|
||
The messy "two separate logs correlated by hand" problem above assumed we
|
||
needed TuneECU's app/cable for the ECU side. We don't — `tunie` already has
|
||
verified, safe, read-only K-line access to this exact bike. Its own live
|
||
dashboard is built on the same mechanism, confirmed in the decompile:
|
||
|
||
- **Service `0x21` ReadDataByLocalIdentifier**, already implemented in
|
||
`tunie` (`src/tunie/kwp2000.py`, used today by `identify.py`'s one-shot
|
||
sweep) — same service TuneECU's live dashboard polls continuously via a
|
||
round-robin scheduler (`m.java`'s `se()`/`Oe()` functions).
|
||
- Records are **2-byte extended identifiers** (not the single-byte ones
|
||
`identify.py` currently sweeps), sent as `0x21 <hi> <lo>`. Recovered the
|
||
actual per-model record table for the Keihin-family default case (array
|
||
`ch` in `m.java:204`): decoded record values `0x0100`, `0x0001`,
|
||
`0x5103`, `0x0139`, `0x0007` (recurring across the 15 polled slots).
|
||
- **Not yet labeled** — which record is RPM vs. throttle position vs.
|
||
coolant temp isn't identified from the decompile alone (the field-name
|
||
mapping lives in UI code not yet fully traced). **Fastest way to finish
|
||
this isn't more static tracing — it's empirical, once the cable's
|
||
connected**: poll all 5-ish records in a loop with ignition on, rev the
|
||
engine / move the throttle by hand, and see which values respond to
|
||
what. RPM scales with engine speed, TPS jumps with throttle movement,
|
||
coolant temp changes slowly. Should take one bench session to label all
|
||
of them.
|
||
|
||
### What data we actually need, and where each piece comes from
|
||
|
||
| Data | Source | Status |
|
||
|---|---|---|
|
||
| RPM | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
|
||
| Throttle position (TPS) | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
|
||
| Coolant temp (exclude cold-engine data) | ECU, live poll (`0x21`) | mechanism confirmed, record ID unlabeled |
|
||
| Wideband AFR | **external hardware, no way around it** | ECU's own O2 is narrowband, useless for rich-mixture tuning regardless of any of the above |
|
||
|
||
Everything except the wideband AFR reading can come from the ECU itself,
|
||
via hardware already owned (the K-line/FTDI cable this project needs
|
||
anyway for `tunie info`) and code already 90% built (`tunie`'s existing
|
||
transport + safety layer, just needs a polling loop added instead of a
|
||
one-shot sweep, plus the 2-byte-record variant of `READ_DATA_BY_LOCAL_ID`).
|
||
**No new ECU-side hardware, no dependency on TuneECU's app, no uncertainty
|
||
about a proprietary analog-input cable** — that whole question from the
|
||
previous plan is moot.
|
||
|
||
### The "onboard live processing unit" idea — sound, and better than the alternatives
|
||
|
||
A small onboard computer (Pi Zero, ESP32-class board, etc.) running two
|
||
things in parallel, writing one synced timestamped log:
|
||
1. `tunie`'s KWP2000 poller hitting the labeled RPM/TPS/coolant records in
|
||
a tight loop over the K-line cable — safe by construction, same
|
||
read-only transport already verified against this bike's protocol.
|
||
2. An ADC channel reading the wideband controller's analog AFR output.
|
||
|
||
This **eliminates the correlation problem entirely** — one log, one
|
||
timestamp column, no post-ride guesswork matching a wideband recording
|
||
against hand-marked throttle positions. It also removes the riding-skill
|
||
burden from the original plan (no need to hit exact throttle marks by
|
||
feel — real TPS is captured directly, at whatever resolution the polling
|
||
loop achieves).
|
||
|
||
**Still required, unavoidably:** the wideband sensor + bung + controller
|
||
(external AFR hardware, see below) and someone actually riding the bike
|
||
through the RPM/throttle range while the logger runs — the bike still has
|
||
to physically experience the conditions being tuned for. What this
|
||
approach removes is the *ambiguity* in capturing that, not the ride itself.
|
||
|
||
**Concrete next steps, in order:**
|
||
1. Extend `tunie` with a 2-byte-record variant of `READ_DATA_BY_LOCAL_ID`
|
||
and a polling-loop mode (currently only does the one-shot `identify`
|
||
sweep). Software-only, doable now, no bike required.
|
||
2. First ECU contact (`tunie info`, per `../STATUS.md` — still blocked on
|
||
the connector cable) doubles as the session to empirically label which
|
||
record is which by watching values change.
|
||
3. Once labeled, wire in the wideband ADC channel alongside the KWP2000
|
||
poller on the same onboard unit.
|
||
4. Do the actual road/bench data capture, post-process off-bike into F
|
||
Trim corrections exactly as described above — just with a clean single
|
||
synced log instead of two to hand-correlate.
|
||
|
||
---
|
||
|
||
## Wideband O2 + datalogger setup plan (not yet acquired — hardware/shopping list)
|
||
|
||
The German guide's original hardware (Wayne Macdonald's proprietary
|
||
TuneBoy/TuneEdit cable with a built-in analog input) is **TuneBoy-specific,
|
||
not confirmed to exist for TuneECU** — this project uses TuneECU (Alain
|
||
Fontaine's tool), a different product built on the same underlying
|
||
Keihin/Sagem architecture. Don't assume TuneECU's cable has an equivalent
|
||
analog input; not verified either way.
|
||
|
||
**Practical path that works regardless:** use a self-contained wideband
|
||
controller with its own onboard/SD datalogging, rather than depending on
|
||
feeding the signal into TuneECU's cable directly. Correlate logs to fuel
|
||
table cells by throttle position + RPM (the German guide's own low-tech
|
||
method — marked throttle-grip positions, steady-state RPM sweeps) instead
|
||
of a live electrical link. Candidates, all with standalone logging:
|
||
|
||
- **Innovate LM-2** or **MTX-L** — two programmable analog outputs, widely
|
||
documented for this exact "log AFR, correlate to ECU table cells"
|
||
workflow on other platforms.
|
||
- **AEM X-Series UEGO** — 0-5V analog out + onboard SD logging, CAN option.
|
||
|
||
Both need: a bung in the exhaust for the sensor (weld-in, ideally before
|
||
any downpipe junction — check whether the Arrow 2-in-1's collector has a
|
||
provision, or where the stock O2 bung sits since it may be reusable), a
|
||
controller box (fits under the seat, same area the SAI solenoid used to
|
||
occupy), switched 12V power, and a way to read RPM alongside AFR (either
|
||
the wideband unit's own RPM input if it has one, or TuneECU's own live
|
||
Dashboard screen running in parallel, watched/logged manually).
|
||
|
||
**Setup steps, when the hardware is in hand** (this project can't do this
|
||
part — physical bung welding, sensor mounting, wiring):
|
||
1. Weld/fit the wideband bung, mount the sensor.
|
||
2. Mount the controller, wire switched 12V + ground + sensor.
|
||
3. Power on, let the sensor heat-soak per its instructions before trusting
|
||
readings.
|
||
4. Load a flat/rich baseline tune (per the German method: AF1/AF2 set to a
|
||
uniformly rich target, F Trim zeroed) so there's no lean risk during
|
||
logging.
|
||
5. Do the steady-state throttle sweeps, log AFR vs. target.
|
||
6. Enter corrections into TuneECU's **F Trim** table (swipe right from the
|
||
F table screen), **Commit trims**, reflash, repeat 3-4 passes.
|
||
7. Set real AF1/AF2 targets once the base fuel table is corrected.
|
||
|
||
This is genuinely the first step in this plan that requires physical
|
||
hardware acquisition and bike work — everything before it in this document
|
||
was achievable offline. Flag it to revisit once you're ready to buy parts.
|
||
|
||
## Stop the SAI solenoid clicking
|
||
|
||
Already physically removed the SAI plumbing; solenoid left wired, clicking
|
||
harmlessly (confirmed no DTC risk, no engine risk). Fix: swap the solenoid
|
||
for a **470 Ω / 0.5 W resistor** across the harness connector's two
|
||
solenoid pins (battery disconnected while wiring). Full procedure and
|
||
alternate 50 Ω/10 W option:
|
||
[`COMMUNITY_TUNING.md`](COMMUNITY_TUNING.md#practical-silencing-a-removed-sai-solenoid-with-a-resistor-aug-2026).
|
||
Then flash `derived/20262-noSAI-only.hex` to suppress the resulting DTC —
|
||
device flag toggle is confirmed clean/disjoint from the Arrow calibration
|
||
(#2 above).
|
||
|
||
---
|
||
|
||
## After tuning: the wideband hardware isn't permanent
|
||
|
||
Once the F Trim corrections are validated and Committed into the permanent
|
||
FuelMap, the wideband sensors were a measurement tool, not a runtime
|
||
requirement — pull them and **plug the bungs with standard M18x1.5
|
||
threaded plugs, not a weld**, so they stay reusable for a future re-tuning
|
||
pass without welding again. Optional, not required: leave one wideband
|
||
installed permanently as an ongoing AFR gauge — worth it specifically
|
||
because a fully SAI/O2-deleted bike has zero live feedback watching for a
|
||
lean condition afterward. Detail: `TUNING_IMPL_PLAN.md` Phase 5.
|
||
|
||
## Best version yet: reuse the stock O2 bungs, make the O2-delete decision reversible too
|
||
|
||
**Instead of drilling anything, or permanently deciding O2 delete now:**
|
||
temporarily unscrew the stock narrowband O2 sensors from their existing
|
||
bungs, thread wideband sensors into those same stock locations for the
|
||
tuning sessions (M18x1.5 is standard — no adapter expected, though worth
|
||
confirming on the actual bike), then **put the stock narrowband sensors
|
||
back afterward**. Zero new holes, zero permanent exhaust modification, and
|
||
it reuses hardware the bike already has.
|
||
|
||
**This surfaces a real tension worth being explicit about, though:** the
|
||
original reason for wanting O2 delete wasn't the road-tuning project — it
|
||
was `RESEARCH.md` §9's finding that O2 delete lets the ECU run a richer,
|
||
cooler idle/cruise mixture and fixes snatchy off-idle response. That's a
|
||
**closed-loop** behavior change, at idle/steady-cruise. The wideband/F Trim
|
||
tuning process only ever corrects the **open-loop** main fuel table
|
||
(WOT/acceleration) — it doesn't touch idle/cruise closed-loop behavior at
|
||
all. So if the stock sensors go back in *and* the final flashed tune
|
||
re-enables the O2 device flag, you get an accurately-tuned WOT table for
|
||
the new exhaust, but the original snatchy/hot-idle problem O2 delete was
|
||
meant to fix **comes back** — the two projects don't automatically bundle.
|
||
|
||
**The actual best move: decouple the physical sensor from the software
|
||
flag.** Put the stock sensors back in the bungs either way (no permanent
|
||
mod, always reversible) — but the *final* flashed tune's O2 device flag is
|
||
an independent choice from whether a sensor happens to be physically
|
||
present:
|
||
- Flag **off**: ECU ignores whatever's in the bung, runs open-loop-derived
|
||
richer idle/cruise — gets the original SAI/O2-delete benefit, with a
|
||
stock sensor sitting there doing nothing (harmless).
|
||
- Flag **on**: full closed loop restored, snatchy/hot-idle behavior
|
||
returns, but with the WOT table now correctly matched to the exhaust.
|
||
|
||
Since nothing physical differs between these two states, **switching
|
||
between them later is just reflashing a different device-flag setting —
|
||
no hardware change either way, forever.** That's a stronger "no permanent
|
||
mods" outcome than just avoiding drilled holes: the O2-delete decision
|
||
itself becomes fully reversible, not just the sensor mounting.
|
||
|
||
**One session-scoped exception:** during the actual tuning rides
|
||
themselves, closed loop needs to be bypassed regardless of the final
|
||
plan — a wideband sensor occupying the narrowband's physical spot can't
|
||
also feed the ECU a valid narrowband signal, so the ECU can't run real
|
||
closed loop during that window. This matches the original German guide's
|
||
approach and is normal for any tuning session (dyno tuners do the same) —
|
||
it's a temporary session state, not a permanent change, and doesn't affect
|
||
which final flag setting gets chosen afterward.
|
||
|
||
**Practical note:** repeatedly swapping sensors in and out of the same
|
||
bung across multiple tuning passes is real wear — use anti-seize on the
|
||
threads to avoid the bung seizing after heat cycles.
|
||
|
||
## The one recurring trap: ECU generation
|
||
|
||
**Every dead end and wrong turn in this research traced back to the same
|
||
mistake: mixing mechanical-odometer and LCD-odometer map/ECU generations.**
|
||
They use different `ecu` type codes in `maps.json`, different absolute
|
||
byte offsets for the same logical parameter (confirmed: our device-flag
|
||
offset reads garbage on an LCD-odo file), and are not interchangeable in
|
||
any way. **Before using any map ID or byte offset found anywhere — forum,
|
||
vendor site, or this project's own derived files — check it's the
|
||
mechanical-odo family (`ecu='0'`, or explicitly labeled "Mechanical
|
||
odometer").** Wrong-generation map IDs that came up and got ruled out:
|
||
`20500`, `20505`, `20507`, `20516`, `20498...LCD`, `20500MapAirboxBonnie_LCD`.
|
||
|
||
---
|
||
|
||
## Local research assets (what's actually on disk)
|
||
|
||
- `reference-maps/*.hex` — real downloaded reference maps: stock
|
||
production/aftermarket (`20187`-`20192`), Arrow 2-in-1/2-in-2 all fuel
|
||
variants (`20262`-`20316`), the real SAI/O2 delete map
|
||
(`20188Map2009AIRBOXBONNY.hex`), the America dyno tune
|
||
(`20184dynoTuneSteveO2-Disable.hex`) plus its stock baseline
|
||
(`20185`/`20186`). **Committed to the repo as of 2026-09 (`.gitignore`
|
||
rule removed, explicit user call)** — no longer working-files-only,
|
||
reachable from a phone browser for TuneECU use.
|
||
- `reference-maps/derived/` — our own composed candidates. **Flashable —
|
||
the checksum problem is solved** (`reference-maps/checksum.py`, see
|
||
`docs/ROADMAP.md`). `20262-arrow-delete-lowthrottle-composed.hex` has
|
||
been flashed on the real bike and road-tested successfully — see
|
||
`ROAD_TEST_LOG.md` for actual outcomes, not just derivation.
|
||
- `reference-maps/{decode_map,reconstruct_rom,table_map,toggle_devices,
|
||
compose_arrow_delete,compose_lowthrottle,checksum}.py` — the working
|
||
toolchain: decrypt → flat ROM → named tables → device-flag edit →
|
||
trim-byte compose → low-throttle table compose → checksum patch.
|
||
- `tuneecu_site_mirror/` — full local mirror of tuneecu.net's
|
||
documentation (built by `tuneecu_crawl.py`), including the German
|
||
road-tuning primer. The live site says it won't be updated after
|
||
September 2024 — this mirror is insurance against link rot.
|
||
- `PERMUTATIONS.md`, `TABLES.md`, `DEVICES.md`, `COMMUNITY_TUNING.md` — the
|
||
detailed research trail (how the ROM format works); this file is the
|
||
index. **`ROAD_TEST_LOG.md`** — the separate, real-world log: what
|
||
actually happened flashing and riding on it, distinct from derived facts
|
||
about the ROM.
|
||
|
||
## Open questions, if this gets picked up again
|
||
|
||
1. ~~Byte→percent scaling formula for the "Idle Fuel Trim (CO)" table~~ —
|
||
**upgraded, Aug 2026** (`reference-maps/TABLES.md`): found TuneECU's
|
||
own CO-Trim edit dialog and a live-value dispatcher, both independently
|
||
confirming the raw byte is **signed two's-complement (-128 to 127),
|
||
used directly with no scaling factor** — not the unsigned 0-255
|
||
interpretation used everywhere earlier in this project. This also
|
||
retroactively explains away the "255 looks like a sentinel" puzzle from
|
||
`PERMUTATIONS.md`: unsigned 255 is just signed -1, an ordinary
|
||
near-neutral value. Encoding confidence: low → medium-high. Still open:
|
||
the exact real-world unit (percent, or an ECU-internal unit close to
|
||
it) isn't separately confirmed. Matters for both project goals per the
|
||
reasoning below, now on firmer ground than when this was first flagged.
|
||
2. Map the ~50-byte region (`0x50010`-`0x50C21`) the America dyno tune
|
||
also rewrote — likely ignition/limiter/other trim, unmapped.
|
||
3. Identify `0x53822` — probably traction control or instrument-cluster
|
||
deactivate per the Android UI's device list, unconfirmed for this ECU
|
||
family.
|
||
4. Actually try the road-tuning method (#5) once the airbox mod happens —
|
||
the real next step, not more offline byte analysis.
|
||
5. First ECU contact (`tunie info`) — still blocked on the physical cable
|
||
adapter, per `../STATUS.md`. Would resolve which exact stock map is
|
||
currently flashed, a prerequisite fact this whole document has been
|
||
assuming rather than confirming.
|