chakokuのブログ(rev4)

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

Rustでドローン制御プログラムを書いてるけど、TurnAroundTimeが長くてしんどい->試作はMicroPythonに変更

タイトルの通りで、Rustで試行錯誤するのはコンパイル、ロード、デバッグの時間がかかり、細かいエラーも出る状況で、コンパイル言語による試作がつらくてテンションが下がっている(ドローンのプロペラの風切り音がうるさすぎるというのもある)。当初の方針を変更して、制御プログラムの初期段階の試作はMicroPythonで行うことにする。
MicroPythonを再度ビルドする

$ uname -a
Linux DESKTOP-TRNV8F8 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ pwd
/home/<uid>/lang/up/stm32/src/micropython/ports/stm32
$ make BOARD=NUCLEO_F401CC V=1 DEBUG=1

arm-none-eabi-size build-NUCLEO_F401CC/firmware.elf
       text    data     bss     dec        hex        filename
 237472      12   20184  257668   3ee84     build-NUCLEO_F401CC/firmware.elf
GEN build-NUCLEO_F401CC/firmware0.bin
arm-none-eabi-objcopy -O binary -j .isr_vector build-NUCLEO_F401CC/firmware.elf build-NUCLEO_F401CC/firmware0.bin
GEN build-NUCLEO_F401CC/firmware1.bin
arm-none-eabi-objcopy -O binary -j .text -j .data -j .ARM build-NUCLEO_F401CC/firmware.elf build-NUCLEO_F401CC/firmware1.bin
GEN build-NUCLEO_F401CC/firmware.dfu
python3 ../../tools/dfu.py -D 0x0483:0xDF11 -b 0x08000000:build-NUCLEO_F401CC/firmware0.bin -b 0x08020000:build-NUCLEO_F401CC/firmware1.bin build-NUCLEO_F401CC/firmware.dfu
GEN build-NUCLEO_F401CC/firmware.hex
arm-none-eabi-objcopy -O ihex build-NUCLEO_F401CC/firmware.elf build-NUCLEO_F401CC/firmware.hex

バイナリサイズは251KBか? Flashが256KBなので、ぎりぎり収まっている。
できたら、センサは3-WireによるSPI制御なので、SPIドライバを改修して、3-Wireモードで動くようにしたい。Cで動かさないとセンサ部分の性能が出ないと思われるので。 サイズ削減のためにFileIOやFlash上のファイルシステムは使えないので、プログラムは毎回Copy&Paste等でDRONEに送り付ける必要がある。
OpenOCD起動

./bin-x64/openocd.exe -f ./scripts/interface/stlink-v2.cfg -f ./scripts/target/stm32f4x.cfg

GDBからOpenOCDに接続してファームをロード

$ pwd
/home/<uid>/lang/up/stm32/src/micropython/ports/stm32/build-NUCLEO_F401CC

$ /usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin/arm-none-eabi-gdb.exe firmware.elf
GNU gdb (GNU Tools for ARM Embedded Processors) 7.6.0.20131129-cvs
Copyright (C) 2013 Free Software Foundation, Inc.

