Skip to content

Conversation

charlesbmi
Copy link
Contributor

First-time opening a PR in this project, not sure if this is the right place to put the code.

I want to be able to determine when a watchdog reset has happened in my firmware. So I added a function to check the reset cause. It probably doesn't belong in wwdt.rs, but wasn't sure if I should make another module.

I also tried implementing a TryFrom(mspm0_metapac::sysctl::vals::Id) but I couldn't sufficiently inspect the type or figure out where it is defined in order to properly use it, so I ended up with an as u8 approach.

Behavior

On a launchpad MSPM0L1306

embassy/examples/mspm0l1306 [ feature/wwdt-reset-detection][?][📦 v0.1.0][🦀 v1.90.0][☁️  ]
❯ cargo run --bin wwdt
   Compiling embassy-mspm0 v0.1.0 (/Users/charlesguan/Development/embassy/embassy-mspm0)
   Compiling embassy-mspm0-l1306-examples v0.1.0 (/Users/charlesguan/Development/embassy/examples/mspm0l1306)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.37s
     Running `probe-rs run --chip MSPM0L1306 --protocol=swd target/thumbv6m-none-eabi/debug/wwdt`
      Erasing ✔ 100% [####################]  16.00 KiB @  14.91 KiB/s (took 1s)
  Programming ✔ 100% [####################]  15.75 KiB @   3.32 KiB/s (took 5s)                                                                                             Finished in 5.82s
[INFO ] Hello world! (wwdt src/bin/wwdt.rs:19)
[INFO ] 🔄 System reset cause: CpurstSwTriggered (wwdt src/bin/wwdt.rs:75)
[INFO ] Started the watchdog timer (wwdt src/bin/wwdt.rs:31)
[INFO ] pet watchdog (wwdt src/bin/wwdt.rs:38)
[INFO ] pet watchdog (wwdt src/bin/wwdt.rs:38)
[INFO ] pet watchdog (wwdt src/bin/wwdt.rs:38)
[INFO ] pet watchdog (wwdt src/bin/wwdt.rs:38)
[INFO ] pet watchdog (wwdt src/bin/wwdt.rs:38)
[INFO ] Stopped the pet command, device will reset in less than 1 second (wwdt src/bin/wwdt.rs:45)
 WARN probe_rs::util::rtt::client: RTT read pointer changed, re-attaching

Unfortunately, I'm not seeing the logs after reset that woudl confirm that the register is getting set to SysrstWwdt0Violation properly. Is there a way for probe-rs and defmt to come back up with the reset-MCU's log-stream?

- Add ResetCause enum with all MSPM0 reset causes from TRM Table 2-9
- Add read_reset_cause() function to read SYSCTL.RSTCAUSE register
- Include helper methods to check for watchdog-specific resets
- Update wwdt example to demonstrate reset cause detection
- Register is automatically cleared after reading per MSPM0 spec

Fixes watchdog reset detection for debugging applications.
@i509VCB
Copy link
Member

i509VCB commented Oct 2, 2025

It probably doesn't belong in wwdt.rs, but wasn't sure if I should make another module.

I think it would belong in lib.rs. Reset is a chip global thing.

Unfortunately, I'm not seeing the logs after reset that woudl confirm that the register is getting set to SysrstWwdt0Violation properly. Is there a way for probe-rs and defmt to come back up with the reset-MCU's log-stream?

The message should be buffered in defmt's internal buffer. I wonder if it has something to do with the hardware racing defmt, or if defmt assumes the contents of the buffer are garbage and clears it before reading after reattach. Is there some way to put a barrier so that execution doesn't advance until something has attached to defmt?

Comment on lines +294 to +315
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),
}
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),
    ....
}

CpurstSwTriggered = 0x1D,
}

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.

Comment on lines +248 to +287
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,
}
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.

Comment on lines +403 to +412
/// 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)
}

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.

@charlesbmi charlesbmi marked this pull request as draft October 2, 2025 23:29
@charlesbmi
Copy link
Contributor Author

Thanks for the feedback and pointers! Will update tomorrow or next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants