chakokuのブログ(rev4)

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

STM32F401CC用にMicroPythonをビルドするがサイズがでかくてFlashに乗らない

先人の情報を参考に、普通にNUCLEO_F401REとしてビルドしてみる。使った環境は、WSL上のUbuntu

$ 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

sudo apt-get install libffi-dev gcc-arm-none-eabi

cd ~/lang/up/stm32/src
git clone https://github.com/micropython/micropython

cd micropython
make -C mpy-cross
cd ports/stm32
make BOARD=NUCLEO_F401RE

なぜか分からないがエラーになった。HAL_Driverが無いと怒られているような。。

LINK build-NUCLEO_F401RE/firmware.elf
arm-none-eabi-ld: cannot find build-NUCLEO_F401RE/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.o: No such file or directory
arm-none-eabi-ld: cannot find build-NUCLEO_F401RE/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc.o: No such file or directory
arm-none-eabi-ld: cannot find build-NUCLEO_F401RE/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc_ex.o: No such file or directory
arm-none-eabi-ld: cannot find build-NUCLEO_F401RE/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.o: No such file or directory
略

同じコマンドをもう一度実行したらHALのライブラリ?をコンパイルして、ビルドまで終わった。

$ make BOARD=NUCLEO_F401RE
*略*
LINK build-NUCLEO_F401RE/firmware.elf
   text    data     bss     dec     hex filename
 301872      12   37408  339292   52d5c build-NUCLEO_F401RE/firmware.elf
GEN build-NUCLEO_F401RE/firmware0.bin
GEN build-NUCLEO_F401RE/firmware1.bin
GEN build-NUCLEO_F401RE/firmware.dfu
GEN build-NUCLEO_F401RE/firmware.hex

本来なら、ST-LINK等で、dfuかbinを焼くのだろう。。が、、UARTのポートが違ってるので修正が必要。

boards/NUCLEO_F401CCのディレクトリを作って、mpconfigboard.hを修正

$ diff ../NUCLEO_F401RE/mpconfigboard.h ./mpconfigboard.h
23a24,25
> #define MICROPY_HW_UART1_TX     (pin_A9)
> #define MICROPY_HW_UART1_RX     (pin_A10)
26,30c28,29
< #define MICROPY_HW_UART6_TX     (pin_C6)
< #define MICROPY_HW_UART6_RX     (pin_C7)
< // UART 2 connects to the STM32F103 (STLINK) on the Nucleo board
< // and this is exposed as a USB Serial port.
< #define MICROPY_HW_UART_REPL        PYB_UART_2
---
>
> #define MICROPY_HW_UART_REPL        PYB_UART_1

いけるかどうかわからないけど、以下でビルドしてみる

$ make BOARD=NUCLEO_F401CC
$ make BOARD=NUCLEO_F401CC

  *略*
LINK build-NUCLEO_F401CC/firmware.elf
   text    data     bss     dec     hex filename
 301900      12   37408  339320   52d78 build-NUCLEO_F401CC/firmware.elf
GEN build-NUCLEO_F401CC/firmware0.bin
GEN build-NUCLEO_F401CC/firmware1.bin
GEN build-NUCLEO_F401CC/firmware.dfu
GEN build-NUCLEO_F401CC/firmware.hex

エラー出ずにビルドできた。次はST-Linkを使って上記ファームを焼く

かつて、ST-Linkを使ってgdbとか動かしていたが、細かいことを忘れたので、ST-Link Utilityを使ってみる。
http://tri-s.world.coocan.jp/tri_S_Reference/ST_LINK/HowToUse_ST_LINK.pdf
STSW-LINK004|デザイン/サポート|STM32, STM8ファミリはSTの32bit/8bit汎用マイクロコントローラ製品


ST-Link Utilityを使って ST-Link経由でSTVAL-DRONE01上のSTM32F401CCにファームを焼こうとしたら、サイズがでかすぎると怒られた

20:43:48 : ST-LINK SN : 1847000000000000000
20:43:48 : V2J29S7
20:43:48 : Connected via SWD.
20:43:48 : SWD Frequency = 4,0 MHz.
20:43:48 : Connection mode : Normal.
20:43:48 : Debug in Low Power mode enabled.
20:43:48 : Device ID:0x423 
20:43:48 : Device flash Size : 256KBytes
20:43:48 : Device family :STM32F401xB/C
20:46:51 : [firmware.hex] opened successfully.
                  Address Ranges [0x08000000 0x080039D0] [0x08020000 0x08066188] 
20:46:51 : [firmware.hex] checksum : 0x01BD7F18 
20:47:43 : File size is bigger than the flash memory size.

Flashサイズが256KBなのだが、MicroPythonファームが294KBであった。はみ出ている。
機能削減して減らすか、最適化コンパイルして減らせられないものだろうか。。

ポーティングしているボードにはリソースの小さいのもあり、STM32L073RZT6の場合、flashメモリが192KBらしい。
NUCLEO_L073RZ/mpconfigboard.h
この場合、以下の機能が外されている。

