工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

ESP32用micropythonで、numpy, scipyライクなパッケージmicropython-ulabを組み込み、FFTしてみる【ulab v0.24.0版】

概要

ESP32用micropythonで、micropython-ulabを組み込んだファームウェアを作成し、無事FFTを実行することができた。また、動作速度も、前回の自前実装FFTより数倍高速化できた。

2020/04/12時点でのmicropython、ulabを使用して実施している。情報が古いことに注意。新しいものは、こちら


背景と目的

前回FFTを実行するためC実装の自作モジュールを組み込んだファームウェアを作成できた。これは、全然使えないわけではないものの、動作速度とメモリ利用効率が悪かったので改善を目指し、知人から存在を聞いたnumpy, scipyライクなパッケージmicropython-ulabを組み込んでみる。


詳細

0.実施条件

前回と同じ。


1.micropython-ulabのダウンロード

cd ~/MicroPythonProjects
git clone https://github.com/v923z/micropython-ulab.git

2.モジュール用ソースコードを配置

micropython-ulabのファイル一式のうち、必要そうなのはcodeディレクトリのものだけのようだ。なので、

~/MicroPythonProjects/
    modules/
        ulab/
            codeディレクトリのソースコード
            :

となるように、

cp -r ~/MicroPythonProjects/micropython-ulab/code ~/MicroPythonProjects/modules/ulab


3.ビルド

前回と同じ要領で、やるのだが、一応make cleanでいったんリセットして実行。makeの引数CFLAGS_EXTRAに、 ulabを参照するように、-DMODULE_ULAB_ENABLED=1をつけるのを忘れない。

cd ~/micropython/ports/esp32
make clean
make USER_C_MODULES=~/MicroPythonProjects/modules CFLAGS_EXTRA = -DMODULE_ULAB_ENABLED=1 all

ビルドしてみたところ、以下のエラーが出た。以下に乗せているのは1か所だが、ndarray.c内で何か所も同じものが出た。

CC /root/MicroPythonProjects/modules/ulab/ndarray.c
In file included from /root/MicroPythonProjects/modules/ulab/ndarray.c:21:0:
/root/MicroPythonProjects/modules/ulab/ndarray.c: In function 'ndarray_make_new_core':
/root/MicroPythonProjects/modules/ulab/ndarray.c:196:39: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
         mp_raise_ValueError(translate("first argument must be an iterable"));
                                       ^
/root/MicroPythonProjects/modules/ulab/ndarray.h:31:22: note: in definition of macro 'translate'
 #define translate(x) x
                      ^
In file included from /root/MicroPythonProjects/modules/ulab/ndarray.c:17:0:
../../py/runtime.h:167:15: note: expected 'mp_rom_error_text_t {aka struct <anonymous> *}' but argument is of type 'char *'
 NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg);

内容を見ると、mp_raise_ValueError関数の引数は、mp_rom_error_text_tだが、char * が与えられているとのこと。 その個所を見てみると、

mp_raise_ValueError(translate("first argument must be an iterable"));

で、translate関数に文字列が渡されて、その戻り値がmp_raise_ValueErrorに渡されている。このtranslateというのは、上記のエラーを見るとわかるように、ndarray.hで、

#define translate(x) x

となっていて、何かを変換しているようで何もしていない。なので、char*が渡されてしまうということだ。

じゃあ、これをど卯修正したらよいか、検討をつけるために、micropythonで、mp_raise_ValueErrorを検索すると、いろいろなソースコードで、

mp_raise_ValueError(MP_ERROR_TEXT(文字列))

となっていた。MP_ERROR_TEXTという関数だかマクロだかわからないが、恐らくこれを使えばいいのではないだろうか?ということで、ndarray.hを以下のように修正。

//#define translate(x) x
#define translate(x) MP_ERROR_TEXT(x)

再度makeしたところ、見事に成功!ulabが組み込まれたファームウェアができたようだ!


4.動作確認

ファームウェアをESP32に書き込み以下を実行。

import utime
import math

# ulab
import ulab
from ulab import fft

# 入力信号
N = 512
x = ulab.array([[0.0] * N])
for i in range(N):
    x[i] = math.cos(2 * math.pi * i / N)

# FFT実行
t0 = utime.ticks_us()
y = fft.fft(x)
t1 = utime.ticks_us()

# 結果表示
for i in range(N):
    i , y[0][i], y[1][i]
"elapsed_time:", (t1-t0) / 1000, "msec"

無事、FFTが実行された。問題の速度だが、前回自前で作成したモノより5倍程度速いことが分かった。ulabの威力は十分だ。

(505, -6.804023e-06, 2.942783e-06)
(506, -7.816223e-08, -7.783505e-07)
(507, -1.806117e-05, 1.898014e-06)
(508, -3.251397e-07, -1.51736e-06)
(509, -1.919823e-05, 2.977413e-06)
(510, -4.541771e-06, -2.580135e-06)
(511, 256.0, -0.0001847311)
>>>
>>> "elapsed_time:", (t1-t0) / 1000, "msec"
('elapsed_time:', 11.56, 'msec')

さらに、Nを増やしてどこまでいくか確認したところ、N=4096までが実行可能だった。時間は20msec弱。それ以上だとメモリエラー。 とはいえ、前回のモノより8倍までいける。ただし、限界ギリギリまでFFTでメモリを使うとなると、他の余裕がなくなるので、実際はもう少し少ない長さまでとなるだろうが。

('elapsed_time:', 19.866, 'msec')


まとめと今後の課題

micropython-ulabを、ESP32用micropythonに組み込み、FFTを実行することができた。