diff options
Diffstat (limited to 'src/drivers')
| -rw-r--r-- | src/drivers/Bma421.cpp | 29 | ||||
| -rw-r--r-- | src/drivers/Bma421.h | 1 | ||||
| -rw-r--r-- | src/drivers/Hrs3300.cpp | 90 | ||||
| -rw-r--r-- | src/drivers/Hrs3300.h | 10 | ||||
| -rw-r--r-- | src/drivers/PinMap.h | 1 | ||||
| -rw-r--r-- | src/drivers/Spi.cpp | 7 | ||||
| -rw-r--r-- | src/drivers/Spi.h | 3 | ||||
| -rw-r--r-- | src/drivers/SpiMaster.cpp | 96 | ||||
| -rw-r--r-- | src/drivers/SpiMaster.h | 12 | ||||
| -rw-r--r-- | src/drivers/SpiNorFlash.cpp | 12 | ||||
| -rw-r--r-- | src/drivers/SpiNorFlash.h | 5 | ||||
| -rw-r--r-- | src/drivers/St7789.cpp | 267 | ||||
| -rw-r--r-- | src/drivers/St7789.h | 46 | ||||
| -rw-r--r-- | src/drivers/Watchdog.cpp | 168 | ||||
| -rw-r--r-- | src/drivers/Watchdog.h | 60 |
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); } } |
