chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

MicroPython用の拡張ライブラリをCで作る

当初、STEVAL-DRONE01の制御プログラムをRUSTで開発しようとしたが、コンパイル言語で試行錯誤するのに疲れて、MicroPythonで作ることにした。STEVAL-DRONE01のFlashメモリが小さいので、MicroPythonの機能を極限まで削減してビルドした結果、STEVAL-DRONE01用にビルドしたMicroPythonではファイルシステムが使えない。だから、、毎回PCからCopy&PasteでSTEVAL-DRONE01上のMicroPythonにプログラムを送る必要がある。これもなかなか手間であり、FIXしたプログラムはMicroPythonのネイティブコードに組み込ませることで、Copy&Pasteする量が減らせられる。
そのためには、FIXしたコードを拡張ライブラリに移す必要がある。詳細には理解できていないが、解説記事等を読んで調べた結果、所定の書き方に従うことで、モジュールやクラスが定義できることが分かった。ソースコードの裏側ではマクロがいろいろ動いているようなのだが、マクロの細かい動作までは調べていない。だから、、少し書き方を間違えると致命的に大量にエラーが出る。一旦エラーが出ると、マクロがどう動いているのか?が分かっていないので直しようがない。だから、、コンパイルが通ったコードは凍結して、そこからブランチで試作、試作がエラーを起こしたら、凍結コードに戻って再度ブランチというやり方でコードを作った(何しろエラーメッセージを見てもなぜundefined symbolになるのか理解できず、ソースコードをどう直したらいいのか見当がつかない)。試行錯誤により、stevalライブラリと、STM32_LEDクラスのひな型まではできた。拡張ライブラリのソースコードは以下

#include "py/runtime.h"


extern const mp_obj_type_t stm32_led_type;

/* ------------------------------------------------- */
/*   Module: stdrone                                 */
/* ------------------------------------------------- */

STATIC const mp_rom_map_elem_t stdrone_module_globals_table[] = {
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_stdrone) },
    { MP_ROM_QSTR(MP_QSTR_STM32_LED), MP_ROM_PTR(&stm32_led_type) },
};

STATIC MP_DEFINE_CONST_DICT(stdrone_module_globals, stdrone_module_globals_table);

const mp_obj_module_t stdrone_user_cmodule = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t *)&stdrone_module_globals,
};

MP_REGISTER_MODULE(MP_QSTR_stdrone, stdrone_user_cmodule, 1);


/* ------------------------------------------------- */
/*    Class: STM32_LED                               */
/*    init: STM32_LED()  //number: 0/1               */
/*    method:                                        */
/*               set(mode)   mode:0/1                */
/*               on()        ret:1                   */
/*               off()       ret:0                   */
/*               status()    ret:status:1/0          */
/* ------------------------------------------------- */


typedef struct _stm32_led_obj_t {
    mp_obj_base_t base;
} stm32_led_obj_t;

STATIC const stm32_led_obj_t stm32_led = {{&stm32_led_type}};

STATIC mp_obj_t stm32_led_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {

    // parse arguments
    return MP_OBJ_FROM_PTR(&stm32_led);
}

STATIC mp_obj_t stm32_led_set(mp_obj_t self_in,mp_obj_t arg1) {
  uint8_t value = 1;
  // write code  here
  return mp_obj_new_int(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(stm32_led_set_obj, stm32_led_set);


STATIC mp_obj_t stm32_led_on(mp_obj_t self_in) {
  uint8_t value = 1;
  // write code  here
  return mp_obj_new_int(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_on_obj, stm32_led_on);


STATIC mp_obj_t stm32_led_off(mp_obj_t self_in) {
  uint8_t value = 0;
  // write code  here
  return mp_obj_new_int(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_off_obj, stm32_led_off);

STATIC mp_obj_t stm32_led_status(mp_obj_t self_in) {
  uint8_t value = 0;
  // write code  here
  return mp_obj_new_int(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_status_obj, stm32_led_status);


STATIC const mp_rom_map_elem_t stm32_led_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&stm32_led_set_obj) },
    { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&stm32_led_on_obj) },
    { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&stm32_led_off_obj) },
    { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&stm32_led_status_obj) },
};

STATIC MP_DEFINE_CONST_DICT(stm32_led_locals_dict, stm32_led_locals_dict_table);

const mp_obj_type_t stm32_led_type = {
    { &mp_type_type },
    .name = MP_QSTR_LED,
    .make_new = stm32_led_make_new,
    .locals_dict = (mp_obj_dict_t *)&stm32_led_locals_dict,
};

本当は、DRONE上にはLEDが2つあって、どちらのLEDを制御するのか、インスタンス生成時の初期化メソッドで指定したいのだが、一旦上記で凍結。LEDを制御するためのコードはまだ書いていない。ひな型まで。
上記ソースをモジュールとして組み込んでビルドした動作結果は以下

MicroPython v1.16-243-g8c4ba575f-dirty on 2021-11-03; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> help('modules')
__main__          stdrone           uheapq            uselect
_thread           termios           uio               usocket
btree             uarray            ujson             ussl
builtins          ubinascii         umachine          ustruct
cmath             ucollections      uos               usys
ffi               ucryptolib        upip              utime
gc                uctypes           upip_utarfile     utimeq
math              uerrno            urandom           uwebsocket
micropython       uhashlib          ure               uzlib
Plus any modules on the filesystem

>>> import stdrone

>>> dir(stdrone)
['__class__', '__name__', 'STM32_LED']

>>> led = stdrone.STM32_LED()

>>> dir(led)
['__class__', 'set', 'off', 'on', 'status']

>>> led.on()
1
>>> led.off()
0
>>> led.status()
0
>>>

■備考
MicroPythonの拡張モジュールを開発するには、あらかじめ用意されたマクロを使いながらモジュールやクラスを定義するのだが、、この順番を勝手に入れ替えるとエラーになる。正しい順番は以下

STATIC mp_obj_t stm32_led_status(mp_obj_t self_in) {
  uint8_t value = 0;
  // write code  here
  return mp_obj_new_int(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_status_obj, stm32_led_status);

これを順番入れ替えて勝手に以下とすると、、

STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_status_obj, stm32_led_status);
STATIC mp_obj_t stm32_led_status(mp_obj_t self_in) {
  uint8_t value = 0;
  // write code  here
  return mp_obj_new_int(value);
}

シンボルが見つかりませんのエラーになる。。なぜなんだろうか??C言語にシンボルの定義順番なんてあるのだろうか??コンパイルの最後にラベルに対してリンカでアドレス解決するはずなのに。。原因が理解できず

                 from ../../../mymodule/mymodule1/stdrone.c:1:
../../../mymodule/mymodule1/stdrone.c:71:56: error: ‘stm32_led_status’ undeclared here (not in a function); did you mean ‘stm32_led_status_obj’?
   71 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(stm32_led_status_obj, stm32_led_status);
      |                                                        ^~~~~~~~~~~~~~~~
../../py/obj.h:337:42: note: in definition of macro ‘MP_DEFINE_CONST_FUN_OBJ_1’
  337 |     {{&mp_type_fun_builtin_1}, .fun._1 = fun_name}
      |                                          ^~~~~~~~
../../../mymodule/mymodule1/stdrone.c:72:17: error: ‘stm32_led_status’ defined but not used [-Werror=unused-function]
   72 | STATIC mp_obj_t stm32_led_status(mp_obj_t self_in) {
      |                 ^~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make: *** [../../py/mkrules.mk:77: build-standard/mymodule1/stdrone.o] Error 1