Skip to content

Link de

github-actions[bot] edited this page Sep 10, 2026 · 16 revisions

Der Link

English · Deutsch

Die beiden Platinen kommunizieren über CAN (Controller Area Network) mit 1 Mbit/s. Zuerst die Verkabelung, dann die Protokollreferenz.

Verkabelung

Bus Classic CAN (kein CAN FD (Flexible Data Rate)), 1 Mbit/s, nur 29-Bit-Identifier
Panel TWAI-Controller (Two-Wire Automotive Interface, der CAN-Controller des ESP32-S3) auf GPIO19 (RX) und GPIO20 (TX; GPIO: General-Purpose Input/Output), über den USB/CAN-Multiplexer der Platine (USB: Universal Serial Bus)
Koprozessor XL2515 (MCP2515-kompatibel) an spi1: SCK GP10, MOSI GP11, MISO GP12, CS GP9, INT GP8; SPI-Takt 10 MHz (SPI: Serial Peripheral Interface)
Transceiver SIT65HVD230 (3,3 V) auf dem Koprozessormodul
Abschluss 120 Ω an beiden Enden
Quarz der XL2515 braucht einen 16-MHz-Quarz; 8 MHz begrenzen den Controller auf 500 kbit/s

Wer CAN wählt, verliert das native USB des Panels. GPIO19 und GPIO20 führen beides, und der Multiplexer FSUSB42UMX (CH422G EXIO5: 0 = USB, 1 = CAN) wählt eines aus. Die Konsole liegt deshalb auf UART0 (Universal Asynchronous Receiver-Transmitter), der zweiten USB-C-Buchse der Platine hinter der USB-UART-Bridge, mit USB-Serial-JTAG (der eingebauten USB-Seriell- und Debug-Bridge des ESP32-S3) als Zweitkonsole.

Kommen keine Frames durch: Den Link in Betrieb nehmen.

Verhalten bei Linkausfall

Der Koprozessor setzt nach 200 ms ohne Anfrage Failsafe-Werte; das Panel eskaliert nach 1 s ohne Antwort. Das Failsafe rastet ein. Zurückkehrender Verkehr stoppt den Stillezähler, hebt das Failsafe aber nicht auf; verlassen wird es durch das Schreiben von 0x5AFE in das Register CLEAR der Control-Page.

Bus off

Ein CAN-Transmitter, der kein Acknowledge bekommt, wiederholt von sich aus und addiert je Versuch 8 auf seinen Transmit Error Counter. Bei 1 Mbit/s erreicht ein Frame, den niemand beantwortet, die Bus-off-Schwelle von 256 in etwa 4 ms — jeder Moment ohne zweiten Knoten am Bus nimmt den Controller des Panels also vom Bus: Koprozessor ohne Strom, nach einem Reset, oder lange genug mit abgeschalteten Interrupts.

Der TWAI-Controller (Two-Wire Automotive Interface) des ESP32-S3 kommt nicht von selbst zurück. can_twai_recover() läuft am Identity-Poll, solange der Link unten ist, einmal pro Sekunde: aus dem Zustand Bus off ruft es twai_initiate_recovery(), wartet die 128 Bus-frei-Signale ab, die der Controller im Zustand Recovering zählt, und startet ihn aus dem Zustand Stopped wieder. Die Erholung dauert damit bis zu drei Polls, also rund 3 s. Ohne sie ist der erste stille Moment am Bus endgültig und nur ein Aus-und-Einschalten hilft.

Der Report, der bei liegendem Link alle 5 s ausgegeben wird, trägt die Zähler:

LINK ...
  bus    tx errors 0 rx errors 0 bus errors 0

Ein Transmit Error Counter, der auf 256 zuläuft, heißt, dass niemand die Frames mit einem ACK (Acknowledge) bestätigt. Die Zeile endet auf -- BUS OFF, wenn das Panel bereits aufgehört hat zu senden. Steht dort statt der Zähler the controller is not running, ist der CAN-Controller gar nicht erst gestartet — das ist eine andere Diagnose als ein Bus ohne Fehler.

Protokoll

Pages mit bis zu 32 Sechzehn-Bit-Registern, gelesen und geschrieben in Fenstern. Der Koprozessor sendet nur als Antwort auf eine Anfrage. Protokollversion 4.0. Die Major-Version ist Register 0 der Page 0. Die Major ändert sich, wenn ein Register seine Bedeutung wechselt oder eine Page umnummeriert wird; die Minor, wenn eine Page oder ein Register am Ende hinzukommt, was ein älteres Panel ignorieren kann.

