ESP32-WROVER-B のSPI-SRAMを試してみる

2019-08-10TechEmbedded, ESP32, ESP32-WROVER-B, HW, Japanese

ついに8MBのSPI-SRAMを搭載した技適マーク付きのESP32-WROVER-Bが日本でも買えるようになりました。

mrubyを存分に動かすには、ESP32-WROOM2では不足を感じていたので、発売を期待して待っていました。
というわけで、スイッチサイエンスさんから早速購入してみました。

ESP32-WROVER-B
ESP32-WROVER-B

今は技術書典の準備とかでじっくり触ることはできないので、まず主題のSRAMの拡張についてだけ確認してみました。

WROVER用のDevkitボードなどはまだないので1、簡単にソフトの書き込みが確認できる環境を作ってみました。

WROOMのDevkitの回路図を参考2に、3.3V電源とリセット関係のスイッチだけ実装しました。

ESP32-WROVER-B test borad2
ESP32-WROVER-B test borad2
ESP32-WROVER-B test borad1
ESP32-WROVER-B test borad1
Testing ESP32-WROVER-B
Testing ESP32-WROVER-B

今は書き込みのたびにボタン押してDLモードへ変更必要ですが、あとでDTR,RTS使った自動モード切り替えにも対応しておこうと思います。

とりあえずWROOMと同じ手順でHelloWorldを書き込んでみると、無事起動して、シリアルに文字列が出力されていることを確認できました。

そして、SPI PSRAMを有効にするために、make menuconfig で必要な設定を行います。
使っているESP-IDFのバージョンは ESP-IDF v3.2-dev-728-ge54f3d96 です

“Component config”>”ESP32-specific”>”Support for external, SPI-connected RAM” を有効にします。
詳細はこんな感じです。

ESP-IDF config2
ESP-IDF config2
ESP-IDF config1
ESP-IDF config1

だいたいそのままでよいかと思いますが、”SPI RAM access method” は、”Make RAM allocatable using malloc() as well” としておくと、特別に意識せずともmalloc()でSPI SRAMを利用するようになるようです。

実際メモリが確保できるのかどうか、下のようなプログラムを走らせてみます。


#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

void task2(void *pvParameters){
  int i=0;
  char* mems[100];
  int total=0;
  for (int i = 0; i < 100; i--) {
    int size = 500*1024;
    mems[i] = malloc(size);
    printf(" Alloc hea2p %d [byte] = %p\n",size,mems[i]);
    if(mems[i]==NULL)break;
    total+=size;
    printf(" Total size2 = %d [Kbyte]\n",total/1024);
  }

  while(true) {
    printf("Loop in Core%d Task2[%d]\n",xPortGetCoreID(), i);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    i++;
  }
}