(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x00000000 in ?? ()

(gdb) monitor reset halt
Unable to match requested speed 2000 kHz, using 1800 kHz
Unable to match requested speed 2000 kHz, using 1800 kHz
adapter speed: 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20010000

(gdb) load
Loading section .isr_vector, size 0x1184 lma 0x8000000
Loading section .text, size 0x38e14 lma 0x8005000
Loading section .ARM, size 0x8 lma 0x803de14
Loading section .data, size 0xc lma 0x803de1c
Start address 0x802d968, load size 237484
Transfer rate: 32 KB/sec, 11308 bytes/write.

一応焼けた。

>>>
MPY: soft reboot
MicroPython v1.16-243-g8c4ba575f-dirty on 2021-10-30; STEVAL-DRONE01 with STM32F401CC
Type "help()" for more information.

>>> help()
Welcome to MicroPython!
For online help please visit http://micropython.org/help/.

Quick overview of commands for the board:
  pyb.info()    -- print some general information
  pyb.delay(n)  -- wait for n milliseconds
  pyb.millis()  -- get number of milliseconds        Switch methods: (), callback(f)
  pyb.LED(n)    -- create an LED object for  0bthardware random number
  pyb.Servo(n)  -- create Servo object for servo n (n=1, -itrrupt a running program
  CTRL-D        -- on a blank line, do a soft reset of

>>> help("modules")
__main__          math              uctypes           urandom
_onewire          micropython       uerrno            uselect
builtins          pyb               uio               ustruct
cmath             uarray            umachine          usys
gc                ucollect

>>> dir(machine)
['__class__', '__name__', 'ADC', 'DEEPSLEEP_RESET', 'HARD_RESET', 'I2C', 'PWRON_RESET', 'Pin', 'RTC', 'SOFT_RESET', 'SPI', 'Signal', 'SoftI2C', 'SoftSPI', 'Timer', 'UART', 'WDT', 'WDT_RESET', 'bootloader', 'deepsleep', 'disable_irq', 'enable_irq', 'freq', 'idle', 'info', 'lightsleep', 'mem16', 'mem32', 'mem8', 'reset', 'reset_cause', 'sleep', 'soft_reset',

>>> dir(pyb)
['__class__', '__name__', 'main', 'ADC', 'ADCAll', 'ExtInt', 'LED', 'Pin', 'RTC', 'SPI', 'Switch', 'Timer', 'UART', 'country', 'dht_readinto', 'disable_irq', 'enable_irq', 'fault_debug', 'repl_info', 'repl_uart', 'wfi']

PWMはどこに行ったのだろうか。。

MicroPythonのManualによると、、pyb.Timerを使ってPWMを構成するようだ。

pyb.Pin と pyb.Timer を参照:

from pyb import Pin, Timer
p = Pin('X1')                            # X1 は TIM2, CH1 を持ちます
tim = Timer(2, freq=1000)
ch = tim.channel(1, Timer.PWM, pin=p)
ch.pulse_width_percent(50)

pyboard 用クイックリファレンス — MicroPython 1.17 ドキュメント

MicroPythonのPinクラス、Timerクラスを組み合わせてPWMにより4基のモータを制御するサンプル

from pyb import Pin, Timer

pin_m1 = Pin(Pin.board.PB6) 
motor1 = Timer(4, freq=500).channel(1, Timer.PWM, pin=pin_m1)
motor1.pulse_width_percent(0)

pin_m2 = Pin(Pin.board.PB7) 
motor2 = Timer(4, freq=500).channel(2, Timer.PWM, pin=pin_m2)
motor2.pulse_width_percent(0)

pin_m3 = Pin(Pin.board.PB8) 
motor3 = Timer(4, freq=500).channel(3, Timer.PWM, pin=pin_m3)
motor3.pulse_width_percent(0)

pin_m4 = Pin(Pin.board.PB9) 
motor4 = Timer(4, freq=500).channel(4, Timer.PWM, pin=pin_m4)
motor4.pulse_width_percent(0)

motor4.pulse_width_percent(0)は0%なので、これを10等にすると10%の出力になる。
PinとTimerの組み合わせは決まっており、ハードウエア仕様書か、stm32f401_af.csvを参照して正しい組み合わせを指定する必要あり。間違っているとAFエラーになる(例:ValueError: Pin(B5) doesn't have an af for Timer(1))。


Python/MicroPythonには自作モジュールを追加する仕組みがあり、それを試してみる。
ユーザ開発用サンプルのexamples/usercmoduleがあるので、これをmicropythonのソースツリーの外側に配置して、make時にUSER_C_MODULESとして指定して組み込んでみる。

$ make BOARD=NUCLEO_F401CC USER_C_MODULES=../../../mymodule V=1 DEBUG=1

sampleソースを修正して、stdroneモジュールに修正、再ビルド。実行した結果、stdroneモジュールが見えている

$ make BOARD=NUCLEO_F401CC USER_C_MODULES=../../../mymodule V=1 DEBUG=1
>>> import stdrone
>>> dir(stdrone)
['__class__', '__name__', 'add_ints']
>>> stdrone.add_ints(1,2)
3

今の状態だと、stdroneモジュール内に関数が直接置かれている状態なので、、この状態からSPI3W(SPI 3Wireモード)クラス等を作って、init / read / write 等に仕立てる必要がある。