Ein Koprozessor, der eine andere Protokoll-Major meldet als das Panel, gilt als abwesend: der Link bleibt unten, der Splash-Screen markiert den Koprozessor-Schritt als fehlgeschlagen neben der gemeldeten Version, das Panel protokolliert beide Majors und meldet protocol mismatch -- will not arm, und kein Write erreicht den Koprozessor. Seine Ausgänge bleiben aus, und das Panel läuft wie ohne angeschlossenen Koprozessor. Panel und Koprozessor auf verschiedenen Seiten eines Major-Sprungs werden deshalb zusammen geflasht.

Identifier

Ein 29-Bit-Extended-Identifier trägt die ganze Adresse; ein Read ist deshalb ein Frame ohne Nutzdaten.

Bits Feld Breite Werte
28..26 Priorität 3 0 control, 1 normal, 2 bulk (reserviert); niedriger gewinnt die Arbitrierung
25..22 op 4 1 READ, 2 WRITE, 3 DATA, 4 ACK (Acknowledge), 5 NACK (Negative Acknowledge)
21..14 page 8 Page Map unten
13..6 offset 8 erstes Register in diesem Frame
5..0 count 6 Register in diesem Frame, 0..32

Die Priorität folgt aus der Page: die Pages CONTROL, LIMITS und FAILSAFE und ihre Quittungen sind Klasse 0; alles andere ist Klasse 1.

Frames

Ein Frame trägt bis zu vier Register (8 Byte, Little-Endian). Jeder Frame trägt seinen eigenen Offset und Count; eine Antwort über mehr als vier Register besteht also aus mehreren unabhängigen Frames in beliebiger Reihenfolge, und ein verlorener Frame kostet einen Registerbereich. Der Host-Poller kennt das angefragte Fenster und ist fertig, sobald jedes Register eingetroffen ist; der Transport setzt nichts zusammen. In den Nutzdaten steckt keine CRC (Cyclic Redundancy Check, Prüfsumme); es gelten die 15-Bit-CRC, der Acknowledge-Slot und die Wiederholung von CAN.

Ein NACK trägt seinen Grund in Register 0:

Wert Grund
1 BAD_PAGE
2 BAD_RANGE: Offset + Count über das Ende der Page hinaus
3 READ_ONLY
4 BAD_VALUE
5 NOT_ARMED

Ein Write gilt ganz oder gar nicht. Jedes Register eines Frames wird geprüft, bevor eines davon gespeichert wird; ein NACK lässt die Page also genau so, wie sie war: keines der bis zu vier Register eines Frames wird übernommen, und keine Nebenwirkung eines Registers läuft. Das Aufheben eines eingerasteten Failsafe ist eine solche Nebenwirkung.

Page Map

Page Name Zugriff Register
0x00 IDENTITY lesen Protokoll major, Protokoll minor, Firmware major, minor, patch, Hardware-Revision, Capabilities-Bitmap
0x01 STATUS lesen Zustand (0 idle, 1 armed, 2 failsafe), Fault-Bitmap, Uptime in ms (zwei Register), angenommene Anfragen (zwei Register), Empfangsfehlerzähler des XL2515, Sendefehlerzähler des XL2515
0x10 CONTROL lesen, schreiben ARM (ungleich null schärft), THROTTLE (0..10000, Hundertstel Prozent, und kommandiert jeden Kanal, den CHAN_CFG als Throttle führt), MOTOR_POLES, CLEAR (0x5AFE schreiben, um das Failsafe zu verlassen). Register 0 bis 2 sind der Frame, der scharfschaltet
0x11 LIMITS deklariert, nicht bedient
0x12 FAILSAFE deklariert, nicht bedient
0x13 CHANNELS lesen, schreiben ein Kommando je Ausgangskanal, 0..1000 des Kanalwegs; acht Kanäle
0x20 BENCH lesen Spannung (10 mV), Strom (10 mA), Leistung (W), Drehzahl (min⁻¹), Temperatur des ESC (Electronic Speed Controller, Motorregler) und des Motors (0,1 °C, vorzeichenbehaftet), Ladung (mAh), Energie (0,1 Wh), Minimalspannung, Maximalstrom, Maximalleistung, Maximaldrehzahl, Flags
0x21 reserviert nicht vergeben; nicht wiederzuverwenden
0x22 OUTPUTS lesen, schreiben je Slot: Treiber (0 keiner, 1 PWM (Pulsweitenmodulation), 2 PPM (Pulspositionsmodulation), 3 DShot, 4 bidirektionales DShot), Pin, erster Kanal und Kanalzahl in einem Register, Rate in Hz (kbit/s für beide DShot-Treiber); acht Slots zu vier Registern
0x23 CHAN_CFG lesen, schreiben je Kanal: Rolle (0 throttle, 1 surface), Slew (Spanne je Sekunde, 0 = sofort), minimaler und maximaler Puls in µs; acht Kanäle zu vier Registern
0x24 CATALOGUE lesen die eigenen Pins der Platine, je ein Register: GPIO-Nummer (General-Purpose Input/Output) in 6 Bit, die daneben aufgedruckte Pad-Nummer in 6, was ihn hält in 4 (0 frei, 1 Heartbeat, 2 CAN, 3 Flash, 4 Debug, 5 Sensor, 15 sonstiges); 32 Slots, und eine Pad-Nummer von 0 heißt, in diesem Slot ist kein Pin
0x25 SHAPE lesen wo diese Pads liegen: Umriss-Breite und -Höhe in 0,01 mm, die Ecke, an der Pad 1 sitzt, und die Pads in einer Reihe gepackt als (Ecke << 8) | je Reihe, das Raster in 0,01 mm und der Abstand von der Kante zur Mitte einer Pad-Reihe. Zwei Reihen auf einem Raster, nummeriert von Pad 1 weg entlang seiner Kante und zurück entlang der gegenüberliegenden. Alles null, wenn der Koprozessor keine Form für seine Platine hat — dann wird sie gelistet und nicht gezeichnet
0x26 ARTWORK lesen was ein Bild der Platine ist: Blöcke Nutzdaten (0, wenn der Koprozessor keines trägt), Breite und Höhe in Pixeln, Format (0 keines, 1 RGB565 mit dem niederwertigen Byte zuerst), Nutzdatenlänge in zwei Registern und eine CRC (zyklische Redundanzprüfung) über die gesamten Nutzdaten mit Startwert null
0x27 ART_DATA lesen, schreiben das Bild selbst: Register 0 schreiben, um den Block zu nennen, dann die Page lesen. Register 0 liest den gerade bedienten Block zurück, Register 1 bis 31 tragen 62 Bytes davon. Ein Block rückt beim Lesen nicht vor, eine verlorene Antwort wird also erneut angefordert statt übersprungen
0x28 PADS lesen die Pads, die keine Pins sind, je ein Register: die Pad-Nummer in 6 Bit, was es ist in 2 (0 kein Pad und die Liste endet, 1 Masse, 2 eine Versorgung, 3 keines von beiden) und die Spannung in 8 Bit zu Zehntelvolt. Null Volt bei einer Versorgung heißt, sie ist keine feste Spannung — das ist nicht dasselbe wie die 0 V einer Masse. 32 Slots, in Pad-Reihenfolge

Fault-Bitmap: Bit 0 Link still, Bit 1 Überstrom, Bit 2 Übertemperatur, Bit 3 Stall, Bit 4 Heartbeat ausgeblieben, Bit 5 Protokollversion abweichend. Faults bleiben gesetzt, bis sie gelesen und gelöscht werden. Bit 0 setzt voraus, dass über den Link mindestens eine Anfrage gelaufen ist: der Koprozessor ist wach, bevor das Panel pollt, und das Warten auf die erste Anfrage ist keine Stille.

Capabilities-Bitmap: Bit 0 ESC-Ansteuerung, Bit 1 ESC-Telemetrie, Bit 2 Servo-PWM, Bit 3 Servo-Strommessung, Bit 4 Akkumessung, Bit 5 Empfängerbus, Bit 6 Vibrationssensor und Indeximpuls, Bit 7 Zellenmonitor, Bit 8 Programmierung. Das Panel leitet daraus die Marken im Menü ab.

BENCH-Flags: Bit 0 Spannung gültig, Bit 1 Strom gültig, Bit 2 Drehzahl gültig, Bit 3 Temperatur des ESC gültig, Bit 4 Temperatur des Motors gültig, Bit 7 simuliert. Die beiden Temperaturen haben getrennte Bits, weil sie aus verschiedenen Quellen kommen und eine davon meist gar nicht kommt: ein ESC meldet seine eigene Temperatur über die erweiterte DShot-Telemetrie und weiß nichts über den Motor, den er treibt. Bis Protokoll 3.0 galt Bit 3 für beide — deshalb ist diese Änderung ein Major. Ein Koprozessor ohne Mess-Frontend setzt Bit 7, und das Panel zeichnet SIMULATION über den Bildschirm.