void app_main()
{
  printf("Hello world!\n");
  
  /* Print chip information */
  esp_chip_info_t chip_info;
  esp_chip_info(&chip_info);
  printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ",
         chip_info.cores,
         (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
         (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
  
  printf("silicon revision %d, ", chip_info.revision);
  
  printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
         (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
  
  int i=0;
  char* mems[100];
  int total=0;
  for (int i = 0; i < 100; i--) {
    int size = 500*1024;
    mems[i] = malloc(size);
    printf(" Alloc heap %d [byte] = %p\n",size,mems[i]);
    if(mems[i]==NULL)break;
    total+=size;
    printf(" Total size = %d [Kbyte]\n",total/1024);
  }
  
  xTaskCreatePinnedToCore(task2, "task2", 4096, NULL, 1, NULL, 1);
  
  while(true) {
    printf("Loop in Core%d Task1[%d]\n",xPortGetCoreID(), i);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    i++;
  }
  
  printf("Restarting now.\n");
  fflush(stdout);
  esp_restart();
}

結果はこちらのようになります。

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0010,len:4
load:0x3fff0014,len:6140
load:0x40078000,len:9504
load:0x40080400,len:6100
entry 0x40080764
I (174) boot: ESP-IDF v3.2-dev-728-ge54f3d96 2nd stage bootloader
I (175) boot: compile time 02:09:24
I (175) boot: Enabling RNG early entropy source…
I (195) boot: SPI Speed : 40MHz
I (209) boot: SPI Mode : DIO
I (221) boot: SPI Flash Size : 4MB
I (234) boot: Partition Table:
I (245) boot: ## Label Usage Type ST Offset Length
I (268) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (291) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (314) boot: 2 factory factory app 00 00 00010000 00100000
I (338) boot: End of partition table
I (351) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x09e50 ( 40528) map
I (467) esp_image: segment 1: paddr=0x00019e78 vaddr=0x3ffb0000 size=0x02200 ( 8704) load
I (489) esp_image: segment 2: paddr=0x0001c080 vaddr=0x3ffb2200 size=0x00000 ( 0) load
I (491) esp_image: segment 3: paddr=0x0001c088 vaddr=0x40080000 size=0x00400 ( 1024) load
I (518) esp_image: segment 4: paddr=0x0001c490 vaddr=0x40080400 size=0x03b80 ( 15232) load
I (582) esp_image: segment 5: paddr=0x00020018 vaddr=0x400d0018 size=0x15d64 ( 89444) map
I (776) esp_image: segment 6: paddr=0x00035d84 vaddr=0x40083f80 size=0x09f68 ( 40808) load
I (882) esp_image: segment 7: paddr=0x0003fcf4 vaddr=0x400c0000 size=0x00000 ( 0) load
I (884) esp_image: segment 8: paddr=0x0003fcfc vaddr=0x50000000 size=0x00000 ( 0) load
I (953) boot: Loaded app from partition at offset 0x10000
I (954) boot: Disabling RNG early entropy source…
I (965) spiram: SPI RAM mode: flash 40m sram 40m
I (971) spiram: PSRAM initialized, cache is in low/high (2-core) mode.
I (993) cpu_start: Pro cpu up.
I (1005) cpu_start: Starting app cpu, entry point is 0x40081154
I (1) cpu_start: App cpu up.
I (6590) spiram: SPI SRAM memory test OK
I (6593) heap_init: Initializing. RAM available for dynamic allocation:
I (6594) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (6612) heap_init: At 3FFB32D8 len 0002CD28 (179 KiB): DRAM
I (6631) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (6651) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (6671) heap_init: At 4008DEE8 len 00012118 (72 KiB): IRAM
I (6690) cpu_start: Pro cpu start user code
I (6705) spiram: Adding pool of 4096K of external SPI memory to heap allocator
I (187) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (188) spiram: Reserving pool of 32K of internal memory for DMA/internal allocations
Hello world!
This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash
Alloc heap 512000 [byte] = 0x3f80001c
Total size = 500 [Kbyte]
Alloc heap 512000 [byte] = 0x3f87d020
Total size = 1000 [Kbyte]
Alloc heap 512000 [byte] = 0x3f8fa024
Total size = 1500 [Kbyte]
Alloc heap 512000 [byte] = 0x3f977028
Total size = 2000 [Kbyte]
Alloc heap 512000 [byte] = 0x3f9f402c
Total size = 2500 [Kbyte]
Alloc heap 512000 [byte] = 0x3fa71030
Total size = 3000 [Kbyte]
Alloc heap 512000 [byte] = 0x3faee034
Total size = 3500 [Kbyte]
Alloc heap 512000 [byte] = 0x3fb6b038
Total size = 4000 [Kbyte]
Alloc heap 512000 [byte] = 0x0
Loop in Core0 Task1[0]
Alloc hea2p 512000 [byte] = 0x0
Loop in Core1 Task2[0]
Loop in Core0 Task1[1]
Loop in Core1 Task2[1]
Loop in Core0 Task1[2]

4MB分mallocしたところで、それ以上はメモリ確保できなくなっていることがわかります。

Alloc heap 512000 [byte] = 0x3fb6b038
Total size = 4000 [Kbyte]
Alloc heap 512000 [byte] = 0x0

これは、ESP32の仕様によるものです。
ESP32のTechnical Reference Manualには次のように書かれています。

1.3.3 External Memory
The ESP32 can access external SPI flash and SPI SRAM as external memory. Table 4 provides a list of external memories that can be accessed by either CPU at a range of addresses on the data and instruction buses. When a CPU accesses external memory through the Cache and MMU, the cache will map the CPU’s address to an external physical memory address (in the external memory’s address space), according to the MMU settings. Due to this address mapping, the ESP32 can address up to 16 MB External Flash and 8 MB External SRAM.

26.3.2.2 External Memory
Accesses to the external flash and external SPI RAM are done through a cache and are also handled by an
MMU. This Cache MMU can apply different mappings, depending on the PID of the process as well as the CPU
the process is running on. The MMU does this in a way that is similar to the internal memory MMU, that is, for
every page of virtual memory, it has a register detailing which physical page this virtual page should map to
https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf

External memory address mapping
External memory address mapping

テーブルを見ると、External SRAMのサイズは4MBとなっています。でもWROVER-Bには8MBのSPI SRAMが乗っています。これは、ESP32のMMUが一度にマッピングできるSPI SRAMの最大が4MBであるためです。
記述を見ると、PIDでマッピング先を切り替えて、残りの4MBにもアクセスできるように見えたので、テストコードでは別コアで動かすタスク(task2)上でmalloc()を呼んでみています。
しかし、別タスク上でも4MB以上のメモリを確保することに失敗しています。

4MBは使えないのか?と思ってForumを少し調べてみると、関係していそうなコメントがいくつか見つかりました。

Re: ESP-WROM-32 + SPIRAM is now possible
Postby WiFive » Fri Apr 20, 2018 9:10 pm

Nice. I think it is possible to give each cpu access to separate external 4MB memory regions so 8 total, but they access it through same address space. Mmu/cache handles the mapping.

Re: ESP-WROM-32 + SPIRAM is now possible
Postby ESP_Sprite » Sun Apr 22, 2018 12:35 am

FWIW, WiFive is correct (as usual). You could either give both CPUs different address spaces, or do something like bank switching using the MMU. Unfortunately, ESP-IDF doesn’t support either option out-of-the-box, though.

https://www.esp32.com/viewtopic.php?f=12&t=5450&hilit=external+SPIRAM#p23667

リファレンスにあるように、CPUごとにアドレス空間を振り分けたり、バンク切り替えのような形でマッピングを切り替えることは可能だけれども、ESP-IDFではサポートしていない模様。

Re: QUESTION – ESP32-D2WD + External ESP-PSRAM64
Postby kmakhil90 » Thu Sep 06, 2018 11:16 am

Hi,

Any update on this PSRAM64 support in idf?

Re: QUESTION – ESP32-D2WD + External ESP-PSRAM64
Postby ESP_Sprite » Fri Sep 07, 2018 2:04 am

There’s an update waiting to be integrated in master. Note that the upper 4MiB are only supported using a bankswitching scheme, they’re not accessible using normal malloc()/free() like the lower 4MiB are.

https://www.esp32.com/viewtopic.php?f=12&t=6957&hilit=external#p30347

最新の中の人のコメントによると、近い内にESP-IDFに更新がありそう。
ただし、PIDを見て自動で切り替わるわけではなく、バンク切り替え的な方法でしかアクセスできないようなので、8MBフルに使おうと思ったとき、アプリ側でメモリの配置先を意識して設計する必要がありそうです。
ESP-IDFに更新あれば確認してみましょう。

追記(2019/08/10)

最新のESP-IDFではhimem APIが実装されており、4MB以上の外付けメモリ空間にもアクセスできるようになっていました。

https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/himem.html