新しいボードへMicroPythonを対応させる方法をざっくり解説

★この記事はMicroPython Advent Calendar 2018に参加しています。★

NUCLEO-L432KCは、STM32L4シリーズのMCUを使った、Arduino Nanoサイズの小型ボードです。
写真のように、他のNUCLEOボードに比べて非常に小さく、省電力ですが、フラッシュは256KB、RAMは48KB+パリティチェック付き16KBと、そこそこのスペックです。
秋月電子の店頭などでは見かけないのですが、RSやdigi-keyなどの通販では1500円くらいで買えます。

MicroPythonのフォーラムの以下のスレッドで話題になっていたのですが、まだMicroPythonが載せられていなかったので載せてみました。

[STM32L432 – NUCLEO 32] – MicroPython Forum

具体的にはどういう作業になるのかを紹介してみたいと思います。

全体構造

まず、MicroPythonの全体のレイヤ構造は上の図のようになります。(py/ 等のパスはMicroPythonのソースツリー内でのパスを示しています)

最上位の処理系は、後で述べるconfigファイル経由で設定をしますが、実装自体には直接手を加える必要はありません。

GPIOやタイマなど、ハードウェア固有のモジュールを実装しているのが、その下のports層になります。
STM32のportsはMicroPythonのルーツ(最初のMicroPythonはSTM32用だった)なので、十分整備されています。
とはいえ、チップの種類による#ifでの切り分けなどは入っていますので、新しいチップを使う場合には多少手が入ります。

このports層と最下部のハードウェア層を仲介する、図で黄色で示した層が今回の作業のメインです。

新規のSoCへの対応

STMicro社のSoCの機能は、STMicro社が提供するHAL(Hardware Abstraction Library=ハードウェアを抽象化するライブラリ)で隠蔽されており、HALの実装にユーザが手を加える必要はありません。
ただ、MicroPythonをビルドする上では、MCUごとのリンカスクリプトおよびAlternate Function一覧表が必要になります。

これらのファイルは

・ports/stm32/boards/チップ名.ld
・ports/stm32/boards/チップ名_af.csv

に置かれます。(実際には、後述のmpconfigboard.mkからファイルを指定します。)

ファイルを作成するに当たっては、SoCの技術情報が必要です。
今回はSTM32L432KCが対象ですので、以下のマニュアルを参照します。

Datasheet – STM32L432KB STM32L432KC – Ultra-low-power Arm® Cortex®-M4 32-bit MCU+FPU, 100DMIPS, up to 256KB Flash, 64KB SRAM, USB FS, analog, audio

新規のボードへの対応

MCUを使って作ったボードの情報(どのポートがどのピン番号に接続されているか、周辺ハードウェアを接続しているか等)は当然ながらHALでは提供されず、ボードごとに新規に作成する必要があります。

ボードごとのファイルは、「ports/stm32/boards/ボード名」に入ります。
最低限、以下の4つのファイルが必要になります。

・pins.csv ボードのピン名(MicroPythonからPin(‘name’)のような形で使用可能)とGPIOポートの対応
・mpconfigboard.h ボードに関する設定。MicroPythonのどの機能を有効化するかなど。
・mpconfigboard.mk Makefile用の変数。前節で決めたファイル名(チップ名.ldや、チップ名_af.csv)など。
・stm32l4xx_hal_conf.h HALライブラリに関する設定。HALのどの機能を有効化するかなど。

ファイルを作成するに当たっては、ボードの技術情報が必要です。
今回はNUCLEO-L432KCが対象ですが、マニュアルはNUCLEO-32シリーズで共通で、以下のマニュアル(UM1956)を参照します。

STM32 Nucleo-32 boards (MB1180) – User manual

リンカスクリプト

リンカスクリプトは、チップの技術情報と、他のチップのリンカスクリプトを参照しながら作成します。
チップのマニュアルのP59にあるメモリーマップで、フラッシュメモリとRAMのアドレスを確認します。

フラッシュが0x08000000から256KB、SRAMが0x20000000から64KBです。0x2000C000からはSRAM2となっていますが、ここはパリティチェック付きのエリアです。

フラッシュの一部をストレージとして使う場合は、そのための領域をリンカスクリプトでも確保する必要があります。
今回は、256KBしかないフラッシュにストレージを入れるのは無理があるので、確保していません。

RAMはスタックとヒープを入れますので、ヒープの上限を定義する必要があります。
今回はヒープ領域は42KB(0x2000A800まで)としました。

RAMの最後の16KBのみがパリティチェック付きになっていますが、今回は、パリティチェック付きの領域は予備として使用せずに残し、48KBをメインメモリとして使うことにしました。
48KBのうち、6KBをスタック専用として確保し、残りがヒープになっています。

パリティチェック付き領域まで含めてメインメモリとして使用するのもアリだと思いますが、他のL4シリーズではこの領域にフラッシュストレージのキャッシュを置いたりしていますので、一応空けておくことにしました。

Alternate Functionsのリスト

MicroPythonのSTM32ポートでは、Alternate Functionを定義するヘッダファイルをCSVファイルから自動生成するようになっています。
そのインプットとして、以下のような感じのCSV形式のAlternate Function一覧表が必要になります。