#define MICROPY_EMIT_THUMB          (0)
#define MICROPY_EMIT_INLINE_THUMB   (0)
#define MICROPY_OPT_COMPUTED_GOTO   (0)
#define MICROPY_PY_BUILTINS_COMPLEX (0)
#define MICROPY_PY_GENERATOR_PEND_THROW (0)
#define MICROPY_PY_MATH             (0)
#define MICROPY_PY_FRAMEBUF         (0)
#define MICROPY_PY_USOCKET          (0)
#define MICROPY_PY_NETWORK          (0)
#define MICROPY_PY_ONEWIRE          (0)
#define MICROPY_PY_STM              (0)
#define MICROPY_PY_PYB_LEGACY       (0)
#define MICROPY_PY_UHEAPQ           (0)
#define MICROPY_PY_UTIMEQ           (0)
#define MICROPY_PY_MACHINE_BITSTREAM (0)
#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
#define MICROPY_HW_ENABLE_ADC       (0)

上記を参考にmpconfigboard.hを定義すればサイズが縮小化されるのでは?と期待

サイズ確認のため普通にビルドしてみる

make BOARD=NUCLEO_L073RZ

以下の通り、201KBに収まっているように思える。

$ make BOARD=NUCLEO_L073RZ
  *略*
LINK build-NUCLEO_L073RZ/firmware.elf
   text    data     bss     dec     hex filename
 194784      68   11748  206600   32708 build-NUCLEO_L073RZ/firmware.elf
GEN build-NUCLEO_L073RZ/firmware.bin
GEN build-NUCLEO_L073RZ/firmware.dfu
GEN build-NUCLEO_L073RZ/firmware.hex

このファームだとエラーなく焼くことができた。(が、MicroPythonのREPL用のUARTポートが違っているのでこのままではPCとつながらない)

そのままdisable定義をCopyするだけだとビルド中にundefineシンボルとかでエラーになる。ソースは変更したくないので、エラーになった場合はenableに戻しながらコンパイルを通した。結果、以下のサイズにしかならず、これではサイズがでかいのでファームが焼けない。

$ make BOARD=NUCLEO_F401CC

LINK build-NUCLEO_F401CC/firmware.elf
   text    data     bss     dec     hex filename
 256088       8   37140  293236   47974 build-NUCLEO_F401CC/firmware.elf
GEN build-NUCLEO_F401CC/firmware0.bin
GEN build-NUCLEO_F401CC/firmware1.bin
GEN build-NUCLEO_F401CC/firmware.dfu
GEN build-NUCLEO_F401CC/firmware.hex

というわけで、build-NUCLEO_L073RZをベースにUARTのポートだけを書き換えてビルドしてみる
しかし、このやり方はSTM32のマイコンシリーズの系統を無視したビルドで、本来は以下のように定義されているL072/L073シリーズ用のピンマッピング情報やライブラリを無理にF401に適用している。だから??ピンアサイン(ピン多重化された機能の定義)とか無茶苦茶になっている可能性がある。

MCU_SERIES = l0
CMSIS_MCU = STM32L073xx
AF_FILE = boards/stm32l072_af.csv
LD_FILES = boards/stm32l072xz.ld boards/common_basic.ld

REPL用UARTの定義だけ修正してビルドした。先ほどのビルドと同様にFlashに乗りそうなサイズに収まった。

$ make BOARD=NUCLEO_L073RZ_KAI

LINK build-NUCLEO_L073RZ_KAI/firmware.elf
   text    data     bss     dec     hex filename
 194784      68   11748  206600   32708 build-NUCLEO_L073RZ_KAI/firmware.elf
GEN build-NUCLEO_L073RZ_KAI/firmware.bin
GEN build-NUCLEO_L073RZ_KAI/firmware.dfu
GEN build-NUCLEO_L073RZ_KAI/firmware.hex

フラッシュは焼けた。あとは、、MicroPythonが正しく動いているかどうか。ST-Link + OpenOCDの組み合わせでデバッグもできるので、あまりビビる必要はないけど。。
コネクタP7からREPLのシリアルが出ているはずなのだが、応答がない。というわけで、、デバッグ
昔の記事と手元のメモを読み返しながら、、openocdを起動、ST-Link経由でターゲットと接続

$ ./bin-x64/openocd.exe -f ./scripts/interface/stlink-v2.cfg  -f ./scripts/target/stm32f4x.cfg
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override u
se 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results mi
ght differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v29 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.152424
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
$ ./arm-none-eabi-gdb.exe

(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: 0x0802296c msp: 0x20004ff8

(gdb) info register
r0             0x0      0
r1             0x0      0
r2             0x0      0
r3             0x0      0
r4             0x0      0
r5             0x0      0
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x0      0
r12            0x0      0
sp             0x0      0x0
lr             0x0      0
pc             0x0      0x0
xPSR           0x0      0

読み取れる値が0ばっかりなのだが、、ターゲットとの接続エラーになっている??
OpenOCD側のログ

target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x0802296c msp: 0x20004ff8

gdbからもう一度haltをしてみる

(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: 0x0802296c msp: 0x20004ff8

(gdb) x/32w 0
 0x0:    0x020004ff8      0x0802296d      0x0801dc01      0x0801dbf1
0x10:   0x00000000      0x00000000      0x00000000      0x00000000
0x20:   0x00000000      0x00000000      0x00000000      0x0801dc03
0x30:   0x00000000      0x00000000      0x0801df01      0x0801df31
0x40:   0x08022871      0x0801dc05      0x0801dc11      0x08022871
0x50:   0x08022871      0x0801dc65      0x0801dc75      0x0801dc85
0x60:   0x08022871      0x0801facd      0x0801fae1      0x0801fb01
0x70:   0x08022871      0x08022871      0x0801dcd3      0x0801dc97


(gdb) info register
r0             0x20004ff8       536891384
r1             0x0      0
r2             0x0      0
r3             0x0      0
r4             0x0      0
r5             0x0      0
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x0      0
r12            0x0      0
sp             0x20004ff8       0x20004ff8
lr             0xffffffff       -1
pc             0x8022972        0x8022972
xPSR           0x1000000        16777216

(gdb) x/32w $pc
0x8022972:      0x4a0c490c      0xe0034b0d      0x31046808      0x32046010
0x8022982:      0xd3f9429a      0x49092000      0xe0014a0a      0x31046008
0x8022992:      0xd3fb4291      0xff6df7ff      0xf7fa4620      0x4ff8ff7a
0x80229a2:      0xf8e02000      0x00000802      0x00442000      0x00442000
0x80229b2:      0x06282000      0x60042000      0x60866045      0x464160c7
0x80229c2:      0x46496101      0x46516141      0x46596181      0x466161c1
0x80229d2:      0x46696201      0x46686241      0xb4024770      0x08494671
0x80229e2:      0x56090049      0x448e0049      0x4770bc02      0xb40246c0

ぱっと見た目にはそれらしいコードを実行しようとしているように思えるのだが・・Flashをダンプ、逆アセンブルしている段階で、シンボルがないしソースもないので、これだけでは分からん。シンボル情報のロードが必要か。

(gdb) disas $pc,+32
Dump of assembler code from 0x8022972 to 0x8022992:
=> 0x08022972:  ldr     r1, [pc, #48]   ; (0x80229a4)
   0x08022974:  ldr     r2, [pc, #48]   ; (0x80229a8)
   0x08022976:  ldr     r3, [pc, #52]   ; (0x80229ac)
   0x08022978:  b.n     0x8022982
   0x0802297a:  ldr     r0, [r1, #0]
   0x0802297c:  adds    r1, #4
   0x0802297e:  str     r0, [r2, #0]
   0x08022980:  adds    r2, #4
   0x08022982:  cmp     r2, r3
   0x08022984:  bcc.n   0x802297a
   0x08022986:  movs    r0, #0
   0x08022988:  ldr     r1, [pc, #36]   ; (0x80229b0)
   0x0802298a:  ldr     r2, [pc, #40]   ; (0x80229b4)
   0x0802298c:  b.n     0x8022992
   0x0802298e:  str     r0, [r1, #0]
   0x08022990:  adds    r1, #4
End of assembler dump.

continueでしばらく実行してCtrl-Cでスタックを確認するとどうもおかしい。暴走しているのか?

(gdb) cont
Continuing.
The target is not running when halt was requested, stopping GDB.

Program received signal SIGINT, Interrupt.
0x0802297a in ?? ()
=> 0x0802297a:  00 00   movs    r0, r0
(gdb) where
#0  0x0802297a in ?? ()
#1  0xffffffff in ?? ()
#2  0xffffffff in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

ロードからもう一度やり直す(デバイスビジネス開拓団様の記事を参考)

$ /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.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i586-mingw32 --target=arm-none-eabi".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from C:\cygwin64\home\<user_name>\lang\up\stm32\src\micropython\ports\s
tm32\build-NUCLEO_L073RZ_KAI\firmware.elf...done.

(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x0802296c in Reset_Handler ()

(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: 0x0802296c msp: 0x20004ff8, semihosting

(gdb) i r
r0             0x0      0
r1             0x0      0
r2             0x0      0
r3             0x0      0
r4             0x0      0
r5             0x0      0
r6             0x0      0
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x0      0
r11            0x0      0
r12            0x0      0
sp             0x20004ff8       0x20004ff8
lr             0xffffffff       -1
pc             0x802296c        0x802296c <Reset_Handler>
xPSR           0x1000000        16777216

(gdb) load
Loading section .isr_vector, size 0xc0 lma 0x8000000
Loading section .text, size 0x2f818 lma 0x80000c0
Loading section .ARM, size 0x8 lma 0x802f8d8
Loading section .data, size 0x44 lma 0x802f8e0
Start address 0x802296c, load size 194852
Transfer rate: 28 KB/sec, 10255 bytes/write.

(gdb) hbreak main
Function "main" not defined.
Make hw breakpoint pending on future shared library load? (y or [n]) y

Hardware assisted breakpoint 1 (main) pending.

(gdb) where
#0  0x0802296c in Reset_Handler ()

(gdb) cont
Continuing.
The target is not running when halt was requested, stopping GDB.

Program received signal SIGINT, Interrupt.
0x0802296c in Reset_Handler ()

(gdb) where
#0  0x0802296c in Reset_Handler ()
(gdb)

ビルドの情報を詳細に表示するためと、デバッグ情報を付け加えるために、以下でビルド

make BOARD=NUCLEO_L073RZ_KAI V=1 DEBUG=1

なお、、リンカオプションもL073RZのままだとメモリマップもおかしいので、configを作り直した。

■参考URL

micropythonをはじめよう(STM32F401編) - 寝台急行はまなす
ARM Mbed その5 OpenOCD+GDB | デバイスビジネス開拓団

MicroPythonを使ってジャイロセンサを評価する

クオータニオンの理解*1が最後まで終っていないが、、クオータニオンがある程度わかっても、ドローンを制御するには、ジャイロセンサのXYZ軸変換とか、カルマンフィルタとかまだまだ技術の壁があることが分かった。で、、少し動かしながらセンサ類のデータ処理を理解したいと思い、ジャイロセンサとかをMicroPythonで制御したい。ここでいきなりRustだとTurnAroundTimeがハンパない。ESP32(MicroPython)と9DoFの組み合わせで勉強するか、あるいは、STVAL-DRONE01にMicroPythonを入れて、ボードに貼り付けられている6軸センサ(LSM6DSL)を制御するか。。どうしたものか。STVAL-DRONE01上のマイコンはSTM32F401CCというやつなんだが、あまりリソースが無い(SRAM:64KB/FlashMemory:256KB)のでMicroPythonが動くのかどうか不明。

MicroPythonはNUCLEO_F401REをサポートしているらしい。NUCLEO_F401REとSTVAL-DRONE01のハード構成と同じだったらそのままビルドしたら流用できそうだが、シリアルポートの定義とかよくわからない。PYB_UART_2ってなぜここでPyBoardが出てくるのだろうか。。

// UART config
#define MICROPY_HW_UART2_TX     (pin_A2)
#define MICROPY_HW_UART2_RX     (pin_A3)
#define MICROPY_HW_UART6_TX     (pin_C6)
#define MICROPY_HW_UART6_RX     (pin_C7)
// UART 2 connects to the STM32F103 (STLINK) on the Nucleo board
// and this is exposed as a USB Serial port.
#define MICROPY_HW_UART_REPL        PYB_UART_2
#define MICROPY_HW_UART_REPL_BAUD   115200

いきなりはハードル高いので、STMicroの評価ボード、NucleoL476RGにMicroPythonを入れてみた。いろんなところで紹介されているので細かいことは省略しますが、、自分の場合、boocho氏の所から、NucleoL476RG用のファームをもらってきて、USB経由でダウンロードしました。ダウンロードが終わると、MicroPythonが走って、USB経由でシリアル接続されます。だから、、シリアルポートに繋がなくても、USB経由(USBの先はUSB変換チップがあってその先はデバッガがあると理解しています)で、MicroPythonのシリアルコンソールと接続できている。が、、これは拡張基板のデバッガが賢いからUSB経由でシリアルが繋げられるのであって、STVAL-DRONE01の基板はUSB変換チップがあるものの、、それを期待していいのかどうか、、上記USB変換チップはレベル変換だけやっていそうで、STM32F401CCからはPA11(USB_DM),PA12(USB_DP)が出ていて、出口ですでにUSB用の信号になっていそうだ。だから。。。MicroPythonファームでUSBサポートしてくれていないと、USB経由ではシリアル接続できない。
NUCLEO_L476RG/mpconfigboard.hから引用していますが、この定義で、拡張ボードのデバッガ経由(USB)でMicroPythonとしてはシリアル接続できることになるのだろうか。。全く分かりません。

// UART config
#define MICROPY_HW_UART2_TX     (pin_A2)
#define MICROPY_HW_UART2_RX     (pin_A3)

#define MICROPY_HW_UART_REPL        PYB_UART_2
#define MICROPY_HW_UART_REPL_BAUD   115200

Nucleoの回路図をざっと見る限り、拡張ボードのデバッガもST-LINK相当のようで、、だったら手持ちのST-LINK互換のデバッガをSTVAL-DRONE01のデバッグポートに繋ぐとMicroPythonのシアルコンソールが繋がるのだろうか・・・そんな甘いモンではないだろう。。
MB1136C_schematic_layout/MB1136.pdf

試しに、ST-LINK互換のデバッガをSTM32の安いボード(デバッガなし)と接続したら、デバイスマネージャ上では単に以下のように出ただけであった。まぁそらそうか。

ユニバーサルシリアルバスデバイス
   STM32 STLink

シリアル出力のDefineが理解できないので、MicroPythonが焼けたとしてもコンソールでは接続できなさそうである。ということで、、STVAL-DRONE01上のSTM32F401CCにMircoPythonを焼く作業は行わず、ESP32+9DoFでジャイロとか姿勢を勉強することにする。もしSTVAL-DRONEのジャイロセンサを扱う時点になったらデータ転送プログラムだけをSTM32F401CCに焼いて、データ処理はESP32かPC側でやってもいいだろう。

■追記
defineを再度見ていて、シリアルコンソールはST-Link経由で接続と書かれていて、確かにボード上もシリアルはST-Linkの拡張デバッガボードに接続されている。使っている足は、PA2(USART_TX),PA3(USART_RX)である。これがPYB_UART_2に相当するのか?? PYB_USART_2ってどこで定義されているのか。。

ちなみに、、Droneに乗っているSTM32F401CCの場合、PA9(USART1_TX),PA10(USART1_RX)がシリアル用接続ピンと繋がっている。

stm32/uart.hで定義されていた。enum型の1始まりで、PYB_UART2は定数2のようである。

typedef enum {
    PYB_UART_NONE = 0,
    PYB_UART_1 = 1,
    PYB_UART_2 = 2,
    PYB_UART_3 = 3,
    PYB_UART_4 = 4,
    PYB_UART_5 = 5,
    PYB_UART_6 = 6,
    PYB_UART_7 = 7,
    PYB_UART_8 = 8,
} pyb_uart_t;

uart.cの初期化関数で、該当UARTが存在するか確認しているので、この辺の定数がボードのレジスタと一致していたら動きそうに思える。STM32側ではUART1と言ってるのだが、UART0もあるのか、レジスタの番地等はマイコンの仕様書で確認が必要。

    switch (uart_id) {
        #if defined(MICROPY_HW_UART1_TX) && defined(MICROPY_HW_UART1_RX)
        case PYB_UART_1: return true;
        #endif

        #if defined(MICROPY_HW_UART2_TX) && defined(MICROPY_HW_UART2_RX)
        case PYB_UART_2: return true;
        #endif

製品仕様書によると、UARTは3本らしい。レジスタマップは以下

0x4001 1400 - 0x4001 17FF USART6
0x4001 1000 - 0x4001 13FF USART1
0x4000 4400 - 0x4000 47FF USART2

stm32f4xx_hal_conf.h

#ifdef HAL_UART_MODULE_ENABLED
 #include "stm32f4xx_hal_uart.h"
#endif /* HAL_UART_MODULE_ENABLED */

#ifdef HAL_USART_MODULE_ENABLED
 #include "stm32f4xx_hal_usart.h"
#endif /* HAL_USART_MODULE_ENABLED */

また戻って来たけど、UART2ならPinA2/A3,UART6ならPinC6/C7だと。
MicroPythonとして指定できるのは、2か6,一方基板で繋がっているのは1だ。
だから、定義の追加が必要だろう。

// UART config
#define MICROPY_HW_UART2_TX     (pin_A2)
#define MICROPY_HW_UART2_RX     (pin_A3)
#define MICROPY_HW_UART6_TX     (pin_C6)
#define MICROPY_HW_UART6_RX     (pin_C7)

ビルドが通るかどうか分からないが、UART1をMicroPythonのコンソールに割り当てるには以下の記述になるかと

// UART config
#define MICROPY_HW_UART1_TX     (pin_A9)
#define MICROPY_HW_UART1_RX     (pin_A10)
#define MICROPY_HW_UART_REPL        PYB_UART_1
#define MICROPY_HW_UART_REPL_BAUD   115200

また、USART6はUQFN48(48pin QuadFlatPackage?)の場合、足が出ていないので使えない。
micropython-esp32/mpconfigboard.h at esp32 · micropython/micropython-esp32 · GitHub

*1:証明を追いかける程度

9DoFによる回転判断と姿勢制御

ドローンの飛行制御プログラムをRustで書くのを目標にクオータニオンを勉強してきた。本に載ってる証明もなるべく追いかけて理解を積み上げたつもりで、そろそろ最後の証明になりつつある。で、クオータニオンが理解できたか?と言われると、腑に落ちてるとは到底言えず、ああそうなんですね、という程度にしか分かっていない。頭の中で幾何学的に分かっていない。式展開として回転なんですね・・という程度

まあそれはいいとして、クオータニオンで実装できるからといって、ドローンの制御はできず、次はジャイロセンサから得られたセンサ情報をXYZの軸の回転量に変換する計算が必要だ。この式はいろんなところに掲載されていて、回転の行列式風なのだが、なぜこの行列式で、回転して混ざった3軸の角速度が絶対座標のXYZにばらせるのか?が分からない。想定した回転(Θとかσとか)の逆回転をやってるのか??。。うーん。証明が理解できなくてもいいからどうやって算出されたのか、式展開を知りたい。

おまけ
さらには、角速度だけだとドリフトが検出できないので、加速度、地磁気のセンサの情報も組み合わせて、最終的な回転角を出すらしい。その方法が、カルマンフィルタを使う方法と、Madgwickフィルタを使う方法があるらしい。それ以外にも我流の方法があるかもしれない。

■ご参考URL

http://indoor-flight.com/uploads/tournament_file/file/00000/29/37fad1ec1df2c5a28602aeb7cce7253b.pdf

https://www.jstage.jst.go.jp/article/jje/50/4/50_182/_pdf
http://www.nilim.go.jp/lab/bcg/siryou/tnn/tnn0514pdf/ks051408.pdf
新入社員が制作実習でドローンの自作に挑戦した話。(1) | 組込み技術ラボ

本を見ながらカレーを作る

大阪はスパイスカレーの店が多くて、北浜で働いていた時は、カシミールにも食べに行った*1。どういうスパイスを使ったらあんな味になるのか想像もできなかったが、その後、スパイスカレーの本が出てきて、スパイスの配合とか作り方がだいぶ分かってきた。エリックサウスの稲田氏が書かれた、「だいたい1ステップか2ステップなのに本格インドカレー」という本を見かけて、スパイスの効いたシャバシャバのカレーがすぐにできたらいいなーと思って、できるだけ本のレシピに沿ってカレーを作ってみた。今回は料理の最終便*2ということで、スパイス用のミルも買って、ホールスパイスからガラムマサラを作ってカレーを作ってみた。
下の写真は、基本のチキンカレー(本に近い料理法)

下の写真は、なすびとズッキーニの野菜カレー(本を適当にアレンジ)

結論から言うと、大阪で流行っている*3スパイスカレー程には突き抜けた味にはならなかったけど、自分としてはカレー屋さんに食べに行かなくても十分に旨いと思える味にはなった。が。。家族には不評で(特に嫁さん)、台所がスパイスでめちゃ臭うし、このカレーは出汁が効いてないと相手にされない始末であった。まぁヨメがそこまで嫌う味だから、ある意味本物に近づいているともいえるのだが。家族で食べるカレーとしては却下されたので、、ヨメが仕事で出かけてる時とかにこっそり作ることにしよう。

■参考にした料理本
だいたい1ステップか2ステップ! なのに本格インドカレー

*1:昼にカシミールで食べるとスパイスが体にまとわりついて、周囲にすぐに分かってしまうという

*2:自分も歳をとってきて、いろんな趣味も順番に店じまいと思っており

*3:今も流行っていると言っていいのかどうか

Pythonでquotanionを扱えるようにクラスを定義してみる

多分Scipyとかにあるんだろうけど、Quotanion(四元数)の演算をプログラムでできるようにPythonでクラスを定義してみた*1
証明とか手で計算していると間違いまくるので、簡単な式変換ならプログラムにやってもらおうという。。
演算子オーバーロード(?)等を活用すると、q1.mul(q2)とせずとも、q1 * q2 とクールに書けるんだろうけど、そのあたり不勉強なので、愚直にメソッドを呼んで計算させる。
注意:強烈なバグが残っている

#!/usr/bin/python3
#
#


class Quotanion:

    def __init__(this,a,b,c,d):
        this.term  = { "1" : a , "i" : b , "j" : c , "k" : d }
        this.keys = ("1", "i", "j", "k")

    def mul(this,arg):
        exp = []
        term  = { "1" : "", "i" : "" , "j" : "" , "k" : "" }
        for key1 in this.keys:
           for key2 in arg.keys:
              new_term = this.term[key1] + arg.term[key2] 
              imagNumber = key1 + key2
              if imagNumber == "11":
                 imagNumber = None
              elif imagNumber == "1i" or imagNumber == "i1" :
                 imagNumber = "i"
              elif imagNumber == "1j" or imagNumber == "j1" :
                 imagNumber = "j"
              elif imagNumber == "1k" or imagNumber == "k1" :
                 imagNumber = "k"
              elif imagNumber == "ij":
                 imagNumber = "k"
              elif imagNumber == "ji":
                 imagNumber = "-k"
              elif imagNumber == "jk":
                 imagNumber = "i"
              elif imagNumber == "kj":
                 imagNumber = "-i"
              elif imagNumber == "ki":
                 imagNumber = "j"
              elif imagNumber == "ik":
                 imagNumber = "-j"

              if not imagNumber:
                 term["1"] += new_term
              elif "i" in imagNumber:
                 if "-" in imagNumber:
                    term["i"] += "-" + new_term
                 else:
                    term["i"] += "+" + new_term
              elif "j" in imagNumber:
                 if "-" in imagNumber:
                    term["j"] += "-" + new_term
                 else:
                    term["j"] += "+" + new_term
              elif "k" in imagNumber:
                 if "-" in imagNumber:
                    term["k"] += "-" + new_term
                 else:
                    term["k"] += "+" + new_term


        q = Quotanion(term["1"], term["i"], term["j"], term["k"])
        return(q)        

    def print(this):
        for key in this.keys[:-1]:
            if key == "1":
                print(f"{this.term[key]} + ",end="")
            else:
                print(f"({this.term[key]}){key} + ",end="")
        key = this.keys[-1]
        print(f"({this.term[key]}){key}")


q1 = Quotanion('a','b','c','d')
q1.print()

q2 = Quotanion('x','y','z','w')
q2.print()

q3 = q1.mul(q2)
q3.print()


# $ python3 qota.py
# a + (b)i + (c)j + (d)k
# x + (y)i + (z)j + (w)k
# ax + (+ay+bx+by+cw-dz)i + (+az-bw+cx+cz+dy)j + (+aw+bz-cy+dx+dw)k


内積はおかしいが、少し改良版。虚数同士の計算はできるが、係数が(a+b)iといった式だとまとに動かない。表面的な実装だとこれぐらいが限界で、式を解釈できるように構文解析が必要だ。lispのS式だったら構文解析なんて面倒な処理いらんけど、pythonベースだからしょうがない。

#!/usr/bin/python3
#
#


# flag for debug print 
DBG = False


class Quotanion:

    def __init__(this,a,b,c,d):
        this.term  = { "1" : a , "i" : b , "j" : c , "k" : d }
        this.type = "Quotanion"

    def set_type(this,type):
        this.type = type

    def size(this):
        exp = "sqrt("
        for key in this.term.keys()[:-1]:
             exp += f"{this.term[key]} + "
        key = this.term.keys()[-1]
        exp += f"{this.term[key]}"
        exp += ")"
        return(exp)

    def conjugate(this):
        term  = { "1" : "", "i" : "" , "j" : "" , "k" : "" }
        term["1"] = this.term["1"]

        for key in ("i","j","k"):
            if "-" in this.term[key]:
                term[key] = this.term[key].replace("-","")
            else:
                term[key] = "-" + this.term[key]
        q = Quotanion(term["1"], term["i"], term["j"], term["k"])
        return(q)        

    def iprod(this,arg):
        term  = { "1" : "", "i" : "" , "j" : "" , "k" : "" }
        term["1"] = this.term["1"] + arg.term["1"]
        term["i"] = this.term["i"] + arg.term["i"]
        term["j"] = this.term["j"] + arg.term["j"]
        term["k"] = this.term["k"] + arg.term["k"]
        q = Quotanion(term["1"], term["i"], term["j"], term["k"])
        q.set_type("innerProduct")
        return(q)        

    def add(this,arg):
        exp = []
        term  = { "1" : "", "i" : "" , "j" : "" , "k" : "" }
        for key in term.keys():
              new_term = this.term[key] 
              if "-" in arg.term[key]:
                   new_term += arg.term[key]
              else:
                   new_term += "+" + arg.term[key]

              term[key] = new_term
        q = Quotanion(term["1"], term["i"], term["j"], term["k"])
        return(q)        

    def mul(this,arg):
        exp = []
        term  = { "1" : "", "i" : "" , "j" : "" , "k" : "" }
        for key1 in this.term.keys():
           for key2 in arg.term.keys():

              if DBG:
                  print(f"{this.term[key1]}{key1} * {arg.term[key2]}{key2} -> ",end="")
              negaFlag = False
              if "-" in this.term[key1]:
                   negaFlag = not negaFlag 
                   new_term = this.term[key1].replace("-","")
              else:
                   new_term = this.term[key1]
              if "-" in arg.term[key2]:
                   negaFlag = not negaFlag
                   new_term += arg.term[key2].replace("-","")
              else:
                   new_term += arg.term[key2]

              image_number = key1 + key2
              if image_number == "11":
                 image_number = None
              elif image_number == "1i" or image_number == "i1" :
                 image_number = "i"
              elif image_number == "1j" or image_number == "j1" :
                 image_number = "j"
              elif image_number == "1k" or image_number == "k1" :
                 image_number = "k"
              elif image_number == "ij":
                 image_number = "k"
              elif image_number == "ji":
                 negaFlag = not negaFlag
                 image_number = "k"
              elif image_number == "jk":
                 image_number = "i"
              elif image_number == "kj":
                 negaFlag = not negaFlag
                 image_number = "i"
              elif image_number == "ki":
                 image_number = "j"
              elif image_number == "ik":
                 negaFlag = not negaFlag
                 image_number = "j"
              elif image_number == "ii" or image_number == "jj" or image_number == "kk":
                 image_number = None
                 negaFlag = not negaFlag

              if DBG:
                  if negaFlag:
                       print(f"-{new_term}{image_number}")
                  else:
                       print(f"{new_term}{image_number}")

              if not image_number:
                 if negaFlag:
                    term["1"] += "-" + new_term
                 else:
                    term["1"] += "+" + new_term
              elif image_number == "i":
                 if negaFlag:
                    term["i"] += "-" + new_term
                 else:
                    term["i"] += "+" + new_term
              elif image_number == "j":
                 if negaFlag:
                    term["j"] += "-" + new_term
                 else:
                    term["j"] += "+" + new_term
              elif image_number == "k":
                 if negaFlag:
                    term["k"] += "-" + new_term
                 else:
                    term["k"] += "+" + new_term
        q = Quotanion(term["1"], term["i"], term["j"], term["k"])
        return(q)        



    def print(this):
        keys = ("1","i","j","k")
        if this.type == "innerProduct":
           print("sqrt(",end="")
           for key in keys[:-1]:
                print(f"{this.term[key]} + ",end="")
           key = keys[-1]
           print(f"{this.term[key]}",end="")
           print(")")

        else:
          for key in keys[:-1]:
            if key == "1":
                print(f"{this.term[key]} + ",end="")
            else:
                print(f"({this.term[key]}){key} + ",end="")
          key = keys[-1]
          print(f"({this.term[key]}){key}")


#
# test
#

q1 = Quotanion('a','b','c','d')
#q1.print()

q2 = Quotanion('x','y','z','w')
#q2.print()


if False:
    print("-----add----")
    q1.print()
    q2.print()
    print("-------------")
    q3 = q1.add(q2)
    print("-----ans----")
    q3.print()


if False:
    print("-----multi----")
    q1.print()
    q2.print()
    print("-------------")
    q3 = q1.mul(q2)
    print("-----ans----")
    q3.print()

#q4 = q1.iprod(q2)
#q4.print()

#print(q1.size())
#print(q2.size())

if False:
    print("----q1----")
    q1.print()
    print("----conjugate of q1----")
    q1c = q1.conjugate()
    q1c.print()


if False:
    q1c = q1.conjugate()
    print("-----multi----")
    q1.print()
    q1c.print()
    print("-------------")
    qq = q1.mul(q1c)
    print("-----ans----")
    qq.print()

a = Quotanion('a','b','c','d')
ac = a.conjugate()

b = Quotanion('x','y','z','w')
bc = b.conjugate()

if False:
    print("--- a(~b) + (b)(~a) -------")
    a.mul(bc).add(b.mul(ac)).print()
#+ax+by+cz+dw++xa+yb+zc+wd + (-ay+bx-cw+dz-xb+ya-zd+wc)i + (-az+bw+cx-dy-xc+yd+za-wb)j + (-aw-bz+cy+dx-xd-yc+zb+wa)k
# 2ax + 2by + 2cz + 2dw  

*1:公式ライブラリとはレベルが違いすぎるので、車輪の再発明とは決して言わない

やりたいこと:クオータニオンを理解して、Rustでドローン姿勢制御プログラムを作る

過去Interface誌で、ドローン用の姿勢制御プログラムの特集があって、本を買って、ドローン開発キットも買った。が、、肝心の姿勢制御のコアであるクオータニオンの概念が全く理解できず*1、開発キットも押し入れに入れたままであった。
普段はPythonを使ってるけど、Rustを使えるようになりたいと思っているが、、何か目的がないとなかなか勉強する気になれない。ドローンとRustに興味があるので、Rustでドローン用の姿勢制御プログラムが書けたらちょうどいいと思う。が、、肝心のクオータニオンが全く分からない。
そんな中、Interface9月号は、数学とプログラミングが特集で、クオータニオンも多少は分かりやすく書かれている。そんなわけで、もう一度、線形代数を復習して、クオータニオンをプログラミングできるレベルには理解したいと思っている。IoT勉強会の準備があって、趣味のPGの時間が取れないのだけど、こっちもボチボチ進めたい。

買った本:
Interface(インターフェース) 2021年 9 月号
数の世界 自然数から実数、複素数、そして四元数へ (ブルーバックス)

■追記
ブルーバックスの「数の世界」は書かれている証明を辿りながら、なんとか四元数の章まではやってきた。まだ内積外積をやっているところで、四元数による回転の章までは20ページぐらいある。なかなか進まない。
一方、結果だけ使ったらいいというのなら、高校数学の美しい物語というサイトで、クオータニオンの説明が簡潔になされている。公式を使うだけなら、行列とsin/cosで回転が扱える(?)ので、手っ取り早く制御回路を組みたいのなら、以下のサイトの公式を使えばいいのでは?と思う(が、、自分の場合はせっかくなので、証明を辿りながら、回転の概念までたどり着きたい)
四元数と三次元空間における回転 | 高校数学の美しい物語

■追記(210822)
式展開をノートに書き写しながらも全ての証明を理解したわけではなく、あぁそういうものなんですね、という程度の理解ではあるが、なんとか回転の章(P189)まではたどり着いた。最終目的地である四元数による回転の理解は次週に持ち越し。

*1:虚数を使って4次元で表現すると計算が楽というのは分かるけど、プログラムに落とすレベルに理解できない

ECHONET Lite用ライブラリ OpenECHOに新しい属性を追加ー>エラー応答(52(Get_SNA))になる。なぜか

やりたいこと:
OpenECHOのElectricStormWindowクラス(通称:電動窓シャッター)に対して属性を加えたい*1。ElectricStormWindowクラスでは開閉状態(OpenCloseStatus 0xEA)がサポートされていない(仕様上も必須属性ではないので)。だから、、setupPropertyMaps()メソッドをoverrideして、属性:0xEAも扱えるようにしたい。
発生した問題:
setupPropertyMapsメソッドをoverrideして、0xEAも扱えますと追加したけど、EchonetLiteのパケットを投げると、OpenECHOからは、エラー:GET_SNAで返される。

送信パケット 
|10|81|0001|0EF001|026301|62(Get)|01|ea(Open Close Status)_00_|
----------------------------------------
OpenECHOからの返却パケット
|10|81|0001|026301|0ef001|52(Get_SNA)|01|ea(Open Close Status)_00_|

なぜエラー:GET_SNA判断されるのか原因が分からなかった。
調査方法:
Processingではブレークができるけど、呼び出し側までトレースでもどれない。しょうがないので、サンプルソースjava版に書き直して、Java用デバッガ、jdbで一行ずつトレースしながら、52(GET_SNA)を返却する原因を調べた。
調査結果:
EchoObjectクラスで返却パケットを組み立てる際、Propertyの正当性をチェックしている。具体的には、ElectricStormWindowクラスのisValidPropertyメソッドを呼んで、正当性を確認している。このメソッドのswith文に、0xEAを加えていなかったため、正当性チェックでfalseとなっていた。
対策:
ElectricStormWindowクラスのisValidPropertyメソッドをoverrideして0xEAの場合の正当性チェックを加える

修正内容:
~疲れたので修正内容は後日追記~

修正結果:
以下の通り、正常に応答するようになった。

送信パケット
|10|81|0001|0EF001|026301|62(Get)|01|ea(Open Close Status)_00_|
----------------------------------------
OpenECHOからの返却パケット
|10|81|0001|026301|0ef001|72(Get_Res)|01|ea(Open Close Status)_01_42|

■ご参考
java版のビルド

#!/bin/sh
CLASS=Shutter

echo "javac -g -cp OpenECHO.jar  ${CLASS}.java"
javac -g -cp OpenECHO.jar  ${CLASS}.java

jdbの起動

#!/bin/sh
CLASS=Shutter

echo "jdb -classpath /<some_pass>/OpenECHO.jar:  ${CLASS}"
jdb -classpath /<some_pass>/OpenECHO.jar:  ${CLASS}

*1:制御したい対象の電動シャッター(文化シャッターとか)は開閉状態を取得する際、0xEAを使う