chakokuのブログ(rev4)

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

STM32/RustでHALを使わずレジスタを直接操作してLチカ

組み込みRustでは、ハードウエア抽象化したライブラリHALを使ってプログラミングするのが正統派なんでしょうが、自分の場合、Rustの正しい所有権の書き方がイマイチわかっておらず、どうやってもペリフェラル操作をmainからモジュール側に出すことができなかった。そんなこんなで、、まずはペリフェラルを操作できないとプログラミングが進まないので、HALを使わず自分でレジスタ操作することにした。勉強が進んだらまた、HALライブラリに置き換えるとして・・
先人のサンプルを参考に、NucleoのL476RGのボードの緑のLEDを点滅させるソースは以下。WAITもタイマを使わずループで待たせている。
GPIOを設定しただけで、本当はクロックとかも正しい値を書きこまないといけないのだろうけど、、そこまでまだ読めてなくて、今はGPIOのGPIOA PA5の出力をL/Hさせているだけ。 だけなんだが、、まぁ泥臭くレジスタを操作しまくればペリフェラルが制御できるので、このやり方を続ければUARTとI2Cも制御できるはず。こんな実装をしてたらRustの恩恵をまったく受けられないのだが。。

pub const GPIOA_BASE: u32 = 0x4800_0000;   //APB2PERIPH_BASE + 0x0800;
pub const MODER_OFFSET: u32 = 0x0;  // GPIOA
pub const BSRR_OFFSET: u32 = 0x18;

pub const RCC_BASE: u32 = 0x4002_1000;
pub const AHB2ENR_OFFSET : u32 = 0x4C;   // RCC_AHB2ENR
pub const GPIO_PIN_5: u32 = 5;


pub fn setup(){

  let reg    = (RCC_BASE + AHB2ENR_OFFSET)  as *mut u32;
  unsafe { core::ptr::write_volatile(reg, 1) };

  let addr = (GPIOA_BASE + MODER_OFFSET)  as *mut u32;
  let set_mode5  = 0x01 << (GPIO_PIN_5 * 2); // 0x01 ... GPIO OUTPUT MODE
  unsafe { 
	let current = core::ptr::read_volatile(addr) ;
        let mut val = current & 0xFFFFF3FF;
        val |= set_mode5;
	core::ptr::write_volatile(addr, val) ;
  }
  let bsrr    = (GPIOA_BASE + BSRR_OFFSET)  as *mut u32;
  loop {
     unsafe { core::ptr::write_volatile(bsrr, 1 << GPIO_PIN_5) };   //on
     let _ = my_wait(0x10);
     unsafe { core::ptr::write_volatile(bsrr, (1 << GPIO_PIN_5) << 16)}; //off
     let _ = my_wait(0x10);
   }
}

pub fn my_wait(count: u64) -> u128{

   let mut zzz:u128 = 0x0;
   for _i in 0..count {
       for _j in 0..0xff {
             zzz +=1;
       }     
   }
   zzz   
}

組み込みRustのむつかしさ

STM32用のRust開発環境を設定して、I2CとかUARTとかボチボチ動いている。が、、あくまでもサンプルコードをベースに修正した程度である。
素の状態から最低限のコードだけで動かそうとすると、いろいろエラーが出てくる。一つはSTM32用ライブラリのエラーで、もう一つはコンパイル時のundefined simbleのエラー。
ブログ等に掲載されているSTM32用サンプルコードがいろいろあるのだけど、STM32LシリーズやSTM32Fシリーズ等、マイコンのシリーズが違ってくるとstm32-HALのライブラリが異なるのか、メソッドが無いといって怒られる。確かにハードウエアが異なるからライブラリも違ってくるので、そこはソースをよく読んで、シリーズごとの違いを手修正で直さないといけないのかも。
で、、ライブラリの不整合が無くなってもリンカの最終段階で、undefinedシンボルで怒られる場合がある。こちらはかなり厄介だ。スタートアップとかが正しく指定できていないのかもしれないが。
Rustという言語のむつかしさもあるけど、それ以上に、ライブラリ不整合、リンカでのundefinedシンボルの問題と、なかなかに道は険しいのであった。
だからどうすべきか??? 自分でスタートアップ書いたり、リンカ定義ファイルを作るぐらいの根性がないと、このレベルの不具合解消は難しい。素でくみ上げるぐらいの気合で取り組むか、、はたまた、基本的にサンプルコードを極力踏襲して、サンプルコードから差分だけ変えて作るか。。

