硬件

现在你应该有点熟悉工具和开发过程了。在这部分我们将切换到真正的硬件上;步骤非常相似。让我们深入进去。

认识你的硬件

在我们开始之前,你需要了解下你的目标设备的一些特性,因为你将用它们来配置项目:

  • ARM 内核。比如 Cortex-M3 。
  • ARM 内核包括一个FPU吗?Cortex-M4F和Cortex-M7F有。
  • 目标设备有多少Flash和RAM?比如 256KiB的Flash和32KiB的RAM。
  • Flash和RAM映射在地址空间的什么位置?比如 RAM通常位于 0x2000_0000 地址处。

你可以在你的设备的数据手册和参考手册上找到这些信息。

这部分,我们会用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有:

  • 一个Cortex-M4F核心,它包含一个单精度FPU。
  • 位于 0x0800_0000 地址的256KiB的Flash。
  • 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了方便起见,我们将忽略它)。

配置

我们将从一个新的模板实例开始。参考先前的QEMU章节,了解如何在没有cargo-generate的情况下完成它。

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

第一步是在.cargo/config.toml中设置一个默认编译目标。

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

我们将使用thumbv7em-none-eabihf,因为它涉及到Cortex-M4F核。

第二步是将存储区域信息(memory region information)输入memory.x

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

注意:如果你因为一些理由,在对某个编译目标首次编译后,改变了memory.x文件,需要在cargo build之前执行cargo clean。因为cargo build可能不会追踪memory.x的更新。

我们将使用hello示例再次开始,但是首先我们必须做一个小改变。

examples/hello.rs中,确保debug::exit()调用被注释掉了或者移除。它只能用于在QEMU中运行的情况。

  1. #[entry]
  2. fn main() -> ! {
  3. hprintln!("Hello, world!").unwrap();
  4. // 退出 QEMU
  5. // 注意 不要在硬件上运行这个;它会打破OpenOCD的状态
  6. // debug::exit(debug::EXIT_SUCCESS);
  7. loop {}
  8. }

你可以像你之前做的一样,使用cargo build检查编译程序,使用cargo-binutils观察二进制项。cortex-m-rt库可以处理所有运行芯片所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动,这同样有用。

  1. cargo build --example hello

调试

调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试有关的信息,可以看the Debugonomicon

像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。

像是在安装验证中做的那样,把你的笔记本/个人电脑和discovery开发板连接起来,检查ST-LINK的短路帽是否被安装了。

在一个终端上运行 openocd 连接到你的开发板上的 ST-LINK 。从模板的根目录运行这个命令;openocd 将会选择 openocd.cfg 文件,它指出了所使用的接口文件(interface file)和目标文件(target file)。

  1. cat openocd.cfg
  1. # Sample OpenOCD configuration for the STM32F3DISCOVERY development board
  2. # Depending on the hardware revision you got you'll have to pick ONE of these
  3. # interfaces. At any time only one interface should be commented out.
  4. # Revision C (newer revision)
  5. source [find interface/stlink.cfg]
  6. # Revision A and B (older revisions)
  7. # source [find interface/stlink-v2.cfg]
  8. source [find target/stm32f3x.cfg]

注意 如果你在安装验证章节中,发现你的discovery开发板是一个更旧的版本,那么你应该修改你的 openocd.cfg 文件,让它去使用 interface/stlink-v2.cfg 。注释掉 interface/stlink.cfg

  1. $ openocd
  2. Open On-Chip Debugger 0.10.0
  3. Licensed under GNU GPL v2
  4. For bug reports, read
  5. http://openocd.org/doc/doxygen/bugs.html
  6. Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
  7. adapter speed: 1000 kHz
  8. adapter_nsrst_delay: 100
  9. Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
  10. none separate
  11. Info : Unable to match requested speed 1000 kHz, using 950 kHz
  12. Info : Unable to match requested speed 1000 kHz, using 950 kHz
  13. Info : clock speed 950 kHz
  14. Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B
  15. Info : using stlink api v2
  16. Info : Target voltage: 2.913879
  17. Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints

在另一个终端,也是从模板的根目录,运行GDB。

  1. gdb-multiarch -q target/thumbv7em-none-eabihf/debug/examples/hello

注意: 像之前一样,你可能需要另一个版本的gdb而不是gdb-multiarch,取决于你在之前的章节安装了什么工具。这也可能使用的是arm-none-eabi-gdb或者只是gdb

接下来把GDB连接到OpenOCD,它正在等待一个在端口3333上的TCP链接。

  1. (gdb) target remote :3333
  2. Remote debugging using :3333
  3. 0x00000000 in ?? ()

