Dev-Branch

Evt. ist es nicht nur der freie Heapspeicher sondern auch der größte zusammenhängende Speicherblock. Der ist wohl ohne PSRAM zu klein um den Audiodekoder zu allokieren, 32 KB ?: Hier ein Lolin D32 (ohne Pro/PSRAM):

Die Frage, die ich mir stellte: Der Decoder war früher ja auch mit weniger - also ohne PSRAM – lauffähig.
Fehlt es hier eventuell nur an einer directive, dass er mit weniger auch auskommen soll oder war früher tatsächlich wesentlich mehr verfügbar?

Es scheint nur wenig Speicher zu fehlen um den MP3-Dekoder auch ohne PSRAM wieder starten zu können:

Wir haben z.B. die Stackgröße des Audiotask hier auf 6000 angehoben um auch Webstreams mit SSL Verbindung starten zu können. Der Audiotask starb zuvor den „stack canary“ Tod. Die SSL Verschlüsselung schluckt viel Speicher, das könnte man z.b. abhängig von PSRAM ja/nein wieder etwas reduzieren.
Wenn Board ohne PSRAM → Keine Webstreams mit SSL

Mit ein paar weiteren Tricks wäre das auch auf einem Board ohne PSRAM wieder abspielbar. Macht gern Vorschläge dazu!

Insgesamt liegt die Zukunft mit all den Features aber auf Boards mit PSRAM. Die sind ja mittlerweile auch fast Standard. Wer jetzt einen Lolin D32 einsetzt kann ihn für kleines Geld durch einen Lolin D32 Pro ersetzen ohne weitere Lötarbeit

4 „Gefällt mir“

Gibt’s jetzt eigentlich noch Probleme mit Stottern beim Abspielen von mp3-Files?
Weil ansonsten wäre es langsam Zeit, mal den Stand von dev in master zu überführen.

Bei mir spielt Alles ohne Ruckeln ab.
Einziges Problem sind die Boards ohne PSRAM, aber das könnte man auch im nächsten Schritt erledigen. Von meiner Seite spricht nichts gegen eine Übernahme in den Master.

Seltsamerweise habe ich seit dem Update Abspielprobleme.
Stand alt: 20240108-1-DEV lief ohne Probleme
Stand neu: 20240111-1-DEV jetzt wird die Musik viel zu langsam und mit Holpern abgespielt.

Ich hatte noch keine Zeit das weiter einzugrenzen, ein Clean Build hat nix gebracht. Aber scheinbar bin ich auch der Einzige mit dem Problem :person_shrugging: Ich muss da noch mal suchen. Der MP3 Task liegt auch bei 91%

Nachtrag: Ich habe gerade ein Update auf „20240114-1-DEV“ gemacht und mein Problem ist behoben :person_shrugging:

Wusste nicht dass https:// auch möglich ist.

Hab nun einen Sender zum Testen gefunden und das einmal versucht.
Ergebnis: Starten und anhören OK.
Sobald ich aber zusätzlich mit der WebGUI drauf gehe, dauert es nicht lange und ich bekommen den Toast, dass die Connection zum ESP unterbrochen wurde.
Gleichzeitig zeigt das LOG im RS232 Terminal: Slow … und nach ein paar Sekunden ist Feierabend.
Das Problem ist hier offensichtlich, dass sich das WiFi des ESP bei dem zusätzlichen Zugriff durch das WebGUI verabschiedet.
Es lässt sich kein WebRadio mehr abspieln und auch der Zugang via WebGUI ist nicht mehr möglich.

Hab trotzdem die Stacksize des Audiotask probehalber auf 8000 erhöht.
Änderte aber leider nichts.

Kommt durch die hohle Last wegen SSL irgend etwas im Bereich WiFi in einen TimeOut?

Hier las LOG.

vor und zwischen folgenden beiden Zeile hab ich im WebGUI Lautstärke verändert und das Log ausgelesen.

I [2770678] Neue Lautstärke empfangen via Queue: 8
I [2790083] info        : slow stream, dropouts are possible

