硬件

到目前为止,您应该对工具和开发过程有所了解。在本节中,我们将切换到真实的硬件;该过程将基本保持不变。让我们深入了解一下。

了解您的硬件

在开始之前,您需要确定目标设备的一些特性,因为这些特性将用于配置项目

  • ARM 内核。例如,Cortex-M3。

  • ARM 内核是否包含 FPU?Cortex-M4F 和 Cortex-M7F 内核包含。

  • 目标设备具有多少闪存和 RAM?例如,256 KiB 闪存和 32 KiB RAM。

  • 闪存和 RAM 在地址空间中映射到哪里?例如,RAM 通常位于地址 0x2000_0000

您可以在设备的数据手册或参考手册中找到此信息。

在本节中,我们将使用我们的参考硬件 STM32F3DISCOVERY。该板包含一个 STM32F303VCT6 微控制器。该微控制器具有

  • 一个包含单精度 FPU 的 Cortex-M4F 内核

  • 位于地址 0x0800_0000 的 256 KiB 闪存。

  • 位于地址 0x2000_0000 的 40 KiB 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.toml 中设置默认编译目标。

tail -n5 .cargo/config.toml
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

我们将使用 thumbv7em-none-eabihf,因为它涵盖了 Cortex-M4F 内核。

注意:您可能还记得上一章,我们必须安装所有目标,这是一个新的目标。因此,不要忘记为该目标运行安装过程 rustup target add thumbv7em-none-eabihf

第二步是将内存区域信息输入 memory.x 文件。

$ cat memory.x
/* Linker script for the STM32F303VCT6 */
MEMORY
{
  /* NOTE 1 K = 1 KiBi = 1024 bytes */
  FLASH : ORIGIN = 0x08000000, LENGTH = 256K
  RAM : ORIGIN = 0x20000000, LENGTH = 40K
}

注意:如果您出于某种原因在对特定构建目标进行首次构建后更改了 memory.x 文件,则在 cargo build 之前执行 cargo clean,因为 cargo build 可能不会跟踪 memory.x 的更新。

我们将再次从 hello 示例开始,但首先我们必须进行一个小小的更改。

examples/hello.rs 中,确保 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);

    loop {}
}

您现在可以使用 cargo build 交叉编译程序,并使用 cargo-binutils 检查二进制文件,就像以前一样。cortex-m-rt crate 处理让您的芯片运行所需的所有魔法,因为,很方便的是,几乎所有 Cortex-M CPU 都以相同的方式启动。

cargo build --example hello

调试

调试看起来会有些不同。实际上,第一步可能看起来不同,具体取决于目标设备。在本节中,我们将展示在 STM32F3DISCOVERY 上运行的程序所需的调试步骤。这旨在作为参考;有关调试的设备特定信息,请查看 Debugonomicon

与以前一样,我们将进行远程调试,客户端将是 GDB 进程。但是,这次服务器将是 OpenOCD。

与在 验证 部分中所做的一样,将发现板连接到您的笔记本电脑/PC,并检查 ST-LINK 标头是否已填充。

在终端上运行 openocd 以连接到发现板上的 ST-LINK。从模板的根目录运行此命令;openocd 将拾取 openocd.cfg 文件,该文件指示要使用哪个接口文件和目标文件。

cat openocd.cfg
# Sample OpenOCD configuration for the STM32F3DISCOVERY development board

# Depending on the hardware revision you got you'll have to pick ONE of these
# interfaces. At any time only one interface should be commented out.

# Revision C (newer revision)
source [find interface/stlink.cfg]

# Revision A and B (older revisions)
# source [find interface/stlink-v2.cfg]

source [find target/stm32f3x.cfg]

注意:如果您在 验证 部分中发现您有一个旧版本的发现板,那么您应该在此时修改 openocd.cfg 文件以使用 interface/stlink-v2.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-multiarch -q target/thumbv7em-none-eabihf/debug/examples/hello

注意:与以前一样,您可能需要使用其他版本的 gdb 而不是 gdb-multiarch,具体取决于您在安装章节中安装了哪个版本。这也可以是 arm-none-eabi-gdb 或只是 gdb

接下来,将 GDB 连接到 OpenOCD,OpenOCD 正在端口 3333 上等待 TCP 连接。

(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 0x1518 lma 0x8000400
Loading section .rodata, size 0x414 lma 0x8001918
Start address 0x08000400, load size 7468
Transfer rate: 13 KB/sec, 2489 bytes/write.

程序现在已加载。该程序使用半主机,因此在我们进行任何半主机调用之前,我们必须告诉 OpenOCD 启用半主机。您可以使用 monitor 命令向 OpenOCD 发送命令。

(gdb) monitor arm semihosting enable
semihosting is enabled

您可以通过调用 monitor help 命令查看所有 OpenOCD 命令。

与以前一样,我们可以使用断点和 continue 命令跳过所有内容,直到 main

(gdb) break main
Breakpoint 1 at 0x8000490: file examples/hello.rs, line 11.
Note: automatically using hardware breakpoints for read-only addresses.

(gdb) continue
Continuing.

Breakpoint 1, hello::__cortex_m_rt_main_trampoline () at examples/hello.rs:11
11      #[entry]

注意:如果您在发出上面的 continue 命令后,GDB 阻塞了终端而不是命中断点,您可能需要仔细检查 memory.x 文件中的内存区域信息是否正确设置了您的设备(包括开始长度)。

使用 step 进入 main 函数。

(gdb) step
halted: PC: 0x08000496
hello::__cortex_m_rt_main () at examples/hello.rs:13
13          hprintln!("Hello, world!").unwrap();

在使用 next 推进程序后,您应该在 OpenOCD 控制台中看到“Hello, world!”以及其他内容。

$ openocd
(..)
Info : halted: PC: 0x08000502
Hello, world!
Info : halted: PC: 0x080004ac
Info : halted: PC: 0x080004ae
Info : halted: PC: 0x080004b0
Info : halted: PC: 0x080004b4
Info : halted: PC: 0x080004b8
Info : halted: PC: 0x080004bc

该消息仅显示一次,因为程序即将进入第 19 行中定义的无限循环:loop {}

您现在可以使用 quit 命令退出 GDB。

(gdb) quit
A debugging session is active.

        Inferior 1 [Remote target] will be detached.

Quit anyway? (y or n)

调试现在需要更多步骤,因此我们将所有这些步骤打包到一个名为 openocd.gdb 的单个 GDB 脚本中。该文件是在 cargo generate 步骤中创建的,应该无需任何修改即可使用。让我们看一看

cat openocd.gdb
target extended-remote :3333

# print demangled symbols
set print asm-demangle on

# detect unhandled exceptions, hard faults and panics
break DefaultHandler
break HardFault
break rust_begin_unwind

monitor arm semihosting enable

load

# start the process but immediately halt the processor
stepi

现在运行 <gdb> -x openocd.gdb target/thumbv7em-none-eabihf/debug/examples/hello 将立即将 GDB 连接到 OpenOCD,启用半主机,加载程序并启动进程。

或者,您可以将 <gdb> -x openocd.gdb 变成一个自定义运行器,以使 cargo run 构建程序启动 GDB 会话。此运行器包含在 .cargo/config.toml 中,但已注释掉。

head -n10 .cargo/config.toml
[target.thumbv7m-none-eabi]
# uncomment this to make `cargo run` execute programs on 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"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
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)