PSRAM trên ESP32 là gì: bo nào có, nhanh chậm ra sao, khi nào cần và cách cấp phát bộ nhớ
PSRAM là RAM ngoài chip của ESP32: module nào có, chậm hơn SRAM bao nhiêu, dữ liệu nào nên đặt vào PSRAM, ps_malloc và heap_caps_malloc, chương trình kiểm tra bộ nhớ trên mọi bo.
ESP32 có 520 KB SRAM trong chip, nhưng sau khi trừ phần của hệ thống và Wi-Fi, chương trình thường chỉ còn khoảng 200–300 KB, và khối liên tục lớn nhất chỉ khoảng 100 KB. Với camera, màn hình lớn, âm thanh hay nhận dạng bằng AI, chừng đó không đủ. Một số module ESP32 có thêm PSRAM: 4 hoặc 8 MB RAM nằm ngoài chip. Bài này giải thích PSRAM là gì, bo nào có, nhanh chậm ra sao, dữ liệu nào nên đặt vào PSRAM, cách cấp phát, và một chương trình kiểm tra bộ nhớ chạy được trên cả bo có và không có PSRAM.
1. PSRAM là gì

PSRAM (pseudo-static RAM) là chip RAM rời, nối với ESP32 qua bus SPI bốn đường giống bus của flash. Nhờ bộ nhớ đệm (cache), chương trình đọc ghi PSRAM như RAM thường, không cần lệnh đặc biệt. Nhưng mỗi lần dữ liệu không có sẵn trong bộ nhớ đệm, CPU phải chờ đọc qua bus SPI, nên PSRAM chậm hơn SRAM trong chip nhiều lần. Như mọi RAM, dữ liệu trong PSRAM mất khi mất điện.
| Module, bo | PSRAM |
|---|---|
| ESP32-WROOM-32 (ESP32 DevKit V1 30 chân, 38 chân) | Không có |
| ESP32-WROVER, ESP32-CAM | 4 MB (một số loại 8 MB, ESP32 đời đầu dùng được 4 MB một lúc) |
| ESP32-S3 (ví dụ module N16R8) | 2–8 MB, loại 8 MB thường là PSRAM tám đường (OPI) nhanh hơn |
| ESP32-C3 | Không có |
Trên module WROVER và ESP32-CAM, PSRAM chiếm GPIO16 và GPIO17: hai chân này không dùng cho việc khác được, kể cả làm UART2.
2. Nhanh chậm ra sao

Với vùng dữ liệu lớn hơn bộ nhớ đệm, PSRAM chậm hơn SRAM trong chip khoảng 10–20 lần. Khi chương trình đọc đi đọc lại một vùng nhỏ, bộ nhớ đệm giữ sẵn dữ liệu nên khoảng cách nhỏ hơn nhiều. Vì vậy PSRAM hợp với dữ liệu lớn, được xử lý tuần tự (ảnh, âm thanh, bộ đệm màn hình), không hợp với vòng lặp tính toán nặng trên dữ liệu nhỏ.
3. Dữ liệu nào để ở đâu

