aod: PPI/RTC-based backlight brightness
This commit is contained in:
parent
20ac7e8df3
commit
3dca742b65
|
@ -2,38 +2,138 @@
|
||||||
#include <hal/nrf_gpio.h>
|
#include <hal/nrf_gpio.h>
|
||||||
#include "displayapp/screens/Symbols.h"
|
#include "displayapp/screens/Symbols.h"
|
||||||
#include "drivers/PinMap.h"
|
#include "drivers/PinMap.h"
|
||||||
|
#include <libraries/delay/nrf_delay.h>
|
||||||
using namespace Pinetime::Controllers;
|
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() {
|
void BrightnessController::Init() {
|
||||||
nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
|
nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
|
||||||
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
|
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
|
||||||
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
|
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);
|
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) {
|
void BrightnessController::Set(BrightnessController::Levels level) {
|
||||||
this->level = level;
|
this->level = level;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
default:
|
default:
|
||||||
case Levels::High:
|
case Levels::High:
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
ApplyBrightness(3 * timerPeriod);
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
|
|
||||||
break;
|
break;
|
||||||
case Levels::Medium:
|
case Levels::Medium:
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
ApplyBrightness(2 * timerPeriod);
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
|
||||||
break;
|
break;
|
||||||
case Levels::Low:
|
case Levels::Low:
|
||||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
ApplyBrightness(timerPeriod);
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
|
break;
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
case Levels::AlwaysOn:
|
||||||
|
ApplyBrightness(timerPeriod / 10);
|
||||||
break;
|
break;
|
||||||
case Levels::Off:
|
case Levels::Off:
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightLow);
|
ApplyBrightness(0);
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
|
|
||||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "nrf_ppi.h"
|
||||||
|
#include "nrfx_gpiote.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
class BrightnessController {
|
class BrightnessController {
|
||||||
public:
|
public:
|
||||||
enum class Levels { Off, Low, Medium, High };
|
enum class Levels { Off, AlwaysOn, Low, Medium, High };
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
void Set(Levels level);
|
void Set(Levels level);
|
||||||
|
@ -20,6 +23,25 @@ namespace Pinetime {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Levels level = Levels::High;
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,11 +242,17 @@ void DisplayApp::Refresh() {
|
||||||
RestoreBrightness();
|
RestoreBrightness();
|
||||||
break;
|
break;
|
||||||
case Messages::GoToSleep:
|
case Messages::GoToSleep:
|
||||||
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
|
||||||
brightnessController.Lower();
|
brightnessController.Lower();
|
||||||
vTaskDelay(100);
|
vTaskDelay(100);
|
||||||
}
|
}
|
||||||
|
// Don't actually turn off the display for AlwaysOn mode
|
||||||
|
if (settingsController.GetAlwaysOnDisplay()) {
|
||||||
|
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
|
||||||
|
} else {
|
||||||
|
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
|
||||||
lcd.Sleep();
|
lcd.Sleep();
|
||||||
|
}
|
||||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
||||||
state = States::Idle;
|
state = States::Idle;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -102,7 +102,9 @@ void SystemTask::Work() {
|
||||||
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
|
watchdog.Setup(7, Drivers::Watchdog::SleepBehaviour::Run, Drivers::Watchdog::HaltBehaviour::Pause);
|
||||||
watchdog.Start();
|
watchdog.Start();
|
||||||
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::ResetReasonToString(watchdog.GetResetReason()));
|
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();
|
spi.Init();
|
||||||
spiNorFlash.Init();
|
spiNorFlash.Init();
|
||||||
|
|
Loading…
Reference in a new issue