pioarduino platform-espressif32

repository·main·Indexed 20 days ago

https://github.com/pioarduino/platform-espressif32

An optimized platform for Espressif microcontrollers featuring enhanced support for Arduino frameworks, filesystems (LittleFS, SPIFFS, FatFS), and 'Hybrid compile' capabilities for ESP32 C2 and C6. It includes a Relinker tool to optimize IRAM usage by moving selected functions to Flash, with specific configuration and verification guides for ESP32 (Classic), ESP32-C3, and ESP32-C6.

Tokens
36.4K
Snippets
112
Records
167
Agent score
71%

What's inside pioarduino-platform-espressif32

  1. Use the ESP32 Exception Decoder standalone mode

    main

    The ESP32 Exception Decoder in standalone mode decodes ESP32 crash logs (exception backtraces) using a firmware ELF file to provide human-readable function names and source code locations. It works offline and automatically detects the architecture (RISC-V or Xtensa) and toolchain binaries like addr2line and gdb.

    Note: Support for this standalone script has ended. It is recommended to use the VSCode Extension ESP32 Exception Decoder as a replacement.

    python3 filter_exception_decoder.py <elf_file> <crash_log>
  2. What is Relinker and how does it work?

    main

    The Relinker is a linker-script post-processing tool used to optimize IRAM (Internal RAM) usage on memory-constrained ESP32 chips (like the ESP32-C2). It selectively moves functions that are safe to run from Flash (i.e., not called from ISR context or during flash operations) from .iram1 sections to .flash.text sections.

    Workflow:

    1. ldgen produces a sections.ld linker script.
    2. The Relinker modifies this script based on three CSV configuration files.
    3. The modified sections.ld is then used by the final Linker (ld/gcc) to produce the ELF binary.

    To perform relocation, the Relinker requires three specific CSV files defining libraries, object files, and function relocation rules.

  3. How ESP32 Wear Leveling works for FAT filesystems

    main

    The ESP32 Arduino Core's FFat library requires FAT filesystem images to be wrapped in a Wear Leveling (WL) layer. Without this layer, mounting the filesystem will fail because the ESP-IDF function esp_vfs_fat_spiflash_mount_rw_wl() expects specific metadata.

    Wear Leveling Structure

    The image is organized into sectors as follows:

    1. Sector 0: WL State Copy 1
    2. Sector 1: WL State Copy 2
    3. Sectors 2 to N: The actual FAT filesystem data (Boot sector, FATs, Root dir, Data area).
    4. Sector N+1: A temporary sector used for WL operations.
    5. Sector N+2: WL State Copy 3
    6. Sector N+3: WL State Copy 4

    Overhead and Alignment

    • Overhead: The implementation uses a fixed overhead of 5 sectors (2 at the start, 2 at the end, and 1 temp sector).
    • Alignment: All data must be aligned to 4096-byte sector boundaries. Unused areas (reserved bytes, padding, and the temp sector) must be filled with 0xFF to represent the erased flash state.
    Total Sectors = Partition Size / Sector Size
    WL Overhead = (2 + 2 + 1) = 5 sectors
    FAT Sectors = Total Sectors - 5
  4. Understand ESP32 FAT Wear Leveling structure

    main

    The FAT filesystem is wrapped in an ESP32 wear leveling layer to protect flash memory. This layer consumes a portion of the partition size as overhead.

    Example Partition Layout: If a partition is 1,507,328 bytes (368 sectors):

    • WL overhead: 20,480 bytes (5 sectors)
    • FAT data: 1,486,848 bytes (363 sectors)

    Internal Structure: [WL State 1][WL State 2][FAT Data][Temp][WL State 3][WL State 4]

  5. How ULP-RISC-V GPIO Polling works

    main

    This example demonstrates a workflow where a ULP-RISC-V coprocessor monitors a GPIO pin while the main CPU is in deep sleep.

    Workflow:

    1. Compilation: The ULP program (written in C) is compiled, linked, and converted to binary format. It is then embedded into the .rodata section of the main ESP-IDF application.
    2. Loading: At runtime, the main CPU uses ulp_riscv_load_binary to load the program into RTC_SLOW_MEM.
    3. Execution: The main CPU configures the wakeup period and starts the coprocessor using ulp_riscv_run.
    4. Monitoring: The ULP program runs periodically. If it detects a state change on the target GPIO, it saves the state and sends a wakeup signal to the main CPU.
    5. Wakeup: The main CPU wakes up, processes the GPIO state, and can then return to deep sleep.
    /* Conceptual workflow summary */
    // 1. Load binary to RTC_SLOW_MEM
    ulp_riscv_load_binary(binary_data, size);
    
    // 2. Start coprocessor
    ulp_riscv_run();
    
    // 3. Enable wakeup and sleep
    // (Main CPU enters deep sleep here)
  6. How to Select Functions for Relinking

    main

    When deciding which functions to move to Flash, follow these guidelines to avoid runtime crashes.

    Functions that MUST stay in IRAM

    • ISRs and functions called from ISRs.
    • SPI Flash operations (spi_flash_*).
    • Functions running before Flash cache initialization.
    • FreeRTOS scheduler-critical functions.
    • Cache error handlers.
    • RTC/Sleep functions.
    • Functions with the IRAM_ATTR attribute in source code.

    Functions that CAN be moved to Flash

    • Initialization functions (called once at startup).
    • Logging functions (unless used in ISR context).
    • Memory management (if not used in ISR).
    • Non-critical library functions.
    • Configuration/setup routines.

    Identifying Candidates

    1. Check Memory Map: Examine .pio/build/<env>/firmware.map after a build.
    2. Use objdump: Use the appropriate toolchain for your chip to inspect symbols:
      • RISC-V (C2, C3, C6, H2): riscv32-esp-elf-objdump -t <path_to_lib>
      • Xtensa (ESP32, S2, S3): xtensa-esp32-elf-objdump -t <path_to_lib>
    # For RISC-V Chips (C2, C3, C6, H2)
    riscv32-esp-elf-objdump -t ~/.platformio/packages/framework-arduinoespressif32-libs/esp32c2/lib/libfreertos.a | grep xTaskGetTickCount
    
    # For Xtensa Chips (ESP32, S2, S3)
    xtensa-esp32-elf-objdump -t ~/.platformio/packages/framework-arduinoespressif32-libs/esp32/lib/libfreertos.a | grep xTaskGetTickCount
  7. How the ULP Pulse Counting Example works

    main

    This example demonstrates how to use the Ultra-Low Power (ULP) FSM coprocessor to monitor an IO pin and count pulses while the main ESP32 CPUs are in deep sleep or executing other code.

    Workflow

    1. Assembly Programming: The ULP program is written in assembly (e.g., ulp/pulse_cnt.S and ulp/wake_up.S). The build system assembles, links, and embeds this into the .rodata section of the ESP-IDF application.
    2. Loading and Starting: The main application (running on the ESP32) uses ulp_load_binary to load the program into RTC_SLOW_MEM. It configures ULP variables and starts execution using ulp_run.
    3. Monitoring: The ULP runs periodically (the period is set by the main program). It performs debouncing on the input signal and increments an edge count variable.
    4. Wakeup Trigger: When the edge count reaches a threshold (defined by the main program), the ULP triggers a wakeup from deep sleep.
    5. Data Persistence: Upon wakeup, the main program reads the pulse count from the ULP, saves it to Non-Volatile Storage (NVS), and returns to deep sleep.

    Hardware Configuration

    In this example, the input signal is connected to GPIO0 (often a button on dev boards). To use a different pin, you must update both gpio_num and ulp_io_number in main.c according to the ESP32 Chip Pin List.

    // Conceptual workflow in main.c
    ulp_load_binary();
    // ... configure ULP variables ...
    ulp_run();
    // ... enable wakeup and enter deep sleep ...
  8. How to choose functions for relocation

    main

    Relocating functions to Flash saves IRAM but carries risks. Choosing the wrong functions can cause system crashes during flash operations.

    Functions that MUST stay in IRAM

    Never move these to flash:

    • ISRs and functions called from ISRs that run with the flash cache disabled.
    • SPI flash operation callbacks (spi_flash_guard_*, spi_flash_os_*).
    • Early boot code (called before flash cache initialization).
    • FreeRTOS scheduler-critical functions (context switch, tick handler).
    • Cache error handlers.
    • RTC/sleep functions that run with flash cache disabled.
    • Any function decorated with IRAM_ATTR.

    Functions that CAN be moved to Flash

    • Initialization functions called once during startup.
    • Logging functions (unless used in an ISR context).
    • Memory allocation functions (if not used in an ISR context).
    • Non-critical library utility functions.
    • Functions guarded by CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH.

    Identification Workflow

    1. Examine the Linker Map: Check .pio/build/<env>/firmware.map to see which functions consume the most IRAM.
    2. Use objdump: List IRAM sections in a library using: riscv32-esp-elf-objdump -h <path_to_library>
    3. Test Incrementally: Move a few functions at a time, rebuild, and test. A crash during a flash operation indicates a function was moved that should have stayed in IRAM.
  9. ESP32 (Classic) Memory and Architecture Specifications

    main

    The ESP32 (Classic) is an Xtensa LX6-based dual-core chip. Understanding its memory constraints is critical when using the relinker to optimize IRAM usage.

    • Architecture: Xtensa LX6 dual-core @ 240 MHz
    • Total SRAM: 520 KB (320 KB DRAM + 200 KB IRAM after cache)
    • Static DRAM Limit: 160 KB (Technical limitation requiring relinker optimization)
    • ROM: 448 KB
    • Flash: External (typically 4 MB)
    • Bluetooth: Supports Classic BR/EDR and BLE 4.2
    • WiFi: 802.11 b/g/n (2.4 GHz)