Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions embassy-mspm0/src/wwdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,82 @@ impl ClosedWindowPercentage {
}
}

/// Reset cause values from SYSCTL.RSTCAUSE register.
/// Based on MSPM0 Technical Reference Manual Table 2-9.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ResetCause {
/// No reset since last read
NoReset = 0x00,
/// VDD < POR- violation, PMU trim parity fault, or SHUTDNSTOREx parity fault
PorHwFailure = 0x01,
/// NRST pin reset (>1s)
PorExternalNrst = 0x02,
/// Software-triggered POR
PorSwTriggered = 0x03,
/// VDD < BOR- violation
BorSupplyFailure = 0x04,
/// Wake from SHUTDOWN
BorWakeFromShutdown = 0x05,
/// Non-PMU trim parity fault
BootrstNonPmuParityFault = 0x08,
/// Fatal clock fault
BootrstClockFault = 0x09,
/// NRST pin reset (<1s)
BootrstExternalNrst = 0x0C,
/// Software-triggered BOOTRST
BootrstSwTriggered = 0x0D,
/// WWDT0 violation - This is the main watchdog reset cause we're interested in
SysrstWwdt0Violation = 0x0E,
/// BSL exit (if present)
SysrstBslExit = 0x10,
/// BSL entry (if present)
SysrstBslEntry = 0x11,
/// Uncorrectable flash ECC error (if present)
SysrstFlashEccError = 0x14,
/// CPU lockup violation
SysrstCpuLockupViolation = 0x15,
/// Debug-triggered SYSRST
SysrstDebugTriggered = 0x1A,
/// Software-triggered SYSRST
SysrstSwTriggered = 0x1B,
/// Debug-triggered CPURST
CpurstDebugTriggered = 0x1C,
/// Software-triggered CPURST
CpurstSwTriggered = 0x1D,
}
Comment on lines +248 to +287
Copy link
Member

@i509VCB i509VCB Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all of these can be produced on every chip. But cfg guarding when a reset cause is available is going to involve some metapac work. Either another big entry in parts.yaml or we try to parse the SVDs to figure out which reset causes are available (but this might require us to fix the errors that cause some SYSCTL modules erroring when extracted).

I'd be fine with just leaving all variants to exist on every chip.


impl TryFrom<u8> for ResetCause {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is public API that we want to guarantee. Since ResetCause is a public enum, it would expose TryFrom in public API.

type Error = u8;

fn try_from(value: u8) -> Result<Self, Self::Error> {
use ResetCause::*;
match value {
0x00 => Ok(NoReset),
0x01 => Ok(PorHwFailure),
0x02 => Ok(PorExternalNrst),
0x03 => Ok(PorSwTriggered),
0x04 => Ok(BorSupplyFailure),
0x05 => Ok(BorWakeFromShutdown),
0x08 => Ok(BootrstNonPmuParityFault),
0x09 => Ok(BootrstClockFault),
0x0C => Ok(BootrstExternalNrst),
0x0D => Ok(BootrstSwTriggered),
0x0E => Ok(SysrstWwdt0Violation),
0x10 => Ok(SysrstBslExit),
0x11 => Ok(SysrstBslEntry),
0x14 => Ok(SysrstFlashEccError),
0x15 => Ok(SysrstCpuLockupViolation),
0x1A => Ok(SysrstDebugTriggered),
0x1B => Ok(SysrstSwTriggered),
0x1C => Ok(CpurstDebugTriggered),
0x1D => Ok(CpurstSwTriggered),
other => Err(other),
}
Comment on lines +294 to +315
Copy link
Member

@i509VCB i509VCB Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a fun little trick you can use here given that ResetCause is repr(u8)

If you write something like the following for each branch you only need to declare the u8 value once.

match value {
    _ if value == NoReset as u8 => Ok(NoReset),
    ....
}

}
}

#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
/// Watchdog Config
Expand Down Expand Up @@ -324,6 +400,16 @@ impl Watchdog {
}
}

/// Read the reset cause from the SYSCTL.RSTCAUSE register.
///
/// This function reads the reset cause register which indicates why the last
/// system reset occurred. The register is automatically cleared after being read,
/// so this should be called only once per application startup.
pub fn read_reset_cause() -> Result<ResetCause, u8> {
let cause_raw = pac::SYSCTL.rstcause().read().id();
ResetCause::try_from(cause_raw as u8)
}

Comment on lines +403 to +412
Copy link
Member

@i509VCB i509VCB Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does open up an interesting safety question: Should we allow the user to call this more than once? With peripheral init it makes sense to do that since otherwise memory safety errors would happen.

In this case it would just produce the wrong reset value. At minimum probably a good idea to mark this function as #[must_use = "Reading reset cause will clear it"] so that the compiler can warn if you try to use it and ignore the result.

pub(crate) trait SealedInstance {
fn regs() -> &'static Regs;
}
Expand Down
29 changes: 28 additions & 1 deletion examples/mspm0l1306/src/bin/wwdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use defmt::*;
use embassy_executor::Spawner;
use embassy_mspm0::gpio::{Level, Output};
use embassy_mspm0::wwdt::{ClosedWindowPercentage, Config, Timeout, Watchdog};
use embassy_mspm0::wwdt::{
read_reset_cause, ClosedWindowPercentage, Config, ResetCause, Timeout, Watchdog,
};
use embassy_time::Timer;
use {defmt_rtt as _, panic_halt as _};

Expand All @@ -20,6 +22,8 @@ async fn main(_spawner: Spawner) -> ! {
let mut conf = Config::default();
conf.timeout = Timeout::Sec1;

log_reset_cause();

// watchdog also resets the system if the pet comes too early,
// less than 250 msec == 25% from 1 sec
conf.closed_window = ClosedWindowPercentage::TwentyFive;
Expand Down Expand Up @@ -52,3 +56,26 @@ async fn main(_spawner: Spawner) -> ! {
// Timer::after_millis(200).await;
// }
}

fn log_reset_cause() {
// Check reset cause early in startup (register is cleared after reading)
match read_reset_cause() {
Ok(ResetCause::SysrstWwdt0Violation) => {
error!(
"πŸ• System was reset due to WWDT0 timeout! Previous run failed to pet watchdog."
);
}
Ok(ResetCause::NoReset) => {
info!("βœ… No reset since last read (first boot or register already read)");
}
Ok(ResetCause::PorExternalNrst) => {
info!("πŸ”„ System reset via NRST pin");
}
Ok(other) => {
info!("πŸ”„ System reset cause: {:?}", other);
}
Err(_) => {
warn!("❓ Unknown reset cause detected (reserved value)");
}
}
}
Loading