Compression with PSRAM
Decompress Once, Access Freely
The Approach
The previous guide covered compressing data in flash and decompressing it chunk by chunk into internal RAM. That works on any ESP32 but adds complexity - you have to process your data in pieces rather than having it all available at once.
If your board has PSRAM, you can do something much cleaner. Decompress everything into PSRAM at boot and access it freely for the rest of your program. No chunking, no decompressor state to manage - just a pointer to your data sitting ready in that big extra workbench.
Compress On Your Computer
The compression step is identical to the previous guide. Use the same Python script to compress your data and generate a PROGMEM header file.
import zlib, sys
with open(sys.argv[1], 'rb') as f:
data = f.read()
compressed = zlib.compress(data, level=9)
print(f"// Original size: {len(data)} bytes")
print(f"// Compressed size: {len(compressed)} bytes")
print(f"const uint32_t originalSize = {len(data)};")
print(f"const uint32_t compressedSize = {len(compressed)};")
print("const uint8_t compressedData[] PROGMEM = {")
print(', '.join(f'0x{b:02x}' for b in compressed))
print("}") Run it like this:
python3 compress.py myimage.bmp > image_data.h Decompress Into PSRAM at Boot
#include <miniz.h>
#include "image_data.h"
uint8_t* imageBuffer = nullptr;
void setup() {
Serial.begin(115200);
delay(1000);
if (!psramFound()) {
Serial.println("No PSRAM found. Use the non-PSRAM version of this guide.");
return;
}
// Allocate space in PSRAM for the decompressed data
imageBuffer = (uint8_t*) ps_malloc(originalSize);
if (imageBuffer == nullptr) {
Serial.println("PSRAM allocation failed.");
return;
}
// Copy compressed data from PROGMEM into a temporary internal RAM buffer
uint8_t* compBuf = (uint8_t*) malloc(compressedSize);
memcpy_P(compBuf, compressedData, compressedSize);
// Decompress directly into PSRAM
mz_ulong destLen = originalSize;
int result = mz_uncompress(imageBuffer, &destLen, compBuf, compressedSize);
free(compBuf);
if (result != MZ_OK) {
Serial.println("Decompression failed.");
return;
}
Serial.println("Decompression complete.");
Serial.printf("Free heap after decompression: %u bytes\n", ESP.getFreeHeap());
Serial.printf("Free PSRAM after decompression: %u bytes\n", ESP.getFreePsram());
// imageBuffer is now a normal pointer to your full decompressed data in PSRAM
Serial.printf("First byte: 0x%02x\n", imageBuffer[0]);
}
void loop() {
// Access imageBuffer freely anywhere in your code
} What Is Happening
At boot the compressed data is copied from PROGMEM into a small temporary internal RAM buffer just long enough to run the decompressor. The decompressor writes the full output directly into PSRAM. The temporary buffer is freed immediately after.
From that point on imageBuffer is just a normal pointer and you can read from it anywhere in your code without any chunking or decompressor overhead.