Доброго здравия, уважаемые читатели!
ESP32 имеет несколько сотен килобайт внутренней оперативной памяти RAM, расположенной на том же самом кристалле, что и процессор и остальные компоненты микросхемы. Для некоторых задач этого объема может быть недостаточно, поэтому разработчики предусмотрели возможность подключения к SoC дополнительной внешней псевдо-статической оперативной памяти PSRAM (Pseudo-static RAM).
Под термином “внешняя” здесь понимается то, что в большинстве случаев микросхема дополнительной PSRAM находится вне кристалла SoC, и подключена к микроконтроллеру посредством высокоскоростной шины SPI или её вариантов (в зависимости от серий SoC это может быть SPI, Dual SPI, Quad SPI или Octal SPI (OPI)). То есть точно так же, как и микросхема FLASH-памяти. Поэтому её также часто называют SPI RAM.
Как правило, микросхема дополнительной PSRAM установлена в корпусе модуля (того самого, который накрыт жестяной крышечкой с маркировкой). Но сейчас производитель также предлагает довольно много вариантов SoC, где PSRAM уже интегрирована на кристалле чипа и находится в одном и том же чипе, что и сам процессор. Но при этом её работа всё равно ничем не отличается от внешней памяти, даже выводы, занятые под интерфейс SPI RAM, точно также не доступны пользователю.
В контексте документации Espressif термины PSRAM и SPI RAM (а также SPIRAM) часто используются как взаимозаменяемые синонимы для обозначения одного и того же типа внешней памяти.
PSRAM hardware – аппаратная поддержка внешней памяти
Прежде чем разбираться с программными API, наверное необходимо хотя бы в общих чертах понимать, что такое PS RAM, как она подключается и адресуется, и чем она отличается от встроенной памяти. Хотя, если честно, это не строго обязательно, и вы можете смело пропустить всю эту техническую лабубу лабуду и переходить к следующему разделу.
Принцип работы PSRAM и её особенности
PSRAM (Pseudo-Static Random Access Memory) — это тип оперативной памяти, который используется в контроллерах Espressif (таких как ESP32, ESP32-S3, ESP32-P4 и т.д.) для расширения доступного объема оперативной памяти, когда встроенной памяти SRAM недостаточно для сложных задач, например, обработки графики или звука [Support for External RAM ESP32-S3; Enabling External PSRAM].
Название «псевдо-статическая» (Pseudo-Static) обусловлено сочетанием технологий двух разных типов памяти:
- Внутренняя структура — DRAM (Dynamic RAM): Физически PSRAM состоит из ячеек динамической памяти. В отличие от обычной статической памяти (SRAM), DRAM требует постоянного «обновления» (refresh) заряда ячеек, иначе данные будут потеряны.
- Интерфейс и управление — как SRAM: Память называется «псевдо-статической», потому что она содержит встроенную аппаратную логику, которая автоматически управляет процессом обновления ячеек. Для процессора это выглядит так, будто он работает с обычной статической памятью, которой не нужны внешние контроллеры для регенерации заряда.
PSRAM стоит дешевле, чем SRAM того же объема, так как плотность упаковки ячеек DRAM выше. Она подключается через высокоскоростные последовательные интерфейсы (SPI, QSPI, Octal SPI) и отображается в виртуальное адресное пространство процессора через блок управления памятью.
Доступ к внешней памяти PSRAM осуществляется процессором CPU не непосредственно (как для “собственной” RAM), а используя кэш (Cache), который взаимодействует с блоком управления памятью (MMU). MMU отображает виртуальные адреса процессора на физические адреса внешней памяти PSRAM. В упрощенном виде взаимосвязь между PSRAM, MMU и CPU можно описать следующим образом:
- CPU обращается к виртуальному адресу памяти.
- MMU (Memory Management Unit) сопоставляет этот адрес с физическим адресом во внешней памяти (PSRAM или FLASH).
- Кэш (Cache) выступает посредником:
- Если данные уже имеются в кэше (cache hit), CPU получает их мгновенно.
- Если данных нет (cache miss), контроллер кэша инициирует запрос необходимых данных к внешней памяти PSRAM через шину SPI/QPI, после чего MMU копирует их из PSRAM и помещает к кэш. Затем процессор уже может считать данные из кэша.
- И наоборот, когда CPU изменяет какие-либо данные в кэше, MMU копирует обновленное содержимое обратно в PSRAM.
В новых сериях (S3, P4) кэш поддерживает более сложные стратегии, такие как Write-back (обновление внешней памяти только при вытеснении грязной линии из кэша).
В качестве примера можно привести схему взаимодействия CPU, MMU и внешней памяти для современных вариантов ESP32 (конкретно это изображение справедливо для ESP32-S3):
Само собой, на все это копирование данных “туда-сюда” требуется дополнительное время, которое зависит от конкретного типа шины передачи данных, размера буфера кэша и некоторых других факторов. Поэтому первое, что следует помнить – доступ к внешней PSRAM всегда осуществляется медленнее, чем к обычной встроенной оперативной памяти. Особенно это характерно для сравнительно устаревшей “классической” серии ESP32 с Quad SPI и небольшим кэшем.
Про что следует помнить, если вы используете PSRAM в своих разработках:
- Напряжение питания памяти: чипы PSRAM бывают рассчитаны на напряжение питания 1.8 В и 3.3 В. Напряжения питания PSRAM должно совпадать с напряжением питания FLASH-памяти, иначе компоненты могут быть повреждены или попросту не работать. Как мы все помним, напряжение питания FLASH и PSRAM определяется с помощью определенного Strapping Pins и eFuse – поэтому будьте особо внимательны перед тем, как задействовать соответствующий вывод в проекте. Некорректный уровень на соответствующем GPIO при старте SoC может повредить память или отключить её, даже если вы просто купили готовый модуль с уже установленной PSRAM. Strapping pins отличаются в разных сериях чипов, поэтому рекомендую ознакомиться с технической документаций.
- Стек задач FreeRTOS по умолчанию всегда размещается во внутренней RAM, так как это гораздо быстрее и гарантированно надежнее. Размещение стека задач в PSRAM не рекомендуется, но тем не менее это технически возможно посредством использования функции
xTaskCreateStatic(), но при этом требует применения особых мер предосторожности [Design Considerations Memory]. - Производительность: Как я уже упомянул выше, производительность PSRAM всегда меньше внутренней памяти из-за использования шины SPI и работы через MMU и кэш.
Особенности современных серий ESP32-Sx, ESP32-Px и т.д.:
Для различных серий SoC Espressif используются разные типы последовательных шин и механизмы управления памятью (MMU), что напрямую влияет на поддерживаемые режимы работы, пропускную способность и максимальный объем адресуемой памяти.
- Прямой доступ (DMA): На современных сериях ESP, периферия может обращаться к PSRAM напрямую через DMA, минуя кэш. Это может вызвать проблемы когерентности данных (когда в кэше CPU находятся старые данные, а в PSRAM — новые, записанные через DMA) [Memory Synchronization ESP32-P4].
- Разделение кэшей: В большинстве современных серий ESP используются раздельные кэши для инструкций (
ICache) и данных (DCache) [Technical Reference Manual ESP32-S3]. - Арбитраж: Если инструкции и данные запрашиваются одновременно, специальный арбитр определяет приоритет доступа к кэшу или внешней памяти. [Technical Reference Manual ESP32-S3].
Рассмотрим эти отличия немного подробнее.
ESP32
Классическая серия SoC ESP32 поддерживает подключение до 8 МБ внешней PSRAM [ESP32 Technical Reference Manual; PSRAM FAQ]. Для обмена данными с памятью на ESP32 используется шина QSPI (Quad SPI, в ней используется 4 линии данных, что позволяет передавать только 4 бита за один такт шины). PSRAM при этом делит линии данных и тактирования с внешней flash-памятью, но использует отдельную линию выбора чипа (CS) [External SPIRAM; Using PSRAM with Custom ESP32 Hardware].
Максимальный размер адресного пространства (окна кэширования), которое может быть одновременно отображено в виртуальную память процессора, для ESP32 составляет максимум 4 МБ (диапазон адресов процессора 0x3F80_0000 — 0x3FBF_FFFF) [Datasheet; Programmers’ Memory Model, External SPIRAM]. Для “классического” ESP32 (оригинальная серия) размер кэша для работы с внешней памятью (и FLASH и PSRAM) составляет 32 КБ для каждого из двух ядер (PRO CPU и APP CPU).
Если на вашем модуле установлено 8 МБ PSRAM, то напрямую («прозрачно») доступны только первые 4 МБ. Чтобы использовать оставшуюся память, необходимо выполнить следующие условия:
- Включить опцию
CONFIG_SPIRAM_BANKSWITCH_ENABLEв menuconfig [CONFIG_SPIRAM_BANKSWITCH_ENABLE]. - Использовать Himem API для переключения банков (bank switching). Это позволяет динамически отображать блоки по 32 КБ из физической памяти в зарезервированные окна виртуального адреса [Himem Overview].
Режимы виртуальной адресации
В классической серии ESP32 режим работы MMU (Memory Management Unit) для внешней памяти (PSRAM) определяется настройками регистров управления кэшем и зависит от того, как распределяются ресурсы между двумя ядрами процессора (PRO_CPU и APP_CPU). Согласно техническому руководству ESP32, существует три основных режима работы виртуальной адресации для внешней RAM [ESP32 Technical Reference Manual]:
1. Нормальный режим (Normal Mode)
В этом режиме виртуальное адресное пространство может иметь разные сопоставления с физическими страницами для каждого из двух ядер.
- PRO_CPU использует свои записи MMU (таблица
LVAddrRAM) для отображения 4 МБ адресного пространства (0x3F80_0000~0x3FBF_FFFF). - APP_CPU использует свои записи MMU (таблица
RVAddrRAM) для того же диапазона адресов. -
Это позволяет двум ядрам обращаться к разным физическим областям памяти через один и тот же виртуальный адрес [ESP32 Technical Reference Manual].
2. Режим Low-High (Low-High Mode)
Этот режим используется, когда обоим ядрам нужно разделять одни и те же физические страницы во внешней памяти.
- Виртуальное пространство 4 МБ делится пополам:
- Нижние 2 МБ (
0x3F80_0000~0x3F9F_FFFF) управляются записями MMU из таблицыLVAddrRAM(PRO_CPU). - Верхние 2 МБ (
0x3FA0_0000~0x3FBF_FFFF) управляются записями MMU из таблицыRVAddrRAM(APP_CPU).
- Нижние 2 МБ (
- При этом верхние 64 записи в
LVAddrRAMи нижние 64 записи вRVAddrRAMне используются [ESP32 Technical Reference Manual].
3. Режим Чет-Нечет (Even-Odd Mode)
Вся внешняя память разбивается на небольшие фрагменты (чанки) по 32 байта для оптимизации доступа.
- Четные чанки управляются записями MMU из таблицы
LVAddrRAM(PRO_CPU). - Нечетные чанки управляются записями MMU из таблицы
RVAddrRAM(APP_CPU). - Обычно записи в обеих таблицах настраиваются на одни и те же значения, чтобы виртуальные страницы отображались в непрерывную физическую память. Этот режим может дать прирост производительности при одновременном и частом обращении обоих ядер к внешней памяти [ESP32 Technical Reference Manual].
Эти режимы настраиваются программно через биты в регистрах управления кэшем:
- Для PRO_CPU: биты
DPORT_PRO_DRAM_HLиDPORT_PRO_DRAM_SPLITв регистреDPORT_PRO_CACHE_CTRL_REG. - Для APP_CPU: биты
DPORT_APP_DRAM_HLиDPORT_APP_DRAM_SPLITв регистреDPORT_APP_CACHE_CTRL_REG[ESP32 Technical Reference Manual].
Важное замечание: Если кэш ядра APP_CPU отключен, использование режимов меняется. Например, в режиме High-Low оба ядра смогут использовать только 2 МБ внешней RAM, а режим Even-Odd в такой конфигурации использовать внешнюю память вообще не рекомендуется [ESP32 Technical Reference Manual].
Важные ограничения для разработчика
- Запрет на стек задач: PSRAM нельзя использовать в качестве стека для задач (Task Stack) обычным способом через
xTaskCreate(), так как это может привести к сбоям при операциях с флэш-памятью. Стек должен находиться в более быстрой внутренней RAM [Design Considerations]. - Доступ через DMA отсутствует: Периферийные устройства, использующие DMA (например, Wi-Fi, Bluetooth или SPI), в классической ESP32 не могут напрямую читать или писать в PSRAM [Support for External RAM ESP32]. Данные необходимо сначала копировать во внутреннюю RAM.
- CONFIG_SPIRAM_CACHE_WORKAROUND: Для классической ESP32 критически важно держать эту опцию включенной (она активна по умолчанию), чтобы избежать известных аппаратных проблем при одновременном обращении к PSRAM и другим ресурсам [Design Considerations].
ESP32-S2
ESP32-S2 обладает рядом специфических аппаратных особенностей при работе с PSRAM (псевдо-статической оперативной памятью), которые отличают его от оригинального ESP32.
Как и прежде, на ESP32-S2 внешняя PSRAM подключается к той же самой шине SPI0/1 (часто называемой MSPI), что и основная FLASH-память, и работает с ней параллельно. PSRAM делит с FLASH-памятью линии данных (I/O) и тактового сигнала (Clock), но использует отдельную линию выбора кристалла CS (Chip Select). В контроллере SPI0/1 для Flash обычно используется сигнал SPICS0, а для PSRAM — SPICS1. Поддерживаются режимы Single SPI, Dual SPI, Quad SPI (QSPI) и Octal SPI (OSPI/OPI). Например, в 4-битном режиме (Quad SPI) используются сигналы SPID, SPIQ, SPIWP и SPIHD [Pin Mapping Between Chip and Flash or PSRAM; SPI Controller (SPI); Enabling External PSRAM].
Для работы с PSRAM используются контроллеры SPI0 и SPI1. SPI0 предназначен для доступа через кэш и EDMA, а SPI1 используется процессором для прямого доступа. Кроме того, ESP32-S2 поддерживает передачу данных через DMA напрямую в/из PSRAM через контроллер GP-SPI2, что экономит внутреннюю память, так как не требует промежуточного копирования [Transactions with Data on PSRAM].
ESP32-S2 поддерживает до 10.5 МБ виртуальных адресов для внешней PSRAM, которые отображаются в карту памяти процессора [Support for External RAM]. В отличие от ESP32, ESP32-S2 позволяет обращаться к внешней RAM как через шину данных, так и через шину инструкций. Это означает, что программы могут выполняться напрямую из PSRAM [ESP32’s family Memory Map 101]. Через высокоскоростной кэш возможны одновременные отображения:
- до 7.5 МБ пространства инструкций (flash/RAM),
- до 4 МБ read‑only данных (flash/RAM),
- до 10.5 МБ read‑write данных (RAM; блоками по 64 КБ).

Кэш внешней памяти является 4-канальным частично-ассоциативным (4-way set associative). Для ESP32-S2 размер кэша для внешней памяти (и Flash, и PSRAM) является настраиваемым и разделен на кэш инструкций (ICache) и кэш данных (DCache). В отличие от классического ESP32, эти кэши независимы. Размер кэша инструкций (ICache) может быть настроен на 8 КБ или 16 КБ, размер кэша данных (DCache) может быть настроен на 0 КБ, 8 КБ или 16 КБ [Cache config]. Через эти кэши ESP32-S2 может одновременно отображать до 7.5 МБ пространства инструкций и до 10.5 МБ пространства данных во внешнюю PSRAM [External Memory Address Mapping].
ESP32-S3
ESP32‑S3 аппаратно поддерживает подключение внешней FLASH и PSRAM по следующим интерфейсам: SPI, Dual SPI, Quad SPI, Octal SPI, QPI, OPI; и для них поддерживается аппаратное шифрование/дешифрование XTS‑AES для защиты кода и данных, размещаемых во внешней памяти. [ESP32-S3 Technical Reference Manual, Datasheet 4.1.2.2]. PSRAM подключается параллельно SPI‑flash, используя те же линии данных и тактирования, но отдельный сигнал CS chip‑select (см. схемотехнические рекомендации и таблицы соответствия выводов для in‑package / off‑package PSRAM). [Hardware guide flash/psram]
Тип шины зависит для модулей производства Espressif от объема дополнительной PSRAM – до 2 МБ включительно используется Quad SPI, выше – Octal SPI:
По адресному пространству ESP32‑S3 может адресовать до 1 ГБ внешней FLASH или PSRAM памяти, но тут следует понимать, что это теоретический предел по адресации, а не типичный физический объём чипов. Через высокоскоростной кэш одновременно может быть отображено:
- до 32 МБ пространства инструкций (instruction bus) во внешнюю flash или RAM;
- до 32 МБ пространства данных (data bus) во внешнюю RAM (чтение/запись) или во flash/RAM только для чтения. [ESP32-S3 Technical Reference Manual 4.3.3.1]
ESP32‑S3 имеет общий кэш для двух ядер:
Кэш обслуживает обращения как к внешней flash, так и к внешней PSRAM. [ESP32-S3 Technical Reference Manual 4.3.3.1, табл. 4.3‑2].
- Кэш инструкций (
ICache) имеет размер: 16 КБ (1 банк) или 32 КБ (2 банка); диапазон адресов0x4200_0000–0x43FF_FFFF - Кэш данных (
DCache) имеет размер: 32 КБ (1 банк) или 64 КБ (2 банка); диапазон адресов0x3C00_0000–0x3DFF_FFFF

ESP32-C5 и ESP32-C61
Аппаратная реализация доступа к PSRAM в серии ESP32-C значительно различается в зависимости от конкретного чипа. В то время как ранние модели серии не поддерживали внешнюю PSRAM, новые чипы (ESP32-C5 и ESP32-C61) получили расширенные возможности работы с внешней памятью.
- ESP32-C2, ESP32-C3 и ESP32-C6: Эти чипы не поддерживают внешние микросхемы PSRAM [ESP-FAQ].
- ESP32-C61: Поддерживает до 2 МБ PSRAM [ESP-FAQ]. В некоторых вариантах PSRAM может быть интегрирована непосредственно в корпус (in-package) с тактовой частотой до 120 МГц [ESP32-C61 Datasheet; External Memory ESP32-C61].
- ESP32-C5: Также поддерживает работу с внешней PSRAM через интерфейсы SPI, Dual SPI, Quad SPI и QPI [ESP32-C5 Datasheet, External Memory ESP32-C5].
Для чипов ESP32-C5 и ESP32-C61 доступ к PSRAM, как и всегда, осуществляется через шину SPI параллельно с FLASH-памятью: PSRAM разделяет линии данных (I/O) и тактирования (CLK) с внешней FLASH-памятью, но для PSRAM используется отдельная линия выбора кристалла (обычно SPICS1), отличная от линии для FLASH (SPICS0) [ESP32-C61 Hardware Design]. Поддерживаются режимы SPI, Dual SPI, Quad SPI, и QPI. Например, в режиме Quad SPI используются линии SIO0–SIO3 [ESP32-C5 Hardware Design].
0x4200_0000 ~ 0x43FF_FFFF в физический адрес внешней памяти. Благодаря этому отображению адресов, ESP32-C5 может адресовать до 32 МБ внешней флэш-памяти и 32 МБ внешней оперативной памяти. Memory Management Unit (MMU) отображает физическое пространство PSRAM в виртуальное адресное пространство процессора в диапазоне 0x4200_0000 ~ 0x43FF_FFFF. Благодаря этому отображению адресов, ESP32-C5 и ESP32-C61 поддерживают до 32 МБ адресного пространства данных, которое может быть отображено блоками по 16, 32 или 64 КБ [ESP32-C61 Datasheet].
ESP32-P4
Ключевые особенности данной серии:
- Интегрированная 16-строчная PSRAM: В отличие от многих других чипов, ESP32-P4 уже включает в себя 16-строчную (sixteen-line) PSRAM, которая работает на напряжении 1,8 В. Важной особенностью является то, что эта PSRAM находится внутри чипа (in-package) и не выведена на внешние выводы чипа [Datasheet v1.3; Series Datasheet].
- Высокоскоростные интерфейсы: Для связи с памятью используются специализированные интерфейсы OPI (Octal Peripheral Interface) и HPI (Hyperbus Interface), что обеспечивает значительно более высокую пропускную способность по сравнению со стандартным SPI [Technical Reference Manual].
- Объем и производительность: Чип поддерживает до 64 МБ PSRAM с максимальной тактовой частотой до 200 МГц [Datasheet v1.3].
- Двухуровневая система кэширования: Доступ к PSRAM осуществляется через продвинутую систему кэш-памяти:
- L1 Cache: включает 16 КБ кэша инструкций и 64 КБ кэша данных.
- L2 Cache: объемом от 128 КБ до 512 КБ. Данные могут отображаться в адресное пространство процессора блоками по 64 КБ [Technical Reference Manual; Series Datasheet].
- Адресное пространство: HP CPU (High-Performance CPU) может обращаться к PSRAM через кэшируемую область памяти (диапазон
0x4800_0000~0x4BFF_FFFF) или даже напрямую для отладки (диапазон0x8800_0000~0x8BFF_FFFF) [Technical Reference Manual]. - Аппаратная безопасность: ESP32-P4 поддерживает автоматическое шифрование и дешифрование данных на лету с использованием алгоритма XTS-AES, что защищает содержимое PSRAM от несанкционированного доступа [Series Datasheet].
- Питание: По умолчанию PSRAM питается от встроенного стабилизатора LDO2. Через меню конфигурации Kconfig можно изменить канал LDO или настроить использование внешнего источника питания [API Guide External RAM].
PSRAM software API – использование внешней памяти в ваших проектах
Теперь плавно переходим ко второй части Марлезонского балета, а именно поговорим о том, как же использовать внешнюю PSRAM в ваших проектах. Подключение ESP PSRAM к вашему проекту зависит от того, какой фреймворк и среду программирования вы предпочитаете: ESP-IDF, Arduino32, Zephyr и т.д.
Подключение ESP PSRAM API к проекту ESP-IDF
Компонент ESP-IDF, который “отвечает” за работу с PSRAM-памятью, называется esp_psram. И прежде чем мы сможем настраивать и использовать PSRAM в проекте, мы должны включить указанный компонент в сборку – на текущий момент документация Espressif требует делать это явно. В актуальной документации для ряда чипов (ESP32‑S2/S3, C5, C61, P4 и др.) прямо сказано:
Опции SPI RAM доступны, только если компонент esp_psram включён в сборку. Чтобы включить SPI RAM в проект, добавьте компонент esp_psram в REQUIRES или PRIV_REQUIRES при регистрации компонента через idf_component_register. [ESP32-S3 ext RAM; ESP32C5 ext RAM; ESP32C61 ext RAM; ESP32-P4 ext RAM]
Между тем, в моих текущих проектах мне всегда было достаточно “включить” эту память через menuconfig, никаких зависимостей я не указывал. А всё потому, что я в основном использовал “классическую” ESP32, а на новых ESP32-S3 задействовать PSRAM пока не было особой необходимости.
- В старых версиях ESP‑IDF (а также для классического ESP32) компонент
esp_psramбыл «вшит» в базовую конфигурацию ESP-IDF. В этом случае нет требования явно добавлятьesp_psramчерез черезidf_component_register. Поэтому во многих примерах и реальных проектах PSRAM просто включают черезmenuconfig, то есть разработчик просто заходит вComponent config → ESP PSRAM / SPI RAM configи включаетCONFIG_SPIRAM, выбирает режим/скорость и т.п. [ESP32 ext RAM; ESP32 Kconfig SPIRAM; ESP32-S3 Kconfig SPIRAM; PSRAM FAQ] - Для SoC новых серий (ESP32‑S2/S3, C5, C61, P4 и др.) это становится более формальным требованием: без включённого в сборку компонента
esp_psramменюSPI RAMвmenuconfigпросто не появится. [ESP32C5 ext RAM; ESP32C61 ext RAM] - Если вы пишете отдельный компонент, который сам по себе зависит от PSRAM‑функционала (например, библиотека, которая всегда должна иметь доступ к
heap_caps_malloc(..., MALLOC_CAP_SPIRAM)и настройкам SPIRAM), необходимо обязательно корректно указать зависимости вidf_component_register. В этом случае компонентesp_psramбудет автоматически включен в любую сборку, в которую вы включаете ваш компонент.
Подведем промежуточный итог: если вы используете классическую ESP32 или сравнительно старую версию ESP-IDF, то ничего никуда включать не обязательно. В ином случае придется предпринять дополнительные действия.
Впрочем, в любом случае явная регистрация компонента esp_psram не помешает – хуже от этого точно не будет. Поэтому давайте посмотрим, как это можно сделать.
- Если вы пишете свой компонент, то просто включите
esp_psramв файле регистрации компонентаCMakeLists.txtв любой из списков зависимостей –REQUIRESилиPRIV_REQUIRES. Например так:idf_component_register( SRCS "src/re_heap.c" INCLUDE_DIRS "include" REQUIRES heap esp_psram ) - Если вы хотите включить
esp_psramв общую сборку проекта, откройте файлmain/CMakeLists.txt(где регистрируется основной компонент проекта), и сделайте то же самое в нем, например так:idf_component_register( SRCS "main.c" INCLUDE_DIRS "." REQUIRES esp_psram )или, если вы хотите скрытую (private) зависимость:
idf_component_register( SRCS "main.c" INCLUDE_DIRS "." PRIV_REQUIRES esp_psram )
Примечание: как определить, в какой из списков зависимостей поместить esp_psram? В общем случае можно поступить так: если esp_psram будет использоваться в заголовочном файле – то он должен быть включен в “явный” список REQUIRES, если только в *.c или *.cpp файлах – можно включить в приватный список PRIV_REQUIRES.
После внесения изменений выполните команду idf.py reconfigure (или idf.py menuconfig, который тоже триггерит CMake), чтобы пересобрать зависимости. После этого в menuconfig должны появиться пункты ESP PSRAM / SPI RAM, где вы сможете включить CONFIG_SPIRAM и выбрать режим использования внешней RAM.
Подключение ESP PSRAM API к проекту Arduino32
Для фреймворка Arduino32 не существует “официальных” библиотек для работы с внешней памятью (то есть выпущенных производителем – Espressif, прим. авт.). Поэтому для доступа к PSRAM придется воспользоваться той же самой esp_psram. Только тут есть “небольшая проблемка” – в Arduino32 воспользоваться menuconfig нельзя (исключение – можно использовать платформу Arduino32 как компонент ESP-IDF, прим. авт.). А это означает, что изменить режим использования внешней RAM вы не сможете, придется с этим смириться. Поэтому мой вам совет – переходите на ESP-IDF, по сути оно не намного сложнее Arduino, а сторонние библиотеки и портировать можно.
В Arduino‑ESP32 PSRAM настраивается через меню Tools (а не через esp_psram как компонент), просто выберите соответствующую опцию PSRAM в настройках платы:
Нет нужной опции в настройках платы? Значит вы выбрали плату, которая не предусматривает варианты с установленной PSRAM! И таки да, в некоторых случаях вы должны будете указать, через какой вариант SPI-интерфейса подключен чип внешней памяти (на примере выше это Quad SPI или Octal SPI).
Для некоторых плат предусмотрена возможность указать тип и размер интегрированной в чип PSRAM (Embedded PSRAM), если таковая присутствует. Но на этом всё, больше никаких параметров PSRAM данный фреймворк не предусматривает. [Arduino PSRAM]
Подключение ESP PSRAM API к проекту PlatformIO + ESP-IDF
Если вы читали мои предыдущие статьи, посвященные проектам на платформе ESP-IDF, но с использованием “альтернативной” системы сборки PlatformIO, то наверняка помните, что довольно часто некоторые опции ESP-IDF приходилось дублировать в файле platformio.ini, дабы система сборки так же “знала” о них. Например таким образом приходится указывать различные “сторонние” файлы и ресурсы, подключаемые к проекту.
В случае с PSRAM ничего подобного на данный момент нет. PlatformIO, по большому счету, абсолютно пофиг на наличие, размер и интерфейсы дополнительной PSRAM. Всё подключение к проекту и дальнейшее конфигурирование SPI RAM производится только средствами самой ESP-IDF, как это было описано выше. Единственное отличие – конфигуратор проекта вызывается не командой idf.py menuconfig, а немного по другому – pio run -t menuconfig.
Конфигурирование ESP PSRAM
Теперь давайте рассмотрим процесс конфигурирования ESP PSRAM. Напомню ещё раз, этот раздел актуален только для тех, кто использует для разработки фреймворк ESP-IDF, в Arduino вы это сделать не сможете – поэтому если вы заядлый ардуинщик, можете смело листать дальше.
Для активации esp_psram, необходимо открыть меню menuconfig, затем пройти в меню Component config → ESP PSRAM, и включить опцию Support for external, SPI-connected RAM. После этого появится дополнительное подменю:
Предупреждение: Содержимое меню SPI RAM config сильно зависит от цели (target), то есть семейства SoC, выбранного в данный момент в проекте. На скриншотах ниже будет представлено меню для ESP32S3 и классической ESP32. Для других серий меню может отличаться.
Проваливаемся в меню SPI RAM config и для ESP32-S3 видим следующий список:
Для классической ESP32 набор опций немного отличается:
Некоторые из опций чипо-зависимы и представляют собой средства для технической настройки PSRAM или шины обмена данными с ней. Например это Mode (QUAD/OCT) of SPI RAM chip in use, Type of SPIRAM chip in use или Set RAM clock speed. Нам они в контексте данной статьи не очень интересны, поэтому остановимся только на тех опциях, что непосредственно влияют на использование PSRAM в приложениях.
Initialize SPI RAM during startup
Опция CONFIG_SPIRAM_BOOT_INIT отвечает за автоматическую инициализацию внешней оперативной памяти PSRAM на самом раннем этапе загрузки системы (startup). Инициализация включает в себя как настройку аппаратной части (контроллера PSRAM), так и конфигурацию логических параметров памяти во время загрузки. Если эта опция включена, память становится доступной сразу же. Это позволяет размещать в PSRAM данные, которые выделяются ещё до запуска основного приложения (например, статические объекты или память, выделяемая в процессе загрузки). Кроме этого, система автоматически переключает настройки Wi-Fi и сетевого стека на значения по умолчанию для работы с внешней памятью – увеличивает размеры буферов и т.д.
Рекомендуется оставлять эту опцию включенной, если только у вас нет специфических требований по ручной или отложенной инициализации внешней памяти в коде приложения. При включенной опции CONFIG_SPIRAM_BOOT_INIT вручную вызывать esp_psram_init() в app_main() не требуется. Если вы отключаете SPIRAM_BOOT_INIT, но планируете использовать стандартную инициализацию в приложении, то должны сами вызвать esp_psram_init() в вашем коде.
Ignore PSRAM when not found
Если предыдущая опция CONFIG_SPIRAM_BOOT_INIT активна, становится доступной другая опция – CONFIG_SPIRAM_IGNORE_NOTFOUND, которая определяет поведение системы в случае, если внешняя оперативная память (PSRAM) не была обнаружена во время загрузки. Данная опция полезна для создания универсальных прошивок, предназначенных для модулей как с установленной PSRAM, так и без неё.
По умолчанию, если поддержка PSRAM включена при компиляции, но чип памяти не найден при запуске, ESP-IDF вызывает панику CPU и прерывает загрузку. Включение этой опции позволяет системе продолжить загрузку даже без PSRAM. Если PSRAM не найдена при включенном игнорировании, система автоматически переключает настройки Wi-Fi и сетевого стека на значения по умолчанию для работы без внешней памяти.
Эта опция недоступна, если включена настройка CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY. Это связано с тем, что в данном режиме компоновщик (linker) назначает статические переменные по конкретным адресам во внешней памяти еще на этапе компиляции, и их отсутствие при запуске сделает работу приложения невозможной.
Run memory test on SPI RAM initialization
Опция CONFIG_SPIRAM_MEMTEST отвечает за проведение базового теста памяти при инициализации внешней PSRAM во время загрузки системы. При включении этой опции система выполняет простейшую проверку целостности внешней оперативной памяти. Если тест обнаруживает ошибки (например, проблемы с физическим подключением или повреждение ячеек), инициализация прерывается (abort), и система выдает ошибку.
Проверка памяти может существенно замедлить время старта устройства. В документации указано, что тест занимает примерно 1 секунду на каждые 4 МБ тестируемой памяти. Для ускорения запуска приложения (startup time) этот тест можно отключить. Например, в логах Zephyr RTOS для ESP32-S3 видно, что отключение теста позволяет сэкономить несколько сотен миллисекунд [Zephyr PSRAM Blog].
В большинстве конфигураций этот параметр включен по умолчанию для обеспечения надежности системы.
SPI RAM access method
Одна из ключевых опций CONFIG_SPIRAM_USE определяет, каким образом внешняя оперативная память будет интегрирована в систему и как программное обеспечение сможет её использовать. В ESP-IDF доступны три основных метода доступа:
1. Интеграция в карту памяти (CONFIG_SPIRAM_USE_MEMMAP)
В этом режиме PSRAM просто отображается в адресное пространство процессора как неуправляемый регион памяти.
- Как использовать память: Вы не сможете выделить память из этого региона через стандартные функции
malloc(). Вам необходимо вручную обращаться к известному диапазону адресов. - Для чего подходит: Для специфических задач, где требуется прямой контроль над областью памяти без участия диспетчера кучи (heap manager).
2. Выделение через heap_caps_malloc (CONFIG_SPIRAM_USE_CAPS_ALLOC)
Память интегрируется в систему управления кучей, но помечается специальным флагом capability MALLOC_CAP_SPIRAM.
- Как использовать память: Чтобы получить память в PSRAM, нужно явно вызвать функцию
heap_caps_malloc(size, MALLOC_CAP_SPIRAM). Обычный вызовmalloc()без этого флага всегда будет возвращать указатель на внутреннюю RAM. - Для чего подходит: Это самый распространенный метод, когда вы хотите точно контролировать, какие данные (например, большие буферы кадров для дисплея или аудиоданные) отправляются во внешнюю память, оставляя быструю внутреннюю память для критичных задач.
3. Полная интеграция в malloc() (CONFIG_SPIRAM_USE_MALLOC)
Внешняя память полностью объединяется с общей кучей системы.
- Как использовать память: Обычный вызов
malloc()может вернуть указатель как на внутреннюю, так и на внешнюю память. Поведение регулируется дополнительными настройками, такими какCONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL– если размер запроса превышает пороговое значение, система предпочтет выделить память в PSRAM. Если внутренняя память закончится,malloc()автоматически начнет выдавать память из PSRAM. - Для чего подходит: Для приложений, которым требуется очень много динамической памяти, а разработчик
забил на работу ине хочет вручную управлять типами памяти для каждого объекта.
Maximum malloc() size, in bytes, to always put in internal memory
Опция CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL определяет пороговый размер (в байтах) для функции malloc(), при котором предпочтение отдается выделению памяти во внутренней RAM, а не во внешней PSRAM.
Если размер запрашиваемого блока памяти меньше установленного значения, система будет пытаться в первую очередь выделить память из более быстрой внутренней оперативной памяти (Internal RAM). Если запрос больше этого значения, система предпочтет использовать внешнюю PSRAM. Но если в предпочтительном регионе (внутреннем или внешнем) недостаточно свободного места, система автоматически попытается выделить память в другом доступном регионе. Благодаря этому malloc() не завершится ошибкой сразу, если один из типов памяти заполнен.
Данная настройка позволяет оптимизировать производительность, сохраняя небольшие, часто используемые объекты во внутренней памяти (которая быстрее и не требует работы кэша), оставляя при этом внешнюю PSRAM для крупных блоков данных.
Try to allocate memories of WiFi and LWIP in SPIRAM firstly. If failed, allocate internal memory
Опция CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP отвечает за приоритетное выделение памяти для стеков Wi-Fi и LWIP (TCP/IP) во внешней PSRAM [Allocation from SPIRAM first].
При включении этой опции система сначала пытается выделить память для нужд Wi-Fi и LWIP во внешней PSRAM. Если попытка не удалась (например, из-за нехватки места), система автоматически переключится на выделение памяти из внутренней оперативной памяти. Это крайне полезно для высвобождения дефицитной внутренней DRAM, которая может понадобиться для критических задач, требующих высокой скорости доступа или DMA-совместимости. Также, когда эта опция активна, LWIP и Wi-Fi могут использовать расширенные размеры внутренних буферов, что потенциально улучшает сетевую производительность при наличии PSRAM.
Данная настройка перестает работать, если включена опция CONFIG_SPIRAM_IGNORE_NOTFOUND и чип PSRAM не был обнаружен при загрузке. В этом случае Wi-Fi и LWIP переключатся на использование стандартных (уменьшенных) буферов во внутренней памяти.
Reserve this amount of bytes for data that specifically needs to be in DMA or internal memory
Опция CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL отвечает за резервирование определенного объема внутренней оперативной памяти, который будет доступен только для специфических аллокаций, требующих размещения исключительно во внутренней RAM.
Стратегия распределения памяти между внутренней и внешней RAM не всегда идеальна. Может случиться ситуация, когда обычные вызовы malloc() полностью исчерпают внутреннюю память. Это приведет к ошибкам при попытке выделить память для критически важных задач, которые физически не могут работать из внешней PSRAM, таких как:
-
Стеки задач FreeRTOS – они принудительно размещаются во внутренней памяти.
-
Буферы для работы DMA (прямого доступа к памяти).
-
Память, которая должна оставаться доступной, когда кэш SPI отключен.
Как это работает? Память из этого зарезервированного пула не выдается при обычном вызове malloc(). Она остается «невидимой» для стандартных аллокаций. Выделение памяти из этого резерва происходит только при явном указании флагов MALLOC_CAP_DMA или MALLOC_CAP_INTERNAL через функцию heap_caps_malloc().
Примечание: Зарезервированный пул может не быть одной непрерывной областью памяти — это зависит от размера резерва и статического использования памяти приложением
Установка этого значения в 0 полностью отключает данную функцию.
Move Instructions in Flash to PSRAM
Опция CONFIG_SPIRAM_FETCH_INSTRUCTIONS отвечает за перенос исполняемого кода (секция .text) из Flash-памяти во внешнюю PSRAM во время загрузки системы. PSRAM часто работает быстрее, чем Flash (особенно в Octal-режиме), что может повысить общую производительность приложения. При включенной опции весь код, который обычно выполняется напрямую из Flash (через кэш), копируется в PSRAM. Виртуальные адреса, соответствующие инструкциям, перенаправляются на область PSRAM.
Move Read-Only Data in Flash to PSRAM
Опция CONFIG_SPIRAM_RODATA отвечает за перенос данных только для чтения (секция .rodata) из Flash-памяти во внешнюю PSRAM при запуске системы, то есть константные данные (например, строки, таблицы констант). Соответствующий диапазон виртуальных адресов для этих данных перенаправляется на PSRAM. Поскольку скорость доступа к PSRAM может быть выше, чем к Flash-памяти (особенно при использовании 8-битной шины Octal PSRAM), это может улучшить общую производительность системы в приложениях с высокой пропускной способностью
Если эта опция включена вместе с SPIRAM_FETCH_INSTRUCTIONS, то функции и данные, к которым обращаются прерывания (ISR), больше не обязательно размещать во внутренней памяти (IRAM/DRAM). Это позволяет им продолжать работу во время операций записи или стирания Flash, так как доступ к ним осуществляется через PSRAM без необходимости отключения кэша.
Enable Executable in place from (XiP) from PSRAM feature
Опция CONFIG_SPIRAM_XIP_FROM_PSRAM включает функцию «Выполнение на месте» (Execute In Place, XiP) для запуска кода из внешней оперативной памяти PSRAM. Она является комбинацией предыдущих двух опций и предназначена для оптимизации производительности и использования памяти в системах с внешней PSRAM. При запуске системы секции .text (инструкции/код) и .rodata (данные только для чтения), которые обычно находятся во Flash-памяти, копируются в PSRAM.
Кроме этого, конфигуратор позволяют перенести секции .bss и .noinit из внутренней оперативной памяти во внешнюю. Перенос данных в PSRAM освобождает место для критически важных задач, которым требуется высокая скорость доступа или которые не могут работать из внешней памяти.
API esp_psram
Заголовочный файл компонента <esp_psram.h> совсем не обязательно непосредственно использовать в проекте. Он содержит объявление всего трех функций:
- Инициализация PSRAM
esp_psram_init()/** * @brief Initialize PSRAM interface/hardware. * * @return * - ESP_OK: On success * - ESP_FAIL: PSRAM isn't initialized successfully, potential reason would be: wrong VDDSDIO, invalid chip ID, etc. * - ESP_ERR_INVALID_STATE: PSRAM is initialized already */ esp_err_t esp_psram_init(void);
Эту функцию необходимо вызвать прежде, чем вы начнете использовать внешнюю память. Но, если включена опция
CONFIG_SPIRAM_BOOT_INIT, описанная выше, то вызывать её не нужно! Она выполнится автоматически при запуске системы. Посему про неё можно забыть для простых применений. - Проверка инициализации PSRAM
esp_psram_is_initialized()/** * @brief If PSRAM has been initialized * * @return * - true: PSRAM has been initialized successfully * - false: PSRAM hasn't been initialized or initialized failed */ bool esp_psram_is_initialized(void);
С помощью этой функции можно проверить, инициализирована ли PSRAM или нет. Если была попытка инициализации PSRAM, но результат
esp_psram_init()был не успешным (например чип внешней памяти отсутствует), то функция вернетfalse. То есть с помощью этой функции можно понять, установлен чип памяти или нет. Но, тем не менее, документация рекомендует использовать для этой цели следующую функцию. - Получить размер внешней памяти
esp_psram_get_size()/** * @brief Get the available size of the attached PSRAM chip * * @return Size in bytes, or 0 if PSRAM isn't successfully initialized */ size_t esp_psram_get_size(void);
Функция
esp_psram_get_size(void)вернет размер установленной PSRAM в байтах; или ноль, если память не установлена или не инициализирована. С помощью этой функции документация советует нам проверять, установлена PSRAM в системе или нет. И сколько конкретно.
Больше модуль <esp_psram.h> не содержит ничего интересного. По сути, если вы пишете универсальную прошивку, то понадобиться он ровно один раз – дабы проверить размер установленной внешней памяти с помощью esp_psram_get_size(). Вся остальная магия происходит уже в malloc() и <esp_heap_caps.h>.
Выделение блоков памяти в PSRAM
В файле <esp_heap_caps.h> определены битовые флаги, которые описывают “возможности” памяти, установленной в системе:
/** * @brief Флаги, указывающие на возможности различных систем памяти */ #define MALLOC_CAP_EXEC (1<<0) ///< Память должна поддерживать выполнение исполняемого кода #define MALLOC_CAP_32BIT (1<<1) ///< Память должна допускать выравнивание 32-битных данных #define MALLOC_CAP_8BIT (1<<2) ///< Память должна допускать доступ к данным с разрешением 8/16/... бит #define MALLOC_CAP_DMA (1<<3) ///< Память должна поддерживать доступ через DMA #define MALLOC_CAP_PID2 (1<<4) ///< Память должна быть отображена в адресное пространство PID2 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_PID3 (1<<5) ///< Память должна быть отображена в адресное пространство PID3 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_PID4 (1<<6) ///< Память должна быть отображена в адресное пространство памяти PID4 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_PID5 (1<<7) ///< Память должна быть отображена в адресное пространство памяти PID5 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_PID6 (1<<8) ///< Память должна быть отображена в адресное пространство памяти PID6 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_PID7 (1<<9) ///< Память должна быть отображена в адресное пространство памяти PID7 (идентификаторы процессов (PID) в настоящее время не используются) #define MALLOC_CAP_SPIRAM (1<<10) ///< Память должна находиться в SPI RAM #define MALLOC_CAP_INTERNAL (1<<11) ///< Память должна быть внутренним; В частности, она не должна исчезать при отключении кэша flash/spiram. #define MALLOC_CAP_DEFAULT (1<<12) ///< Память может быть возвращена в вызове выделения памяти, не зависящем от возможностей (например, malloc(), calloc()). #define MALLOC_CAP_IRAM_8BIT (1<<13) ///< Память должна находиться в IRAM и допускать невыровненный доступ. #define MALLOC_CAP_RETENTION (1<<14) ///< Доступ к памяти должен осуществляться с помощью DMA с сохранением данных. #define MALLOC_CAP_RTCRAM (1<<15) ///< Память должна находиться в быстрой памяти RTC. #define MALLOC_CAP_TCM (1<<16) ///< Память должна находиться в памяти TCM. #define MALLOC_CAP_DMA_DESC_AHB (1<<17) ///< Память должна быть способна содержать AHB. Дескрипторы DMA #define MALLOC_CAP_DMA_DESC_AXI (1<<18) ///< Память должна быть способна содержать дескрипторы AXI DMA #define MALLOC_CAP_CACHE_ALIGNED (1<<19) ///< Память должна быть выровнена по размеру кэш-линии любых промежуточных кэшей #define MALLOC_CAP_SIMD (1<<20) ///< Память должна быть способна использоваться для инструкций SIMD (т.е. допускать доступ к данным, специфичным для SIMD-инструкций)
Здесь нас интересует в первую очередь флаг MALLOC_CAP_SPIRAM – с помощью него мы можем указать, что хотим получить блок памяти именно в SPIRAM / PSRAM. Но на самом деле применять его не всегда нужно, при настройках по умолчанию можно обойтись и без этого.
1. Если в настройках SPIRAM выбрана опция CONFIG_SPIRAM_USE_MALLOC, а она выбрана “по умолчанию” (в том числе в ArduinoESP32), то не требуется ничего нигде указывать – просто вызовите malloc() или calloc(), указав размер желаемого блока памяти.
- Если запрашиваемый размер превышает заданный в
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL(по умолчанию это 16 КБ), то менеджер памяти попытается выделить блок памяти именно в SPIRAM. - Если запрашиваемый размер меньше или равен пороговому значению, то аллокатор сначала попытается выделить память во внутренней (internal) памяти.
- Если в приоритетном типе памяти нет подходящего блока, аллокатор автоматически попробует выделить память из другого доступного типа [Support for External RAM].
То есть всё что меньше 16 КБ – выделяется из DRAM, все что больше – из SPIRAM. Пороговый размер, разумеется, можно изменять (кроме ArduinoESP32).
#include <stdlib.h>
#include <stdio.h>
void app_main(void)
{
// Размер выделения, превышающий порог CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL, заставит аллокатор предпочесть SPIRAM.
size_t size = 1024 * 10;
void* ptr = malloc(size);
if (ptr == NULL) {
printf("Ошибка выделения памяти\n");
return;
}
// Использование памяти...
free(ptr);
}
На мой взгляд это не очень “умная” стратегия, но зато самому думать не нужно. Всё делается автоматически. Этот способ отлично подходит, например, если вы вдруг решили портировать существующий проект на новый модуль с PSRAM.
Впрочем, даже в этом случае никто не запрещает вам выделять память в SPIRAM “вручную” (даже если размер запрашиваемого блока меньше порогового значения), используя heap_caps_malloc() – см. следующий пункт. Освобождение памяти в обоих случаях производится через стандартную функцию free().
2. Если в конфигураторе задана опция CONFIG_SPIRAM_USE_CAPS_ALLOC, то в этом случае вы сами, и только сами решаете, под какие нужды какой тип памяти использовать. Для выделения блока памяти в SPIRAM вы должны вызвать функцию heap_caps_malloc() вместо стандартной malloc(), с указанием флага MALLOC_CAP_SPIRAM:
#include <stdio.h>
#include "esp_heap_caps.h"
void app_main(void)
{
size_t size = 2048; // Размер в байтах
// Выделение памяти именно во внешней RAM с использованием флага MALLOC_CAP_SPIRAM
void *ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
if (ptr == NULL) {
printf("Не удалось выделить память в SPIRAM\n");
} else {
printf("Память успешно выделена в SPIRAM по адресу: %p\n", ptr);
// ... использование памяти ...
// Освобождение памяти производится обычной функцией free()
free(ptr);
}
}
Всё, что запрашивается “обычной” malloc(), будет выделяться во внутренней памяти DRAM.
Примечание: память, выделенную через heap_caps_malloc(), можно освободить с помощью стандартной функции free() или специальной heap_caps_free().
Чем это хорошо? Все “системные” выделения памяти – буферы, стеки задач и т.д., остаются в DRAM “по умолчанию”, какого бы размера они не были. А вот выделение памяти под какие-то сравнительно “медленные” (то есть не требовательные к производительности) процессы, можно перенести и в SPIRAM.
Например в SPIRAM я часто собираю большие JSON-пакеты из отдельных составных частей – строк. Ну на самом деле – в 99.9% случаев какая разница, сколько формируется JSON – одну миллисекунду или 100 (цифры взяты совершено условно – пальцем в потолок, на реальном железе я это не измерял)?
Но тут есть несколько неудобств:
- Если вы пишете “универсальную” прошивку, которая должна работать на модулях и без PSRAM, и с оной – то вам придется каждый раз проверять это.
- Если попытка выделения памяти в SPIRAM по какой-то причине завершилась неудачно, то система не попытается выделить блок в DRAM автоматически, вам нужно будет сделать это самому (при необходимости).
Для исправления этого поведения я написал совсем крохотную библиотечку-компонент (подробности ниже).
3. Стратегия CONFIG_SPIRAM_USE_MEMMAP является наиболее сложным вариантом интеграции. В этом режиме внешняя память просто отображается (маппится) в адресное пространство данных процессора, но не добавляется в аллокатор кучи. Это означает, что функции malloc() или heap_caps_malloc() не будут использовать эту память автоматически [Integrate RAM into memory map (S3)]. При использовании этой ваше стратегии приложение само несет полную уголовную ответственность за управление всей внешней памятью, координацию использования буферов и предотвращение повреждения данных.
Так как память в этом случае не управляется кучей (heap), вам нужно знать начальный адрес и размер области PSRAM (обычно эти сведения доступны через системные константы или документацию на конкретный SoC).
#include <stdio.h>
#include <stdint.h>
#include "esp_log.h"
// Примечание: Адрес начала PSRAM зависит от конкретного чипа (например, ESP32-S3)
// и того, как ESP-IDF распределил виртуальное адресное пространство.
// Обычно для ручного управления рекомендуется использовать другие режимы,
// но в режиме MEMMAP вы работаете напрямую с адресами.
void app_main(void)
{
// В режиме MEMMAP внешняя память отображается в виртуальное пространство данных.
// Вы можете создавать указатели на эту область, если знаете её адрес.
// Пример (адреса приведены для иллюстрации):
uint32_t *external_ram_ptr = (uint32_t *)0x3C000000;
// Запись данных
external_ram_ptr[0] = 0xDEADBEEF;
external_ram_ptr[1] = 0xCAFEBABE;
// Чтение данных
printf("Данные из PSRAM: 0x%08X, 0x%08X\n", external_ram_ptr[0], external_ram_ptr[1]);
}
Важные ограничения:
- Вы не можете вызвать
free()для этой памяти, так как она не была выделена черезmalloc(). Всё что записано в память, останется тамнавсегдадо перезаписи. - Нет встроенного механизма, предотвращающего перезапись одних данных другими, если вы используете несколько буферов в одной области.
Espressif рекомендует по возможности использовать стратегии с аллокатором кучи (CONFIG_SPIRAM_USE_CAPS_ALLOC или CONFIG_SPIRAM_USE_MALLOC), так как ручное управление адресами в CONFIG_SPIRAM_USE_MEMMAP сложнее и может быть легко подвержено ошибкам.
Зато для чипов ESP32 (оригинальной серии) при наличии более 4 МБ PSRAM в этом режиме существует Himem API, который позволяет использовать “банкротство” (bank switching) для доступа к памяти свыше лимита адресного пространства [Himem API Example].
Компонент re_heap
И в заключении приведу код своего компонента, которым я пользуюсь в настоящее время. Может быть кому-то и пригодится. Он реализует такой подход:
- Если внешняя PSRAM установлена и инициализирована, то по при вызове
re_psram_malloc()вначале пытаемся выделить память в PSRAM, и только если попытка не удалась – то попытаемся выделить память в DRAM черезmalloc(). - Если внешняя PSRAM не обнаружена, то все запросы памяти перенаправляются на обычный
malloc(). - Всё, что выделяется с помощью стандартной
malloc(), в DRAM и остается – то есть все “системные” блоки остаются во внутренней памяти (кроме буферов WiFi, как мы помним).
Заголовочный файл:
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "multi_heap.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Инициализация модуля
*/
static void re_psram_init(void);
/**
* Выделяет блок памяти по возможности в SPIRAM, но если SPIRAM не доступна - то в общей куче
* @param size Размер необходимого блока памяти
* @return В случае успеха - действительный указатель на блок памяти, иначе NULL
*/
void* re_psram_malloc(size_t size);
/**
* Выделяет блоки памяти по возможности в SPIRAM, но если SPIRAM не доступна - то в общей куче
* @param count Количество необходимых блоков памяти
* @param size Размер необходимого блока памяти
* @return В случае успеха - действительный указатель на первый блок памяти, иначе NULL
*/
void* re_psram_calloc(size_t count, size_t size);
#ifdef __cplusplus
}
#endif
Код:
/* See header file for comments */
#include "sdkconfig.h"
#include "re_heap.h"
#include "esp_heap_caps.h"
#if CONFIG_SPIRAM
#include "esp_psram.h"
#endif
#if CONFIG_SPIRAM
static size_t re_psram_size = 0;
#endif
static void re_psram_init(void)
{
#if CONFIG_SPIRAM
re_psram_size = esp_psram_get_size();
#endif
}
void* re_psram_malloc(size_t size)
{
#if CONFIG_SPIRAM
if (re_psram_size > 0) {
void* addr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
if (addr != NULL) return addr;
};
#endif
return malloc(size);
}
void* re_psram_calloc(size_t count, size_t size)
{
#if CONFIG_SPIRAM
if (re_psram_size > 0) {
void* addr = heap_caps_calloc(count, size, MALLOC_CAP_SPIRAM);
if (addr != NULL) return addr;
};
#endif
return calloc(count, size);
}
CMakeLists.txt
idf_component_register(
SRCS "src/re_heap.c"
INCLUDE_DIRS "include"
REQUIRES heap esp_psram
)
Ну а на этом позвольте откланяться, с вами был Александр aka kotyara12. До новых встреч!
Пожалуйста, оцените статью:
-= Каталог статей (список по разделам) =- -= Архив статей (плитки, все подряд) =-









В более поздних версиях ESP-IDF есть возможность создать задачу в PSRAM, вызвав xTaskCreateWithCaps
Ну, возможно, не рекомендуется создавать в PSRAM. Делал это на ESP32-S3.
Ограничение такое, что нельзя из этой задачи обращаться к NVS или устанавливать OTA обновления. Обновления ставлю из задачи в SRAM, а из хранилища в PSRAM достаю/записываю в него через компонент-провайдер, который занимает место в SRAM.
Нужно было для MQTT и Web-сервера, ибо mbedTLS довольно прожорлив, хоть и указывал стратегию с выделением в PSRAM
Согласен, mbedTLS очень прожорлив до памяти. Но можно “опциями” mbedTLS поиграться. Я даже сравнение делал когда-то: https://kotyara12.ru/iot/esp32-mbedtls-heap/, можете ознакомиться на данном сайте.
Но лично мне никогда не приходилось идти на такие вот ухищрения, хотя у меня обычно и MQTT(S), и несколько HTTPS одновременно работают. Ничего, free RAM на уровне 48% остается. И это без PSRAM совсем