Rejestrowanie z częstotliwością 8 kHz na ESP32-S3 w Arduino: uruchamianie programu z PSRAM (XiP)
IMU Jupitera przenosi się na 10MHz SPI na następnej płytce, aby mógł rejestrować przy 8kHz, w stosunku do 2kHz, które osiąga teraz na I2C. Na forum Rocketry Forum ktoś słusznie postawił pytanie, czy ESP32-S3 przy 240MHz może naprawdę „rejestrować" przy 8kHz, w przeciwieństwie do Just czytania czujnika tak szybko. To dobre pytanie i chociaż byłem pewny, że możemy to zrobić, nie weryfikowałem tego jeszcze, więc zamiast zgadywać, poszliśmy i zmierzyliśmy to prawidłowo. To jest to, co znaleźliśmy, a na dole znajduje się przebudowana biblioteka Arduino, którą ostatecznie uzyskaliśmy, jeśli chcesz spróbować w swoich własnych projektach.
To nie jest naprawdę szybkość zegara
Rzecz, która zatrzymuje większość projektów ESP32 z szybkim rejestrowaniem, to nie procesor, ale flash. W normalnej kompilacji program działa bezpośrednio z tego samego chipa flash, na którym rejestrujesz, przez małą pamięć podręczną. Za każdym razem, gdy piszesz do tego flash, ESP wyłącza pamięć podręczną na obu rdzeniach do czasu zakończenia zapisu, więc każdy kod, który nie siedzi już w wewnętrznej RAM, po prostu się zatrzymuje. Nie zwalnia, tylko się zatrzymuje. Zapis strony zajmuje około milisekundy, a wymazanie bloku 64KB może zamarznąć wszystko na 100 do 200ms. W przypadku komputera pokładowego działającego w pętli o stałej szybkości, to jest prawdziwym problemem.
Co rejestrujemy
Każda próbka IMU to żyro jako trzy 16-bitowe liczby całkowite i przyspieszenie jako trzy 24-bitowe liczby całkowite, akcelerometry o wysokim i niskim g połączone w jeden odczyt, czyli 15 bajtów na próbkę.
Początkowo planowaliśmy przechowywać połączone przyspieszenie jako liczby zmiennoprzecinkowe, ale liczby całkowite okazały się lepszym wyborem. Jednostką jest dokładnie 1/2048 g, co jest własnym krokiem akcelerometru o niskim g na jego zakresie 16g (0,488mg firmy ST to ta liczba zaokrąglona), więc jego odczyty lądują na liczbach całkowitych i nic, co czujnik mierzy, nie jest tracone. Zamienianie go z powrotem na g to dzielenie przez 2048, które jest dokładne, a 24 bity obejmują około ±4000g, znacznie więcej niż 256g, które potrzebuje zakres wysokiego g. To 3 bajty na oś zamiast 4, a w przeciwieństwie do liczby zmiennoprzecinkowej można ją później kodować deltą, jeśli będziemy chcieć (więcej na ten temat poniżej).
Wszystko inne, co rejestruje Jupiter (stan lotu 500Hz, barometr, magnetometr, GNSS, zasilanie itp.) dodaje około 20kB/s niezależnie od szybkości IMU, więc im szybciej idziemy, tym mniejszy udział w danych: 40% przy 2kHz, ćwierć przy 4kHz i tylko 14% przy 8kHz.
| Szybkość IMU | Dane IMU (kB/s) | Inne dane (kB/s) | Razem (kB/s) | Z deltą (kB/s) |
|---|---|---|---|---|
| 2kHz | 30 | 20 | 50 | 42 |
| 4kHz | 60 | 20 | 80 | 64 |
| 8kHz | 120 | 20 | 140 | 108 |
Liczba pozostałych danych jest przybliżona. Kolumna z deltą to nasze oszacowanie całkowitej wartości flash w fazie napędu, gdzie wibracja silnika czyni kodowanie deltą najmniej efektywnym, z kodowaniem deltą tylko danych IMU. Na platformie i szybowaniu byłoby to jeszcze niższe. Więcej informacji o kodowaniu deltą znajduje się poniżej.
Jak Jupiter rejestruje
- IMU paczkuje swoje próbki we własnym FIFO, więc pętla nie wykonuje 8000 odczytów na sekundę. Działa przy 500Hz i wyciąga to, co czeka w jednym pęku za przejście, co wynosi 16 próbek na przejście przy 8kHz.
- Każda surowa próbka trafia do pierścieniowych buforów w PSRAM. Nic nie jest tasowane, pierścienie to stałe bloki pamięci z pozycjami odczytu i zapisu, a rejestrator po prostu idzie wzdłuż nich na miejscu. Oznacza to również, że dane sprzed startu już tam siedzą, gdy zostanie wykryty start.
- Mały bufor 4KB w wewnętrznej RAM zbiera rekordy i zapisuje je do dedykowanej surowej partycji flash. Ta partycja jest wymazywana przed lotem, więc w locie to tylko zapisy stron, bez wymazywania i bez systemu plików na drodze.
- Po wylądowaniu kompresuje cały lot do pojedynczego pliku, przesyłanego strumieniowo, więc nigdy nie potrzebuje całego lotu w pamięci, niezależnie od szybkości, z jaką był rejestrowany.
Wszystko to jednak wpada w problem flash wyżej, gdzie pojawia się następna część.
Uruchamianie programu z PSRAM
ESP32-S3 może skopiować program do PSRAM przy starcie i uruchamiać go stamtąd zamiast z flash (Espressif to XIP z PSRAM). Po wykonaniu tej czynności zapis na flash nie wymaga już wyłączania pamięci podręcznej, więc pętla trwa podczas zapisu na flash. Problem dla użytkowników Arduino polega na tym, że jest to ustawienie nieużywane w prekompilowanych bibliotekach Espressif, więc nie możesz tego włączyć ze swojego szkicu, dlatego przebudowaliśmy biblioteki S3 dla rdzenia Arduino z włączonym.
ECC, ponieważ rakiety się nagrzewają
Zamiast tego włączyliśmy ECC dla PSRAM. Espressif ocenia osemkrotny PSRAM w S3R8 na 65C, a 85C z włączonym ECC. 65C brzmi dużo, ale ciemna rakieta siedząca na platformie startowej w słońcu zaskakująco się nagrzewa wewnątrz, więc do komputera pokładowego wydawało się to rozsądnym miejscem do rozpoczęcia. Kosztuje to jedną szesnastą PSRAM, którą łatwo możemy zaoszczędzić.
Warto zauważyć, że jest to ocena temperatury otoczenia, a nie ocena temperatury wewnętrznej chipa. A ponieważ Jupiter nie używa RF (Wifi/BT itp.), prawdopodobnie będzie się nieco bardziej rozciągać.
Jeszcze jeden mały: gdzie się znajduje bufor zapisu
Stwierdziliśmy, że bufor zapisu 4KB również ma znaczenie. Jeśli znajduje się w PSRAM, sterownik flash ESP-IDF kopiuje go przez mały bufor 32 bajtów i zapisuje każde 32 bajty jako oddzielne polecenie do chipa flash. Jeśli znajduje się w wewnętrznej RAM, przekazuje go bezpośrednio, 64 bajty na polecenie, co jest maksimum, które kontroler flash S3 zaakceptuje za jednym razem. Każde polecenie kosztuje mniej więcej to samo, niezależnie od tego, co nosi, więc połowa poleceń to prawie dwa razy szybciej: na pełnej mocy zmierzyliśmy 209kB/s z PSRAM i 335kB/s z wewnętrznej RAM. 4KB wewnętrznej RAM to nic, więc to była łatwa zmiana.
Test
Dodaliśmy test do oprogramowania, które zapisuje bufor lotu w taki sam sposób, w jaki rejestrator lotu to robi, podczas gdy pętla 500Hz ciągle działa, a następnie raportuje, jak bardzo pętla została wstrzymana. Zapisuje przy 55kB/s, potem 166kB/s, potem na pełnych obrotach, potem wymazuje wszystko, co napisało. Te dwie szybkości zostały ustawione na podstawie naszego wcześniejszego planu zmiennoprzecinkowego (18 bajtów na próbkę) dla rejestrowania 2kHz i 8kHz, więc są nieco wyższe niż to, czego wymaga format całkowity, co sprawia, że test jest nieco trudniejszy niż w rzeczywistości. Aby było jasne w jednej kwestii, IMU na tej płytce jest nadal na I2C przy 2kHz, więc szybsze fazy to obciążenie flash szybszego rejestrowania, a nie szybszego próbkowania. Ta sama płytka, ten sam test, tylko wymienione biblioteki Arduino.
Każda komórka to najpierw standardowy rdzeń Arduino, potem nasze przebudowane biblioteki z programem w PSRAM i włączonym ECC. Pętla powinna działać przy 500Hz, przejście co 2ms, a najgorsza luka to najdłuższy czas między dwoma przejściami.
| Podczas gdy flash jest | Szybkość pętli | Najgorsza luka | Przepełnienia FIFO |
|---|---|---|---|
| Zapis przy 55kB/s | 349 → 499Hz | 15,8 → 5,4ms | 42 → 0 |
| Zapis przy 166kB/s | 193 → 499Hz | 15,0 → 5,2ms | 77 → 0 |
| Zapis na pełnych obrotach | 196 → 499Hz | 12,9 → 4,6ms | 24 → 0 |
| Wymazywanie (bloki 64KB) | 17 → 499Hz | 202 → 4,9ms | 3 → 0 |
Na standardowym rdzeniu pętla się rozpada, gdy tylko flash jest zajęty, nie mogła nawet nadążyć z fazą 166kB/s (osiągnęła 160), a wymazanie zatrzymuje ją na 200ms. Każde przepełnienie to bufor IMU FIFO się zapełnia i próbki się tracą. Z programem w PSRAM utrzymuje 499Hz przez wszystkie to, w tym wymazanie. Najgorsza luka około 5ms to tylko najwolniejsze przejście pętli (to robi to bez żadnego zapisu na flash w ogóle), FIFO IMU to wchłania, i nic się nie traci.
Kodowanie deltą
Kodowanie deltą oznacza przechowywanie tego, jak bardzo zmienił się każdy odczyt od ostatniego, zamiast samego odczytu. W tych szybkościach jedna próbka jest bardzo zbliżona do następnej, więc większość zmian mieści się w jednym lub dwóch bajtach zamiast dwóch lub trzech. To miejsce, gdzie liczby całkowite się rozliczają: zmiana między dwiema liczbami całkowitymi sama w sobie jest małą liczbą całkowitą, podczas gdy bity liczby zmiennoprzecinkowej się nie zmniejszają, gdy wartość ledwo się porusza. W krokach 1/2048 g zmiana do około 4g między próbkami mieści się w 2 bajtach.
Ile to oszczędza zależy od tego, co robi rakieta. Siedząc na platformie lub szybując, większość próbek spadłaby do 6 do 8 bajtów, ale w fazie napędu z silnikiem trzęsącym wszystko to więcej jak 11, a faza napędu jest ważna, ponieważ to tam szybkość rejestrowania jest najwyższa. To nasze przybliżone oszacowanie, a nie pomiar na razie. Jupiter nie koduje deltą bufora lotu w tej chwili, ponieważ nie potrzebuje tego do szybkości. Gdzie by to pomogło, to czas rejestrowania: bufor lotu 7,8MB mieści około 58 sekund przy pełnym 8kHz surowym lub około 75 sekund z deltą zakodowaną, zanim hybrydowe rejestrowanie zmniejszy szybkość.
Więc jak szybko może rejestrować?
Łącząc zmierzone szybkości flash z wielkościami danych powyżej, podajemy maksimum, które każda konfiguracja mogłaby utrzymać:
| Konfiguracja | Flash, na pełnych obrotach (kB/s) | Maksymalna szybkość, surowe / delta (kHz) | Dane 8kHz, surowe / delta (kB/s i udział flash) |
|---|---|---|---|
| Standardowy rdzeń, bufor w PSRAM* | 160 | 9 / 12 | 140 / 108 88% / 68% |
| Standardowy rdzeń, bufor w wewnętrznej RAM* | 306 | 19 / 25 | 140 / 108 46% / 35% |
| Przebudowane biblioteki, bufor w PSRAM | 209 | 12 / 17 | 140 / 108 67% / 52% |
| Przebudowane biblioteki, bufor w wewnętrznej RAM (Jupiter) | 335 | 21 / 28 | 140 / 108 42% / 32% |
* Flash nadąża na standardowym rdzeniu, ale pętla nie. Spadł poniżej 200Hz i stracił próbki IMU w każdej szybkości, którą spróbowaliśmy, więc w praktyce nie może rejestrować czysto przy żadnym z nich.
Raw to 15 bajtów na próbkę, zakodowana deltą to nasze oszacowanie 11 bajtów boostu, oba zawierają 20kB/s wszystkiego innego, i to szybkości maksymalne mierzone z działającą pętlą, zaokrąglone w dół bez niczego zaoszczędzanego. Szyna SPI 10MHz nie jest w żadnym razie ograniczeniem, mogłaby przenosić około 59 000 próbek na sekundę.
Więc 8kHz potrzebuje około 140kB/s wobec 335kB/s, które flash zajmuje na pełnych obrotach, około 42% bez żadnego kodowania deltą w ogóle, lub około 108kB/s (32%) z nim. Procesor też nie pracuje zbyt ciężko: przy 2kHz większość przejścia pętli jest spędzana na czekaniu na szynie I2C, a na 10MHz SPI ten sam odczyt przy 8kHz powinien zająć około 0,3ms. Sam IMU osiąga maksymalnie 8kHz, więc reszta to rezerwowy zapas. To jeszcze testowanie na stole do czasu, gdy przyjedzie płytka SPI, a ustawienia domyślne będą i tak niższe, przy czym rejestrowanie spada jego szybkość po fazie napędu, ale S3 wygląda na dobrze przygotowany do tego.
Spróbuj sam
Skoro już ciężką pracę wykonaliśmy, przebudowane biblioteki ESP32-S3 znajdują się w pobieranychch na dole tego posta dla każdego innego korzystającego z Arduino. Dotyczą one tylko esp32 core 3.3.12 z Boards Manager i zastępują ten folder:
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
W systemie Windows zamknij Arduino IDE, a następnie otwórz wiersz polecenia (wpisz cmd w menu Start, a nie PowerShell) i wklej to. Znajduje zip w folderze Pobieranie bez względu na to, co dodał pobieracz do jego nazwy, i przechowuje oryginalny folder jako 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"
Ostatnia linia czyści zbuforowaną kopię skompilowanego rdzenia IDE, aby został przebudowany względem nowych bibliotek. Aby wrócić do standardu:
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"
Nie używaj Extract All w Eksploratorze na zip, kilka ścieżek wewnątrz jest zbyt długo dla niego i po cichu pominę pliki. Polecenie powyżej lub 7-Zip są w porządku.
Kilka rzeczy do wiedzy:
- Przetestowaliśmy to na S3 z 8MB osemkrotnym PSRAM (S3R8), z PSRAM ustawionym na OPI PSRAM w menu Narzędzia. Nie spróbowaliśmy quad PSRAM, i ponieważ program działa z PSRAM, nie będzie pasować do płytek bez żadnych.
- Program jest kopiowany do PSRAM przy starcie, więc tracisz tę ilość PSRAM, plus jedną szesnastą dla ECC.
- Jest zbudowany z esp32-arduino-lib-builder Espressif na ESP-IDF 5.5.5, to samo co oficjalne biblioteki 3.3.12, z dodanymi tylko dwa ustawienia. Całą zasługę należy Espressif. To nieoficjalne i tak jak jest, więc zachowaj swój standardowy folder.
Jeśli wolisz zbudować to sam, to esp32-arduino-lib-builder Espressif (Linux lub WSL), wyewidencjonowany w wersjach wymienionych w pliku versions.txt w oficjalnym folderze bibliotek 3.3.12, z tymi dwiema liniami dodanymi do configs/defconfig.esp32s3 przed uruchomieniem kompilacji:
CONFIG_SPIRAM_XIP_FROM_PSRAM=y CONFIG_SPIRAM_ECC_ENABLE=y
IMU Jupitera przenosi się na 10MHz SPI na następnej płytce, aby mógł rejestrować przy 8kHz, w stosunku do 2kHz, które osiąga teraz na I2C. Na forum Rocketry Forum ktoś słusznie postawił pytanie, czy ESP32-S3 przy 240MHz może naprawdę „rejestrować" przy 8kHz, w przeciwieństwie do Just czytania czujnika tak szybko. To dobre pytanie i chociaż byłem pewny, że możemy to zrobić, nie weryfikowałem tego jeszcze, więc zamiast zgadywać, poszliśmy i zmierzyliśmy to prawidłowo. To jest to, co znaleźliśmy, a na dole znajduje się przebudowana biblioteka Arduino, którą ostatecznie uzyskaliśmy, jeśli chcesz spróbować w swoich własnych projektach.