この一覧表はチップのマニュアルのp55~p58にあるTable15をCSV形式にしたものとほぼ同じです。
ただし、

・AF0~AF15に加えて、マニュアルのp52~54に掲載されている「Additional Functions」からADC、COMP、DACの情報も含める
・1つのセルに複数の機能が書かれる際の書式が若干違う(USART2_RTS_DE → USART2_RTS/USART2_DEなど)

といったあたりが異なります。

この表はゼロから作る必要は無く、近しいチップのデータをコピーしてきて差分だけ編集するのが楽です。
編集には表計算ソフトが必要です。私はGoogle Spreadsheetで編集しました。

ボードのピン名とチップの信号線との対応(pins.csv)

MicroPythonでPinインスタンス作成時に指定する名前を割り当てます。
名前は、通常チップまたはボードのGPIOポートのピンに割り当てられた名前を使います。
この名前はチップの信号線に対するエイリアスみたいなもので、一つの信号線に対して複数の名前を割り当ててもかまいません。
この名前とチップの信号線との対応表がpins.csvです。

形式は
NAME,PIN
となっており、NAMEは任意の文字列です。MicroPythonからは、主にPinオブジェクトを生成する際に
machine.Pin('NAME', machine.Pin.OUT)
のように使われます。
PINは信号線の名称で、前述のAlternate Functionリストの2列目で定義されていなければなりません。

NAMEとPINの対応は、NUCLEO-L432KCではボードのマニュアル(Figure.8)に掲載されていますので、これを見ながらデータを作成します。

ちょっと話が逸れますが、この図はNAMEとPINが1対1に対応していますが実はオンボードのLEDもPB3に接続されています。
ですのでPB3=D13をは他のピンとは挙動が異なる可能性があります。
PB3のAlternate FunctionにはSPI3_SCKもありますので、SPI3を使用する際は注意する必要があるかもしれません。

ボードに関する設定(mpconfigboard.h)

様々なオプション項目を設定します。項目には、主に

・MicroPython言語エンジンのオプション
・ハードウェアに依存するオプション

の2つのカテゴリがあります。

オプション項目を設定するファイルの階層は以下のような構成になっていて、より下位での指定項目が優先されます。(mpconfigboard.hでボードごとのオプションが書けるのは、STM32用port固有の仕様です。)

py/mphal.h // HW関連オプション
 └py/mpconfig.h // 言語関連オプション
   └ports/stm32/mpconfigport.h // プラットホーム固有のオプション
     └ports/stm32/boards/BOARD/mpconfigboard.h

ファイルの中身はCの#defineマクロで、言語エンジン関連のオプションは
#define MICROPY_PY_XXXX YY
の形式となります。
オプションに関するドキュメントは現時点では存在しないので、py/以下のソースコードにあるコメントを見てオプションの意味を判断する必要があります。

ハードウェアに依存するオプションは、STM32用portでは
#define MICROPY_HW_XXXX YY
の形式となっています(形式はportの実装側で決められます)。
こちらも、意味はports/stm32/以下のソースコードから判断しなければなりません。

なお、このファイルでは、I2CやSPIに割り当てるデフォルトのGPIOポートも指定しています。
NUCLEO-L432KC用のSTMicroの「公式な」割り当ては、ボードのマニュアルのTable 16に掲載されているのですが、どうもこの表は間違っているようです。
たとえば、I2C1のSCLとSDAの信号を上記の表ではPA6とPA5にマップしているのですが、Alternate Functionの表を見ると、PA5やPA6にはI2C1を割り当てることができません。
そのため、mbedのL432KCのページを見ながら独自に割り当てを決めました。

REPL用デバイスの設定

STM32用portのオプションで重要な項目として、REPLに用いるデバイスの指定があります。

STMicroが出しているNucleoやDiscoveryなどの評価用ボードでは、ST-LinkというUSB接続のデバッグツールが搭載されています。ソフトウェアの書き込みや、mbed用USBマスストレージなどの機能はST-Linkが行っています。
ST-Linkの機能の一つに、MCUのシリアルポートをUSB側にトンネルするUSBシリアル変換機能(Virtual Com Port: VCP)があります。
STM32用MicroPythonもこの機能を使ってREPLを提供しますが、そのためにはVCPに接続されているデバイスをREPL用デバイスとして指定する必要があります。

ボードのマニュアルのFigure.10を参照すると、VCPに接続されているのはPA2(TX)、PA15(RX)であることが分かります。

また、チップのマニュアルを見ると、PA2/PA15を使うUART(USART)デバイスはUART2であることが分かります。

従って、mpconfigboard.hの中ではREPLデバイスとしてUART2を、UART2の入出力ピンとしてPA2/PA15を指定します。
具体的には以下のようになります。

#define MICROPY_HW_UART2_TX     (pin_A2)  // VCP TX
#define MICROPY_HW_UART2_RX     (pin_A15) // VCP RX

#define MICROPY_HW_UART_REPL        PYB_UART_2

