Wie Micropython den Arbeitsspeicher nutzt

Aus Micropython
Version vom 23. August 2026, 15:36 Uhr von Mp-admin (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „== Speicheraufteilung des RAM in MicroPython == Bei MicroPython auf einem ESP32 muss man zwischen dem physisch vorhandenen RAM des Mikrocontrollers und dem Speicher unterscheiden, den MicroPython tatsächlich für seine Laufzeitumgebung verwendet. Die genaue Aufteilung ist abhängig von ESP32-Variante, Firmware, MicroPython-Version, aktivierten Funktionen und ggf. vorhandenem PSRAM. Die folgenden Angaben beziehen sich daher auf das grundlegende Speicher…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Speicheraufteilung des RAM in MicroPython

[Bearbeiten | Quelltext bearbeiten]

Bei MicroPython auf einem ESP32 muss man zwischen dem physisch vorhandenen RAM des Mikrocontrollers und dem Speicher unterscheiden, den MicroPython tatsächlich für seine Laufzeitumgebung verwendet.

Die genaue Aufteilung ist abhängig von ESP32-Variante, Firmware, MicroPython-Version, aktivierten Funktionen und ggf. vorhandenem PSRAM. Die folgenden Angaben beziehen sich daher auf das grundlegende Speichermodell und nicht auf eine bestimmte Firmware.

1. Physischer RAM des ESP32

[Bearbeiten | Quelltext bearbeiten]

Der ESP32 besitzt mehrere unterschiedliche RAM-Bereiche. Ein Teil davon ist für spezielle Hardwarefunktionen reserviert oder wird von der ESP-IDF bzw. MicroPython verwendet.

Beim klassischen ESP32 (ESP32-D0WD) stehen intern ungefähr 520 KiB SRAM zur Verfügung. Diese sind jedoch nicht einfach als ein zusammenhängender Speicherbereich nutzbar.

Bereich Typische Größe Verwendung
DRAM ca. 320 KiB Daten, Variablen, MicroPython-Heap
IRAM ca. 128 KiB Programmcode, zeitkritischer Code
RTC RAM 8 KiB Daten, die bestimmte Sleep-Modi überleben können

Die tatsächlich für MicroPython verfügbaren Größen sind kleiner, da bereits Teile des RAM von Bootloader, ESP-IDF, Hardwaretreibern und MicroPython selbst benötigt werden.

2. MicroPython-Heap

[Bearbeiten | Quelltext bearbeiten]

Der wichtigste Speicherbereich für Python-Programme ist der MicroPython-GC-Heap.

MicroPython verwendet einen eigenen Garbage Collector (GC). Python-Objekte wie

  • Integer und Float-Objekte
  • Strings
  • Listen
  • Dictionaries
  • Tupel
  • Klassen und Instanzen
  • Bytearrays
  • Python-Codeobjekte
  • LVGL-Objekte und deren Python-Wrapper

werden grundsätzlich aus diesem Heap angefordert.

Der aktuelle Zustand kann mit

import gc

print(gc.mem_alloc())
print(gc.mem_free())

ermittelt werden.

`gc.mem_alloc()` liefert den aktuell belegten Speicher des MicroPython-Heaps.

`gc.mem_free()` liefert den aktuell freien Speicher innerhalb dieses Heaps.

Die Summe aus beiden Werten entspricht ungefähr der aktuellen Größe des GC-Heaps:

GC-Heap = mem_alloc() + mem_free()

Dabei handelt es sich nicht um den gesamten freien RAM des ESP32. Es wird ausschließlich der Speicher betrachtet, den der MicroPython-Garbage-Collector verwaltet.

3. Aufbau des MicroPython-GC-Heaps

[Bearbeiten | Quelltext bearbeiten]

Der GC-Heap ist wiederum in kleine Blöcke unterteilt. MicroPython verwaltet diese Blöcke mit einer Bitmap.

Auf einem 32-Bit-System entspricht ein GC-Block typischerweise vier Maschinenwörtern, also:

4 × 4 Byte = 16 Byte

Objekte werden aus einer oder mehreren solchen Blöcken aufgebaut.

Das bedeutet auch, dass der Heap nicht beliebig fein aufgeteilt werden kann. Kleine Objekte benötigen mindestens einen Heap-Block.

Mit

import micropython

micropython.mem_info()

kann man wesentlich detailliertere Informationen über den Heap ausgeben.

Mit

micropython.mem_info(1)

kann MicroPython zusätzlich eine Darstellung der Heap-Belegung ausgeben.

Diese Informationen sind besonders nützlich, wenn ein Programm trotz scheinbar ausreichendem freien Speicher keine größeren Objekte mehr anlegen kann.

4. Fragmentierung des Heaps

[Bearbeiten | Quelltext bearbeiten]

Ein wichtiger Unterschied besteht zwischen freiem Speicher und zusammenhängendem freiem Speicher.

Beispielsweise kann ein Heap so aussehen:

[####][....][##][....][######][..][###][........]

Dabei sind `.` freie Bereiche und `#` belegte Bereiche.

Es kann insgesamt viel freier Speicher vorhanden sein, trotzdem kann eine große Speicheranforderung fehlschlagen, wenn kein ausreichend großer zusammenhängender Bereich verfügbar ist.

Der Garbage Collector kann nicht immer beliebig fragmentierte Speicherbereiche zusammenführen.

Deshalb ist bei größeren Objekten nicht nur

gc.mem_free()

interessant, sondern auch die tatsächliche Heap-Struktur.

5. Python-Code und Flash

[Bearbeiten | Quelltext bearbeiten]

Python-Quelltext liegt zunächst als Datei im Flash-Dateisystem.

Beim Import wird der Code jedoch verarbeitet und in eine für MicroPython geeignete interne Darstellung gebracht.

Je nach Situation entstehen dabei zusätzliche Objekte im RAM.

Deshalb kann beispielsweise

import lvgl

deutlich mehr RAM benötigen als die Größe der eigentlichen Datei vermuten lässt.

Bei großen Modulen ist deshalb die Dateigröße im Flash kein zuverlässiger Hinweis auf den später benötigten RAM.

6. Bytecode und Frozen Modules

[Bearbeiten | Quelltext bearbeiten]

MicroPython kann Module als Frozen Modules in die Firmware integrieren.

Der Python-Code wird dabei bereits beim Erstellen der Firmware in die Firmware aufgenommen.

Dadurch muss der entsprechende Quelltext nicht aus dem Dateisystem geladen werden.

Frozen Modules haben deshalb insbesondere bei kleinen Mikrocontrollern einen wichtigen Vorteil:

Normaler Python-Code Frozen Module
Datei im Flash-Dateisystem Bestandteil der Firmware
Import aus dem Dateisystem Import aus der Firmware
zusätzlicher Dateisystemplatz erforderlich kein Dateisystemplatz erforderlich
Code kann teilweise RAM benötigen Code kann weitgehend direkt aus dem Flash verwendet werden

Allerdings benötigen auch Frozen Modules RAM für die tatsächlich erzeugten Python-Objekte und Laufzeitdaten.

Neben dem MicroPython-Heap existieren Stacks für die CPU bzw. die einzelnen ESP32-Tasks.

Der Stack wird unter anderem für

  • Funktionsaufrufe
  • lokale Variablen auf C-Ebene
  • Interrupts
  • Treiber
  • FreeRTOS-Tasks

verwendet.

Der Stack gehört nicht zum MicroPython-GC-Heap.

Deshalb bedeutet beispielsweise

gc.mem_free() = 30 KiB

nicht, dass insgesamt noch 30 KiB RAM frei sind.

Umgekehrt kann ein System bereits an eine RAM-Grenze stoßen, obwohl `gc.mem_free()` noch einen scheinbar komfortablen Wert anzeigt.

MicroPython selbst besteht größtenteils aus C-Code.

Auch die ESP-IDF und die Hardwaretreiber verwenden Speicher außerhalb des MicroPython-GC-Heaps.

Dieser Speicher wird über die C-/ESP-IDF-Speicherverwaltung angefordert.

Dazu gehören beispielsweise:

  • Netzwerk-Stack
  • WLAN
  • TCP/IP
  • Bluetooth
  • SPI-/I2C-Treiber
  • Displaytreiber
  • DMA-Puffer
  • LVGL
  • ESP-IDF-Komponenten
  • interne MicroPython-Datenstrukturen

Dieser Bereich ist für `gc.mem_free()` unsichtbar.

Das erklärt, warum der tatsächlich noch verfügbare RAM kleiner sein kann als der durch `gc.mem_free()` angezeigte Wert.

9. DMA-fähiger Speicher

[Bearbeiten | Quelltext bearbeiten]

Bestimmte Hardwarekomponenten des ESP32 können nicht auf jeden RAM-Bereich zugreifen.

Beispielsweise benötigen DMA-Controller Speicher mit bestimmten Eigenschaften.

Daher können Treiber eigene DMA-fähige Speicherbereiche reservieren.

Das ist insbesondere bei

  • SPI
  • I2S
  • Displaycontrollern
  • SD-Karten
  • Kameras
  • Audio

relevant.

Ein großer freier MicroPython-Heap bedeutet deshalb nicht automatisch, dass ein ausreichend großer DMA-Puffer angelegt werden kann.

Viele ESP32-Boards besitzen zusätzlich externes PSRAM.

Beim klassischen ESP32 sind beispielsweise 2 MiB oder 4 MiB PSRAM verbreitet.

PSRAM erweitert den verfügbaren Speicher erheblich, ist aber technisch nicht identisch mit dem internen SRAM.

Je nach MicroPython-Firmware kann PSRAM

  • vollständig oder teilweise in den MicroPython-Heap integriert sein,
  • für bestimmte C-Datenstrukturen verwendet werden,
  • von einzelnen Treibern bevorzugt genutzt werden,
  • oder überhaupt nicht für den normalen Python-Heap zur Verfügung stehen.

Deshalb muss bei einer konkreten Firmware geprüft werden, wie PSRAM tatsächlich eingebunden wurde.

Der ESP32 besitzt zusätzlich einen kleinen RTC-Speicher.

Dieser Speicher ist für Funktionen rund um Sleep-Modi vorgesehen.

Ein Teil der Daten kann je nach Sleep-Modus erhalten bleiben.

Der RTC-Speicher gehört nicht zum normalen MicroPython-GC-Heap.

Die folgende Darstellung zeigt das grundsätzliche Modell:

ESP32 RAM
│
├── Internes SRAM
│   │
│   ├── DRAM
│   │   │
│   │   ├── MicroPython GC-Heap
│   │   │   ├── Python-Objekte
│   │   │   ├── Strings
│   │   │   ├── Listen
│   │   │   ├── Dictionaries
│   │   │   └── sonstige Python-Daten
│   │   │
│   │   ├── C-Heap
│   │   │   ├── ESP-IDF
│   │   │   ├── Treiber
│   │   │   ├── Netzwerk
│   │   │   └── LVGL
│   │   │
│   │   └── DMA-Puffer
│   │
│   ├── IRAM
│   │   └── Programmcode / zeitkritischer Code
│   │
│   └── RTC RAM
│       └── Sleep-/RTC-Daten
│
└── Externes PSRAM (falls vorhanden)
    │
    ├── C-Heap
    ├── Treiber
    └── ggf. MicroPython GC-Heap

13. Was zeigt `gc.mem_free()` tatsächlich?

[Bearbeiten | Quelltext bearbeiten]

Für die Praxis ist die wichtigste Erkenntnis:

gc.mem_free()
        │
        ▼
freier Speicher im MicroPython-GC-Heap

und nicht:

gesamter noch verfügbarer RAM des ESP32

Für eine vollständige Speicheranalyse müssen daher mindestens mehrere Bereiche betrachtet werden:

Messwert Bedeutung
`gc.mem_alloc()` Von Python-Objekten belegter GC-Heap
`gc.mem_free()` Freier GC-Heap
`micropython.mem_info()` Detaillierte Struktur des GC-Heaps
ESP-IDF Heap-Informationen Freier/benutzter C-Speicher
PSRAM-Informationen Zustand des externen RAM
`os.statvfs("/")` Speicherplatz des Flash-Dateisystems
`esp32.flash_size()` Physische Größe des Flash
`esp32.Partition.info()` Aufteilung des Flash in Partitionen

14. Bedeutung für das CYD mit LVGL

[Bearbeiten | Quelltext bearbeiten]

Für unser CYD ist insbesondere die Unterscheidung zwischen Flash und RAM wichtig.

Der Flash enthält unter anderem:

  • MicroPython-Firmware
  • Partitionstabelle
  • Bootloader
  • Python-Dateien
  • LVGL-Firmwarebestandteile
  • Dateisystem

Der RAM enthält dagegen während des Betriebs unter anderem:

  • MicroPython-GC-Heap
  • Python-Objekte
  • LVGL-Objekte
  • Display-Puffer
  • Touch-Daten
  • SPI-/DMA-Puffer
  • Netzwerkdaten
  • C-Datenstrukturen

Gerade LVGL kann relativ viel RAM benötigen. Ein Display-Puffer von beispielsweise 240 × 320 Pixeln benötigt bei 16 Bit Farbtiefe bereits:

240 × 320 × 2 Byte
= 153.600 Byte
≈ 150 KiB

Zwei solche Buffer würden bereits etwa 300 KiB benötigen.

Damit wird verständlich, warum bei einer LVGL-MicroPython-Firmware der verfügbare RAM wesentlich schneller knapp werden kann als der Flash.

15. Unser `memtools.py`

[Bearbeiten | Quelltext bearbeiten]

Für `memtools.py` bedeutet das, dass `mem_view()` künftig sinnvollerweise mehrere voneinander unabhängige Speicherinformationen anzeigen sollte:

mem_view()
│
├── Flash
│   ├── Gesamtgröße
│   └── Partitionen
│
├── Filesystem
│   ├── Gesamt
│   ├── Belegt
│   └── Frei
│
├── MicroPython GC
│   ├── Gesamt
│   ├── Belegt
│   └── Frei
│
├── C-Heap
│   ├── Gesamt
│   ├── Belegt
│   └── Frei
│
└── PSRAM
    ├── Gesamt
    ├── Belegt
    └── Frei

Damit würde `memtools.py` nicht nur den MicroPython-Heap, sondern tatsächlich die wesentlichen Speicherressourcen des ESP32 überwachen.