ハードウェア

ここまでで、ツールと開発プロセスにある程度慣れたはずです。このセクションでは、実際のハードウェアに切り替えます。 開発プロセスは、ほとんど同じままです。飛び込みましょう。

ハードウェアを知る

始める前に、プロジェクトの設定に利用するターゲットデバイスのいくつかの特徴を確認する必要があります。

  • ARMコア、例えばCortex-M3です。
  • そのARMコアはFPUを搭載していますか?Cortex-M4FとCortex-M7Fは、搭載しています。
  • ターゲットデバイスに搭載されているフラッシュメモリとRAMの容量はいくらですか? 例えば、フラッシュは256KiBでRAMは32KiBです。
  • フラッシュメモリとRAMは、アドレス空間のどこにマッピングされていますか? 例えば、RAMは、通常0x2000_0000番地に位置します。

これらの情報は、デバイスのデータシートかリファレンスマニュアルに掲載されています。

このセクションでは、私たちのリファレンスハードウェアであるSTM32F3DISCOVERYを使用します。 このボードは、STM32F303VCT6マイクロコントローラを1つ搭載しています。このマイクロコントローラは以下のものを持っています。

  • 単精度FPUを含むCortex-M4Fコアが1つ
  • 0x0800_0000番地に配置された256KiBのフラッシュメモリ
  • 0x2000_0000番地に配置された40KiBのRAM。(別のRAM領域もありますが、説明の簡単化のため、取り扱いません)

設定

テンプレートの新しいインスタンスを使って、スクラッチから書いていきましょう。 cargo-generateを使用しない方法については、前セクションのQEMUを参照して下さい。

$ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
 Project Name: app
 Creating project called `app`...
 Done! New project created /tmp/app

 $ cd app

第一ステップは、.cargo/configにデフォルトコンパイルターゲットを設定することです。

$ tail -n5 .cargo/config
[build]
# 以下のコンパイルターゲットから1つを選びます
# target = "thumbv6m-none-eabi"    # Cortex-M0およびCortex-M0+
# target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4およびCortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4FおよびCortex-M7F (with FPU)

Cortex-M4Fコアを対象とするものとして、thumbv7em-none-eabihfを使います。

第二ステップは、memory.xファイルにメモリ領域の情報を入力することです。

$ cat memory.x
/* STM32F303VCT6用のリンカスクリプト */
MEMORY
{
  /* 注記 1 K = 1 KiBi = 1024バイト */
  FLASH : ORIGIN = 0x08000000, LENGTH = 256K
  RAM : ORIGIN = 0x20000000, LENGTH = 40K
}

debug::exit()の呼び出しが、コメントアウトされているか削除されていることを確認して下さい。 これは、QEMUで実行する時のみ、使用します。

#[entry]
fn main() -> ! {
    hprintln!("Hello, world!").unwrap();

    // exit QEMU
    // NOTE do not run this on hardware; it can corrupt OpenOCD state
    // debug::exit(debug::EXIT_SUCCESS);
    // QEMUを終了する
    // 注記、ハードウェア上で実行しないで下さい。OpenOCDの状態を破壊する可能性があります。
    // debug::exit(debug::EXIT_SUCCESS);

    loop {}
}

これまでやってきた通り、cargo buildでプログラムをクロスコンパイルし、 cargo-binutilsでバイナリを調べることができます。 cortex-m-rtクレートは、チップを動作させるために必要な、全てのおまじないを処理します。 便利なことに、ほとんど全てのCortex-M CPUが同じ方法で起動します。

$ cargo build --example hello

デバッグ

デバッグ方法は少し違います。実際、最初のステップは、ターゲットデバイスによって異なります。 このセクションでは、STM32F3DISCOVERY上で実行しているプログラムをデバッグするために必要となる手順を説明します。 これは、参考の役目を果たします。デバイス固有のデバッグ情報は、 the Debugonomiconを参照して下さい。

以前と同様に、リモートデバッグを行います。クライアントがGDBプロセスであることも同様です。 しかし、今回、サーバはOpenOCDになります。

インストールの確認セクションでやったように、ノートPCまたはPCをdiscoveryボードに接続し、 ST-LINKヘッダが設定されていることを確認して下さい。

discoveryボードのST-LINKに接続するために、端末でopenocdを実行して下さい。 このコマンドは、テンプレートプロジェクトのルートディレクトリから実行して下さい。 openocdは、どのインタフェースファイルとターゲットファイルを使うか、が記述されているopenocd.cfgファイルを見つけます。

$ cat openocd.cfg
# STM32F3DISCOVERY開発ボード用のOpenOCD設定サンプル

# 持っているハードウェアのリビジョンに応じて、これらのインタフェースのうち、1つを選んで下さい。
# 常に、1つのインタフェースがコメントアウトされているべきです。

# Revision C (newer revision)
# リビジョンC (新しいリビジョン)
source [find interface/stlink-v2-1.cfg]

# リビジョンAとB(古いリビジョン)
# source [find interface/stlink-v2.cfg]

source [find target/stm32f3x.cfg]

注記 インストールの確認セクションで、古いバージョンのdiscoveryボードを持っていることが判明している場合、 interface/stlink-v2.cfgを使うようにopenocd.cfgファイルを修正する必要があります。

$ openocd
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 use 'transport select <transport>'.
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 2.913879
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints

別の端末で、GDBを実行します。こちらも、テンプレートプロジェクトのルートディレクトから実行して下さい。

$ <gdb> -q target/thumbv7em-none-eabihf/debug/examples/hello

次に、TCP 3333ポートで接続待ちしているOpenOCDに、GDBを接続します。

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

それでは、loadコマンドを使って、マイクロコントローラにプログラムを書き込んで下さい。

(gdb) load
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x1e70 lma 0x8000400
Loading section .rodata, size 0x61c lma 0x8002270
Start address 0x800144e, load size 10380
Transfer rate: 17 KB/sec, 3460 bytes/write.

プログラムがロードされました。このプログラムはセミホスティングを使用します。そこで、 セミホスティングを呼び出して何かを行う前に、OpenOCDにセミホスティングを有効にするように、 指示する必要があります。

(gdb) monitor arm semihosting enable
semihosting is enabled

monitor helpコマンドを実行することで、全てのOpenOCDコマンドを見ることができます。

以前のように、ブレイクポイントとcontinueコマンドを使用することで、mainまでスキップすることができます。

(gdb) break main
Breakpoint 1 at 0x8000d18: file examples/hello.rs, line 15.

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

Breakpoint 1, main () at examples/hello.rs:15
15          let mut stdout = hio::hstdout().unwrap();

nextでプログラムを先に進めると、以前と同じ結果になるはずです。

(gdb) next
16          writeln!(stdout, "Hello, world!").unwrap();

(gdb) next
19          debug::exit(debug::EXIT_SUCCESS);

この時点で、OpenOCDコンソールに、他のものと入り混じって「Hello, world!」と表示されるはずです。

$ openocd
(..)
Info : halted: PC: 0x08000e6c
Hello, world!
Info : halted: PC: 0x08000d62
Info : halted: PC: 0x08000d64
Info : halted: PC: 0x08000d66
Info : halted: PC: 0x08000d6a
Info : halted: PC: 0x08000a0c
Info : halted: PC: 0x08000d70
Info : halted: PC: 0x08000d72

もう一度nextを実行して、プロセッサにdebug::exitを実行させます。 これはブレイクポイントとして動作し、プロセスを停止します。

(gdb) next

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0800141a in __syscall ()

また、OpenOCDコンソールに次のものが表示されます。

$ openocd
(..)
Info : halted: PC: 0x08001188
semihosting: *** application exited ***
Warn : target not halted
Warn : target not halted
target halted due to breakpoint, current mode: Thread
xPSR: 0x21000000 pc: 0x08000d76 msp: 0x20009fc0, semihosting

しかし、マイクロコントローラ上で動作しているプロセスは終了していないため、 continueもしくは同様のコマンドを使って、プログラムを再開することができます。

ここで、quitコマンドを使うことで、GDBを終了できます。

(gdb) quit

デバッグにはもう少しステップが必要なので、これらのステップをopenocd.gdbというGDBスクリプトにまとめました。

$ cat openocd.gdb
target remote :3333

# デマングルされたシンボルを表示します
set print asm-demangle on

# 未処理の例外、ハードフォールト、パニックを検出します
break DefaultHandler
break HardFault
break rust_begin_unwind

monitor arm semihosting enable

load

# プロセスを開始しますが、すぐにプロセッサを停止します
stepi

<gdb> -x openocd.gdb $programを実行することで、GDBはすぐにOpenOCDに接続し、 セミホスティングを有効化し、プログラムをロードした上で、プロセスを開始します。

別の方法として、<gdb> -x openocd.gdbをカスタムランナーにして、cargo runでプログラムをビルドし、 さらにGDBセッションを開始することもできます。このランナーは、.cargo/configに含まれていますが、 コメントアウトされています。

$ head -n10 .cargo/config
[target.thumbv7m-none-eabi]
# ここのコメントアウトを外すと、`cargo run`はQEMUでプログラムを実行します
# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# 3つの選択肢のうち、1つのコメントアウトを外すと、`cargo run`はGDBセッションを開始します。
# どの選択肢を使うか、は対象システムによって異なります。
runner = "arm-none-eabi-gdb -x openocd.gdb"
# runner = "gdb-multiarch -x openocd.gdb"
# runner = "gdb -x openocd.gdb"
$ cargo run --example hello
(..)
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x1e70 lma 0x8000400
Loading section .rodata, size 0x61c lma 0x8002270
Start address 0x800144e, load size 10380
Transfer rate: 17 KB/sec, 3460 bytes/write.
(gdb)