yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
ESP32 Core Board📖
1. ESP32 Introduction
  • 1. ESP32S3 Overview
  • 2. Environment Setup Introduction
  • 3. Dev Board Introduction
2. ESP-IDF Development Tutorial
1. Before Development
  • 1. Environment Setup (Windows)
  • 2. Environment Setup (Linux)
  • 3. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. DMA
  • 8. ADC
  • 9. PWM Breathing LED
  • 10. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. PSRAM External Memory Experiment
  • 5. Flash Read and Write Experiment
  • 6. USB Emulated Mass Storage Experiment
4. FreeRTOS operating system
  • 1.FreeRTOS Core Concepts Theory
  • 2.Task Creation and Management
  • 3.Queue
  • 4.Binary Semaphore
  • 5.Mutex
  • 6.Event Groups
  • 7.Software Timer
  • 8.Task Scheduler
  • 9.Low Power Mode
  • 10.Deadlock
  • 11.Priority Inversion
5. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. Socket Programming Basics
  • 9. HTTP Client Experiment
  • 10. HTTP Server Experiment
  • 11. MQTT IoT Communication Protocol
  • 12. WIFI OTA Upgrade
  • 13. Bluetooth Fundamentals
  • 14. Bluetooth Slave Mode
  • 15. Bluetooth Master Mode
6. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
7. Xiaozhi AI Voice
  • 1. Burn Xiaozhi Firmware
  • 2. Network and Configuration
  • 3. AI Agent Settings
  • 4. Program Analysis
  • 5. Free Chat
3. Arduino Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. ADC
  • 8. PWM Breathing LED
  • 9. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication
  • 2. I2C OLED Display
  • 3. SPI Bus Communication
  • 4. Flash Read and Write
  • 5. USB Emulated Mass Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Server Experiment
  • 5. UDP Client Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT IoT Communication Protocol
  • 11. WIFI OTA Upgrade
  • 12. Bluetooth Fundamentals
  • 13. Bluetooth Slave Mode
  • 14. Bluetooth Master Mode
5. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
4. MicroPython Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
  • 3. Thonny IDE
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. TIMER_IT
  • 5. Watchdog Timer
  • 6. ADC
  • 7. PWM Breathing LED
  • 8. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. Flash Read and Write Experiment
  • 5. Print Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT
  • 11. Bluetooth Fundamentals
  • 12. Bluetooth Slave Mode
  • 13. Bluetooth Master Mode
5. Audio Applications
  • 1. I2S Audio Playback Experiment
  • 2. Repeater Experiment
Download
  • Code
  • Hardware_Info
  • 3D_Model_File
  • Software_Tools
  • VMware
  • VM_File
Download ZIP

Welcome to ESP32 Core Board repository

4. PSRAM External Memory Experiment

PSRAM External Memory Experiment


PSRAM External Memory Experiment1. PSRAM Overview2. Core Concepts2.1 ESP32-S3 Memory Classification2.2 heap_caps API Core Functions2.3 Memory Allocation Strategy2.4 Timer-Driven vs Task-Driven3. ESP32-S3 PSRAM Hardware Specifications4. Project Architecture and Flow5. Hardware Dependencies6. Source Code Details6.1 Data Structure Definition6.2 mem_poll_callback() — Timer Callback6.3 app_main() — Entry Point7. Operation Flow7.1 Build and Flash Process7.2 Normal Output8. FAQ


1. PSRAM Overview

PSRAM (Pseudo Static RAM) is an external expansion memory combining DRAM high density and SRAM ease of use. ESP32-S3 connects off-chip PSRAM through Octal SPI interface, providing additional megabytes of RAM space for the MCU.

Typical use cases:

ScenarioDescription
Image ProcessingCamera frame buffer (QVGA ~ 150KB/frame)
Audio ProcessingAudio acquisition buffer, MP3 decoding
Display BufferTFT LCD frame buffer (320×240×16b ≈ 150KB)
Large Data CacheJSON parsing, web data, sensor logs
AI / ML InferenceTensorFlow Lite Micro model weights and intermediate tensors

This project uses heap_caps API to poll and print memory usage of 4 memory types every second, helping developers understand the allocation status of various memory types on the board.


