Hardware

By now you should be somewhat familiar with the tooling and the developmentprocess. In this section we'll switch to real hardware; the process will remainlargely the same. Let's dive in.

Know your hardware

Before we begin you need to identify some characteristics of the target deviceas these will be used to configure the project:

  • The ARM core. e.g. Cortex-M3.

  • Does the ARM core include an FPU? Cortex-M4F and Cortex-M7F cores do.

  • How much Flash memory and RAM does the target device have? e.g. 256 KiB ofFlash and 32 KiB of RAM.

  • Where are Flash memory and RAM mapped in the address space? e.g. RAM iscommonly located at address 0x2000_0000.

You can find this information in the data sheet or the reference manual of yourdevice.

In this section we'll be using our reference hardware, the STM32F3DISCOVERY.This board contains an STM32F303VCT6 microcontroller. This microcontroller has:

  • A Cortex-M4F core that includes a single precision FPU

  • 256 KiB of Flash located at address 0x0800_0000.

  • 40 KiB of RAM located at address 0x2000_0000. (There's another RAM region butfor simplicity we'll ignore it).

Configuring

We'll start from scratch with a fresh template instance. Refer to theprevious section on QEMU for a refresher on how to do this withoutcargo-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

Step number one is to set a default compilation target in .cargo/config.

  1. $ tail -n5 .cargo/config
  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)

We'll use thumbv7em-none-eabihf as that covers the Cortex-M4F core.

The second step is to enter the memory region information into the memory.xfile.

  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. }

Make sure the debug::exit() call is commented out or removed, it is usedonly for running in QEMU.

  1. #[entry]
  2. fn main() -> ! {
  3. hprintln!("Hello, world!").unwrap();
  4. // exit QEMU
  5. // NOTE do not run this on hardware; it can corrupt OpenOCD state
  6. // debug::exit(debug::EXIT_SUCCESS);
  7. loop {}
  8. }

You can now cross compile programs using cargo buildand inspect the binaries using cargo-binutils as you did before. Thecortex-m-rt crate handles all the magic required to get your chip running,as helpfully, pretty much all Cortex-M CPUs boot in the same fashion.

  1. $ cargo build --example hello

Debugging

Debugging will look a bit different. In fact, the first steps can look differentdepending on the target device. In this section we'll show the steps required todebug a program running on the STM32F3DISCOVERY. This is meant to serve as areference; for device specific information about debugging check out theDebugonomicon.

As before we'll do remote debugging and the client will be a GDB process. Thistime, however, the server will be OpenOCD.

As done during the verify section connect the discovery board to your laptop /PC and check that the ST-LINK header is populated.

On a terminal run openocd to connect to the ST-LINK on the discovery board.Run this command from the root of the template; openocd will pick up theopenocd.cfg file which indicates which interface file and target file to use.

  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-v2-1.cfg]
  6. # Revision A and B (older revisions)
  7. # source [find interface/stlink-v2.cfg]
  8. source [find target/stm32f3x.cfg]

NOTE If you found out that you have an older revision of the discoveryboard during the verify section then you should modify the openocd.cfgfile at this point to use interface/stlink-v2.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

On another terminal run GDB, also from the root of the template.

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

Next connect GDB to OpenOCD, which is waiting for a TCP connection on port 3333.

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

Now proceed to flash (load) the program onto the microcontroller using theload command.

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

The program is now loaded. This program uses semihosting so before we do anysemihosting call we have to tell OpenOCD to enable semihosting. You can sendcommands to OpenOCD using the monitor command.

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

You can see all the OpenOCD commands by invoking the monitor help command.

Like before we can skip all the way to main using a breakpoint and thecontinue command.

  1. (gdb) break main
  2. Breakpoint 1 at 0x8000d18: file examples/hello.rs, line 15.
  3. (gdb) continue
  4. Continuing.
  5. Note: automatically using hardware breakpoints for read-only addresses.
  6. Breakpoint 1, main () at examples/hello.rs:15
  7. 15 let mut stdout = hio::hstdout().unwrap();

Advancing the program with next should produce the same results as before.

  1. (gdb) next
  2. 16 writeln!(stdout, "Hello, world!").unwrap();
  3. (gdb) next
  4. 19 debug::exit(debug::EXIT_SUCCESS);

At this point you should see "Hello, world!" printed on the OpenOCD console,among other stuff.

  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

Issuing another next will make the processor execute debug::exit. This actsas a breakpoint and halts the process:

  1. (gdb) next
  2. Program received signal SIGTRAP, Trace/breakpoint trap.
  3. 0x0800141a in __syscall ()

It also causes this to be printed to the OpenOCD console:

  1. $ openocd
  2. (..)
  3. Info : halted: PC: 0x08001188
  4. semihosting: *** application exited ***
  5. Warn : target not halted
  6. Warn : target not halted
  7. target halted due to breakpoint, current mode: Thread
  8. xPSR: 0x21000000 pc: 0x08000d76 msp: 0x20009fc0, semihosting

However, the process running on the microcontroller has not terminated and youcan resume it using continue or a similar command.

You can now exit GDB using the quit command.

  1. (gdb) quit

Debugging now requires a few more steps so we have packed all those steps into asingle GDB script named openocd.gdb.

  1. $ cat openocd.gdb
  1. target 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

Now running <gdb> -x openocd.gdb $program will immediately connect GDB toOpenOCD, enable semihosting, load the program and start the process.

Alternatively, you can turn <gdb> -x openocd.gdb into a custom runner to makecargo run build a program and start a GDB session. This runner is includedin .cargo/config but it's commented out.

  1. $ head -n10 .cargo/config
  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)