MicroPythonにPinクラスを追加する(RPiベアメタル)

pinclass.png

少しずつ進めているRaspberry Pi用ベアメタルMicroPythonですが、今日は基本中の基本のPinクラスを実装し、Lチカができるようになりました。
I2CやSPIを実装するにも、まずどのPinを使うか指定する必要がありますので、とにかくPinクラスが使えるようにしなければなりません。

クラスの定義はモジュールの定義と似ています。
モジュールの定義方法は、下記の記事で以前紹介した通りです。

MicroPythonからラズパイのフレームバッファへ描画する: 楽しくやろう。

これと対比してクラスの定義の記述の仕方をまとめておきます。

●クラスの追加
・新しいクラスが所属するモジュールのためのCソースファイルを(無ければ)作り、Makefileに追加する。
・Cソースの中で、mp_obj_type_t型(構造体)の定数を宣言する。これがクラスの実体となる。メンバ変数はいろいろあるが(py/obj.hで定義されている)、base(この定数自体の型)、name(クラス名)、print(REPLでオブジェクトを文字列として出力する)、make_new(新しいインスタンスを作る)が必須。locals_dict(インスタンスが共有するクラス変数やインスタンスメソッドのリスト)もほぼ必須。baseは必ず&mp_type_typeとする。(mp_type_typeはobjtype.cの中で定義されている定数で、型を作るクラス、つまりメタクラス。)
・このクラスが所属するモジュールの定義の中で、そのモジュール内のグローバルオブジェクトのリストであるmp_rom_map_elem_t型の定数に

{ MP_ROM_QSTR(MP_QSTR_クラス名), MP_ROM_PTR(&上記の定数) }

を追加する。

●クラス内グローバルオブジェクトの追加
・mp_rom_map_elem_t型の定数配列を宣言する。これがクラス内グローバルオブジェクトのリストとなる。
・MP_DEFINE_CONST_DICT(定数名,上記の定数配列の名前)でmp_obj_dict_t型の定数を宣言する。
・mp_obj_dict_t型の定数を、クラスを表すmp_obj_type_t型の定数(上記で定義済み)のlocals_dictからポイントする。
・グローバルオブジェクトのリストへ、

{ MP_ROM_QSTR(MP_QSTR_オブジェクト名), マクロ(オブジェクトの実体) }

という形式でオブジェクトを追加する。
「マクロ」はオブジェクトの実体がintならMP_ROM_INT(整数値)、それ以外ならMP_ROM_PTR(ポインタ)。

●インスタンスの定義とインスタンスメソッドの追加
・インスタンスを表す型を構造体として定義する。このとき、最初のメンバ変数は必ず

mp_obj_base_t base;

とし、その値は常にクラスを表すmp_obj_type_t型の定数とする。
・コンストラクタは、クラス・positional argsの個数・keyword argsの個数・引数の配列を受け取り、インスタンスのためのメモリを確保し、初期化してmp_obj_t型を返す関数として定義する。
その関数へのポインタを、mp_obj_type_t型のメンバ変数make_newへ代入する。
メモリ確保のための手段はpy/misc.hに各種定義されている。mallocは使えないことに注意。最もシンプルな方法としては、インスタンスを表す構造体の型をmyobj_tとすると、m_new_obj(myobj_t)でインスタンスオブジェクトが生成できる。
・インスタンスメソッドの実体は、mp_obj_t型を返す関数として定義する。
・マクロMP_DEFINE_CONST_FUN_OBJ_XXで関数をMicroPythonのオブジェクトとして定義する。XXの部分は関数の引数の個数により異なる。(0~3:0個~3個、VAR:可変長、VR_BETWEEN:最小個数と最大個数が決まっている、KW:キーワード=値の形式を用いる)
・グローバルオブジェクトのリストmp_rom_map_elem_t型配列へ

 { MP_ROM_QSTR(MP_QSTR_関数名), MP_ROM_PTR(&関数名オブジェクト名) }

を追加する

こんな感じです。インスタンスメソッドの追加はモジュールへの関数追加と全く同じですね。
以下のリンクも参照してください。

Adding a Module — MicroPython Development Documentation 1.0 documentation

今回はPinクラスを定義することが主眼ですが、MicroPythonのお作法では、Pin、I2C、SPIといったハードウェア寄りのクラスはmachineモジュールが持つようになっていますので、machineモジュールも定義しています。
追加したコードはこんな感じです。

added Pin class (machine.Pin), init(), value(), constants(IN, OUT, PU… · boochow/micropython-raspberrypi@e744494

Pinのインスタンスの実体は構造体(machine_pin_obj_t型として定義)ですが、インスタンスと物理的なGPIOとが1対1に対応しますので、無限にインスタンスを作ることはできません。
その代わり、スタティック変数として構造体のGPIOのピン個数(54個)分の配列を用意し、コンストラクタはPin番号に応じてこの配列のいずれかを返すようになっています。

他に初期化(init)と値の読み書き(value)の2つのインスタンスメソッドを定義しています。
これらのメソッドはGPIOレジスタにアクセスして処理を行います。
レジスタの操作方法については、以下の記事で紹介済みですので省略します。

Raspberry Pi Zero WでベアメタルLチカ: 楽しくやろう。
Raspberry Pi Zero + ベアメタルMicroPythonでのLチカ: 楽しくやろう。

上記で触れていない、入力ピンとして使用する場合のプルアップ・プルダウンについては以下のリンクが参考になりました。

bare metal – Modifying GPIO memory for one pin turns multiple on – Raspberry Pi Stack Exchange

現状では、Pi Zero WのLEDの点灯・消灯がGPIO47への出力(0で点灯)と入力(LEDを通じてVCCに接続されているので1になる)ができることが確認できています。

Raspberry PiのGPIOには、シンプルな入出力のほかに、SPIやI2Cとして使うためのAlternate Functionの指定や、割り込み、クロック出力などの機能があります。
いずれ、これらの機能のサポートをPinクラスに追加していきたいと思います。

コメント