Hàm ngắt không nên đọc ghi PSRAM: lúc đang ghi flash, bộ nhớ đệm bị tắt và PSRAM tạm thời không truy cập được. Với ESP32 đời đầu, nhiều ngoại vi dùng DMA (I2S, SPI tốc độ cao) cũng cần bộ đệm nằm trong SRAM; thư viện camera của ESP32-CAM đã tự lo việc này.
4. Cấp phát bộ nhớ trong PSRAM
- Bật PSRAM: với bo "ESP32 Dev Module" chọn Tools → PSRAM → Enabled; bo "AI Thinker ESP32-CAM" đã bật sẵn; ESP32-S3 chọn đúng loại QSPI hoặc OPI theo module. Gói esp32 3.x tự dò PSRAM lúc khởi động,
psramFound()cho biết kết quả. ps_malloc(n)(Arduino) hoặcheap_caps_malloc(n, MALLOC_CAP_SPIRAM)(ESP-IDF): cấp phát chắc chắn trong PSRAM.heap_caps_malloc(n, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT): cấp phát chắc chắn trong SRAM trong chip.malloc()thường: với cấu hình của gói esp32, khối lớn hơn 4 KB tự được đặt vào PSRAM khi có PSRAM; khối nhỏ ở lại SRAM.- Biến toàn cục lớn (mảng khai báo sẵn) vẫn nằm trong SRAM; muốn dùng PSRAM thì cấp phát lúc chạy.
ESP.getFreeHeap() chỉ cho biết tổng còn trống; khi cần một khối lớn, xem thêm heap_caps_get_largest_free_block(): bộ nhớ bị chia nhỏ thì tổng còn nhiều mà vẫn không cấp được một khối lớn.
5. Chương trình kiểm tra bộ nhớ
Chương trình chạy được trên mọi bo. Trên ESP32 DevKit V1 (không PSRAM), nó in bộ nhớ trong còn bao nhiêu và thử cấp phát 200 KB liên tục. Trên bo có PSRAM, nó đo thêm tốc độ ghi, chép của hai loại bộ nhớ, cấp phát và kiểm tra một khối 2 MB, và cho biết malloc(100 KB) rơi vào đâu.
// PYWORLD.VN - PSRAM trên ESP32: kiểm tra có PSRAM không, xem bộ nhớ trong và PSRAM còn bao nhiêu, cấp phát khối lớn, đo tốc độ ghi đọc
// Chạy được trên mọi bo: DevKit V1 (module WROOM-32, không có PSRAM) chỉ in phần bộ nhớ trong; ESP32-CAM, WROVER, S3 có PSRAM thì đo thêm
// Không cần thư viện ngoài
#include <esp_heap_caps.h>
const size_t KHOI_DO = 64 * 1024; // 64 KB để đo tốc độ
const int SO_LAN = 50;
// Đo tốc độ ghi (memset) và chép (memcpy) trên một vùng nhớ, trả về MB/s.
void doTocDo(const char *ten, uint8_t *vung, uint8_t *dich) {
uint32_t t = micros();
for (int i = 0; i < SO_LAN; i++) memset(vung, i, KHOI_DO);
float ghi = (float)KHOI_DO * SO_LAN / (micros() - t); // byte/µs = MB/s
t = micros();
for (int i = 0; i < SO_LAN; i++) memcpy(dich, vung, KHOI_DO);
float chep = (float)KHOI_DO * SO_LAN / (micros() - t);
Serial.printf(" %-12s ghi %6.1f MB/s, chep %6.1f MB/s\n", ten, ghi, chep);
}
void inBoNho() {
Serial.printf("Bo nho trong: con %u byte, khoi lien tuc lon nhat %u byte\n",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
if (psramFound()) {
Serial.printf("PSRAM: tong %lu byte, con %lu byte, khoi lon nhat %lu byte\n", (unsigned long)ESP.getPsramSize(),
(unsigned long)ESP.getFreePsram(), (unsigned long)ESP.getMaxAllocPsram());
}
}
void setup() {
Serial.begin(115200);
delay(500);
inBoNho();
if (!psramFound()) {
Serial.println("Khong co PSRAM (module WROOM). Thu cap phat 200 KB lien tuc trong bo nho trong:");
void *p = malloc(200 * 1024);
Serial.println(p ? " Duoc" : " That bai: bo nho trong khong co khoi lien tuc lon nhu vay");
free(p);
return;
}
// Cấp phát rõ ràng: heap_caps_malloc chọn loại bộ nhớ; ps_malloc là cách viết gọn của Arduino cho PSRAM
uint8_t *trong = (uint8_t *)heap_caps_malloc(KHOI_DO, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *trong2 = (uint8_t *)heap_caps_malloc(KHOI_DO, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
uint8_t *ngoai = (uint8_t *)ps_malloc(KHOI_DO);
uint8_t *ngoai2 = (uint8_t *)ps_malloc(KHOI_DO);
if (!trong || !trong2 || !ngoai || !ngoai2) {
Serial.println("Khong cap phat duoc vung do toc do");
return;
}
Serial.println("Toc do (64 KB x 50 lan):");
doTocDo("RAM trong", trong, trong2);
doTocDo("PSRAM", ngoai, ngoai2);
free(trong);
free(trong2);
free(ngoai);
free(ngoai2);
// Một khối 2 MB: bộ nhớ trong không bao giờ có, PSRAM thì có
size_t lon = 2 * 1024 * 1024;
uint8_t *khoiLon = (uint8_t *)ps_malloc(lon);
if (khoiLon) {
for (size_t i = 0; i < lon; i++) khoiLon[i] = i & 0xFF;
uint32_t tong = 0;
for (size_t i = 0; i < lon; i++) tong += khoiLon[i];
Serial.printf("Khoi 2 MB trong PSRAM: ghi va doc lai, tong = %lu (dung: %lu)\n", (unsigned long)tong,
(unsigned long)(lon / 256 * (255 * 256 / 2)));
free(khoiLon);
}
// malloc thường: khối lớn hơn 4 KB tự được đặt vào PSRAM khi có (cấu hình của gói esp32)
void *m = malloc(100 * 1024);
Serial.printf("malloc(100 KB) nam o %s\n", esp_ptr_external_ram(m) ? "PSRAM" : "bo nho trong");
free(m);
inBoNho();
}
void loop() {}
6. Kết quả mong đợi

Trên DevKit V1, cấp phát 200 KB thất bại dù tổng bộ nhớ trống gần 300 KB: SRAM trong chip được chia thành nhiều vùng, không vùng nào đủ lớn. Đây là lý do các dự án camera, màn hình lớn cần module có PSRAM. Với các dự án cảm biến, điều khiển, gửi dữ liệu thông thường, ESP32 DevKit V1 không có PSRAM là đủ dùng.
7. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
psramFound() trả về false trên bo có PSRAM | Chọn sai bo; chưa bật PSRAM; chọn sai QSPI/OPI trên ESP32-S3 | Chọn đúng bo, bật PSRAM đúng loại |
| ESP32-CAM báo "Camera init failed", thiếu bộ nhớ | Không có PSRAM hoặc chưa bật, khung ảnh quá lớn | Chọn bo AI Thinker ESP32-CAM; giảm độ phân giải |
| Cấp phát khối lớn thất bại dù heap còn nhiều | Bộ nhớ bị chia nhỏ | Cấp phát khối lớn ngay đầu chương trình; dùng PSRAM |
| Chương trình chậm hẳn khi chuyển dữ liệu sang PSRAM | Vòng lặp tính toán trên dữ liệu ở PSRAM | Chép phần đang xử lý vào SRAM rồi tính |
| UART2 hoặc GPIO16, 17 không hoạt động trên WROVER | Hai chân này nối với PSRAM | Dùng chân khác cho UART2 |
| Khởi động lại khi ngắt đọc dữ liệu | Dữ liệu của ngắt nằm trong PSRAM | Để dữ liệu dùng trong ngắt ở SRAM |
8. Xem thêm
- ESP32 DevKit V1 30 chân và 38 chân: module WROOM-32, không PSRAM, đủ cho phần lớn dự án cảm biến, điều khiển.
- Bài ESP32-CAM trong loạt module phổ biến: bo có PSRAM phổ biến nhất.
- Bài bảng phân vùng flash và bài so sánh ESP32, S3, C3, C6 trong cùng loạt ESP32 nâng cao.
Xem thêm