From 3f08155159cf20ea19b3194b4bdc49426e005f48 Mon Sep 17 00:00:00 2001 From: KaffeinatedKat Date: Fri, 29 Sep 2023 21:00:07 -0600 Subject: [PATCH 01/15] feat: always on display --- src/components/settings/Settings.h | 15 ++++++++++++++- src/displayapp/DisplayApp.cpp | 6 +++++- .../screens/settings/SettingDisplay.cpp | 14 ++++++++++++-- src/displayapp/screens/settings/SettingDisplay.h | 2 +- src/systemtask/SystemTask.cpp | 15 +++++++++++---- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 063120774a..d75cd678ab 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -214,6 +214,17 @@ namespace Pinetime { return settings.screenTimeOut; }; + void SetAlwaysOnDisplay(bool state) { + if (state != settings.alwaysOnDisplay) { + settingsChanged = true; + } + settings.alwaysOnDisplay = state; + }; + + bool GetAlwaysOnDisplay() const { + return settings.alwaysOnDisplay; + }; + void SetShakeThreshold(uint16_t thresh) { if (settings.shakeWakeThreshold != thresh) { settings.shakeWakeThreshold = thresh; @@ -286,13 +297,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 3fd34b3a8c..c7fb62abc5 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -203,7 +203,11 @@ void DisplayApp::Refresh() { TickType_t queueTimeout; switch (state) { case States::Idle: - queueTimeout = portMAX_DELAY; + if (settingsController.GetAlwaysOnDisplay()) { + queueTimeout = lv_task_handler(); + } else { + queueTimeout = portMAX_DELAY; + } break; case States::Running: if (!currentScreen->IsRunning()) { diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index bd533e675b..760f1e9e0f 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -15,7 +15,7 @@ namespace { } } -constexpr std::array SettingDisplay::options; +constexpr std::array SettingDisplay::options; SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, settingsController {settingsController} { @@ -46,7 +46,11 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime char buffer[4]; for (unsigned int i = 0; i < options.size(); i++) { cbOption[i] = lv_checkbox_create(container1, nullptr); - snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000); + if (options[i] == 0) { + sprintf(buffer, "%s", "Always On"); + } else { + sprintf(buffer, "%2ds", options[i] / 1000); + } lv_checkbox_set_text(cbOption[i], buffer); cbOption[i]->user_data = this; lv_obj_set_event_cb(cbOption[i], event_handler); @@ -64,6 +68,12 @@ SettingDisplay::~SettingDisplay() { } void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { + if (settingsController.GetScreenTimeOut() == 0) { + settingsController.SetAlwaysOnDisplay(true); + } else { + settingsController.SetAlwaysOnDisplay(false); + } + if (event == LV_EVENT_CLICKED) { for (unsigned int i = 0; i < options.size(); i++) { if (object == cbOption[i]) { diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index 64212c0217..a43704637f 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -21,7 +21,7 @@ namespace Pinetime { private: DisplayApp* app; - static constexpr std::array options = {5000, 7000, 10000, 15000, 20000, 30000}; + static constexpr std::array options = {5000, 7000, 10000, 15000, 20000, 30000, 0}; Controllers::Settings& settingsController; lv_obj_t* cbOption[options.size()]; diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index a56c259105..fb7493aaf5 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -198,7 +198,10 @@ void SystemTask::Work() { 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)) { @@ -231,7 +234,7 @@ void SystemTask::Work() { break; } case Messages::GoToSleep: - if (doNotGoToSleep) { + if (doNotGoToSleep or settingsController.GetAlwaysOnDisplay()) { break; } state = SystemTaskState::GoingToSleep; // Already set in PushMessage() @@ -323,7 +326,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)) { @@ -503,7 +510,7 @@ void SystemTask::OnTouchEvent() { } void SystemTask::PushMessage(System::Messages msg) { - if (msg == Messages::GoToSleep && !doNotGoToSleep) { + if (msg == Messages::GoToSleep && !doNotGoToSleep && !settingsController.GetAlwaysOnDisplay()) { state = SystemTaskState::GoingToSleep; } From 5bfae219e465a78ef4cb65ed4cb63123a357425e Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:37:52 +0000 Subject: [PATCH 02/15] aod: PPI/RTC-based backlight brightness --- .../brightness/BrightnessController.cpp | 124 ++++++++++++++++-- .../brightness/BrightnessController.h | 24 +++- src/displayapp/DisplayApp.cpp | 10 +- src/systemtask/SystemTask.cpp | 4 +- 4 files changed, 146 insertions(+), 16 deletions(-) diff --git a/src/components/brightness/BrightnessController.cpp b/src/components/brightness/BrightnessController.cpp index 0392158cbc..4d1eba6ac5 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 7f86759a67..650749a83c 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/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index c7fb62abc5..5e68ef23bb 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -242,11 +242,17 @@ void DisplayApp::Refresh() { 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); + } else { + brightnessController.Set(Controllers::BrightnessController::Levels::Off); + lcd.Sleep(); + } PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); state = States::Idle; break; diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index fb7493aaf5..0dea5f9813 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(); From 301e68b059b47603943d7332d144d60c2ef54262 Mon Sep 17 00:00:00 2001 From: John Crawford Date: Sun, 1 Oct 2023 10:38:45 -0600 Subject: [PATCH 03/15] aod: integrate with display timeout --- .../screens/settings/SettingDisplay.cpp | 37 ++++++++++++------- .../screens/settings/SettingDisplay.h | 4 +- src/systemtask/SystemTask.cpp | 4 +- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index 760f1e9e0f..12d0f561f1 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -9,13 +9,20 @@ 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; +constexpr std::array SettingDisplay::options; SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, settingsController {settingsController} { @@ -46,20 +53,23 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime char buffer[4]; for (unsigned int i = 0; i < options.size(); i++) { cbOption[i] = lv_checkbox_create(container1, nullptr); - if (options[i] == 0) { - sprintf(buffer, "%s", "Always On"); - } else { - sprintf(buffer, "%2ds", options[i] / 1000); - } + 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.GetAlwaysOnDisplay()); + lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT); + alwaysOnCheckbox->user_data = this; + lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler); } SettingDisplay::~SettingDisplay() { @@ -67,13 +77,12 @@ SettingDisplay::~SettingDisplay() { settingsController.SaveSettings(); } -void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { - if (settingsController.GetScreenTimeOut() == 0) { - settingsController.SetAlwaysOnDisplay(true); - } else { - settingsController.SetAlwaysOnDisplay(false); - } +void SettingDisplay::ToggleAlwaysOn() { + settingsController.SetAlwaysOnDisplay(!settingsController.GetAlwaysOnDisplay()); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplay()); +} +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++) { if (object == cbOption[i]) { diff --git a/src/displayapp/screens/settings/SettingDisplay.h b/src/displayapp/screens/settings/SettingDisplay.h index a43704637f..b6d147c829 100644 --- a/src/displayapp/screens/settings/SettingDisplay.h +++ b/src/displayapp/screens/settings/SettingDisplay.h @@ -18,13 +18,15 @@ namespace Pinetime { ~SettingDisplay() override; void UpdateSelected(lv_obj_t* object, lv_event_t event); + void ToggleAlwaysOn(); private: DisplayApp* app; - static constexpr std::array options = {5000, 7000, 10000, 15000, 20000, 30000, 0}; + static constexpr std::array options = {5000, 7000, 10000, 15000, 20000, 30000}; Controllers::Settings& settingsController; lv_obj_t* cbOption[options.size()]; + lv_obj_t* alwaysOnCheckbox; }; } } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 0dea5f9813..5bd71c389d 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -236,7 +236,7 @@ void SystemTask::Work() { break; } case Messages::GoToSleep: - if (doNotGoToSleep or settingsController.GetAlwaysOnDisplay()) { + if (doNotGoToSleep) { break; } state = SystemTaskState::GoingToSleep; // Already set in PushMessage() @@ -512,7 +512,7 @@ void SystemTask::OnTouchEvent() { } void SystemTask::PushMessage(System::Messages msg) { - if (msg == Messages::GoToSleep && !doNotGoToSleep && !settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::GoToSleep && !doNotGoToSleep) { state = SystemTaskState::GoingToSleep; } From 765f0374695c94c1a5f547cac050480e18297137 Mon Sep 17 00:00:00 2001 From: John Crawford Date: Tue, 3 Oct 2023 18:50:36 -0600 Subject: [PATCH 04/15] aod: disable while in notification sleep --- src/components/settings/Settings.h | 39 +++++++++++++++++-- .../screens/settings/SettingDisplay.cpp | 6 +-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index d75cd678ab..1ab6709508 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -196,6 +196,14 @@ namespace Pinetime { if (status != settings.notificationStatus) { settingsChanged = true; } + // Disable always on screen while sleep mode is enabled + if (settings.alwaysOnDisplay.enabled) { + if (status == Notification::Sleep) { + settings.alwaysOnDisplay.state = false; + } else { + settings.alwaysOnDisplay.state = true; + } + } settings.notificationStatus = status; }; @@ -215,16 +223,32 @@ namespace Pinetime { }; void SetAlwaysOnDisplay(bool state) { - if (state != settings.alwaysOnDisplay) { + if (state != settings.alwaysOnDisplay.state) { settingsChanged = true; } - settings.alwaysOnDisplay = state; + settings.alwaysOnDisplay.state = state; }; bool GetAlwaysOnDisplay() const { - return settings.alwaysOnDisplay; + return settings.alwaysOnDisplay.state; }; + void SetAlwaysOnDisplaySetting(bool state) { + if (state != settings.alwaysOnDisplay.enabled) { + settingsChanged = true; + } + settings.alwaysOnDisplay.enabled = state; + + // Don't enable always on if we are currently in notification sleep + if (GetNotificationStatus() != Notification::Sleep) { + SetAlwaysOnDisplay(state); + } + } + + bool GetAlwaysOnDisplaySetting() const { + return settings.alwaysOnDisplay.enabled; + } + void SetShakeThreshold(uint16_t thresh) { if (settings.shakeWakeThreshold != thresh) { settings.shakeWakeThreshold = thresh; @@ -299,12 +323,19 @@ namespace Pinetime { static constexpr uint32_t settingsVersion = 0x0008; + // To enable disabling it during notification sleep, differentiate between + // the setting being on, and the setting being set by the user + struct alwaysOnDisplayData { + bool enabled = false; + bool state = false; + }; + struct SettingsData { uint32_t version = settingsVersion; uint32_t stepsGoal = 10000; uint32_t screenTimeOut = 15000; - bool alwaysOnDisplay = false; + alwaysOnDisplayData alwaysOnDisplay; ClockType clockType = ClockType::H24; WeatherFormat weatherFormat = WeatherFormat::Metric; diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp index 12d0f561f1..57a64d7fc7 100644 --- a/src/displayapp/screens/settings/SettingDisplay.cpp +++ b/src/displayapp/screens/settings/SettingDisplay.cpp @@ -66,7 +66,7 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime alwaysOnCheckbox = lv_checkbox_create(container1, nullptr); lv_checkbox_set_text(alwaysOnCheckbox, "Always On"); - lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplay()); + 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); @@ -78,8 +78,8 @@ SettingDisplay::~SettingDisplay() { } void SettingDisplay::ToggleAlwaysOn() { - settingsController.SetAlwaysOnDisplay(!settingsController.GetAlwaysOnDisplay()); - lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplay()); + settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting()); + lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting()); } void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) { From d99651d8662bd8468b8e1210dac56a7134522ca8 Mon Sep 17 00:00:00 2001 From: John Crawford Date: Sat, 14 Oct 2023 10:16:49 -0600 Subject: [PATCH 05/15] aod: switch to 8 colors when always on --- src/displayapp/DisplayApp.cpp | 7 ++++++- src/drivers/St7789.cpp | 18 ++++++++++++++++++ src/drivers/St7789.h | 6 ++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 5e68ef23bb..1a579cb1c7 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -249,6 +249,7 @@ void DisplayApp::Refresh() { // Don't actually turn off the display for AlwaysOn mode if (settingsController.GetAlwaysOnDisplay()) { brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn); + lcd.LowPowerOn(); } else { brightnessController.Set(Controllers::BrightnessController::Levels::Off); lcd.Sleep(); @@ -257,7 +258,11 @@ void DisplayApp::Refresh() { state = States::Idle; break; case Messages::GoToRunning: - lcd.Wakeup(); + if (settingsController.GetAlwaysOnDisplay()) { + lcd.LowPowerOff(); + } else { + lcd.Wakeup(); + } lv_disp_trig_activity(nullptr); ApplyBrightness(); state = States::Running; diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index c22f2199c3..274e2b625a 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -127,6 +127,14 @@ 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::DisplayOn() { WriteCommand(static_cast(Commands::DisplayOn)); } @@ -198,6 +206,16 @@ void St7789::HardwareReset() { vTaskDelay(pdMS_TO_TICKS(125)); } +void St7789::LowPowerOn() { + IdleModeOn(); + NRF_LOG_INFO("[LCD] Low power mode"); +} + +void St7789::LowPowerOff() { + IdleModeOff(); + 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 844e0180fd..ccc951ff7b 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(); @@ -45,6 +47,8 @@ namespace Pinetime { void DisplayInversionOn(); void NormalModeOn(); void WriteToRam(const uint8_t* data, size_t size); + void IdleModeOn(); + void IdleModeOff(); void DisplayOn(); void DisplayOff(); @@ -68,6 +72,8 @@ namespace Pinetime { MemoryDataAccessControl = 0x36, VerticalScrollDefinition = 0x33, VerticalScrollStartAddress = 0x37, + IdleModeOff = 0x38, + IdleModeOn = 0x39, PixelFormat = 0x3a, VdvSet = 0xc4, }; From 1876249589f981b11a7be7b67411d2a446da3f47 Mon Sep 17 00:00:00 2001 From: John Crawford Date: Sat, 14 Oct 2023 15:19:50 -0600 Subject: [PATCH 06/15] aod: lower refresh rate when always on --- src/drivers/St7789.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/drivers/St7789.h | 5 +++++ 2 files changed, 44 insertions(+) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 274e2b625a..035d61c9dd 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -16,6 +16,7 @@ void St7789::Init() { nrf_gpio_pin_set(pinReset); HardwareReset(); SoftwareReset(); + Command2Enable(); SleepOut(); PixelFormat(); MemoryDataAccessControl(); @@ -63,6 +64,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; @@ -135,6 +147,31 @@ void St7789::IdleModeOff() { WriteCommand(static_cast(Commands::IdleModeOff)); } +void St7789::FrameRateLow() { + WriteCommand(static_cast(Commands::FrameRate)); + // Enable frame rate control for partial/idle mode, 8x frame divider + // 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 + 0x1f, // Idle mode frame rate (lowest possible) + 0x1f, // Partial mode frame rate (lowest possible, unused) + }; + WriteData(args, sizeof(args)); +} + +void St7789::FrameRateNormal() { + WriteCommand(static_cast(Commands::FrameRate)); + constexpr uint8_t args[] = { + 0x00, // Disable frame rate control and divider + 0x0f, // Idle mode frame rate (normal) + 0x0f, // Partial mode frame rate (normal, unused) + }; + WriteData(args, sizeof(args)); +} + void St7789::DisplayOn() { WriteCommand(static_cast(Commands::DisplayOn)); } @@ -208,11 +245,13 @@ void St7789::HardwareReset() { void St7789::LowPowerOn() { IdleModeOn(); + FrameRateLow(); NRF_LOG_INFO("[LCD] Low power mode"); } void St7789::LowPowerOff() { IdleModeOff(); + FrameRateNormal(); NRF_LOG_INFO("[LCD] Normal power mode"); } diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index ccc951ff7b..e249e0b004 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -39,6 +39,7 @@ namespace Pinetime { void HardwareReset(); void SoftwareReset(); + void Command2Enable(); void SleepOut(); void EnsureSleepOutPostDelay(); void SleepIn(); @@ -49,6 +50,8 @@ namespace Pinetime { void WriteToRam(const uint8_t* data, size_t size); void IdleModeOn(); void IdleModeOff(); + void FrameRateNormal(); + void FrameRateLow(); void DisplayOn(); void DisplayOff(); @@ -75,7 +78,9 @@ namespace Pinetime { IdleModeOff = 0x38, IdleModeOn = 0x39, PixelFormat = 0x3a, + FrameRate = 0xb3, VdvSet = 0xc4, + Command2Enable = 0xdf, }; void WriteData(uint8_t data); void WriteData(const uint8_t* data, size_t size); From 4da315317f51e6562c41e9be1774b2e199b13722 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sat, 14 Oct 2023 16:02:23 -0600 Subject: [PATCH 07/15] aod: fix brightness getting stuck high --- src/displayapp/DisplayApp.cpp | 6 +++--- src/displayapp/Messages.h | 2 +- src/systemtask/SystemTask.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 1a579cb1c7..6fda99db51 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -238,9 +238,6 @@ void DisplayApp::Refresh() { case Messages::DimScreen: DimScreen(); break; - case Messages::RestoreBrightness: - RestoreBrightness(); - break; case Messages::GoToSleep: while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) { brightnessController.Lower(); @@ -257,6 +254,9 @@ void DisplayApp::Refresh() { PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); state = States::Idle; break; + case Messages::NotifyDeviceActivity: + lv_disp_trig_activity(nullptr); + break; case Messages::GoToRunning: if (settingsController.GetAlwaysOnDisplay()) { lcd.LowPowerOff(); diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index dada30888d..1418f6bea0 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/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 5bd71c389d..211e19ec81 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -194,7 +194,7 @@ 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; @@ -245,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(); @@ -256,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); } @@ -268,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; @@ -466,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; From 6c2fcea6114abde4fc0f6a14bd712d7f3f38de34 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:42:14 +0000 Subject: [PATCH 08/15] aod: fix flashlight brightness restore --- src/displayapp/screens/FlashLight.cpp | 2 ++ src/displayapp/screens/FlashLight.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/displayapp/screens/FlashLight.cpp b/src/displayapp/screens/FlashLight.cpp index 1b7cf39ce7..f169fac121 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 2b710ed595..c5404e93f9 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; From 259c171cae2e59d8ae8161f6120655340e323577 Mon Sep 17 00:00:00 2001 From: John Crawford Date: Tue, 17 Oct 2023 08:19:01 -0600 Subject: [PATCH 09/15] aod: lower voltage going to the display --- src/drivers/St7789.cpp | 14 ++++++++++++++ src/drivers/St7789.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 035d61c9dd..48b65acbd2 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -27,6 +27,7 @@ void St7789::Init() { #endif NormalModeOn(); SetVdv(); + PowerControl(); DisplayOn(); } @@ -176,6 +177,19 @@ 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::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { WriteCommand(static_cast(Commands::ColumnAddressSet)); uint8_t colArgs[] = { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index e249e0b004..68e9f05827 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -54,6 +54,7 @@ namespace Pinetime { void FrameRateLow(); void DisplayOn(); void DisplayOff(); + void PowerControl(); void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); @@ -81,6 +82,8 @@ namespace Pinetime { FrameRate = 0xb3, VdvSet = 0xc4, Command2Enable = 0xdf, + PowerControl1 = 0xd0, + PowerControl2 = 0xe8, }; void WriteData(uint8_t data); void WriteData(const uint8_t* data, size_t size); From 40f98d547bc7ddd513f8a2576333d4bba92873bc Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Mon, 23 Oct 2023 22:22:46 +0100 Subject: [PATCH 10/15] aod: lower lcd voltage --- src/drivers/St7789.cpp | 7 +++++++ src/drivers/St7789.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 48b65acbd2..cdfa6a3467 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -28,6 +28,7 @@ void St7789::Init() { NormalModeOn(); SetVdv(); PowerControl(); + GateControl(); DisplayOn(); } @@ -190,6 +191,12 @@ void St7789::PowerControl() { 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)); uint8_t colArgs[] = { diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 68e9f05827..96d16b93c1 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -55,6 +55,7 @@ namespace Pinetime { void DisplayOn(); void DisplayOff(); void PowerControl(); + void GateControl(); void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void SetVdv(); @@ -84,6 +85,7 @@ namespace Pinetime { Command2Enable = 0xdf, PowerControl1 = 0xd0, PowerControl2 = 0xe8, + GateControl = 0xb7, }; void WriteData(uint8_t data); void WriteData(const uint8_t* data, size_t size); From 8051ae87b6befee4a33bb9603e55df7fd1a296aa Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:05:41 +0000 Subject: [PATCH 11/15] aod: porch control: 2Hz idle + 75Hz on --- src/drivers/St7789.cpp | 42 +++++++++++++++++++++++++++++++----------- src/drivers/St7789.h | 10 +++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index cdfa6a3467..0df19b452e 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -25,6 +25,9 @@ void St7789::Init() { #ifndef DRIVER_DISPLAY_MIRROR DisplayInversionOn(); #endif + PorchSet(); + FrameRateNormalSet(); + IdleFrameRateOff(); NormalModeOn(); SetVdv(); PowerControl(); @@ -149,27 +152,44 @@ void St7789::IdleModeOff() { WriteCommand(static_cast(Commands::IdleModeOff)); } -void St7789::FrameRateLow() { - WriteCommand(static_cast(Commands::FrameRate)); - // Enable frame rate control for partial/idle mode, 8x frame divider +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 - 0x1f, // Idle mode frame rate (lowest possible) - 0x1f, // Partial mode frame rate (lowest possible, unused) + 0x1e, // Idle mode frame rate + 0x1e, // Partial mode frame rate (unused) }; WriteData(args, sizeof(args)); } -void St7789::FrameRateNormal() { - WriteCommand(static_cast(Commands::FrameRate)); +void St7789::IdleFrameRateOff() { + WriteCommand(static_cast(Commands::FrameRateIdle)); constexpr uint8_t args[] = { 0x00, // Disable frame rate control and divider - 0x0f, // Idle mode frame rate (normal) - 0x0f, // Partial mode frame rate (normal, unused) + 0x0a, // Idle mode frame rate (normal) + 0x0a, // Partial mode frame rate (normal, unused) }; WriteData(args, sizeof(args)); } @@ -266,13 +286,13 @@ void St7789::HardwareReset() { void St7789::LowPowerOn() { IdleModeOn(); - FrameRateLow(); + IdleFrameRateOn(); NRF_LOG_INFO("[LCD] Low power mode"); } void St7789::LowPowerOff() { IdleModeOff(); - FrameRateNormal(); + IdleFrameRateOff(); NRF_LOG_INFO("[LCD] Normal power mode"); } diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h index 96d16b93c1..9c778905c0 100644 --- a/src/drivers/St7789.h +++ b/src/drivers/St7789.h @@ -50,12 +50,14 @@ namespace Pinetime { void WriteToRam(const uint8_t* data, size_t size); void IdleModeOn(); void IdleModeOff(); - void FrameRateNormal(); - void FrameRateLow(); + 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(); @@ -80,12 +82,14 @@ namespace Pinetime { IdleModeOff = 0x38, IdleModeOn = 0x39, PixelFormat = 0x3a, - FrameRate = 0xb3, + 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); From 504fd051ad1afd7eae09b048a98db7e781941ca5 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:28:53 +0000 Subject: [PATCH 12/15] aod: constant frequency idle frames --- src/displayapp/DisplayApp.cpp | 49 ++++++++++++++++++++++++++++++++++- src/displayapp/DisplayApp.h | 7 +++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 6fda99db51..88ce085fd7 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -154,6 +154,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; @@ -204,7 +234,21 @@ void DisplayApp::Refresh() { switch (state) { case States::Idle: if (settingsController.GetAlwaysOnDisplay()) { - queueTimeout = lv_task_handler(); + 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) { + lv_task_handler(); + // Drop frames that we've missed if the loop took way longer than expected to execute + while (queueTimeout == 0) { + alwaysOnTickCount += 1; + queueTimeout = CalculateSleepTime(); + } + } } else { queueTimeout = portMAX_DELAY; } @@ -247,6 +291,9 @@ void DisplayApp::Refresh() { 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(); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 96bce4dd1d..356e490fe4 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -135,6 +135,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; }; } } From 2710237e8c668a655f6270d6e34998c4422de21b Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:21:13 +0100 Subject: [PATCH 13/15] aod: run LVGL task handler until all work finished --- src/displayapp/DisplayApp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 88ce085fd7..bc9a57bc77 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -242,7 +242,9 @@ void DisplayApp::Refresh() { // If not true, then wait that amount of time queueTimeout = CalculateSleepTime(); if (queueTimeout == 0) { - lv_task_handler(); + // Keep running the task handler if it still has things to draw + while (!lv_task_handler()) { + }; // Drop frames that we've missed if the loop took way longer than expected to execute while (queueTimeout == 0) { alwaysOnTickCount += 1; From 2b1f194f3e1707eb6966bcf2da4b81fbbe70730a Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Tue, 23 Apr 2024 00:16:19 +0100 Subject: [PATCH 14/15] aod: avoid spinning DisplayApp under high LVGL load --- src/displayapp/DisplayApp.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index bc9a57bc77..f5a92117c2 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -242,14 +242,16 @@ void DisplayApp::Refresh() { // If not true, then wait that amount of time queueTimeout = CalculateSleepTime(); if (queueTimeout == 0) { - // Keep running the task handler if it still has things to draw - while (!lv_task_handler()) { + // 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(); + } }; - // Drop frames that we've missed if the loop took way longer than expected to execute - while (queueTimeout == 0) { - alwaysOnTickCount += 1; - queueTimeout = CalculateSleepTime(); - } } } else { queueTimeout = portMAX_DELAY; From cc1d4fe4ccd90b91b8a3865cd81857f1dcbfbb52 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:52:35 +0100 Subject: [PATCH 15/15] aod: simplify AOD disablement based on notification status --- src/components/settings/Settings.h | 37 ++++-------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 1ab6709508..602de3a585 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -196,14 +196,6 @@ namespace Pinetime { if (status != settings.notificationStatus) { settingsChanged = true; } - // Disable always on screen while sleep mode is enabled - if (settings.alwaysOnDisplay.enabled) { - if (status == Notification::Sleep) { - settings.alwaysOnDisplay.state = false; - } else { - settings.alwaysOnDisplay.state = true; - } - } settings.notificationStatus = status; }; @@ -222,31 +214,19 @@ namespace Pinetime { return settings.screenTimeOut; }; - void SetAlwaysOnDisplay(bool state) { - if (state != settings.alwaysOnDisplay.state) { - settingsChanged = true; - } - settings.alwaysOnDisplay.state = state; - }; - bool GetAlwaysOnDisplay() const { - return settings.alwaysOnDisplay.state; + return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep; }; void SetAlwaysOnDisplaySetting(bool state) { - if (state != settings.alwaysOnDisplay.enabled) { + if (state != settings.alwaysOnDisplay) { settingsChanged = true; } - settings.alwaysOnDisplay.enabled = state; - - // Don't enable always on if we are currently in notification sleep - if (GetNotificationStatus() != Notification::Sleep) { - SetAlwaysOnDisplay(state); - } + settings.alwaysOnDisplay = state; } bool GetAlwaysOnDisplaySetting() const { - return settings.alwaysOnDisplay.enabled; + return settings.alwaysOnDisplay; } void SetShakeThreshold(uint16_t thresh) { @@ -323,19 +303,12 @@ namespace Pinetime { static constexpr uint32_t settingsVersion = 0x0008; - // To enable disabling it during notification sleep, differentiate between - // the setting being on, and the setting being set by the user - struct alwaysOnDisplayData { - bool enabled = false; - bool state = false; - }; - struct SettingsData { uint32_t version = settingsVersion; uint32_t stepsGoal = 10000; uint32_t screenTimeOut = 15000; - alwaysOnDisplayData alwaysOnDisplay; + bool alwaysOnDisplay = false; ClockType clockType = ClockType::H24; WeatherFormat weatherFormat = WeatherFormat::Metric;