2. Core Concepts

2.1 ESP32-S3 Memory Classification

ESP32-S3's memory is divided into multiple types by physical location and access characteristics, queried through MALLOC_CAP_* flags:

Memory Typeheap_caps FlagDescription
Internal DRAMMALLOC_CAP_INTERNAL \| MALLOC_CAP_8BITOn-chip 512KB SRAM, fastest, byte-addressable
PSRAM (SPIRAM)MALLOC_CAP_SPIRAMOff-chip Octal PSRAM, large capacity (2MB/8MB), slightly slower
DEFAULTMALLOC_CAP_DEFAULTSystem default allocation strategy (internal first, use PSRAM when insufficient)
IRAM 8-bitMALLOC_CAP_IRAM_8BITByte-addressable portion of internal instruction RAM

2.2 heap_caps API Core Functions

size_t heap_caps_get_total_size(uint32_t caps);       // Total size
size_t heap_caps_get_free_size(uint32_t caps);        // Free size
// Largest free block
size_t heap_caps_get_largest_free_block(uint32_t caps);

Largest free block reflects memory fragmentation degree. When this value is much smaller than free_size, large allocation may fail even if total free is sufficient.

2.3 Memory Allocation Strategy

When calling malloc() (equivalent to heap_caps_malloc(size, MALLOC_CAP_DEFAULT)), system allocates by priority:

  1. Prefer internal DRAM

  2. Use PSRAM when internal DRAM is insufficient

  3. Return NULL when neither is available

Use heap_caps_malloc(size, MALLOC_CAP_SPIRAM) to force allocation on PSRAM; MALLOC_CAP_INTERNAL for forced internal RAM.

2.4 Timer-Driven vs Task-Driven

MethodImplementationUse Case
FreeRTOS Task + vTaskDelayIndependent stack, can call blocking APIComplex periodic tasks
esp_timer callback (this example)Lightweight, no independent stackSimple polling, log printing

Timer callback runs in interrupt context, cannot call blocking APIs (such as vTaskDelay, printf large buffer), but ESP_LOGI and other log macros are safe.


3. ESP32-S3 PSRAM Hardware Specifications

FeatureParameter
Interface TypeOctal SPI (8-wire)
Max Frequency80 MHz (DTR mode)
Typical Capacity2 MB / 8 MB (depending on board model)
Mapping MethodCPU can directly access through Cache (shares with Flash)
Enable Configurationmenuconfig → CONFIG_SPIRAM=y
Internal SRAM512 KB (about 320 KB available for user)

Note: PSRAM must be enabled in menuconfig first (CONFIG_SPIRAM=y), otherwise MALLOC_CAP_SPIRAM corresponds to 0 bytes.


4. Project Architecture and Flow

xxxxxxxxxx
app_main()
  ├─ ESP_LOGI()                    Print startup title
  ├─ heap_caps_get_total_size()    Detect if PSRAM exists
  ├─ for(4 types) ESP_LOGI()       Print initial Total/Free/LargestFree
  ├─ esp_timer_create()            Create 1Hz periodic timer
  └─ esp_timer_start_periodic()    Start timer
       │
       └─ mem_poll_callback()      Triggers once per second (interrupt context)
            ├─ heap_caps_get_total_size()       Total capacity
            ├─ heap_caps_get_free_size()        Free capacity
            ├─ Calculate used = total - free     Used amount
            ├─ heap_caps_get_largest_free_block() Max block
            ├─ ESP_LOGI()                       Print [type] four metrics
            └─ current_idx = (idx+1) % 4        Switch to next type

Polling order: SPIRAM → INTERNAL → DEFAULT → IRAM_8BIT → SPIRAM ... (cycle, 1 type/second)


5. Hardware Dependencies

Hardware Connection: None. All memory is on-chip/on-board, no external wiring required.

Required Components: esp_heap_caps, esp_timer, FreeRTOS

How to Enable PSRAM?

1.Open ESP-IDF Terminal:

Open ESP-IDF Terminal

2.Enter Settings Menu Command:

Enter menuconfig Command

3.Wait for Popup, Navigate: Component config→ESP PSRAM→Select "Support for external, SPI-connected RAM"

Popup Interface