チップにもよりますが、通常、一つの機能(UART2)に対して、Alternate Function指定によって複数のピンアサインが可能(たとえばSTM32L432KCでは、UART2_RXはPA15のほかPA3も使える)なので、用いるピンは明示的に指定する必要があります。

ボードに関する設定(mpconfigboard.mk)

mpconfigboard.mkは、その拡張子の通り、Makefileからインクルードされるファイルで、ボード固有の指定をMakefileで行う必要がある場合に利用します。

最低限必要な項目は以下の4つのようです(以下はNUCLEO_L432KC用のデータ)。

MCU_SERIES = l4
CMSIS_MCU = STM32L432xx
AF_FILE = boards/stm32l432_af.csv
LD_FILES = boards/stm32l432.ld boards/common_basic.ld

AF_FILEはAlternate Functionのテーブル、LD_FILESはリンカスクリプトです。

内蔵フラッシュメモリをファイルシステムとしても使用する場合には、これ以外に以下の2つが必要(アドレスは使用するチップによって異なります)です。

TEXT0_ADDR = 0x08000000
TEXT1_ADDR = 0x08004000

上の例では、まずフラッシュの先頭部分(TEXT0_ADDR)にISRを置くための領域があります。
その後の領域はファイルシステム用領域(約0x4000=16KB)となり、MicroPython本体はその後、TEXT1_ADDRで指定したアドレスから書き込みます。

Makefileを見ると、ビルドしたELFファイルから、書き込み用のバイナリファイルをTEXT0_ADDRから開始する部分(firmware0.bin)とTEXT1_ADDRから開始する部分(firmware1.bin)の2つに分けて生成しています。

リンカスクリプトは、ライブラリ的に利用可能な共通のスクリプトが3つあり、それぞれ以下のような用途になっています。

・boards/common_basic.ld フラッシュにISRとMicroPython本体を配置する最もシンプルな形式
・boards/common_bl.ld ブートローダからMicroPythonをロードする形式
・boards/common_ifs.ld フラッシュに内部ファイルシステムも持つ形式

今回はフラッシュの容量の制約から、内部ファイルシステムを持ちませんので、common_basic.ldを使っています。

HALライブラリに関する設定(stm32l4xx_hal_conf.h)

STM32のHALには非常に多くの機能があるので、どれを利用するかこのファイルで選択できます。
このファイルはサイズは大きいのですが、同じアーキテクチャのMCU用のファイルが既にある場合にはそれをベースにすれば良いので、一番手がかからないファイルです。

今回は、NUCLEO-L476G用のファイルをコピーし、ざっと項目をチェックして、SDカード関連のオプションを外しました。

NUCLEO-L432KC用の例

以上の作業で、MicroPythonのNUCLEO-L432KC用ビルドを開始することができます。

ただ、コンパイルエラーが全く出ないわけではなく、STM32L4シリーズの他のMCUとの差分について、若干エラーが出ました。
その主な原因は、STM32L432は

・GPIO Dが無い
・Timer 3が無い
・SPI 2が無い
・ADCのダブルモード(2つのADC入力の差分を計測)が無い
・フラッシュメモリがシングルバンク構成(他のSTM32L4シリーズのボードは2バンク構成)

ということで、いずれも割と単純な#ifで切り分けてL432用コードを挿入することで解決できました。

以上で、NUCLEO-L432KCでMicroPythonを動作させることができるようになりました。

記事冒頭の写真はI2C経由でのOLED制御を試したところですが、SPIも問題なく動作しています。

また、ADCについても値を読み取れることを確認しました。

作成したファイルは、以下のプルリクエストで公開しています。

stm32: Add support for STM32L432KC and NUCLEO_L432KC by boochow · Pull Request #4330 · micropython/micropython

(2018/12/6追記:正式にマージされました。)

コメント

  1. 白阪一郎 より:

    micropythonへの新しいハードウェアドライバの追加方法を探して、ここにたどり着きました。
    新しいハードウェアドライバの開発環境はどのようにされていますか?例えばSTM32 portでDMA等を使ったドライバを作るケースで、micropythonソースにあるdma.cやpin.cを使うとしたらこれらをSTM32のIDEにある同様のライブラリの代わりにこれを使うように開発環境を作るようにするのでしょうか。ネットを探してもハードウェア依存しないようなモジュールの追加例しかなく悩んでいました。
    宜しくお願いします。

  2. boochow より:

    はい、MicroPythonの既存のコードが触っているハードウェアを自分のコードでも触りたい場合は、既存のコードの内容を把握した上で自分のコードを追加する必要があると思います。私自身はハードウェアリソースを既存コードとシェアする実装はしたことがないですが。
    以前NUCLEO-F767ZIのethernetドライバを書きましたが、そのときはハードウェアは既存のコードが触っていなかったので、呼び出し側(networkモジュール)とのインタフェースだけを考慮すれば済みました。
    開発環境を作るということはしていません。MicroPythonのportsそのものに手を加えていく感じです。

  3. 白阪一郎 より:

    早速回答ありがとうございます。
    確かに既存ドライバを調べて使うか、新たに使い慣れたソフトで固めて作るかですね。
    やりたいことは色々あるので効率良くやる手の先例がないのかお聞きしました。
    ありがとうございます。