接下来使用load命令,继续 flash(加载) 程序到微控制器上。

  1. (gdb) load
  2. Loading section .vector_table, size 0x400 lma 0x8000000
  3. Loading section .text, size 0x1518 lma 0x8000400
  4. Loading section .rodata, size 0x414 lma 0x8001918
  5. Start address 0x08000400, load size 7468
  6. Transfer rate: 13 KB/sec, 2489 bytes/write.

程序现在被加载了。这个程序使用半主机模式,因此在我们调用半主机模式之前,我们必须告诉OpenOCD使能半主机。你可以使用 monitor 命令,发送命令给OpenOCD 。

  1. (gdb) monitor arm semihosting enable
  2. semihosting is enabled

通过调用 monitor help 命令,你能看到所有的OpenOCD命令。

像我们之前一样,使用一个断点和 continue 命令我们可以跳过所有的步骤到 main

  1. (gdb) break main
  2. Breakpoint 1 at 0x8000490: file examples/hello.rs, line 11.
  3. Note: automatically using hardware breakpoints for read-only addresses.
  4. (gdb) continue
  5. Continuing.
  6. Breakpoint 1, hello::__cortex_m_rt_main_trampoline () at examples/hello.rs:11
  7. 11 #[entry]

注意 如果在你使用了上面的continue命令后,GDB阻塞住了终端而不是停在了断点处,你可能需要检查下memory.x文件中的存储分区的信息,对于你的设备来说是否被正确的设置了起始位置大小 。

使用step步进main函数里。

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

在使用了next让函数继续执行之后,你应该看到 “Hello, world!” 被打印到了OpenOCD控制台上。

  1. $ openocd
  2. (..)
  3. Info : halted: PC: 0x08000e6c
  4. Hello, world!
  5. Info : halted: PC: 0x08000d62
  6. Info : halted: PC: 0x08000d64
  7. Info : halted: PC: 0x08000d66
  8. Info : halted: PC: 0x08000d6a
  9. Info : halted: PC: 0x08000a0c
  10. Info : halted: PC: 0x08000d70
  11. Info : halted: PC: 0x08000d72

消息只打印一次,然后进入定义在19行的无限循环中: loop {}

使用 quit 命令,你现在可以退出 GDB 了。

  1. (gdb) quit
  2. A debugging session is active.
  3. Inferior 1 [Remote target] will be detached.
  4. Quit anyway? (y or n)

现在调试比之前多了点步骤,因此我们已经把所有步骤打包进一个名为 openocd.gdb 的GDB脚本中。这个文件在 cargo generate 步骤中被生成,因此不需要任何修改了。让我们看一下:

  1. cat openocd.gdb
  1. target extended-remote :3333
  2. # print demangled symbols
  3. set print asm-demangle on
  4. # detect unhandled exceptions, hard faults and panics
  5. break DefaultHandler
  6. break HardFault
  7. break rust_begin_unwind
  8. monitor arm semihosting enable
  9. load
  10. # start the process but immediately halt the processor
  11. stepi

现在运行 <gdb> -x openocd.gdb target/thumbv7em-none-eabihf/debug/examples/hello 将会立即把GDB和OpenOCD连接起来,使能半主机,加载程序和启动进程。

另外,你能将 <gdb> -x openocd.gdb 放进一个自定义的 runner 中,使 cargo run 能编译程序并启动一个GDB会话。这个 runner 在 .cargo/config.toml 中,但是它被注释掉了。

  1. head -n10 .cargo/config.toml
  1. [target.thumbv7m-none-eabi]
  2. # uncomment this to make `cargo run` execute programs on QEMU
  3. # runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
  4. [target.'cfg(all(target_arch = "arm", target_os = "none"))']
  5. # uncomment ONE of these three option to make `cargo run` start a GDB session
  6. # which option to pick depends on your system
  7. runner = "arm-none-eabi-gdb -x openocd.gdb"
  8. # runner = "gdb-multiarch -x openocd.gdb"
  9. # runner = "gdb -x openocd.gdb"
  1. $ cargo run --example hello
  2. (..)
  3. Loading section .vector_table, size 0x400 lma 0x8000000
  4. Loading section .text, size 0x1e70 lma 0x8000400
  5. Loading section .rodata, size 0x61c lma 0x8002270
  6. Start address 0x800144e, load size 10380
  7. Transfer rate: 17 KB/sec, 3460 bytes/write.
  8. (gdb)