hier vom Anfang:

I [2752344] Kontroll-Kommando empfangen via Queue: 4
I [2752344] Kommando: Nächster Titel
I [2752346] info        : Connect to new host: "https://stream.antenne.com/oldies/mp3-192"
I [2752358] info        : buffers freed, free Heap: 74556 bytes
I [2753301] info        : SSL has been established in 942 ms, free Heap: 31488 bytes
N [2753306] 'https://stream.antenne.com/oldies/mp3-192' wird abgespielt (7 von 7)
I [2753343] info        : redirect to new host "https://antnds.streamabc.net/ands-antndsoldies-mp3-192-4005366?sABC=65n92orr%230%23sq3qn165p240p5s0nn5623r7sr0n65p8%23&aw_0_1st.playerid=&amsparams=playerid:;skey:1...
I [2753355] info        : Connect to new host: "https://antnds.streamabc.net/ands-antndsoldies-mp3-192-4005366?sABC=65n92orr%230%23sq3qn165p240p5s0nn5623r7sr0n65p8%23&aw_0_1st.playerid=&amsparams=playerid:;skey:1...
N [2753371] no cover image for webstream
I [2753379] info        : buffers freed, free Heap: 62156 bytes
I [2754909] info        : SSL has been established in 1529 ms, free Heap: 34912 bytes
I [2755124] info        : MP3Decoder has been initialized, free Heap: 15584 bytes , free stack 1776 DWORDs
I [2755124] lasthost    : https://antnds.streamabc.net/ands-antndsoldies-mp3-192-4005366?sABC=65n92orr%230%23sq3qn165p240p5s0nn5623r7sr0n65p8%23&aw_0_1st.playerid=&amsparams=playerid:;skey:1705585646
I [2755833] info        : stream ready
I [2755833] info        : syncword found at pos 0
I [2755843] info        : Channels: 2
I [2755843] info        : SampleRate: 44100
I [2755843] info        : BitsPerSample: 16
I [2755843] info        : BitRate: 192000
I [2763166] Neue Lautstärke empfangen via Queue: 9
I [2764288] Neue Lautstärke empfangen via Queue: 10
I [2765270] Neue Lautstärke empfangen via Queue: 11
I [2768520] Neue Lautstärke empfangen via Queue: 10
I [2769771] Neue Lautstärke empfangen via Queue: 9
I [2770678] Neue Lautstärke empfangen via Queue: 8
I [2790083] info        : slow stream, dropouts are possible
I [2791084] info        : slow stream, dropouts are possible
I [2792085] info        : slow stream, dropouts are possible
I [2793086] info        : slow stream, dropouts are possible
I [2793882] info        : Stream lost -> try new connection
I [2793883] info        : Connect to new host: "https://antnds.streamabc.net/ands-antndsoldies-mp3-192-4005366?sABC=65n92orr%230%23sq3qn165p240p5s0nn5623r7sr0n65p8%23&aw_0_1st.playerid=&amsparams=playerid:;skey:1...
I [2793905] info        : buffers freed, free Heap: 68340 bytes
[2796607][E][WiFiClientSecure.cpp:144] connect(): start_ssl_client: -1
I [2796608] info        : Request https://antnds.streamabc.net/ands-antndsoldies-mp3-192-4005366?sABC=65n92orr%230%23sq3qn165p240p5s0nn5623r7sr0n65p8%23&aw_0_1st.playerid=&amsparams=playerid:;skey:1705585646 fail...
N [2796629] station     : 
I [2796629] streamtitle :
I [2796629] icyurl      :
N [2796630] Ende der Playlist erreicht.

Man sieht hier schön das der Heapspeicher zu Ende geht. Unter 10-15KB reagiert die Weboberfläche nicht mehr. Alternativ stellen die meisten Radiosender eine URL ohne SSL zur Verfügung, für schwachbrüstige Empfänger wie ESP-32.

Insgesamt sollten wir aber noch mehr Speicher einsparen:

Es wurde zuletzt immer mehr statischer Speicher reserviert. Das beugt zwar Heap-Fragmentierung vor, raubt aber zur Laufzeit freien Heap. Das ist auch der Grund warum Boards ohne PSRAM nicht mehr laufen.
PlatformIO bietet ein schönes Tool für die Speichernutzung („Inspect“). Dort kann ich das belegte statische RAM auflisten. Hier könnte man einsparen:

1.) Der 32KB „Buffer“ wird nur für den Webupload benötigt. Evt. machen wir den zu Lasten der Upload-Geschwindigkeit kleiner oder allokieren den nur wenn auch wirklich ein Upload gestartet wird.

2.) 4KB „buf“ wird nur für die Auflistung der NVS Einträge benötigt. In der Praxis nur wenn man den „Tools“ Tab in der Weboberfläche öffnet.

3.) 1,2KB „knownNetworks“ reserviert 10 gespeicherte Wifi-Netzwerke. Hier könnte man eine Liste verwenden mit z.B. std.

4.) 512Bytes „readBuffer“ max Lesepuffer des PN5180. Der wird nie verwendet da wir nur die UID lesen. Habe ich bereits entfernt hier, muss nur noch in platform.ini geändert werden.

Mit kleinen Änderungen kann ich ESPuino auch wieder auf einem Board ohne PSRAM starten und Musik abspielen (Lolin D32). Evt. läuft dann auch SSL Webradio stabiler.

4 „Gefällt mir“

Da könnte man ja BOARD_HAS_PSRAM verwenden und wenn das nicht gegeben ist, dann versucht man es mal nur mit 16 kB. Oder?

3 „Gefällt mir“

Ja genauso habe ich es mal testweise gemacht:

#ifdef BOARD_HAS_PSRAM
	static const uint32_t chunk_size = 16384; // bigger chunks increase write-performance to SD-Card
#else
	static const uint32_t chunk_size = 2048; // save memory if no PSRAM is available
#endif
1 „Gefällt mir“

Das würde aber nichts ändern, denn mein Versuch war MIT PSRAM.

E (725) esp_core_dump_flash: No core dump partition found!
E (725) esp_core_dump_flash: No core dump partition found!
I [78] Maximale Inaktivitätszeit wurde aus NVS geladen: 10 Minuten
D [129] RFID-Tags koennen jetzt gescannt werden...
N [130] Port-expander gefunden
N [131] Interrupt für Port-Expander aktiviert
I [133] Initiale Lautstärke wurde aus NVS geladen: 10
I [144] Maximale Lautstärke für Lautsprecher wurde aus NVS geladen: 21
N [144] Lautsprecher eingeschaltet

 _____   ____    ____            _
| ____| / ___|  |  _ \   _   _  (_)  _ __     ___
|  _|   \__  \  | |_) | | | | | | | | '_ \   / _ \
| |___   ___) | |  __/  | |_| | | | | | | | | (_) |
|_____| |____/  |_|      \__,_| |_| |_| |_|  \___/
         Rfid-controlled musicplayer


N [264] Software-revision: 20240106-1-DEV
N [264] Git-revision: 6a2adf5-dirty
N [264] Arduino version: 2.0.14
N [274] ESP-IDF version: 4.4.6
N [275] Wakeup was not caused by deepsleep: 0
N [275] Versuche SD-Karte im SD_MMC-Modus (1 Bit) zu mounten...
D [285] SD card type: SDHC
N [285] SD-Kartengröße / freier Speicherplatz: 14910 MB / 14868 MB
I [296] FTP-User wurde aus NVS geladen: esp32
I [297] FTP-Passwort wurde aus NVS geladen: esp32
D [358] RC522 firmware version=0
D [408] RFID-Tags koennen jetzt gescannt werden...
I [409] Hostname aus NVS geladen: ESPuino
N [413] SSID 0 von NVS geladen: FritzBox
N [662] Versuche mit WLAN 'FritzBox' zu verbinden...
D [710] Freier Heap-Speicher nach Setup-Routine: 113444
D [710] PSRAM: 4191963 bytes
D [710] Flash-size: 16777216 bytes
N [1466] Verbunden mit WLAN 'FritzBox' (Signalstärke: -53 dBm, Kanal: 13, MAC-Adresse: xx:xx:xx:xx:xx:xx)
N [1466] Aktuelle IP: 192.168.xxx.xxx
N [1478] Synchronisiere Uhrzeit via NTP...
N [1490] mDNS gestartet: http://ESPuino.local
N [1497] HTTP-Server gestartet.
N [6049] Datum/Uhrzeit empfangen von NTP-Server: 18.01.2024, 16:22:08