Select ESP PSRAM

Select Support for external, SPI-connected RAM

4.Continue to: SPI RAM config→Mode(QUAD/OCT) of SPI RAM chip in use (Octal Mode PSRAM)

Select Octal Mode PSRAM

5.Select Octal Mode PSRAM

Select Octal Mode PSRAM

6.Save with "S" key, Exit with "Q" key

menuconfig Required Options:

MacroValueDescription
CONFIG_SPIRAMyMust enable, otherwise PSRAM unavailable

6. Source Code Details

6.1 Data Structure Definition

Organize 4 memory type names and heap_caps flags into a lookup table structure. Timer callback uses current_idx to poll by index:

This code defines a memory type information table, storing names and corresponding heap_caps flags for SPIRAM, INTERNAL, DEFAULT, and IRAM_8BIT. The timer callback only needs to use current_idx as index to query which memory type to query this time, making code cleaner and easier to extend.

​x
// Memory type enum
typedef enum {
    MEM_IDX_SPIRAM = 0,     // Index 0: PSRAM
    MEM_IDX_INTERNAL,       // Index 1: Internal SRAM
    // Index 2: Default allocation pool
    MEM_IDX_DEFAULT,
    // Index 3: Internal instruction RAM
    MEM_IDX_IRAM_8BIT,
    MEM_TYPE_COUNT          // Total count (4)
} mem_type_idx_t;

// Memory type info struct: name + heap_caps flags
typedef struct {
    const char *name;       // Memory type name
    uint32_t caps;          // heap_caps query flags
} mem_type_info_t;

// Lookup by index
static const mem_type_info_t mem_types[MEM_TYPE_COUNT] = {
    [MEM_IDX_SPIRAM]    = { .name = "SPIRAM",   .caps = MALLOC_CAP_SPIRAM                     },
    [MEM_IDX_INTERNAL]  = { .name = "INTERNAL", .caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT },
    [MEM_IDX_DEFAULT]   = { .name = "DEFAULT",  .caps = MALLOC_CAP_DEFAULT                    },
    [MEM_IDX_IRAM_8BIT] = { .name = "IRAM_8BIT",.caps = MALLOC_CAP_IRAM_8BIT                  },
};

// Current polling index, +1 per second, wraps at 4
static uint8_t current_idx = 0;

6.2 mem_poll_callback() — Timer Callback

This function is the timer's "heartbeat" callback, triggered once per second by hardware timer. Each trigger uses current_idx to query which memory type to query, calls heap_caps_get_* series API to get that memory's total, free, used, and largest contiguous block, prints to serial, then increments index to point to next type. This way only one memory type is polled per second, 4 seconds complete one round, cycle repeats.

xxxxxxxxxx
/**
 * @brief  Timer callback: print one memory type info per second
 *
 * @param   arg  Unused
 */
static void mem_poll_callback(void *arg)
{
    // Lookup current type
    const mem_type_info_t *info = &mem_types[current_idx];

    // Query four metrics of this memory type
    // Total size
    size_t total        = heap_caps_get_total_size(info->caps);
    // Free size
    size_t free         = heap_caps_get_free_size(info->caps);
    // Used = total - free
    size_t used         = (total >= free) ? (total - free) : 0;
    // Largest free block
    size_t largest_free = heap_caps_get_largest_free_block(info->caps);

    ESP_LOGI(TAG, "[%s] Total: %u  Free: %u  Used: %u  LargestFree: %u",
             info->name, total, free, used, largest_free);

    // Index+1, round-robin 4 types
    current_idx = (current_idx + 1) % MEM_TYPE_COUNT;
}

Four metrics: Total (total capacity of this type), Free (remaining free), Used (used), LargestFree (largest contiguous free block). LargestFree decreasing indicates increasing memory fragmentation.

6.3 app_main() — Entry Point

app_main is the main function executed after program starts. It first checks if PSRAM hardware exists and prints the result, then prints initial snapshot of 4 memory types (total, free, largest block), finally creates a 1Hz (once per second) hardware timer. After timer starts, mem_poll_callback will be automatically called every second, and main function finishes execution.

xxxxxxxxxx
/**
 * @brief  Application entry point
 */
