3 Exceptions, and interrupts, are a hardware mechanism by which the processor
4 handles asynchronous events and fatal errors (e.g. executing an invalid
5 instruction). Exceptions imply preemption and involve exception handlers,
6 subroutines executed in response to the signal that triggered the event.
8 The `cortex-m-rt` crate provides an [`exception`] attribute to declare exception
11 [`exception`]: https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.exception.html
14 // Exception handler for the SysTick (System Timer) exception
21 Other than the `exception` attribute exception handlers look like plain
22 functions but there's one more difference: `exception` handlers can *not* be
23 called by software. Following the previous example, the statement `SysTick();`
24 would result in a compilation error.
26 This behavior is pretty much intended and it's required to provide a feature:
27 `static mut` variables declared *inside* `exception` handlers are *safe* to use.
32 static mut COUNT: u32 = 0;
34 // `COUNT` has type `&mut u32` and it's safe to use
39 As you may know, using `static mut` variables in a function makes it
40 [*non-reentrant*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). It's undefined behavior to call a non-reentrant function,
41 directly or indirectly, from more than one exception / interrupt handler or from
42 `main` and one or more exception / interrupt handlers.
44 Safe Rust must never result in undefined behavior so non-reentrant functions
45 must be marked as `unsafe`. Yet I just told that `exception` handlers can safely
46 use `static mut` variables. How is this possible? This is possible because
47 `exception` handlers can *not* be called by software thus reentrancy is not
52 Here's an example that uses the system timer to raise a `SysTick` exception
53 roughly every second. The `SysTick` exception handler keeps track of how many
54 times it has been called in the `COUNT` variable and then prints the value of
55 `COUNT` to the host console using semihosting.
57 > **NOTE**: You can run this example on any Cortex-M device; you can also run it
65 extern crate panic_halt;
69 use cortex_m::peripheral::syst::SystClkSource;
70 use cortex_m_rt::{entry, exception};
71 use cortex_m_semihosting::{
78 let p = cortex_m::Peripherals::take().unwrap();
79 let mut syst = p.SYST;
81 // configures the system timer to trigger a SysTick exception every second
82 syst.set_clock_source(SystClkSource::Core);
83 // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz
84 syst.set_reload(12_000_000);
85 syst.enable_counter();
86 syst.enable_interrupt();
93 static mut COUNT: u32 = 0;
94 static mut STDOUT: Option<HStdout> = None;
98 // Lazy initialization
100 *STDOUT = hio::hstdout().ok();
103 if let Some(hstdout) = STDOUT.as_mut() {
104 write!(hstdout, "{}", *COUNT).ok();
107 // IMPORTANT omit this `if` block if running on real hardware or your
108 // debugger will end in an inconsistent state
110 // This will terminate the QEMU process
111 debug::exit(debug::EXIT_SUCCESS);
117 $ tail -n5 Cargo.toml
123 cortex-m-rt = "0.6.3"
125 cortex-m-semihosting = "0.3.1"
129 $ cargo run --release
130 Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
134 If you run this on the Discovery board you'll see the output on the OpenOCD
135 console. Also, the program will *not* stop when the count reaches 9.
137 ## The default exception handler
139 What the `exception` attribute actually does is *override* the default exception
140 handler for a specific exception. If you don't override the handler for a
141 particular exception it will be handled by the `DefaultHandler` function, which
145 fn DefaultHandler() {
150 This function is provided by the `cortex-m-rt` crate and marked as
151 `#[no_mangle]` so you can put a breakpoint on "DefaultHandler" and catch
152 *unhandled* exceptions.
154 It's possible to override this `DefaultHandler` using the `exception` attribute:
158 fn DefaultHandler(irqn: i16) {
159 // custom default handler
163 The `irqn` argument indicates which exception is being serviced. A negative
164 value indicates that a Cortex-M exception is being serviced; and zero or a
165 positive value indicate that a device specific exception, AKA interrupt, is
168 ## The hard fault handler
170 The `HardFault` exception is a bit special. This exception is fired when the
171 program enters an invalid state so its handler can *not* return as that could
172 result in undefined behavior. Also, the runtime crate does a bit of work before
173 the user defined `HardFault` handler is invoked to improve debuggability.
175 The result is that the `HardFault` handler must have the following signature:
176 `fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to
177 registers that were pushed into the stack by the exception. These registers are
178 a snapshot of the processor state at the moment the exception was triggered and
179 are useful to diagnose a hard fault.
181 Here's an example that performs an illegal operation: a read to a nonexistent
184 > **NOTE**: This program won't work, i.e. it won't crash, on QEMU because
185 > `qemu-system-arm -machine lm3s6965evb` doesn't check memory loads and will
186 > happily return `0 `on reads to invalid memory.
192 extern crate panic_halt;
194 use core::fmt::Write;
197 use cortex_m_rt::{entry, exception, ExceptionFrame};
198 use cortex_m_semihosting::hio;
202 // read a nonexistent memory location
204 ptr::read_volatile(0x3FFF_FFFE as *const u32);
211 fn HardFault(ef: &ExceptionFrame) -> ! {
212 if let Ok(mut hstdout) = hio::hstdout() {
213 writeln!(hstdout, "{:#?}", ef).ok();
220 The `HardFault` handler prints the `ExceptionFrame` value. If you run this
221 you'll see something like this on the OpenOCD console.
238 The `pc` value is the value of the Program Counter at the time of the exception
239 and it points to the instruction that triggered the exception.
241 If you look at the disassembly of the program:
245 $ cargo objdump --bin app --release -- -d -no-show-raw-insn -print-imm-hex
248 8000942: movw r0, #0xfffe
249 8000946: movt r0, #0x3fff
250 800094a: ldr r0, [r0]
251 800094c: b #-0x4 <ResetTrampoline+0xa>
254 You can lookup the value of the program counter `0x0800094a` in the dissassembly.
255 You'll see that a load operation (`ldr r0, [r0]` ) caused the exception.
256 The `r0` field of `ExceptionFrame` will tell you the value of register `r0`
257 was `0x3fff_fffe` at that time.