# Software — Turbo Busa — HasiKe

> Die ECU-Software der Turbo Busa erklärt: Speeduino-Firmware auf dem Teensy 4.1 — Drehzahlberechnung, Einspritzformel, Korrekturen, Zündwinkel & Dwell, Boost-PID. Mit interaktiven Animationen.

Quelle: https://hasike.de/turbo-busa/software/

## Der *Ablauf*.

Von der Zahnflanke am Kurbelwellensensor bis zum Funken vergehen wenige
 Millisekunden — und dazwischen läuft diese Kette. Jeden Block antippen für Details.

↑ Block antippen — hier erscheinen die Details.

## Drehzahl & *Kurbelwinkel*.

Alles beginnt mit einer Zeitmessung: Der Decoder stoppt die Mikrosekunden
 zwischen den Flanken des Geberrads. Das Kurbelrad der Hayabusa trägt acht
 gleichmäßig verteilte Nocken — alle 45° eine Flanke, acht Messpunkte pro
 Umdrehung. Daraus leitet die Firmware ihre wichtigste Größe ab:
 **Wie viele Mikrosekunden dauert ein Grad Kurbelwinkel?**

Bei 11.000/min bleiben pro Grad nur noch **~15 µs**. Deshalb
 rechnet die Firmware hier nicht mit Gleitkomma, sondern mit
 **Festkomma-Arithmetik** — Winkel→Zeit als UQ24.8, Zeit→Winkel
 als UQ1.15. Das ist auf dem Cortex-M7 in wenigen Takten erledigt und
 driftet nicht.

**Mehr Details**

- Code: `crankMaths.cpp` — `angleToTimeMicroSecPerDegree()` und `timeToAngleDegPerMicroSec()`, beide inline und LTO-optimiert.
- Der Umrechnungsfaktor wird einmal pro Umdrehung neu gesetzt (`setAngleConverterRevolutionTime()`), nicht bei jedem Aufruf neu dividiert.
- Sync: Weil alle acht Nocken gleich aussehen, liefert das Kurbelrad allein keine Referenz. Erst die einzelne Nocke an der Nockenwelle legt fest, welche Flanke welchem Kurbelwinkel entspricht — und welche der beiden Umdrehungen des Viertakts gerade läuft. In Speeduino ist das der Dual-Wheel-Decoder: gleichmäßiges Kurbelrad plus Nockenwellen-Sync.
- Auflösung: 45° zwischen zwei Flanken sind grob. Zwischen den Nocken rechnet die Firmware linear aus der zuletzt gemessenen Umdrehungszeit hoch — bei stark wechselnder Drehzahl ist genau das die größte Fehlerquelle im Zündwinkel.
- Verliert der Decoder den Sync, fällt die Firmware kontrolliert auf semi-sequentiellen Betrieb zurück, statt zu raten. Das aktuelle Tune fährt ohnehin schon so — Sequential kommt erst, wenn der Trigger-Winkel am Motor bestätigt ist.
- Gemessene Genauigkeit auf dem Teensy 4.1: Trigger-Jitter < ±0,1°, Zündung ±0,05°.

## Die *Einspritzformel*.

Die zentrale Formel der Einspritzung. Ausgangspunkt ist `REQ_FUEL` —
 die Einspritzzeit, die bei 100 % Zylinderfüllung genau das
 Stöchiometrie-Gemisch (14,7:1) ergibt. In meinem Tune:
 **8,3 ms pro Arbeitszyklus** — gerechnet aus 1299 cm³, vier
 Zylindern und den Stock-Düsen mit 257 cm³/min. Das VE-Kennfeld sagt, wie
 voll der Zylinder wirklich ist, der MAP-Anteil skaliert mit dem Druck, und
 am Ende kommt die Injektor-Öffnungszeit dazu.

