aboutsummaryrefslogtreecommitdiffstats
path: root/src/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/Bma421.cpp29
-rw-r--r--src/drivers/Bma421.h1
-rw-r--r--src/drivers/Hrs3300.cpp90
-rw-r--r--src/drivers/Hrs3300.h10
-rw-r--r--src/drivers/PinMap.h1
-rw-r--r--src/drivers/Spi.cpp7
-rw-r--r--src/drivers/Spi.h3
-rw-r--r--src/drivers/SpiMaster.cpp96
-rw-r--r--src/drivers/SpiMaster.h12
-rw-r--r--src/drivers/SpiNorFlash.cpp12
-rw-r--r--src/drivers/SpiNorFlash.h5
-rw-r--r--src/drivers/St7789.cpp267
-rw-r--r--src/drivers/St7789.h46
-rw-r--r--src/drivers/Watchdog.cpp168
-rw-r--r--src/drivers/Watchdog.h60
15 files changed, 555 insertions, 252 deletions
diff --git a/src/drivers/Bma421.cpp b/src/drivers/Bma421.cpp
index 84d76ab3..74d47d06 100644
--- a/src/drivers/Bma421.cpp
+++ b/src/drivers/Bma421.cpp
@@ -22,6 +22,16 @@ namespace {
void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
nrf_delay_us(period_us);
}
+
+ // Scale factors to convert accelerometer counts to milli-g
+ // from datasheet: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
+ // The array index to use is stored in accel_conf.range
+ constexpr int16_t accelScaleFactors[] = {
+ [BMA4_ACCEL_RANGE_2G] = 1024, // LSB/g +/- 2g range
+ [BMA4_ACCEL_RANGE_4G] = 512, // LSB/g +/- 4g range
+ [BMA4_ACCEL_RANGE_8G] = 256, // LSB/g +/- 8g range
+ [BMA4_ACCEL_RANGE_16G] = 128 // LSB/g +/- 16g range
+ };
}
Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
@@ -74,7 +84,6 @@ void Bma421::Init() {
if (ret != BMA4_OK)
return;
- struct bma4_accel_config accel_conf;
accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
accel_conf.range = BMA4_ACCEL_RANGE_2G;
accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
@@ -102,19 +111,21 @@ void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) {
Bma421::Values Bma421::Process() {
if (not isOk)
return {};
+ struct bma4_accel rawData;
struct bma4_accel data;
- bma4_read_accel_xyz(&data, &bma);
+ bma4_read_accel_xyz(&rawData, &bma);
+
+ // Scale the measured ADC counts to units of 'binary milli-g'
+ // where 1g = 1024 'binary milli-g' units.
+ // See https://github.com/InfiniTimeOrg/InfiniTime/pull/1950 for
+ // discussion of why we opted for scaling to 1024 rather than 1000.
+ data.x = 1024 * rawData.x / accelScaleFactors[accel_conf.range];
+ data.y = 1024 * rawData.y / accelScaleFactors[accel_conf.range];
+ data.z = 1024 * rawData.z / accelScaleFactors[accel_conf.range];
uint32_t steps = 0;
bma423_step_counter_output(&steps, &bma);
- int32_t temperature;
- bma4_get_temperature(&temperature, BMA4_DEG, &bma);
- temperature = temperature / 1000;
-
- uint8_t activity = 0;
- bma423_activity_output(&activity, &bma);
-
// X and Y axis are swapped because of the way the sensor is mounted in the PineTime
return {steps, data.y, data.x, data.z};
}
diff --git a/src/drivers/Bma421.h b/src/drivers/Bma421.h
index fb832514..5269f62b 100644
--- a/src/drivers/Bma421.h
+++ b/src/drivers/Bma421.h
@@ -41,6 +41,7 @@ namespace Pinetime {
TwiMaster& twiMaster;
uint8_t deviceAddress = 0x18;
struct bma4_dev bma;
+ struct bma4_accel_config accel_conf; // Store the device configuration for later reference.
bool isOk = false;
bool isResetOk = false;
DeviceTypes deviceType = DeviceTypes::Unknown;
diff --git a/src/drivers/Hrs3300.cpp b/src/drivers/Hrs3300.cpp
index 6c47ae28..a4b72479 100644
--- a/src/drivers/Hrs3300.cpp
+++ b/src/drivers/Hrs3300.cpp
@@ -6,6 +6,7 @@
#include "drivers/Hrs3300.h"
#include <algorithm>
+#include <iterator>
#include <nrf_gpio.h>
#include <FreeRTOS.h>
@@ -14,8 +15,14 @@
using namespace Pinetime::Drivers;
+namespace {
+ static constexpr uint8_t ledDriveCurrentValue = 0x2f;
+}
+
/** Driver for the HRS3300 heart rate sensor.
- * Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/drivers/hrs3300.py
+ * Original implementation from wasp-os : https://github.com/wasp-os/wasp-os/blob/master/wasp/drivers/hrs3300.py
+ *
+ * Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
*/
Hrs3300::Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, twiAddress {twiAddress} {
}
@@ -26,19 +33,21 @@ void Hrs3300::Init() {
Disable();
vTaskDelay(100);
- // HRS disabled, 12.5 ms wait time between cycles, (partly) 20mA drive
- WriteRegister(static_cast<uint8_t>(Registers::Enable), 0x60);
+ // HRS disabled, 50ms wait time between ADC conversion period, current 12.5mA
+ WriteRegister(static_cast<uint8_t>(Registers::Enable), 0x50);
- // (partly) 20mA drive, power on, "magic" (datasheet says both
- // "reserved" and "set low nibble to 8" but 0xe gives better results
- // and is used by at least two other HRS3300 drivers
- WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0x6E);
+ // Current 12.5mA and low nibble 0xF.
+ // Note: Setting low nibble to 0x8 per the datasheet results in
+ // modulated LED driver output. Setting to 0xF results in clean,
+ // steady output during the ADC conversion period.
+ WriteRegister(static_cast<uint8_t>(Registers::PDriver), ledDriveCurrentValue);
- // HRS and ALS both in 16-bit mode
- WriteRegister(static_cast<uint8_t>(Registers::Res), 0x88);
+ // HRS and ALS both in 15-bit mode results in ~50ms LED drive period
+ // and presumably ~50ms ADC conversion period.
+ WriteRegister(static_cast<uint8_t>(Registers::Res), 0x77);
- // 8x gain, non default, reduced value for better readings
- WriteRegister(static_cast<uint8_t>(Registers::Hgain), 0xc);
+ // Gain set to 1x
+ WriteRegister(static_cast<uint8_t>(Registers::Hgain), 0x00);
}
void Hrs3300::Enable() {
@@ -46,6 +55,8 @@ void Hrs3300::Enable() {
auto value = ReadRegister(static_cast<uint8_t>(Registers::Enable));
value |= 0x80;
WriteRegister(static_cast<uint8_t>(Registers::Enable), value);
+
+ WriteRegister(static_cast<uint8_t>(Registers::PDriver), ledDriveCurrentValue);
}
void Hrs3300::Disable() {
@@ -53,42 +64,41 @@ void Hrs3300::Disable() {
auto value = ReadRegister(static_cast<uint8_t>(Registers::Enable));
value &= ~0x80;
WriteRegister(static_cast<uint8_t>(Registers::Enable), value);
-}
-uint32_t Hrs3300::ReadHrs() {
- auto m = ReadRegister(static_cast<uint8_t>(Registers::C0DataM));
- auto h = ReadRegister(static_cast<uint8_t>(Registers::C0DataH));
- auto l = ReadRegister(static_cast<uint8_t>(Registers::C0dataL));
- return ((l & 0x30) << 12) | (m << 8) | ((h & 0x0f) << 4) | (l & 0x0f);
+ WriteRegister(static_cast<uint8_t>(Registers::PDriver), 0);
}
-uint32_t Hrs3300::ReadAls() {
- auto m = ReadRegister(static_cast<uint8_t>(Registers::C1dataM));
- auto h = ReadRegister(static_cast<uint8_t>(Registers::C1dataH));
- auto l = ReadRegister(static_cast<uint8_t>(Registers::C1dataL));
- return ((h & 0x3f) << 11) | (m << 3) | (l & 0x07);
-}
+Hrs3300::PackedHrsAls Hrs3300::ReadHrsAls() {
+ constexpr Registers dataRegisters[] =
+ {Registers::C1dataM, Registers::C0DataM, Registers::C0DataH, Registers::C1dataH, Registers::C1dataL, Registers::C0dataL};
+ // Calculate smallest register address
+ constexpr uint8_t baseOffset = static_cast<uint8_t>(*std::min_element(std::begin(dataRegisters), std::end(dataRegisters)));
+ // Calculate largest address to determine length of read needed
+ // Add one to largest relative index to find the length
+ constexpr uint8_t length = static_cast<uint8_t>(*std::max_element(std::begin(dataRegisters), std::end(dataRegisters))) - baseOffset + 1;
-void Hrs3300::SetGain(uint8_t gain) {
- constexpr uint8_t maxGain = 64U;
- gain = std::min(gain, maxGain);
- uint8_t hgain = 0;
- while ((1 << hgain) < gain) {
- ++hgain;
+ Hrs3300::PackedHrsAls res;
+ uint8_t buf[length];
+ auto ret = twiMaster.Read(twiAddress, baseOffset, buf, length);
+ if (ret != TwiMaster::ErrorCodes::NoError) {
+ NRF_LOG_INFO("READ ERROR");
}
+ // hrs
+ uint8_t m = static_cast<uint8_t>(Registers::C0DataM) - baseOffset;
+ uint8_t h = static_cast<uint8_t>(Registers::C0DataH) - baseOffset;
+ uint8_t l = static_cast<uint8_t>(Registers::C0dataL) - baseOffset;
+ // There are two extra bits (17 and 18) but they are not read here
+ // as resolutions >16bit aren't practically useful (too slow) and
+ // all hrs values throughout InfiniTime are 16bit
+ res.hrs = (buf[m] << 8) | ((buf[h] & 0x0f) << 4) | (buf[l] & 0x0f);
- WriteRegister(static_cast<uint8_t>(Registers::Hgain), hgain << 2);
-}
-
-void Hrs3300::SetDrive(uint8_t drive) {
- auto en = ReadRegister(static_cast<uint8_t>(Registers::Enable));
- auto pd = ReadRegister(static_cast<uint8_t>(Registers::PDriver));
-
- en = (en & 0xf7) | ((drive & 2) << 2);
- pd = (pd & 0xbf) | ((drive & 1) << 6);
+ // als
+ m = static_cast<uint8_t>(Registers::C1dataM) - baseOffset;
+ h = static_cast<uint8_t>(Registers::C1dataH) - baseOffset;
+ l = static_cast<uint8_t>(Registers::C1dataL) - baseOffset;
+ res.als = ((buf[h] & 0x3f) << 11) | (buf[m] << 3) | (buf[l] & 0x07);
- WriteRegister(static_cast<uint8_t>(Registers::Enable), en);
- WriteRegister(static_cast<uint8_t>(Registers::PDriver), pd);
+ return res;
}
void Hrs3300::WriteRegister(uint8_t reg, uint8_t data) {
diff --git a/src/drivers/Hrs3300.h b/src/drivers/Hrs3300.h
index 8bbdc69a..6f721448 100644
--- a/src/drivers/Hrs3300.h
+++ b/src/drivers/Hrs3300.h
@@ -21,6 +21,11 @@ namespace Pinetime {
Hgain = 0x17
};
+ struct PackedHrsAls {
+ uint16_t hrs;
+ uint16_t als;
+ };
+
Hrs3300(TwiMaster& twiMaster, uint8_t twiAddress);
Hrs3300(const Hrs3300&) = delete;
Hrs3300& operator=(const Hrs3300&) = delete;
@@ -30,10 +35,7 @@ namespace Pinetime {
void Init();
void Enable();
void Disable();
- uint32_t ReadHrs();
- uint32_t ReadAls();
- void SetGain(uint8_t gain);
- void SetDrive(uint8_t drive);
+ PackedHrsAls ReadHrsAls();
private:
TwiMaster& twiMaster;
diff --git a/src/drivers/PinMap.h b/src/drivers/PinMap.h
index a70cfc41..238b965d 100644
--- a/src/drivers/PinMap.h
+++ b/src/drivers/PinMap.h
@@ -34,6 +34,7 @@ namespace Pinetime {
static constexpr uint8_t SpiFlashCsn = 5;
static constexpr uint8_t SpiLcdCsn = 25;
static constexpr uint8_t LcdDataCommand = 18;
+ static constexpr uint8_t LcdReset = 26;
static constexpr uint8_t TwiScl = 7;
static constexpr uint8_t TwiSda = 6;
diff --git a/src/drivers/Spi.cpp b/src/drivers/Spi.cpp
index e477622b..a95a7eae 100644
--- a/src/drivers/Spi.cpp
+++ b/src/drivers/Spi.cpp
@@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn {
nrf_gpio_pin_set(pinCsn);
}
-bool Spi::Write(const uint8_t* data, size_t size) {
- return spiMaster.Write(pinCsn, data, size);
+bool Spi::Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
+ return spiMaster.Write(pinCsn, data, size, preTransactionHook);
}
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
@@ -27,7 +27,8 @@ bool Spi::WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* d
}
bool Spi::Init() {
- nrf_gpio_pin_set(pinCsn); /* disable Set slave select (inactive high) */
+ nrf_gpio_cfg_output(pinCsn);
+ nrf_gpio_pin_set(pinCsn);
return true;
}
diff --git a/src/drivers/Spi.h b/src/drivers/Spi.h
index 9b6a30f4..0c5edf08 100644
--- a/src/drivers/Spi.h
+++ b/src/drivers/Spi.h
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <cstddef>
+#include <functional>
#include "drivers/SpiMaster.h"
namespace Pinetime {
@@ -14,7 +15,7 @@ namespace Pinetime {
Spi& operator=(Spi&&) = delete;
bool Init();
- bool Write(const uint8_t* data, size_t size);
+ bool Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
void Sleep();
diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp
index 234884ab..19422ef3 100644
--- a/src/drivers/SpiMaster.cpp
+++ b/src/drivers/SpiMaster.cpp
@@ -94,32 +94,45 @@ bool SpiMaster::Init() {
return true;
}
-void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
- // Create an event when SCK toggles.
- NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) |
- (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
+void SpiMaster::SetupWorkaroundForErratum58() {
+ nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
+ nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE,
+ .pull = NRF_GPIO_PIN_NOPULL,
+ .is_watcher = false,
+ .hi_accuracy = true,
+ .skip_gpio_setup = true};
+ if (!workaroundActive) {
+ // Create an event when SCK toggles.
+ APP_ERROR_CHECK(nrfx_gpiote_in_init(pin, &gpioteCfg, NULL));
+ nrfx_gpiote_in_event_enable(pin, false);
+
+ // Stop the spim instance when SCK toggles.
+ nrf_ppi_channel_endpoint_setup(workaroundPpi, nrfx_gpiote_in_event_addr_get(pin), spiBaseAddress->TASKS_STOP);
+ nrf_ppi_channel_enable(workaroundPpi);
+ }
- // Stop the spim instance when SCK toggles.
- NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel];
- NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP;
- NRF_PPI->CHENSET = 1U << ppi_channel;
spiBaseAddress->EVENTS_END = 0;
// Disable IRQ
- spim->INTENCLR = (1 << 6);
- spim->INTENCLR = (1 << 1);
- spim->INTENCLR = (1 << 19);
+ spiBaseAddress->INTENCLR = (1 << 6);
+ spiBaseAddress->INTENCLR = (1 << 1);
+ spiBaseAddress->INTENCLR = (1 << 19);
+ workaroundActive = true;
}
-void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
- NRF_GPIOTE->CONFIG[gpiote_channel] = 0;
- NRF_PPI->CH[ppi_channel].EEP = 0;
- NRF_PPI->CH[ppi_channel].TEP = 0;
- NRF_PPI->CHENSET = ppi_channel;
+void SpiMaster::DisableWorkaroundForErratum58() {
+ nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
+ if (workaroundActive) {
+ nrfx_gpiote_in_uninit(pin);
+ nrf_ppi_channel_disable(workaroundPpi);
+ }
spiBaseAddress->EVENTS_END = 0;
- spim->INTENSET = (1 << 6);
- spim->INTENSET = (1 << 1);
- spim->INTENSET = (1 << 19);
+
+ // Enable IRQ
+ spiBaseAddress->INTENSET = (1 << 6);
+ spiBaseAddress->INTENSET = (1 << 1);
+ spiBaseAddress->INTENSET = (1 << 19);
+ workaroundActive = false;
}
void SpiMaster::OnEndEvent() {
@@ -131,29 +144,23 @@ void SpiMaster::OnEndEvent() {
if (s > 0) {
auto currentSize = std::min((size_t) 255, s);
PrepareTx(currentBufferAddr, currentSize);
- currentBufferAddr += currentSize;
- currentBufferSize -= currentSize;
+ currentBufferAddr = currentBufferAddr + currentSize;
+ currentBufferSize = currentBufferSize - currentSize;
spiBaseAddress->TASKS_START = 1;
} else {
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
- if (taskToNotify != nullptr) {
- vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
- }
-
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
- BaseType_t xHigherPriorityTaskWoken2 = pdFALSE;
- xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2);
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2);
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void SpiMaster::OnStartedEvent() {
}
-void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size) {
+void SpiMaster::PrepareTx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->TXD.PTR = bufferAddress;
spiBaseAddress->TXD.MAXCNT = size;
spiBaseAddress->TXD.LIST = 0;
@@ -163,7 +170,7 @@ void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile
spiBaseAddress->EVENTS_END = 0;
}
-void SpiMaster::PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size) {
+void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->TXD.PTR = 0;
spiBaseAddress->TXD.MAXCNT = 0;
spiBaseAddress->TXD.LIST = 0;
@@ -173,21 +180,23 @@ void SpiMaster::PrepareRx(const volatile uint32_t bufferAddress, const volatile
spiBaseAddress->EVENTS_END = 0;
}
-bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
+bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
if (data == nullptr)
return false;
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
ASSERT(ok == true);
- taskToNotify = xTaskGetCurrentTaskHandle();
this->pinCsn = pinCsn;
if (size == 1) {
- SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0);
+ SetupWorkaroundForErratum58();
} else {
- DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
+ DisableWorkaroundForErratum58();
}
+ if (preTransactionHook != nullptr) {
+ preTransactionHook();
+ }
nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = (uint32_t) data;
@@ -195,8 +204,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize);
PrepareTx(currentBufferAddr, currentSize);
- currentBufferSize -= currentSize;
- currentBufferAddr += currentSize;
+ currentBufferSize = currentBufferSize - currentSize;
+ currentBufferAddr = currentBufferAddr + currentSize;
spiBaseAddress->TASKS_START = 1;
if (size == 1) {
@@ -204,6 +213,9 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
;
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
+
+ DisableWorkaroundForErratum58();
+
xSemaphoreGive(mutex);
}
@@ -213,10 +225,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
- taskToNotify = nullptr;
-
this->pinCsn = pinCsn;
- DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
+ DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
@@ -262,10 +272,8 @@ void SpiMaster::Wakeup() {
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
- taskToNotify = nullptr;
-
this->pinCsn = pinCsn;
- DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
+ DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
diff --git a/src/drivers/SpiMaster.h b/src/drivers/SpiMaster.h
index 8b698c57..be6e5351 100644
--- a/src/drivers/SpiMaster.h
+++ b/src/drivers/SpiMaster.h
@@ -1,10 +1,13 @@
#pragma once
#include <cstddef>
#include <cstdint>
+#include <functional>
#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>
+#include "nrfx_gpiote.h"
+#include "nrf_ppi.h"
namespace Pinetime {
namespace Drivers {
@@ -31,7 +34,7 @@ namespace Pinetime {
SpiMaster& operator=(SpiMaster&&) = delete;
bool Init();
- bool Write(uint8_t pinCsn, const uint8_t* data, size_t size);
+ bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
@@ -43,8 +46,8 @@ namespace Pinetime {
void Wakeup();
private:
- void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
- void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
+ void SetupWorkaroundForErratum58();
+ void DisableWorkaroundForErratum58();
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size);
@@ -56,8 +59,9 @@ namespace Pinetime {
volatile uint32_t currentBufferAddr = 0;
volatile size_t currentBufferSize = 0;
- volatile TaskHandle_t taskToNotify;
SemaphoreHandle_t mutex = nullptr;
+ static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0;
+ bool workaroundActive = false;
};
}
}
diff --git a/src/drivers/SpiNorFlash.cpp b/src/drivers/SpiNorFlash.cpp
index 28f82fe6..3bc45cca 100644
--- a/src/drivers/SpiNorFlash.cpp
+++ b/src/drivers/SpiNorFlash.cpp
@@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} {
}
void SpiNorFlash::Init() {
- device_id = ReadIdentificaion();
+ device_id = ReadIdentification();
NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d",
device_id.manufacturer,
device_id.type,
@@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() {
void SpiNorFlash::Sleep() {
auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
- spi.Write(&cmd, sizeof(uint8_t));
+ spi.Write(&cmd, sizeof(uint8_t), nullptr);
NRF_LOG_INFO("[SpiNorFlash] Sleep")
}
@@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() {
uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03};
uint8_t id = 0;
spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1);
- auto devId = device_id = ReadIdentificaion();
+ auto devId = device_id = ReadIdentification();
if (devId.type != device_id.type) {
NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed");
} else {
@@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() {
NRF_LOG_INFO("[SpiNorFlash] Wakeup")
}
-SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
+SpiNorFlash::Identification SpiNorFlash::ReadIdentification() {
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
Identification identification;
spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification));
@@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) {
len -= toWrite;
}
}
+
+SpiNorFlash::Identification SpiNorFlash::GetIdentification() const {
+ return device_id;
+}
diff --git a/src/drivers/SpiNorFlash.h b/src/drivers/SpiNorFlash.h
index 8a063fea..028f92b4 100644
--- a/src/drivers/SpiNorFlash.h
+++ b/src/drivers/SpiNorFlash.h
@@ -20,7 +20,6 @@ namespace Pinetime {
uint8_t density = 0;
};
- Identification ReadIdentificaion();
uint8_t ReadStatusRegister();
bool WriteInProgress();
bool WriteEnabled();
@@ -33,6 +32,8 @@ namespace Pinetime {
bool ProgramFailed();
bool EraseFailed();
+ Identification GetIdentification() const;
+
void Init();
void Uninit();
@@ -40,6 +41,8 @@ namespace Pinetime {
void Wakeup();
private:
+ Identification ReadIdentification();
+
enum class Commands : uint8_t {
PageProgram = 0x02,
Read = 0x03,
diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp
index cfd5bd2c..482fbad6 100644
--- a/src/drivers/St7789.cpp
+++ b/src/drivers/St7789.cpp
@@ -1,66 +1,123 @@
+#include <cstring>
#include "drivers/St7789.h"
#include <hal/nrf_gpio.h>
-#include <libraries/delay/nrf_delay.h>
#include <nrfx_log.h>
#include "drivers/Spi.h"
+#include "task.h"
using namespace Pinetime::Drivers;
-St7789::St7789(Spi& spi, uint8_t pinDataCommand) : spi {spi}, pinDataCommand {pinDataCommand} {
+St7789::St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset) : spi {spi}, pinDataCommand {pinDataCommand}, pinReset {pinReset} {
}
void St7789::Init() {
- spi.Init();
nrf_gpio_cfg_output(pinDataCommand);
- nrf_gpio_cfg_output(26);
- nrf_gpio_pin_set(26);
+ nrf_gpio_cfg_output(pinReset);
+ nrf_gpio_pin_set(pinReset);
HardwareReset();
SoftwareReset();
+ Command2Enable();
SleepOut();
- ColMod();
+ PixelFormat();
MemoryDataAccessControl();
- ColumnAddressSet();
- RowAddressSet();
+ SetAddrWindow(0, 0, Width, Height);
// P8B Mirrored version does not need display inversion.
#ifndef DRIVER_DISPLAY_MIRROR
DisplayInversionOn();
#endif
+ PorchSet();
+ FrameRateNormalSet();
+ IdleFrameRateOff();
NormalModeOn();
SetVdv();
+ PowerControl();
+ GateControl();
DisplayOn();
}
-void St7789::WriteCommand(uint8_t cmd) {
- nrf_gpio_pin_clear(pinDataCommand);
- WriteSpi(&cmd, 1);
+void St7789::WriteData(uint8_t data) {
+ WriteData(&data, 1);
}
-void St7789::WriteData(uint8_t data) {
- nrf_gpio_pin_set(pinDataCommand);
- WriteSpi(&data, 1);
+void St7789::WriteData(const uint8_t* data, size_t size) {
+ WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
+ nrf_gpio_pin_set(pinDataCommand);
+ });
+}
+
+void St7789::WriteCommand(uint8_t data) {
+ WriteCommand(&data, 1);
}
-void St7789::WriteSpi(const uint8_t* data, size_t size) {
- spi.Write(data, size);
+void St7789::WriteCommand(const uint8_t* data, size_t size) {
+ WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
+ nrf_gpio_pin_clear(pinDataCommand);
+ });
+}
+
+void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
+ spi.Write(data, size, preTransactionHook);
}
void St7789::SoftwareReset() {
+ EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
- nrf_delay_ms(150);
+ // If sleep in: must wait 120ms before sleep out can sent (see driver datasheet)
+ // Unconditionally wait as software reset doesn't need to be performant
+ sleepIn = true;
+ lastSleepExit = xTaskGetTickCount();
+ vTaskDelay(pdMS_TO_TICKS(125));
+}
+
+void St7789::Command2Enable() {
+ WriteCommand(static_cast<uint8_t>(Commands::Command2Enable));
+ constexpr uint8_t args[] = {
+ 0x5a, // Constant
+ 0x69, // Constant
+ 0x02, // Constant
+ 0x01, // Enable
+ };
+ WriteData(args, sizeof(args));
}
void St7789::SleepOut() {
+ if (!sleepIn) {
+ return;
+ }
WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
+ // Wait 5ms for clocks to stabilise
+ // pdMS rounds down => 6 used here
+ vTaskDelay(pdMS_TO_TICKS(6));
+ // Cannot send sleep in or software reset for 120ms
+ lastSleepExit = xTaskGetTickCount();
+ sleepIn = false;
+}
+
+void St7789::EnsureSleepOutPostDelay() {
+ TickType_t delta = xTaskGetTickCount() - lastSleepExit;
+ // Due to timer wraparound, there is a chance of delaying when not necessary
+ // It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad
+ if (delta < pdMS_TO_TICKS(125)) {
+ vTaskDelay(pdMS_TO_TICKS(125) - delta);
+ }
}
void St7789::SleepIn() {
+ if (sleepIn) {
+ return;
+ }
+ EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
+ // Wait 5ms for clocks to stabilise
+ // pdMS rounds down => 6 used here
+ vTaskDelay(pdMS_TO_TICKS(6));
+ sleepIn = true;
}
-void St7789::ColMod() {
- WriteCommand(static_cast<uint8_t>(Commands::ColMod));
+void St7789::PixelFormat() {
+ WriteCommand(static_cast<uint8_t>(Commands::PixelFormat));
+ // 65K colours, 16-bit per pixel
WriteData(0x55);
- nrf_delay_ms(10);
}
void St7789::MemoryDataAccessControl() {
@@ -79,54 +136,110 @@ void St7789::MemoryDataAccessControl() {
#endif
}
-void St7789::ColumnAddressSet() {
- WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
- WriteData(0x00);
- WriteData(0x00);
- WriteData(Width >> 8u);
- WriteData(Width & 0xffu);
-}
-
-void St7789::RowAddressSet() {
- WriteCommand(static_cast<uint8_t>(Commands::RowAddressSet));
- WriteData(0x00);
- WriteData(0x00);
- WriteData(320u >> 8u);
- WriteData(320u & 0xffu);
-}
-
void St7789::DisplayInversionOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
- nrf_delay_ms(10);
}
void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
- nrf_delay_ms(10);
+}
+
+void St7789::IdleModeOn() {
+ WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn));
+}
+
+void St7789::IdleModeOff() {
+ WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff));
+}
+
+void St7789::PorchSet() {
+ WriteCommand(static_cast<uint8_t>(Commands::Porch));
+ constexpr uint8_t args[] = {
+ 0x02, // Normal mode front porch
+ 0x03, // Normal mode back porch
+ 0x01, // Porch control enable
+ 0xed, // Idle mode front:back porch
+ 0xed, // Partial mode front:back porch (partial mode unused but set anyway)
+ };
+ WriteData(args, sizeof(args));
+}
+
+void St7789::FrameRateNormalSet() {
+ WriteCommand(static_cast<uint8_t>(Commands::FrameRateNormal));
+ // Note that the datasheet table is imprecise - see formula below table
+ WriteData(0x0a);
+}
+
+void St7789::IdleFrameRateOn() {
+ WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
+ // According to the datasheet, these controls should apply only to partial/idle mode
+ // However they appear to apply to normal mode, so we have to enable/disable
+ // every time we enter/exit always on
+ constexpr uint8_t args[] = {
+ 0x12, // Enable frame rate control for partial/idle mode, 4x frame divider
+ 0x1e, // Idle mode frame rate
+ 0x1e, // Partial mode frame rate (unused)
+ };
+ WriteData(args, sizeof(args));
+}
+
+void St7789::IdleFrameRateOff() {
+ WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
+ constexpr uint8_t args[] = {
+ 0x00, // Disable frame rate control and divider
+ 0x0a, // Idle mode frame rate (normal)
+ 0x0a, // Partial mode frame rate (normal, unused)
+ };
+ WriteData(args, sizeof(args));
}
void St7789::DisplayOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOn));
}
+void St7789::PowerControl() {
+ WriteCommand(static_cast<uint8_t>(Commands::PowerControl1));
+ constexpr uint8_t args[] = {
+ 0xa4, // Constant
+ 0x00, // Lowest possible voltages
+ };
+ WriteData(args, sizeof(args));
+
+ WriteCommand(static_cast<uint8_t>(Commands::PowerControl2));
+ // Lowest possible boost circuit clocks
+ WriteData(0xb3);
+}
+
+void St7789::GateControl() {
+ WriteCommand(static_cast<uint8_t>(Commands::GateControl));
+ // Lowest possible VGL/VGH
+ WriteData(0x00);
+}
+
void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet));
- WriteData(x0 >> 8);
- WriteData(x0 & 0xff);
- WriteData(x1 >> 8);
- WriteData(x1 & 0xff);
+ uint8_t colArgs[] = {
+ static_cast<uint8_t>(x0 >> 8), // x start MSB
+ static_cast<uint8_t>(x0), // x start LSB
+ static_cast<uint8_t>(x1 >> 8), // x end MSB
+ static_cast<uint8_t>(x1) // x end LSB
+ };
+ WriteData(colArgs, sizeof(colArgs));
WriteCommand(static_cast<uint8_t>(Commands::RowAddressSet));
- WriteData(y0 >> 8);
- WriteData(y0 & 0xff);
- WriteData(y1 >> 8);
- WriteData(y1 & 0xff);
-
- WriteToRam();
+ uint8_t rowArgs[] = {
+ static_cast<uint8_t>(y0 >> 8), // y start MSB
+ static_cast<uint8_t>(y0), // y start LSB
+ static_cast<uint8_t>(y1 >> 8), // y end MSB
+ static_cast<uint8_t>(y1) // y end LSB
+ };
+ memcpy(addrWindowArgs, rowArgs, sizeof(rowArgs));
+ WriteData(addrWindowArgs, sizeof(addrWindowArgs));
}
-void St7789::WriteToRam() {
+void St7789::WriteToRam(const uint8_t* data, size_t size) {
WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
+ WriteData(data, size);
}
void St7789::SetVdv() {
@@ -138,50 +251,48 @@ void St7789::SetVdv() {
void St7789::DisplayOff() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOff));
- nrf_delay_ms(500);
-}
-
-void St7789::VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
- WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollDefinition));
- WriteData(topFixedLines >> 8u);
- WriteData(topFixedLines & 0x00ffu);
- WriteData(scrollLines >> 8u);
- WriteData(scrollLines & 0x00ffu);
- WriteData(bottomFixedLines >> 8u);
- WriteData(bottomFixedLines & 0x00ffu);
}
void St7789::VerticalScrollStartAddress(uint16_t line) {
verticalScrollingStartAddress = line;
WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollStartAddress));
- WriteData(line >> 8u);
- WriteData(line & 0x00ffu);
+ uint8_t args[] = {
+ static_cast<uint8_t>(line >> 8), // Frame memory line pointer MSB
+ static_cast<uint8_t>(line) // Frame memory line pointer LSB
+ };
+ memcpy(verticalScrollArgs, args, sizeof(args));
+ WriteData(verticalScrollArgs, sizeof(verticalScrollArgs));
}
void St7789::Uninit() {
}
-void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) {
- if (x >= Width || y >= Height) {
- return;
- }
-
- SetAddrWindow(x, y, x + 1, y + 1);
-
- nrf_gpio_pin_set(pinDataCommand);
- WriteSpi(reinterpret_cast<const uint8_t*>(&color), 2);
-}
-
void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) {
SetAddrWindow(x, y, x + width - 1, y + height - 1);
- nrf_gpio_pin_set(pinDataCommand);
- WriteSpi(data, size);
+ WriteToRam(data, size);
}
void St7789::HardwareReset() {
- nrf_gpio_pin_clear(26);
- nrf_delay_ms(10);
- nrf_gpio_pin_set(26);
+ nrf_gpio_pin_clear(pinReset);
+ vTaskDelay(pdMS_TO_TICKS(1));
+ nrf_gpio_pin_set(pinReset);
+ // If hardware reset started while sleep out, reset time may be up to 120ms
+ // Unconditionally wait as hardware reset doesn't need to be performant
+ sleepIn = true;
+ lastSleepExit = xTaskGetTickCount();
+ vTaskDelay(pdMS_TO_TICKS(125));
+}
+
+void St7789::LowPowerOn() {
+ IdleModeOn();
+ IdleFrameRateOn();
+ NRF_LOG_INFO("[LCD] Low power mode");
+}
+
+void St7789::LowPowerOff() {
+ IdleModeOff();
+ IdleFrameRateOff();
+ NRF_LOG_INFO("[LCD] Normal power mode");
}
void St7789::Sleep() {
diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h
index 8a1bdfca..9c778905 100644
--- a/src/drivers/St7789.h
+++ b/src/drivers/St7789.h
@@ -1,6 +1,9 @@
#pragma once
#include <cstddef>
#include <cstdint>
+#include <functional>
+
+#include <FreeRTOS.h>
namespace Pinetime {
namespace Drivers {
@@ -8,7 +11,7 @@ namespace Pinetime {
class St7789 {
public:
- explicit St7789(Spi& spi, uint8_t pinDataCommand);
+ explicit St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset);
St7789(const St7789&) = delete;
St7789& operator=(const St7789&) = delete;
St7789(St7789&&) = delete;
@@ -16,37 +19,51 @@ namespace Pinetime {
void Init();
void Uninit();
- void DrawPixel(uint16_t x, uint16_t y, uint32_t color);
- void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void VerticalScrollStartAddress(uint16_t line);
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
+ void LowPowerOn();
+ void LowPowerOff();
void Sleep();
void Wakeup();
private:
Spi& spi;
uint8_t pinDataCommand;
+ uint8_t pinReset;
uint8_t verticalScrollingStartAddress = 0;
+ bool sleepIn;
+ TickType_t lastSleepExit;
void HardwareReset();
void SoftwareReset();
+ void Command2Enable();
void SleepOut();
+ void EnsureSleepOutPostDelay();
void SleepIn();
- void ColMod();
+ void PixelFormat();
void MemoryDataAccessControl();
void DisplayInversionOn();
void NormalModeOn();
- void WriteToRam();
+ void WriteToRam(const uint8_t* data, size_t size);
+ void IdleModeOn();
+ void IdleModeOff();
+ void FrameRateNormalSet();
+ void IdleFrameRateOff();
+ void IdleFrameRateOn();
void DisplayOn();
void DisplayOff();
+ void PowerControl();
+ void GateControl();
+ void PorchSet();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv();
void WriteCommand(uint8_t cmd);
- void WriteSpi(const uint8_t* data, size_t size);
+ void WriteCommand(const uint8_t* data, size_t size);
+ void WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
enum class Commands : uint8_t {
SoftwareReset = 0x01,
@@ -62,15 +79,26 @@ namespace Pinetime {
MemoryDataAccessControl = 0x36,
VerticalScrollDefinition = 0x33,
VerticalScrollStartAddress = 0x37,
- ColMod = 0x3a,
+ IdleModeOff = 0x38,
+ IdleModeOn = 0x39,
+ PixelFormat = 0x3a,
+ FrameRateIdle = 0xb3,
+ FrameRateNormal = 0xc6,
VdvSet = 0xc4,
+ Command2Enable = 0xdf,
+ PowerControl1 = 0xd0,
+ PowerControl2 = 0xe8,
+ GateControl = 0xb7,
+ Porch = 0xb2,
};
void WriteData(uint8_t data);
- void ColumnAddressSet();
+ void WriteData(const uint8_t* data, size_t size);
static constexpr uint16_t Width = 240;
static constexpr uint16_t Height = 320;
- void RowAddressSet();
+
+ uint8_t addrWindowArgs[4];
+ uint8_t verticalScrollArgs[2];
};
}
}
diff --git a/src/drivers/Watchdog.cpp b/src/drivers/Watchdog.cpp
index d0907a65..eeeb6cfd 100644
--- a/src/drivers/Watchdog.cpp
+++ b/src/drivers/Watchdog.cpp
@@ -2,74 +2,148 @@
#include <mdk/nrf.h>
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<uint32_t>(sleepBehaviour) | static_cast<uint32_t>(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 = 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;
-void Watchdog::Start() {
- NRF_WDT->TASKS_START = 1;
+ 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::Kick() {
- NRF_WDT->RR[0] = WDT_RR_RR_Reload;
+void Watchdog::Setup(uint8_t timeoutSeconds, SleepBehaviour sleepBehaviour, HaltBehaviour haltBehaviour) {
+ SetBehaviours(sleepBehaviour, haltBehaviour);
+ SetTimeout(timeoutSeconds);
+ EnableFirstReloadRegister();
+
+ resetReason = ::GetResetReason();
}
-Watchdog::ResetReasons Watchdog::ActualResetReason() const {
- uint32_t reason = NRF_POWER->RESETREAS;
- NRF_POWER->RESETREAS = 0xffffffff;
+void Watchdog::Start() {
+ // Write 1 in the START task to start the watchdog
+ NRF_WDT->TASKS_START = 1;
+}
- 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;
+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;
}
-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 <cstdint>
+#include <nrf52_bitfields.h>
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);
+ /// Indicates the reasons of a reset of the MCU
+ enum class ResetReason { ResetPin, Watchdog, SoftReset, CpuLockup, SystemOff, LpComp, DebugInterface, NFC, HardReset };
+
+ /// 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();
- void Kick();
- ResetReasons ResetReason() const {
+ /// 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);
}
}