Pythonがマイクロコントローラで利用できるようになったMicroPythonは画期的なシステムですが、メモリ容量の制約が付きまといますね。

Pythonでは、数値にしろ文字列にしろ、あるいはそれらの集合体の配列なども、ヒープ領域と呼ばれるメモリ領域から格納領域を取得し格納され操作されます。このため、ある程度のデータを処理するプログラムでは、そのデータを格納するために必要な領域をヒープ領域から確保できることが大前提となります。

Lチカに代表されるような小さく単純なプログラムでは何の問題もないのですが、小さくてもかまわないので画像処理をしようとか、配列、行列を使用した簡単な数値処理をしようと思ったとたんに、以下のような呪いの言葉が出てきて、我々の前途を阻みます。

MemoryError: memory allocation failed, allocating xxxxxx bytes

ヒープサイズ概要

新たなプログラムを作成する際の心構えとして、現在手元にあるMicroPythonの実行環境で、それぞれどの程度のヒープサイズが使用できるのか確認してみました。

MCUヒープサイズRAMPSRAM
ESP32111168520K
ESP32-WROVER4098240520K8MB
ESP32-S38196480512K8MB
ESP32-C3133568400K
ESP82663795250K
RP2040226560264K
調査結果の概要

ESP32系は、PSRAMが使えると4MBとか8MBに増えるのですが、PSRAMが使えない場合には100KBと少しという感じですね。ESP8266はさらに少なく40KB弱となっています。

ESP32系の100KBでも、配列などであからさまにメモリを使用しないアプリケーションでは、特にメモリの制約は感じないことが多いのですが、ESP8266は少し気の利いたアプリケーションを作ろうとすると、メモリが確保できないといってくることが多いようです。MicroPythonをストレスをあまり感じることなく使おうと思ったら、ESP8266系ではなく、ESP32計を使う方がよさそうです。また、ESP32のなかでもESP32-C3は、ESP8266の置き換えを想定して開発されており、他のESP32計に比べるとコストも低めなので、コストなども含めESP8266の延長線上で考えたい場合には、ESP32-C3を使うのがよいかもしれません。

Raspberry Pi Pico/RP2040は素晴らしいですね。200KBを超えるヒープ領域を使用できます。もともとMCUに乗っているRAM自体が264KBしかないのに、そのほとんどがヒープ領域に割り当てられているということですよね。これを見ると、ESP32系は内蔵RAMの多くの領域はどこに行ったのかなと思ってしまいます。実装コードを読めばよいのでしょうが、ESP32とRP2040の大きな違いは、無線機能ですので、その機能のために残りのRAMが占有されているのかもしれません。ただ、Arduinoでは、無線が使える状態で、アプリケーション側で300KB程度はRAMを使えたように思いますので、なぞです。

ESP32系の100KBとRP2040系の200KBでは、画像処理などをしようとすると全く違った世界になってしまいますね。

何のためのESP32系かといわれるかもしれませんが、もし無線機能が多量のRAMを占有しているのだとすると、無線機能を無効化して、ヒープ容量を200KBか300KBに増加させたMicroPythonの実装が出てきたら、それはそれで大いに使うことになるのではないかと思っています。

各MCUでの調査結果

ESP32: ESP32-SLIM, ESP32-KEY-R2

MicroPython v1.20.0 on 2023-04-26; ESP32 module with ESP32
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 720 out of 15360
GC: total: 111168, used: 2512, free: 108656
 No. of 1-blocks: 36, 2-blocks: 9, max blk sz: 18, max free sz: 6504
>>> 

ESP32-WROVER: ESP32-WROVER-KEY-R2

MicroPython v1.20.0 on 2023-04-26; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 720 out of 15360
GC: total: 4098240, used: 2992, free: 4095248
 No. of 1-blocks: 43, 2-blocks: 10, max blk sz: 18, max free sz: 255636
>>> 

ESP32-S3: ESP32-S3-KEY-R2

MicroPython v1.20.0 on 2023-04-26; ESP32S3 module (spiram octal) with ESP32S3
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 720 out of 15360
GC: total: 8196480, used: 2512, free: 8193968
 No. of 1-blocks: 36, 2-blocks: 9, max blk sz: 18, max free sz: 511836
>>> 

ESP32-C3-UART: ESP32-C3-SLIM, ESP32-C3-KEY

MicroPython v1.20.0 on 2023-04-26; ESP32C3 module with ESP32C3
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 1060 out of 14336
GC: total: 133568, used: 2576, free: 130992
 No. of 1-blocks: 37, 2-blocks: 9, max blk sz: 18, max free sz: 7911
>>> 

ESP32-C3-USB

ESP8266: ESP8266-SLIM, ESP8266-KEY-R2

MicroPython v1.20.0 on 2023-04-26; ESP module with ESP8266
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 2144 out of 8192
GC: total: 37952, used: 1248, free: 36704
 No. of 1-blocks: 15, 2-blocks: 6, max blk sz: 18, max free sz: 2277
>>> 

Raspberry Pi Pico

MicroPython v1.20.0 on 2023-04-26; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> import micropython
>>> import gc
>>> gc.collect()
>>> micropython.mem_info()
stack: 524 out of 7936
GC: total: 226560, used: 7024, free: 219536
 No. of 1-blocks: 97, 2-blocks: 25, max blk sz: 64, max free sz: 12715
>>>