Die harte Grenze ist der **Duty Cycle**: Der Injektor braucht Zeit
 zum Schließen. Die Firmware deckelt die Pulsweite auf 85 % des
 Arbeitszyklus — dreh den Regler hoch und schau, wann das Limit zuschlägt.
 Genau da entscheidet sich beim Turbo-Umbau die Injektorgröße: Mit den
 Stock-Düsen ist bei rund 150 PS Schluss.
 [→ Düsen im Tune](../tune/#einspritzung)

**Mehr Details**

- Code: `fuel_calcs.cpp` — die Kette `computeInitialPw → applyMapMode → applyAFRMultiplier → applyCorrections → includeOpenTime → includeAe`, komplett in Ganzzahl-Arithmetik.
- Der **MAP/Baro-Faktor** ist im aktuellen Tune **abgeschaltet**: Die Last kommt über die Drosselklappe, und ohne Lader bleibt der Saugrohrdruck unter dem Umgebungsdruck. Sobald der Turbo dran ist, muss er an — der Regler oben zeigt, was er dann rechnet.
- Die Öffnungszeit (1,0 ms) wird **batteriespannungs-korrigiert**: Bei 6 V braucht der Injektor das 2,5-fache, ab 14 V den einfachen Wert (`injOpen × batCorrection`).
- Einspritzung läuft derzeit **semi-sequentiell**, gepaart 1+3 und 2+4: zwei Einspritzungen pro Arbeitsspiel, dafür völlig unabhängig von der Nockenwellenphase. Die Firmware halbiert REQ\_FUEL dafür selbst. Vollsequentiell wird erst umgestellt, wenn der Trigger-Winkel mit der Blitzpistole bestätigt ist.
- PW-Limit im Code: `dutyLim % × T_rev × 2 / nSquirts` — die ×2, weil ein Viertakt-Zyklus zwei Umdrehungen dauert.
- Beschleunigungsanreicherung (AE) reagiert auf die Änderungsrate der Drosselklappe (TPS-DOT) — das Pendant zur Beschleunigerpumpe beim Vergaser.

## Die *Korrekturen*-Kette.

Das Kennfeld gilt für den warmen Motor am Meer. Alles andere regeln
 Korrekturen: Jede liefert einen Prozentwert, alle werden
 **multipliziert** und skalieren am Ende die Pulsweite.
 Kalter Motor? +30 %. Heiße Ansaugluft? Dünnere Luft, −5 %.
 E85 im Tank? Braucht ~30 % mehr Volumen.

Bei der **Zündung** läuft dieselbe Idee als Addition: Der
 Basis-Zündwinkel aus dem Kennfeld wird Stufe für Stufe verschoben —
 Ethanol-Anteil früher, heiße Ansaugluft später, Klopfen sofort später.
 Fixe Winkel (Kurbeln, Grundeinstellung) überschreiben am Schluss alles.

**Mehr Details — die echten Ketten aus dem Code**

- **Sprit** (`correctionsFuel()`): Warmup (WUE) → Nachstart (ASE) → Kurbeln → Beschleunigung (AE) → Flood Clear → Lambda-Regelung (PID) → IAT-Dichte → Baro → Flex Fuel → Sprittemperatur → Launch → Schubabschaltung (DFCO). Produkt gedeckelt bei 1500 %.
- **Zündung** (`correctionsIgn()`): Flex → Wasser-Methanol → IAT-Retard → CLT-Advance → Leerlauf → Soft-Limiter → Nitrous → Soft-Launch → Flat-Shift → **Klopf-Retard** → DFCO → fixe Winkel zuletzt.
- **Aktiv sind heute nur Warmlauf, Nachstart, Anlassen, Ansaugluftdichte und Beschleunigung.** Lambda-Regelung, Flex-Fuel, Klopf-Retard und Schubabschaltung sind im Tune abgeschaltet — die Regler oben zeigen, was die Kette rechnet, sobald sie an sind. Eine Regelung, die auf ein noch grobes VE-Kennfeld draufregelt, verdeckt genau die Fehler, die man finden will. [→ Korrekturen im Tune](../tune/#korrekturen)
- Die Lambda-Regelung ist ein eigener **PID-Regler**, der die Breitband-Sonde gegen den AFR-Zielwert aus dem Kennfeld regelt — aber nur im eingeschwungenen Betrieb, nicht beim Beschleunigen.
- Klopf-Retard (in Arbeit, zusammen mit der TPIC8101-Hardware): Klopfen erkannt → Zündung sofort ein paar Grad später, danach langsame Erholung in 0,5°-Schritten.
- Batteriespannung korrigiert nicht die Pulsweite, sondern gezielt die Injektor-Öffnungszeit und den Dwell — dafür gibt es getrennte Spannungs-Kennlinien.

Kennlinien beispielhaft — die echten Kurven liegen im Tune.

## Zündwinkel & *Dwell*.

Die Zündspule braucht eine feste **Ladezeit (Dwell)** — in meinem
 Tune 1,6 ms im Betrieb und 2,5 ms beim Anlassen, weil die Bordspannung
 dann einbricht. Das Problem: Die Firmware plant in Millisekunden, die
 Kurbelwelle dreht aber in Grad. Also wird der Dwell mit dem
 µs-pro-Grad-Faktor aus Rechnung 01 in einen Winkel umgerechnet und
 **rückwärts vom Zündzeitpunkt** geplant.

Dreh die Drehzahl hoch: Aus denselben 1,6 ms werden immer mehr Grad —
 bei 10.000/min lädt die Spule **gut ein Viertel der Umdrehung**.
 Genau deshalb hat die Busa vier Einzelspulen mit je einem eigenen IGBT.
 Im aktuellen Tune läuft die Zündung als **Wasted COP**: Jede
 Spule feuert zweimal pro Arbeitsspiel, also einmal pro Umdrehung — auch in
 den Ausstoßtakt hinein. Das ist phasenunabhängig und deshalb sicher für die
 Inbetriebnahme, halbiert aber die Pause zwischen zwei Ladevorgängen.
 [→ Dwell im Detail](../tune/#dwell)

**Mehr Details**

- Code: `computeDwell()` in `dwell.cpp` — beim Kurbeln gilt ein eigener (viel längerer) Dwell, im Betrieb wahlweise Festwert oder 3D-Dwell-Kennfeld.
- Dwell wird **spannungskorrigiert**: bei 6 V das 2,4-fache, bei 10 V das 1,5-fache, ab 14 V der einfache Wert (`correctionsDwell()`).
- Sicherheitsnetz im Tune: Dwell-Limit **4 ms** — eine dauerbestromte Spule brennt sonst ab. Zusätzlich sitzt in der Rev-3.0-Hardware der Watchdog-Not-Aus dahinter.
- Zündmodus heute: **Wasted COP**, phasenunabhängig. Mit bestätigtem Trigger-Winkel wird auf **sequentiell** umgestellt — dann feuert jede Spule nur noch auf ihren eigenen Verdichtungstakt und die thermische Belastung halbiert sich.
- Der Scheduler übersetzt „Zündung in 23°“ per `angleToTimeMicroSecPerDegree()` in einen Timer-Compare-Wert — der Funke kommt aus dem Hardware-Timer, nicht aus der Loop. Deshalb ±0,05°.

## Boost-Regelung — *PID*.

Fürs Turbo-Kapitel: Das Wastegate-Ventil wird per PWM angesteuert, und ein
 **PID-Regler** vergleicht den gemessenen Ladedruck (MAP) mit dem
 Ziel aus dem Boost-Kennfeld (Drosselklappe × Drehzahl). Zu wenig Druck →
 mehr Duty, Wastegate bleibt zu. Zu viel → Duty runter, Abgas am Turbo vorbei.

Spiel mit den Reglern: Viel **Kp** reagiert schnell,
 überschwingt aber — beim Ladedruck heißt Überschwingen
 **Overboost**. **Ki** bügelt den
 Restfehler weg, macht es aber träge-schwingend, wenn er zu groß wird.

**Mehr Details**

- Code: `boostControl()` in `auxiliaries.cpp` — Integer-PID (`integerPID_ideal`) direkt auf `MAP → boostDuty`.
- Die Regelung aktiviert sich erst **oberhalb des Umgebungsdrucks** — darunter regelt nur die Wastegate-Feder, der Regler sammelt keinen sinnlosen Integral-Fehler.
- **Flex-Boost:** Mit steigendem Ethanol-Anteil darf das Ziel automatisch höher (E85 kühlt und klopft später) — eigene Korrekturtabelle auf dem Boost-Ziel.
- Ehrlicher Stand: Im aktuellen Tune ist Boost Control **aus**; als letzte Sicherung liegt ein Ladedruck-Cut bei 200 kPa hinterlegt — der Motor läuft erst noch ohne Turbo ein. Die Regelstrecke hier ist eine vereinfachte Simulation.
- Übers Boost-Kennfeld ist auch **Boost by Gear** möglich — weniger Ladedruck in den unteren Gängen, damit das Hinterrad Bodenkontakt behält.

## Schutz & *Grenzen*.

Die Firmware kann all das hier. Wie viel davon im aktuellen Tune wirklich
 scharf ist, steht in jeder Karte dabei — bei einer Erstinbetriebnahme sind die
 Grenzen absichtlich eng und die Komfortfunktionen aus.
 [→ Schutz im Tune](../tune/#schutz)

### Zweistufiger Limiter

Ab **5.500/min** stellt der Soft-Limiter die Zündung auf feste
 10° und macht den Motor spürbar stumpf, bei **6.000/min**
 schneidet der Hard-Cut Sprit und Zündung. Zwei Stufen, damit der Übergang
 kein Schlag ins Getriebe ist. Bewusst weit unter dem Serien-Begrenzer von
 10.900 — er wird erst schrittweise angehoben, jede Stufe mit Datenlog.

### Engine Protection

Öldruck, Kühlmitteltemperatur, Overboost: Verlässt ein Wert seinen
 Bereich, drosselt die Firmware über einen eigenen
 Schutz-Drehzahlbegrenzer, statt einfach weiterzufeuern.

### Fail-Safe-Werte

Liefert ein Sensor Unsinn (MAP unplausibel, CLT abgesteckt), rechnet
 die Firmware mit sicheren **Ersatzwerten** weiter und meldet
 den Fehler — liegen bleiben ist keine Option.

### Dwell-Limit

Maximal **4 ms** Bestromung pro Ladevorgang — egal, was
 Kennfeld oder Spannungskorrektur sagen. Dahinter sitzt zusätzlich der
 Hardware-Watchdog der [Rev-3.0-Platine](../ecu/).

### DFCO

Schubabschaltung: Gas zu, Drehzahl hoch → Einspritzung komplett aus,
 mit sanftem Taper beim Wiedereinsetzen. Spart Sprit und hält den
 Auspuff kühl — außer man will Anti-Lag. **Im aktuellen Tune aus**,
 weil der Übergang ein sauber abgestimmtes Kennfeld voraussetzt.

### Launch & Flat-Shift

Launch Control hält die Drehzahl am Start per Zünd-Cut, der
 Quickshifter (über das [Sensor-Modul](../ecu/#sensormodul))
 schaltet unter Volllast ohne Kupplung — beides über dieselbe
 Zündwinkel-Maschinerie. **Beides im aktuellen Tune aus** —
 das kommt, wenn der Motor läuft.

## Der *Stack*.

Die Firmware ist mein Fork des Open-Source-Projekts
 **Speeduino** — angepasst auf das Dropbear-Board-Konzept mit
 Teensy 4.1 und die Busa-Hardware. Kein Grund, ein Engine-Management von
 null zu schreiben: Speeduino ist seit Jahren in tausenden Motoren
 unterwegs, und jede Zeile ist nachlesbar.

Getunt wird über **TunerStudio** — Kennfelder live am laufenden
 Motor, dazu Datenlogging auf SD-Karte für die Analyse danach. Gebaut wird
 mit **PlatformIO**, ein Befehl vom Quellcode zum Flash:
 `pio run -e teensy41 -t upload`.

**Code-Struktur**

- `decoders.cpp` — Trigger-Decoder (Interrupt-Ebene, jede Flanke zählt)
- `crankMaths.cpp` — Festkomma-Umrechnung Winkel ↔ Zeit
- `fuel_calcs.cpp` — Pulsweiten-Pipeline inkl. Staging & Duty-Limit
- `corrections.cpp` — alle Sprit- und Zündkorrekturen, Lambda-PID
- `scheduler.cpp` — Winkel → Hardware-Timer-Events
- `auxiliaries.cpp` — Boost-PID, Lüfter, Nitrous, Wasser-Methanol
- `engineProtection.cpp` — Limiter, Öldruck-/Overboost-Schutz
- `acc_mc33810.cpp` — SPI-Ansteuerung des Treiber-ICs der [Rev-3.0-Hardware](../ecu/)

## FAQ.

### Warum Speeduino und nicht selbst geschrieben?

Ein Engine-Management steckt voller Spezialfälle — Sync-Verlust, Sensor-Ausfälle, Limiter-Übergänge. Speeduino hat die alle schon durchlebt und ist offen (GPL v3): Ich kann jede Berechnung nachlesen und für die Busa anpassen, statt jahrelang eigene Bugs zu jagen. Meine Anpassungen leben im eigenen Fork auf GitHub.

### Was ist ein VE-Kennfeld?

Eine 16×16-Tabelle über Drehzahl und Last, die sagt, wie voll sich der Zylinder wirklich füllt (Volumetric Efficiency in %). Zwischen den Stützstellen wird bilinear interpoliert. Zusammen mit REQ\_FUEL ergibt das direkt die Einspritzzeit — siehe Rechnung 02.

### Woher weiß die ECU, welcher Zylinder dran ist?

Das Kurbelrad liefert den Winkel innerhalb einer Umdrehung — aber ein Viertakt-Zyklus dauert zwei. Erst das Nockenwellensignal löst die Mehrdeutigkeit auf. Mit diesem Sync *können* Einspritzung und Zündung vollsequentiell laufen: jeder Zylinder exakt auf seinen eigenen Takt. Im [Erststart-Tune](../tune/) ist das noch nicht eingeschaltet — solange der Trigger-Winkel nicht mit der Blitzpistole bestätigt ist, läuft alles bewusst phasenunabhängig.

### Läuft der Motor damit schon?

Noch nicht. Firmware und ein vollständiges [Erststart-Tune](../tune/) stehen — alle Zahlen auf dieser Seite kommen daraus —, aber der Motor ist damit noch nie gelaufen. Als Nächstes: Sensoren kalibrieren, Trigger-Winkel mit der Blitzpistole bestätigen, Saugbetrieb einfahren. Erst danach kommt der Turbo und mit ihm Boost-Regelung und Klopfschutz. Die Hardware dafür ist fertig — beide Platinen sind durchgeroutet, Details auf der [ECU-Seite](../ecu/).

## Code *lesen*?

Der komplette Fork ist offen — oder schau dir an,
 auf welcher Hardware das alles läuft.
