Programmier- & Elektronik-Gedanken

Protokollierung mit 8kHz auf einem ESP32-S3 in Arduino: Ausführung des Programms aus dem PSRAM (XiP)

NNeil Bowen10 Min. Lesezeit

Der IMU des Jupiter wechselt beim nächsten Board zu 10MHz SPI, damit er mit 8kHz protokollieren kann, gegenüber den derzeit über I2C erreichbaren 2kHz. Im Rocketry Forum hat jemand zu Recht die Frage gestellt, ob ein ESP32-S3 mit 240MHz wirklich bei 8kHz „protokollieren" kann, oder nur den Sensor so schnell auslesen kann. Es ist eine gute Frage und obwohl ich zuversichtlich war, dass wir es schaffen können, hatte ich es noch nicht überprüft, also haben wir es ordnungsgemäß gemessen, statt zu raten. Das ist das, was wir gefunden haben, und am Ende finden Sie die erneuerte Arduino-Bibliothek, falls Sie sie in Ihren eigenen Projekten ausprobieren möchten.

Es ist wirklich nicht die Taktgeschwindigkeit

Das, was die meisten ESP32-Projekte vom schnellen Protokollieren abhält, ist nicht die CPU, sondern der Flash. Bei einem normalen Build läuft das Programm direkt aus demselben Flash-Chip, auf den Sie protokollieren, durch einen kleinen Cache. Jedes Mal, wenn Sie in diesen Flash schreiben, schaltet der ESP den Cache auf beiden Kernen aus, bis der Schreibvorgang beendet ist, daher stoppt jeder Code, der nicht bereits im internen RAM sitzt, einfach. Nicht verlangsamt, stoppt. Ein Seiten-Schreibvorgang dauert etwa eine Millisekunde, und das Löschen eines 64KB-Blocks kann alles für 100 bis 200ms einfrieren. Für einen Flugcomputer, der eine feste Ratenschleife ausführt, ist das das eigentliche Problem.

Was wir protokollieren

Jede IMU-Probe ist der Gyroskop als drei 16-Bit-Ganzzahlen und die Beschleunigung als drei 24-Bit-Ganzzahlen, die Beschleunigungsmesser mit hohem und niedrigem g zu einer Messung verschmolzen, also 15 Bytes pro Probe.

