Mycropythonにnumpyライクなulabを組み込んでビルドする

# はじめに
MicropythonでFFTを使いたいと思い色々調べつつ備忘録的にこの記事を書いています
## Mycropythonについて
今更なので割愛. 公式WEBページは下記
https://micropython.org/
## 今回使用したボード
https://www.adafruit.com/product/4900
特徴
・Raspberry　Pi Picoと同じRP2040 SOCを搭載
・Flashは8MB
・Seeedstudio Xiaoシリーズと同じ大きさ
・USB Type C connector
・オンボードでNeoPixelとSTEMMA QTコネクタ
残念な点はちょっと高価
https://www.switch-science.com/products/7211
## NumPyについて
Pythonライブラリで科学技術計算の基礎パッケージ
https://numpy.org/ja/
公式をはじめ解説はそこかしこにあるのでそれらを参照
## ulabについて
基本は配列計算を行う為のライブラリにnumpyやscipyがサポートするファンクションの一部を組み込んだものでfftやrandomがサポートされる
Githubページに詳細やFFTベンチマーク結果が載っている
https://github.com/v923z/micropython-ulab
CircuitPythonではリリースにあらかじめ含まれているポートもある
(ちなみにQT Py RP2040は含まれている)
**(ならCircuit Pythonを使えよ！)**
Micropythonのリリースには含まれていないので自分でビルドする必要がある
# ビルド
ということでmicropython-ulab Girhubページの手順にしたがってビルドをしていく
https://github.com/v923z/micropython-ulab?tab=readme-ov-file#rp2-based-boards
### 今回使用した環境
・Windows11 + VMWare Workstation 17 Player **(注意：非営利目的の使用のみ)**
・Ubuntu20.04.2
### 0. 前準備：gcc armコンパイラのインストール
その他makeやcmake等は予め入っていたので必要なものが何かは把握出来ていない
・sudo apt updateとsudo apt upgradeを実行
```shell
$ sudo apt install gcc-arm-none-eabi
```
・コンパイル用ディレクトリ(便宜的に$MP_DIRと表記)を作成してそこへ移動
### 1.micropython-ulabリポジトリのクローン
```shell
$ git clone https://github.com/v923z/micropython-ulab.git
```
### 2.ビルドスクリプト"build/rp2.sh"の修正
```shell
$ cd micropython-ulab
$ cp build/rp2.sh build/qtpy_rp2.sh
$ vi build/qtpy_rp2.sh
$ chemod +x build/qtpy_rp2.sh
```
適当なエディタを使って24行目のmakeコマンドにBOARD指定を追加
make USER_C_MODULES=$ULAB_DIR/micropython.cmake <font color=#0F00FF>BOARD=ADAFRUIT_QTPY_RP2040</font>
```shell
$ diff build/rp2.sh build/qtpy_rp2.sh 
24c24
< make USER_C_MODULES=$ULAB_DIR/micropython.cmake
---
> make USER_C_MODULES=$ULAB_DIR/micropython.cmake BOARD=ADAFRUIT_QTPY_RP2040
```
### 3.ビルド
```shell
$ ./build/qtpy_rp2.sh
Cloning ulab
Cloning into 'ulab'...
... 途中省略 ...
[ 98%] Linking CXX executable firmware.elf
   text	   data	    bss	    dec	    hex	filename
 453352	      0	   5520	 458872	  70078	$MP_DIR/micropython-ulab/micropython/ports/rp2/build-ADAFRUIT_QTPY_RP2040/firmware.elf
[100%] Built target firmware
```
```shell
$ ls -l micropython/ports/rp2/build-ADAFRUIT_QTPY_RP2040/
合計 20020
-rw-rw-r-- 1 k1 k1   21027  6月 12 11:43 CMakeCache.txt
drwxrwxr-x 9 k1 k1    4096  6月 12 11:44 CMakeFiles
-rw-rw-r-- 1 k1 k1  895679  6月 12 11:43 Makefile
-rw-rw-r-- 1 k1 k1    1840  6月 12 11:43 cmake_install.cmake
drwxrwxr-x 6 k1 k1    4096  6月 12 11:43 elf2uf2
-rwxrwxr-x 1 k1 k1  453352  6月 12 11:44 firmware.bin
-rw-rw-r-- 1 k1 k1 6994300  6月 12 11:44 firmware.dis
-rwxrwxr-x 1 k1 k1 7253384  6月 12 11:44 firmware.elf
-rw-rw-r-- 1 k1 k1 2208587  6月 12 11:44 firmware.elf.map
-rw-rw-r-- 1 k1 k1 1275225  6月 12 11:44 firmware.hex
-rw-rw-r-- 1 k1 k1  906752  6月 12 11:44 firmware.uf2
-rw-rw-r-- 1 k1 k1  423122  6月 12 11:43 frozen_content.c
drwxrwxr-x 3 k1 k1    4096  6月 12 11:43 frozen_mpy
drwxrwxr-x 3 k1 k1    4096  6月 12 11:43 generated
drwxrwxr-x 5 k1 k1    4096  6月 12 11:43 genhdr
drwxrwxr-x 6 k1 k1    4096  6月 12 11:43 pico-sdk
-rw-rw-r-- 1 k1 k1   16844  6月 12 11:43 pins_ADAFRUIT_QTPY_RP2040.c
```

**<font color=#FF8F00>手っ取り早く試したいならビルド済のイメージがここにあります</font>**
https://github.com/v923z/micropython-builder

