Fix ESP32-S3 “quad_psram: PSRAM ID read error … wrong PSRAM line mode”
On ESP32-S3 the PSRAM type must match the module: OPI for N8R8/N16R8, QSPI for N8R2/N4R2. Pick the right one in Tools → PSRAM and psramFound() works.
Community draft — report a mistake
What you see
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce2810,len:0x178c
entry 0x403c8914
E (199) quad_psram: PSRAM ID read error: 0x00ffffff, PSRAM chip not found or not supported, or wrong PSRAM line mode
E (200) esp_psram: PSRAM enabled but initialization failed. Bailing out.
psramFound(): 0ESP-ROM:esp32s3-20210327 Build:Mar 27 2021 rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) SPIWP:0xee mode:DIO, clock div:1 load:0x3fce2810,len:0x178c entry 0x403c8914 E (199) quad_psram: PSRAM ID read error: 0x00ffffff, PSRAM chip not found or not supported, or wrong PSRAM line mode E (200) esp_psram: PSRAM enabled but initialization failed. Bailing out. psramFound(): 0
ESP32-S3 modules come with two kinds of PSRAM: quad (4 data lines) on R2 modules and octal (8 data lines) on R8 modules. The chip can’t tell which one is fitted. The Tools → PSRAM setting tells it, and if that setting is wrong, the PSRAM’s ID read returns garbage.
Read the module marking on the metal can, choose OPI for R8 or QSPI for R2, upload again and psramFound() returns true.
Quick fix — try in order
- 1
Match Tools → PSRAM to the module
The module code ends in R2 or R8. R8 (N8R8, N16R8, N32R8) = 8 MB octal PSRAM → select “OPI PSRAM”. R2 (N4R2, N8R2) = 2 MB quad PSRAM → select “QSPI PSRAM”. No R = no PSRAM → “Disabled”.
- 2
Seeing “octal_psram” instead?
That means OPI was selected on a quad-PSRAM module. Switch to QSPI PSRAM.
- 3
Set Flash Size too
The N number is flash in MB: N16R8 → Flash Size 16MB. A wrong flash size gives a different boot error; set both at once.
- 4
Keep GPIO35–37 free on octal modules
On N8R8/N16R8 modules GPIO35, 36 and 37 are used by the octal PSRAM. Using them in your sketch breaks PSRAM.
- 5
Confirm with the reset-reason sketch
It prints the PSRAM size it found. 8192 KB on an R8 module means the setting is right.
Diagnostic sketch
// FlashReady diagnostic: why did my ESP32 reset?
// Prints the reset reason, chip, flash and PSRAM details once at boot, then a heartbeat
// every 5 s. If the uptime keeps going back to 0, the board is rebooting - and the
// "Reset reason" line tells you which kind of reset it was.
// Works on ESP32, ESP32-C3 and ESP32-S3 (on C3/S3 native USB set Tools -> USB CDC On Boot -> Enabled).
#include <esp_system.h>
#include <esp_flash.h>
const char *reasonText(esp_reset_reason_t r) {
switch (r) {
case ESP_RST_POWERON: return "POWERON - power applied or EN/RST pressed";
case ESP_RST_EXT: return "EXT - external reset pin";
case ESP_RST_SW: return "SW - ESP.restart() or a library called esp_restart()";
case ESP_RST_PANIC: return "PANIC - crash (Guru Meditation); scroll up for the backtrace";
case ESP_RST_INT_WDT: return "INT_WDT - interrupt watchdog (an ISR or critical section ran too long)";
case ESP_RST_TASK_WDT: return "TASK_WDT - task watchdog (loop() blocked too long)";
case ESP_RST_WDT: return "WDT - other watchdog (RTC or timer group)";
case ESP_RST_DEEPSLEEP: return "DEEPSLEEP - woke from deep sleep";
case ESP_RST_BROWNOUT: return "BROWNOUT - supply dipped below ~2.4 V; fix the power";
case ESP_RST_SDIO: return "SDIO - reset over SDIO";
default: return "other / unknown";
}
}
void setup() {
Serial.begin(115200);
delay(1500); // give native-USB boards time to enumerate before printing
esp_reset_reason_t r = esp_reset_reason();
Serial.println();
Serial.println("=== FlashReady reset-reason report ===");
Serial.printf("Reset reason : %d (%s)\n", (int)r, reasonText(r));
Serial.printf("Chip : %s rev %d, %d core(s), %lu MHz\n", ESP.getChipModel(), ESP.getChipRevision(),
ESP.getChipCores(), (unsigned long)ESP.getCpuFreqMHz());
uint32_t physical = 0;
esp_flash_get_physical_size(esp_flash_default_chip, &physical);
uint32_t header = ESP.getFlashChipSize();
Serial.printf("Flash (chip) : %lu KB\n", (unsigned long)(physical / 1024));
Serial.printf("Flash (IDE) : %lu KB at %lu MHz\n", (unsigned long)(header / 1024),
(unsigned long)(ESP.getFlashChipSpeed() / 1000000));
if (physical && header > physical) {
Serial.println("WARNING : Tools -> Flash Size is larger than the real chip. Lower it.");
}
Serial.printf("PSRAM : %lu KB%s\n", (unsigned long)(ESP.getPsramSize() / 1024),
ESP.getPsramSize() ? "" : " (none found or PSRAM disabled in Tools)");
Serial.printf("Sketch : %lu KB used, %lu KB free in app partition\n",
(unsigned long)(ESP.getSketchSize() / 1024), (unsigned long)(ESP.getFreeSketchSpace() / 1024));
Serial.printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
Serial.println("======================================");
}
void loop() {
static uint32_t last = 0;
if (millis() - last >= 5000) {
last = millis();
Serial.printf("alive, uptime %lu s, free heap %lu\n", (unsigned long)(millis() / 1000),
(unsigned long)ESP.getFreeHeap());
}
}// FlashReady diagnostic: why did my ESP32 reset?
// Prints the reset reason, chip, flash and PSRAM details once at boot, then a heartbeat
// every 5 s. If the uptime keeps going back to 0, the board is rebooting - and the
// "Reset reason" line tells you which kind of reset it was.
// Works on ESP32, ESP32-C3 and ESP32-S3 (on C3/S3 native USB set Tools -> USB CDC On Boot -> Enabled).
#include <esp_system.h>
#include <esp_flash.h>
const char *reasonText(esp_reset_reason_t r) {
switch (r) {
case ESP_RST_POWERON: return "POWERON - power applied or EN/RST pressed";
case ESP_RST_EXT: return "EXT - external reset pin";
case ESP_RST_SW: return "SW - ESP.restart() or a library called esp_restart()";
case ESP_RST_PANIC: return "PANIC - crash (Guru Meditation); scroll up for the backtrace";
case ESP_RST_INT_WDT: return "INT_WDT - interrupt watchdog (an ISR or critical section ran too long)";
case ESP_RST_TASK_WDT: return "TASK_WDT - task watchdog (loop() blocked too long)";
case ESP_RST_WDT: return "WDT - other watchdog (RTC or timer group)";
case ESP_RST_DEEPSLEEP: return "DEEPSLEEP - woke from deep sleep";
case ESP_RST_BROWNOUT: return "BROWNOUT - supply dipped below ~2.4 V; fix the power";
case ESP_RST_SDIO: return "SDIO - reset over SDIO";
default: return "other / unknown";
}
}
void setup() {
Serial.begin(115200);
delay(1500); // give native-USB boards time to enumerate before printing
esp_reset_reason_t r = esp_reset_reason();
Serial.println();
Serial.println("=== FlashReady reset-reason report ===");
Serial.printf("Reset reason : %d (%s)\n", (int)r, reasonText(r));
Serial.printf("Chip : %s rev %d, %d core(s), %lu MHz\n", ESP.getChipModel(), ESP.getChipRevision(),
ESP.getChipCores(), (unsigned long)ESP.getCpuFreqMHz());
uint32_t physical = 0;
esp_flash_get_physical_size(esp_flash_default_chip, &physical);
uint32_t header = ESP.getFlashChipSize();
Serial.printf("Flash (chip) : %lu KB\n", (unsigned long)(physical / 1024));
Serial.printf("Flash (IDE) : %lu KB at %lu MHz\n", (unsigned long)(header / 1024),
(unsigned long)(ESP.getFlashChipSpeed() / 1000000));
if (physical && header > physical) {
Serial.println("WARNING : Tools -> Flash Size is larger than the real chip. Lower it.");
}
Serial.printf("PSRAM : %lu KB%s\n", (unsigned long)(ESP.getPsramSize() / 1024),
ESP.getPsramSize() ? "" : " (none found or PSRAM disabled in Tools)");
Serial.printf("Sketch : %lu KB used, %lu KB free in app partition\n",
(unsigned long)(ESP.getSketchSize() / 1024), (unsigned long)(ESP.getFreeSketchSpace() / 1024));
Serial.printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
Serial.println("======================================");
}
void loop() {
static uint32_t last = 0;
if (millis() - last >= 5000) {
last = millis();
Serial.printf("alive, uptime %lu s, free heap %lu\n", (unsigned long)(millis() / 1000),
(unsigned long)ESP.getFreeHeap());
}
}Why it happens
What does “wrong PSRAM line mode” mean?
Quad PSRAM talks over 4 data lines, octal over 8. The S3 must be told which before boot; asking an octal chip for its ID in quad mode (or the reverse) returns 0x00ffffff or 0x00000000.
My sketch runs anyway
Arduino cores continue without PSRAM, so small sketches run. Camera, large display buffers and TensorFlow Lite then fail with out-of-memory errors.
Board-specific notes
XIAO ESP32S3 (Sense): Has 8 MB octal PSRAM — select OPI PSRAM, or the camera won't start.
ESP32-CAM (classic ESP32): The original ESP32 has its own PSRAM error with different causes; see ESP32-CAM PSRAM not found.
Did this fix it?
Thanks — that helps us keep this page right.