Wir hatten ursprünglich geplant, die verschmolzene Beschleunigung als Floats zu speichern, aber Ganzzahlen erwiesen sich als die bessere Wahl. Die Einheit ist genau 1/2048 g, was der eigene Schritt des Beschleunigungsmessers mit niedrigem g auf seinem 16g-Bereich ist (ST's 0,488mg ist diese Zahl gerundet), daher landen seine Messwerte auf ganzen Zahlen und nichts, was der Sensor misst, geht verloren. Eine Umrechnung zurück zu g ist eine Division durch 2048, die exakt ist, und 24 Bits decken etwa ±4000g ab, weit mehr als der 256g-Bereich mit hohem g benötigt. Es sind 3 Bytes pro Achse statt 4, und im Gegensatz zu einem Float kann es später Delta-codiert werden, falls gewünscht (mehr dazu weiter unten).

Alles andere, das der Jupiter protokolliert (der 500Hz-Flugzustand, Baro, Magnetometer, GNSS, Stromversorgung usw.), addiert etwa 20kB/s unabhängig von der IMU-Rate, daher wird die schneller, desto kleiner wird sein Anteil an den Daten: 40% davon bei 2kHz, ein Viertel bei 4kHz und nur 14% bei 8kHz.

IMU-Rate IMU-Daten (kB/s) Andere Daten (kB/s) Gesamt (kB/s) Mit Delta (kB/s)
2kHz 30 20 50 42
4kHz 60 20 80 64
8kHz 120 20 140 108

Die Angabe zu den anderen Daten ist ungefähr. Die Spalte „mit Delta" ist unsere Schätzung des Gesamtflusses zum Flash im Boost, wo die Motorvibration die Delta-Codierung am wenigsten wirksam macht, wobei nur die IMU-Daten Delta-codiert sind. Auf dem Pad und im Gleitflug wäre es noch niedriger. Weiter unten finden Sie mehr Informationen zur Delta-Codierung.

Wie der Jupiter protokolliert

  • Der IMU sammelt seine Proben in seinem eigenen FIFO, daher führt die Schleife nicht 8000 Lesevorgänge pro Sekunde durch. Sie läuft bei 500Hz und zieht bei jedem Durchgang alles, was wartet, in einem Burst, was 16 Proben pro Durchgang bei 8kHz sind.
  • Jede unbearbeitete Probe geht in Ringpuffer im PSRAM. Nichts wird herumgewirbelt, die Ringe sind feste Speicherblöcke mit Lese- und Schreibpositionen, und der Logger läuft einfach an Ort und Stelle. Das bedeutet auch, dass die Daten vor dem Start bereits dort sitzen, wenn der Start erkannt wird.
  • Ein kleiner 4KB-Puffer im internen RAM sammelt die Aufzeichnungen und schreibt sie auf eine dedizierte Raw-Flash-Partition. Diese Partition wird vor dem Flug gelöscht, daher sind es während des Flugs nur Seiten-Schreibvorgänge, keine Löschvorgänge und kein Dateisystem im Weg.
  • Nach der Landung komprimiert es den gesamten Flug in eine einzelne Datei, gestreamt, damit es den gesamten Flug nie im Speicher benötigt, unabhängig von der Rate, mit der er protokolliert wurde.

Das läuft immer noch auf das oben genannte Flash-Problem an, daher kommt der nächste Teil.

Das Programm aus PSRAM ausführen

Der ESP32-S3 kann das Programm beim Start in den PSRAM kopieren und von dort aus ausführen, statt vom Flash (Espressif nennt es XIP aus PSRAM). Sobald es das tut, benötigt ein Flash-Schreibvorgang nicht mehr, dass der Cache ausgeschaltet wird, sodass die Schleife weiter läuft, während der Flash geschrieben wird. Das Problem für Arduino-Benutzer ist, dass es eine Einstellung ist, die nicht in den vorkompilierten Bibliotheken von Espressif verwendet wird, daher ist es nicht etwas, das Sie aus Ihrem Sketch aktivieren können, also haben wir die S3-Bibliotheken für den Arduino-Kern mit aktivierter Einstellung neu erstellt.

ECC, weil Raketen heiß werden

Während wir dabei waren, haben wir ECC für den PSRAM aktiviert. Espressif bewertet den oktalen PSRAM im S3R8 mit 65°C und 85°C mit ECC ein. 65°C klingt nach viel, aber eine dunkle Rakete, die bei Sonnenschein auf einem Pad sitzt, wird überraschend warm im Inneren, also für einen Flugcomputer schien es der sinnvolle Platz zum Anfangen zu sein. Es kostet ein Sechzehntel des PSRAM, das wir leicht erübrigen können.
Es ist erwähnenswert, dass dies die Umgebungstemperaturbeurteilung ist, nicht die interne Temperaturbeurteilung des Chips. Und da der Jupiter kein HF verwendet (Wifi/BT usw.) wird er wahrscheinlich ein wenig weiter gehen.

Noch ein kleiner: Wo der Schreibpuffer lebt

Wir stellten fest, dass auch der 4KB-Schreibpuffer wichtig ist. Wenn er sich im PSRAM befindet, kopiert der ESP-IDF-Flash-Treiber ihn durch einen winzigen 32-Byte-Puffer und schreibt jeweils 32 Bytes als separaten Befehl auf den Flash-Chip. Wenn es sich im internen RAM befindet, gibt es ihn direkt weiter, 64 Bytes pro Befehl, was das Maximum ist, das der S3-Flash-Controller auf einmal annimmt. Jeder Befehl kostet ungefähr gleich viel, was er trägt, daher sind halb so viele Befehle fast doppelte Geschwindigkeit: absolut gemessen wir 209kB/s aus PSRAM und 335kB/s aus internem RAM. 4KB internem RAM ist nichts, daher war das eine einfache Änderung.

Der Test

Wir haben einen Test zur Firmware hinzugefügt, der den Flugpuffer auf dieselbe Weise schreibt wie der Fluglogger, während die 500Hz-Schleife weiter läuft, und dann meldet, wie sehr die Schleife beeinträchtigt wurde. Es schreibt mit 55kB/s, dann 166kB/s, dann absolut, dann löscht alles, was es geschrieben hat. Diese beiden Raten wurden aus unserem früheren Float-Plan (18 Bytes pro Probe) für 2kHz- und 8kHz-Protokollierung gesetzt, daher liegen sie etwas über dem, was das Ganzzahl-Format benötigt, was den Test etwas schwärer macht als das echte Leben. Um etwas klarzustellen, der IMU auf diesem Board ist immer noch auf I2C bei 2kHz, also sind die schnelleren Phasen die Flash-Last schnellerer Protokollierung, nicht schnellere Abtastung. Gleiches Board, gleicher Test, nur die Arduino-Bibliotheken ausgetauscht.

Jede Zelle ist zunächst der Standard-Arduino-Kern, dann unsere erneuerten Bibliotheken mit dem Programm im PSRAM und ECC aktiviert. Die Schleife sollte mit 500Hz laufen, ein Durchgang alle 2ms, und die größte Lücke ist die längste Zeit zwischen zwei Durchgängen.

Während der Flash Schleifenrate Größte Lücke FIFO-Überläufe
Schreiben bei 55kB/s 349 → 499Hz 15,8 → 5,4ms 42 → 0
Schreiben bei 166kB/s 193 → 499Hz 15,0 → 5,2ms 77 → 0
Schreiben absolut 196 → 499Hz 12,9 → 4,6ms 24 → 0
Löschen (64KB-Blöcke) 17 → 499Hz 202 → 4,9ms 3 → 0

Mit dem Standard-Kern bricht die Schleife zusammen, sobald der Flash beschäftigt ist, sie konnte nicht einmal mit der 166kB/s-Phase Schritt halten (sie schaffte 160), und das Löschen hält sie für 200ms an. Jeder Überlauf ist das FIFO des IMU, das sich füllt, und Proben gehen verloren. Mit dem Programm im PSRAM hält es 499Hz durch alles, einschließlich des Löschens. Die größte Lücke von etwa 5ms ist nur der langsamste Durchgang der Schleife selbst (das tut es auch ohne Flash-Schreiben), das FIFO des IMU saugt es auf, und nichts geht verloren.

Delta-Codierung

Delta-Codierung bedeutet, zu speichern, wie sehr sich jede Messung seit der letzten geändert hat, anstatt die Messung selbst zu speichern. Bei diesen Raten ist eine Probe sehr nah an der nächsten, daher passen die meisten Änderungen in ein oder zwei Bytes statt zwei oder drei. Hier verdienen sich die Ganzzahlen ihren Platz: die Änderung zwischen zwei Ganzzahlen ist selbst eine kleine Ganzzahl, während die Bits eines Float nicht schrumpfen, wenn sich der Wert kaum bewegt. In 1/2048 g-Schritten passt eine Änderung von bis zu etwa 4g zwischen Proben in 2 Bytes.

Wie viel es spart, hängt davon ab, was die Rakete tut. Auf dem Pad oder im Gleitflug würden die meisten Proben auf 6 bis 8 Bytes fallen, aber im Boost mit dem Motor, der alles schüttelt, sind es eher 11, und der Boost ist das, worauf es ankommt, da dort die Protokollierrate am höchsten ist. Das ist unsere grobe Schätzung statt einer Messung im Moment. Der Jupiter Delta-codiert den Flugpuffer derzeit nicht, da er dafür nicht schnell genug ist. Wo es helfen würde, ist die Aufzeichnungszeit: Der 7,8MB-Flugpuffer hält etwa 58 Sekunden bei vollständiger 8kHz-Rohdaten oder etwa 75 Sekunden Delta-codiert, bevor die Hybrid-Protokollierung die Rate senkt.

Also, wie schnell könnte es protokollieren?

Die gemessenen Flash-Geschwindigkeiten mit den Datengrößen oben kombiniert ergibt das Maximum, das jede Einrichtung halten könnte:

Einrichtung Flash, absolut (kB/s) Max-Rate, Roh / Delta (kHz) 8kHz-Daten, Roh / Delta (kB/s und Anteil des Flash)
Standard-Kern, Puffer im PSRAM* 160 9 / 12 140 / 108
88% / 68%
Standard-Kern, Puffer im internen RAM* 306 19 / 25 140 / 108
46% / 35%
Erneuerte Bibliotheken, Puffer im PSRAM 209 12 / 17 140 / 108
67% / 52%
Erneuerte Bibliotheken, Puffer im internen RAM (der Jupiter) 335 21 / 28 140 / 108
42% / 32%

* Der Flash hält auf dem Standard-Kern Schritt, aber die Schleife nicht. Sie fiel unter 200Hz und verlor IMU-Proben bei jeder Rate, die wir versuchten, daher kann sie in der Praxis bei keiner davon sauber protokollieren.

Roh sind 15 Bytes pro Probe, Delta-codiert ist unsere 11-Byte-Boost-Schätzung, beide enthalten die 20kB/s von allem anderen, und das sind die absolut gemessenen Geschwindigkeiten mit der laufenden Schleife, abgerundet ohne etwas Reserviertes. Der 10MHz-SPI-Bus ist bei keinem davon das Limit, er könnte etwa 59.000 Proben pro Sekunde tragen.

Also benötigt 8kHz etwa 140kB/s gegen die 335kB/s, die der Flash absolut nimmt, etwa 42% davon ohne Delta-Codierung überhaupt, oder etwa 108kB/s (32%) damit. Die CPU arbeitet auch nicht so hart: bei 2kHz wird der größte Teil jedes Schleifendurchgangs damit verbracht, auf den I2C-Bus zu warten, und auf 10MHz SPI sollte derselbe Lesevorgang bei 8kHz etwa 0,3ms dauern. Der IMU selbst hat eine Obergrenze von 8kHz, der Rest ist Spielraum. Es ist immer noch Banktest, bis das SPI-Board eintrifft, und die Standardeinstellungen werden sowieso niedriger sein, wobei die Protokollierung nach dem Boost ihre Rate senkt, aber der S3 sieht sich dem gut gewachsen.

Versuchen Sie es selbst

Da wir die harte Arbeit geleistet haben, befinden sich die erneuerten ESP32-S3-Bibliotheken in den Downloads am Ende dieses Beitrags für alle anderen, die Arduino verwenden. Sie sind nur für den esp32-Kern 3.3.12 vom Board Manager und ersetzen diesen Ordner:

Windows  %LOCALAPPDATA%\Arduino15\packages\esp32\tools\esp32s3-libs\3.3.12
Mac      ~/Library/Arduino15/packages/esp32/tools/esp32s3-libs/3.3.12
Linux    ~/.arduino15/packages/esp32/tools/esp32s3-libs/3.3.12

Schließen Sie unter Windows die Arduino IDE und öffnen Sie dann eine Eingabeaufforderung (geben Sie cmd in das Startmenü ein, nicht PowerShell) und fügen Sie dies ein. Es findet die zip in Ihrem Downloads-Ordner, was immer der Download zu seinem Namen hinzugefügt hat, und es behält Ihren ursprünglichen Ordner als 3.3.12.stock:

cd /d "%LOCALAPPDATA%\Arduino15\packages\esp32\tools\esp32s3-libs"
ren 3.3.12 3.3.12.stock && for %f in ("%USERPROFILE%\Downloads\*esp32s3-libs-3.3.12-xip-ecc*.zip") do tar -xf "%f"
rmdir /s /q "%LOCALAPPDATA%\arduino\cores"

Die letzte Zeile löscht die zwischengespeicherte Kopie des kompilierten Kerns der IDE, damit er gegen die neuen Bibliotheken neu erstellt wird. Um zurück zu den Standard-Einstellungen zu gehen:

cd /d "%LOCALAPPDATA%\Arduino15\packages\esp32\tools\esp32s3-libs"
ren 3.3.12 3.3.12.xip-ecc && ren 3.3.12.stock 3.3.12
rmdir /s /q "%LOCALAPPDATA%\arduino\cores"

Verwenden Sie nicht die Funktion „Alle extrahieren" des Explorers auf der zip, einige der Pfade darin sind zu lang dafür und es werden stillschweigend Dateien übersprungen. Der obige Befehl oder 7-Zip sind in Ordnung.

Ein paar Dinge zum Wissen:

  • Wir haben es auf einem S3 mit 8MB oktalem PSRAM (dem S3R8) getestet, wobei PSRAM auf OPI PSRAM im Menü „Werkzeuge" gesetzt ist. Wir haben es nicht auf Quad-PSRAM versucht, und da das Programm aus PSRAM läuft, eignet es sich nicht für Boards ohne welche.
  • Das Programm wird beim Start in den PSRAM kopiert, daher verlieren Sie so viel PSRAM, plus ein Sechzehntel davon für ECC.
  • Es wird mit Espressifs esp32-arduino-lib-builder auf ESP-IDF 5.5.5 erstellt, gleich wie die offiziellen 3.3.12-Bibliotheken, mit nur zwei hinzugefügten Einstellungen. Alle Verdienste gehen an Espressif. Es ist inoffiziell und wie besehen, also behalten Sie Ihren Standard-Ordner.

Wenn Sie es lieber selbst erstellen möchten, ist es Espressifs esp32-arduino-lib-builder (Linux oder WSL), ausgecheckt bei den in der Datei versions.txt in den offiziellen 3.3.12-Bibliotheken aufgeführten Versionen, mit diesen zwei Zeilen, die configs/defconfig.esp32s3 vor dem Ausführen des Builds hinzugefügt werden:

CONFIG_SPIRAM_XIP_FROM_PSRAM=y
CONFIG_SPIRAM_ECC_ENABLE=y