Compare commits

...

21 commits

Author SHA1 Message Date
JF 0663532b4f
Merge 6f146fc0cd into 3a0d673df4 2024-08-17 16:31:25 +02:00
Jean-François Milants 3a0d673df4 Display the SPI flash JEDEC IDs in SystemInformation.
Some checks failed
CI / build-firmware (push) Successful in 5m33s
CI / build-simulator (push) Failing after 2s
CI / get-base-ref-size (push) Has been skipped
CI / Compare build size (push) Has been skipped
This is needed since a new memory chip will be used in future batches of PineTimes.
2024-08-05 20:34:41 +02:00
mark9064 53dc9dafe7 aod: simplify AOD disablement based on notification status 2024-08-05 20:32:43 +02:00
mark9064 a407902b06 aod: avoid spinning DisplayApp under high LVGL load 2024-08-05 20:32:43 +02:00
mark9064 3e8accde69 aod: run LVGL task handler until all work finished 2024-08-05 20:32:43 +02:00
mark9064 2bb611db8e aod: constant frequency idle frames 2024-08-05 20:32:43 +02:00
mark9064 ef88e8165c aod: porch control: 2Hz idle + 75Hz on 2024-08-05 20:32:43 +02:00
mark9064 da9ab4a7b4 aod: lower lcd voltage 2024-08-05 20:32:43 +02:00
John Crawford 0bcd7e0009 aod: lower voltage going to the display 2024-08-05 20:32:43 +02:00
mark9064 bf69e0dcc5 aod: fix flashlight brightness restore 2024-08-05 20:32:43 +02:00
mark9064 947c4f5067 aod: fix brightness getting stuck high 2024-08-05 20:32:43 +02:00
John Crawford 0960d67001 aod: lower refresh rate when always on 2024-08-05 20:32:43 +02:00
John Crawford 5385f7e275 aod: switch to 8 colors when always on 2024-08-05 20:32:43 +02:00
John Crawford e884b053d3 aod: disable while in notification sleep 2024-08-05 20:32:43 +02:00
John Crawford 85a2181b64 aod: integrate with display timeout 2024-08-05 20:32:43 +02:00
mark9064 3dca742b65 aod: PPI/RTC-based backlight brightness 2024-08-05 20:32:43 +02:00
KaffeinatedKat 20ac7e8df3 feat: always on display 2024-08-05 20:32:43 +02:00
Jean-François Milants 6f146fc0cd Watchface : remove the WatchFace enum
Generate the includes necessary for the watch face by CMake.
2024-03-19 21:39:57 +01:00
Jean-François Milants 7c2ead9054 Watchface : remove the WatchFace enum
Watch faces are not identified by their type at build type and their name at runtime
2024-03-19 20:28:45 +01:00
Jean-François Milants 45f0d9880c Code cleaning : Split displayapp/apps/Apps.h.in into 2 files : Apps.h and WatchFaces.h 2024-02-27 21:19:43 +01:00
Jean-François Milants 73b22aaaa4 Code cleaning : remove includes to Apps.h that were not needed. 2024-02-27 21:19:43 +01:00
36 changed files with 533 additions and 124 deletions

View file

@ -2,38 +2,138 @@
#include <hal/nrf_gpio.h>
#include "displayapp/screens/Symbols.h"
#include "drivers/PinMap.h"
#include <libraries/delay/nrf_delay.h>
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_RTC_Type*>(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<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&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;
}
}

View file

@ -2,11 +2,14 @@
#include <cstdint>
#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);
};
}
}

View file

@ -1,9 +1,12 @@
#pragma once
#include <cstdint>
#include <bitset>
#include <cstring>
#include <array>
#include "components/brightness/BrightnessController.h"
#include "components/fs/FS.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -60,15 +63,18 @@ namespace Pinetime {
void Init();
void SaveSettings();
void SetWatchFace(Pinetime::Applications::WatchFace face) {
if (face != settings.watchFace) {
void SetWatchFace(const char* face) {
const char* currentWatchFace = settings.watchFace.data();
if (std::strcmp(face, currentWatchFace) != 0) {
settingsChanged = true;
auto len = std::min(std::strlen(face), Pinetime::Applications::MaxWatchFaceNameSize);
std::memcpy(settings.watchFace.data(), face, len);
settings.watchFace[len] = 0;
}
settings.watchFace = face;
};
Pinetime::Applications::WatchFace GetWatchFace() const {
return settings.watchFace;
const char* GetWatchFace() const {
return settings.watchFace.data();
};
void SetChimeOption(ChimesOption chimeOption) {
@ -214,6 +220,21 @@ namespace Pinetime {
return settings.screenTimeOut;
};
bool GetAlwaysOnDisplay() const {
return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep;
};
void SetAlwaysOnDisplaySetting(bool state) {
if (state != settings.alwaysOnDisplay) {
settingsChanged = true;
}
settings.alwaysOnDisplay = state;
}
bool GetAlwaysOnDisplaySetting() const {
return settings.alwaysOnDisplay;
}
void SetShakeThreshold(uint16_t thresh) {
if (settings.shakeWakeThreshold != thresh) {
settings.shakeWakeThreshold = thresh;
@ -286,18 +307,20 @@ 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;
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
std::array<char, Pinetime::Applications::MaxWatchFaceNameSize + 1> watchFace = {0};
ChimesOption chimesOption = ChimesOption::None;
PineTimeStyle PTS;

View file

@ -81,7 +81,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem)
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: lcd {lcd},
touchPanel {touchPanel},
batteryController {batteryController},
@ -97,6 +98,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
brightnessController {brightnessController},
touchHandler {touchHandler},
filesystem {filesystem},
spiNorFlash {spiNorFlash},
lvgl {lcd, filesystem},
timer(this, TimerCallback),
controllers {batteryController,
@ -154,6 +156,36 @@ void DisplayApp::InitHw() {
lcd.Init();
}
TickType_t DisplayApp::CalculateSleepTime() {
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
// Divide both the numerator and denominator by 8 to increase the number of ticks (frames) before the overflow tick is reached
TickType_t elapsedTarget = ROUNDED_DIV((configTICK_RATE_HZ / 8) * alwaysOnTickCount * alwaysOnRefreshPeriod, 1000 / 8);
// ROUNDED_DIV overflows when numerator + (denominator floordiv 2) > uint32 max
// in this case around 9 hours
constexpr TickType_t overflowTick = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
// Assumptions
// Tick rate is multiple of 8
// Needed for division trick above
static_assert(configTICK_RATE_HZ % 8 == 0);
// Local tick count must always wraparound before the system tick count does
// As a static assert we can use 64 bit ints and therefore dodge overflows
// Always on overflow time (ms) < system tick overflow time (ms)
static_assert((uint64_t) overflowTick * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
if (alwaysOnTickCount == overflowTick) {
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
}
if (elapsedTarget > ticksElapsed) {
return elapsedTarget - ticksElapsed;
} else {
return 0;
}
}
void DisplayApp::Refresh() {
auto LoadPreviousScreen = [this]() {
FullRefreshDirections returnDirection;
@ -203,7 +235,29 @@ void DisplayApp::Refresh() {
TickType_t queueTimeout;
switch (state) {
case States::Idle:
queueTimeout = portMAX_DELAY;
if (settingsController.GetAlwaysOnDisplay()) {
if (!currentScreen->IsRunning()) {
LoadPreviousScreen();
}
// Check we've slept long enough
// Might not be true if the loop received an event
// If not true, then wait that amount of time
queueTimeout = CalculateSleepTime();
if (queueTimeout == 0) {
// Only advance the tick count when LVGL is done
// Otherwise keep running the task handler while it still has things to draw
// Note: under high graphics load, LVGL will always have more work to do
if (lv_task_handler() > 0) {
// Drop frames that we've missed if drawing/event handling took way longer than expected
while (queueTimeout == 0) {
alwaysOnTickCount += 1;
queueTimeout = CalculateSleepTime();
}
};
}
} else {
queueTimeout = portMAX_DELAY;
}
break;
case States::Running:
if (!currentScreen->IsRunning()) {
@ -234,20 +288,34 @@ void DisplayApp::Refresh() {
case Messages::DimScreen:
DimScreen();
break;
case Messages::RestoreBrightness:
RestoreBrightness();
break;
case Messages::GoToSleep:
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
brightnessController.Lower();
vTaskDelay(100);
}
lcd.Sleep();
// Don't actually turn off the display for AlwaysOn mode
if (settingsController.GetAlwaysOnDisplay()) {
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
lcd.LowPowerOn();
// Record idle entry time
alwaysOnTickCount = 0;
alwaysOnStartTime = xTaskGetTickCount();
} else {
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
lcd.Sleep();
}
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
state = States::Idle;
break;
case Messages::NotifyDeviceActivity:
lv_disp_trig_activity(nullptr);
break;
case Messages::GoToRunning:
lcd.Wakeup();
if (settingsController.GetAlwaysOnDisplay()) {
lcd.LowPowerOff();
} else {
lcd.Wakeup();
}
lv_disp_trig_activity(nullptr);
ApplyBrightness();
state = States::Running;
@ -436,7 +504,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::Clock: {
const auto* watchFace =
std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
return watchfaceDescription.watchFace == settingsController.GetWatchFace();
return std::strcmp(watchfaceDescription.name, settingsController.GetWatchFace()) == 0;
});
if (watchFace != userWatchFaces.end())
currentScreen.reset(watchFace->create(controllers));
@ -492,8 +560,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
int i = 0;
for (const auto& userWatchFace : userWatchFaces) {
items[i++] =
Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
items[i++] = Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
}
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
} break;
@ -535,7 +602,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
bleController,
watchdog,
motionController,
touchPanel);
touchPanel,
spiNorFlash);
break;
case Apps::FlashLight:
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);

View file

@ -66,7 +66,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem);
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start(System::BootErrors error);
void PushMessage(Display::Messages msg);
@ -96,6 +97,7 @@ namespace Pinetime {
Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::TouchHandler& touchHandler;
Pinetime::Controllers::FS& filesystem;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::FirmwareValidator validator;
Pinetime::Components::LittleVgl lvgl;
@ -135,6 +137,13 @@ namespace Pinetime {
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> 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;
};
}
}

View file

@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
Pinetime::Controllers::AlarmController& /*alarmController*/,
Pinetime::Controllers::BrightnessController& /*brightnessController*/,
Pinetime::Controllers::TouchHandler& /*touchHandler*/,
Pinetime::Controllers::FS& /*filesystem*/)
Pinetime::Controllers::FS& /*filesystem*/,
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
: lcd {lcd}, bleController {bleController} {
}

View file

@ -18,6 +18,7 @@ namespace Pinetime {
class St7789;
class Cst816S;
class Watchdog;
class SpiNorFlash;
}
namespace Controllers {
@ -59,7 +60,8 @@ namespace Pinetime {
Pinetime::Controllers::AlarmController& alarmController,
Pinetime::Controllers::BrightnessController& brightnessController,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::FS& filesystem);
Pinetime::Controllers::FS& filesystem,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Start();
void Start(Pinetime::System::BootErrors) {

View file

@ -18,7 +18,7 @@ namespace Pinetime {
TimerDone,
BleFirmwareUpdateStarted,
DimScreen,
RestoreBrightness,
NotifyDeviceActivity,
ShowPairingKey,
AlarmTriggered,
Chime,

View file

@ -1,19 +1,13 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "Controllers.h"
#include "displayapp/Controllers.h"
#include "displayapp/screens/Alarm.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Timer.h"
#include "displayapp/screens/Twos.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/WatchFaceDigital.h"
#include "displayapp/screens/WatchFaceAnalog.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
#include "displayapp/screens/WatchFaceTerminal.h"
@WATCHFACE_INCLUDE@
namespace Pinetime {
namespace Applications {
@ -28,7 +22,6 @@ namespace Pinetime {
};
struct WatchFaceDescription {
WatchFace watchFace;
const char* name;
Screens::Screen* (*create)(AppControllers& controllers);
bool (*isAvailable)(Controllers::FS& fileSystem);
@ -39,9 +32,11 @@ namespace Pinetime {
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
}
template <WatchFace t>
template <class t>
consteval WatchFaceDescription CreateWatchFaceDescription() {
return {WatchFaceTraits<t>::watchFace, WatchFaceTraits<t>::name, &WatchFaceTraits<t>::Create, &WatchFaceTraits<t>::IsAvailable};
static_assert(std::char_traits<char>::length(WatchFaceTraits<t>::name) < 16,
"The name of the watch faces is limited to 15 characters max");
return {WatchFaceTraits<t>::name, &WatchFaceTraits<t>::Create, &WatchFaceTraits<t>::IsAvailable};
}
template <template <Apps...> typename T, Apps... ts>
@ -49,7 +44,7 @@ namespace Pinetime {
return {CreateAppDescription<ts>()...};
}
template <template <WatchFace...> typename T, WatchFace... ts>
template <template <class...> typename T, class... ts>
consteval std::array<WatchFaceDescription, sizeof...(ts)> CreateWatchFaceDescriptions(T<ts...>) {
return {CreateWatchFaceDescription<ts>()...};
}

View file

@ -45,35 +45,14 @@ namespace Pinetime {
Error
};
enum class WatchFace : uint8_t {
Digital,
Analog,
PineTimeStyle,
Terminal,
Infineat,
CasioStyleG7710,
};
template <Apps>
struct AppTraits {};
template <WatchFace>
struct WatchFaceTraits {};
template <Apps... As>
struct TypeList {
static constexpr size_t Count = sizeof...(As);
};
using UserAppTypes = TypeList<@USERAPP_TYPES@>;
template <WatchFace... Ws>
struct WatchFaceTypeList {
static constexpr size_t Count = sizeof...(Ws);
};
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
static_assert(UserWatchFaceTypes::Count >= 1);
}
}

View file

@ -19,20 +19,47 @@ else ()
endif ()
if(DEFINED ENABLE_WATCHFACES)
set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
else()
set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
endif()
set(DEFAULT_WATCHFACE_TYPES ${ENABLE_WATCHFACES})
else ()
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFaceDigital")
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFaceAnalog")
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFacePineTimeStyle")
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFaceTerminal")
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFaceInfineat")
list(APPEND DEFAULT_WATCHFACE_TYPES "Pinetime::Applications::Screens::WatchFaceCasioStyleG7710")
endif ()
# Generate the list of watchface types necessary to instantiate WatchFaceTypeList<> needed in WatchFace.h
set(FIRST_ITERATION TRUE)
foreach (watchface IN LISTS DEFAULT_WATCHFACE_TYPES)
if (${FIRST_ITERATION})
string(APPEND WATCHFACE_TYPES "${watchface}")
set(FIRST_ITERATION FALSE)
else ()
string(APPEND WATCHFACE_TYPES ", ${watchface}")
endif ()
endforeach ()
# Generate the forward declarations needed in WatchFace.h
foreach (w IN LISTS DEFAULT_WATCHFACE_TYPES)
string(FIND ${w} "::" classIndex REVERSE)
string(LENGTH ${w} watchfaceFullLength)
math(EXPR watchfaceLength "${watchfaceFullLength} - (${classIndex} + 2)")
math(EXPR beginIndex "${classIndex}+2")
string(SUBSTRING ${w} ${beginIndex} ${watchfaceLength} className)
string(SUBSTRING ${w} 0 ${classIndex} namespaceName)
string(APPEND WATCHFACE_NAMESPACE "namespace ${namespaceName} { class ${className}; }\n")
# TODO the include path should be specified by the CMake file of the watchface
string(APPEND WATCHFACE_INCLUDE "#include \"displayapp/screens/${className}.h\"\n")
endforeach ()
add_library(infinitime_apps INTERFACE)
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/../")
# Generate the list of user apps to be compiled into the firmware
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Apps.h.in ${CMAKE_CURRENT_BINARY_DIR}/Apps.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/WatchFaces.h.in ${CMAKE_CURRENT_BINARY_DIR}/WatchFaces.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../UserApps.h.in ${CMAKE_CURRENT_BINARY_DIR}/../UserApps.h)

View file

@ -0,0 +1,22 @@
#pragma once
#include <cstddef>
#include <cstdint>
@WATCHFACE_NAMESPACE@
namespace Pinetime {
namespace Applications {
static constexpr size_t MaxWatchFaceNameSize = 15;
template <class T>
struct WatchFaceTraits {};
template <class... Ws>
struct WatchFaceTypeList {
static constexpr size_t Count = sizeof...(Ws);
};
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
static_assert(UserWatchFaceTypes::Count >= 1);
}
}

View file

@ -2,7 +2,6 @@
#include <array>
#include <memory>
#include "displayapp/apps/Apps.h"
#include "Screen.h"
#include "ScreenList.h"
#include "displayapp/Controllers.h"

View file

@ -1,6 +1,5 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "displayapp/screens/Screen.h"
#include <array>
#include <cstdint>

View file

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

View file

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

View file

@ -5,7 +5,6 @@
#include <array>
#include "displayapp/screens/Screen.h"
#include "displayapp/widgets/PageIndicator.h"
#include "displayapp/apps/Apps.h"
#include "components/settings/Settings.h"
#define MAXLISTITEMS 4

View file

@ -38,7 +38,8 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel)
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash)
: app {app},
dateTimeController {dateTimeController},
batteryController {batteryController},
@ -47,6 +48,7 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp* app,
watchdog {watchdog},
motionController {motionController},
touchPanel {touchPanel},
spiNorFlash {spiNorFlash},
screens {app,
0,
{[this]() -> std::unique_ptr<Screen> {
@ -186,10 +188,12 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label, true);
const auto& bleAddr = bleController.Address();
auto spiFlashId = spiNorFlash.GetIdentification();
lv_label_set_text_fmt(label,
"#808080 BLE MAC#\n"
" %02x:%02x:%02x:%02x:%02x:%02x"
" %02x:%02x:%02x:%02x:%02x:%02x\n"
"\n"
"#808080 SPI Flash# %02x-%02x-%02x\n"
"\n"
"#808080 Memory heap#\n"
" #808080 Free# %d\n"
@ -202,6 +206,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
bleAddr[2],
bleAddr[1],
bleAddr[0],
spiFlashId.manufacturer,
spiFlashId.type,
spiFlashId.density,
xPortGetFreeHeapSize(),
xPortGetMinimumEverFreeHeapSize(),
mallocFailedCount,

View file

@ -29,7 +29,8 @@ namespace Pinetime {
const Pinetime::Controllers::Ble& bleController,
const Pinetime::Drivers::Watchdog& watchdog,
Pinetime::Controllers::MotionController& motionController,
const Pinetime::Drivers::Cst816S& touchPanel);
const Pinetime::Drivers::Cst816S& touchPanel,
const Pinetime::Drivers::SpiNorFlash& spiNorFlash);
~SystemInfo() override;
bool OnTouchEvent(TouchEvents event) override;
@ -42,6 +43,7 @@ namespace Pinetime {
const Pinetime::Drivers::Watchdog& watchdog;
Pinetime::Controllers::MotionController& motionController;
const Pinetime::Drivers::Cst816S& touchPanel;
const Pinetime::Drivers::SpiNorFlash& spiNorFlash;
ScreenList<5> screens;

View file

@ -4,7 +4,6 @@
#include <cstdint>
#include <memory>
#include "displayapp/screens/Screen.h"
#include "displayapp/apps/Apps.h"
#include "components/datetime/DateTimeController.h"
#include "components/settings/Settings.h"
#include "components/battery/BatteryController.h"

View file

@ -11,6 +11,7 @@
#include "components/ble/NotificationManager.h"
#include "displayapp/screens/BatteryIcon.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -89,8 +90,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::Analog> {
static constexpr WatchFace watchFace = WatchFace::Analog;
struct WatchFaceTraits<Screens::WatchFaceAnalog> {
static constexpr const char* name = "Analog face";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -10,7 +10,7 @@
#include "components/datetime/DateTimeController.h"
#include "components/ble/BleController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -103,8 +103,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::CasioStyleG7710> {
static constexpr WatchFace watchFace = WatchFace::CasioStyleG7710;
struct WatchFaceTraits<Screens::WatchFaceCasioStyleG7710> {
static constexpr const char* name = "Casio G7710";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -10,7 +10,7 @@
#include "components/ble/BleController.h"
#include "displayapp/widgets/StatusIcons.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -76,8 +76,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::Digital> {
static constexpr WatchFace watchFace = WatchFace::Digital;
struct WatchFaceTraits<Screens::WatchFaceDigital> {
static constexpr const char* name = "Digital face";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -8,7 +8,7 @@
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -101,8 +101,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::Infineat> {
static constexpr WatchFace watchFace = WatchFace::Infineat;
struct WatchFaceTraits<Screens::WatchFaceInfineat> {
static constexpr const char* name = "Infineat face";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -12,6 +12,7 @@
#include "components/ble/SimpleWeatherService.h"
#include "components/ble/BleController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -122,8 +123,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::PineTimeStyle> {
static constexpr WatchFace watchFace = WatchFace::PineTimeStyle;
struct WatchFaceTraits<Screens::WatchFacePineTimeStyle> {
static constexpr const char* name = "PineTimeStyle";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -8,6 +8,7 @@
#include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
namespace Controllers {
@ -70,8 +71,7 @@ namespace Pinetime {
}
template <>
struct WatchFaceTraits<WatchFace::Terminal> {
static constexpr WatchFace watchFace = WatchFace::Terminal;
struct WatchFaceTraits<Screens::WatchFaceTerminal> {
static constexpr const char* name = "Terminal";
static Screens::Screen* Create(AppControllers& controllers) {

View file

@ -9,10 +9,17 @@
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
void TimeoutEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<SettingDisplay*>(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<SettingDisplay*>(obj->user_data);
screen->ToggleAlwaysOn();
}
}
}
constexpr std::array<uint16_t, 6> SettingDisplay::options;
@ -49,13 +56,20 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime
snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
lv_checkbox_set_text(cbOption[i], buffer);
cbOption[i]->user_data = this;
lv_obj_set_event_cb(cbOption[i], event_handler);
lv_obj_set_event_cb(cbOption[i], TimeoutEventHandler);
SetRadioButtonStyle(cbOption[i]);
if (settingsController.GetScreenTimeOut() == options[i]) {
lv_checkbox_set_checked(cbOption[i], true);
}
}
alwaysOnCheckbox = lv_checkbox_create(container1, nullptr);
lv_checkbox_set_text(alwaysOnCheckbox, "Always On");
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
lv_obj_add_state(alwaysOnCheckbox, LV_STATE_DEFAULT);
alwaysOnCheckbox->user_data = this;
lv_obj_set_event_cb(alwaysOnCheckbox, AlwaysOnEventHandler);
}
SettingDisplay::~SettingDisplay() {
@ -63,6 +77,11 @@ SettingDisplay::~SettingDisplay() {
settingsController.SaveSettings();
}
void SettingDisplay::ToggleAlwaysOn() {
settingsController.SetAlwaysOnDisplaySetting(!settingsController.GetAlwaysOnDisplaySetting());
lv_checkbox_set_checked(alwaysOnCheckbox, settingsController.GetAlwaysOnDisplaySetting());
}
void SettingDisplay::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
for (unsigned int i = 0; i < options.size(); i++) {

View file

@ -18,6 +18,7 @@ namespace Pinetime {
~SettingDisplay() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
void ToggleAlwaysOn();
private:
DisplayApp* app;
@ -25,6 +26,7 @@ namespace Pinetime {
Controllers::Settings& settingsController;
lv_obj_t* cbOption[options.size()];
lv_obj_t* alwaysOnCheckbox;
};
}
}

View file

@ -12,12 +12,12 @@ constexpr const char* SettingWatchFace::symbol;
namespace {
uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
Pinetime::Applications::WatchFace watchface) {
const char* watchface) {
size_t index = 0;
auto found = std::find_if(watchfaces.begin(),
watchfaces.end(),
[&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
const bool result = item.watchface == watchface;
[&index, watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
const bool result = (std::strcmp(item.name, watchface) == 0);
if (!result) {
index++;
}
@ -30,13 +30,13 @@ namespace {
return index;
}
Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
const char* IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
size_t index) {
if (index >= watchfaces.size()) {
return watchfaces[0].watchface;
return watchfaces[0].name;
}
return watchfaces[index].watchface;
return watchfaces[index].name;
}
}

View file

@ -11,6 +11,7 @@
#include "displayapp/screens/CheckboxList.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include "displayapp/apps/WatchFaces.h"
namespace Pinetime {
@ -21,7 +22,6 @@ namespace Pinetime {
public:
struct Item {
const char* name;
WatchFace watchface;
bool enabled;
};

View file

@ -10,7 +10,7 @@ SpiNorFlash::SpiNorFlash(Spi& spi) : spi {spi} {
}
void SpiNorFlash::Init() {
device_id = ReadIdentificaion();
device_id = ReadIdentification();
NRF_LOG_INFO("[SpiNorFlash] Manufacturer : %d, Memory type : %d, memory density : %d",
device_id.manufacturer,
device_id.type,
@ -32,7 +32,7 @@ void SpiNorFlash::Wakeup() {
uint8_t cmd[cmdSize] = {static_cast<uint8_t>(Commands::ReleaseFromDeepPowerDown), 0x01, 0x02, 0x03};
uint8_t id = 0;
spi.Read(reinterpret_cast<uint8_t*>(&cmd), cmdSize, &id, 1);
auto devId = device_id = ReadIdentificaion();
auto devId = device_id = ReadIdentification();
if (devId.type != device_id.type) {
NRF_LOG_INFO("[SpiNorFlash] ID on Wakeup: Failed");
} else {
@ -41,7 +41,7 @@ void SpiNorFlash::Wakeup() {
NRF_LOG_INFO("[SpiNorFlash] Wakeup")
}
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
SpiNorFlash::Identification SpiNorFlash::ReadIdentification() {
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
Identification identification;
spi.Read(&cmd, 1, reinterpret_cast<uint8_t*>(&identification), sizeof(Identification));
@ -145,3 +145,7 @@ void SpiNorFlash::Write(uint32_t address, const uint8_t* buffer, size_t size) {
len -= toWrite;
}
}
SpiNorFlash::Identification SpiNorFlash::GetIdentification() const {
return device_id;
}

View file

@ -20,7 +20,6 @@ namespace Pinetime {
uint8_t density = 0;
};
Identification ReadIdentificaion();
uint8_t ReadStatusRegister();
bool WriteInProgress();
bool WriteEnabled();
@ -33,6 +32,8 @@ namespace Pinetime {
bool ProgramFailed();
bool EraseFailed();
Identification GetIdentification() const;
void Init();
void Uninit();
@ -40,6 +41,8 @@ namespace Pinetime {
void Wakeup();
private:
Identification ReadIdentification();
enum class Commands : uint8_t {
PageProgram = 0x02,
Read = 0x03,

View file

@ -16,6 +16,7 @@ void St7789::Init() {
nrf_gpio_pin_set(pinReset);
HardwareReset();
SoftwareReset();
Command2Enable();
SleepOut();
PixelFormat();
MemoryDataAccessControl();
@ -24,8 +25,13 @@ void St7789::Init() {
#ifndef DRIVER_DISPLAY_MIRROR
DisplayInversionOn();
#endif
PorchSet();
FrameRateNormalSet();
IdleFrameRateOff();
NormalModeOn();
SetVdv();
PowerControl();
GateControl();
DisplayOn();
}
@ -63,6 +69,17 @@ void St7789::SoftwareReset() {
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::Command2Enable() {
WriteCommand(static_cast<uint8_t>(Commands::Command2Enable));
constexpr uint8_t args[] = {
0x5a, // Constant
0x69, // Constant
0x02, // Constant
0x01, // Enable
};
WriteData(args, sizeof(args));
}
void St7789::SleepOut() {
if (!sleepIn) {
return;
@ -127,10 +144,79 @@ void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
}
void St7789::IdleModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn));
}
void St7789::IdleModeOff() {
WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff));
}
void St7789::PorchSet() {
WriteCommand(static_cast<uint8_t>(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<uint8_t>(Commands::FrameRateNormal));
// Note that the datasheet table is imprecise - see formula below table
WriteData(0x0a);
}
void St7789::IdleFrameRateOn() {
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
// According to the datasheet, these controls should apply only to partial/idle mode
// However they appear to apply to normal mode, so we have to enable/disable
// every time we enter/exit always on
// In testing this divider appears to actually be 16x?
constexpr uint8_t args[] = {
0x13, // Enable frame rate control for partial/idle mode, 8x frame divider
0x1e, // Idle mode frame rate
0x1e, // Partial mode frame rate (unused)
};
WriteData(args, sizeof(args));
}
void St7789::IdleFrameRateOff() {
WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle));
constexpr uint8_t args[] = {
0x00, // Disable frame rate control and divider
0x0a, // Idle mode frame rate (normal)
0x0a, // Partial mode frame rate (normal, unused)
};
WriteData(args, sizeof(args));
}
void St7789::DisplayOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOn));
}
void St7789::PowerControl() {
WriteCommand(static_cast<uint8_t>(Commands::PowerControl1));
constexpr uint8_t args[] = {
0xa4, // Constant
0x00, // Lowest possible voltages
};
WriteData(args, sizeof(args));
WriteCommand(static_cast<uint8_t>(Commands::PowerControl2));
// Lowest possible boost circuit clocks
WriteData(0xb3);
}
void St7789::GateControl() {
WriteCommand(static_cast<uint8_t>(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<uint8_t>(Commands::ColumnAddressSet));
uint8_t colArgs[] = {
@ -198,6 +284,18 @@ void St7789::HardwareReset() {
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::LowPowerOn() {
IdleModeOn();
IdleFrameRateOn();
NRF_LOG_INFO("[LCD] Low power mode");
}
void St7789::LowPowerOff() {
IdleModeOff();
IdleFrameRateOff();
NRF_LOG_INFO("[LCD] Normal power mode");
}
void St7789::Sleep() {
SleepIn();
nrf_gpio_cfg_default(pinDataCommand);

View file

@ -24,6 +24,8 @@ namespace Pinetime {
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
void LowPowerOn();
void LowPowerOff();
void Sleep();
void Wakeup();
@ -37,6 +39,7 @@ namespace Pinetime {
void HardwareReset();
void SoftwareReset();
void Command2Enable();
void SleepOut();
void EnsureSleepOutPostDelay();
void SleepIn();
@ -45,8 +48,16 @@ namespace Pinetime {
void DisplayInversionOn();
void NormalModeOn();
void WriteToRam(const uint8_t* data, size_t size);
void IdleModeOn();
void IdleModeOff();
void FrameRateNormalSet();
void IdleFrameRateOff();
void IdleFrameRateOn();
void DisplayOn();
void DisplayOff();
void PowerControl();
void GateControl();
void PorchSet();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv();
@ -68,8 +79,17 @@ namespace Pinetime {
MemoryDataAccessControl = 0x36,
VerticalScrollDefinition = 0x33,
VerticalScrollStartAddress = 0x37,
IdleModeOff = 0x38,
IdleModeOn = 0x39,
PixelFormat = 0x3a,
FrameRateIdle = 0xb3,
FrameRateNormal = 0xc6,
VdvSet = 0xc4,
Command2Enable = 0xdf,
PowerControl1 = 0xd0,
PowerControl2 = 0xe8,
GateControl = 0xb7,
Porch = 0xb2,
};
void WriteData(uint8_t data);
void WriteData(const uint8_t* data, size_t size);

View file

@ -123,7 +123,8 @@ Pinetime::Applications::DisplayApp displayApp(lcd,
alarmController,
brightnessController,
touchHandler,
fs);
fs,
spiNorFlash);
Pinetime::System::SystemTask systemTask(spi,
spiNorFlash,

View file

@ -102,7 +102,9 @@ void SystemTask::Work() {
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
watchdog.Start();
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason()));
APP_GPIOTE_INIT(2);
if (!nrfx_gpiote_is_init()) {
nrfx_gpiote_init();
}
spi.Init();
spiNorFlash.Init();
@ -192,13 +194,16 @@ void SystemTask::Work() {
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
break;
case Messages::DisableSleeping:
doNotGoToSleep = true;
break;
case Messages::GoToRunning:
spi.Wakeup();
// SPI doesn't go to sleep for always on mode
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Wakeup();
}
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -240,7 +245,7 @@ void SystemTask::Work() {
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break;
case Messages::OnNewTime:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
alarmController.ScheduleAlarm();
@ -251,7 +256,7 @@ void SystemTask::Work() {
if (state == SystemTaskState::Sleeping) {
GoToRunning();
} else {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
}
@ -263,7 +268,7 @@ void SystemTask::Work() {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
break;
case Messages::BleConnected:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NotifyDeviceActivity);
isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5;
break;
@ -323,7 +328,11 @@ void SystemTask::Work() {
// if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
spiNorFlash.Sleep();
}
spi.Sleep();
// Must keep SPI awake when still updating the display for always on
if (!settingsController.GetAlwaysOnDisplay()) {
spi.Sleep();
}
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
@ -457,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;