Auch wenn wir irgendwann z.B. mur noch Geräte mit min…Rev 3 unterstützen sparen wir viel Speicher.

1 „Gefällt mir“

Ich würde sagen, wir machen die Buffer von 1) und 2) dynamisch. Somit verbrauchen sie nur Speicher, wenn sie benötigt werden.

Für 3 bietet sich ein std::vector an. Wir wissen wie viele Einträge wir zum Startzeit haben und es ist unwahrscheinlich, dass sich die Anzahl der Einträge viel während der Laufzeit ändern → nicht viele realloc’s vom Vector (wenn viele Änderungen / Laufzeit zu erwarten sind, wäre eine Linked List wie std::list besser).

3 „Gefällt mir“

Dieser Artikel wird gerade auf der PlatformIO Startseite angeboten & passt zum Thema:

grafik

Dynamically static allocation in embedded systems — qqmrichter (personaljournal.ca)

Grundsätzlich ein recht gutes Artikel, er erklärt gut die Vor- und Nachteile der unterschiedlichen herangehensweisen. Wobei er hier wahrscheinlich nicht mit einem ESP, sondern etwas kleinerem (zB ATmegs mit 1-2kB RAM) arbeitet. Da ist Heap Fragmentierung natürlich umso schmerzvoller.

Zu seinem Code, muss ich halt sagen, er hat ein std::vector<events_t*> neu erfunden (plus ein * bei der Definition seines Pointer-Arrays vergessen). Kann aber sein, dass es bei seinem gcc keine c++ stdlib gibt (zB die alten avr-gcc).

Ich hätte an seiner Stelle bei dem Event System (je nachdem wie dynamisch es ist) eine linked list (d.h. jede Struktur hat einen internen Pointer der auf den nächsten Eintrag zeigt) verwendet. Damit opfert man Performance für weniger Speicher (bzw exakt so viel wie man benötigt + keine große zusammenhängenden Speicherblöck notwendig), da man sich immer durch die Liste „hangeln“ muss.
Bzw eine Kombination von seiner Lösung und der verketten Liste wäre auch möglich, durch Blöcke von zb 8 event_t die selbst ebenfalls als std::list verkettet sind. Dann hat man das Beste aus beiden Welten :wink:


Aber da es auch gut zum Thema passt, hier ein sneak Preview von der Branch für die Speicheroprimierung.
Folgende Dinge sind schon drin:

  • Nr 1 von tueddy (Speicher wird dynamisch allokiert und am Ende des gesamten Uploads wieder frei gegeben + weniger Buffer wenn kein PSRAM da ist)
  • Nr 2 mit NVS Enumerierung verwendet jetzt den offiziellen NVS Iterator von espressif (wie viel unterschied das macht habe ich jetzt noch nicht geprüft)

MP3 kann ich schon mit meinem ESP32 abspielen, auch wenn ich den PSRAM deaktiviere.

Fehlen tun noch die WiFi Einstellungen, die kommen wahrscheinlich zuerst am Mittwoch. Danach werde ich die Branch verschönern und eine PR starten.

7 „Gefällt mir“

@laszloh Danke für Deinen Code-Vorschlag, finde ich sehr gut!
Hast Du mehrfache Uploads probiert? Es müsste der gleiche zuvor freigegebene Speicher wiederverwendet werden ohne Fragmentierung. Ist das bei Dir der Fall?

Das Allokieren kann fehlschlagen wenn kein zusammenhängender 32KB Block frei ist, obwohl noch genug Heap frei wäre. Das passiert ja jetzt beim Abspielen von Audio ohne PSRAM. Für den Upload-Puffer könnte man einen 2. Versuch mit einem kleineren Speicher z.B. 2 oder 4KB unternehmen wenn malloc(32K) fehlschlägt.

Für die 4KB NVS Auflistung gibt es auch noch ein Beispiel das den Puffer lokal mit malloc() und free() verwendet hier.

Nr. 4 Die 500 Bytes Einsparung für den PN5180 sind bereits eingecheckt, Kleinvieh macht auch Mist…

Meinst du ein Ordner hochladen? Ja, das funktioniert, weil bei einem Form Upload nur nachdem der gesamte Upload fertig ist 1x der Handler für das AsyncWebServerRequest ausgelöst wird. Das sieht man auch sehr schon in dem Debugger von Firefox/Chrome bei dem Network Pfad, da kann man beobachten, wann dass HTTP-200 von dem ESP zurück kommt.

Was noch nicht ganz sauber funktioniert ist, wenn man schnell hintereinander einzelne Dateien hochladet, dann wird der Buffer jedes einzelne mal allokiert und wieder zerstört. Hier überlege ich noch, wie das gut zu lösen wäre. Ein Timestamp und eine fest kodierte Zeit (zb bis 15s nach dem letzten Upload), wird der Buffer gehalten und danach wieder freigegeben.

Stimmt! Guter Punkt, ich habe auch kein Abfangmechanismus dagegen. Ich schau, wie ich die reduktion einbauen kann (wahscheinlich recht einfach mit dividion durch 2). Werde auch schauen, dass ich statt 1 zusammen hängenden Block zu allokieren auch 2 (bzw nr_of_buffers) Blöcke machen.

Was mir bei der Lösung (und auch bei dem aktuellen Code) einen kalten Schauer über den Rücken jagt ist der Umstand, dass wir direkt auf die rohe NVS Datenstruktur zugreifen. Wenn espressif das Layout auch nur im Ansatz ändert (und in den NVS Treiber ein Kompabilitäts-Layer einzieht), explodiert uns der Code potenziell ins Gesicht.

Wenn wir schon eine Unterstützung für einen Iterator im NVS von esp-idf haben, sollten wir den verwenden, statt so low-level auf die NVS-Partition zuzugreifen. Machen wir ja bei der FAT32 Partition der SD-Karte ja auch nicht :wink:


Zu Nr 3 habe ich zwei Lösungen: Commits · laszloh/ESPuino · GitHub

a) std::vector verwenden

Damit verbrauchen wir noch immer Speicher (im schlimmsten Fall weiterhin die 10 * sizeof(WiFiSettings). Im Schnitt sollte das aber schon eine Einsparung bringen, zB ich habe bis heute max 3 WiFi Netzwerke in meinem ESPuino gehabt → 70% Einsparung (okay, weniger, weil std::vector auch ein Overhead hat).
Hier ändere ich an der Struktur im NVS nichts, damit sind die Änderungen einfach und überschaubar. Aber die Einstellungen sind noch immer im RAM, auch wenn wir sie nicht mehr benötigen… (siehe Punkt b :slight_smile: )

Commit: Change knownNetworks to std::vector

b) Die WiFi Einstellungen nicht in den RAM laden

Hier gehe ich ein Schritt weiter. Mit der Annahme, dass wir 99% der Zeit die WiFi Einstellungen nicht brauchen, sollten sie auch nciht im RAM liegen. Stattdessen sollten wir sie direkt aus dem NVS laden, wenn sie gebraucht werden. Am Besten ohne viel Speicher allokieren zu müssen, am Besten: nur auf dem Stack → keine dynamische Speicherallokierung.

Aktuell wir das Array mit den aktiven Einstellungen 1:1 in einen einzelnen NVS Eintrag geschrieben. Dadurch dass die Einstellungen auch im RAM sind, macht das die Änderung einfach:

  1. Änderung im RAM ausführen
  2. RAM Bereich 1:1 in den NVS schreiben
  3. Profit

