Speicherplatz sparen ist beim Arduino Pro Mini und anderen ATmega328P-Boards oft der entscheidende Faktor, ob ein Projekt „passt“ oder ob Sie plötzlich vor der Meldung stehen, dass der Sketch zu groß ist. Der ATmega328P stellt nominell 32 KB Flash-Speicher zur Verfügung, doch ein Teil davon ist durch den Bootloader belegt, sodass je nach Bootloader-Variante typischerweise weniger nutzbarer Programmspeicher übrig bleibt. Gerade bei komfortablen Bibliotheken, umfangreichen Menüs, vielen Debug-Ausgaben oder mehreren Sensor-Stacks wächst der Code schnell über die Grenze. Die gute Nachricht: In sehr vielen Projekten lässt sich ohne Funktionsverlust deutlich Flash einsparen – oft zweistellig in Prozent – wenn Sie systematisch vorgehen. Dieser Artikel zeigt bewährte Strategien für effizienten Code für 32 KB Flash-Speicher: von Compiler-Optionen über Bibliotheksauswahl bis hin zu datengetriebenem Design, PROGMEM, String-Handling und schlanken Protokollen. Als Hintergrundquellen sind die Arduino-Referenz zu Strings und Speicherverhalten (Arduino String – Referenz) sowie die avr-libc-Dokumentation zu PROGMEM (avr-libc: Program Space (PROGMEM)) hilfreich, weil sie die Konzepte hinter den Optimierungen erläutern.
Verstehen, was „32 KB Flash“ in der Praxis bedeutet
Flash ist der nichtflüchtige Programmspeicher, in dem Ihr kompiliertes Programm (Maschinencode) und oft auch konstante Daten liegen. Beim ATmega328P sind es 32 KB, aber die nutzbare Größe hängt davon ab, ob ein Bootloader vorhanden ist. Das erklärt, warum derselbe Sketch manchmal auf einem Board „passt“ und auf einem anderen knapp scheitert. Für die Optimierung ist daher nicht nur die Zahl „32 KB“ wichtig, sondern die Frage: Wie groß ist Ihr aktuelles Binär-Image, und welche Teile verursachen Wachstum?
- Flash: Programmspeicher (Code + konstante Daten, wenn Sie sie dort ablegen)
- SRAM: Arbeitsspeicher (Variablen, Heap/Stack, dynamische Strings)
- EEPROM: nichtflüchtig, aber klein und langsamer; sinnvoll für Konfigurationen
Flash-Optimierung ist häufig ein Zusammenspiel aus Code-Struktur, Datenhaltung und Bibliothekswahl. Viele „Flash-Probleme“ sind außerdem indirekt: Debug-Strings füllen Flash, dynamische Strings füllen SRAM, und beides führt zu instabilem Verhalten.
Erst messen, dann optimieren: Woher kommt der Platzverbrauch?
Bevor Sie umbauen, sollten Sie die Größenangaben der Arduino IDE oder Ihres Build-Systems ernst nehmen und nicht nur „gefühlt“ optimieren. Typisch sehen Sie eine Meldung wie „Sketch uses X bytes (Y%) of program storage space“. Das ist Ihr Startpunkt. Zusätzlich lohnt es sich, die Ausgaben je nach Build-Konfiguration zu vergleichen: Debug-Version mit vielen Serial-Ausgaben versus Release-Version ohne Debug.
- Trennen Sie Debug und Release: Debug-Strings und Prüfcode kosten oft überraschend viel Flash.
- Ändern Sie immer nur eine Variable: Sonst wissen Sie nicht, welche Maßnahme wirklich spart.
- Vergleichen Sie Bibliotheksalternativen: Manche Bibliotheken sind sehr komfortabel, aber groß.
Der größte Hebel: Debug-Ausgaben konsequent schlank halten
Serial.print()-Ausgaben sind für die Entwicklung wertvoll, aber sie bringen zwei Kosten mit: (1) die Zeichenketten landen im Flash (oder im SRAM, wenn falsch verwendet), und (2) oft ziehen Sie durch Debugging zusätzliche Codepfade und Formatierungen hinein. Viele Projekte verlieren 2–8 KB Flash allein an Log-Ausgaben.
Compile-Time-Schalter für Debug
Nutzen Sie Präprozessor-Schalter, um Debug-Ausgaben vollständig zu entfernen, statt sie nur „nicht aufzurufen“. Der Compiler kann dann ungenutzte Pfade besser eliminieren.
- DEBUG definieren: Nur in Entwicklungsbuilds aktivieren
- Makros nutzen: Debug-Ausgaben zentral schalten, statt überall im Code zu verteilen
F() Macro und konstante Strings korrekt ablegen
Ein Klassiker: Wenn Sie String-Literale direkt in Serial.print() verwenden, werden sie je nach Umgebung in SRAM kopiert oder als konstante Daten im Flash gehalten, aber nicht immer optimal. Das F()-Makro sorgt typischerweise dafür, dass der Text im Flash bleibt und nicht unnötig SRAM belegt. Das spart nicht nur SRAM, sondern verhindert indirekt, dass Sie später „aus Platznot“ zusätzliche Workarounds einbauen, die wiederum Flash kosten.
Für den Umgang mit Strings und die Nachteile dynamischer String-Objekte ist die Arduino-Referenz eine gute Grundlage (Arduino String – Referenz).
Bibliotheken kritisch prüfen: Komfort kostet Flash
Viele Arduino-Bibliotheken sind so gestaltet, dass sie Einsteigerfreundlichkeit, Portabilität und Features priorisieren. Das ist großartig – kostet aber Flash. Wenn Sie an die 32 KB-Grenze kommen, lohnt es sich, Bibliotheken bewusst zu wählen oder mit schlankeren Alternativen zu arbeiten.
- „Alles-in-einem“-Libraries vermeiden: Wenn Sie nur 10% der Features nutzen, zahlen Sie oft für 100%.
- Grafik/Display-Libraries: Besonders groß, wenn Fonts, Layout-Engines oder Zeichenroutinen enthalten sind.
- Sensor-Stacks: Manche Bibliotheken bringen Kalibrierung, Filter und Debug gleich mit.
Nur das kompilieren, was Sie brauchen
Achten Sie auf Bibliotheken, die Features per Compile-Flags deaktivierbar machen. Bei vielen Projekten ist es effektiver, ein Feature auszuschalten (z. B. zusätzliche Schriftarten, Floating-Point-Ausgaben), als „irgendwo“ 50 Zeilen Code zu sparen.
PROGMEM: Große Tabellen und Texte in den Flash verlagern
Wenn Ihr Projekt Menüs, Lookup-Tabellen, Textbausteine, Bitmap-Daten oder Kommandolisten enthält, ist PROGMEM ein Muss. Damit legen Sie konstante Daten explizit im Programmspeicher ab und vermeiden, dass sie beim Start ins SRAM kopiert werden. Wichtig: PROGMEM spart primär SRAM, aber indirekt kann es auch Flash-Optimierungen ermöglichen, weil Sie dann weniger Workarounds für Speicherknappheit brauchen und Datenstrukturen kompakter gestalten können.
Die avr-libc-Dokumentation erklärt, wie Program Space Zugriff und PROGMEM funktionieren (avr-libc: PROGMEM und pgmspace).
Typische Kandidaten für PROGMEM
- Menütexte, Hilfetexte, Statusmeldungen
- Lookup-Tabellen (Sinus, Gamma-Korrektur, Kennlinien)
- Protokoll-Strings und Befehlsnamen
- Große Konstanten-Arrays
Floating Point vermeiden: Floats sind teuer im Flash
Auf AVR-Mikrocontrollern ist Floating Point im Vergleich zu Integer-Arithmetik besonders teuer, weil viele Operationen über Software-Routinen abgewickelt werden. Das kann mehrere Kilobyte Flash kosten, vor allem wenn Sie umfangreiche Formatierung (z. B. Serial.print() von floats) nutzen.
Fixed-Point statt Float: Messwerte effizient darstellen
Viele Sensorwerte lassen sich als Ganzzahl mit Skalierung abbilden, etwa „Temperatur in 0,01 °C“ oder „Spannung in mV“. So vermeiden Sie float-Rechnungen und erhalten trotzdem präzise Werte. Die Skalierung ist leicht erklärbar:
Beispiel: Sie speichern die Spannung in Millivolt. Dann ist S = 1000, und Wert_scaled ist eine Ganzzahl. Ausgabeformatierung erledigen Sie über Integer-Division und Rest, statt über float-Printf.
Formatierung schlank halten: printf-Overhead und String-Ketten
Komfortable Formatierung kann riesigen Overhead erzeugen. Besonders printf-artige Funktionen (je nach Setup) ziehen umfangreiche Formatierungsroutinen ins Binary. Auch langes „Zusammenbauen“ von Strings kann Code aufblasen.
- Keine unnötigen Formatierer: Nutzen Sie einfache Ausgaben statt komplexer Formatstrings.
- Kurze, strukturierte Logs: Schlüssel-Wert-Ausgaben mit festen Tokens sind oft kleiner als freie Textsätze.
- Wiederholte Phrasen vermeiden: Lieber Codes oder kurze Labels, statt jedes Mal ganze Sätze.
Statt Text: Codes und Tabellen
Wenn Sie Statusmeldungen ausgeben, sparen Sie Flash, wenn Sie statt langer Texte kurze Codes senden und diese im PC-Tool interpretieren. Beispiel: „E03“ statt „Sensor nicht gefunden“. Das ist nicht „schöner“, aber sehr effektiv, wenn Flash knapp ist.
Schlanke Protokolle und effiziente Datendarstellung
Viele Projekte übertragen Daten seriell, über Funk oder I2C. Wenn Sie dabei ASCII-Text senden, kostet das sowohl Flash (Formatierung) als auch Laufzeit. Binäre Protokolle können Flash sparen, wenn sie einfach aufgebaut sind.
- Binärrahmen: Startbyte, Länge, Payload, einfache Prüfsumme
- Feste Strukturen: Vermeiden Sie teure Parser für variable Textformate
- CSV nur, wenn nötig: CSV ist praktisch, aber nicht immer der kleinste Weg
Prüfsummen minimalistisch rechnen
Eine einfache XOR-Prüfsumme kostet sehr wenig Code und ist für viele „Debug“-Protokolle ausreichend:
Code-Struktur: Wiederverwendung ohne Overhead
Ein häufiger Irrtum: „Wiederverwendung“ spart immer Flash. In Wirklichkeit kann ungeschickte Abstraktion Flash erhöhen, wenn Sie viele kleine Funktionen erzeugen, die der Compiler nicht effizient optimiert, oder wenn virtuelle Methoden/Polymorphie ins Spiel kommen. Auf AVR ist ein pragmatischer Stil oft am kleinsten: klare C-ähnliche Funktionen, wenig dynamische Konstrukte, sparsame Nutzung von Templates.
- Kurze Hot-Path-Funktionen: Nicht unnötig aufsplitten, wenn es keinen Strukturgewinn gibt
- Inline bewusst einsetzen: Inline kann Flash erhöhen, wenn es Code dupliziert
- Keine virtuellen Klassen, wenn nicht nötig: VTables und indirekte Calls kosten Platz
Switch-Case vs. lange if-Ketten
Bei Zustandsautomaten kann switch je nach Fallzahl effizienter sein als lange if/else-Ketten, vor allem wenn der Compiler eine Sprungtabelle erzeugt. Das ist nicht garantiert, aber oft ein sinnvoller Ansatz für klaren und kompakten Code.
Lookup-Tabellen statt teurer Berechnungen
Rechenintensive Funktionen (Trigonometrie, komplexe Filter, nichtlineare Kennlinien) kosten Flash und Laufzeit. Häufig ist eine Lookup-Tabelle kleiner und schneller. Die Tabelle legen Sie idealerweise in PROGMEM ab. Das spart nicht nur CPU, sondern verhindert auch, dass Sie große mathematische Routinen ins Binary ziehen.
- Gamma-Korrektur: Häufig als 256-Byte-Tabelle
- Sensor-Kennlinien: Diskrete Stützstellen, lineare Interpolation
- Winkel/Sinus: In vielen Anwendungen reichen 90°-Tabellen plus Symmetrie
Konstanten, Typen und Datenbreiten: Kleine Typen, große Wirkung
Der ATmega328P ist ein 8-bit Mikrocontroller. Wenn Sie überall int oder gar long verwenden, ist das nicht automatisch falsch, aber es kann Flash kosten, weil größere Typen oft größere Operationen nach sich ziehen. In vielen Fällen reicht uint8_t oder uint16_t. Das ist nicht nur SRAM-sparsam, sondern kann auch Codepfade verkleinern.
- uint8_t: Für Zähler, Indizes, Flags, kleine Werte
- uint16_t: Für ADC-Werte, Timer, moderate Bereiche
- uint32_t: Für millis()-Logik oder größere Zähler, wenn nötig
Bitfelder und Flags statt bool-Arrays
Statt viele boolsche Variablen oder Arrays zu nutzen, können Sie Flags in einem Byte bündeln. Das spart SRAM und kann in manchen Fällen auch Flash sparen, weil Ihre Logik kompakter wird.
Dead Code eliminieren: Features modular abschalten
In Projekten, die „gewachsen“ sind, bleibt häufig Code zurück, der nicht mehr genutzt wird: alternative Sensorpfade, alte Debug-Menüs, Prototyp-Routinen. Das kostet Flash und erschwert Optimierung. Ein konsequentes „Feature Toggling“ schafft Ordnung.
- Feature-Flags: Kompilierzeit-Schalter für Module (z. B. DISPLAY, BLE, LOGGING)
- Konfiguration zentralisieren: Eine Datei für alle Optionen statt verteilter #defines
- Unbenutztes löschen: Nicht nur auskommentieren, sondern entfernen oder in Branch auslagern
Compiler-Optimierungen bewusst nutzen: -Os statt „irgendwas“
Die Arduino-Toolchain nutzt in der Regel bereits Optimierungen, die auf geringe Größe abzielen. Dennoch lohnt es sich, die Build-Konfiguration zu kennen, insbesondere wenn Sie außerhalb der Standard-IDE arbeiten (PlatformIO, Makefiles). Das Flag -Os optimiert üblicherweise auf Codegröße und ist auf AVR häufig die beste Wahl. Zusätzlich kann „Link-Time Optimization“ (LTO) erhebliche Einsparungen bringen, weil der Linker ungenutzte Teile über Modulgrenzen hinweg entfernt.
- -Os: Optimiert auf Größe
- LTO: Entfernt ungenutzten Code effektiver, in vielen Setups standardmäßig aktivierbar
- Garbage Collection of Sections: Hilft, nicht genutzte Funktionen zu verwerfen
Wenn Sie sich tiefer einarbeiten möchten, ist die avr-libc-Dokumentation eine gute technische Grundlage, weil sie AVR-spezifische Besonderheiten erklärt (avr-libc Dokumentation).
Interrupts, Timer und Libraries: Versteckte Flash-Kosten erkennen
Timer- oder Interrupt-Bibliotheken wirken klein, ziehen aber oft Hilfsfunktionen, Initialisierungen und Abstraktionslayer nach sich. Ein typisches Beispiel sind Bibliotheken, die mehrere Timer-Backends unterstützen: Sie zahlen für Portabilität, auch wenn Sie nur einen Timer nutzen.
- Einfach halten: Nutzen Sie bei Bedarf direkte Registerkonfiguration statt schwerer Frameworks
- Nur einen Timer-Ansatz: Mischen verschiedener Timer-Libraries erhöht Overhead
- ISR kurz halten: Lange ISR-Logik zieht Code in Bereiche, die schwer zu optimieren sind
Menüs, UI und Texte: Datengetrieben statt hardcodiert
Viele Flash-Probleme entstehen, weil Menüs und UI-Logik „hart verdrahtet“ wurden: viele Funktionen, viele Strings, viele if/else-Strukturen. Ein datengetriebener Ansatz kann Flash sparen: Eine Tabelle beschreibt Menüeinträge, und eine generische Engine rendert oder verarbeitet sie. Der Trick: Die Engine muss wirklich generisch und klein bleiben, sonst gewinnt man nichts.
- Menüeinträge als Daten: ID, Label (PROGMEM), Handler-Funktion
- Kurze Labels: Abkürzungen statt Sätze
- Wiederholte Patterns: Ein Codepfad statt vieler ähnlicher Funktionen
Wenn es trotzdem knapp bleibt: Priorisieren statt „noch mehr Tricks“
Wenn Sie nach den großen Hebeln immer noch knapp über der Grenze sind, hilft selten ein weiterer „kleiner Trick“, sondern eine bewusste Priorisierung. Fragen Sie sich: Welche Funktion ist wirklich nötig? Was kann zur Laufzeit konfiguriert werden statt fest im Code zu liegen? Was kann auf den PC ausgelagert werden (z. B. umfangreiche Textausgaben, Formatierung, Statistik)?
- Ausgaben reduzieren: Codes statt Texte, PC-Interpretation statt Mikrocontroller-Text
- Features optional machen: Kompilierzeit-Schalter und klare Profile (Lite/Full)
- Speicherintensive Module austauschen: Bibliothek wechseln oder minimalen Treiber selbst schreiben
Praxis-Checkliste: In welcher Reihenfolge Sie Flash sparen
- Debug-Ausgaben abschalten: Compile-Time-Flags, kurze Logs, F()-Makro konsequent nutzen.
- Boardprofil prüfen: Richtige Pro-Mini-Variante (8 MHz/16 MHz) wählen, damit keine indirekten Overheads entstehen.
- Floating Point minimieren: Fixed-Point, Integer-Skalierung, keine float-Formatierung.
- Bibliotheken auditieren: Nur benötigte Features, schlankere Alternativen, unnötige Module entfernen.
- PROGMEM einsetzen: Tabellen und Texte in Program Space, Zugriff über pgmspace.
- Codepfade straffen: Dead Code löschen, Zustandslogik vereinfachen, redundante Funktionen reduzieren.
- Build-Optimierung prüfen: -Os und ggf. LTO aktiv, ungenutzte Sections entfernen lassen.
Wenn Sie diese Schritte konsequent anwenden, erreichen Sie in vielen realen Pro-Mini-Projekten eine spürbare Entlastung des Flash-Speichers, ohne „unsauber“ zu werden. Der Schlüssel ist weniger ein einzelner Trick als eine klare Prioritätenliste: erst Debug und Datenhaltung, dann Bibliotheken und numerische Darstellung, danach Struktur und Build-Optimierung. So wird effizienter Code für 32 KB Flash-Speicher nicht zu einer einmaligen Rettungsaktion, sondern zu einem reproduzierbaren Entwicklungsstil.
IoT-PCB-Design, Mikrocontroller-Programmierung & Firmware-Entwicklung
PCB Design • Arduino • Embedded Systems • Firmware
Ich biete professionelle Entwicklung von IoT-Hardware, einschließlich PCB-Design, Arduino- und Mikrocontroller-Programmierung sowie Firmware-Entwicklung. Die Lösungen werden zuverlässig, effizient und anwendungsorientiert umgesetzt – von der Konzeptphase bis zum funktionsfähigen Prototyp.
Diese Dienstleistung richtet sich an Unternehmen, Start-ups, Entwickler und Produktteams, die maßgeschneiderte Embedded- und IoT-Lösungen benötigen. Finden Sie mich auf Fiverr.
Leistungsumfang:
-
IoT-PCB-Design & Schaltplanerstellung
-
Leiterplattenlayout (mehrlagig, produktionstauglich)
-
Arduino- & Mikrocontroller-Programmierung (z. B. ESP32, STM32, ATmega)
-
Firmware-Entwicklung für Embedded Systems
-
Sensor- & Aktor-Integration
-
Kommunikation: Wi-Fi, Bluetooth, MQTT, I²C, SPI, UART
-
Optimierung für Leistung, Stabilität & Energieeffizienz
Lieferumfang:
-
Schaltpläne & PCB-Layouts
-
Gerber- & Produktionsdaten
-
Quellcode & Firmware
-
Dokumentation & Support zur Integration
Arbeitsweise:Strukturiert • Zuverlässig • Hardware-nah • Produktorientiert
CTA:
Planen Sie ein IoT- oder Embedded-System-Projekt?
Kontaktieren Sie mich gerne für eine technische Abstimmung oder ein unverbindliches Angebot. Finden Sie mich auf Fiverr.

