diff --git a/README.md b/README.md index e4f6707f..d3d1130f 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devic - [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md) - [Updating the software](doc/gettingStarted/updating-software.md) - [About the firmware and bootloader](doc/gettingStarted/about-software.md) -- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle) - - [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather) +- [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle) + - [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/) ### Companion apps diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index 146152f8..504cad14 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -80,7 +80,7 @@ int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble return static_cast(arg)->OnCommand(ctxt); } -SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) { +SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) { } void SimpleWeatherService::Init() { diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 4bbefcfc..03d2f6ff 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -40,7 +40,7 @@ namespace Pinetime { class SimpleWeatherService { public: - explicit SimpleWeatherService(const DateTime& dateTimeController); + explicit SimpleWeatherService(DateTime& dateTimeController); void Init(); @@ -140,7 +140,7 @@ namespace Pinetime { uint16_t eventHandle {}; - const Pinetime::Controllers::DateTime& dateTimeController; + Pinetime::Controllers::DateTime& dateTimeController; std::optional currentWeather; std::optional forecast; diff --git a/src/components/brightness/BrightnessController.cpp b/src/components/brightness/BrightnessController.cpp index 0392158c..4d1eba6a 100644 --- a/src/components/brightness/BrightnessController.cpp +++ b/src/components/brightness/BrightnessController.cpp @@ -2,38 +2,138 @@ #include #include "displayapp/screens/Symbols.h" #include "drivers/PinMap.h" +#include using namespace Pinetime::Controllers; +namespace { + // reinterpret_cast is not constexpr so this is the best we can do + static NRF_RTC_Type* const RTC = reinterpret_cast(NRF_RTC2_BASE); +} + void BrightnessController::Init() { nrf_gpio_cfg_output(PinMap::LcdBacklightLow); nrf_gpio_cfg_output(PinMap::LcdBacklightMedium); nrf_gpio_cfg_output(PinMap::LcdBacklightHigh); + + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); + nrf_gpio_pin_clear(PinMap::LcdBacklightHigh); + + static_assert(timerFrequency == 32768, "Change the prescaler below"); + RTC->PRESCALER = 0; + // CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0 + RTC->CC[1] = timerPeriod; + // Enable compare events for CC0,CC1 + RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000; + // Disable all interrupts + RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011; Set(level); } +void BrightnessController::ApplyBrightness(uint16_t rawBrightness) { + // The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3} + // These brightness levels do not use PWM: they only set/clear the corresponding pins + // Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up + // E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin + // Note: Raw brightness does not necessarily correspond to a linear perceived brightness + + uint8_t pin; + if (rawBrightness > 2 * timerPeriod) { + rawBrightness -= 2 * timerPeriod; + pin = PinMap::LcdBacklightHigh; + } else if (rawBrightness > timerPeriod) { + rawBrightness -= timerPeriod; + pin = PinMap::LcdBacklightMedium; + } else { + pin = PinMap::LcdBacklightLow; + } + if (rawBrightness == timerPeriod || rawBrightness == 0) { + if (lastPin != UNSET) { + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + nrf_ppi_channel_disable(ppiBacklightOff); + nrf_ppi_channel_disable(ppiBacklightOn); + nrfx_gpiote_out_uninit(lastPin); + nrf_gpio_cfg_output(lastPin); + } + lastPin = UNSET; + if (rawBrightness == 0) { + nrf_gpio_pin_set(pin); + } else { + nrf_gpio_pin_clear(pin); + } + } else { + // If the pin on which we are doing PWM is changing + // Disable old PWM channel (if exists) and set up new one + if (lastPin != pin) { + if (lastPin != UNSET) { + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + nrf_ppi_channel_disable(ppiBacklightOff); + nrf_ppi_channel_disable(ppiBacklightOn); + nrfx_gpiote_out_uninit(lastPin); + nrf_gpio_cfg_output(lastPin); + } + nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE, + .init_state = NRF_GPIOTE_INITIAL_VALUE_LOW, + .task_pin = true}; + APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg)); + nrfx_gpiote_out_task_enable(pin); + nrf_ppi_channel_endpoint_setup(ppiBacklightOff, + reinterpret_cast(&RTC->EVENTS_COMPARE[0]), + nrfx_gpiote_out_task_addr_get(pin)); + nrf_ppi_channel_endpoint_setup(ppiBacklightOn, + reinterpret_cast(&RTC->EVENTS_COMPARE[1]), + nrfx_gpiote_out_task_addr_get(pin)); + nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast(&RTC->TASKS_CLEAR)); + nrf_ppi_channel_enable(ppiBacklightOff); + nrf_ppi_channel_enable(ppiBacklightOn); + } else { + // If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low) + RTC->TASKS_STOP = 1; + nrf_delay_us(rtcStopTime); + // Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state + nrfx_gpiote_out_task_force(pin, false); + } + // CC0 switches the backlight off (pin transitions from low to high) + RTC->CC[0] = rawBrightness; + RTC->TASKS_CLEAR = 1; + RTC->TASKS_START = 1; + lastPin = pin; + } + switch (pin) { + case PinMap::LcdBacklightHigh: + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); + break; + case PinMap::LcdBacklightMedium: + nrf_gpio_pin_clear(PinMap::LcdBacklightLow); + nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + break; + case PinMap::LcdBacklightLow: + nrf_gpio_pin_set(PinMap::LcdBacklightMedium); + nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + } +} + void BrightnessController::Set(BrightnessController::Levels level) { this->level = level; switch (level) { default: case Levels::High: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); - nrf_gpio_pin_clear(PinMap::LcdBacklightHigh); + ApplyBrightness(3 * timerPeriod); break; case Levels::Medium: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_clear(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(2 * timerPeriod); break; case Levels::Low: - nrf_gpio_pin_clear(PinMap::LcdBacklightLow); - nrf_gpio_pin_set(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(timerPeriod); + break; + case Levels::AlwaysOn: + ApplyBrightness(timerPeriod / 10); break; case Levels::Off: - nrf_gpio_pin_set(PinMap::LcdBacklightLow); - nrf_gpio_pin_set(PinMap::LcdBacklightMedium); - nrf_gpio_pin_set(PinMap::LcdBacklightHigh); + ApplyBrightness(0); break; } } diff --git a/src/components/brightness/BrightnessController.h b/src/components/brightness/BrightnessController.h index 7f86759a..650749a8 100644 --- a/src/components/brightness/BrightnessController.h +++ b/src/components/brightness/BrightnessController.h @@ -2,11 +2,14 @@ #include +#include "nrf_ppi.h" +#include "nrfx_gpiote.h" + namespace Pinetime { namespace Controllers { class BrightnessController { public: - enum class Levels { Off, Low, Medium, High }; + enum class Levels { Off, AlwaysOn, Low, Medium, High }; void Init(); void Set(Levels level); @@ -20,6 +23,25 @@ namespace Pinetime { private: Levels level = Levels::High; + static constexpr uint8_t UNSET = UINT8_MAX; + uint8_t lastPin = UNSET; + // Maximum time (μs) it takes for the RTC to fully stop + static constexpr uint8_t rtcStopTime = 46; + // Frequency of timer used for PWM (Hz) + static constexpr uint16_t timerFrequency = 32768; + // Backlight PWM frequency (Hz) + static constexpr uint16_t pwmFreq = 1000; + // Wraparound point in timer ticks + // Defines the number of brightness levels between each pin + static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq; + // Warning: nimble reserves some PPIs + // https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53 + // SpiMaster uses PPI 0 for an erratum workaround + // Channel 1, 2 should be free to use + static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1; + static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2; + + void ApplyBrightness(uint16_t val); }; } } diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp index f0ccb5e5..7f58c9b3 100644 --- a/src/components/datetime/DateTimeController.cpp +++ b/src/components/datetime/DateTimeController.cpp @@ -1,6 +1,8 @@ #include "components/datetime/DateTimeController.h" #include #include +#include +#include "nrf_assert.h" using namespace Pinetime::Controllers; @@ -12,11 +14,16 @@ namespace { } DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} { + mutex = xSemaphoreCreateMutex(); + ASSERT(mutex != nullptr); + xSemaphoreGive(mutex); } void DateTime::SetCurrentTime(std::chrono::time_point t) { + xSemaphoreTake(mutex, portMAX_DELAY); this->currentDateTime = t; - UpdateTime(previousSystickCounter); // Update internal state without updating the time + UpdateTime(previousSystickCounter, true); // Update internal state without updating the time + xSemaphoreGive(mutex); } void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { @@ -29,13 +36,15 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, /* .tm_year = */ year - 1900, }; - tm.tm_isdst = -1; // Use DST value from local time zone - currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm)); - NRF_LOG_INFO("%d %d %d ", day, month, year); NRF_LOG_INFO("%d %d %d ", hour, minute, second); - UpdateTime(previousSystickCounter); + tm.tm_isdst = -1; // Use DST value from local time zone + + xSemaphoreTake(mutex, portMAX_DELAY); + currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm)); + UpdateTime(previousSystickCounter, true); + xSemaphoreGive(mutex); systemTask->PushMessage(System::Messages::OnNewTime); } @@ -45,25 +54,34 @@ void DateTime::SetTimeZone(int8_t timezone, int8_t dst) { dstOffset = dst; } -void DateTime::UpdateTime(uint32_t systickCounter) { +std::chrono::time_point DateTime::CurrentDateTime() { + xSemaphoreTake(mutex, portMAX_DELAY); + UpdateTime(nrf_rtc_counter_get(portNRF_RTC_REG), false); + xSemaphoreGive(mutex); + return currentDateTime; +} + +void DateTime::UpdateTime(uint32_t systickCounter, bool forceUpdate) { // Handle systick counter overflow uint32_t systickDelta = 0; if (systickCounter < previousSystickCounter) { - systickDelta = 0xffffff - previousSystickCounter; + systickDelta = static_cast(portNRF_RTC_MAXTICKS) - previousSystickCounter; systickDelta += systickCounter + 1; } else { systickDelta = systickCounter - previousSystickCounter; } - /* - * 1000 ms = 1024 ticks - */ - auto correctedDelta = systickDelta / 1024; - auto rest = systickDelta % 1024; + auto correctedDelta = systickDelta / configTICK_RATE_HZ; + // If a second hasn't passed, there is nothing to do + // If the time has been changed, set forceUpdate to trigger internal state updates + if (correctedDelta == 0 && !forceUpdate) { + return; + } + auto rest = systickDelta % configTICK_RATE_HZ; if (systickCounter >= rest) { previousSystickCounter = systickCounter - rest; } else { - previousSystickCounter = 0xffffff - (rest - systickCounter); + previousSystickCounter = static_cast(portNRF_RTC_MAXTICKS) - (rest - systickCounter - 1); } currentDateTime += std::chrono::seconds(correctedDelta); diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h index f719df7d..5a453f20 100644 --- a/src/components/datetime/DateTimeController.h +++ b/src/components/datetime/DateTimeController.h @@ -5,6 +5,8 @@ #include #include #include "components/settings/Settings.h" +#include +#include namespace Pinetime { namespace System { @@ -45,8 +47,6 @@ namespace Pinetime { */ void SetTimeZone(int8_t timezone, int8_t dst); - void UpdateTime(uint32_t systickCounter); - uint16_t Year() const { return 1900 + localTime.tm_year; } @@ -124,12 +124,10 @@ namespace Pinetime { static const char* MonthShortToStringLow(Months month); static const char* DayOfWeekShortToStringLow(Days day); - std::chrono::time_point CurrentDateTime() const { - return currentDateTime; - } + std::chrono::time_point CurrentDateTime(); - std::chrono::time_point UTCDateTime() const { - return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); + std::chrono::time_point UTCDateTime() { + return CurrentDateTime() - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60); } std::chrono::seconds Uptime() const { @@ -141,10 +139,14 @@ namespace Pinetime { std::string FormattedTime(); private: + void UpdateTime(uint32_t systickCounter, bool forceUpdate); + std::tm localTime; int8_t tzOffset = 0; int8_t dstOffset = 0; + SemaphoreHandle_t mutex = nullptr; + uint32_t previousSystickCounter = 0; std::chrono::time_point currentDateTime; std::chrono::seconds uptime {0}; diff --git a/src/components/datetime/TODO.md b/src/components/datetime/TODO.md new file mode 100644 index 00000000..e9590898 --- /dev/null +++ b/src/components/datetime/TODO.md @@ -0,0 +1,41 @@ +# Refactoring needed + +## Context + +The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) highlighted some +limitations in the design of DateTimeController: the granularity of the time returned by `DateTime::CurrentDateTime()` +is limited by the frequency at which SystemTask calls `DateTime::UpdateTime()`, which is currently set to 100ms. + +@mark9064 provided more details +in [this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041#issuecomment-2048528967). + +The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) provided some changes +to `DateTime` controller that improves the granularity of the time returned by `DateTime::CurrentDateTime()`. + +However, the review showed that `DateTime` cannot be `const` anymore, even when it's only used to get the current time, +since `DateTime::CurrentDateTime()` changes the internal state of the instance. + +We tried to identify alternative implementation that would have maintained the "const correctness" but we eventually +figured that this would lead to a re-design of `DateTime` which was out of scope of the initial PR (Continuous time +updates and always on display). + +So we decided to merge this PR #2041 and agree to fix/improve `DateTime` later on. + +## What needs to be done? + +Improve/redesign `DateTime` so that it + +* provides a very granular (ideally down to the millisecond) date and time via `CurrentDateTime()`. +* can be declared/passed as `const` when it's only used to **get** the time. +* limits the use of mutex as much as possible (an ideal implementation would not use any mutex, but this might not be + possible). +* improves the design of `DateTime::Seconds()`, `DateTime::Minutes()`, `DateTime::Hours()`, etc as + explained [in this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054#pullrequestreview-2037033105). + +Once this redesign is implemented, all instances/references to `DateTime` should be reviewed and updated to use `const` +where appropriate. + +Please check the following PR to get more context about this redesign: + +* [#2041 - Continuous time updates by @mark9064](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) +* [#2054 - Continuous time update - Alternative implementation to #2041 by @JF002](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054) \ No newline at end of file diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 06312077..602de3a5 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -214,6 +214,21 @@ namespace Pinetime { return settings.screenTimeOut; }; + bool GetAlwaysOnDisplay() const { + return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep; + }; + + void SetAlwaysOnDisplaySetting(bool state) { + if (state != settings.alwaysOnDisplay) { + settingsChanged = true; + } + settings.alwaysOnDisplay = state; + } + + bool GetAlwaysOnDisplaySetting() const { + return settings.alwaysOnDisplay; + } + void SetShakeThreshold(uint16_t thresh) { if (settings.shakeWakeThreshold != thresh) { settings.shakeWakeThreshold = thresh; @@ -286,13 +301,15 @@ namespace Pinetime { private: Pinetime::Controllers::FS& fs; - static constexpr uint32_t settingsVersion = 0x0007; + static constexpr uint32_t settingsVersion = 0x0008; struct SettingsData { uint32_t version = settingsVersion; uint32_t stepsGoal = 10000; uint32_t screenTimeOut = 15000; + bool alwaysOnDisplay = false; + ClockType clockType = ClockType::H24; WeatherFormat weatherFormat = WeatherFormat::Metric; Notification notificationStatus = Notification::On; diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 3fd34b3a..394a3239 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -81,7 +81,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem) + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash) : lcd {lcd}, touchPanel {touchPanel}, batteryController {batteryController}, @@ -97,6 +98,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, brightnessController {brightnessController}, touchHandler {touchHandler}, filesystem {filesystem}, + spiNorFlash {spiNorFlash}, lvgl {lcd, filesystem}, timer(this, TimerCallback), controllers {batteryController, @@ -154,6 +156,36 @@ void DisplayApp::InitHw() { lcd.Init(); } +TickType_t DisplayApp::CalculateSleepTime() { + TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime; + // Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached + TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8); + // ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max + // in this case around 9 hours + constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod); + + // Assumptions + + // Tick rate is multiple of 8 + // Needed for division trick above + static_assert(configTICK_RATE_HZ % 8 == 0); + + // Local tick count must always wraparound before the system tick count does + // As a static assert we can use 64 bit ints and therefore dodge overflows + // Always on overflow time (ms) < system tick overflow time (ms) + static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ); + + if (alwaysOnTickCount == overflowTick) { + alwaysOnTickCount = 0; + alwaysOnStartTime = xTaskGetTickCount(); + } + if (elapsedTarget > ticksElapsed) { + return elapsedTarget - ticksElapsed; + } else { + return 0; + } +} + void DisplayApp::Refresh() { auto LoadPreviousScreen = [this]() { FullRefreshDirections returnDirection; @@ -203,7 +235,29 @@ void DisplayApp::Refresh() { TickType_t queueTimeout; switch (state) { case States::Idle: - queueTimeout = portMAX_DELAY; + if (settingsController.GetAlwaysOnDisplay()) { + if (!currentScreen->IsRunning()) { + LoadPreviousScreen(); + } + // Check we've slept long enough + // Might not be true if the loop received an event + // If not true, then wait that amount of time + queueTimeout = CalculateSleepTime(); + if (queueTimeout == 0) { + // Only advance the tick count when LVGL is done + // Otherwise keep running the task handler while it still has things to draw + // Note: under high graphics load, LVGL will always have more work to do + if (lv_task_handler() > 0) { + // Drop frames that we've missed if drawing/event handling took way longer than expected + while (queueTimeout == 0) { + alwaysOnTickCount += 1; + queueTimeout = CalculateSleepTime(); + } + }; + } + } else { + queueTimeout = portMAX_DELAY; + } break; case States::Running: if (!currentScreen->IsRunning()) { @@ -234,20 +288,34 @@ void DisplayApp::Refresh() { case Messages::DimScreen: DimScreen(); break; - case Messages::RestoreBrightness: - RestoreBrightness(); - break; case Messages::GoToSleep: - while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) { + while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) { brightnessController.Lower(); vTaskDelay(100); } - lcd.Sleep(); + // Don't actually turn off the display for AlwaysOn mode + if (settingsController.GetAlwaysOnDisplay()) { + brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn); + lcd.LowPowerOn(); + // Record idle entry time + alwaysOnTickCount = 0; + alwaysOnStartTime = xTaskGetTickCount(); + } else { + brightnessController.Set(Controllers::BrightnessController::Levels::Off); + lcd.Sleep(); + } PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); state = States::Idle; break; + case Messages::NotifyDeviceActivity: + lv_disp_trig_activity(nullptr); + break; case Messages::GoToRunning: - lcd.Wakeup(); + if (settingsController.GetAlwaysOnDisplay()) { + lcd.LowPowerOff(); + } else { + lcd.Wakeup(); + } lv_disp_trig_activity(nullptr); ApplyBrightness(); state = States::Running; @@ -535,7 +603,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio bleController, watchdog, motionController, - touchPanel); + touchPanel, + spiNorFlash); break; case Apps::FlashLight: currentScreen = std::make_unique(*systemTask, brightnessController); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 96bce4dd..d443b8b2 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -66,7 +66,8 @@ namespace Pinetime { Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem); + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash); void Start(System::BootErrors error); void PushMessage(Display::Messages msg); @@ -96,6 +97,7 @@ namespace Pinetime { Pinetime::Controllers::BrightnessController& brightnessController; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::FS& filesystem; + Pinetime::Drivers::SpiNorFlash& spiNorFlash; Pinetime::Controllers::FirmwareValidator validator; Pinetime::Components::LittleVgl lvgl; @@ -135,6 +137,13 @@ namespace Pinetime { Utility::StaticStack appStackDirections; bool isDimmed = false; + + TickType_t CalculateSleepTime(); + TickType_t alwaysOnTickCount; + TickType_t alwaysOnStartTime; + // If this is to be changed, make sure the actual always on refresh rate is changed + // by configuring the LCD refresh timings + static constexpr uint32_t alwaysOnRefreshPeriod = 500; }; } } diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index 28892723..bcb8db0e 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Pinetime::Controllers::AlarmController& /*alarmController*/, Pinetime::Controllers::BrightnessController& /*brightnessController*/, Pinetime::Controllers::TouchHandler& /*touchHandler*/, - Pinetime::Controllers::FS& /*filesystem*/) + Pinetime::Controllers::FS& /*filesystem*/, + Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/) : lcd {lcd}, bleController {bleController} { } diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index c1bf6243..162ff257 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -18,6 +18,7 @@ namespace Pinetime { class St7789; class Cst816S; class Watchdog; + class SpiNorFlash; } namespace Controllers { @@ -59,7 +60,8 @@ namespace Pinetime { Pinetime::Controllers::AlarmController& alarmController, Pinetime::Controllers::BrightnessController& brightnessController, Pinetime::Controllers::TouchHandler& touchHandler, - Pinetime::Controllers::FS& filesystem); + Pinetime::Controllers::FS& filesystem, + Pinetime::Drivers::SpiNorFlash& spiNorFlash); void Start(); void Start(Pinetime::System::BootErrors) { diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index dada3088..1418f6be 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -18,7 +18,7 @@ namespace Pinetime { TimerDone, BleFirmwareUpdateStarted, DimScreen, - RestoreBrightness, + NotifyDeviceActivity, ShowPairingKey, AlarmTriggered, Chime, diff --git a/src/displayapp/screens/FlashLight.cpp b/src/displayapp/screens/FlashLight.cpp index 1b7cf39c..f169fac1 100644 --- a/src/displayapp/screens/FlashLight.cpp +++ b/src/displayapp/screens/FlashLight.cpp @@ -17,6 +17,7 @@ namespace { FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessController& brightnessController) : systemTask {systemTask}, brightnessController {brightnessController} { + previousBrightnessLevel = brightnessController.Level(); brightnessController.Set(Controllers::BrightnessController::Levels::Low); flashLight = lv_label_create(lv_scr_act(), nullptr); @@ -52,6 +53,7 @@ FlashLight::FlashLight(System::SystemTask& systemTask, Controllers::BrightnessCo FlashLight::~FlashLight() { lv_obj_clean(lv_scr_act()); lv_obj_set_style_local_bg_color(lv_scr_act(), LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK); + brightnessController.Set(previousBrightnessLevel); systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping); } diff --git a/src/displayapp/screens/FlashLight.h b/src/displayapp/screens/FlashLight.h index 2b710ed5..c5404e93 100644 --- a/src/displayapp/screens/FlashLight.h +++ b/src/displayapp/screens/FlashLight.h @@ -27,6 +27,7 @@ namespace Pinetime { Controllers::BrightnessController& brightnessController; Controllers::BrightnessController::Levels brightnessLevel = Controllers::BrightnessController::Levels::High; + Controllers::BrightnessController::Levels previousBrightnessLevel; lv_obj_t* flashLight; lv_obj_t* backgroundAction; diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp index 18fb7ad2..dd39f88a 100644 --- a/src/displayapp/screens/SystemInfo.cpp +++ b/src/displayapp/screens/SystemInfo.cpp @@ -38,7 +38,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app, const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel) + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash) : app {app}, dateTimeController {dateTimeController}, batteryController {batteryController}, @@ -47,6 +48,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app, watchdog {watchdog}, motionController {motionController}, touchPanel {touchPanel}, + spiNorFlash {spiNorFlash}, screens {app, 0, {[this]() -> std::unique_ptr { @@ -186,10 +188,12 @@ std::unique_ptr SystemInfo::CreateScreen3() { lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr); lv_label_set_recolor(label, true); const auto& bleAddr = bleController.Address(); + auto spiFlashId = spiNorFlash.GetIdentification(); lv_label_set_text_fmt(label, "#808080 BLE MAC#\n" - " %02x:%02x:%02x:%02x:%02x:%02x" + " %02x:%02x:%02x:%02x:%02x:%02x\n" "\n" + "#808080 SPI Flash# %02x-%02x-%02x\n" "\n" "#808080 Memory heap#\n" " #808080 Free# %d\n" @@ -202,6 +206,9 @@ std::unique_ptr SystemInfo::CreateScreen3() { bleAddr[2], bleAddr[1], bleAddr[0], + spiFlashId.manufacturer, + spiFlashId.type, + spiFlashId.density, xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize(), mallocFailedCount, diff --git a/src/displayapp/screens/SystemInfo.h b/src/displayapp/screens/SystemInfo.h index 199af51e..3129c463 100644 --- a/src/displayapp/screens/SystemInfo.h +++ b/src/displayapp/screens/SystemInfo.h @@ -29,7 +29,8 @@ namespace Pinetime { const Pinetime::Controllers::Ble& bleController, const Pinetime::Drivers::Watchdog& watchdog, Pinetime::Controllers::MotionController& motionController, - const Pinetime::Drivers::Cst816S& touchPanel); + const Pinetime::Drivers::Cst816S& touchPanel, + const Pinetime::Drivers::SpiNorFlash& spiNorFlash); ~SystemInfo() override; bool OnTouchEvent(TouchEvents event) override; @@ -42,6 +43,7 @@ namespace Pinetime { const Pinetime::Drivers::Watchdog& watchdog; Pinetime::Controllers::MotionController& motionController; const Pinetime::Drivers::Cst816S& touchPanel; + const Pinetime::Drivers::SpiNorFlash& spiNorFlash; ScreenList<5> screens; diff --git a/src/displayapp/screens/WatchFaceAnalog.h b/src/displayapp/screens/WatchFaceAnalog.h index 2eee657e..958ff64d 100644 --- a/src/displayapp/screens/WatchFaceAnalog.h +++ b/src/displayapp/screens/WatchFaceAnalog.h @@ -75,7 +75,7 @@ namespace Pinetime { BatteryIcon batteryIcon; - const Controllers::DateTime& dateTimeController; + Controllers::DateTime& dateTimeController; const Controllers::Battery& batteryController; const Controllers::Ble& bleController; Controllers::NotificationManager& notificationManager; diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index bd533e67..57a64d7f 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -9,10 +9,17 @@ using namespace Pinetime::Applications::Screens; namespace { - void event_handler(lv_obj_t* obj, lv_event_t event) { + void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); screen->UpdateSelected(obj, event); } + + void AlwaysOnEventHandler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + auto* screen = static_cast(obj->user_data); + screen->ToggleAlwaysOn(); + } + } } constexpr std::array SettingDisplay::options; @@ -49,13 +56,20 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000); lv_checkbox_set_text(cbOption[i], buffer); cbOption[i]->user_data = this; - lv_obj_set_event_cb(cbOption[i], event_handler); + lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler); SetRadioButtonStyle(cbOption[i]); if (settingsController.GetScreenTimeOut() == options[i]) { lv_checkbox_set_checked(cbOption[i], true); } } + + alwaysOnCheckbox = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(alwaysOnCheckbox, "Always On"); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); + lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT); + alwaysOnCheckbox->user_data = this; + lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler); } SettingDisplay::~SettingDisplay() { @@ -63,6 +77,11 @@ SettingDisplay::~SettingDisplay() { settingsController.SaveSettings(); } +void SettingDisplay::ToggleAlwaysOn() { + settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting()); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); +} + void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { if (event == LV_EVENT_CLICKED) { for (unsigned int i = 0; i < options.size(); i++) { diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index 64212c02..b6d147c8 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -18,6 +18,7 @@ namespace Pinetime { ~SettingDisplay() override; void UpdateSelected(lv_obj_t* object, lv_event_t event); + void ToggleAlwaysOn(); private: DisplayApp* app; @@ -25,6 +26,7 @@ namespace Pinetime { Controllers::Settings& settingsController; lv_obj_t* cbOption[options.size()]; + lv_obj_t* alwaysOnCheckbox; }; } } diff --git a/src/drivers/SpiNorFlash.cpp b/src/drivers/SpiNorFlash.cpp index 56a8aabd..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, @@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() { uint8_t cmd[cmdSize] = {static_cast(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03}; uint8_t id = 0; spi.Read(reinterpret_cast(&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(Commands::ReadIdentification); Identification identification; spi.Read(&cmd, 1, reinterpret_cast(&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 12e95a41..0df19b45 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -1,3 +1,4 @@ +#include #include "drivers/St7789.h" #include #include @@ -15,17 +16,22 @@ void St7789::Init() { 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(); } @@ -63,6 +69,17 @@ void St7789::SoftwareReset() { vTaskDelay(pdMS_TO_TICKS(125)); } +void St7789::Command2Enable() { + WriteCommand(static_cast(Commands::Command2Enable)); + constexpr uint8_t args[] = { + 0x5a, // Constant + 0x69, // Constant + 0x02, // Constant + 0x01, // Enable + }; + WriteData(args, sizeof(args)); +} + void St7789::SleepOut() { if (!sleepIn) { return; @@ -97,8 +114,9 @@ void St7789::SleepIn() { sleepIn = true; } -void St7789::ColMod() { - WriteCommand(static_cast(Commands::ColMod)); +void St7789::PixelFormat() { + WriteCommand(static_cast(Commands::PixelFormat)); + // 65K colours, 16-bit per pixel WriteData(0x55); } @@ -118,22 +136,6 @@ void St7789::MemoryDataAccessControl() { #endif } -void St7789::ColumnAddressSet() { - WriteCommand(static_cast(Commands::ColumnAddressSet)); - WriteData(0x00); - WriteData(0x00); - WriteData(Width >> 8u); - WriteData(Width & 0xffu); -} - -void St7789::RowAddressSet() { - WriteCommand(static_cast(Commands::RowAddressSet)); - WriteData(0x00); - WriteData(0x00); - WriteData(320u >> 8u); - WriteData(320u & 0xffu); -} - void St7789::DisplayInversionOn() { WriteCommand(static_cast(Commands::DisplayInversionOn)); } @@ -142,22 +144,98 @@ void St7789::NormalModeOn() { WriteCommand(static_cast(Commands::NormalModeOn)); } +void St7789::IdleModeOn() { + WriteCommand(static_cast(Commands::IdleModeOn)); +} + +void St7789::IdleModeOff() { + WriteCommand(static_cast(Commands::IdleModeOff)); +} + +void St7789::PorchSet() { + WriteCommand(static_cast(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(Commands::FrameRateNormal)); + // Note that the datasheet table is imprecise - see formula below table + WriteData(0x0a); +} + +void St7789::IdleFrameRateOn() { + WriteCommand(static_cast(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 + // In testing this divider appears to actually be 16x? + constexpr uint8_t args[] = { + 0x13, // Enable frame rate control for partial/idle mode, 8x frame divider + 0x1e, // Idle mode frame rate + 0x1e, // Partial mode frame rate (unused) + }; + WriteData(args, sizeof(args)); +} + +void St7789::IdleFrameRateOff() { + WriteCommand(static_cast(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(Commands::DisplayOn)); } +void St7789::PowerControl() { + WriteCommand(static_cast(Commands::PowerControl1)); + constexpr uint8_t args[] = { + 0xa4, // Constant + 0x00, // Lowest possible voltages + }; + WriteData(args, sizeof(args)); + + WriteCommand(static_cast(Commands::PowerControl2)); + // Lowest possible boost circuit clocks + WriteData(0xb3); +} + +void St7789::GateControl() { + WriteCommand(static_cast(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(Commands::ColumnAddressSet)); - WriteData(x0 >> 8); - WriteData(x0 & 0xff); - WriteData(x1 >> 8); - WriteData(x1 & 0xff); + uint8_t colArgs[] = { + static_cast(x0 >> 8), // x start MSB + static_cast(x0), // x start LSB + static_cast(x1 >> 8), // x end MSB + static_cast(x1) // x end LSB + }; + WriteData(colArgs, sizeof(colArgs)); WriteCommand(static_cast(Commands::RowAddressSet)); - WriteData(y0 >> 8); - WriteData(y0 & 0xff); - WriteData(y1 >> 8); - WriteData(y1 & 0xff); + uint8_t rowArgs[] = { + static_cast(y0 >> 8), // y start MSB + static_cast(y0), // y start LSB + static_cast(y1 >> 8), // y end MSB + static_cast(y1) // y end LSB + }; + memcpy(addrWindowArgs, rowArgs, sizeof(rowArgs)); + WriteData(addrWindowArgs, sizeof(addrWindowArgs)); } void St7789::WriteToRam(const uint8_t* data, size_t size) { @@ -179,8 +257,12 @@ void St7789::DisplayOff() { void St7789::VerticalScrollStartAddress(uint16_t line) { verticalScrollingStartAddress = line; WriteCommand(static_cast(Commands::VerticalScrollStartAddress)); - WriteData(line >> 8u); - WriteData(line & 0x00ffu); + uint8_t args[] = { + static_cast(line >> 8), // Frame memory line pointer MSB + static_cast(line) // Frame memory line pointer LSB + }; + memcpy(verticalScrollArgs, args, sizeof(args)); + WriteData(verticalScrollArgs, sizeof(verticalScrollArgs)); } void St7789::Uninit() { @@ -202,6 +284,18 @@ void St7789::HardwareReset() { 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() { SleepIn(); nrf_gpio_cfg_default(pinDataCommand); diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 45d4b56d..9c778905 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -24,6 +24,8 @@ namespace Pinetime { 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(); @@ -37,16 +39,25 @@ namespace Pinetime { void HardwareReset(); void SoftwareReset(); + void Command2Enable(); void SleepOut(); void EnsureSleepOutPostDelay(); void SleepIn(); - void ColMod(); + void PixelFormat(); void MemoryDataAccessControl(); void DisplayInversionOn(); void NormalModeOn(); 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(); @@ -68,16 +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 WriteData(const uint8_t* data, size_t size); - void ColumnAddressSet(); 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/main.cpp b/src/main.cpp index ee6a6d3d..ab50fa74 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd, alarmController, brightnessController, touchHandler, - fs); + fs, + spiNorFlash); Pinetime::System::SystemTask systemTask(spi, spiNorFlash, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index e3d40d35..211e19ec 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -102,7 +102,9 @@ void SystemTask::Work() { watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause); watchdog.Start(); NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason())); - APP_GPIOTE_INIT(2); + if (!nrfx_gpiote_is_init()) { + nrfx_gpiote_init(); + } spi.Init(); spiNorFlash.Init(); @@ -192,13 +194,16 @@ void SystemTask::Work() { if (!bleController.IsFirmwareUpdating()) { doNotGoToSleep = false; } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); break; case Messages::DisableSleeping: doNotGoToSleep = true; break; case Messages::GoToRunning: - spi.Wakeup(); + // SPI doesn't go to sleep for always on mode + if (!settingsController.GetAlwaysOnDisplay()) { + spi.Wakeup(); + } // Double Tap needs the touch screen to be in normal mode if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { @@ -240,7 +245,7 @@ void SystemTask::Work() { heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); break; case Messages::OnNewTime: - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime); if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) { alarmController.ScheduleAlarm(); @@ -251,7 +256,7 @@ void SystemTask::Work() { if (state == SystemTaskState::Sleeping) { GoToRunning(); } else { - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); } displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification); } @@ -263,7 +268,7 @@ void SystemTask::Work() { displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered); break; case Messages::BleConnected: - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); isBleDiscoveryTimerRunning = true; bleDiscoveryTimer = 5; break; @@ -323,7 +328,11 @@ void SystemTask::Work() { // if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions. spiNorFlash.Sleep(); } - spi.Sleep(); + + // Must keep SPI awake when still updating the display for always on + if (!settingsController.GetAlwaysOnDisplay()) { + spi.Sleep(); + } // Double Tap needs the touch screen to be in normal mode if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { @@ -410,8 +419,6 @@ void SystemTask::Work() { } monitor.Process(); - uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG); - dateTimeController.UpdateTime(systick_counter); NoInit_BackUpTime = dateTimeController.CurrentDateTime(); if (nrf_gpio_pin_read(PinMap::Button) == 0) { watchdog.Reload(); @@ -459,7 +466,7 @@ void SystemTask::HandleButtonAction(Controllers::ButtonActions action) { return; } - displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness); + displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity); using Actions = Controllers::ButtonActions;