リンカでのエラー、StartUpルーチンが何かおかしいようだ

  = note: /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
          (.text+0x16): undefined reference to `__libc_csu_fini'
          /usr/bin/ld: (.text+0x1d): undefined reference to `__libc_csu_init'
          /usr/bin/ld: (.text+0x2a): undefined reference to `__libc_start_main'
          collect2: error: ld returned 1 exit status

上記と同じような症状の記事、解決策がいまいちわからんが・・
"Hello World" no_std linker error - help - The Rust Programming Language Forum

プロジェクトのディレクトリに.cargoディレクトリを掘って、config.tomlを以下の内容で設定したら上記エラーは解消した。

$ cat .cargo/config.toml
#
#
[build]
rustflags = ["-C", "link-args=-lc"]

というわけで、、先人のブログを参考にしつつ、なんとか素のSTM32L4X6のひな型はできたようだ。
素のひな型ができたところで、STM32のGPIOを加えると、途端にリンク時のエラーが出た。浅い理解で組み込みRustを使っているせいだろう。リンクする対象が間違ってるのか、ビルドの指定が間違っているのか。。素の状態から自分の望むライブラリを加えるアプローチだと、エラーが頻発して、その都度やり直しになる。勉強にはなるかもだけどいくら時間があっても足りない。だから、、サンプルのモリモリのコードを削って必要な分だけ残すアプローチに変更する。

= note: <path_user>/lang/rust/stm32/src/paisen/target/debug/deps/paisen-f8a7cd7c539f1067.25lazqxwii3x4twt.rcgu.o: in function `cortex_m::interrupt::enable':
/home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.3/src/interrupt.rs:48: undefined reference to `__cpsie'
<path_user>/lang/rust/stm32/src/paisen/target/debug/deps/paisen-f8a7cd7c539f1067.25lazqxwii3x4twt.rcgu.o: in function `cortex_m::interrupt::disable':
/home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.3/src/interrupt.rs:38: undefined reference to `__cpsid'
<path_user>/lang/rust/stm32/src/paisen/target/debug/deps/paisen-f8a7cd7c539f1067.anqm6z1ob0uppwu.rcgu.o: in function `cortex_m::register::primask::read':
/home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.3/src/register/primask.rs:29: undefined reference to `__primask_r'
collect2: error: ld returned 1 exit status

所有権関連で出たエラー

error[E0507]: cannot move out of `self.serial_port` which is behind a shared reference

おおよその意味は、「借用している値なので、変更してはいけません」(所有権は移動できません、かも)。構造体等の値はあちこちに移さず、ライブラリ内で生成、利用、消費すべきか。。

SerialやらSPIやらペリフェラルの操作をモジュール化して分割したいのだが、、あまり理解せずにモジュール化しても所有権の問題が出て以下のように怒られたりする。

error[E0507]: cannot move out of `self.serial_port` which is behind a shared reference
61 |      let (mut tx,_) = self.serial_port.split();
   |                       ^^^^^^^^^^^^^^^^ move occurs because `self.serial_port` has type `Serial<hal::pac::USART2, (PA2<Alternate<hal::gpio::AF7, Input<Floating>>>, PA3<Alternate<hal::gpio::AF7, Input<Floating>>>)>`, which does not implement the `Copy` trait

ポート等のレジスタはメモリマップドIOなので、Rustからはメモリ(構造体か?)とみなして動くのだが、、Rustでは構造体は所有権で守られていて、気軽にサブルーチンに渡してレジスタ操作しようとしても、所有権がないと怒られるのであった。

組み込みRustをまずはきっちり勉強すべきかも。
導入 - The Embedded Rust Book
所有権とは? - The Rust Programming Language 日本語版
自分がRustの処理系になったつもりで、どんなタイミングでメモリを回収するのか?をよく考えると、所有権が多少は理解できて、上記のような権利ある、なしに悩むことがなくなるのかも。

ここ数日のRustによるプログラミングを振り返ってみて、、ハマりポイントは、(1)Rustによる所有権の扱い(自分の理解不足)、(2)STM-HALと呼ばれるハード抽象化レイヤーに対応したライブラリ(stm32l4xx_hal)の使い方の2点であった。サンプルはmainでLチカとかシリアルとかを動かしているのだけど、これを外部モジュールに出そうとすると、途端にレジスタ(構造体)の所有権が問題になってくる。少し自分のやりたいように変えようと思っても所有権に正しく対応したコードが書けないので、文法的には(所有権も文法か?)正しくても権限の点でコンパイル段階でエラーとして拒否される。というわけで、、勉強もかねて、HALの部分は自力でSTM32用のドライバを作るつもりです。最初のうちは、Singletonモデルにならず、複数のライブラリでレジスタを触れてしまうかもしれないけど、、自分の腕が上がってきたら、レジスタはSingletonモデルを導入して、正しく排他できるようにすればよいかと。まずはどうにかして動かすのが先決。

リンカの定義やstartupまで公開している記事があるので(情報開示ありがたい)、この記事等を参考に、一度素から組んでみたい。。
rust で組み込み(Cortex-M3) – GitHub 出張所 – プログラム関係のブログはここに


■追記(2022/2/1)
Rustを学んでマスターしたいと思っていたが、組み込みRustのむつかしさ(所有権をクリアして周辺IOをどう使っていいのか分からない、マイコンシリーズに適用できるライブラリはどれを使ったらいいのか分からないetc)の壁が厚く、Droneを飛ばす件は音が大きくてヨメサンに怒られることもあって、一旦中断、今は Dart/Flutterでマルチプラットフォームのスマフォアプリを作ろうとしているという。。

STM32用バイナリがビルドできるよう組み込み用Rust環境をセットアップ

絶対座標で計算するから大変なんであって、XYZ軸の傾きを検知して、相対的にモータの回転数を±で補正する程度のアルゴリズムで、まずはドローンを飛ばしてみようと思う(変な方に飛んでいくかもしれないが)。ただ、このままMicroPython で試作を続けるとあまりに楽でRustに移行しなさそうなので、、ドローンが離陸*1するコードは絶対にRustで書こうと決めた。というわけで、、STM32に対応したRustをUbuntuに導入する。先人のブログを参考に手順は以下

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup target add thumbv7em-none-eabihf
cargo install cargo-binutils
rustup component add llvm-tools-preview
cargo install cargo-generate
$ rustc --print target-list

            *略*
thumbv6m-none-eabi
thumbv7a-pc-windows-msvc
thumbv7a-uwp-windows-msvc
thumbv7em-none-eabi
thumbv7em-none-eabihf
thumbv7m-none-eabi
thumbv7neon-linux-androideabi
thumbv7neon-unknown-linux-gnueabihf
thumbv7neon-unknown-linux-musleabihf

STM32用にビルドするには、マイコンのメモリマップに合ったリンカ設定やらスタートアップやらもろもろが必要になるのだが、そこを0から準備しているとどれほど時間がかかるか分からないので(問題に出くわして自力で解決できるかどうかも不明)、セットアップも先人に頼ることにした。
https://github.com/stm32-rs/stm32-rs

git clone git@github.com:stm32-rs/stm32f1xx-hal.git

example配下のblinky.rsに対してLEDのピンを修正してビルド
buildすると、thumbv7em-none-eabiが足りないと言われるので以下を再実行

rustup target add thumbv7em-none-eabi
cargo build --example blinky --release --features=stm32l4x6

/stm32l4xx-hal/target/thumbv7em-none-eabi/release/examples配下にblinkyが作られる。これを何も考えずに、Drag&DropでNUCLEOのボードに書き込み・・・動かない(動いているけどLEDが点滅しないのかも)

OpenOCDを使わないとだめなんだろうか。。
記事を参考にOpenOCD+gdbで試す。
OpenOCDでNucleoボードに接続

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

gdbを起動

$ /usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin/arm-none-eabi-gdb.exe  target/thumbv7em-none-eabi/release/examples/blinky

Reading symbols from C:\cygwin64\home\sumi\lang\rust\stm32\stm32l4xx-hal\target\thumbv7em-none-eabi\release\examples\blinky...done.

(gdb) target remote localhost:3333

Remote debugging using localhost:3333
Reset ()    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.15/src/lib.rs:497
497     /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.15/src/lib.rs: No such file or directory.

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

(gdb) load
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x2640 lma 0x8000400
Loading section .rodata, size 0xea0 lma 0x8002a40
Start address 0x8000400, load size 14560
Transfer rate: 12 KB/sec, 4853 bytes/write.

(gdb) l
492     in /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.15/src/lib.rs

(gdb) l main
20      use crate::rt::ExceptionFrame;
21
22      use crate::sh::hio;
23      use core::fmt::Write;
24
25      #[entry]
26      fn main() -> ! {
27          let mut hstdout = hio::hstdout().unwrap();
28
29          writeln!(hstdout, "Hello, world!").unwrap();

(gdb) l
30
31          let cp = cortex_m::Peripherals::take().unwrap();
32          let dp = hal::stm32::Peripherals::take().unwrap();
33
34          let mut flash = dp.FLASH.constrain(); // .constrain();
35          let mut rcc = dp.RCC.constrain();
36          let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1);
37
38          // Try a different clock configuration
39          let clocks = rcc.cfgr.hclk(8.mhz()).freeze(&mut flash.acr, &mut pwr)
;
(gdb) hbreak main

Hardware assisted breakpoint 1 at 0x80005d0: file examples/blinky.rs, line 25.

(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             0x20010000       0x20010000
lr             0xffffffff       -1
pc             0x8000400        0x8000400 <Reset>
xPSR           0x1000000        16777216

(gdb) cont
Continuing.

Breakpoint 1, main () at examples/blinky.rs:25
25      #[entry]

(gdb) l
20      use crate::rt::ExceptionFrame;
21
22      use crate::sh::hio;
23      use core::fmt::Write;
24
25      #[entry]
26      fn main() -> ! {
27          let mut hstdout = hio::hstdout().unwrap();
28
29          writeln!(hstdout, "Hello, world!").unwrap();

(gdb) n
Note: automatically using hardware breakpoints for read-only addresses.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x08002902 in __c_m_sh_syscall ()
    at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/option.rs:388
388     /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/option.rs: No such file or directory.
(gdb)

mainの次でハングしているのだろうか。スタックがおかしいのか?
スタックがおかしいのではないようだ。。なぜこんなところで止まるのか分からないが。

(gdb) where
#0  0x08002902 in __c_m_sh_syscall ()
    at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/option.rs:388
#1  0x080027e8 in syscall1 (_nr=5, _arg=536936268)
    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/lib.rs:215
#2  syscall<[usize; 3]> (nr=5, arg=0x2000ff4c)
    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/lib.rs:207
#3  write_all (fd=1, buffer=...)
    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/hio.rs:69
#4  write_all (self=<optimized out>, buffer=...)
    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/hio.rs:34
#5  write_str (self=<optimized out>, s=...)
    at /home/sumi/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/hio.rs:40
#6  _$LT$$RF$mut$u20$W$u20$as$u20$core..fmt..Write$GT$::write_str::hb6fe49e168115967 (self=<optimized out>, s=...)
    at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/fmt/mod.rs:191
#7  0x08001e9e in core::fmt::write::hf86e9be5054282ca ()
    at library/core/src/fmt/mod.rs:1131
#8  0x08000632 in blinky::__cortex_m_rt_main::h1a11350d26d59f31 ()
    at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/fmt/mod.rs:184
#9  0x080005d4 in main () at examples/blinky.rs:25

mainのソースとしては以下の行

    let mut hstdout = hio::hstdout().unwrap();

セミホスティングを使うための初期設定と思われるが、Lチカだけやりたいので、セミホスティング要らないので、ややこしい行はコメントにしてみる。

セミホスティングの行をコメント、ビルドしてデバッグ

     //    let mut hstdout = hio::hstdout().unwrap();
     //    writeln!(hstdout, "Hello, world!").unwrap();

gdbを起動、mainでハードウエアブレーク設定して、contで実行

umbv7em-none-eabi/release/examples/blinkyq4/bin/arm-none-eabi-gdb.exe  target/th

Reading symbols from #######\lang\rust\stm32\stm32l4xx-hal\target\thumbv7em-none-eabi\release\examples\blinky...done.

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

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

(gdb) load
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x2500 lma 0x8000400
Loading section .rodata, size 0xde8 lma 0x8002900
Start address 0x8000400, load size 14056
Transfer rate: 11 KB/sec, 4685 bytes/write.

(gdb) hbreak main
Hardware assisted breakpoint 1 at 0x800058e: file examples/blinky.rs, line 25.

(gdb) cont
Continuing.

Breakpoint 1, main () at examples/blinky.rs:25
25      #[entry]

(gdb) n
Note: automatically using hardware breakpoints for read-only addresses.

Program received signal SIGINT, Interrupt.
blinky::__cortex_m_rt_main::h1a11350d26d59f31 () at src/delay.rs:72
72                  while !self.syst.has_wrapped() {}
(gdb)

LED2が点滅した。。 ゆえに、、セミホスティングは何か手当が必要、タイマとかGPIOは使える。

バイナリは動くバージョンになったということで、、Drag&Dropでファームに焼けるのかやってみた。
正常ファームの状態に正しいファームを上書きインストールしたことになり、正しく書けたかどうか、判断が難しいが、単純にDrag&DropでもLEDは点滅している。多分、デバッガなくてもこの方法で書き込めるのだろう。とはいえ、組み込みプログラムの開発においてシリアル等の通信手段がない場合、デバッガがないと非常に困るので、結局はOpenOCD+GDDBは必須だと思いますが。。

■追記
gdbセミホスティングを有効にするコマンドがあって、これを打ち込んでいなかったのを思い出した。

monitor arm semihosting enable

上記を打ち込むとどういう挙動になるのか再度試す。

OpenOCD側のコンソールに以下が表示、LEDが点滅されるのを確認した。よって、gdb側からセミホスティングを有効にする操作をしていなかったのが原因。

semihosting is enabled
Hello, world!

以上により、基本的なテストは行えたので、、今後はRust+STM32用ドライバを使って、USBシリアルと、GPIO/SPIがプログラムで制御できるようにして、センサデータをシリアルダンプしたり、簡単なモニタプログラムを作ってPC側からセンサデータを取得、モータ制御用テーブルを書き換えたり、そんなことができるように、Rustでドローン制御プログラムを組む予定。

■Rustのコードサイズ

$ size blinky
   text    data     bss     dec     hex filename
  14560       0       4   14564    38e4 blinky

RustによるLチカのコードサイズは、バイナリ領域で14KB程度。STEVAL-DRONE01に乗っているSTM32F103CはFlash 256Kなので、十分に余裕がある。かなりややこしいコードを書いても大丈夫。。

■振り返り
RustでLチカサンプルをビルドして走らせた。先人の説明記事が明確だったのと、インストールツール類が枯れるレベルに仕上がっているので、Rust環境を作るのはほとんどトラブルなくできた。一方、サンプルソースを見てもC言語等とはかなり違っていて、ぱっと見た限り、コードの表記の意味が全然分からない。ライブラリとか自分で自由にプログラミングできるレベルまでは時間かけて勉強しないといけないと感じた。


■参考URL
Rustで組み込みプログラミングの第一歩、LチカとHello Worldを試してみた | DevelopersIO
https://nkon.github.io/Rust-embedded/

*1:1mmでも浮くような

6DoFのLSM6DSLからジャイロ、加速度、温度のセンサーデータを取ってみる

SPIでレジスタが読めるようなので、センサを動かすための最低限の設定だけ行って、ジャイロセンサ、加速度センサ、温度センサの値を読んでみた。以下がその値。ドローンを置いただけなので、Y軸の加速度だけ値が出ている。それ以外は誤差なんだろう。

# gyroscope(x,y,z)    acceleration(x,y,z)   temp
[2, -83, -11]      [171, -203, 16554]  27.96078
[-255, -80, -2]  [155, -192, 16534]  27.92549
[-3, -69, -6]      [166, -215, 16539]  27.92157
[-3, -72, -9]      [168, -213, 16544]  27.96471
[3, -80, -2]       [155, -183, 16547]  27.97647
[-5, -72, -8]     [149, -202, 16553]  27.91765

STEVAL-DRONEをXYZの各軸で回転させると、ロール、ピッチ、ヨーに沿ってセンタの値は変化している。が、、計算に使っていい値域になっているのかは
もう少し仕様書やサンプルコーディングを見ないと分からない。温度はサンプルを調べて実装したのでそれらしい値が出ているが、ジャイロと加速度センサについては、サンプルソースを少し見た限りでは、最大値で割り算とかしている?ようで、、レジスタを読むだけでは多分ダメなんだろう。

絶対値はおかしいとして、どれぐらい動作を検知できているか、ピッチング、ローリング、ヨーイングが発生した時のジャイロセンサーの値(X,Y,Z軸)をグラフ化した。左から、ドローンに対してピッチング、ローリング、ヨーイングさせた時のジャイロセンサの値で、それぞれの操作において、X軸、Y軸、Z軸が最も強く反応している。(グラフ上はY軸、X軸が入れ替わっている。センサの取り付け角度が右回りに90度回転しているため。直進方向がY軸になっているらしい)

加速度センサの計測値は以下

ジャイロセンサーの値は回転速度なので、得られた値を積分することで、回転角が分かる仕組み。ただし、回転するとセンサの軸も傾くので、その分を絶対座標のXYZ軸に戻してから*1積分する必要があるという。。傾いたセンサをXYZ軸の基準として、それを使い続けるとなぜダメなのか?? センサからの情報に基づき回転の計算を終えてから、地球の絶対座標に変換したらダメなんだろうか?? (あるいは、順番が違うだけで結局は同じ計算をしているのか??)

以下は、STマイクロ社の新しいセンサ(LSM9D1)に対して、無理やりAdafruitのドライバを動かしてデータを取ったところ。正規化されているので、何万とかそういった値にはないっていない

0.0 0.0 0.0                               # ジャイロ
-0.01022 0.07406 -0.24108       # 加速度
0.1757765 0.2814561 0.01420262    # 地磁気
21.5                                 # 温度

グラフを見ていると値域はおおよそ-30,000~+30,000の幅で変動している。この±30,000って符号付2バイト整数の値域(-32,768~32,767)と大体合っていて、2バイト整数で表現できる範囲でほぼ振り切っているということだと理解しました。これ以上の加速度を与えると値を表現できずに0x7FFF(32,767)でサチることになるのだろう。

■追記
微積を使って絶対座標で厳密に姿勢を計算しながら制御するのが正統な実装なんだろうけど、ジャイロと加速度センサから得られた値の増減だけに着目して、モータの回転数を上下させるだけでも姿勢制御が可能なのでは?と思えるのだった。ただしフィードバックしすぎて発振しないような鈍感さパラメータは必要と思いますが。例のPID制御につながる話なのかもしれません。

■追記
ジャイロスコープ、角速度の値域は、レジスタ設定の値を最大値として比率で計算するようである(たしかにここは適当に設定したレジスタ値であった)。
AN5040[LSM6DSL: always-on 3D accelerometer and 3D gyroscope]より


■追記
ジャイロスコープ、加速度センサの値域に基づき正規化?したデータは以下(ジャイロはMAX±250dps 、加速度はMAX±2G)。10秒でドローンの機首を真上まで上げて、10秒で水平までおろした。角速度は、9dps (90度/10秒だから)

        gyro(degree/s)   |     accelerometer(G)    | temp
------------------------------------------------------------
-0.299,   0.366, -0.220, | -0.013, -0.046, 1.001,  |31.675,
-0.629,  -1.526,  0.146, | -0.036,  0.008, 1.011,  |31.667,
-2.680,  -10.724, 0.531, |  0.063, -0.013, 1.010,  |31.651,
 2.905,  -6.793, -0.281, |  0.157,  0.004, 0.998,  |31.710,
 0.073,  -6.653,  0.092, |  0.250,  0.003, 0.968,  |31.694,
-0.293,  -8.441,  0.214, |  0.373, -0.014, 0.951,  |31.710,
 0.153, -10.480,  0.970, |  0.426,  0.004, 0.908,  |31.698,
-0.098,  -3.912,  0.397, |  0.534, -0.004, 0.870,  |31.682,
 0.269,  -1.984, -0.348, |  0.568, -0.008, 0.842,  |31.659,
-0.281,  -5.084, -0.525, |  0.592,  0.001, 0.821,  |31.671,
-0.336,  -1.215,  1.483, |  0.642,  0.011, 0.781,  |31.714,
-0.049,  -3.870, -0.140, |  0.660, -0.009, 0.766,  |31.706,
 0.195,  -7.764, -0.610, |  0.714, -0.007, 0.692,  |31.710,
 0.018,  -4.822,  0.275, |  0.765, -0.017, 0.651,  |31.667,
-1.233,  -4.956,  0.208, |  0.806, -0.005, 0.609,  |31.647,
-0.409,  -6.409,  0.439, |  0.851, -0.009, 0.561,  |31.678,
-0.928,  -9.113, -0.623, |  0.882,  0.000, 0.495,  |31.722,
-0.574,  -7.501, -0.922, |  0.938, -0.008, 0.342,  |31.725,
-1.471, -12.836,  0.110, |  0.954,  0.008, 0.223,  |31.698,
 0.568,  -9.778, -0.586, |  0.983, -0.007, 0.100,  |31.702,
						   |
 0.293,  -2.918, -0.360, |  0.999,  0.008, 0.045,  |31.698,
 0.775,  10.694,  1.123, |  0.999,  0.002, 0.102,  |31.741,
						   |
-1.099,   4.126, -1.013, |  0.980,  0.008, 0.200,  |31.694,
-1.135,   9.076,  0.458, |  0.967,  0.007, 0.279,  |31.718,
 1.325,   5.646, -0.592, |  0.930, -0.011, 0.413,  |31.675,
 0.714,   5.219, -0.214, |  0.877, -0.002, 0.460,  |31.729,
-0.055,   6.195,  0.024, |  0.838, -0.023, 0.551,  |31.663,
-0.641,   5.615, -0.464, |  0.800, -0.003, 0.615,  |31.690,
-0.745,   3.235, -0.031, |  0.776,  0.003, 0.657,  |31.753,
 1.147,   6.616, -0.482, |  0.716, -0.013, 0.716,  |31.718,
 0.702,   3.467,  0.452, |  0.681,  0.006, 0.759,  |31.671,
-0.275,   4.535,  0.311, |  0.633, -0.003, 0.792,  |31.698,
 3.851,   6.287, -0.916, |  0.618,  0.003, 0.830,  |31.690,
-0.427,   5.683, -0.385, |  0.489, -0.014, 0.862,  |31.686,
-0.476,   8.466, -0.604, |  0.393,  0.001, 0.913,  |31.698,
 0.195,   1.874, -0.049, |  0.332,  0.008, 0.956,  |31.710,
-0.378,   3.656, -0.366, |  0.280, -0.008, 0.974,  |31.714, 
 0.275,   3.626,  0.116, |  0.231, -0.010, 0.989,  |31.667,
 1.349,   7.526, -0.525, |  0.142, -0.013, 0.996,  |31.718,
-0.244,  10.779, -0.293, |  0.028, -0.017, 1.015,  |31.682,
-0.061,  -0.275, -0.012, |  0.001, -0.002, 1.012,  |31.710,
-0.122,  -0.305, -0.024, | -0.001, -0.002, 1.008,  |31.702,

動作としてはY軸回転(ピッチ)なので、ジャイロセンサはY軸で検知されるはず。一方、ドローンのセンサは90度曲がっているから、、ピッチ動作はX軸で検知されると解釈していたが、上記を見ると、ピッチの検知はY軸の様だ(教科書通りの軸)。だったら冒頭のテストは間違いだったのか??
値は9dpsが出力されるはずだが、手による操作なので、多少ぶれているとして、-10、+10に近い値が出ている。だから、、ジャイロはだいたい正しい値を出していそうだと。一方の加速度センサについて、ドローンを水平においてる時はZ軸に1G、機首を完全に上げた時はX軸に1Gとなっている。斜め45度の時は、X軸とZ軸にGがかかって、x,y,z = 0.7, 0, 0.7である。これは、、直角三角形の底辺が1の場合、斜辺?は0.7というやつだと理解しています。

*1:行列演算

ビットバンギングで、SPI 3-Wireモードを実装して加速度センサ(LSM6DSL)のレジスタを読んでみる

STEVAL-DRONE01上の加速度センサはマイコンとSPI 3-Wireモードで接続されていてMicroPython上で利用可能な標準のSPIドライバでは制御できない*1
泥臭いが、ビットバンギングでIOポートを上げ下げしてSPI 3-Wire相当のバスを作って加速度センサのレジスタを読んでみた。
結果、以下の通りで動いてそうな感じ。

>>> reg_dump()
00: 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 6a
10: 00 00 08 00 00 00 00 00 e0 00 00 00 00 00 00 c2
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
30: 00 00 00 00 00 00 00 00 00 00 00 10 00 00 bb a8
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50: 00 00 00 00 84 00 00 00 00 00 00 00 00 00 00 00
60: 00 00 00 00 00 bb 00 00 00 00 00 00 00 00 00 00
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80: 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 6a
90: 00 00 08 00 00 00 00 00 e0 00 00 00 00 00 00 c2
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
b0: 00 00 00 00 00 00 00 00 00 00 00 10 00 00 bb a8
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
d0: 00 00 00 00 84 00 00 00 00 00 00 00 00 00 00 00
e0: 00 00 00 00 00 bb 00 00 00 00 00 00 00 00 00 00
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

あたりまえだが、、ダンプさせるともっさり動いている。

SPI 3-Wireモードによる制御サンプルは以下

#-----------------------------------------
from pyb import Pin

# def for SPI
SPI_READ_BIT = 0x80

CTRL3_C = 0x12
CTRL6_C = 0x15
SIM_3WIRE = 0x08

lsm6d_cs = None
s2_clk = None
s2_sda = None


def reg_dump():
    for addr in range(0xff+1):
        data = spi_read_reg(addr)
        if (addr % 16) == 0:
            print(f"\n{addr:02x}: ", end="")
        print(f"{data:02x} ", end="")
    print("")

def spi_init():
    global lsm6d_cs, s2_clk, s2_sda

    lsm6d_cs = Pin(Pin.cpu.A8, mode=Pin.OUT)
    s2_clk = Pin(Pin.cpu.B13, mode=Pin.OUT)
    s2_sda = Pin(Pin.cpu.B15, mode=Pin.OUT)


def spi_write_reg(addr,data):
    global lsm6d_cs, s2_clk, s2_sda

    s2_sda = Pin(Pin.cpu.B15, mode=Pin.OUT)
    lsm6d_cs.high()
    spi_out(addr, no_ctrl_cs = True)
    spi_out(data, no_ctrl_cs = True)
    lsm6d_cs.low()

def spi_read_reg(addr):
    global lsm6d_cs, s2_clk, s2_sda

    s2_sda = Pin(Pin.cpu.B15, mode=Pin.OUT)
    lsm6d_cs.high()
    spi_out(addr | SPI_READ_BIT, no_ctrl_cs = True)
    s2_sda = Pin(Pin.cpu.B15, mode=Pin.IN)
    data = spi_in(no_ctrl_cs = True)
    #print(f"{data:02x}")
    lsm6d_cs.low()
    return data    

def spi_out(data,no_ctrl_cs = False):
    global lsm6d_cs, s2_clk, s2_sda

    # setup SPI bus
    if not no_ctrl_cs:
        lsm6d_cs.high()
    s2_clk.high()
    s2_sda.high()

    lsm6d_cs.low()

    mask = 0x80
    for i in range(8):
        s2_clk.low()
        #print("C_L ",end="")
        if data & mask:
            s2_sda.high()
            #print("D_H ",end="")
        else:
            s2_sda.low()
            #print("D_L ",end="")
        s2_clk.high()
        #print("C_H")
        mask >>= 1

    if not no_ctrl_cs:
        lsm6d_cs.high()



def spi_in(no_ctrl_cs = False):
    global lsm6d_cs, s2_clk, s2_sda
 
    # setup SPI bus
    if not no_ctrl_cs:
        lsm6d_cs.high()
    s2_clk.high()
 
    lsm6d_cs.low()
 
    data = 0
    for i in range(8):
        data <<= 1
        s2_clk.low()
        #print("C_L ",end="")
        s2_clk.high()
        #print("C_H ",end="")
        if s2_sda.value() == 1:
            #print("D_H")
            data |=1
        else:
            pass
            #print("D_L")
 
    if not no_ctrl_cs:
        lsm6d_cs.high()
 
    return data 
 
 
#---------------------------------------------- 
 
spi_init()

# set 3-wire mode
addr = CTRL3_C
data = SIM_3WIRE

spi_write_reg(addr,data)
data = spi_read_reg(addr)
print(f"{data:02x}")
reg_dump()

■追記
あらためて読み直すと、、以下のCS操作は逆ではなかろうか。

def spi_write_reg(addr,data):
    global lsm6d_cs, s2_clk, s2_sda

    s2_sda = Pin(Pin.cpu.B15, mode=Pin.OUT)
    lsm6d_cs.high()                              #  not cs.high but  cs.low ??
    spi_out(addr, no_ctrl_cs = True)
    spi_out(data, no_ctrl_cs = True)
    lsm6d_cs.low()                                #  not cs.low  but  cs.high ??

*1:SPI 4-Wireモードのみサポート

STEVAL-DRONE01の制御ボードに搭載されたセンサーを読む(読みたい)

STEVAL-DRONE01に制御ボードSTEVAL-FCU001V1が搭載されていて、そのボードには、角速度センサ(LIS2MDL)や加速度センサ(LSM6DSL)が搭載されている。STM32F401CCとセンサーはSPIで接続されている。が、、回路図をよく読むと接続が、SPIの3線(CS/CLK/MOSI)なのであった。普通はMISOも含めた4線なのだが。これはSPIの3-wire Bidirectionalモードと呼ばれるらしい。MOSIで送受信の両方やってしまうというもの。だから、マスターとスレーブはMOSI(スレーブ側はMISO)に対して入力/出力モードを切り替えながら動作することになる。さすがにMicroPythonではSPI 3-wireモードは実装されておらず、掲示板等では、俺作ってみるぜとか書き込まれたりする。STM32ではハードウエアレベルでBidirectionalModeをサポートしていて、レジスタの該当のビットを操作することで、SPI通信時の入出力の方向を変えられるようだ。

問題はMicroPythonのSPIドライバでどうやって3-wireモードを実現するか。。今回はあくまでもレジスタの値取得が目的でありアクセス性能は求めないので、Pinをソフトウエアで上げ下げしながらSPI風の信号を作り出す、ビットバンギングで組んでみようと思う。Pinをソフトウエアですべて制御したら、どのタイミングで入出力の方向を切り替えるとか管理できるのでデバッグもしやすい。クロックの精度とか無視するならソフトでSPIバスを作り出すのはそれほど難しくない。

STM32F401CC用にMicroPythonをgdbでデバッグする->なんとかREPLが動いた

背景:
STEVAL-DRONE01上にSTM32F401CCが搭載されていて、普通にCとかRUSTとかで開発するにはサイズの問題が出ないと思うが、センサのデータ処理や姿勢制御アルゴリズムについて試行錯誤したいのでSTM32F401CC上でMicroPythonを動かしたいと思っている。が、マイコンに内蔵されているFlashMemoryが256KBのため、普通にビルドしたMicroPythonではサイズが大きすぎて搭載できない。だから、、他の省リソースマイコン用のMicroPythonをベースにSTM32F401CC用のMicroPythonをカスタマイズして動かそうとしている。。が、、動かない。そこで、ST-LINK経由でOpenOCD + GDBデバッグ
以下のコマンドでOpenOCDとgdbを起動

./bin-x64/openocd.exe -f ./scripts/interface/stlink-v2.cfg  -f ./scripts/target/stm32f4x.cfg
/usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin/arm-none-eabi-gdb.exe ./build-NUCLEO_L073RZ_KAI2/firmware.elf

gdbのセットアップとロード

target remote localhost:3333
monitor reset halt
load
hbreak stm32_main
cont

stm32_mainで一旦止めて、Cソース行の単位でステップ実行

320         MICROPY_BOARD_STARTUP();
(gdb) n
320         MICROPY_BOARD_STARTUP();
(gdb) n
367         HAL_InitTick(TICK_INT_PRIORITY);
(gdb) n
370         SystemClock_Config();
(gdb) n

Breakpoint 4, stm32_main (reset_mode=0) at main.c:312
312         SCB->VTOR = MICROPY_HW_VTOR;
(gdb) n
316         SCB->CCR |= SCB_CCR_STKALIGN_Msk;
(gdb) n
320         MICROPY_BOARD_STARTUP();
(gdb) n
367         HAL_InitTick(TICK_INT_PRIORITY);
(gdb) n
370         SystemClock_Config();
(gdb) n

Breakpoint 4, stm32_main (reset_mode=0) at main.c:312
312         SCB->VTOR = MICROPY_HW_VTOR;
(gdb)

スタートアップの所でグルグル回っているようだ。ステップ実行で調べると、FLASH->ACRの値を変更しようとしてハードフォールトが発生している。多分番地が正しく設定されていないからでは?と推測

(gdb) n
109         FLASH->ACR |= FLASH_ACR_LATENCY;
(gdb) n
HardFault_Handler () at stm32_it.c:213
213         __asm volatile (

FLASHのが多分レジスタ用構造体の開始番地で->ACRでオフセットの番地を計算しているのだろうと推測。FLASHはマクロか何かで置換されているのか、シンボル不明になっている。

104     void SystemClock_Config(void) {
105         // Enable power control peripheral
106         __HAL_RCC_PWR_CLK_ENABLE();
107
108         // Set flash latency to 1 because SYSCLK > 16MHz
(gdb) l
109         FLASH->ACR |= FLASH_ACR_LATENCY;
110
111         // Enable the 16MHz internal oscillator
112         RCC->CR |= RCC_CR_HSION;
113         while (!(RCC->CR & RCC_CR_HSIRDY)) {
114         }
115
116         // Use HSI16 and the PLL to get a 32MHz SYSCLK
117         RCC->CFGR = 1 << RCC_CFGR_PLLDIV_Pos | 1 << RCC_CFGR_PLLMUL_Pos;
118         RCC->CR |= RCC_CR_PLLON;
(gdb) p/x FLASH
No symbol "FLASH" in current context.

(gdb) disas $pc,+16
Dump of assembler code from 0x8023912 to 0x8023922:
=> 0x08023912 <SystemClock_Config+14>:  ldr     r2, [pc, #88]   ; (0x802396c <SystemClock_Config+104>)
   0x08023914 <SystemClock_Config+16>:  ldr     r1, [r2, #0]
   0x08023916 <SystemClock_Config+18>:  movs    r0, #1
   0x08023918 <SystemClock_Config+20>:  orrs    r1, r0
   0x0802391a <SystemClock_Config+22>:  str     r1, [r2, #0]
   0x0802391c <SystemClock_Config+24>:  ldr     r2, [r3, #0]
   0x0802391e <SystemClock_Config+26>:  orrs    r2, r0
   0x08023920 <SystemClock_Config+28>:  str     r2, [r3, #0]
End of assembler dump.
(gdb)

アセンブルを見てもイマイチどれがどう対応しているのか不明。だから、、nextiで機械語単位でステップをかけて落ちた所の操作対象レジスタが誤った番地だろうと推測。

アドレス: 0x08023914を実行した時点でハードフォールトになったので、この命令でしくじっていると判断。

(gdb) nexti
0x08023914      109         FLASH->ACR |= FLASH_ACR_LATENCY;
   0x08023912 <SystemClock_Config+14>:  16 4a   ldr     r2, [pc, #88]   ; (0x802396c <SystemClock_Conf
ig+104>)
=> 0x08023914 <SystemClock_Config+16>:  11 68   ldr     r1, [r2, #0]
   0x08023916 <SystemClock_Config+18>:  01 20   movs    r0, #1
   0x08023918 <SystemClock_Config+20>:  01 43   orrs    r1, r0
   0x0802391a <SystemClock_Config+22>:  11 60   str     r1, [r2, #0]
(gdb) i r
r0             0x0      0
r1             0x0      0
r2             0x40022000       1073881088
r3             0x40021000       1073876992
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            0xe000ed00       -536810240
sp             0x2000ffb8       0x2000ffb8
lr             0x8022c8f        134360207
pc             0x8023914        0x8023914 <SystemClock_Config+16>
xPSR           0x1000000        16777216

(gdb) nexti
HardFault_Handler () at stm32_it.c:213
213         __asm volatile (
=> 0x08022fd8 <HardFault_Handler+0>:    70 46   mov     r0, lr
   0x08022fda <HardFault_Handler+2>:    c0 08   lsrs    r0, r0, #3
   0x08022fdc <HardFault_Handler+4>:    ef f3 08 80     mrs     r0, MSP
   0x08022fe0 <HardFault_Handler+8>:    01 d3   bcc.n   0x8022fe6 <HardFault_Handler+14>
   0x08022fe2 <HardFault_Handler+10>:   ef f3 09 80     mrs     r0, PSP
   0x08022fe6 <HardFault_Handler+14>:   85 e7   b.n     0x8022ef4 <HardFault_C_Handler>

推測だが、間接アドレッシングで、r2で示される番地にr1の値を入れると。r2の値が0x40022000 なので、
この値がFLASHレジスタのベースアドレスと解釈してビルドされているのだろう。多分。

=> 0x08023914 <SystemClock_Config+16>:  11 68   ldr     r1, [r2, #0]

STM32L073のFLASHレジスタの番地と、STM32F401のレジスタの番地が違ってるのではなかろうか。。

STM32L0x3 の仕様書を確認すると、0x4002_2000からFLASH Registerの領域で、オフセット0の番地が、FLASH_ACRとなっている。だから、STM32L073としての動作は正しい。一方、STM32F401CCの場合、FLASH InterfaceRegisterの領域は、0x4002 3C00 である。だから、間違った領域に値を書こうとしている。ちなみに、0x4002_2000はAHB用のレジスタ空間だけど、該当の番地にはレジスタが存在しない。だからアクセス異常になってリセットがかかったのだろう。これらのアドレス解決はソースを見ないと確実には分からないが、build用のマイコンシリーズ定義に従いifdefで切り替えられていると思う。マイコンシリーズ定義は、STM32L073を指定する、l0 (エルゼロ)を設定しているので、レジスタ群の番地がSTM32L073として解決されていると思う。悩みポイントとしては、マイコンシリーズ定義を本来のf0(エフゼロ)にしてしまうと、高機能関数群が一度に入ってくるので、サイズが大きくなってしまう。切り離しもむつかしい(中途半端に切り離すとシンボル未定義エラーになる)

やりたいこと:
サイズが大きくなるようなモリモリの機能はいらないから、レジスタマップだけはF401に合わせたい。だけど、レジスタマップと機能はペアで実装されるものだから、切り離して実装するのは無理だろう。。多分。

今回問題になっている、SystemClock_Config (file:powercontrolboot.c)のソースを確認すると、STM32L0のifdefが存在し、やはりマイコン種別として間違ったルーチンが呼び出されいる。STM32F4のマクロが有効になった状態でビルドされるようにしないと、正しい初期化等ができない。

#elif defined(STM32L0)

void SystemClock_Config(void) {
    // Enable power control peripheral
    __HAL_RCC_PWR_CLK_ENABLE();

    // Set flash latency to 1 because SYSCLK > 16MHz
    FLASH->ACR |= FLASH_ACR_LATENCY;
    // Set flash latency to 1 because SYSCLK > 24MHz
    FLASH->ACR = (FLASH->ACR & ~0x7) | 0x1;

マイコンシリーズの異なるSTM32L072RZをベースに、STM32F101CCに持っていくアプローチはif defが異なりすぎてなかなかまともにビルドできないので、STM32F401REから機能を削ってサイズを減らすアプローチに変更した。
この場合、エラーが出たら、機能を有効化すればよいので、ビルドエラーは容易に回避可能であるが、やはり、F4シリーズの場合ベースのビルドでは機能モリモリでサイズが大きくなる傾向が強く、ifdefで機能を削る程度ではあまり効果が得られない。ということで、、機能を付けたり減らしたりという程度ではなかなか小サイズのMicroPythonを作り出すのは難しいことが分かった。(MicroPython自体、機能を自由に選択してマイコンFlashサイズにフィットさせるような考えでは作られていないので)

$ make BOARD=NUCLEO_F401CC V=1 DEBUG=1
     *略*
arm-none-eabi-ld: build-NUCLEO_F401CC/firmware.elf section `.text' will not fit in region `FLASH_TEXT'
arm-none-eabi-ld: region `FLASH_TEXT' overflowed by 150800 bytes
arm-none-eabi-ld: build-NUCLEO_F401CC/modpyb.o:(.rodata.pyb_module_globals_table+0x7c): undefined reference to `pyb_flash_type'
make: *** [Makefile:714: build-NUCLEO_F401CC/firmware.elf] Error 1
$ make BOARD=NUCLEO_F401CC V=1

