Zephyr下esp32s3外部psram配置细节

ESPS32S3本身有512KB的内部RAM,在内部RAM不够的情况下,可以通过配置外部PSRAM来扩展内容容量。Zephyr目前已经完整的支持ESP32S3的PSRAM。本文从使用入手,通过分析配置和实现来剖析esp32s3的PSRAM在Zephyr下的细节。

使用 链接到标题

设备树配置 链接到标题

要启用PSRAM,需要在设备树中按照PSRAM的大小配置PSRAM节点, 8M的PSRAM配置如下:

&psram0 {
	size = <DT_SIZE_M(8)>;
};

Zephyr在zephyr/dts/xtensa/espressif/esp32s3下提供esp32s3相关模块的dtsi文件其中已经根据模块配置了PSRAM节点,文件名中r后面的数字表示PSRAM的大小:

esp32s3_mini_n4r2.dtsi  esp32s3_r2.dtsi         esp32s3_wroom_n16r2.dtsi  esp32s3_wroom_n4r8.dtsi
esp32s3_mini_n8.dtsi    esp32s3_r8.dtsi         esp32s3_wroom_n16r8.dtsi  esp32s3_wroom_n8.dtsi
esp32s3_pico_n8r2.dtsi  esp32s3_r8v.dtsi        esp32s3_wroom_n4.dtsi     esp32s3_wroom_n8r2.dtsi
esp32s3_fn8.dtsi     esp32s3_pico_n8r8.dtsi  esp32s3_wroom_n16.dtsi  esp32s3_wroom_n4r2.dtsi   esp32s3_wroom_n8r8.dts

用户可以根据使用的模块在板级dts中包含dtsi文件来配置PSRAM

#include <espressif/esp32s3/esp32s3_wroom_n16r8.dtsi>

Kconfig配置 链接到标题

Kconfig配置决定是否要启用PSRAM,在prj.conf添加CONFIG_ESP_SPIRAM=y即可启用PSRAM。 默认情况下Zephyr以4线40M的PSRAM模式工作。通过下面的配置项可以修改PSRAM工作模式:

  • 连线模式2选一
    • CONFIGSPIRAM_MODE_QUAD=y 使用4线模式
  • 连线模式2选一CONFIG_SPIRAM_MODE_OCT=y 使用8线模式
  • 时钟2选一
    • CONFIG_SPIRAM_SPEED_40M=y 使用40M的时钟
    • CONFIG_SPIRAM_SPEED_80M=y 使用80M的时钟

ESP32S3的PSRAM支持ECC,开启后将会占用总量的1/16来放ECC

  • CONFIG_SPIRAM_ECC_ENABLE=y 开启ECC功能

使用PSRAM 链接到标题

配置PSRAM后Zephyr生成的代码和数据均不会直接使用PSRAM,有下面三种方式进行使用

1. XIP Flash上指令和数据搬到PSRAM 链接到标题

Zephyr支持将XIP Flash上的指令和数据搬到PSRAM上执行,以获得更高的执行速度

  • CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y 将指令从XIP Flash搬运到PSRAM
  • CONFIG_SPIRAM_RODATA=y 将只读数据从XIP Flash搬运到PSRAM

2. 在ld脚本中指定 链接到标题

在ld脚本中指定,例如lvgl和mbedtls的heap

KEEP(*(.lvgl_buf*))
. = ALIGN(16);
KEEP(*(.lvgl_heap*))
. = ALIGN(16);
KEEP(*(.mbedtls_heap*))
. = ALIGN(16);

3. 代码中使用PSRAM上的heap 链接到标题

启用PSRAM后,Zephyr会为esp32s3建立一个shared_multi_heap, 用户可以从该heap中分配到PSRAM的内存来使用

char *m_ext = shared_multi_heap_aligned_alloc(SMH_REG_ATTR_EXTERNAL, 32, BUF_SIZE);
memset(m_ext, 0, BUF_SIZE);
shared_multi_heap_free(SMH_REG_ATTR_EXTERNAL, m_ext);

PSRAM初始化过程 链接到标题

esp32s3在__esp_platform_app_start中执行PSRAM初始化过程,在z_prep_c进入到Zephyr世界前执行:

  	//初始化psram
	esp_init_psram();

  	//在psram上建立共享堆
	int err = esp_psram_smh_init();

	if (err) {
		printk("Failed to initialize PSRAM shared multi heap (%d)\n", err);
	}

esp_init_psram实现在zephyr/soc/espressif/common/esp_psram.c中,

void esp_init_psram(void)
{
    //初始化psram
	if (esp_psram_init()) {
		ets_printf("Failed to Initialize external RAM, aborting.\n");
		return;
	}

    //检查psram大小是否符合配置
	if (esp_psram_get_size() < CONFIG_ESP_SPIRAM_SIZE) {
		ets_printf("External RAM size is less than configured.\n");
	}

    //检查psram是否通过内存测试
	if (IS_ENABLED(CONFIG_ESP_SPIRAM_MEMTEST)) {
		if (esp_psram_is_initialized()) {
			if (!esp_psram_extram_test()) {
				ets_printf("External RAM failed memory test!");
				return;
			}
		}
	}

    //初始化psram的bss
	memset(&_ext_ram_bss_start, 0,
	       (&_ext_ram_bss_end - &_ext_ram_bss_start) * sizeof(_ext_ram_bss_start));
}

esp_psram_init实现在modules/hal/espressif/components/esp_psram/esp_psram.c中,主要任务是psram通过mmu到ext_iram和ext_iram内存空间的映射,如果要在psram运行指令和放置rodata,就将flash上的指令和data搬运到psram。

实现说明 链接到标题

所有与PSRAM的配置项都放在zephyr/soc/espressif/common/Kconfig.spiram中进行配置,除了ESP_SPIRAM_SIZE从设备中获取外,其他配置项都有默认值。

config ESP_SPIRAM_SIZE
	int "Size of SPIRAM part"
	default $(dt_node_int_prop_int,$(ESP32_PSRAM0_NODE_PATH),size) if $(dt_nodelabel_enabled,psram0)
	default 0
	help
	  Specify size of SPIRAM part.
	  NOTE: In ESP32, if SPIRAM size is greater than 4MB,
	  only lower 4MB can be allocated using k_malloc().

Zephyr对esp32s3的PSRAM配置初始化主要是通过zephyr/soc/espressif/common/esp_psram.c调用modules/hal/espressif/components/esp_psram/esp_psram.c中的函数实现。

指令和数据不搬运到PSRAM的情况 链接到标题