# ファームウエアの書き込み
・USBケーブルでボードをPCに接続しBOOTスイッチを押した状態でRSTスイッチを押してすぐ離すとRPI-RP2というドライブとしてPCに認識される
・このドライブへDrag & Dropで"micropython/ports/rp2/build-ADAFRUIT_QTPY_RP2040/firmware.uf2"を書き込むと自動的にブートが行われる
```python
>>> MicroPython v1.24.0-preview.36.gd7d77d91b on 2024-06-12; Adafruit QT Py RP2040 with RP2040

Type "help()" for more information.

>>> help("modules")

__main__          asyncio/lock      gc                random

_asyncio          asyncio/stream    hashlib           re

_boot             binascii          heapq             rp2

_boot_fat         builtins          io                select

_onewire          cmath             json              struct

_rp2              collections       machine           sys

_thread           cryptolib         math              time

array             deflate           micropython       uasyncio

asyncio/__init__  dht               neopixel          uctypes

asyncio/core      ds18x20           onewire           ulab

asyncio/event     errno             os                vfs

asyncio/funcs     framebuf          platform

Plus any modules on the filesystem
```
## ulabがmoduleとして組み込まれていることが確認できた
---
## <font color="RED">(OPTION I) ulab.numpy.fft.fft()のcomplex対応</font>
下記のリンクにあるようにデフォルトではfft結果はrealとimageそれぞれがarrayとして得られる
https://micropython-ulab.readthedocs.io/en/latest/numpy-fft.html
これをCPythonのnumpyとコンパチブルにするには下記リンクのマニュアルコンパイル手順に以下の1.～3.を追加する
https://github.com/v923z/micropython-ulab?tab=readme-ov-file#rp2-based-boards
#### 1. submoduleの追加
（不要かもしれないがWarninbgがでたので）btstckとlwipを追加する
~~~shell
$ cd micropython
$ git submodule update --init lib/tinyusb
$ git submodule update --init lib/pico-sdk
$ git submodule update --init  lib/btstack
$ git submodule update --init  lib/lwip
$ cd lib/pico-sdk
$ git submodule update --init lib/tinyusb
~~~
#### 2. code/ulab.hの変更
クローンしたulabリポジトリのcode/ulab.hのULAB_FFT_IS_NUMPY_COMPATIBLE定義を変更する
~~~shell
$ diff -c ../ulab/code/ulab.h.org ../ulab/code/ulab.h
*** ulab/code/ulab.h.org	2024-06-13 10:21:59.740449646 +0900
--- ulab/code/ulab.h	2024-06-13 10:19:43.422227473 +0900
***************
*** 440,446 ****
  // Note that in this case, the input also must be numpythonic,
  // i.e., the real an imaginary parts cannot be passed as two arguments
  #ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE
! #define ULAB_FFT_IS_NUMPY_COMPATIBLE    (0)
  #endif
  
  #ifndef ULAB_FFT_HAS_FFT
--- 440,446 ----
  // Note that in this case, the input also must be numpythonic,
  // i.e., the real an imaginary parts cannot be passed as two arguments
  #ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE
! #define ULAB_FFT_IS_NUMPY_COMPATIBLE    (1)
  #endif
  
  #ifndef ULAB_FFT_HAS_FFT

~~~
#### 3. コンパイル
Qt Py RP2040ボード用のsubmodulesを設定したmake実行後、USER_C_MODULESとBOARD設定を追加してmakeする
~~~shell
$ cd port/rp2
$ make BOARD=ADAFRUIT_QTPY_RP2040 submodules
$ make USER_C_MODULES=/YOUR_PATH_TO/ulab/code/micropython.cmake BOARD=ADAFRUIT_QTPY_RP2040
(省略)
   text	   data	    bss	    dec	    hex	filename
 453112	      0	   5520	 458632	  6ff88	/home/k1/MP/micropython/ports/rp2/build-ADAFRUIT_QTPY_RP2040/firmware.elf
[100%] Built target firmware
~~~
uf2ファイルがmicropython/ports/rp2/build-ADAFRUIT_QTPY_RP2040/firmware.uf2としてできている
このuf2ファイルをQt Py RP2040ボードに焼いてfftを実行してみると
~~~python
>>> from ulab import numpy as np

x = np.linspace(0, 10, num=1024)
y = np.sin(x)
z = np.zeros(len(x))

x_fft = np.fft.fft(x)

print(x_fft)

v = abs(x_fft)
print(v)
array([5119.996+0.0j, -5.004667+1631.333j, -5.004776+815.6588j, ..., -5.005463-543.764j, -5.005635-815.6588j, -5.006575-1631.333j], dtype=complex)
array([5119.996, 1631.34, 815.6742, ..., 543.787, 815.6742, 1631.341], dtype=float32)
~~~
fft結果が複素数で返っておりabs()で絶対値が求められていることが確認できた

---

## <font color=#FF2F7F>(OPTION II) 複数のUSER_C_MODULESを定義してコンパイルする方法</font>

各モジュールのcmakefileをincludeしたcmakeファイルを作成し、そのパスをコンパイル時のUSER_C_MODULESとして指定する
以下はulabとst7789_mpyの2モジュールを組み込む例
~~~shell
$ cd $MP_DIR
$ cat micropython.cmake
include($MP_DIR/ulab/code/micropython.cmake)
include($MP_DIR/st7789_mpy/st7789/micropython.cmake)
$ cd micropytho/ports/rp2
$ make USER_C_MODULES=$MP_DIR/micropython.cmake BOARD=ADAFRUIT_QTPY_RP2040
~~~

---