arm-none-eabi-size build-NUCLEO_F401CC/firmware.elf
   text    data     bss     dec     hex filename
 254968      12   37140  292120   47518 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

ST-Link Utilityでファームは焼けた

21:23:51 : ST-LINK SN : 18470000000000000
21:23:51 : V2J29S7
21:23:51 : Connected via SWD.
21:23:51 : SWD Frequency = 4,0 MHz.
21:23:51 : Connection mode : Normal.
21:23:51 : Debug in Low Power mode enabled.
21:23:52 : Device ID:0x423 
21:23:52 : Device flash Size : 256KBytes
21:23:52 : Device family :STM32F401xB/C
21:24:01 : [firmware.hex] opened successfully.
                  Address Ranges [0x08000000 0x080039D0] [0x08005000 0x0803FA34] 
21:24:01 : [firmware.hex] checksum : 0x017CD635 
21:24:15 : Memory programmed in 7s and 47ms.

焼くには焼いたが、、動かしてみるとやっぱりおかしい。デバッグ情報付いていないので詳細は分からないが、再起動を繰り返している印象。またどこかでアドレスの不一致か、あるいは、Flashメモリの使い方のまずさか?により異常が発生しているようだ。アセンブラコードを頼りにトレースするのか!?

最低、シンボルは見えるようなので、、おおよそどこらへんにいるかはわかりそうだ。

(gdb) disas $pc,+32
Dump of assembler code from 0x8026110 to 0x8026130:
=> 0x08026110 <stm32_main+12>:  str     r3, [r4, #8]
   0x08026112 <stm32_main+14>:  ldr     r3, [r4, #20]
   0x08026114 <stm32_main+16>:  orr.w   r3, r3, #512    ; 0x200
   0x08026118 <stm32_main+20>:  sub     sp, #32
   0x0802611a <stm32_main+22>:  str     r3, [r4, #20]
   0x0802611c <stm32_main+24>:  mov     r6, r0
   0x0802611e <stm32_main+26>:  bl      0x8026c08 <powerctrl_check_enter_bootloader>
   0x08026122 <stm32_main+30>:  ldr     r3, [pc, #504]  ; (0x802631c <stm32_main+536>)
   0x08026124 <stm32_main+32>:  ldr     r2, [r3, #0]
   0x08026126 <stm32_main+34>:  orr.w   r2, r2, #512    ; 0x200
   0x0802612a <stm32_main+38>:  str     r2, [r3, #0]
   0x0802612c <stm32_main+40>:  ldr     r2, [r3, #0]
   0x0802612e <stm32_main+42>:  orr.w   r2, r2, #1024   ; 0x400
End of assembler dump.
(gdb) where
#0  0x08026110 in stm32_main ()
#1  0x0802c8b2 in .bss_zero_entry ()
#2  0x0802c8b2 in .bss_zero_entry ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

機械語単位でステップ実行してみると、init_flash_fsで異常が発生して再起動しているようだ。Flash上のファイルシステムが正しく初期化できないのだろう。

0x0802627e in stm32_main ()
=> 0x0802627e <stm32_main+378>: ff f7 c7 fe     bl      0x8026010 <init_flash_fs>
(gdb)

Breakpoint 1, 0x08026110 in stm32_main ()
=> 0x08026110 <stm32_main+12>:  a3 60   str     r3, [r4, #8]
(gdb)

init_flash_fsにHardware Breadpointを設定して再実行、

(gdb) i b
Num     Type           Disp Enb Address    What
1       hw breakpoint  keep y   0x08026110 <stm32_main+12>
        breakpoint already hit 7 times
(gdb) disable 1
(gdb) hb init_flash_fs
Hardware assisted breakpoint 2 at 0x8026010
(gdb) cont
Continuing.

Breakpoint 2, 0x08026010 in init_flash_fs ()
=> 0x08026010 <init_flash_fs+0>:        03 28   cmp     r0, #3
(gdb) disas $pc,+32
Dump of assembler code from 0x8026010 to 0x8026030:
=> 0x08026010 <init_flash_fs+0>:        cmp     r0, #3
   0x08026012 <init_flash_fs+2>:        push    {r4, lr}
   0x08026014 <init_flash_fs+4>:        mov     r4, r0
   0x08026016 <init_flash_fs+6>:        bne.n   0x802601c <init_flash_fs+12>
   0x08026018 <init_flash_fs+8>:        bl      0x8026f10 <factory_reset_create_filesystem>
   0x0802601c <init_flash_fs+12>:       ldr     r0, [pc, #52]   ; (0x8026054 <init_flash_fs+68>)
   0x0802601e <init_flash_fs+14>:       movw    r1, #1906       ; 0x772
   0x08026022 <init_flash_fs+18>:       bl      0x801e384 <mp_vfs_mount_and_chdir_protected>
   0x08026026 <init_flash_fs+22>:       cmn.w   r0, #19
   0x0802602a <init_flash_fs+26>:       bne.n   0x802604c <init_flash_fs+60>
   0x0802602c <init_flash_fs+28>:       cmp     r4, #3
   0x0802602e <init_flash_fs+30>:       bne.n   0x802603a <init_flash_fs+42>
End of assembler dump.
(gdb) nexti
0x08026012 in init_flash_fs ()
=> 0x08026012 <init_flash_fs+2>:        10 b5   push    {r4, lr}

最後の手段として、、FLASHを無効化にするオプションを有効にして再度ビルド、サイズもきちきち収まって、REPLが動いた。

MPY: soft reboot
MicroPython v1.16-243-g8c4ba575f-dirty on 2021-08-29; NUCLEO-F401CC with STM32F401CC
Type "help()" for more information.
>>> import gc
>>> gc.mem_free()
46000

>>> help('modules')
__main__          micropython       uhashlib       uselect
_onewire          pyb                     uio                ustruct
_uasyncio         uarray                ujson             usys
builtins            ubinascii            umachine       utime
cmath             ucollections       uos                  uzlib
gc                   uctypes              urandom
math              uerrno                ure
Plus any modules on the filesystem
>>>

こうやってみると、_uasyncio、_onewire、uhashlib、ujson、ure、uselect、uzlib等もいらないと思うけど、、
これらが切りはせるのかは不明

Flashが無いことになっているので、、uos.mkdir()などを実行するとエラーになる。ハングしないのでよく作りこまれていますね。。

>>> uos.mkdir('/hoge')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 19] ENODEV

GPIOの番号とかずれてるかもしれないので、I2CやSPIを動かすにはしばらくかかるかもしれませんが、インタープリタマイコン操作できるのは楽だ~。特に使い慣れないマイコンの場合、周辺I/O等の試行錯誤が繰り返しできるので理解もはかどるはず。

忘れないように最終設定を記録
file:boards/NUCLEO_F401CC/mpconfigboard.h
(UARTのPINを修正、機能無効化を定義、それ以外は、STM32F401REのまま。だから、、Pin配置が多分違っていてSPI, I2C等は動かないと思われる)

#define MICROPY_HW_BOARD_NAME       "NUCLEO-F401CC"
#define MICROPY_HW_MCU_NAME         "STM32F401CC"

#define MICROPY_EMIT_THUMB          (0)
#define MICROPY_EMIT_INLINE_THUMB   (0)
#define MICROPY_OPT_COMPUTED_GOTO   (0)
#define MICROPY_PY_GENERATOR_PEND_THROW (0)
#define MICROPY_PY_MACHINE_BITSTREAM (0)

#define MICROPY_PY_FRAMEBUF         (0)
#define MICROPY_PY_USOCKET          (0)
#define MICROPY_PY_NETWORK          (0)
#define MICROPY_PY_STM              (0)
#define MICROPY_PY_PYB_LEGACY       (0)
#define MICROPY_PY_UHEAPQ           (0)
#define MICROPY_PY_UTIMEQ           (0)

//# trial (8/29 22:09)
//#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (1)
#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)

#define MICROPY_HW_HAS_SWITCH       (1) 
#define MICROPY_HW_HAS_FLASH        (0)     // trial (8/29 22:43)
#define MICROPY_HW_HAS_LCD          (0)    // trial (8/29 22:43)
#define MICROPY_HW_ENABLE_RTC       (1)
#define MICROPY_HW_ENABLE_SDCARD    (0)     // trial (8/29 22:43)
#define MICROPY_HW_ENABLE_SERVO     (0)    // trial (8/29 22:43)
#define MICROPY_HW_ENABLE_USB       (0)    // trial (8/29 22:43)
#define MICROPY_HW_ENABLE_DAC       (0)    // trial (8/29 22:43)


// HSE is 8MHz, HSI is 16MHz CPU freq set to 84MHz
// Default source for the clock is HSI.
// For revisions of the board greater than C-01, HSE can be used as a
// clock source by removing the #define MICROPY_HW_CLK_USE_HSE line
#define MICROPY_HW_CLK_USE_HSI (1)

#if MICROPY_HW_CLK_USE_HSI
#define MICROPY_HW_CLK_PLLM (16)
#else
#define MICROPY_HW_CLK_PLLM (8)
#endif
#define MICROPY_HW_CLK_PLLN (336)
#define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV4)
#define MICROPY_HW_CLK_PLLQ (7)

// UART config
#define MICROPY_HW_UART1_TX     (pin_A9)
#define MICROPY_HW_UART1_RX     (pin_A10)
#define MICROPY_HW_UART2_TX     (pin_A2)
#define MICROPY_HW_UART2_RX     (pin_A3)
// 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_1
#define MICROPY_HW_UART_REPL_BAUD   115200

// I2C buses
#define MICROPY_HW_I2C1_SCL (pin_B8)        // Arduino D15, pin 3 on CN10
#define MICROPY_HW_I2C1_SDA (pin_B9)        //         D14, pin 5 on CN10
#define MICROPY_HW_I2C2_SCL (pin_B10)       // Arduino D6,  pin 25 on CN10
#define MICROPY_HW_I2C2_SDA (pin_B3)        // Arduino D3,  pin 31 on CN10
#define MICROPY_HW_I2C3_SCL (pin_A8)        // Arduino D7,  pin 23 on CN10
#define MICROPY_HW_I2C3_SDA (pin_C9)        //              pin  1 on CN10

// SPI buses
#define MICROPY_HW_SPI1_NSS     (pin_A15)   //              pin 17 on CN7
#define MICROPY_HW_SPI1_SCK     (pin_A5)    // Arduino D13, pin 11 on CN10
#define MICROPY_HW_SPI1_MISO    (pin_A6)    // Arduino D12, pin 13 on CN10
#define MICROPY_HW_SPI1_MOSI    (pin_A7)    // Arduino D11, pin 15 on CN10

#define MICROPY_HW_SPI2_NSS     (pin_B12)   //              pin 16 on CN10
#define MICROPY_HW_SPI2_SCK     (pin_B13)   //              pin 30 on CN10
#define MICROPY_HW_SPI2_MISO    (pin_B14)   //              pin 28 on CN10
#define MICROPY_HW_SPI2_MOSI    (pin_B15)   //              pin 26 on CN10

#define MICROPY_HW_SPI3_NSS     (pin_A4)    // Arduino A2,  pin 32 on CN7
#define MICROPY_HW_SPI3_SCK     (pin_B3)    // Arduino D3,  pin 31 on CN10
#define MICROPY_HW_SPI3_MISO    (pin_B4)    // Arduino D5,  pin 27 on CN10
#define MICROPY_HW_SPI3_MOSI    (pin_B5)    // Arduino D4,  pin 29 on CN10

// USRSW is pulled low. Pressing the button makes the input go high.
#define MICROPY_HW_USRSW_PIN        (pin_C13)
#define MICROPY_HW_USRSW_PULL       (GPIO_NOPULL)
#define MICROPY_HW_USRSW_EXTI_MODE  (GPIO_MODE_IT_FALLING)
#define MICROPY_HW_USRSW_PRESSED    (0)

// LEDs
#define MICROPY_HW_LED1             (pin_A5) // Green LD2 LED on Nucleo
#define MICROPY_HW_LED_ON(pin)      (mp_hal_pin_high(pin))
#define MICROPY_HW_LED_OFF(pin)     (mp_hal_pin_low(pin))

boards/NUCLEO_F401CC/mpconfigboard.mk

MCU_SERIES = f4
CMSIS_MCU = STM32F401xE
AF_FILE = boards/stm32f401_af.csv
LD_FILES = boards/stm32f401cc.ld boards/common_ifs.ld
TEXT0_ADDR = 0x08000000
TEXT1_ADDR = 0x08020000

# trial (8/29 22:09)

# MicroPython settings
MICROPY_VFS_FAT = 0

# Don't include default frozen modules because MCU is tight on flash space
FROZEN_MANIFEST ?=

boards/stm32f401cc.ld

/*
    GNU linker script for STM32F401CC
*/

/* Specify the memory areas */
MEMORY
{
    FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 256K /* entire flash */
    FLASH_ISR (rx)  : ORIGIN = 0x08000000, LENGTH = 16K /* sector 0 */
    FLASH_FS (rx)   : ORIGIN = 0x08004000, LENGTH = 4K /* sectors 1 16K but use 4K*/
    FLASH_TEXT (rx) : ORIGIN = 0x08005000, LENGTH = 236K /* 12K +  sectors 2,3 are 16K sectors 4 is 64K 5 are 128K */
    RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 64K
}

/* produce a link error if there is not this amount of RAM for these sections */
_minimum_stack_size = 2K;
_minimum_heap_size = 16K; /* tunable */

/* Define the stack.  The stack is full descending so begins just above last byte
   of RAM.  Note that EABI requires the stack to be 8-byte aligned for a call. */
_estack = ORIGIN(RAM) + LENGTH(RAM) - _estack_reserve;
_sstack = _estack - 16K;

/* RAM extents for the garbage collector */
_ram_start = ORIGIN(RAM);
_ram_end = ORIGIN(RAM) + LENGTH(RAM);
_heap_start = _ebss; /* heap starts just after statically allocated memory */
_heap_end = _sstack;
$ pwd
 ....  ports/stm32/boards/NUCLEO_F401CC

$ ls
mpconfigboard.h  mpconfigboard.mk  pins.csv  stm32f4xx_hal_conf.h

pins.csv , stm32fxx_hal_conf.hは、../NUCLEO_F401RE/からコピー

さらに不要なライブラリを外した。起動時の標準ライブラリは以下

>>> help('modules')
__main__          math                   uctypes           urandom
_onewire          micropython       uerrno            uselect
builtins            pyb                      uio                  ustruct
cmath              uarray                  umachine       usys
gc                    ucollections         uos                  utime
Plus any modules on the filesystem

現在のファームのサイズ、sizeコマンドによると、223776B (219KB)、割り当てた領域が236KBなので、ほとんど余裕がない。きちきちで収まっている。

$ /usr/local/GNUToolsARMEmbedded/4.8_2013q4/bin/arm-none-eabi-size.exe firmware.elf
   text    data     bss     dec     hex filename
 203584      12   20180  223776   36a20 firmware.elf

STEVAL-DRONE01の制御基板にはLEDが2つ付いているのだが、、Lチカも以下の記述で実行可能。楽だ。

>>> import pyb
>>> led1 = pyb.LED(1)
>>> led1.toggle()
>>> led1.toggle()

■追記
MicroPythonを使って長いけど、普段はビルドされたESP32用ファームを使ってるだけなので、内部構造とか全く分かっていなかった。こうやってデバッグしていると、分からないながらも少しずつ知識が増える。だから。。?? 課題が発生するとそれを解決しようとあれこれ調べるので勉強になる。