From b3d00ba62296b0cc112ef9da14902e5b6e440a31 Mon Sep 17 00:00:00 2001 From: Per Lindgren <per.lindgren@ltu.se> Date: Fri, 4 Jan 2019 14:38:28 +0100 Subject: [PATCH] more examples --- .vscode/tasks.json | 12 +++++ README.md | 101 ++++++++++++++++++++++++++++++++++---- examples/exception.rs | 37 ++++++++++++++ examples/exception_itm.rs | 42 ++++++++++++++++ examples/hello.rs | 2 +- examples/panic.rs | 1 - 6 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 examples/exception.rs create mode 100644 examples/exception_itm.rs diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5e0d1a1..4e31401 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,6 +14,18 @@ "kind": "build", "isDefault": true } + }, + { + "type": "shell", + "label": "cargo build --examples --release", + "command": "cargo build --examples --release", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } } ] } \ No newline at end of file diff --git a/README.md b/README.md index 56b6fa4..63adb37 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ > Examples and exercises for the Nucleo STM32F401re/STM32F11re devkits. +--- ## Dependencies @@ -16,13 +17,19 @@ $ rustup target add thumbv7em-none-eabihf ``` - For programming (flashing) and debugging - - openocd (install using your package manager) - - arm-none-eabi toolchain (install using your package manager). In the following we refer the `arm-none-eabi-gdb` as just `gdb` for brevity. + - `openocd` (install using your package manager) + - `arm-none-eabi` toolchain (install using your package manager). In the following we refer the `arm-none-eabi-gdb` as just `gdb` for brevity. -- st-flash (for low level access to the MCU flash) +- `st-flash` (for low level access to the MCU flash) +- `itmdump` (for ITM trace output) +- `vscode` and `cortex-debug` (optional for an integrated debugging experience) + +--- ## Examples +--- + ### Hello 1. Connect your devkit using USB. To check that it is found you can run: @@ -51,7 +58,7 @@ Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints Info : Listening on port 3333 for gdb connections ``` -`openocd` should connect to your target using the `stlink` programmer (onboard your Nucleo devkit). You may need to hold the `RESET` button (black), while starting `openocd`. If that does not work, disconnect the USB cable, hold the `RESET` button, re-connect the USB, start `openocd` then let go of the button. +`openocd` should connect to your target using the `stlink` programmer (onboard your Nucleo devkit). See the `Trouble Shooting` section if you run into trouble. 3. In another terminal (in the same `app` folder) run: @@ -101,6 +108,8 @@ Program received signal SIGINT, Interrupt. You have now compiled and debugged a minimal Rust `hello` example. `gdb` is a very useful tool so lookup some tutorials/docs (e.g., https://sourceware.org/gdb/onlinedocs/gdb/), a Cheat Sheet can be found at https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf. +--- + ### ITM 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.) @@ -126,19 +135,83 @@ Under the hood there is much less overhead, the serial transfer rate is set to 2 The `stlink` programmer, buffers packages but has limited buffer space. Hence in practise, you should keep tracing to short messages, else the buffer will overflow (and the programmer might crash). See trouble shooting section if you run into trouble. +--- + +### Rust `panic` Handling + +The `rust` compiler statically analyses your code, but in cases some errors cannot be detected at compile time (e.g., array indexing out of bounds, division by zore etc.). The `rust` compiler generates code checking such faults at run-time, instead of just crashing (or even worse, continuing with faulty/undefined values like a `C` program would) . A fault in Rust will render a `panic`, whith an associated error message (useful to debugging the application). We can choose how such `panic`s should be treated, e.g., transmitting the error message using `semihosting`, `ITM`, some other channel (e.g. a serial port), or simply aborting the program. + +The `panic` example demonstrates some possible use cases. + +The `openocd.gdb` script sets a breakpoint at `rust_begin_unwind` (a function in the `rust core` library, used to recover errors.) + +When running the example (see above howto compile and run), the `gdb` terminal will show: + +``` console +... +Breakpoint 2, main () at examples/panic.rs:27 +27 panic!("Oops") +(gdb) c +Continuing. +halted: PC: 0x08000404 + +Breakpoint 1, rust_begin_unwind (_info=0x20017fb4) + at /home/pln/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs:33 +33 atomic::compiler_fence(Ordering::SeqCst); +(gdb) p *_info +$1 = core::panic::PanicInfo {payload: core::any::&Any {pointer: 0x8000760 <.Lanon.21a036e607595cc96ffa1870690e4414.142> "\017\004\000", vtable: 0x8000760 <.Lanon.21a036e607595cc96ffa1870690e4414.142>}, message: core::option::Option<&core::fmt::Arguments>::Some(0x20017fd0), location: core::panic::Location {file: <error reading variable>, line: 27, col: 5}} +``` + +Here `p *_info` prints the arument to `rust_begin_unwind`, at the far end you will find `line: 27, col 5`, which correstponds to the source code calling `panic("Ooops")`. (`gdb` is not (yet) Rust aware enough to figure out how the `file` field should be interpreted, but at least we get some useful information). + +Alternatively we can trace the panic message over `semihosting` (comment out `extern crate panic_halt` and uncomment `extern crate panic_semihosting`). + +The `openocd` console should now show: + +``` consolse +Info : halted: PC: 0x080011a0 +panicked at 'Oops', examples/panic.rs:27:5 +``` + +Under the hood, this approach involves *formatting* of the panic message, which implementation occupies a bit of flash memory (in our case we have 512kB so plenty enough, but for the smallest of MCUs this may be a problem). Another drawback is that it requires a debugger to be connected and active. + +Another alternative is to use ITM (uncomment `extern crate panic_itm`), this is faster, but be aware, the message may overflow the `ITM` buffer, so it may be unreliable. Also it assumes, that the ITM stream is actively monitored. + +A third alternative would be to store the panic message in some non-volatile memory (flash, eeprom, etc.). This allows for true post-mortem debugging of a unit put in production. This approach is used e.g. in automotive applications where the workshop can read-out error codes of your vehicle. + +--- + +### Exception Handling + +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. + +As an exercise make it use the ITM instead. + -# Trouble shooting +--- + +# Trouble Shooting Working with embedded targets involves a lot of tooling, and many things can go wrong. +--- + ## `openocd` fails to connect -If you end up with a program that puts the MCU in a bad state, even a reset might not help you. In that case you can erase the flash memory. `st-flash` connects to the target directly (bypassing `gdb` and `openocd`) and hence more likely to get access to the target even if its in a bad state. +If you end up with a program that puts the MCU in a bad state. + +- Hold the `RESET` button (black), while starting `openocd`. If that does not work, disconnect the USB cable, hold the `RESET` button, re-connect the USB, start `openocd` then then let go of the button. + +- However even a reset might not help you. In that case you can erase the flash memory. `st-flash` connects to the target directly (bypassing `gdb` and `openocd`) and hence more likely to get access to the target even if its in a bad state. ``` console $ st-flash erase ``` +--- + ## `gdb` fails to connect `openocd` acts as a *gdb server*, while `gdb` is a *gdb client*. By default they connect over port `:3333` (: indicates that the port is on the *localhost*, not a remote connection). In cases you might have another `gdb` connection blocking the port. @@ -176,6 +249,8 @@ arm-none- 7825 pln 14u IPv4 442734 0t0 TCP localhost:59560->localhost: $ kill -9 7825 ``` +--- + ## `itmdump` no tracing or faulty output There can be a number of reasons ITM tracing fails. @@ -270,7 +345,9 @@ In case the ITM buffer is saturated, ITM tracing stops working (and might be har This ensures 1) the program will not yet again overflow the ITM buffer, 2) the faulty program is gone (and not restarted accidently on a `RESET`), 3) the programmer firmware is restarted and does not carry any persistent state, notice a `RESET` applies only to the target, not the programmer, so if the programmer crashes it needs to be power cycled), 4) the FIFO `/tmp/itm.log`, `openocd` and `gdb` will have fresh states. -## Visual Studio Code +--- + +# 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: @@ -289,7 +366,7 @@ It is possible to run `gdb` from within the `vscode`. `vscode` is highly configu You may step, view the current context `variables`, add `watches`, inspect the `call stack`, add `breakpoints`, inspect `peripherals` and `registers`. Read more in the documentation for the plugin. -### Caveats +## Caveats Visual Studio Code is not an "IDE", its a text editor with plugin support, with an API somewhat limiting what can be done from within a plugin (in comparison to Eclipse, IntelliJ...) regarding panel layouts etc. E.g., as far as I know you cannot view the `adapter output` (`openocd`) at the same time as the ITM trace, they are both under the `OUTPUT` tab. Moreover, each time you re-start a debug session, you need to re-select the `SWO: Name [port 0, type console]` to view the ITM output. There are some `hax` around this: @@ -335,7 +412,9 @@ adapter speed: 8000 kHz - Using the `gdb` terminal (`DEBUG CONSOLE`) from within `vscode` is somewhat instable/experimental. E.g., `CTRL+c` does not `break` the target (use `F6`, or write `interrupt`). The `contiune` command, indeed continues execution (and the *control bar* changes mode, but you cannot `break` using neither `F6` nor `interrupt`). So it seems that the *state* of the `cortex-debug` plugin is not correctly updated. Moreover setting breakpoints from the `gdb` terminal indeed informs `gdb` about the breakpoint, but the state in `vscode` is not updated, so be aware. -## launch configurations +--- + +## Vscode Launch Configurations The first three lauch configurations in the `.vscode/launch.json` file: @@ -417,7 +496,9 @@ The first three lauch configurations in the `.vscode/launch.json` file: We see some similarities to the `openocd.gdb` file, we don't need to explicitly connect to the target (that is automatic). Also launching `openocd` is automatic (for good and bad, its re-started each time). `postLaunchCommands` allows arbitrary commands to be executed by `gdb` once the session is up. E.g. in the `hello` case we enable `semihosting`, while in the `itm` case we run `monitor reset init` to get the MCU in 64MHz (first example) or 16MHz (third example), before running the application (continue). Notice the first example uses the "stock" `openocd` configuration files, while the third example uses our local configuration files (that does not change the core frequency). -## Advanced usage +--- + +# GDB Advanced Usage There are numerous ways to automate `gdb`. Scripts can be run by the `gdb` command `source` (`so` for short). Scripting common tasks like setting breakpoints, dumping some memory region etc. can be really helpful. diff --git a/examples/exception.rs b/examples/exception.rs new file mode 100644 index 0000000..8461bc6 --- /dev/null +++ b/examples/exception.rs @@ -0,0 +1,37 @@ +//! 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 +//! +//! --- + +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::peripheral::syst::SystClkSource; +use cortex_m::Peripherals; +use cortex_m_rt::{entry, exception}; +use cortex_m_semihosting::hprint; + +#[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() { + hprint!(".").unwrap(); +} diff --git a/examples/exception_itm.rs b/examples/exception_itm.rs new file mode 100644 index 0000000..90799e7 --- /dev/null +++ b/examples/exception_itm.rs @@ -0,0 +1,42 @@ +//! 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; +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 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 + syst.enable_counter(); + syst.enable_interrupt(); + + loop {} +} + +#[exception] +fn SysTick() { + let mut p = unsafe { cortex_m::Peripherals::steal() }; + let stim = &mut p.ITM.stim[0]; + iprint!(stim, "."); +} diff --git a/examples/hello.rs b/examples/hello.rs index d1c84b2..f36bdb3 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -3,7 +3,7 @@ #![no_main] #![no_std] -extern crate panic_abort; +extern crate panic_halt; use cortex_m_rt::entry; use cortex_m_semihosting::hprintln; diff --git a/examples/panic.rs b/examples/panic.rs index 9738eaf..51ea8d1 100644 --- a/examples/panic.rs +++ b/examples/panic.rs @@ -11,7 +11,6 @@ // `panic!` halts execution; the panic message is ignored // extern crate panic_halt; -// extern crate panic_abort; // Reports panic messages to the host stderr using semihosting // NOTE to use this you need to uncomment the `panic-semihosting` dependency in Cargo.toml -- GitLab