diff --git a/.vscode/launch.json b/.vscode/launch.json index a8ae86066661e2e0817ca9cd646eb8f32b80ff79..15356f85dc87ed067790775c1cab246c2de6c16c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,7 @@ "request": "launch", "servertype": "openocd", "name": "itm 64Mhz (debug)", + "preLaunchTask": "cargo build --example itm", "executable": "./target/thumbv7em-none-eabihf/debug/examples/itm", "configFiles": [ "interface/stlink.cfg", @@ -37,6 +38,7 @@ "request": "launch", "servertype": "openocd", "name": "hello 16Mhz (debug)", + "preLaunchTask": "cargo build --example hello", "executable": "./target/thumbv7em-none-eabihf/debug/examples/hello", "configFiles": [ "interface/stlink.cfg", @@ -47,6 +49,22 @@ ], "cwd": "${workspaceRoot}" }, + { + "type": "cortex-debug", + "request": "launch", + "servertype": "openocd", + "name": "crash (release)", + "preLaunchTask": "cargo build --example crash --release", + "executable": "./target/thumbv7em-none-eabihf/release/examples/crash", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32f4x.cfg" + ], + "postLaunchCommands": [ + "b HardFault" + ], + "cwd": "${workspaceRoot}" + }, { "type": "cortex-debug", "request": "launch", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4e31401eb4cc7a17bf3acb4b2582471a349b674a..649f1aacb794503e5e07ca2741cbd87333a72b6e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,6 +26,42 @@ "kind": "build", "isDefault": true } - } + }, + { + "type": "shell", + "label": "cargo build --example hello", + "command": "cargo build --example hello", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "shell", + "label": "cargo build --example crash --release", + "command": "cargo build --example crash --release", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "shell", + "label": "cargo build --example itm", + "command": "cargo build --example itm", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6da4855573eb7cff30e7f704df2014e5020eaf0f..cc13cce1e280c56dc5e0ca4af013628478ac63f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ panic-halt = "0.2.0" # Uncomment for the panic example. panic-semihosting = "0.5.1" panic-itm = "0.4.0" +bare-metal = "0.2.4" # Uncomment for the allocator example. # alloc-cortex-m = "0.3.5" diff --git a/README.md b/README.md index 63adb377beced12d03ec310512352289125cec19..ad2d2e1a7ca519292639736e833d00463b5f4dec 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,15 @@ $ rustup target add thumbv7em-none-eabihf - `itmdump` (for ITM trace output) - `vscode` and `cortex-debug` (optional for an integrated debugging experience) +* https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug + --- ## Examples --- -### Hello +### Hello World! Building and Debugging an Application 1. Connect your devkit using USB. To check that it is found you can run: @@ -110,9 +112,9 @@ You have now compiled and debugged a minimal Rust `hello` example. `gdb` is a ve --- -### ITM +### ITM Tracing -The `hello` example uses the `semihosting` interface to emit the trace information (appearing in the `openocd` terminal). The drawback is that `semihosting` is incredibly slow as it involves a lot of machinery to process each character. (Essentially, it writes a character to a given position in memory, runs a dedicated break instruction, `openocd` detecects the break, reads the character at the given postition in memory and emits the character to the console.) +The `hello.rs` example uses the `semihosting` interface to emit the trace information (appearing in the `openocd` terminal). The drawback is that `semihosting` is incredibly slow as it involves a lot of machinery to process each character. (Essentially, it writes a character to a given position in memory, runs a dedicated break instruction, `openocd` detecects the break, reads the character at the given postition in memory and emits the character to the console.) A better approach is to use de bultin in ITM (Instrumentation Trace Macrocell), designed to more efficently implement tracing. The onboard `stlink` programmer can put up to 4 characters into an ITM package, and transmit that to the host (`openocd`). `openocd` can process the incoming data and send it to a file or FIFO queue. The ITM package stream needs to be decoded (header + data). To this end we use the `itmdump` tool. @@ -123,7 +125,7 @@ $ mkfifo /tmp/itm.log $ itmdump -f /tmp/itm.log -F ``` -Now you can compile and run the `itm` application using the same steps as the `hello` programg. In the `itmdump` console you should now have the trace output. +Now you can compile and run the `itm.rs` application using the same steps as the `hello` program. In the `itmdump` console you should now have the trace output. ``` console $ mkfifo /tmp/itm.log @@ -181,13 +183,22 @@ A third alternative would be to store the panic message in some non-volatile mem --- -### Exception Handling +### Exception Handling and Core Peripheral Access The ARM Cortex-M processors features a set of *core* peripherpherals and *exception* handlers. These offer basic functionality independent of vendor (NXP, STM, ...). The `SysTick` perihperal is a 24-bit countdown timer, that raises a `SysTick` exception when hitting 0 and reloads the set value. Seen as a real-time system, we can dispatch the `SysTick` task in a periodic fashion (without accumulated drift under some additional constraints). -In the example a `.` is emitted by the `SysTick` handler using `semihosting`. Running the example should give you a periodic updated of the `openocd` console. +In the `exception.rs` example a `.` is emitted by the `SysTick` handler using `semihosting`. Running the example should give you a periodic updated of the `openocd` console. + +The `exception_itm.rs` and `exception_itm_raw.rs` uses the ITM instead. The difference is the way +they gain access to the `ITM` periphereal. In the first case we *steal* the whole set of core peripherals (stealing works only in `--release` mode), while the in the second case we use *raw* pointer access to the `ITM`. In both cases, the code is *unsafe*, as there is no guarantee that other tasks may access the peripheral simultaneously (causing a conflict/race). Later we will see how the concurrency problem is solved in RTFM to offer safe access to peripherals. + +--- + +### Crash - Analysing the Exception Frame + +In case the execution of an intstruction fails, a `HardFault` exception is raised by the hardware, and the `HardFault` handler is executed. We can define our own handler as in example `crash.rs`. In `main` we attempt to read an illegal address, causing a `HardFault`, and we hit a breakpoint (`openocd.gdb` script sets a breakbpoint at the `HardFualt` handler). From there you can print the exception frame, reflecting the state of the MCU when the error occured. You can use `gdb` to give a `back trace` of the call-stack leading up to the error. See the example for detailed information. + -As an exercise make it use the ITM instead. --- @@ -349,11 +360,18 @@ This ensures 1) the program will not yet again overflow the ITM buffer, 2) the f # Visual Studio Code -It is possible to run `gdb` from within the `vscode`. `vscode` is highly configurable, (keyboard shortcuts, keymaps, plugins etc.). Using the default setup, and the `cortex-debug` plugin you can: +`vscode` is highly configurable, (keyboard shortcuts, keymaps, plugins etc.) There is Rust support through the `rls-vscode` plugin (https://github.com/rust-lang/rls-vscode). + +It is possible to run `arm-none-eabi-gdb` from within the `vscode` using the `cortex-debug` plugin (https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). + +For general informaiton regarding debugging in `vscode`, see https://code.visualstudio.com/docs/editor/debugging. + +Some useful (default) shortcuts: + +- `CTRL+m` compilation tasks, (e.g., compile all examples `cargo build --examples`). Cargo is smart and just re-compiles what is changed. -- `CTRL+m` compile all examples (`cargo build --examples`). Cargo is smart and just re-compiles what is changed. +- `CTRL+d` debug launch configurations, enter debug mode to choose a binary (e.g., `itm 64MHz (debug)`) -- `CTRL+d` enter debug mode to choose a binary. (`itm 64MHz (debug)`) - `F5` to start. It will open the `cortex_m_rt/src/lib.rs` file, which contains the startup code. From there you can continue `F5` again. - `F6` to break. The program will now be in the infinite loop. - You can view the ITM trace in the `OUTPUT` tab, choose the dropdown `SWO: ITM [port 0, type console]`. It should now display: diff --git a/examples/exception_itm.rs b/examples/exception_itm.rs index 90799e78d483506077120035e88469d7fa4b2394..01144a86e562a0346d0c786d23fe2d0ef0bbbf65 100644 --- a/examples/exception_itm.rs +++ b/examples/exception_itm.rs @@ -13,18 +13,14 @@ extern crate panic_halt; use cortex_m::peripheral::syst::SystClkSource; +use cortex_m::{iprint, Peripherals}; use cortex_m_rt::{entry, exception}; -use cortex_m::{iprint, iprintln, Peripherals}; #[entry] fn main() -> ! { - cortex_m::asm::bkpt(); - let mut p = Peripherals::take().unwrap(); + let p = Peripherals::take().unwrap(); let mut syst = p.SYST; - let stim = &mut p.ITM.stim[0]; - iprintln!(stim, "start"); - // configures the system timer to trigger a SysTick exception every second syst.set_clock_source(SystClkSource::Core); syst.set_reload(16_000_000); // period = 1s @@ -36,6 +32,14 @@ fn main() -> ! { #[exception] fn SysTick() { + // Here we steal all the peripherals. + // + // This is unsafe, as some other task/tasks may access the peripherals + // simultaneously, causing a conflict/race. + // + // The operation is checked in `debug` mode but not in release + // using a `debug_assert`. As the periherals is already taken + // by main, we need to compile in `--release` mode, to avoid a panic. let mut p = unsafe { cortex_m::Peripherals::steal() }; let stim = &mut p.ITM.stim[0]; iprint!(stim, "."); diff --git a/examples/exception_itm_raw.rs b/examples/exception_itm_raw.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4f7872c26fd45929b6b05e4ce5e365cb6822eac --- /dev/null +++ b/examples/exception_itm_raw.rs @@ -0,0 +1,41 @@ +//! Overriding an exception handler +//! +//! You can override an exception handler using the [`#[exception]`][1] attribute. +//! +//! [1]: https://rust-embedded.github.io/cortex-m-rt/0.6.1/cortex_m_rt_macros/fn.exception.html +//! +//! Notice, steal will panic! in debug mode, due to a `debug_assert` (ignored in release). +//! --- + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::peripheral::{syst::SystClkSource, ITM}; +use cortex_m::{iprint, Peripherals}; +use cortex_m_rt::{entry, exception}; + +#[entry] +fn main() -> ! { + let p = Peripherals::take().unwrap(); + let mut syst = p.SYST; + + // configures the system timer to trigger a SysTick exception every second + syst.set_clock_source(SystClkSource::Core); + syst.set_reload(16_000_000); // period = 1s + syst.enable_counter(); + syst.enable_interrupt(); + + loop {} +} + +#[exception] +fn SysTick() { + // here we access `ITM` using a *raw* pointer + // this is unsafe, as there may be other tasks accessing the peripheral + // simultaneously (and that might cause a conflict/race) + let itm = unsafe { &mut *ITM::ptr() }; + let stim = &mut itm.stim[0]; + iprint!(stim, "."); +} diff --git a/openocd.gdb b/openocd.gdb index fa7f424954e287720f0c3ca90064829f37d71cf7..ea08e46bcfff317a2503291c69b6e56a4077b2ab 100644 --- a/openocd.gdb +++ b/openocd.gdb @@ -5,8 +5,8 @@ set print asm-demangle on # detect unhandled exceptions, hard faults and panics #break DefaultHandler -#break UserHardFault -#break rust_begin_unwind +break HardFault +break rust_begin_unwind # *try* to stop at the user entry point (it might be gone due to inlining) break main