Skip to content
Snippets Groups Projects
Commit 6dd5de07 authored by Per Lindgren's avatar Per Lindgren
Browse files

more examples

parent fea324da
No related branches found
No related tags found
No related merge requests found
[package]
name = "app"
categories = ["embedded", "no-std"]
authors = ["Per Lindgren <per.lindgren@ltu.se>", "Emil Fresk <emil.fresk@gmail.com>"]
description = "Example project"
keywords = ["arm", "cortex-m", "mav"]
authors = ["Per Lindgren <per.lindgren@ltu.se>"]
description = "Example project (app)"
keywords = ["arm", "cortex-m", "rtfm", "e7020e"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/korken89/trustflight_firmware"
version = "0.1.0"
......@@ -17,11 +17,16 @@ cortex-m-semihosting = "0.3.5"
cortex-m = "0.6.2"
aligned = "0.3.2"
ufmt = "0.1.0"
panic-itm = "0.4.1"
panic-itm = "0.4.1"
nb = "0.1.2"
[dependencies.cortex-m-rt]
version = "0.6.12"
version = "0.6.12"
[dependencies.stm32f4]
version = "0.9.0"
features = ["stm32f401", "rt"]
optional = true
[dependencies.stm32f4xx-hal]
version = "0.6.0"
......@@ -35,8 +40,13 @@ test = false
bench = false
# Built options for different examples
# [[example]]
[[example]]
name = "device"
required-features = ["stm32f4"]
[[example]]
name = "serial"
required-features = ["stm32f4xx-hal"]
[profile.dev]
opt-level = 1
......@@ -45,7 +55,7 @@ debug = true
lto = false
[profile.release]
opt-level = "s" # optimize for size
#opt-level = "s" # optimize for size
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
......@@ -205,75 +205,37 @@ In case the execution of an instruction fails, a `HardFault` exception is raised
### Device Crates and System View Descriptions (SVDs)
Besides the ARM provided *core* peripherals the STM32F401re/STM32F411re MCUs has numerous vendor specific peripherals (GPIOs, Timers, USARTs etc.). The vendor provides a System View Description (SVD) specifying the register block layouts (fields, enumerated values, etc.). Using the `svd2rust` tool we can derive a `Peripheral Access Crate` (PAC) providing an API for the device that allow us to access each register according to the vendors specification. The `device.rs` example showcase how a PAC for the STM32F401re/STM32F411re MCUs can be added. (These MCUs have the same set of peripherals, only the the maximum clock rating differs.) Here we use the STM32F413 as that covers the STM32F401/411/413 devices.
Besides the ARM provided *core* peripherals the STM32F401re/STM32F411re MCUs has numerous vendor specific peripherals (GPIOs, Timers, USARTs etc.). The vendor provides a System View Description (SVD) specifying the register block layouts (fields, enumerated values, etc.). Using the `svd2rust` tool we can derive a `Peripheral Access Crate` (PAC) providing an API for the device that allow us to access each register according to the vendors specification. The `device.rs` example showcase how a PAC for the STM32F401re/STM32F411re MCUs can be added. (These MCUs have the same set of peripherals, only the the maximum clock rating differs.)
The example output a `.` each second over `semihosting` and `ITM`.
Looking at the `Corgo.toml` file we find:
``` toml
[package]
authors = ["Per Lindgren <per.lindgren@ltu.se>"]
edition = "2018"
readme = "README.md"
name = "app"
version = "0.1.0"
[dependencies]
cortex-m-rt = "0.6.7"
cortex-m-semihosting = "0.3.2"
# panic-abort = "0.3.1" # requires nightly toolchain
panic-halt = "0.2.0"
panic-semihosting = "0.5.1"
panic-itm = "0.4.0"
bare-metal = "0.2.4"
nb = "0.1.1"
heapless = "0.4.1"
``` shell
$ cargo run --example device --features stm32f4
```
[dependencies.cortex-m-rtfm]
version = "0.4.0"
optional = true
The example output a `.` each second over `semihosting` and `ITM`.
[dependencies.cortex-m]
version = "0.5.8"
# features = ["inline-asm"] # <- currently requires nightly compiler
#### The `Cargo.toml` file
# Uncomment for the allocator example.
# alloc-cortex-m = "0.3.5"
Looking at the `Cargo.toml` file we find:
``` toml
...
[dependencies.stm32f4]
version = "0.5.0"
features = ["stm32f413", "rt"]
optional = true
[dependencies.stm32f4xx-hal]
git = "https://github.com/stm32-rs/stm32f4xx-hal.git"
version = "0.2.8"
features = ["stm32f413", "rt"]
optional = true
version = "0.9.0"
features = ["stm32f401", "rt"]
optional = true
[features]
pac = ["stm32f4"]
hal = ["stm32f4xx-hal"]
rtfm = ["cortex-m-rtfm"]
rtfm-tq = ["cortex-m-rtfm/timer-queue"]
# this lets you use `cargo fix`!
[[bin]]
name = "app"
test = false
bench = false
...
[profile.release]
incremental = false # disable incremental build to allow lto on nightly
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
# Built options for different examples
[[example]]
name = "device"
required-features = ["stm32f4"]
...
```
We compile `stm32f4` (a generic library for all STMF4 MCUs) with `features = ["stm32f413", "rt"]`, which indicates the specific MCU with `rt` (run-time support) enabled. By having the PAC as an optional dependency, we did not need to compile it (unless we need it, and as you might have experienced already compiling the PAC takes a bit of time to compile initially). (An SVD file is typically > 50k lines, amounting to the same (or more) lines of Rust code.)
We compile `stm32f4` (a generic library for all STMF4 MCUs) with `features = ["stm32f401", "rt"]`, which indicates the specific MCU with `rt` (so we get the interrupt vector etc.). By having the PAC as an optional dependency, we did not need to compile it (unless we need it, and as you might have experienced already compiling the PAC takes a bit of time to compile initially). (An SVD file is typically > 50k lines, amounting to the same (or more) lines of Rust code.)
By compiling with `--features stm32f4` we "opt-in" this dependency.
---
......@@ -288,9 +250,9 @@ Looking closer at the example, `rcc` is a *singleton* (`constrain` consumes the
This pattern ensures that the clock configuration will remain unchanged (the `freeze` function cannot be called again, as the `rcc` is consumed, also you cannot get a new `rcc` as the `RCC` was consumed by `contstrain`).
Why is this important you may ask? Well, this *pattern* allows the compiler to check and ensure that your code (or some library that you use) does not make changes to the system (in this case the clocking), wich reduces the risk of errors and improves robustness.
Why is this important you may ask? Well, this *pattern* allows the compiler to check and ensure that your code (or some library that you use) does not make changes to the system (in this case the clocking), which reduces the risk of errors and improves robustness.
Similarly, we `split` the `GPIOA` into its parts (pins), and select the operating mode to `af7` for `tx` (the transmit pin `pa2`), and `rx` (the receive pin `pa3`). For details see, RM0368, figure 17 (page 151), and table 9 in STM32F401xD STM32F401xE. The GPIO pins `pa2` and `pa3` are (by default) connected to the `stlink` programmer, see section 6.8 of the Nucleo64 user manual `UM1724`. When the `stlink` programmer is connected to a linux host, the device `\dev\ttyACM0` appears as a *virtual com port* (connected to `pa2`/`pa3` by default).
Similarly, we `split` the `GPIOA` into its parts (pins), and select the operating mode to `af7` for `tx` (the transmit pin `pa2`), and `rx` (the receive pin `pa3`). For details see, RM0368, figure 17 (page 151), and table 9 in STM32F401xD STM32F401xE. The GPIO pins `pa2` and `pa3` are (by default) connected to the `stlink` programmer, see section 6.8 of the Nucleo64 user manual `UM1724`. When the `stlink` programmer is connected to a linux host, the device `\dev\ttyACM0` appears as a *virtual com port*.
Now we can call `Serial::usart2` to setup the serial communication, (according to table 9 in STM32F401xD STM32F401xE documentation it is USART2).
......@@ -306,7 +268,7 @@ As the underlying hardware implementation buffers only a single byte, the input
You can now compile and run the example. Start `moserial` (or some other serial communication tool), connect to `/dev/ttyACM0` with 115200 8N1). Now write a single character `a` in the `Outgoing` pane, followed by pressing <Enter>. You should receive back an `a` from the target. In the ITM trace output you should see:
``` console
[2019-01-08T10:31:36.867Z] Ok 97.
Ok 97
```
Depending if <Enter> was encoded as CR+LF, CR, LF, TAB, ... you will get additional bytes sent (and received). Try sending multiple characters at once, e.g. `abcd`, you will see that the you well get a buffer overflow.
......
//! Debugging a crash (exception)
#![no_main]
#![no_std]
extern crate panic_halt;
use core::ptr;
use cortex_m_rt::{entry, exception};
#[entry]
#[inline(never)]
fn main() -> ! {
unsafe {
// read an address outside of the RAM region to cause a HardFault exception
ptr::read_volatile(0x2FFF_FFFF as *const u32);
}
loop {
continue;
}
}
#[exception]
#[inline(never)]
fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
// to inline never and read_volatile required to
// avoid `ef` being optimized out in release mode
unsafe {
ptr::read_volatile(ef);
}
loop {
continue;
}
}
//
// Most crash conditions trigger a hard fault exception, whose handler is defined via
// ``` rust
// #[exception]
// fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
// ...
// ```
//
// `cortex-m-rt` generates a trampoline, that calls into your user defined `
// HardFault` handler. We look at the generated trampoline:
//
// ``` rust
// #[doc(hidden)]
// #[export_name = "HardFault"]
// #[link_section = ".HardFault.user"]
// pub unsafe extern "C" fn __cortex_m_rt_HardFault_trampoline(frame: &::cortex_m_rt::ExceptionFrame) {
// __cortex_m_rt_HardFault(frame)
// }
// ```
//
// The `HardFault` handler has access to the exception `frame`, a
// snapshot of the CPU registers at the moment of the exception.
//
// To better see what is happening we make a `--release` build
// (It reduces the amount of redundant code.)
//
// ``` text
// $ cargo run --example crash --release
// ...
// Breakpoint 1, main () at examples/crash.rs:69
// (gdb) continue
// Breakpoint 3, HardFault (frame=0x20007fe0) at examples/crash.rs:82
// 82 #[exception]
// (gdb) p/x *frame
// $1 = cortex_m_rt::ExceptionFrame {r0: 0x2fffffff, r1: 0xf00000, r2: 0x0, r3: 0x0, r12: 0x0, lr: 0x8000405, pc: 0x800040a, xpsr: 0x61000000}
// (gdb) disassemble frame.pc
// Dump of assembler code for function crash::__cortex_m_rt_main:
// 0x08000406 <+0>: mvn.w r0, #3489660928 ; 0xd0000000
// 0x0800040a <+4>: ldr r0, [r0, #0]
// 0x0800040c <+6>: b.n 0x800040c <crash::__cortex_m_rt_main+6>
// End of assembler dump.
// ```
//
// The program counter (frame.pc) contains the address of the instruction that caused the exception. In GDB one can
// disassemble the program around this address to observe the instruction that caused the
// exception. In our case its the `ldr r0, [r0, #0]` caused the exception. This instruction tried to load (read) a 32-bit word
// from the address stored in the register `r0`. Looking again at the contents of `ExceptionFrame`
// we find that `r0` contained the address `0x2FFF_FFFF` when this instruction was executed.
//
// Looking at the assembly `mvn.w r0, #3489660928 ; 0xd0000000`.
// This is a *move* and *not*, so the resulting value here is actually
// 0x2fffffff. Why did it not do it straight up then as 0x2FFF_FFFF?
//
// Well a 32 bit constant cannot be stored in a 32 bit instruction.
// So under the hood it stores 0xd0, bit shifts it and bit wise inversion.
// This is the level of optimization Rust + LLVM is capable of.
//
// We can further backtrace the calls leading up to the fault.
// ``` text
// ((gdb) bt
// #0 HardFault (frame=0x20007fe0) at examples/crash.rs:79
// #1 <signal handler called>
// #2 core::ptr::read_volatile (src=0x2fffffff)
// at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/ptr/mod.rs:948
// #3 crash::__cortex_m_rt_main () at examples/crash.rs:71
// #4 0x08000404 in main () at examples/crash.rs:66
// ```
// Here we see that on frame #2 we are doing the read causing havoc.
//! Using a device crate
//!
#![no_main]
#![no_std]
#[allow(unused_extern_crates)]
extern crate panic_halt;
use cortex_m::{iprint, peripheral::syst::SystClkSource};
use cortex_m_rt::entry;
use cortex_m_semihosting::hprintln;
// `svd2rust` generated Peripheral Access Crate (PAC).
use stm32f4::stm32f401::{interrupt, Interrupt, ITM, NVIC};
#[entry]
fn main() -> ! {
let p = cortex_m::Peripherals::take().unwrap();
let mut syst = p.SYST;
let mut nvic = p.NVIC;
nvic.enable(Interrupt::EXTI0);
// configure the system timer to wrap around every second
syst.set_clock_source(SystClkSource::Core);
syst.set_reload(16_000_000); // 1s
syst.enable_counter();
loop {
// busy wait until the timer wraps around
while !syst.has_wrapped() {}
// trigger the `EXTI0` interrupt
NVIC::pend(Interrupt::EXTI0);
}
}
// try commenting out this line: you'll end in `default_handler` instead of in `exti0`
#[interrupt]
fn EXTI0() {
hprintln!(".").unwrap();
let itm = unsafe { &mut *ITM::ptr() };
let stim = &mut itm.stim[0];
iprint!(stim, ".");
}
......@@ -27,7 +27,9 @@ fn main() -> ! {
syst.enable_counter();
syst.enable_interrupt();
loop {}
loop {
continue;
}
}
#[exception]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment