diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index 511ecf50..d6fa7365 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -101,24 +101,24 @@ std::unique_ptr SystemInfo::CreateScreen1() { std::unique_ptr SystemInfo::CreateScreen2() { auto batteryPercent = batteryController.PercentRemaining(); const auto* resetReason = [this]() { - switch (watchdog.ResetReason()) { - case Drivers::Watchdog::ResetReasons::Watchdog: + switch (watchdog.GetResetReason()) { + case Drivers::Watchdog::ResetReason::Watchdog: return "wtdg"; - case Drivers::Watchdog::ResetReasons::HardReset: + case Drivers::Watchdog::ResetReason::HardReset: return "hardr"; - case Drivers::Watchdog::ResetReasons::NFC: + case Drivers::Watchdog::ResetReason::NFC: return "nfc"; - case Drivers::Watchdog::ResetReasons::SoftReset: + case Drivers::Watchdog::ResetReason::SoftReset: return "softr"; - case Drivers::Watchdog::ResetReasons::CpuLockup: + case Drivers::Watchdog::ResetReason::CpuLockup: return "cpulock"; - case Drivers::Watchdog::ResetReasons::SystemOff: + case Drivers::Watchdog::ResetReason::SystemOff: return "off"; - case Drivers::Watchdog::ResetReasons::LpComp: + case Drivers::Watchdog::ResetReason::LpComp: return "lpcomp"; - case Drivers::Watchdog::ResetReasons::DebugInterface: + case Drivers::Watchdog::ResetReason::DebugInterface: return "dbg"; - case Drivers::Watchdog::ResetReasons::ResetPin: + case Drivers::Watchdog::ResetReason::ResetPin: return "rst"; default: return "?"; diff --git a/src/drivers/Watchdog.cpp b/src/drivers/Watchdog.cpp index d0907a65..6c2c963b 100644 --- a/src/drivers/Watchdog.cpp +++ b/src/drivers/Watchdog.cpp @@ -2,74 +2,148 @@ #include using namespace Pinetime::Drivers; -void Watchdog::Setup(uint8_t timeoutSeconds) { - NRF_WDT->CONFIG &= ~(WDT_CONFIG_SLEEP_Msk << WDT_CONFIG_SLEEP_Pos); - NRF_WDT->CONFIG |= (WDT_CONFIG_HALT_Run << WDT_CONFIG_SLEEP_Pos); +namespace { + /// The watchdog is always driven by a 32768kHz clock + constexpr uint32_t ClockFrequency = 32768; + /// Write this value in the reload register to reload the watchdog + constexpr uint32_t ReloadValue = 0x6E524635UL; - NRF_WDT->CONFIG &= ~(WDT_CONFIG_HALT_Msk << WDT_CONFIG_HALT_Pos); - NRF_WDT->CONFIG |= (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos); + /// Configures the behaviours (pause or run) of the watchdog while the CPU is sleeping or halted by the debugger + /// + /// @param sleepBehaviour Configure the watchdog to either be paused, or kept running, while the CPU is sleeping + /// @param haltBehaviour Configure the watchdog to either be paused, or kept running, while the CPU is halted by the debugger + void SetBehaviours(Watchdog::SleepBehaviour sleepBehaviour, Watchdog::HaltBehaviour haltBehaviour) { + // NRF_WDT->CONFIG : only the 1st and 4th bits are relevant. + // Bit 0 : Behavior when the CPU is sleeping + // Bit 3 : Behavior when the CPU is halted by the debugger + // O means that the CPU is paused during sleep/halt, 1 means that the watchdog is kept running + NRF_WDT->CONFIG = static_cast(sleepBehaviour) | static_cast(haltBehaviour); + } - /* timeout (s) = (CRV + 1) / 32768 */ - // JF : 7500 = 7.5s - uint32_t crv = (((timeoutSeconds * 1000u) << 15u) / 1000) - 1; - NRF_WDT->CRV = crv; + /// Configure the timeout delay of the watchdog (called CRV, Counter Reload Value, in the documentation). + /// + /// @param timeoutSeconds Timeout of the watchdog, expressed in seconds + void SetTimeout(uint8_t timeoutSeconds) { + // According to the documentation: + // Clock = 32768 + // timeout [s] = ( CRV + 1 ) / Clock + // -> CRV = (timeout [s] * Clock) -1 + NRF_WDT->CRV = (timeoutSeconds * ClockFrequency) - 1; + } - /* Enable reload requests */ - NRF_WDT->RREN = (WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos); + /// Enables the first reload register + /// + /// The hardware provides 8 reload registers. To reload the watchdog, all enabled + /// register must be refreshed. + /// + /// This driver only enables the first reload register. + void EnableFirstReloadRegister() { + // RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent + // one of the eight reload registers available. + // In this case, we enable only the first one. + NRF_WDT->RREN |= 1; + } - resetReason = ActualResetReason(); + /// Returns the reset reason provided by the POWER subsystem + Watchdog::ResetReason GetResetReason() { + /* NRF_POWER->RESETREAS + * -------------------------------------------------------------------------------------------------------------------- * + * Bit | Reason (if bit is set to 1) + * ----|---------------------------------------------------------------------------------------------------------------- * + * 0 | Reset from the pin reset + * 1 | Reset from the watchdog + * 2 | Reset from soft reset + * 3 | Reset from CPU lock-up + * 16 | Reset due to wake up from System OFF mode when wakeup is triggered from DETECT signal from GPIO + * 17 | Reset due to wake up from System OFF mode when wakeup is triggered from ANADETECT signal from LPCOMP + * 18 | Reset due to wake up from System OFF mode when wakeup is triggered from entering into debug interface mode + * 19 | Reset due to wake up from System OFF mode by NFC field detect + * -------------------------------------------------------------------------------------------------------------------- */ + const uint32_t reason = NRF_POWER->RESETREAS; + NRF_POWER->RESETREAS = 0xffffffff; + + uint32_t value = reason & 0x01; // avoid implicit conversion to bool using this temporary variable. + if (value != 0) { + return Watchdog::ResetReason::ResetPin; + } + + value = (reason >> 1u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::Watchdog; + } + + value = (reason >> 2u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::SoftReset; + } + + value = (reason >> 3u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::CpuLockup; + } + + value = (reason >> 16u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::SystemOff; + } + + value = (reason >> 17u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::LpComp; + } + + value = (reason >> 18u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::DebugInterface; + } + + value = (reason >> 19u) & 0x01u; + if (value != 0) { + return Watchdog::ResetReason::NFC; + } + + return Watchdog::ResetReason::HardReset; + } +} + +void Watchdog::Setup(uint8_t timeoutSeconds, SleepBehaviour sleepBehaviour, HaltBehaviour haltBehaviour) { + SetBehaviours(sleepBehaviour, haltBehaviour); + SetTimeout(timeoutSeconds); + EnableFirstReloadRegister(); + + resetReason = ::GetResetReason(); } void Watchdog::Start() { + // Write 1 in the START task to start the watchdog NRF_WDT->TASKS_START = 1; } -void Watchdog::Kick() { - NRF_WDT->RR[0] = WDT_RR_RR_Reload; +void Watchdog::Reload() { + // Write the reload value 0x6E524635UL to the reload register to reload the watchdog. + // NOTE : This driver enables only the 1st reload register. + NRF_WDT->RR[0] = ReloadValue; } -Watchdog::ResetReasons Watchdog::ActualResetReason() const { - uint32_t reason = NRF_POWER->RESETREAS; - NRF_POWER->RESETREAS = 0xffffffff; - - if (reason & 0x01u) - return ResetReasons::ResetPin; - if ((reason >> 1u) & 0x01u) - return ResetReasons::Watchdog; - if ((reason >> 2u) & 0x01u) - return ResetReasons::SoftReset; - if ((reason >> 3u) & 0x01u) - return ResetReasons::CpuLockup; - if ((reason >> 16u) & 0x01u) - return ResetReasons::SystemOff; - if ((reason >> 17u) & 0x01u) - return ResetReasons::LpComp; - if ((reason) &0x01u) - return ResetReasons::DebugInterface; - if ((reason >> 19u) & 0x01u) - return ResetReasons::NFC; - return ResetReasons::HardReset; -} - -const char* Watchdog::ResetReasonToString(Watchdog::ResetReasons reason) { +const char* Pinetime::Drivers::ResetReasonToString(Watchdog::ResetReason reason) { switch (reason) { - case ResetReasons::ResetPin: + case Watchdog::ResetReason::ResetPin: return "Reset pin"; - case ResetReasons::Watchdog: + case Watchdog::ResetReason::Watchdog: return "Watchdog"; - case ResetReasons::DebugInterface: + case Watchdog::ResetReason::DebugInterface: return "Debug interface"; - case ResetReasons::LpComp: + case Watchdog::ResetReason::LpComp: return "LPCOMP"; - case ResetReasons::SystemOff: + case Watchdog::ResetReason::SystemOff: return "System OFF"; - case ResetReasons::CpuLockup: + case Watchdog::ResetReason::CpuLockup: return "CPU Lock-up"; - case ResetReasons::SoftReset: + case Watchdog::ResetReason::SoftReset: return "Soft reset"; - case ResetReasons::NFC: + case Watchdog::ResetReason::NFC: return "NFC"; - case ResetReasons::HardReset: + case Watchdog::ResetReason::HardReset: return "Hard reset"; default: return "Unknown"; diff --git a/src/drivers/Watchdog.h b/src/drivers/Watchdog.h index 65a505cb..c075232e 100644 --- a/src/drivers/Watchdog.h +++ b/src/drivers/Watchdog.h @@ -1,24 +1,68 @@ #pragma once #include +#include namespace Pinetime { namespace Drivers { + /// Low level driver for the watchdog based on the nRF52832 Product Specification V1.1 + /// + /// This driver initializes the timeout and sleep and halt behaviours of the watchdog + /// in the method Watchdog::Setup(). + /// + /// The watchdog can then be started using the method Watchdog::Start(). At this point, the watchdog runs + /// and will reset the MCU if it's not reloaded before the timeout elapses. + /// + /// The watchdog can be reloaded using Watchdog::Kick(). + /// + /// The watchdog also provide the cause of the last reset (reset pin, watchdog, soft reset, hard reset,... See + /// Watchdog::ResetReasons). class Watchdog { public: - enum class ResetReasons { ResetPin, Watchdog, SoftReset, CpuLockup, SystemOff, LpComp, DebugInterface, NFC, HardReset }; - void Setup(uint8_t timeoutSeconds); - void Start(); - void Kick(); + /// Indicates the reasons of a reset of the MCU + enum class ResetReason { ResetPin, Watchdog, SoftReset, CpuLockup, SystemOff, LpComp, DebugInterface, NFC, HardReset }; - ResetReasons ResetReason() const { + /// Behaviours of the watchdog when the CPU is sleeping + enum class SleepBehaviour : uint8_t { + /// Pause watchdog while the CPU is sleeping + Pause = 0 << WDT_CONFIG_SLEEP_Pos, + /// Keep the watchdog running while the CPU is sleeping + Run = 1 << WDT_CONFIG_SLEEP_Pos + }; + + /// Behaviours of the watchdog when the CPU is halted by the debugger + enum class HaltBehaviour : uint8_t { + /// Pause watchdog while the CPU is halted by the debugger + Pause = 0 << WDT_CONFIG_HALT_Pos, + /// Keep the watchdog running while the CPU is halted by the debugger + Run = 1 << WDT_CONFIG_HALT_Pos + }; + + /// Configures the watchdog with a specific timeout, behaviour when sleeping and when halted by the debugger + /// + /// @param sleepBehaviour Configure the watchdog to either be paused, or kept running, while the CPU is sleeping + /// @param haltBehaviour Configure the watchdog to either be paused, or kept running, while the CPU is halted by the debugger + void Setup(uint8_t timeoutSeconds, SleepBehaviour sleepBehaviour, HaltBehaviour haltBehaviour); + + /// Starts the watchdog. The watchdog will reset the MCU when the timeout period is elapsed unless you call + /// Watchdog::Kick before the end of the period + void Start(); + + /// Reloads the watchdog. + /// + /// Ensure that you call this function regularly with a period shorter + /// than the timeout period to prevent the watchdog from resetting the MCU. + void Reload(); + + /// Returns the reason of the last reset + ResetReason GetResetReason() const { return resetReason; } - static const char* ResetReasonToString(ResetReasons reason); - private: - ResetReasons resetReason; - ResetReasons ActualResetReason() const; + ResetReason resetReason; }; + + /// Converts a reset reason to a human readable string + const char* ResetReasonToString(Watchdog::ResetReason reason); } } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 0621c6ef..b199d53f 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -99,9 +99,9 @@ void SystemTask::Process(void* instance) { void SystemTask::Work() { BootErrors bootError = BootErrors::None; - watchdog.Setup(7); + watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause); watchdog.Start(); - NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason())); + NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason())); APP_GPIOTE_INIT(2); spi.Init(); @@ -403,7 +403,7 @@ void SystemTask::Work() { dateTimeController.UpdateTime(systick_counter); NoInit_BackUpTime = dateTimeController.CurrentDateTime(); if (nrf_gpio_pin_read(PinMap::Button) == 0) { - watchdog.Kick(); + watchdog.Reload(); } } #pragma clang diagnostic pop