void app_main(void)
{
    ESP_LOGI(TAG, "===== PSRAM Memory Polling Start =====");

    // Check if PSRAM is available: total > 0 means PSRAM exists and is enabled
    if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) {
        ESP_LOGI(TAG, "PSRAM detected");
    } else {
        // PSRAM not found
        ESP_LOGW(TAG, "PSRAM not detected");
    }

    // Print 4 memory type snapshots
    for (int i = 0; i < MEM_TYPE_COUNT; i++) {
        const mem_type_info_t *info = &mem_types[i];
        // Total size
        size_t total        = heap_caps_get_total_size(info->caps);
        // Free size
        size_t free         = heap_caps_get_free_size(info->caps);
        // Largest free block
        size_t largest_free = heap_caps_get_largest_free_block(info->caps);
        ESP_LOGI(TAG, "[%s] Total: %u  Free: %u  LargestFree: %u",
                 info->name, total, free, largest_free);
    }

    // Create 1Hz periodic timer, auto-calls mem_poll_callback every second
    const esp_timer_create_args_t timer_args = {
        // Callback function pointer
        .callback = &mem_poll_callback,
        // Timer name (for debugging)
        .name     = "mem_poll",
    };
    esp_timer_handle_t timer;
    // Create timer
    esp_timer_create(&timer_args, &timer);
    // Start, 1s period = 1,000,000 microseconds
    esp_timer_start_periodic(timer, 1000000);
}

7. Operation Flow

7.1 Build and Flash Process

For detailed flash process: 1. Before Development → 3. Build and Flash

7.2 Normal Output

After power-on, first print initial snapshot of all 4 memory types, then poll one per second:

xxxxxxxxxx
I (1263) MEM_POLL: ===== PSRAM Memory Polling Start =====
I (1264) MEM_POLL: PSRAM detected
I (1265) MEM_POLL: [SPIRAM] Total: 2097152  Free: 2096736  LargestFree: 2096736
I (1266) MEM_POLL: [INTERNAL] Total: 327680  Free: 289456  LargestFree: 262144
I (1267) MEM_POLL: [DEFAULT] Total: 2424832  Free: 2386592  LargestFree: 2096736
I (1268) MEM_POLL: [IRAM_8BIT] Total: 327680  Free: 289456  LargestFree: 262144

I (2268) MEM_POLL: [SPIRAM] Total: 2097152  Free: 2096736  Used: 416  LargestFree: 2096736
I (3268) MEM_POLL: [INTERNAL] Total: 327680  Free: 289456  Used: 38224  LargestFree: 262144
I (4268) MEM_POLL: [DEFAULT] Total: 2424832  Free: 2386592  Used: 38240  LargestFree: 2096736
I (5268) MEM_POLL: [IRAM_8BIT] Total: 327680  Free: 289456  Used: 38224  LargestFree: 262144

PSRAM Serial Running Diagram

Key verification points:

  • PSRAM detected confirms PSRAM enabled successfully

  • DEFAULT = INTERNAL + SPIRAM (about 2.4MB), meaning default allocation pool contains both memory types

  • LargestFree close to Free indicates memory fragmentation is controllable after system startup


8. FAQ

SymptomCauseSolution
PSRAM Total = 0CONFIG_SPIRAM not enabledmenuconfig → Component config → ESP PSRAM → Support for external, SPI-connected RAM → y
PSRAM Total = 0 (menuconfig enabled)Board has no PSRAM hardware / Octal PSRAM mode doesn't match actual chipConfirm if board has PSRAM, check Octal PSRAM mode configuration
heap_caps_malloc returns NULLTarget memory type free insufficient / severe fragmentationCheck LargestFree, if much smaller than Free means early allocation needed or reduce long-term holds
DEFAULT total not equal to INTERNAL+SPIRAMSystem reserves some internal RAM for Wi-Fi/BT protocol stackNormal phenomenon, MALLOC_CAP_DEFAULT may exclude DMA reservation area, etc.
Calling blocking API in timer callback causes exceptionTimer callback runs in interrupt contextCallback can only call ESP_LOGI and other non-blocking APIs, complex tasks use FreeRTOS Task
SPI Bus Communication Experiment
Flash Read and Write Experiment

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文