WebAssembly をウェブブラウザーで活用する(2)

この記事はゆるWeb勉強会@札幌 Advent Calendar 2021の 3日目の記事です。

去年のアドベントカレンダーより…

自分は、去年のゆるWebアドベントカレンダーで書きました…

WebAssembly をウェブブラウザーで活用する(1)

次の回では、C/C++ もしくは Rust でかかれたプログラムを WebAssembly に移植して動作させるデモをやってみたいと思います!(続く

次の回になるまで 1年かかりました…(すいませんw

というわけで、今年の記事はここ数ヶ月取り組んでいました C/C++/Rust のプログラムを WebAssembly で動作させた顛末を例に、Wasm(WebAssembly の略です) の周辺技術事情やキーワードを書いていきたいと思います。

WebAssembly とは

WebAssembly はバイナリーの実行コード形式です。C/C++/Rust/AssemblyScript をはじめとするさまざまな言語を .wasm 拡張子のバイトコードにコンパイルすることができ、それらの言語でかかれたプログラムを、ウェブブラウザーや WebAssembly ランタイム上で動作させることができます。

画像

特にウェブブラウザーは、これまでコンピュータ言語として JavaScript のみをネイティブでサポートしていましたが、WebAssembly の仕組みを使うことで、別の言語でかかれたソフトウェアやライブラリーを活用することができるようになりました。

中でも C/C++/Rust はシステムやミドルウェアよりのライブラリーの実装が多くありますので、画像・動画処理、文字列パーサー、仮想マシン、3D 処理、ファイル圧縮展開、そして今回取り上げる音声処理など従来 JavaScript で再実装が必要になっていた部分を、WebAssembly にビルドするという手順だけで手軽にウェブブラウザーに持ち込めます。

まずは、今年2021年に気になった WebAssembly の実装をいくつか紹介します。

⏩ Adobe Photoshop の WebAssembly による実装:

おそらく画像処理部分の C/C++ コードをクライアント版と共有していると思います。

Adobe PhotoshopにWebブラウザ版が登場。何ができる?

配信中では宇宙飛行士のヘルメットガラスの反射を修正ツールで取り除き、また色味を調整するなどの内容が披露。操作に伴うレスポンス遅延等もみられない、スムーズな編集を実現していることが示された。

ちなみに、Chromium 系のブラウザーの開発者ツールに、直接 C/C++ ソースのデバッグができる仕組み(と拡張)が追加されたのは、Web 版 Photoshop をつくっていたからという話がありました。

https://twitter.com/h1romas4/status/1348242121631830017

⏩ WebAssembly で動作可能な Rust でかかれた形態素解析 Goya:

日本語形態素解析がウェブブラウザー上で動作します。形態素解析系はビルドが大変だったりしますので、.wasm 版があるのは大変ありがたいです。

WebAssemblyの形態素解析器GoyaをRustで作った

Goyaという形態素解析器を Rust で作りました。本記事は利用者目線で Goya の紹介をします。技術的な詳細については別途記事を書きます。

⏩ Python の WebAssembly 版の Pyodide:

CPython が C/C++ で実装されていることを利用して、インタープリタ・ランタイムごと Wasm にビルドし、ウェブブラウザー上で Python を実行し、導入の操作なしに Python を使うことができます。

Try Pyodide (no installation needed)

Try Pyodide in a REPL directly in your browser. For further information, see the documentation.

⏩ PHP の WebAssembly 版:

Pyodide と同様に PHP のインタープリタも C/C++ ですので Wasm にビルドしてしまえば、あろうことか PHP がウェブブラウザーで動作します。 😀

PIB: PHP in Browser (and Node.js) aka php-wasm

https://seanmorris.github.io/php-wasm/

WebAssembly で実装されたシンセサイザー

今回の Wasm 実装例とするプログラムは WebAssembly にビルドされたシンセサイザーです。

次のリンクから動作を確認できます。(後述しますがマルチスレッド(SharedArrayBuffer) を使っている関係で iOS/Safari では現在動作しません)

https://chipstream.netlify.app/

画面をクリックするとサンプルの音楽が再生されたと思いますが、この音声波形生成(シンセサイズ)を C++/Rust でかかれた WebAssembly で行っています。

シンセサイザーとは “音” を電子機器を使って生成するハードウェア・ソフトウェアの総称です。

音を電子機器でつくる方式はいろいろあり、現在は PCM と呼ばれる録音した波形をコンピュータで変化させる方式が主流で、イメージとしてはピアノのラの音(440Hz)をマイクで収録し、それをコンピュータの演算で 880Hz にして高いラにして発音させるようなことをしています。(実際にはもっと複雑です)

ここで作成したシンセサイザーは FM 音源と呼ばれるシンセサイザーの方式で、サイン波(口で発声するのなら、”ポー”みたいな電子音)をサイン波で変調することで倍音を加え、デモのような電子音のような、そうでないような美しい不思議な音を発音させることができます。

画像

1980年代に非常に多く使われたシンセサイザーの方式で、日本で一番なじみがあるのが山手線のホームで流れる発車音のエレピの音だと思います。また 1990年台のゲーム基板や、2000年台初頭のガラケーにも搭載されていましたので、ゲームセンターや着メロで聞き覚えがある方もいらっしゃるかもしれません。

WebAssembly でできること

よくWebAssembly は計算しかできないと言われますが、まずはその通りで今回のシンセサイザーの例でも波形の計算を C++/Rust で行っています。

音声波形計算の単純な例をあげると、1秒を 44100(サンプリング周波数)で割った配列をつくり、16bit(量子化ビット数)の -32768 〜 32767 の値で 440Hz のサイン波をつくれば”ポー(ラー)”波形の計算となります。このシンセサイザーに、ラを0.5秒鳴らせといった演奏データ(シーケンス)を渡すことで音楽となる手はずです。

WebAssembly が計算しかできないと言われるゆえんは、WebAssembly からホストとなる(この場合はウェブブラウザー)の機能には直接アクセスできないことで(ユーザと対話できない)、今回の場合も計算された波形はブラウザーの JavaScript を経由して WebAudio API に渡して発音してもらうようなプログラムとなっています。

ソースコードを以下に公開していますので興味がある方は覗いてみてください。

実際には ymfm という C++ でかかれた YAMAHA FM 音源チップのエミュレーションライブラリーを、演奏データの制御やサウンドチップのサンプリングレートのコンバートを行う Rust のプログラムとリンクする形で動作しています。

https://github.com/h1romas4/libymfm.wasm

This repository is an experimental WebAssembly build of the [ymfm](https://github.com/aaronsgiles/ymfm) Yamaha FM sound cores library.

使わせてもらっている ymfm ライブラリーのソースコードは一切触れることなく、そのまま WebAssembly にビルドしています。Rust と C++ をリンクして動作する Wasm の例としても見ることができるかもしれません。

その他、リアルタイム音声処理に関しては処理の遅れが音切れという形で現実世界に悪影響を及ぼしますので、マルチスレッド(WebWorker)を使っていたり、同期するために SharedArrayBuffer を使っていたり、従来から使われている ScriptAudioProcesser ではなく AudioWorklet を使っているなど、 WebAssembly との組み合わせもあまり見ないサンプルになっていますので、JavaScript 的にも、もしかすると参考になるかもしれません。

なお、スレッド同期に使っている SharedArrayBuffer の iOS/Safari での実装が 2021年 12月現在テクニカルプレビュー中です。次のリリースあたりで使えるようになると思いますが、現在は Safari で動作しません。そのうちに動作し始めると思います。

ウェブブラウザーを超えて

さて、JavaScript がウェブブラウザーから飛び出し Node.js や Deno といった形で取り出されたように、WebAssembly にも同様なランタイムがあります。

いろいろな実装があり群雄割拠している状態ですが、自分が使っているものをいくつか紹介します。

wasmtime

Standalone JIT-style runtime for WebAssembly, using Cranelift

Wasmer

Run any code on any client. With WebAssembly and Wasmer.

Wasm3

The fastest WebAssembly interpreter, and the most universal runtime

wasmtime がリファレンス実装的な印象で、Wasmer は高速で各言語バインディングが充実、Wasm3 は JIT を使わないインタープリタ型で JIT 禁止の iOS 環境でも動作可能、また最小構成で Arduino などのマイコンでも動作といった特徴があります。

WebAssembly ランタイムはもちろん単体で .wasm を動作させることができますが、さまざまな言語をホストとして .wasm を実行をする仕組みが備わっています。

“wasmer-python” を利用して、この記事でウェブブラウザーで呼び出したシンセサイザーの .wasm ファイルとまったく同じものを Python から呼び出してサウンドプログラミングした動作例が次の動画となります。(ソースコード: https://github.com/h1romas4/libymfm.wasm/tree/main/examples/python

.wasm は OS を選びませんので、ランタイムと組み合わせることでウェブブラウザー(JavaScript)に限らずさまざまな言語から呼び出して活用することが可能です。

Wasmer の言語対応:

特に C/C++ でかかれたライブラリーのスクリプト言語からの呼び出しは、使われる範囲的にインターネット経由の入力が伴うことが多く、セキュリティーの面で気になる部分がでてきますが、Wasm をかぶせることでサンドボックスとなり安全性が高まり、また OS の依存もなくなりますので扱いが簡単です。 .wasm がひとつあればどこでも動作します。

PHP で画像処理したいと思ったら wasmer-php で Wasm ビルドした C/C++ ライブラリーを安全に使う…なんていうのもアイディアかもしれません。

最近ではソフトウェアのプラグインシステムとしても WebAssembly を活用し、そのソフトウェアがつくられた言語以外でプラグインをかけるようにしているしている例もみられるようになりました。

zellij

Zellij includes a layout system, and a plugin system allowing one to create plugins in any language that compiles to WebAssembly.

まとめ

  • WebAssembly はいろいろな言語でつくられたソフトウェア・ライブラリーを、ウェブブラウザーやランタイム上で OS を選ぶことなく動作させることができる。
  • WebAssembly のランタイムと各言語のバインディングを使うことで、言語を他の言語で拡張して使うこともできる。

WebAssembly の今後は「計算しかできない」を解決していく Interface Type や WASI といった仕様の策定・実装、GC やマルチスレッドの対応の強化が進み、活用範囲や対応言語も増え、目にする機会も増えるかなと思っています。クラウドの Lambda 的な部分での採用も増えていきそうです。

面白いことがあればぜひ教えてください。てなわけで、また来年(!?)…!

ツイッターやってます:

https://twitter.com/h1romas4/status/1460572622475841542

ゆるWeb勉強会

ゆるWeb勉強会とは札幌で開催している @tacck さん主催のWeb系勉強会で、現在はオンライン配信も行っていてどなたでも参加できます。いろんな方の発表を聴き、またわいわい質問もすることができますので、興味がある方は覗いてみてください!

ゆるWeb勉強会@札幌

主に「Web」というキーワードに関する、ゆるい勉強会です。 プログラムを始めたばかりの人も、仕事でバリバリやっている人も、どなたでも歓迎です!

「勉強し初めて、次に何をやればいいんだろう?」 「普段使っていない言語やフレームワーク、知っている人に導入部分を聞いてみたい。」 「いつもはバックエンドだけど、たまに触るフロントエンドの話も知りたい。」 「実際、みんな現場ではどうやって開発してるの?」

仕事・趣味を問わず、普段の仕事の内容も問わず、「Web系」に関することを、ゆるっとみんなで話してみたいです。

WebAssembly をウェブブラウザーで活用する(1)

この記事は ゆるWeb勉強会@札幌 Advent Calendar 2020 の 12日目です 😀

今年 2020年、WebAssembly は W3C 勧告に到達し、モダンウェブブラウザーで安心して WebAssembly を活用できるようになった年となりました。

また、ウェブブラウザー外で WebAssembly を動作させ、さまざまな環境で動作するユニバーサルバイナリーとして、コンテナー技術やマイコンなどで動作させる動きも広がった年でもありました。

この記事では、まずはウェブブラウザー視点から、WebAssembly が現在どのように活用され初めているのかを紹介してみたいと思います。

WebAssembly でできること、できないこと

WebAssembly の実態は低レベルマシンコードで、ウェブブラウザーなどに実装される WebAssembly のランタイムがこれを読み込み、プログラムとして実行する環境です。

現在、さまざまなコンピューター言語が WebAssembly に対応しており、それらの言語でかかれたプログラムを WebAssembly にコンパイルし、ウェブブラウザーで実行することができます。

ウェブブラウザーではこれまで、プログラム言語としては JavaScript しか動作しませんでしたが、WebAssembly の登場により、JavaScript 以外の言語のプログラムも実行することができるようになりました。

WebAssembly に「ネイティブ」対応している言語は多くありますが、自分が把握している言語は次のとおりです。

  • C/C++
  • Rust
  • AssemblyScript
  • Go
  • Swift

「ネイティブ」と書いたのには訳があり、WebAssembly 上で動作する言語はその他にも多数存在し、たとえば Python なども動作しますが、スクリプト言語のインタープリタや JIT は、元をただせば C/C++ でつくられており、WebAssembly が C/C++ に対応していることに立脚すると、それらのインタープリタ自体を WebAssembly で動作させてしまえば、ウェブブラウザーで Python が動作する、、そういう仕組みになっています。(C#/WebAssembly などはこの方式です)

さて、このように WebAssembly では多数の言語が動作しますが、その言語でつくられたソフトウェアはそのままでは動作するというわけではありません。ウェブブラウザーで動作する WebAssembly は現在のところ、DOM などのウェブブラウザーの機能(Web IDL)にアクセスすることができないからです。

つまりユーザーへの入出力部分(画面を描くであるとか、クリックを受け付けるとか)は、従来の JavaScript で作成し、処理の部分のみを WebAssembly に投げるようにプログラムを構成します。これが現在のウェブブラウザー上の WebAssembly でできない部分です。

ちなみに、今年 W3C 勧告となった WebAssembly の仕様は MVP(最初)の仕様群となっています。

ウェブブラウザーを含む各 WebAssembly ランタイムは次の仕様となる Proposals を絶賛先行実装中で、これらの実装が進むと WebAssembly から直接ウェブブラウザーの機能(Web IDL) へのアクセスも可能になります。(そのようになるように作業が進められています)

実際に使えるようになるのは、来年か再来年かになるかと思いますが、、楽しみです!

WebAssembly 活用例

WebAssembly のできることできないことが分かったところで、WebAssembly の活用例をみながら、どうやって実装しているのかを紹介していきたいと思います。

Ruffle A Flash Player emulator written in Rust

まずは、ニュースなどでもでていましたので知っている方もいらっしゃるかもですが、Adobe Flash を WebAssembly で実装した Ruffle。

https://github.com/ruffle-rs/ruffle

A Flash Player emulator written in Rust

ウェブブラウザーのプラグインで Flash サポート終了するなら、WebAssembly でつくってしまえば良いのでは?という発想よりつくられた、そのまま .swf ファイルが実行できるプログラムで Rust 製です。

デモが次のサイトから見ることができます。お手持ちの .swf があれば動かしてみると面白いかもしれません。

https://ruffle.rs/demo/

描画は canvas を、音声は WebAudio を JavaScript でインターフェースし、.swf の解析や実行を Rust 側で行っています。

同様に Microsoft Silverlight も WebAssembly に移植した実装が存在します。

https://opensilver.net/

ffmpeg.wasm

ウェブブラウザーが、どのような画像形式や動画再生をサポートするのかでやきもきする時代は WebAssembly の登場により終焉を迎えました。なぜならば、それらのデコーダーは C/C++ でかかれているからです。つまりそのプログラムをそのまま WebAssembly にしてしまえば、どのような画像でも動画でも再生できてしまいます。

というわけで、ffmpeg は様々な動画コーデックをもつ有名な C/C++ でかかれたオープンソースですが、これを WebAssembly コンパイルにしてインターフェースしたのが ffmpeg.wasm になります。動画再生だけはなく生成やエンコードも可能です。

https://github.com/ffmpegwasm/ffmpeg.wasm

FFmpeg for browser and node, powered by WebAssembly

ffmpeg.wasm は npm パッケージ化されていますので、使いたいなぁと思ったら、package.json の依存に加えるだけで使うことができます。

このような感じで WebAssembly できていると知らずに node のパッケージを使っていることも増えているのではないかと思います。(自分が確認したものでは .gz を展開するライブラリーが .wasm を使っているパターンがありました。

poton Rust/WebAssembly image processing library

動画に続いて画像処理系のライブラリーです。

これまでも画像に対してフィルターをかけたいなどといった場合は、CSS でネイティブに備わる機能が使えましたが、自分の思ったとおりの処理をしたい場合は、JavaScript で行う必要がありました。

もちろん JavaScript でもプログラミング可能ですが、画像処理に関してはプログラムの書きやすさを考慮すると他の言語を使ったほうが有利です。また WebAssembly にすることで高速化がかなり期待できます。

poton は Rust で実装された画像処理ライブラリーで、減色やリサイズ、フィルター、回転などなどの処理を WebAssemby で行うことができます。

デモが次のサイトから確認できます。

https://silvia-odwyer.github.io/photon/demo.html

こちらのライブラリーも npm 化されていますので、手軽に自分のプログラムから利用可能です。


ウェブブラウザーで動作する WebAssembly は、高速性もさることながら、さまざまな言語のエコシステムをそのまま活用できるのが大きな魅力の一つです。

WebAssembly 登場以前は、行いたいと思った処理を JavaScript で書き直す必要がありましたが、今後はその必要がなくなり、特に C/C++/Rust でかかれたすぐれたライブラリーをそのまま使うことができます。

次の回では、C/C++ もしくは Rust でかかれたプログラムを WebAssembly に移植して動作させるデモをやってみたいと思います!(続く

関連

M5Stack Core2 SDK でメガドライブエミュレーターをビルドする

はじめに

しばらく品切れが続いていました M5Stack Core2 を買うことができました。嬉しいです。 😀

Core2 には PSRAM が 8M ついているということで、そのあたりを確認しつつ、ハローワールドがてらメガドライブエミュレーター(Genesis Plus GX)を移植してブートさせてみました。

ソースコードを github で公開しています。

Genesis-Plus-GX M5Stack Core2 porting (no optimize and super slow)

https://github.com/h1romas4/m5stack-genplus

まだビルドしてエミュレーターの起動を確認したところまでのソース断面となっていますので、最適化はまったくしておらずとっても遅いです。

後述しますが、単純に不足したメモリーを SRAM から拡張のクロックが 80MHz PSRAM にメモリーを逃したのと、LCD への仮想 VRAM 転送で何も工夫をしていないためです。

(速くする方法を思いついたらちょこちょこいじるかもしれません。また、いい方法があったらぜひお教えください!)

この記事では M5Stack Core2 上でこういった少し大きめのソースをビルドするノウハウと、PSRAM の使い方を紹介してみたいと思います。

esp-idf のビルドシステムを使った M5Stack Core2 アプリのビルド

M5Stack Core2 の標準の C/C++ 開発キットは Arduino IDE となっていますが、移植元となっている Genesis-Plus-GX のソースツリーが比較的大きいのと、各コンパイルオプションなども細かく修正しながら作業したかったので、esp-idf ビルドシステムを使って M5Stack Core2 向けのバイナリーの作成を行っています。

esp-idf が提供する Makefile を用いるとプロジェクトの構成を次のようにすることができます。なお、esp-idf の 4系のバージョンでは cmake を使うように変わっていますが、ここでは esp-idf 3.3 系を使うため Makefile 方式としています。

+ components
    + arduino (https://github.com/espressif/arduino-esp32)
    + m5stack
        + M5Core2 (https://github.com/m5stack/M5Core2)
        component.mk
    + genplus 
        [ソース]
        component.mk
+ esp-idf (https://github.com/espressif/esp-idf)
+ main
    main.cpp
    component.mk
sdkconfig
Makefile

maincomponents ディレクトリの構造と各コンポーネントしたに配置された components.mk とルートの sdkconfigMakefile が esp-idf のビルドシステムが認識する要素です。

そのまま使いたい時は、m5stack-genplusgit clone --recursive していらないファイル消すのが簡単かもです。:D

プロジェクトテンプレートリポジトリーと How to create のドキュメントをつくりました。また、github actions で自動ビルドもかけれるようにしています。使う場合は github にログインして以下から Use This Template ボタンを押して、自身のリポジトリーを作成してプログラミングを開始すると便利です。

https://github.com/h1romas4/m5stack-core2-template

esp-idf build system template for M5Stack Core2.

さて、M5Core2 ライブラリーが依存するプロジェクトは esp-idf と arduino-esp2 ですが、現在 Arduino IDE で提供されている一式がどの断面を使っているのかが分からなかったので、ビルドが通るものを試行錯誤して、以下のバージョンでソースツリーに git submodule で固定して導入しています。

現在リリース版の arduino-esp32 は v1.0.4 の esp-idf 3.2 依存ですが、これではビルドが通らず未リリースの arduino-esp32 v1.0.5 相当で、esp-idf は 3.3 の最終コミットを使っています。 (どうも esp-idf v3.2 系及び v3.3.5 タグだと必要なファイルがなかったり、コンパイルエラーになるなどビルドが通りませんでした)

(2021/6)現在の最新版の組み合わせは次のようになっています。

nameversionhash
esp-idf3.3.5
arduino-esp321.0.6
M5Core20.0.3(latest)54b958b

git submodule で導入しておくと、バージョンが切り替わったときの上げ下げやヒストリーの確認も容易かと思います。(このようにしておくとプロジェクトごとに依存のバージョンを固定できます)

ビルドは、各 OS に対応した xtensa-esp32 のツールチェイン(gcc)を導入後、make するだけで M5Stack Core2 向けのバイナリーが作成できます。 m5stack-genplus の github をビルドする手順は次のようになります。 (Windows では MSYS2 の窓より…)

# submodule があるので --recursive 指定 
git clone --recursive https://github.com/h1romas4/m5stack-genplus.git
cd m5stack-genplus
# This repository includes eps-idf
export IDF_PATH=$(pwd)/esp-idf
# CPU 4コア CPU の場合 -j4 とコアすると速い
make -j4
# 書き込み & 実行
make flash monitor

sdkconfig ファイル

sdkconfig ファイルは esp-idf や esp-idf に対応した各コンポーネントが使う define の定義です。これは make menuconfig コマンドでメニューから生成することができます。

m5stack-genplus リポジトリーには sdkconfig がコミットされていますので各種設定済みで、変えるのは転送先のシリアルポートの設定くらいですが、esp-idf や arduino-esp32 のどの機能を使うかなどなどの設定ができます。

保存すると sdkconfig に値が書き込まれますが、基本的に make や C/C++ 中の define 定義集のようなものなので、キー名でソースコードを grep すると何が有効になったのかが分かります。

component.mk ファイル

各 component.mk の中にはそのコンポーネントで使われるソースファイルやインクルードファイルの位置、CFLAGS などのコンパイルオプションが定義できます。

Genesis Plus GX モジュールでは次のように定義しています。

COMPONENT_SRCDIRS := core core/z80 core/m68k core/input_hw core/sound core/cart_hw core/cart_hw/svp core/ntsc m5stack
COMPONENT_ADD_INCLUDEDIRS := core m5stack core/cart_hw core/cart_hw/svp core/cd_hw core/debug core/input_hw core/m68k core/ntsc core/sound core/themor core/z80

CFLAGS := \
    -DLSB_FIRST \
    -DUSE_16BPP_RENDERING \
    -DMAXROMSIZE=131072 \
    -DHAVE_ALLOCA_H \
    -DALT_RENDERER \
    -DALIGN_LONG \
    -DM5STACK \
    -fomit-frame-pointer \
    -Wno-strict-aliasing \
    -mlongcalls

利用できるオプション値は次のドキュメントから参照できます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.3/api-guides/build-system.html#optional-project-variables

Optional Project Variables

ライブラリーなどを M5Stack に移植して動作させる時は、CFLAGSCPPFLAGS などが容易に設定できますので便利に使えるのではないかと思います。components ディレクトリに任意の名前でディレクトリを作成してソースをがばっと入れ、component.mk を書いてあげればビルド対象になります。

またライブラリーにユーザー設定がある場合は Kconfig.projbuild をつくって配置することで、make menuconfig(sdkconfig) 用のメニュー定義も入れることができるようです。

PSRAM の使い方 (.bss セグメントを逃がす)

esp-idf で大きめのプログラムをリンクすると、

region `dram0_0_seg' overflowed by 1995968 bytes

このようなエラーメッセージでリンクできない場合があります。これは static の領域を確保する .bss セグメントが足りない場合に出力されます。Genesis Plus GX のビルドでは当初、上記のように 2MB 近く足りませんでした。

この .bss セグメントを PSRAM にもっていくオプションがあり、次の menuconfig の設定から有効にすることができます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-guides/external-ram.html

→ Component config → ESP32-specific → SPI RAM config 
→ Allow .bss segment placed in external memory

define 値的には CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY となりますので esp-idf を grep してみると分かりやすいです。このオプションを有効にした後、ソースファイル上の static 変数に EXT_RAM_ATTR をつけると、PSRAM に領域を持っていくことができます。

#ifdef M5STACK
#include "esp_attr.h"
#endif

#ifdef M5STACK
EXT_RAM_ATTR static uint32 bp_lut[0x10000];
#else
static uint32 bp_lut[0x10000];
#endif

イメージ的にはこんな感じになります。 EXT_RAM_ATTR は未定義はブランクなので、これでも大丈夫です。

#ifdef M5STACK
EXT_RAM_ATTR
#endif
static uint32 bp_lut[0x10000];

なお、PSRAM は CPU から最大 80MHz の SPI で接続されており、通常の SRAM より速度が遅く、転送レートは最大で SRAM 960MB/sec に対して 40MB/sec ほどのようです。このため、主要なロジックで使われるメモリーを載せると処理速度が低下してしまうはずです。

How slow is PSRAM vs SRAM (anyone have quantitative info?)

Internal SRAM is 32bit @ 240MHz max, so 960MByte/second. PSRAM is 4-bit @ 80MHz, so 40MByte/second.

PSRAM の使い方 (malloc)

PSRAM がコンフィグレーションで有効になっていると、通常の malloc 関数で PSRAM も使ってくれます。

https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-guides/external-ram.html

Support for external RAM

PSRAM ではなく速い SRAM 側に確定で確保したいなど、明示的にどちらから取得するかを決めたい場合は、次の設定ができます。

→ Component config → ESP32-specific → SPI RAM config

また、M5Stack Core2 には 8MB の PSRAM が搭載されていますが、使えるのは 4MB までのようで、それより上の 4MB を使う場合は himem API で別途取得するようです。(今回は未検証)

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

The himem allocation API

VS Code の設定(おまけ)

あんまり関係ありませんが、開発は VS Code + C/C++ Extention で行いました。インテリセンスの効きもよく大変良いです。

VS Code ではプロジェクトに .vscode/c_cpp_properties.json を配置して次のように設定すると便利です。

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/build/include",
                "${workspaceFolder}/components/arduino/cores/**",
                "${workspaceFolder}/components/arduino/libraries/**",
                "${workspaceFolder}/components/arduino/variants/esp32",
                "${workspaceFolder}/components/m5stack/M5Core2/**",
                "${workspaceFolder}/esp-idf/components/**"
            ],
            "defines": [
                "ESP32=1",
                "ARDUINO_ARCH_ESP32=1",
                "BOARD_HAS_PSRAM",
                "ARDUINO=10800",
                "M5STACK",
                "LSB_FIRST",
                "USE_16BPP_RENDERING",
                "MAXROMSIZE=131072",
                "HAVE_ALLOCA_H",
                "ALT_RENDERER",
                "ALIGN_LONG"
            ],
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "~/devel/toolchain/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

defines にはコンパイルオプションで指定した define を定義しておくといい感じにパーサーが感知して色分けしてくれます。sdkconfig の define 値については、自動的に生成された build/include/sdkconfig.h を読むことで自動的に反映するようになっています。

例えばですが、上記のようにコンパイルオプションで定義された M5STACK 値の ifdef が有効になって else がグレーアウトしますので分かりやすいです。

おわりに

というわけで、M5Stack Core2 ハローハッピーワールドでした。

他にもいくつかプログラムを動かしてみましたが、タッチセンサーもよく動いて M5Stack Core2 楽しいです。ビルドも固まりましたので、引き続き何かつくってみたいです!

関連記事