Refactor, document and fix the Watchdog driver (#1710)

* Refactor and document the Watchdog driver to make it more readable.

Fix the configuration of the behaviours configuration that was not properly implemented (but it didn't cause any side effect since the correct value was eventually set in NRF_WDT->CONFIG).

Fix the wrong interpretation of the reset reasons caused by implicit conversions of int to bool.
This commit is contained in:
JF 2023-04-30 15:56:13 +02:00 committed by GitHub
parent c22e30a4a6
commit 5f19f689f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 69 deletions

View file

@ -101,24 +101,24 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen1() {
std::unique_ptr<Screen> SystemInfo::CreateScreen2() { std::unique_ptr<Screen> SystemInfo::CreateScreen2() {
auto batteryPercent = batteryController.PercentRemaining(); auto batteryPercent = batteryController.PercentRemaining();
const auto* resetReason = [this]() { const auto* resetReason = [this]() {
switch (watchdog.ResetReason()) { switch (watchdog.GetResetReason()) {
case Drivers::Watchdog::ResetReasons::Watchdog: case Drivers::Watchdog::ResetReason::Watchdog:
return "wtdg"; return "wtdg";
case Drivers::Watchdog::ResetReasons::HardReset: case Drivers::Watchdog::ResetReason::HardReset:
return "hardr"; return "hardr";
case Drivers::Watchdog::ResetReasons::NFC: case Drivers::Watchdog::ResetReason::NFC:
return "nfc"; return "nfc";
case Drivers::Watchdog::ResetReasons::SoftReset: case Drivers::Watchdog::ResetReason::SoftReset:
return "softr"; return "softr";
case Drivers::Watchdog::ResetReasons::CpuLockup: case Drivers::Watchdog::ResetReason::CpuLockup:
return "cpulock"; return "cpulock";
case Drivers::Watchdog::ResetReasons::SystemOff: case Drivers::Watchdog::ResetReason::SystemOff:
return "off"; return "off";
case Drivers::Watchdog::ResetReasons::LpComp: case Drivers::Watchdog::ResetReason::LpComp:
return "lpcomp"; return "lpcomp";
case Drivers::Watchdog::ResetReasons::DebugInterface: case Drivers::Watchdog::ResetReason::DebugInterface:
return "dbg"; return "dbg";
case Drivers::Watchdog::ResetReasons::ResetPin: case Drivers::Watchdog::ResetReason::ResetPin:
return "rst"; return "rst";
default: default:
return "?"; return "?";

View file

@ -2,74 +2,148 @@
#include <mdk/nrf.h> #include <mdk/nrf.h>
using namespace Pinetime::Drivers; using namespace Pinetime::Drivers;
void Watchdog::Setup(uint8_t timeoutSeconds) { namespace {
NRF_WDT->CONFIG &= ~(WDT_CONFIG_SLEEP_Msk << WDT_CONFIG_SLEEP_Pos); /// The watchdog is always driven by a 32768kHz clock
NRF_WDT->CONFIG |= (WDT_CONFIG_HALT_Run << WDT_CONFIG_SLEEP_Pos); 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); /// Configures the behaviours (pause or run) of the watchdog while the CPU is sleeping or halted by the debugger
NRF_WDT->CONFIG |= (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos); ///
/// @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<uint32_t>(sleepBehaviour) | static_cast<uint32_t>(haltBehaviour);
}
/* timeout (s) = (CRV + 1) / 32768 */ /// Configure the timeout delay of the watchdog (called CRV, Counter Reload Value, in the documentation).
// JF : 7500 = 7.5s ///
uint32_t crv = (((timeoutSeconds * 1000u) << 15u) / 1000) - 1; /// @param timeoutSeconds Timeout of the watchdog, expressed in seconds
NRF_WDT->CRV = crv; 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 */ /// Enables the first reload register
NRF_WDT->RREN = (WDT_RREN_RR0_Enabled << WDT_RREN_RR0_Pos); ///
/// 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() { void Watchdog::Start() {
// Write 1 in the START task to start the watchdog
NRF_WDT->TASKS_START = 1; NRF_WDT->TASKS_START = 1;
} }
void Watchdog::Kick() { void Watchdog::Reload() {
NRF_WDT->RR[0] = WDT_RR_RR_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 { const char* Pinetime::Drivers::ResetReasonToString(Watchdog::ResetReason reason) {
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) {
switch (reason) { switch (reason) {
case ResetReasons::ResetPin: case Watchdog::ResetReason::ResetPin:
return "Reset pin"; return "Reset pin";
case ResetReasons::Watchdog: case Watchdog::ResetReason::Watchdog:
return "Watchdog"; return "Watchdog";
case ResetReasons::DebugInterface: case Watchdog::ResetReason::DebugInterface:
return "Debug interface"; return "Debug interface";
case ResetReasons::LpComp: case Watchdog::ResetReason::LpComp:
return "LPCOMP"; return "LPCOMP";
case ResetReasons::SystemOff: case Watchdog::ResetReason::SystemOff:
return "System OFF"; return "System OFF";
case ResetReasons::CpuLockup: case Watchdog::ResetReason::CpuLockup:
return "CPU Lock-up"; return "CPU Lock-up";
case ResetReasons::SoftReset: case Watchdog::ResetReason::SoftReset:
return "Soft reset"; return "Soft reset";
case ResetReasons::NFC: case Watchdog::ResetReason::NFC:
return "NFC"; return "NFC";
case ResetReasons::HardReset: case Watchdog::ResetReason::HardReset:
return "Hard reset"; return "Hard reset";
default: default:
return "Unknown"; return "Unknown";

View file

@ -1,24 +1,68 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <nrf52_bitfields.h>
namespace Pinetime { namespace Pinetime {
namespace Drivers { 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 { class Watchdog {
public: public:
enum class ResetReasons { ResetPin, Watchdog, SoftReset, CpuLockup, SystemOff, LpComp, DebugInterface, NFC, HardReset }; /// Indicates the reasons of a reset of the MCU
void Setup(uint8_t timeoutSeconds); enum class ResetReason { ResetPin, Watchdog, SoftReset, CpuLockup, SystemOff, LpComp, DebugInterface, NFC, HardReset };
void Start();
void Kick();
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; return resetReason;
} }
static const char* ResetReasonToString(ResetReasons reason);
private: private:
ResetReasons resetReason; ResetReason resetReason;
ResetReasons ActualResetReason() const;
}; };
/// Converts a reset reason to a human readable string
const char* ResetReasonToString(Watchdog::ResetReason reason);
} }
} }

View file

@ -99,9 +99,9 @@ void SystemTask::Process(void* instance) {
void SystemTask::Work() { void SystemTask::Work() {
BootErrors bootError = BootErrors::None; BootErrors bootError = BootErrors::None;
watchdog.Setup(7); watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
watchdog.Start(); 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); APP_GPIOTE_INIT(2);
spi.Init(); spi.Init();
@ -403,7 +403,7 @@ void SystemTask::Work() {
dateTimeController.UpdateTime(systick_counter); dateTimeController.UpdateTime(systick_counter);
NoInit_BackUpTime = dateTimeController.CurrentDateTime(); NoInit_BackUpTime = dateTimeController.CurrentDateTime();
if (nrf_gpio_pin_read(PinMap::Button) == 0) { if (nrf_gpio_pin_read(PinMap::Button) == 0) {
watchdog.Kick(); watchdog.Reload();
} }
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop