硬件
现在你应该熟悉了工具与开发过程. 在本节我们来试试真正的硬件. 该过程基本不变.让我们开始:
了解你的硬件
在我们开始之前你需要了解硬件的特点以便于配置项目:
- ARM内核. e.g. Cortex-M3.
- ARM内核有FPU吗? Cortex-M4F和Cortex-M7F有.
- 目标设备有多大闪存和RAM? e.g. 256KiB闪存32KiB内存.
- 闪存和RAM在的地址在多少? e.g. RAM通常位于
0x2000_0000
.
你可以在用户手册和数据手册中找到这些信息.
在本届我们使用我们的参考硬件STM32F3DISCOVERY. 这块板子有一个STM32F303VCT6.这块MCU有:
- 一个带有单精度FPU的Cortex-M4F内核
- 位于0x0800_0000的256KiB闪存
- 位于0x2000_0000的40KiB内存(还有另一个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
# 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内核.
第二步是把内存区域信息输入到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
}
NOTE如果你因为某些原因修改了
memory.x
,并且之前做过了编译, 那你需要执行cargo clean
再执行cargo build
,因为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
这个库包含了一切能让你芯片运行的魔法,它很有帮助,因为几乎所有的Cortex-M CPU都可以用相同的方式引导.
$ cargo build --example hello
调试
调试过程看起来有些不同了.事实上,第一步根据目标设备不同也有不同.这一节中我们会展示在STM32DISCOBVERY上debug的步骤.这仅供参考,有关设备的调试请参考the Debugonomicon.
和以前一样,我们进行远程调试,客户端是GDB.但是这次服务端则是OpenOCD.
像之前在验证安装中所做的一样,将板子连接到电脑,然后检查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]
NOTE 如果你在用旧版本的DISCOVERY板子,你应该修改一下
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> -q target/thumbv7em-none-eabihf/debug/examples/hello
先一步连接GDB到OpenOCD.
(gdb) target remote :3333
Remote debugging using :3333
0x00000000 in ?? ()
现在使用load
命令烧录程序到mcu.
(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.
现在程序被加载了.之前程序使用semihosting,因此在我们进行任何semihosting操作时,应该先告诉OpenOCD启用semihosting.可以使用monitor
命令.
(gdb) monitor arm semihosting enable
semihosting is enabled
你也可以使用
monitor help
查看所用OpenOCD命令.
前之前那样给main
加断点并执行continue
(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();
NOTE 如果在发出上面的
continue
命令后GDB阻塞了终端而不是到达断点,则你可能要仔细检查一下是否已为您的设备正确设置了memory.x
文件中的存储区域信息(起始位置和长度).
使用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
但是,mcu上的进程不没有终止,你可以使用continue
或类似的命令恢复进程.
你现在可以用quit
来退出GDB
(gdb) quit
现在调试需要更多的步骤了,那让我们来把这些步骤打包成一个叫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,启动semihosting,然后烧录程序并启动.
$ head -n10 .cargo/config
[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)