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 transformed to 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
50 > Note that the `exception` attribute transforms definitions of static variables
51 > inside the function by wrapping them into `unsafe` blocks and providing us
52 > with new appropriate variables of type `&mut` of the same name.
53 > Thus we can dereference the reference via `*` to access the values of the variables without
54 > needing to wrap them in an `unsafe` block.
58 Here's an example that uses the system timer to raise a `SysTick` exception
59 roughly every second. The `SysTick` exception handler keeps track of how many
60 times it has been called in the `COUNT` variable and then prints the value of
61 `COUNT` to the host console using semihosting.
63 > **NOTE**: You can run this example on any Cortex-M device; you can also run it
75 use cortex_m::peripheral::syst::SystClkSource;
76 use cortex_m_rt::{entry, exception};
77 use cortex_m_semihosting::{
84 let p = cortex_m::Peripherals::take().unwrap();
85 let mut syst = p.SYST;
87 // configures the system timer to trigger a SysTick exception every second
88 syst.set_clock_source(SystClkSource::Core);
89 // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz
90 syst.set_reload(12_000_000);
92 syst.enable_counter();
93 syst.enable_interrupt();
100 static mut COUNT: u32 = 0;
101 static mut STDOUT: Option<HStdout> = None;
105 // Lazy initialization
106 if STDOUT.is_none() {
107 *STDOUT = hio::hstdout().ok();
110 if let Some(hstdout) = STDOUT.as_mut() {
111 write!(hstdout, "{}", *COUNT).ok();
114 // IMPORTANT omit this `if` block if running on real hardware or your
115 // debugger will end in an inconsistent state
117 // This will terminate the QEMU process
118 debug::exit(debug::EXIT_SUCCESS);
130 cortex-m-rt = "0.6.3"
132 cortex-m-semihosting = "0.3.1"
136 $ cargo run --release
137 Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
141 If you run this on the Discovery board you'll see the output on the OpenOCD
142 console. Also, the program will *not* stop when the count reaches 9.
144 ## The default exception handler
146 What the `exception` attribute actually does is *override* the default exception
147 handler for a specific exception. If you don't override the handler for a
148 particular exception it will be handled by the `DefaultHandler` function, which
152 fn DefaultHandler() {
157 This function is provided by the `cortex-m-rt` crate and marked as
158 `#[no_mangle]` so you can put a breakpoint on "DefaultHandler" and catch
159 *unhandled* exceptions.
161 It's possible to override this `DefaultHandler` using the `exception` attribute:
165 fn DefaultHandler(irqn: i16) {
166 // custom default handler
170 The `irqn` argument indicates which exception is being serviced. A negative
171 value indicates that a Cortex-M exception is being serviced; and zero or a
172 positive value indicate that a device specific exception, AKA interrupt, is
175 ## The hard fault handler
177 The `HardFault` exception is a bit special. This exception is fired when the
178 program enters an invalid state so its handler can *not* return as that could
179 result in undefined behavior. Also, the runtime crate does a bit of work before
180 the user defined `HardFault` handler is invoked to improve debuggability.
182 The result is that the `HardFault` handler must have the following signature:
183 `fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to
184 registers that were pushed into the stack by the exception. These registers are
185 a snapshot of the processor state at the moment the exception was triggered and
186 are useful to diagnose a hard fault.
188 Here's an example that performs an illegal operation: a read to a nonexistent
191 > **NOTE**: This program won't work, i.e. it won't crash, on QEMU because
192 > `qemu-system-arm -machine lm3s6965evb` doesn't check memory loads and will
193 > happily return `0 `on reads to invalid memory.
201 use core::fmt::Write;
204 use cortex_m_rt::{entry, exception, ExceptionFrame};
205 use cortex_m_semihosting::hio;
209 // read a nonexistent memory location
211 ptr::read_volatile(0x3FFF_FFFE as *const u32);
218 fn HardFault(ef: &ExceptionFrame) -> ! {
219 if let Ok(mut hstdout) = hio::hstdout() {
220 writeln!(hstdout, "{:#?}", ef).ok();
227 The `HardFault` handler prints the `ExceptionFrame` value. If you run this
228 you'll see something like this on the OpenOCD console.
245 The `pc` value is the value of the Program Counter at the time of the exception
246 and it points to the instruction that triggered the exception.
248 If you look at the disassembly of the program:
252 $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex
255 8000942: movw r0, #0xfffe
256 8000946: movt r0, #0x3fff
257 800094a: ldr r0, [r0]
258 800094c: b #-0x4 <ResetTrampoline+0xa>
261 You can lookup the value of the program counter `0x0800094a` in the disassembly.
262 You'll see that a load operation (`ldr r0, [r0]` ) caused the exception.
263 The `r0` field of `ExceptionFrame` will tell you the value of register `r0`
264 was `0x3fff_fffe` at that time.