From aac929654b71b6278927f6e8e638f6bfdd35db6d Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:59:25 +0100 Subject: [PATCH 1/5] 8hz idle --- src/drivers/St7789.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp index 0df19b45..482fbad6 100644 --- a/src/drivers/St7789.cpp +++ b/src/drivers/St7789.cpp @@ -175,9 +175,8 @@ void St7789::IdleFrameRateOn() { // 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 + 0x12, // Enable frame rate control for partial/idle mode, 4x frame divider 0x1e, // Idle mode frame rate 0x1e, // Partial mode frame rate (unused) }; From 839960de1fedd6997efda9817e6007eab648072f Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 25 Aug 2024 22:57:24 +0100 Subject: [PATCH 2/5] Refactor into defined states --- src/displayapp/DisplayApp.cpp | 55 ++++++++++++++++++----------------- src/displayapp/DisplayApp.h | 2 +- src/displayapp/Messages.h | 1 + src/systemtask/Messages.h | 1 + src/systemtask/SystemTask.cpp | 29 ++++++++++++------ src/systemtask/SystemTask.h | 2 +- 6 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 076b4f8a..544970da 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -220,28 +220,27 @@ void DisplayApp::Refresh() { TickType_t queueTimeout; switch (state) { case States::Idle: - if (settingsController.GetAlwaysOnDisplay()) { - if (!currentScreen->IsRunning()) { - LoadPreviousScreen(); + queueTimeout = portMAX_DELAY; + break; + case States::AOD: + 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(); + } } - // 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: @@ -284,6 +283,7 @@ void DisplayApp::Refresh() { if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) { switch (msg) { case Messages::GoToSleep: + case Messages::GoToAOD: if (state != States::Running) { break; } @@ -292,7 +292,7 @@ void DisplayApp::Refresh() { vTaskDelay(100); } // Turn brightness down (or set to AlwaysOn mode) - if (settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::GoToAOD) { brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn); } else { brightnessController.Set(Controllers::BrightnessController::Levels::Off); @@ -305,17 +305,18 @@ void DisplayApp::Refresh() { while (!lv_task_handler()) { }; } - // Turn LCD display off (or set to low power for AlwaysOn mode) - if (settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::GoToAOD) { lcd.LowPowerOn(); // Record idle entry time alwaysOnTickCount = 0; alwaysOnStartTime = xTaskGetTickCount(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD); + state = States::AOD; } else { lcd.Sleep(); + PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); + state = States::Idle; } - PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping); - state = States::Idle; break; case Messages::NotifyDeviceActivity: lv_disp_trig_activity(nullptr); @@ -324,7 +325,7 @@ void DisplayApp::Refresh() { if (state == States::Running) { break; } - if (settingsController.GetAlwaysOnDisplay()) { + if (state == States::AOD) { lcd.LowPowerOff(); } else { lcd.Wakeup(); diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index d443b8b2..73492808 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -49,7 +49,7 @@ namespace Pinetime { namespace Applications { class DisplayApp { public: - enum class States { Idle, Running }; + enum class States { Idle, Running, AOD }; enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim }; DisplayApp(Drivers::St7789& lcd, diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index dcfff4c2..d2abc8e5 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -6,6 +6,7 @@ namespace Pinetime { namespace Display { enum class Messages : uint8_t { GoToSleep, + GoToAOD, GoToRunning, UpdateBleConnection, TouchEvent, diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index 81be9151..39fb4114 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -17,6 +17,7 @@ namespace Pinetime { HandleButtonEvent, HandleButtonTimerEvent, OnDisplayTaskSleeping, + OnDisplayTaskAOD, EnableSleeping, DisableSleeping, OnNewDay, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index e55c9ad8..b5f28437 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -284,9 +284,10 @@ void SystemTask::Work() { HandleButtonAction(action); } break; case Messages::OnDisplayTaskSleeping: + case Messages::OnDisplayTaskAOD: // The state was set to GoingToSleep when GoToSleep() was called // If the state is no longer GoingToSleep, we have since transitioned back to Running - // In this case absorb the OnDisplayTaskSleeping + // In this case absorb the OnDisplayTaskSleeping/AOD // as DisplayApp is about to receive GoToRunning if (state != SystemTaskState::GoingToSleep) { break; @@ -298,7 +299,7 @@ void SystemTask::Work() { } // Must keep SPI awake when still updating the display for always on - if (!settingsController.GetAlwaysOnDisplay()) { + if (msg == Messages::OnDisplayTaskSleeping) { spi.Sleep(); } @@ -307,7 +308,11 @@ void SystemTask::Work() { touchPanel.Sleep(); } - state = SystemTaskState::Sleeping; + if (msg == Messages::OnDisplayTaskSleeping) { + state = SystemTaskState::Sleeping; + } else { + state = SystemTaskState::AODSleeping; + } break; case Messages::OnNewDay: // We might be sleeping (with TWI device disabled. @@ -383,8 +388,8 @@ void SystemTask::GoToRunning() { if (state == SystemTaskState::Running) { return; } - // SPI doesn't go to sleep for always on mode - if (!settingsController.GetAlwaysOnDisplay()) { + // SPI only switched off when entering Sleeping, not AOD or GoingToSleep + if (state == SystemTaskState::Sleeping) { spi.Wakeup(); } @@ -413,16 +418,22 @@ void SystemTask::GoToSleep() { return; } NRF_LOG_INFO("[systemtask] Going to sleep"); - displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); + if (settingsController.GetAlwaysOnDisplay()) { + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToAOD); + } else { + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep); + } heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep); state = SystemTaskState::GoingToSleep; }; void SystemTask::UpdateMotion() { - if (IsSleeping() && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) || - settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) || - motionController.GetService()->IsMotionNotificationSubscribed())) { + // Only consider disabling motion updates specifically in the Sleeping state + // AOD needs motion on to show up to date step counts + if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) || + settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) || + motionController.GetService()->IsMotionNotificationSubscribed())) { return; } diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 8a4e5954..752c5611 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -52,7 +52,7 @@ namespace Pinetime { namespace System { class SystemTask { public: - enum class SystemTaskState { Sleeping, Running, GoingToSleep }; + enum class SystemTaskState { Sleeping, Running, GoingToSleep, AODSleeping }; SystemTask(Drivers::SpiMaster& spi, Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::TwiMaster& twiMaster, From 3ebc25dd4b8813af621dfb0029b19e7a2d1a9dc5 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:25:54 +0100 Subject: [PATCH 3/5] Replace rounded div macro --- src/displayapp/DisplayApp.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 544970da..50e8ec30 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -159,8 +159,11 @@ void DisplayApp::InitHw() { 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 + auto RoundedDiv = [](uint32_t a, uint32_t b) { + return ((a + (b / 2)) / b); + }; + TickType_t elapsedTarget = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8); + // RoundedDiv 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); From 7ced109181e2270e5b5f8004d67482710044deb1 Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 22 Sep 2024 00:34:34 +0100 Subject: [PATCH 4/5] Improve sleep time calculation docs --- src/displayapp/DisplayApp.cpp | 33 +++++++++++++++++++-------------- src/displayapp/DisplayApp.h | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 50e8ec30..6fcc4c30 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -157,15 +157,20 @@ void DisplayApp::InitHw() { } 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 + // Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame + // Next frame time is frame count * refresh period (ms) * tick rate + auto RoundedDiv = [](uint32_t a, uint32_t b) { return ((a + (b / 2)) / b); }; - TickType_t elapsedTarget = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8); // RoundedDiv 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); + // in this case around 9 hours (=overflow frame count / always on refresh period) + constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod); + + TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime; + // Divide both the numerator and denominator by 8 (=GCD(1000,1024)) + // to increase the number of ticks (frames) before the overflow tick is reached + TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8); // Assumptions @@ -173,17 +178,17 @@ TickType_t DisplayApp::CalculateSleepTime() { // 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 + // Frame count must always wraparound more often than the system tick count does // 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); + // Using 64bit ints here to avoid overflow + static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ); - if (alwaysOnTickCount == overflowTick) { - alwaysOnTickCount = 0; + if (alwaysOnFrameCount == overflowFrameCount) { + alwaysOnFrameCount = 0; alwaysOnStartTime = xTaskGetTickCount(); } - if (elapsedTarget > ticksElapsed) { - return elapsedTarget - ticksElapsed; + if (targetRenderTick > ticksElapsed) { + return targetRenderTick - ticksElapsed; } else { return 0; } @@ -240,7 +245,7 @@ void DisplayApp::Refresh() { 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; + alwaysOnFrameCount += 1; queueTimeout = CalculateSleepTime(); } } @@ -311,7 +316,7 @@ void DisplayApp::Refresh() { if (msg == Messages::GoToAOD) { lcd.LowPowerOn(); // Record idle entry time - alwaysOnTickCount = 0; + alwaysOnFrameCount = 0; alwaysOnStartTime = xTaskGetTickCount(); PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD); state = States::AOD; diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 73492808..2f276eaf 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -139,7 +139,7 @@ namespace Pinetime { bool isDimmed = false; TickType_t CalculateSleepTime(); - TickType_t alwaysOnTickCount; + TickType_t alwaysOnFrameCount; 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 From e0870ec430cbe885458b72c7e0414ae9da19d54f Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:37:30 +0100 Subject: [PATCH 5/5] Restrict hardware reactivation when not sleeping --- src/systemtask/SystemTask.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index b5f28437..90a1e971 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -388,17 +388,19 @@ void SystemTask::GoToRunning() { if (state == SystemTaskState::Running) { return; } - // SPI only switched off when entering Sleeping, not AOD or GoingToSleep - if (state == SystemTaskState::Sleeping) { - spi.Wakeup(); - } + if (state == SystemTaskState::Sleeping || state == SystemTaskState::AODSleeping) { + // SPI only switched off when entering Sleeping, not AOD or GoingToSleep + if (state == SystemTaskState::Sleeping) { + spi.Wakeup(); + } - // Double Tap needs the touch screen to be in normal mode - if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { - touchPanel.Wakeup(); - } + // Double Tap needs the touch screen to be in normal mode + if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) { + touchPanel.Wakeup(); + } - spiNorFlash.Wakeup(); + spiNorFlash.Wakeup(); + } displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning); heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);