521 lines
15 KiB
Markdown
521 lines
15 KiB
Markdown
# `cortex-m-quickstart`
|
|
|
|
> A template for building applications for ARM Cortex-M microcontrollers
|
|
|
|
This project is developed and maintained by the [Cortex-M team][team].
|
|
|
|
# [Documentation](https://rust-embedded.github.io/cortex-m-quickstart/cortex_m_quickstart)
|
|
|
|
## Dependencies
|
|
|
|
To build embedded programs using this template you'll need:
|
|
|
|
- Nightly Rust toolchain from 2018-08-28 or newer: `rustup default nightly`
|
|
- The `cargo generate` subcommand: `cargo install cargo-generate`
|
|
- `rust-std` components (pre-compiled `core` crate) for the ARM Cortex-M
|
|
targets. Run:
|
|
|
|
``` console
|
|
$ rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf
|
|
```
|
|
|
|
## Non-dependencies
|
|
|
|
This section list programs that are *not* required to build embedded programs
|
|
but are useful or necessary for embedded development.
|
|
|
|
To flash (put the firmware on the target device) and debug embedded programs
|
|
you'll need these additional programs:
|
|
|
|
<!-- TODO These bullets should link to the debugonomicon, which has instructions -->
|
|
<!-- on how to install these tools -->
|
|
|
|
- GDB with ARM support or LLDB, for debugging.
|
|
- QEMU with ARM support, for running embedded programs on the build machine.
|
|
- OpenOCD and similar, which make debugging possible at all.
|
|
|
|
To inspect the produced binaries you'll want [`cargo-binutils`].
|
|
|
|
[`cargo-binutils`]: https://crates.io/crates/cargo-binutils
|
|
|
|
## Usage
|
|
|
|
### First timers
|
|
|
|
#### Building
|
|
|
|
If you are already familiar with the process of building and debugging embedded
|
|
Rust programs feel free to skip this section!
|
|
|
|
To get you familiar with building and debugging embedded Rust programs we'll use
|
|
the template with its default values to build a program for the LM3S6965, a
|
|
microcontroller with a Cortex-M3 core that QEMU can emulate.
|
|
|
|
In this section we'll use a debugger (GDB or LLDB) and QEMU. Be sure to have
|
|
them installed!
|
|
|
|
1. Initialize the template
|
|
|
|
``` console
|
|
$ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
|
|
```
|
|
|
|
2. Build the example "Hello, world!" program
|
|
|
|
``` rust
|
|
// example/hello.rs
|
|
#![no_main]
|
|
#![no_std]
|
|
|
|
#[macro_use]
|
|
extern crate cortex_m_rt;
|
|
extern crate cortex_m_semihosting as sh;
|
|
extern crate panic_semihosting;
|
|
|
|
use core::fmt::Write;
|
|
|
|
use sh::hio;
|
|
|
|
entry!(main);
|
|
|
|
fn main() -> ! {
|
|
let mut stdout = hio::hstdout().unwrap();
|
|
writeln!(stdout, "Hello, world!").unwrap();
|
|
|
|
loop {}
|
|
}
|
|
```
|
|
|
|
We'll cross compile the program for the `thumbv7m-none-eabi` target. This target
|
|
covers Cortex-M3 devices like the one we are targeting.
|
|
|
|
``` console
|
|
$ cargo build --target thumbv7m-none-eabi --example hello
|
|
```
|
|
You'll find the output binary in the following path:
|
|
`target/thumbv7m-none-eabi/debug/examples/hello`. The output is an ELF file.
|
|
|
|
If you have `file` installed you can print the file type of the output to
|
|
confirm it's an ELF file:
|
|
|
|
```
|
|
$ ( cd target/thumbv7m-none-eabi/debug/examples && file hello )
|
|
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
|
|
```
|
|
|
|
If you have `cargo-binutils` installed you can look at the ELF header of the
|
|
output:
|
|
|
|
> NOTE `cargo-readelf` will be available in a *future* release of binutils
|
|
> (v0.1.3)
|
|
|
|
``` console
|
|
$ cargo readelf --example hello -- --file-headers
|
|
```
|
|
|
|
``` text
|
|
ELF Header:
|
|
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
|
|
Class: ELF32
|
|
Data: 2's complement, little endian
|
|
Version: 1 (current)
|
|
OS/ABI: UNIX - System V
|
|
ABI Version: 0x0
|
|
Type: EXEC (Executable file)
|
|
Machine: ARM
|
|
Version: 0x1
|
|
Entry point address: 0x23A3
|
|
Start of program headers: 52 (bytes into file)
|
|
Start of section headers: 673340 (bytes into file)
|
|
Flags: 0x5000200
|
|
Size of this header: 52 (bytes)
|
|
Size of program headers: 32 (bytes)
|
|
Number of program headers: 2
|
|
Size of section headers: 40 (bytes)
|
|
Number of section headers: 21
|
|
Section header string table index: 19
|
|
```
|
|
|
|
If you look at the bottom of the Cargo configuration file (`.cargo/config`)
|
|
you'll see that the `thumbv7m-none-eabi` target has been set as the default
|
|
compilation target. This means that you do *not* need to pass the `--target`
|
|
flag to cross compile because it's already implied. So:
|
|
|
|
``` console
|
|
$ cargo build --example hello
|
|
```
|
|
|
|
Does the same as before.
|
|
|
|
3. There's no step 3! Your build is done, but for completeness let's look at how
|
|
to run this program on QEMU.
|
|
|
|
#### Running the program on QEMU
|
|
|
|
Execute this command and you are done.
|
|
|
|
``` console
|
|
$ qemu-system-arm \
|
|
-cpu cortex-m3 \
|
|
-machine lm3s6965evb \
|
|
-nographic \
|
|
-semihosting-config enable=on,target=native \
|
|
-kernel target/thumbv7m-none-eabi/debug/examples/hello
|
|
Hello, world!
|
|
```
|
|
|
|
The above command will block the terminal because the `hello` program never
|
|
ends! To terminate QEMU input this in your terminal: `Ctrl+A`, followed by `X`.
|
|
|
|
Let me break down that long command for you:
|
|
|
|
- `qemu-system-arm`. This is the QEMU emulator. There are a few variants of
|
|
these QEMU binaries; this one does full *system* emulation of *ARM* machines
|
|
-- hence the name.
|
|
|
|
- `-cpu cortex-m3`. This tells QEMU to emulate a Cortex-M3 CPU. Specifying the
|
|
CPU model lets us catch some miscompilation errors: for example, running a
|
|
program compiled for the Cortex-M4F, which has a hardware FPU, will make QEMU
|
|
error during its execution.
|
|
|
|
- `-machine lm3s6965evb`. This tells QEMU to emulate the LM3S6965EVB, a
|
|
evaluation board that contains a LM3S6965 microcontroller.
|
|
|
|
- `-nographic`. This tells QEMU to not launch its GUI.
|
|
|
|
- `-semihosting-config (..)`. This tells QEMU to enable semihosting. Semihosting
|
|
lets the emulated device, among other things, use the host stdout, stderr and
|
|
stdin and create files on the host.
|
|
|
|
- `-kernel $file`. This tells QEMU which binary to flash (kind of) and run on
|
|
the emulated machine.
|
|
|
|
#### Debugging
|
|
|
|
First, we launch a QEMU instance:
|
|
|
|
``` console
|
|
$ qemu-system-arm \
|
|
-cpu cortex-m3 \
|
|
-machine lm3s6965evb \
|
|
-nographic \
|
|
-semihosting-config enable=on,target=native \
|
|
-gdb tcp::3333 \
|
|
-S \
|
|
-kernel target/thumbv7m-none-eabi/debug/examples/hello
|
|
```
|
|
|
|
Note that this command won't print anything to the console but will block the
|
|
terminal. We have passed two extra flags this time:
|
|
|
|
- `-gdb tcp::3333`. This tells QEMU to wait for a GDB connection on TCP
|
|
port 3333.
|
|
|
|
- `-S`. This tells QEMU to freeze the machine at startup. Without this the
|
|
program would have reached the end of `main` before we had a chance to launch
|
|
the debugger!
|
|
|
|
Next, we start up LLDB in another terminal:
|
|
|
|
``` console
|
|
$ lldb target/thumbv7m-none-eabi/debug/examples/hello
|
|
```
|
|
|
|
And tell it to connect to the GDB server that QEMU created:
|
|
|
|
``` console
|
|
(lldb) gdb-remote 3333
|
|
Process 1 stopped
|
|
* thread #1, stop reason = signal SIGTRAP
|
|
frame #0: 0x000023a2 hello`Reset at lib.rs:475
|
|
472
|
|
473 #[doc(hidden)]
|
|
474 #[no_mangle]
|
|
-> 475 pub unsafe extern "C" fn Reset() -> ! {
|
|
476 extern "C" {
|
|
477 // This symbol will be provided by the user via the `entry!` macro
|
|
478 fn main() -> !;
|
|
```
|
|
|
|
You'll see that the process is halted and that the program counter is pointing
|
|
to a function named `Reset`. That is the reset handler: what Cortex-M cores
|
|
execute upon booting.
|
|
|
|
This reset handler will eventually call our `main` function. Let's skip all the
|
|
way there using a breakpoint and the `continue` command:
|
|
|
|
``` console
|
|
(lldb) breakpoint set -name hello::main
|
|
Breakpoint 1: where = hello`hello::main::h9cf19a1378cbd1b8 + 2 at hello.rs:20, address = 0x000006a8
|
|
|
|
(lldb) continue
|
|
Process 1 resuming
|
|
Process 1 stopped
|
|
* thread #1, stop reason = breakpoint 1.1
|
|
frame #0: 0x000006a8 hello`hello::main::h9cf19a1378cbd1b8 at hello.rs:20
|
|
17 entry!(main);
|
|
18
|
|
19 fn main() -> ! {
|
|
-> 20 let mut stdout = hio::hstdout().unwrap();
|
|
21 writeln!(stdout, "Hello, world!").unwrap();
|
|
22
|
|
23 loop {}
|
|
```
|
|
|
|
We are now close to the code that prints "Hello, world!". Let's move the program
|
|
forward using the `next` command.
|
|
|
|
``` console
|
|
(lldb) next
|
|
Process 1 stopped
|
|
* thread #1, stop reason = step over
|
|
frame #0: 0x000006be hello`hello::main::h9cf19a1378cbd1b8 at hello.rs:21
|
|
18
|
|
19 fn main() -> ! {
|
|
20 let mut stdout = hio::hstdout().unwrap();
|
|
-> 21 writeln!(stdout, "Hello, world!").unwrap();
|
|
22
|
|
23 loop {}
|
|
24 }
|
|
|
|
(lldb) next
|
|
Process 1 stopped
|
|
* thread #1, stop reason = step over
|
|
frame #0: 0x000006f6 hello`hello::main::h9cf19a1378cbd1b8 at hello.rs:23
|
|
20 let mut stdout = hio::hstdout().unwrap();
|
|
21 writeln!(stdout, "Hello, world!").unwrap();
|
|
22
|
|
-> 23 loop {}
|
|
24 }
|
|
```
|
|
|
|
At this point you should see "Hello, world!" printed on the terminal that's
|
|
running `qemu-system-arm`.
|
|
|
|
``` console
|
|
$ qemu-system-arm (..)
|
|
Hello, world!
|
|
```
|
|
|
|
That's it! You can now exit `lldb`, which will also cause QEMU to terminate.
|
|
|
|
```
|
|
(lldb) exit
|
|
Quitting LLDB will kill one or more processes. Do you really want to proceed: [Y/n] y
|
|
```
|
|
|
|
### Not first timers
|
|
|
|
#### Building
|
|
|
|
This section explains how to configure this template to build programs for some
|
|
specific Cortex-M device.
|
|
|
|
0. Before everything else, you need to know the characteristics of the target
|
|
device:
|
|
|
|
- What's the ARM core? e.g. Cortex-M3.
|
|
|
|
- Does the ARM core include an FPU? e.g. the Cortex-M4F does.
|
|
|
|
- How much Flash memory and RAM does the target device has? e.g. 40 KB of RAM
|
|
|
|
- Where is Flash memory and RAM mapped in the address space? e.g. `0x2000_0000`
|
|
is common for RAM.
|
|
|
|
You should be able to find this information in the data sheet and / or reference
|
|
manual of your device.
|
|
|
|
As an example, we'll use the [STM32F303VCT6] microcontroller. This device has:
|
|
|
|
[STM32F303VCT6]: https://www.st.com/en/microcontrollers/stm32f303vc.html
|
|
|
|
- a Cortex-M4F core that includes a single precision FPU
|
|
|
|
- 256 KB of Flash located at address `0x0800_0000`.
|
|
|
|
- 40 KB of RAM located at address `0x2000_0000`. (There's another RAM region but
|
|
for simplicity we'll ignore it).
|
|
|
|
1. Initialize the template
|
|
|
|
``` console
|
|
$ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
|
|
```
|
|
|
|
2. Set the default compilation target in `.cargo/config`.
|
|
|
|
For the STM32F303VCT6, we pick the `thumbv7em-none-eabihf` target as that covers
|
|
the Cortex-M4F core.
|
|
|
|
``` console
|
|
$ tail .cargo/config
|
|
```
|
|
|
|
``` toml
|
|
[build]
|
|
# 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)
|
|
```
|
|
|
|
3. Enter the memory region information into `memory.x`.
|
|
|
|
As we mentioned before the STM32F303VCT6 has 40 KB of RAM located at address
|
|
`0x2000_0000` and 256 KB of Flash memory located at address `0x0800_0000`.
|
|
|
|
``` console
|
|
$ cat memory.x
|
|
```
|
|
|
|
``` text
|
|
MEMORY
|
|
{
|
|
/* NOTE K = KiBi = 1024 bytes */
|
|
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
|
|
RAM : ORIGIN = 0x20000000, LENGTH = 40K
|
|
}
|
|
```
|
|
|
|
4. Build one of the examples:
|
|
|
|
``` console
|
|
$ cargo build --example hello
|
|
```
|
|
|
|
You are done! You should be able to flash and run this example on your
|
|
device.
|
|
|
|
#### Debugging
|
|
|
|
Teaching you how to flash and debug this program on *your* device is out of
|
|
scope for this document due to the sheer variety of possible hardware / software
|
|
combinations. But the steps required to flash and debug this program on the
|
|
[STM32F3DISCOVERY] using OpenOCD are provided below as a reference.
|
|
|
|
[STM32F3DISCOVERY]: https://www.st.com/en/evaluation-tools/stm32f3discovery.html
|
|
|
|
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 the
|
|
`openocd.cfg` file which indicates which interface file and target file to use.
|
|
|
|
``` console
|
|
$ cat openocd.cfg
|
|
```
|
|
|
|
``` text
|
|
source [find interface/stlink-v2-1.cfg]
|
|
source [find target/stm32f3x.cfg]
|
|
```
|
|
|
|
``` console
|
|
$ 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
|
|
```
|
|
|
|
On another terminal run GDB, also from the root of the template.
|
|
|
|
``` console
|
|
$ cat openocd.gdb
|
|
```
|
|
|
|
``` text
|
|
target remote :3333
|
|
|
|
# print demangled symbols
|
|
set print asm-demangle on
|
|
|
|
# detect unhandled exceptions and hard faults
|
|
break DefaultHandler
|
|
break UserHardFault
|
|
|
|
monitor arm semihosting enable
|
|
|
|
load
|
|
```
|
|
|
|
``` console
|
|
$ arm-none-eabi-gdb -x openocd.gdb target/thumbv7em-none-eabihf/debug/examples/hello
|
|
(..)
|
|
Loading section .vector_table, size 0x400 lma 0x8000000
|
|
Loading section .text, size 0x21dc lma 0x8000400
|
|
Loading section .rodata, size 0x6a4 lma 0x80025e0
|
|
Start address 0x800238c, load size 11392
|
|
Transfer rate: 17 KB/sec, 3797 bytes/write.
|
|
|
|
(gdb) list
|
|
470 #[no_mangle]
|
|
471 pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;
|
|
472
|
|
473 #[doc(hidden)]
|
|
474 #[no_mangle]
|
|
475 pub unsafe extern "C" fn Reset() -> ! {
|
|
476 extern "C" {
|
|
477 // This symbol will be provided by the user via the `entry!` macro
|
|
478 fn main() -> !;
|
|
479
|
|
```
|
|
|
|
The `openocd.gdb` script will connect GDB to OpenOCD and then flash the program
|
|
into the device. After that you can do a normal debugging session.
|
|
|
|
If you `continue` the program past the semihosting write operation you'll see
|
|
"Hello, world" printed on the OpenOCD console.
|
|
|
|
``` console
|
|
(gdb) continue
|
|
```
|
|
|
|
``` console
|
|
$ openocd
|
|
(..)
|
|
Hello, world!
|
|
```
|
|
|
|
## Next steps
|
|
|
|
> TODO point the reader to embedded-hal, awesome-embedded-rust, etc.
|
|
|
|
# License
|
|
|
|
Licensed under either of
|
|
|
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
|
|
at your option.
|
|
|
|
## Contribution
|
|
|
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
|
dual licensed as above, without any additional terms or conditions.
|
|
|
|
## Code of Conduct
|
|
|
|
Contribution to this crate is organized under the terms of the [Rust Code of
|
|
Conduct][CoC], the maintainer of this crate, the [Cortex-M team][team], promises
|
|
to intervene to uphold that code of conduct.
|
|
|
|
[CoC]: CODE_OF_CONDUCT.md
|
|
[team]: https://github.com/rust-embedded/wg#the-cortex-m-team
|