Skip to main content
RKTK API Docs RKTK Home Repo

rktk_drivers_common/
panic_utils.rs

1//! Panic message handling utilities.
2//!
3//! Because doing something complicated in panic_handler is difficult (eg. async function can't be called),
4//! alternatively you can reboot the device in panic_handler.
5//! By saving panic info in uninit section, you can display panic message on display after reboot.
6//!
7//! Note that this module depends on .uninit section, which is handled by cortex_m_rt.
8
9use core::{fmt::Write, mem::MaybeUninit, ptr::write_volatile};
10
11use embassy_time::Timer;
12use embedded_graphics::{
13    mono_font::{MonoFont, MonoTextStyle, ascii::FONT_8X13},
14    pixelcolor::BinaryColor,
15    prelude::*,
16    text::{Baseline, Text},
17};
18use rktk::{
19    drivers::interface::display::DisplayDriver,
20    task::display::utils::{RotatedDrawTarget, Rotation},
21};
22
23#[unsafe(link_section = ".uninit.PANICINFO")]
24static mut PANIC_INFO: MaybeUninit<PanicMessage> = MaybeUninit::uninit();
25const PANIC_INFO_MAGIC: u32 = 0x54_41_43_4B;
26
27#[repr(C)]
28pub struct PanicMessage {
29    magic: u32,
30    len: u16,
31    data: [u8; 1024],
32}
33
34impl Write for PanicMessage {
35    fn write_str(&mut self, s: &str) -> core::fmt::Result {
36        let writable_str = if s.len() + self.len as usize > self.data.len() {
37            let writable_len = self.data.len() - self.len as usize;
38            if writable_len == 0 {
39                return Ok(());
40            }
41            s.get(..writable_len).unwrap_or(s)
42        } else {
43            s
44        };
45
46        let new_len = writable_str.len() as u16 + self.len;
47        self.data[self.len as usize..new_len as usize].copy_from_slice(writable_str.as_bytes());
48        self.len = new_len;
49
50        Ok(())
51    }
52}
53
54impl Default for PanicMessage {
55    fn default() -> Self {
56        Self { magic: PANIC_INFO_MAGIC, len: 0, data: [0; 1024] }
57    }
58}
59
60impl PanicMessage {
61    fn reset() -> Self {
62        Self { magic: 0, len: 0, data: [0; 1024] }
63    }
64}
65
66/// Save panic info to uninit section.
67///
68/// This function should be called in panic_handler.
69pub fn save_panic_info(info: &core::panic::PanicInfo) {
70    let mut panic_info = PanicMessage::default();
71    write!(panic_info, "{info}").ok();
72
73    unsafe {
74        write_volatile(&raw mut PANIC_INFO, MaybeUninit::new(panic_info));
75    }
76}
77
78pub fn read_panic_message() -> Option<PanicMessage> {
79    unsafe {
80        let info = core::ptr::read(&raw const PANIC_INFO);
81        let info = info.assume_init();
82        if info.magic == PANIC_INFO_MAGIC {
83            write_volatile(&raw mut PANIC_INFO, MaybeUninit::new(PanicMessage::reset()));
84            Some(info)
85        } else {
86            None
87        }
88    }
89}
90
91pub fn parse_panic_message(panic_info: &PanicMessage) -> &str {
92    // if panic_info.len == 0 {
93    //     return "No panic message";
94    // }
95    match core::str::from_utf8(&panic_info.data[..panic_info.len as usize]) {
96        Ok(str) => str,
97        Err(e) => {
98            let valid_len = e.valid_up_to();
99            core::str::from_utf8(&panic_info.data[..valid_len]).unwrap()
100        }
101    }
102}
103
104const FONT: MonoFont = FONT_8X13;
105
106/// Display panic message on display is exists.
107/// This is intended to be called before [`rktk::task::start`] and takes a display builder.
108///
109/// If previous panic message exists, this function will display it on the display and return None.
110/// Otherwise, it will return the display builder.
111///
112/// When None is returned, caller can stop execution using something like [`cortex_m::asm::udf`]
113pub async fn display_message_if_panicked<D: DisplayDriver<Color = BinaryColor>>(display: &mut D) {
114    if let Some(panic_info) = read_panic_message()
115        && display.init().await.is_ok()
116    {
117        let char_width = FONT.character_size.width as usize;
118
119        let str = parse_panic_message(&panic_info);
120
121        rktk_log::error!("Previous panic detected: {:?}", str);
122
123        let str_len = str.lines().map(|line| line.chars().count()).max().unwrap_or(0);
124
125        let orig_display_size = display.draw_target().bounding_box().size;
126        let rotation = if orig_display_size.width > orig_display_size.height {
127            Rotation::Rotate0
128        } else {
129            Rotation::Rotate90
130        };
131
132        let rotated_display = RotatedDrawTarget::new(display.draw_target(), rotation);
133        let display_width = rotated_display.bounding_box().size.width as usize;
134        let overflow_len = if str_len * char_width > display_width {
135            str_len - display_width / char_width + 1
136        } else {
137            0
138        };
139
140        display_mes(display, "Panic!", Point::zero(), rotation).await;
141        Timer::after_millis(400).await;
142
143        if overflow_len > 0 {
144            loop {
145                for i in 0..=overflow_len {
146                    display_mes(display, str, Point::new(-((i * char_width) as i32), 0), rotation)
147                        .await;
148                    Timer::after_millis(200).await;
149                }
150            }
151        } else {
152            display_mes(display, str, Point::zero(), rotation).await;
153            Timer::after_secs(100000000).await;
154        }
155    }
156}
157pub async fn display_mes<D: DisplayDriver<Color = BinaryColor>>(
158    display: &mut D,
159    str: &str,
160    pos: Point,
161    rotation: Rotation,
162) {
163    {
164        let mut rotated_display = RotatedDrawTarget::new(display.draw_target(), rotation);
165
166        let _ = rotated_display.clear(BinaryColor::Off);
167        let _ = Text::with_baseline(
168            str,
169            pos,
170            MonoTextStyle::new(&FONT, BinaryColor::On),
171            Baseline::Top,
172        )
173        .draw(&mut rotated_display);
174    }
175    let _ = display.flush().await;
176}