概要
- Heap Memory Allocation ESP-IDF Programming Guide
上記の内容を中心に、実験しながら確認してみました。
スタックとヒープ、スタティックメモリ
ESP32はArduino CoreやESP-IDFを使う場合にはFreeRTOS上で動いています。一時的なメモリはタスク作成時に割り当てられたスタックを利用します。
大きめのローカル変数を確保しようとしてエラーになるのは、スタックメモリが足りていません。一方ヒープメモリはmalloc()関数などにより確保されるメモリであり、比較的大きめのサイズで利用することが多いです。
スタティックメモリはグルーバル変数などの実行前に確保されているメモリになります。
RAMの種類
ESP32には複数種類の物理RAMが搭載されており、利用用途におうじて使い分ける必要があります。
DRAM(Data RAM)は、一番一般的なメモリでデータを保持するヒープとして使われます。IRAM(Instruction RAM)は、主にプログラムなどを保持するメモリとして使われます。汎用メモリとして使うためには32ビットアライメントでアクセスする必要があります。
そしてDRAMともIRAMとも利用可能なD/IRAMもあります。
つまり、プログラムはIRAMかD/IRAMしか利用できませんが、データは制限はありますがすべてのRAMに保存することができます。
IRAM領域にデータを保存する場合には、32ビットアライメントアクセスですので、32ビット単位で確保する必要があります。8ビットや16ビット単位でアクセスしようとすると例外が発生してしまいますので、32ビット単位で読み取って、必要ないデータをマスクするなどの処理が必要になります。
この他にSPI経由で接続されたPSRAMがあります。8MBの容量を持つ、専用RAMが販売されていますが、現在4MBまでしか利用することができません。また、接続するピンも固定されているので、基本的には自分で接続することはできないと思ったほうがいいです。
ESP32-WROVER-*などのモジュールではPSRAMが搭載されています。アクセス速度は遅いのですが、メモリをたくさん使えるようになるのでカメラなどの画像を扱う場合にはPSRAM搭載モジュールを採用している場合が多いです。
メモリ用途の種類
一般的なヒープメモリの他に、特殊用途で利用可能なメモリを確保する方法が用意されています。
DMA対応メモリは、SPIやI2Sなどにハードウエアを利用したDMA転送で使える領域のメモリです。SPI接続のPSRAMはDMA転送で利用できないので除外されます。
32ビットアライメント対応メモリは、主にIRAMのことですが通常データ領域には割り当てられないので、意図的に指定することで利用することが可能です。
外部SPIメモリはPSRAMのことです。内蔵メモリよりはアクセス速度が遅いですが、大きなメモリが必要になったときには指定することができます。
メモリ状況表示
void setup() {
Serial.begin(115200);
delay(100);
Serial.printf("===============================================================\n");
Serial.printf("Mem Test\n");
Serial.printf("===============================================================\n");
Serial.printf("esp_get_free_heap_size() : %6d\n", esp_get_free_heap_size() );
Serial.printf("esp_get_minimum_free_heap_size() : %6d\n", esp_get_minimum_free_heap_size() );
//xPortGetFreeHeapSize()(データメモリ)ヒープの空きバイト数を返すFreeRTOS関数です。これはを呼び出すのと同じheap_caps_get_free_size(MALLOC_CAP_8BIT)です。
Serial.printf("xPortGetFreeHeapSize() : %6d\n", xPortGetFreeHeapSize() );
//xPortGetMinimumEverFreeHeapSize()また、関連heap_caps_get_minimum_free_size()するものを使用して、ブート以降のヒープの「最低水準点」を追跡できます。
Serial.printf("xPortGetMinimumEverFreeHeapSize() : %6d\n", xPortGetMinimumEverFreeHeapSize() );
//heap_caps_get_free_size() さまざまなメモリ機能の現在の空きメモリを返すためにも使用できます。
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_EXEC) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_EXEC) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_32BIT) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_32BIT) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_8BIT) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DMA) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_DMA) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID2) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID2) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID4) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID4) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID5) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID5) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID6) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID6) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID7) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID7) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_SPIRAM) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_INTERNAL) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DEFAULT) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT) );
//Serial.printf("heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) );
Serial.printf("heap_caps_get_free_size(MALLOC_CAP_INVALID) : %6d\n", heap_caps_get_free_size(MALLOC_CAP_INVALID) );
//heap_caps_get_largest_free_block()ヒープ内の最大の空きブロックを返すために使用できます。これは、現在可能な最大の単一割り当てです。この値を追跡し、合計空きヒープと比較すると、ヒープの断片化を検出できます。
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_EXEC) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_EXEC) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_32BIT) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_32BIT) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DMA) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID2) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID2) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID4) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID4) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID5) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID5) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID6) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID6) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID7) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID7) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) );
//Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT): %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT) );
Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_INVALID) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_INVALID) );
//heap_caps_get_minimum_free_size()指定された機能を持つすべての領域の合計最小空きメモリを取得します。
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_DMA) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_DMA) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID2) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID2) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID4) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID4) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID5) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID5) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID6) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID6) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID7) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID7) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT) );
//Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_IRAM_8BIT) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_IRAM_8BIT) );
Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID) );
//heap_caps_get_info()multi_heap_info_t上記の関数からの情報に加えて、ヒープ固有の追加データ(割り当て数など)を含む構造体を返します。
//Skip
// heap_caps_print_heap_info()が返す情報の要約をstdoutに出力しheap_caps_get_info()ます。
//Serial.printf("heap_caps_print_heap_info(MALLOC_CAP_INTERNAL) :\n");
//heap_caps_print_heap_info(MALLOC_CAP_INTERNAL);
// heap_caps_dump()そしてheap_caps_dump_all()意志出力は、ヒープ内の各ブロックの構造に関する情報を詳述します。これは大量の出力になる可能性があることに注意してください。
//Serial.printf("heap_caps_dump() :\n");
//heap_caps_dump(MALLOC_CAP_INTERNAL);
}
void loop() {
}
上記がメモリ状況表示系の関数を呼び出したスケッチ例です。一般的なESP32ボードと、PSRAMを搭載したESP32ボードで実行してみます。
=============================================================== Mem Test =============================================================== esp_get_free_heap_size() : 288812 esp_get_minimum_free_heap_size() : 283140 xPortGetFreeHeapSize() : 288812 xPortGetMinimumEverFreeHeapSize() : 283140 heap_caps_get_free_size(MALLOC_CAP_EXEC) : 202212 heap_caps_get_free_size(MALLOC_CAP_32BIT) : 362208 heap_caps_get_free_size(MALLOC_CAP_8BIT) : 288812 heap_caps_get_free_size(MALLOC_CAP_DMA) : 288812 heap_caps_get_free_size(MALLOC_CAP_PID2) : 0 heap_caps_get_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_free_size(MALLOC_CAP_PID4) : 0 heap_caps_get_free_size(MALLOC_CAP_PID5) : 0 heap_caps_get_free_size(MALLOC_CAP_PID6) : 0 heap_caps_get_free_size(MALLOC_CAP_PID7) : 0 heap_caps_get_free_size(MALLOC_CAP_SPIRAM) : 0 heap_caps_get_free_size(MALLOC_CAP_INTERNAL) : 362208 heap_caps_get_free_size(MALLOC_CAP_DEFAULT) : 288812 heap_caps_get_free_size(MALLOC_CAP_INVALID) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_EXEC) : 113792 heap_caps_get_largest_free_block(MALLOC_CAP_32BIT) : 125024 heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) : 125024 heap_caps_get_largest_free_block(MALLOC_CAP_DMA) : 125024 heap_caps_get_largest_free_block(MALLOC_CAP_PID2) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID4) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID5) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID6) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID7) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) : 125024 heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) : 125024 heap_caps_get_largest_free_block(MALLOC_CAP_INVALID) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC) : 202192 heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT) : 356516 heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) : 283140 heap_caps_get_minimum_free_size(MALLOC_CAP_DMA) : 283140 heap_caps_get_minimum_free_size(MALLOC_CAP_PID2) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID4) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID5) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID6) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID7) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) : 356516 heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT) : 283140 heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID) : 0
上記が一般的なESP32ボードです。esp_get_free_heap_size()とxPortGetFreeHeapSize()が同じ数値を返しているので、同じ用途だと思われます。espがつくのがESP-IDFの関数で、xPortがつくのがFreeRTOSの関数ですね。どちらを使ってもよいと思いますが、汎用的なのはFreeRTOSの関数かもしれません。
メモリは確保して開放するを繰り返すと、空いている領域が飛び飛びになります。そのため実際の残メモリと、連続した一番広いメモリ領域は一致しません。freeが付いている関数は空き容量の合計で、minimumが付いている関数は実際には使えないだろうと思われる領域を除いた保守的な空き容量です。largestがついている関数は、一度に確保できる連続したメモリ領域のサイズです。
=============================================================== Mem Test =============================================================== esp_get_free_heap_size() : 4483188 esp_get_minimum_free_heap_size() : 4477056 xPortGetFreeHeapSize() : 4483188 xPortGetMinimumEverFreeHeapSize() : 4477056 heap_caps_get_free_size(MALLOC_CAP_EXEC) : 192880 heap_caps_get_free_size(MALLOC_CAP_32BIT) : 4547252 heap_caps_get_free_size(MALLOC_CAP_8BIT) : 4483188 heap_caps_get_free_size(MALLOC_CAP_DMA) : 288936 heap_caps_get_free_size(MALLOC_CAP_PID2) : 0 heap_caps_get_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_free_size(MALLOC_CAP_PID4) : 0 heap_caps_get_free_size(MALLOC_CAP_PID5) : 0 heap_caps_get_free_size(MALLOC_CAP_PID6) : 0 heap_caps_get_free_size(MALLOC_CAP_PID7) : 0 heap_caps_get_free_size(MALLOC_CAP_SPIRAM) : 4194252 heap_caps_get_free_size(MALLOC_CAP_INTERNAL) : 353000 heap_caps_get_free_size(MALLOC_CAP_DEFAULT) : 4483188 heap_caps_get_free_size(MALLOC_CAP_INVALID) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_EXEC) : 113792 heap_caps_get_largest_free_block(MALLOC_CAP_32BIT) : 4194252 heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) : 4194252 heap_caps_get_largest_free_block(MALLOC_CAP_DMA) : 124968 heap_caps_get_largest_free_block(MALLOC_CAP_PID2) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID3) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID4) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID5) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID6) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_PID7) : 0 heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) : 4194252 heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) : 124968 heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) : 4194252 heap_caps_get_largest_free_block(MALLOC_CAP_INVALID) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC) : 192860 heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT) : 4541100 heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) : 4477056 heap_caps_get_minimum_free_size(MALLOC_CAP_DMA) : 282804 heap_caps_get_minimum_free_size(MALLOC_CAP_PID2) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID4) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID5) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID6) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_PID7) : 0 heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM) : 4194252 heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) : 346848 heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT) : 4477056 heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID) : 0
PSRAMを搭載しているボードでの実験結果が上記です。PSRAMを搭載していると、空き容量が一桁違います。
数値をみていると、一番少なくて重要そうなのがDMA対応メモリです。PSRAMを利用できないので一番小さいサイズになっています。freeは288936ですが、largestが124968となっています。これはESP32のメモリ空間自体が複数に分割されているため、DMAで利用できるメモリは1個目が124968までとなります。分割してもう1個ぐらい確保できそうですが、DMA転送は連続しているメモリ領域である必要があるので利点が半減してしまいます。大きなサイズのDMA転送を行う場合には、他の用途で使われる前に、まずはDMA転送用のメモリを確保する必要がありそうですね。
DEFAULTが通常にメモリ確保したときの残容量です。数値をみてみるとDMA対応メモリとPSRAMメモリの合計になっています。32BITアライメントメモリは標準では利用しないようです。
まとめ
ざっくりとですが、ESP32のヒープメモリの管理がわかってきました。今後実際にメモリの確保をしながらどう変化するのか調べてみたいと思います。



コメント