Compare commits

...

7 commits

Author SHA1 Message Date
mark9064 ad3b13eaf7
Merge e0870ec430 into 8598142c27 2024-10-10 20:50:55 +02:00
NeroBurner 8598142c27
Remove unused submodule QCBOR (#2138)
Some checks failed
CI / build-firmware (push) Successful in 5m57s
CI / build-simulator (push) Failing after 3s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
The submodule isn't used anymore. Remove the submodule reference
completely.
2024-10-09 20:26:08 +02:00
mark9064 e0870ec430 Restrict hardware reactivation when not sleeping 2024-09-22 22:37:30 +01:00
mark9064 7ced109181 Improve sleep time calculation docs 2024-09-22 16:07:26 +01:00
mark9064 3ebc25dd4b Replace rounded div macro 2024-09-22 16:06:01 +01:00
mark9064 839960de1f Refactor into defined states 2024-09-22 16:06:01 +01:00
mark9064 aac929654b 8hz idle 2024-09-22 16:06:01 +01:00
9 changed files with 84 additions and 65 deletions

3
.gitmodules vendored
View file

@ -4,9 +4,6 @@
[submodule "src/libs/littlefs"]
path = src/libs/littlefs
url = https://github.com/littlefs-project/littlefs.git
[submodule "src/libs/QCBOR"]
path = src/libs/QCBOR
url = https://github.com/laurencelundblade/QCBOR.git
[submodule "src/libs/arduinoFFT"]
path = src/libs/arduinoFFT
url = https://github.com/kosme/arduinoFFT.git

View file

@ -157,12 +157,20 @@ void DisplayApp::InitHw() {
}
TickType_t DisplayApp::CalculateSleepTime() {
// 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);
};
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
// 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 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);
// 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
@ -170,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;
}
@ -220,7 +228,9 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout;
switch (state) {
case States::Idle:
if (settingsController.GetAlwaysOnDisplay()) {
queueTimeout = portMAX_DELAY;
break;
case States::AOD:
if (!currentScreen->IsRunning()) {
LoadPreviousScreen();
}
@ -235,13 +245,10 @@ 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();
}
};
}
} else {
queueTimeout = portMAX_DELAY;
}
break;
case States::Running:
@ -284,6 +291,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 +300,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 +313,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;
alwaysOnFrameCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
state = States::AOD;
} else {
lcd.Sleep();
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
}
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
@ -324,7 +333,7 @@ void DisplayApp::Refresh() {
if (state == States::Running) {
break;
}
if (settingsController.GetAlwaysOnDisplay()) {
if (state == States::AOD) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();

View file

@ -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,
@ -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

View file

@ -6,6 +6,7 @@ namespace Pinetime {
namespace Display {
enum class Messages : uint8_t {
GoToSleep,
GoToAOD,
GoToRunning,
UpdateBleConnection,
TouchEvent,

View file

@ -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)
};

@ -1 +0,0 @@
Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e

View file

@ -17,6 +17,7 @@ namespace Pinetime {
HandleButtonEvent,
HandleButtonTimerEvent,
OnDisplayTaskSleeping,
OnDisplayTaskAOD,
EnableSleeping,
DisableSleeping,
OnNewDay,

View file

@ -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();
}
if (msg == Messages::OnDisplayTaskSleeping) {
state = SystemTaskState::Sleeping;
} else {
state = SystemTaskState::AODSleeping;
}
break;
case Messages::OnNewDay:
// We might be sleeping (with TWI device disabled.
@ -381,8 +386,9 @@ void SystemTask::GoToRunning() {
if (state == SystemTaskState::Running) {
return;
}
// SPI doesn't go to sleep for always on mode
if (!settingsController.GetAlwaysOnDisplay()) {
if (state == SystemTaskState::Sleeping || state == SystemTaskState::AODSleeping) {
// SPI only switched off when entering Sleeping, not AOD or GoingToSleep
if (state == SystemTaskState::Sleeping) {
spi.Wakeup();
}
@ -392,6 +398,7 @@ void SystemTask::GoToRunning() {
}
spiNorFlash.Wakeup();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
@ -411,14 +418,20 @@ void SystemTask::GoToSleep() {
return;
}
NRF_LOG_INFO("[systemtask] Going to sleep");
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) ||
// 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;

View file

@ -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,