mirror of
https://github.com/InfiniTimeOrg/InfiniTime.git
synced 2024-10-22 15:11:51 +02:00
Compare commits
7 commits
515a3cae27
...
ad3b13eaf7
Author | SHA1 | Date | |
---|---|---|---|
ad3b13eaf7 | |||
8598142c27 | |||
e0870ec430 | |||
7ced109181 | |||
3ebc25dd4b | |||
839960de1f | |||
aac929654b |
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,9 +4,6 @@
|
||||||
[submodule "src/libs/littlefs"]
|
[submodule "src/libs/littlefs"]
|
||||||
path = src/libs/littlefs
|
path = src/libs/littlefs
|
||||||
url = https://github.com/littlefs-project/littlefs.git
|
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"]
|
[submodule "src/libs/arduinoFFT"]
|
||||||
path = src/libs/arduinoFFT
|
path = src/libs/arduinoFFT
|
||||||
url = https://github.com/kosme/arduinoFFT.git
|
url = https://github.com/kosme/arduinoFFT.git
|
||||||
|
|
|
@ -157,12 +157,20 @@ void DisplayApp::InitHw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TickType_t DisplayApp::CalculateSleepTime() {
|
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;
|
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
|
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
|
||||||
TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8);
|
// to increase the number of ticks (frames) before the overflow tick is reached
|
||||||
// ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max
|
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);
|
||||||
// in this case around 9 hours
|
|
||||||
constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
|
|
||||||
|
|
||||||
// Assumptions
|
// Assumptions
|
||||||
|
|
||||||
|
@ -170,17 +178,17 @@ TickType_t DisplayApp::CalculateSleepTime() {
|
||||||
// Needed for division trick above
|
// Needed for division trick above
|
||||||
static_assert(configTICK_RATE_HZ % 8 == 0);
|
static_assert(configTICK_RATE_HZ % 8 == 0);
|
||||||
|
|
||||||
// Local tick count must always wraparound before the system tick count does
|
// Frame count must always wraparound more often than 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)
|
// 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) {
|
if (alwaysOnFrameCount == overflowFrameCount) {
|
||||||
alwaysOnTickCount = 0;
|
alwaysOnFrameCount = 0;
|
||||||
alwaysOnStartTime = xTaskGetTickCount();
|
alwaysOnStartTime = xTaskGetTickCount();
|
||||||
}
|
}
|
||||||
if (elapsedTarget > ticksElapsed) {
|
if (targetRenderTick > ticksElapsed) {
|
||||||
return elapsedTarget - ticksElapsed;
|
return targetRenderTick - ticksElapsed;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -220,28 +228,27 @@ void DisplayApp::Refresh() {
|
||||||
TickType_t queueTimeout;
|
TickType_t queueTimeout;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case States::Idle:
|
case States::Idle:
|
||||||
if (settingsController.GetAlwaysOnDisplay()) {
|
queueTimeout = portMAX_DELAY;
|
||||||
if (!currentScreen->IsRunning()) {
|
break;
|
||||||
LoadPreviousScreen();
|
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) {
|
||||||
|
alwaysOnFrameCount += 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;
|
break;
|
||||||
case States::Running:
|
case States::Running:
|
||||||
|
@ -284,6 +291,7 @@ void DisplayApp::Refresh() {
|
||||||
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
|
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
|
||||||
switch (msg) {
|
switch (msg) {
|
||||||
case Messages::GoToSleep:
|
case Messages::GoToSleep:
|
||||||
|
case Messages::GoToAOD:
|
||||||
if (state != States::Running) {
|
if (state != States::Running) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -292,7 +300,7 @@ void DisplayApp::Refresh() {
|
||||||
vTaskDelay(100);
|
vTaskDelay(100);
|
||||||
}
|
}
|
||||||
// Turn brightness down (or set to AlwaysOn mode)
|
// Turn brightness down (or set to AlwaysOn mode)
|
||||||
if (settingsController.GetAlwaysOnDisplay()) {
|
if (msg == Messages::GoToAOD) {
|
||||||
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
|
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
|
||||||
} else {
|
} else {
|
||||||
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
|
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
|
||||||
|
@ -305,17 +313,18 @@ void DisplayApp::Refresh() {
|
||||||
while (!lv_task_handler()) {
|
while (!lv_task_handler()) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Turn LCD display off (or set to low power for AlwaysOn mode)
|
if (msg == Messages::GoToAOD) {
|
||||||
if (settingsController.GetAlwaysOnDisplay()) {
|
|
||||||
lcd.LowPowerOn();
|
lcd.LowPowerOn();
|
||||||
// Record idle entry time
|
// Record idle entry time
|
||||||
alwaysOnTickCount = 0;
|
alwaysOnFrameCount = 0;
|
||||||
alwaysOnStartTime = xTaskGetTickCount();
|
alwaysOnStartTime = xTaskGetTickCount();
|
||||||
|
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
|
||||||
|
state = States::AOD;
|
||||||
} else {
|
} else {
|
||||||
lcd.Sleep();
|
lcd.Sleep();
|
||||||
|
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
||||||
|
state = States::Idle;
|
||||||
}
|
}
|
||||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
|
||||||
state = States::Idle;
|
|
||||||
break;
|
break;
|
||||||
case Messages::NotifyDeviceActivity:
|
case Messages::NotifyDeviceActivity:
|
||||||
lv_disp_trig_activity(nullptr);
|
lv_disp_trig_activity(nullptr);
|
||||||
|
@ -324,7 +333,7 @@ void DisplayApp::Refresh() {
|
||||||
if (state == States::Running) {
|
if (state == States::Running) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (settingsController.GetAlwaysOnDisplay()) {
|
if (state == States::AOD) {
|
||||||
lcd.LowPowerOff();
|
lcd.LowPowerOff();
|
||||||
} else {
|
} else {
|
||||||
lcd.Wakeup();
|
lcd.Wakeup();
|
||||||
|
|
|
@ -49,7 +49,7 @@ namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
class DisplayApp {
|
class DisplayApp {
|
||||||
public:
|
public:
|
||||||
enum class States { Idle, Running };
|
enum class States { Idle, Running, AOD };
|
||||||
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
|
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
|
||||||
|
|
||||||
DisplayApp(Drivers::St7789& lcd,
|
DisplayApp(Drivers::St7789& lcd,
|
||||||
|
@ -139,7 +139,7 @@ namespace Pinetime {
|
||||||
bool isDimmed = false;
|
bool isDimmed = false;
|
||||||
|
|
||||||
TickType_t CalculateSleepTime();
|
TickType_t CalculateSleepTime();
|
||||||
TickType_t alwaysOnTickCount;
|
TickType_t alwaysOnFrameCount;
|
||||||
TickType_t alwaysOnStartTime;
|
TickType_t alwaysOnStartTime;
|
||||||
// If this is to be changed, make sure the actual always on refresh rate is changed
|
// If this is to be changed, make sure the actual always on refresh rate is changed
|
||||||
// by configuring the LCD refresh timings
|
// by configuring the LCD refresh timings
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Pinetime {
|
||||||
namespace Display {
|
namespace Display {
|
||||||
enum class Messages : uint8_t {
|
enum class Messages : uint8_t {
|
||||||
GoToSleep,
|
GoToSleep,
|
||||||
|
GoToAOD,
|
||||||
GoToRunning,
|
GoToRunning,
|
||||||
UpdateBleConnection,
|
UpdateBleConnection,
|
||||||
TouchEvent,
|
TouchEvent,
|
||||||
|
|
|
@ -175,9 +175,8 @@ void St7789::IdleFrameRateOn() {
|
||||||
// According to the datasheet, these controls should apply only to partial/idle mode
|
// 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
|
// However they appear to apply to normal mode, so we have to enable/disable
|
||||||
// every time we enter/exit always on
|
// every time we enter/exit always on
|
||||||
// In testing this divider appears to actually be 16x?
|
|
||||||
constexpr uint8_t args[] = {
|
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, // Idle mode frame rate
|
||||||
0x1e, // Partial mode frame rate (unused)
|
0x1e, // Partial mode frame rate (unused)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 56b17bf9f74096774944bcac0829adcd887d391e
|
|
|
@ -17,6 +17,7 @@ namespace Pinetime {
|
||||||
HandleButtonEvent,
|
HandleButtonEvent,
|
||||||
HandleButtonTimerEvent,
|
HandleButtonTimerEvent,
|
||||||
OnDisplayTaskSleeping,
|
OnDisplayTaskSleeping,
|
||||||
|
OnDisplayTaskAOD,
|
||||||
EnableSleeping,
|
EnableSleeping,
|
||||||
DisableSleeping,
|
DisableSleeping,
|
||||||
OnNewDay,
|
OnNewDay,
|
||||||
|
|
|
@ -284,9 +284,10 @@ void SystemTask::Work() {
|
||||||
HandleButtonAction(action);
|
HandleButtonAction(action);
|
||||||
} break;
|
} break;
|
||||||
case Messages::OnDisplayTaskSleeping:
|
case Messages::OnDisplayTaskSleeping:
|
||||||
|
case Messages::OnDisplayTaskAOD:
|
||||||
// The state was set to GoingToSleep when GoToSleep() was called
|
// The state was set to GoingToSleep when GoToSleep() was called
|
||||||
// If the state is no longer GoingToSleep, we have since transitioned back to Running
|
// 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
|
// as DisplayApp is about to receive GoToRunning
|
||||||
if (state != SystemTaskState::GoingToSleep) {
|
if (state != SystemTaskState::GoingToSleep) {
|
||||||
break;
|
break;
|
||||||
|
@ -298,7 +299,7 @@ void SystemTask::Work() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must keep SPI awake when still updating the display for always on
|
// Must keep SPI awake when still updating the display for always on
|
||||||
if (!settingsController.GetAlwaysOnDisplay()) {
|
if (msg == Messages::OnDisplayTaskSleeping) {
|
||||||
spi.Sleep();
|
spi.Sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +308,11 @@ void SystemTask::Work() {
|
||||||
touchPanel.Sleep();
|
touchPanel.Sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = SystemTaskState::Sleeping;
|
if (msg == Messages::OnDisplayTaskSleeping) {
|
||||||
|
state = SystemTaskState::Sleeping;
|
||||||
|
} else {
|
||||||
|
state = SystemTaskState::AODSleeping;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Messages::OnNewDay:
|
case Messages::OnNewDay:
|
||||||
// We might be sleeping (with TWI device disabled.
|
// We might be sleeping (with TWI device disabled.
|
||||||
|
@ -381,17 +386,19 @@ void SystemTask::GoToRunning() {
|
||||||
if (state == SystemTaskState::Running) {
|
if (state == SystemTaskState::Running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// SPI doesn't go to sleep for always on mode
|
if (state == SystemTaskState::Sleeping || state == SystemTaskState::AODSleeping) {
|
||||||
if (!settingsController.GetAlwaysOnDisplay()) {
|
// SPI only switched off when entering Sleeping, not AOD or GoingToSleep
|
||||||
spi.Wakeup();
|
if (state == SystemTaskState::Sleeping) {
|
||||||
}
|
spi.Wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
// Double Tap needs the touch screen to be in normal mode
|
// Double Tap needs the touch screen to be in normal mode
|
||||||
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
|
||||||
touchPanel.Wakeup();
|
touchPanel.Wakeup();
|
||||||
}
|
}
|
||||||
|
|
||||||
spiNorFlash.Wakeup();
|
spiNorFlash.Wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
|
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
|
||||||
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
|
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
|
||||||
|
@ -411,16 +418,22 @@ void SystemTask::GoToSleep() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NRF_LOG_INFO("[systemtask] Going to sleep");
|
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);
|
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
|
||||||
|
|
||||||
state = SystemTaskState::GoingToSleep;
|
state = SystemTaskState::GoingToSleep;
|
||||||
};
|
};
|
||||||
|
|
||||||
void SystemTask::UpdateMotion() {
|
void SystemTask::UpdateMotion() {
|
||||||
if (IsSleeping() && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
|
// Only consider disabling motion updates specifically in the Sleeping state
|
||||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
|
// AOD needs motion on to show up to date step counts
|
||||||
motionController.GetService()->IsMotionNotificationSubscribed())) {
|
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
|
||||||
|
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
|
||||||
|
motionController.GetService()->IsMotionNotificationSubscribed())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Pinetime {
|
||||||
namespace System {
|
namespace System {
|
||||||
class SystemTask {
|
class SystemTask {
|
||||||
public:
|
public:
|
||||||
enum class SystemTaskState { Sleeping, Running, GoingToSleep };
|
enum class SystemTaskState { Sleeping, Running, GoingToSleep, AODSleeping };
|
||||||
SystemTask(Drivers::SpiMaster& spi,
|
SystemTask(Drivers::SpiMaster& spi,
|
||||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
|
||||||
Drivers::TwiMaster& twiMaster,
|
Drivers::TwiMaster& twiMaster,
|
||||||
|
|
Loading…
Reference in a new issue