MOTOR_POLES ist die Magnetzahl des geprüften Motors, gerade und zwischen 2 und 42, oder null: dann hat es niemand gesagt. Ein bidirektionaler DShot-ESC meldet elektrische Perioden und weiß nicht, woran er angeschraubt ist; das ist also die eine Zahl, die die Leitung tragen muss, damit der Coprozessor eine mechanische Drehzahl melden kann. Bei null meldet er keine Drehzahl statt einer aus einer Schätzung abgeleiteten. Das Panel sendet sie aus der Einstellung Motor poles, sobald ein Coprozessor zu antworten beginnt, erneut bei jeder Änderung der Einstellung und im Frame, der scharfschaltet, ob eine Änderung offen ist oder nicht. Ein Schreibvorgang, den niemand beantwortet, bleibt offen und geht beim nächsten 50-ms-Poll erneut hinaus; einer, den der Coprozessor ablehnt, wird nicht wiederholt, denn dieselbe einmal abgelehnte Anfrage wird jedes Mal abgelehnt, und er wartet stattdessen auf die nächste Änderung oder die nächste Link-up-Flanke. Der Schutz bei null greift für eine nie gesendete Zahl, nicht für eine veraltete: jede Zahl, die die Einstellung zulässt, liegt im Bereich, den die Page annimmt.

Der Coprozessor verweigert einen Pin, den er nicht treiben darf — die Safety-Leitung, die Pins des CAN-Controllers und jede Nummer über dem letzten GPIO des Bauteils. Ein verweigerter Slot bleibt ungebunden, während die Page weiterhin zurückliest, was gefordert wurde. DShot und die Output-Treiber hat den Rest.

Einträge in CHAN_CFG und OUTPUTS werden ganz geschrieben, vier Register auf einmal. Der Pulsbereich eines Kanals ist standardmäßig 1000..2000 µs; Endpunkte außerhalb von 500..2500 µs werden mit BAD_VALUE abgewiesen. Ein Kommando außerhalb seines Bereichs wird begrenzt. Zwei Slots auf einem Pin oder zwei Slots, die denselben Kanal ausgeben, werden abgewiesen. Über das Schärfen entscheidet der Koprozessor: ein Schreiben von ARM wird mit NOT_ARMED abgewiesen, solange der Link im Failsafe ist oder dem Heartbeat nicht vertraut wird.

Ein Schärfen vom Panel sind zwei Transaktionen. CLEAR geht zuerst und allein: der Koprozessor prüft ARM gegen sein Failsafe, bevor er ein CLEAR aus demselben Frame anwendet, also wird ein Frame mit beidem genau dann mit NOT_ARMED abgewiesen, wenn das Clear nötig war. Danach gehen ARM, THROTTLE und MOTOR_POLES als ein Frame mit drei Registern ab Offset 0, sodass der Koprozessor den Lauf mit der Polzahl beginnt, die das Panel gesendet hat, oder ihn nicht beginnt. Jeder Poll alle 50 ms danach schreibt ARM und THROTTLE; eine während eines Laufs geänderte Polzahl geht beim nächsten Poll in einem eigenen Write. Die Regeln der Page -- der Throttle-Bereich, die Polzahl, die CLEAR-Magic, ARM im Failsafe abgewiesen, und dass eine Ablehnung nichts speichert -- stehen in shared/link/link_control.c, unter test_link_pages.

Bit Timing

shared/can/can_timing.c berechnet die Segmentierung für den Takt jedes Controllers und verlangt, dass die Bitrate exakt aufgeht. Beide Enden tasten bei 75 % des Bits ab:

Controller Takt Prescaler Quanta je Bit tseg1 tseg2 sjw
TWAI (ESP32-S3) 80 MHz APB (Advanced Peripheral Bus) 4 20 14 5 4
XL2515 16-MHz-Quarz, intern halbiert 1 8 5 2 2

Acht Quanta sind das Minimum für ein Bit. Deshalb braucht der XL2515 für 1 Mbit/s einen 16-MHz-Quarz, und deshalb liegt sein Sample Point fest bei 75 %; das TWAI-Timing ist passend dazu gewählt. test_can_timing hält beides fest.

Budget

Ein Poll der Bench-Page ist ein Anfrage-Frame und vier Daten-Frames (13 Register). Bei 20 Hz sind das 1,55 % des Busses. Die Nutzlast von Classic CAN bei 1 Mbit/s mit 29-Bit-Identifiern und vollem Bit Stuffing liegt im ungünstigsten Fall bei etwa 52 kB/s; der erwartete Verkehr liegt bei 12 bis 30 kB/s.

Tests

Das Identifier-Layout wird Bit für Bit über den gesamten 29-Bit-Raum geprüft; der Timing-Rechner ist an von Hand nachgerechnete Beispiele gebunden; test_link_loopback lässt den Host-Poller gegen den Device-Dispatcher laufen, über einen Bus, der Frames verwirft, verzögert und umsortiert: geteilte Antworten in umgekehrter Reihenfolge, abgewiesene Schreibzugriffe, verlorene Teilstücke, die eine Anfrage unbeantwortet lassen statt halb beantwortet, und der Watchdog des Geräts, der auf einem stillen Bus feuert.

Clone this wiki locally