Wenn wir aber nun den RAM Block weg lassen haben wir das Problem, dass wir den NVS Eintrag laden müssen → bis zu 1,2kB an Speicher wird benötigt → malloc zwingend notwendig. Die Schritte schauen so aus:

  1. malloc von max 1200 Bytes
  2. NVS in den RAM laden
  3. Änderung durchführen / Eintrag auslesen
  4. RAM Bereich zurückschreiben
  5. RAM freigeben

Um hier kein malloc zu machen, habe ich mir überlegt, die Struktur der WiFi Einstellungen in NVS zu ändern. Statt 1 Eintrag für alle Netzwerkeinstellungen zu haben, mache ich 1 Eintrag / Netzwerkeinstellung. Es wäre super, wenn man die SSID als key nehmen könnte, geht aber nicht, da keys nur 15 Charekter lang sein können, SSIDs das Doppelte (wäre ja zu einfach gewesen). Somit gleibt mir nichts anderes übrig als die Keys durchzunummerieren (ein Vorteil, damit kann ich schön auf „eh nicht mehr als 10 Einträge, gelle“ prüfen). Ich packe alle in ein eigenes NVS namespace, womit ich schnell über alle Einträge mit den NVS Iterator drüber gehen kann um den Richtigen zu finden.

Danach muss nur noch der gefundene Eintrag geladen/geändert/gelöscht werden. Wir ersparen uns den dynamischen Speicher, da ein WiFiSettings 120 Byte ist was noch recht angenehm auf den Stack passt. Ein Nachteil ist das leicht höhere NVS Speicherverbrauch (rechnerisch sollten es 5 Entries / Eintrag sein, statt 5 - 40 für einen gesamten Eintrag). Meiner Meinung nach ist das zu verkraften:

alt:
	leer (0 WiFi Einträge):
		used: 819, total: 8064 (precent: 10.15)
	voll (10 WiFi Einträge):
		used: 859, total: 8064 (precent: 10.65)
	
neu:
	leer (0 WiFi Einträge):
		used: 819, total: 8064 (precent: 10.15)
	voll (10 WiFi Einträge):
		used: 878, total: 8064 (precent: 10.88)

Commit: Remove all wifi settings from RAM

3 „Gefällt mir“

Ich habe noch einen Punkt für die RAM Einsparung gefunden (reiht sich unter Kleinvieh ein):

4. Bei AsyncWebServer wird bei jedem Aufruf von on ein AsyncCallbackWebHandler instanziiert. Das sind 124 Byte / Handler, zB bei dem Endpunkt „/explorer“ sind das 744 Bytes.

Sparen können wir diese, wenn wir alle Funktionen des Endpunktes in einem eigenen Handler, der die abstrakte AsyncWebHandler implementieren. Grundsätzlich sind alle Endpunkte mit mehr als ~3 HTTP-Request Type (also HTTP_GET, HTTP_PATCH, etc) ein guter Kandidat für eine solche Behandlung.

Neue Softwareversion im DEV-Bereich: 20240201-1-DEV:

  • Einsparung von ca. 40KB Heap-Speicher:
    -32KB Puffer für Datei-Upload wird dynamisch zugewiesen.
    -4KB Einsparung und vereinfachte Auflistung der NVS-Einträge
    -500B Einsparung in der PN5180 Bibliothek
  • Französische Übersetzung für Log/Webinterface
  • Web.cpp wurde auch ohne Änderungen immer neu kompiliert
  • Audioknacken bei High Quality OPUS Dateien behoben

Durch die Einsparungen kann nun auch auf Boards ohne PSRAM wie z.B. Lolin D32 wieder Musik abgespielt werden.

Danke an alle Beteiligten & viel Spaß beim Testen!

9 „Gefällt mir“

Kleine Beobachtung von mir…

/debug
führt zu
JSON-Puffer zu klein für Daten
in der Konsole (die JSON ist korrekt aber unvollständig)
debug.json.txt (1,1 KB)