Skip to content

Commit

Permalink
aod: RTC based backlight control
Browse files Browse the repository at this point in the history
  • Loading branch information
mark9064 committed Jun 19, 2024
1 parent be98013 commit e3afe07
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 56 deletions.
1 change: 0 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ set(SDK_SOURCE_FILES
"${NRF5_SDK_PATH}/integration/nrfx/legacy/nrf_drv_clock.h"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_clock.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_gpiote.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_timer.c"
"${NRF5_SDK_PATH}/modules/nrfx/soc/nrfx_atomic.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_saadc.c"

Expand Down
99 changes: 49 additions & 50 deletions src/components/brightness/BrightnessController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
#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);
Expand All @@ -13,55 +19,56 @@ void BrightnessController::Init() {
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);

timer = NRFX_TIMER_INSTANCE(1);
nrfx_timer_config_t timerCfg = {.frequency = NRF_TIMER_FREQ_1MHz,
.mode = NRF_TIMER_MODE_TIMER,
.bit_width = NRF_TIMER_BIT_WIDTH_32,
.interrupt_priority = 6,
.p_context = nullptr};
// Callback will never fire, use empty expression
APP_ERROR_CHECK(nrfx_timer_init(&timer, &timerCfg, [](auto, auto) {
}));
nrfx_timer_extended_compare(&timer,
NRF_TIMER_CC_CHANNEL1,
nrfx_timer_us_to_ticks(&timer, 1000),
NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK,
false);
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 val) {
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 (val > 2000) {
val -= 2000;
if (rawBrightness > 2 * timerPeriod) {
rawBrightness -= 2 * timerPeriod;
pin = PinMap::LcdBacklightHigh;
} else if (val > 1000) {
val -= 1000;
} else if (rawBrightness > timerPeriod) {
rawBrightness -= timerPeriod;
pin = PinMap::LcdBacklightMedium;
} else {
pin = PinMap::LcdBacklightLow;
}
if (val == 1000 || val == 0) {
if (rawBrightness == timerPeriod || rawBrightness == 0) {
if (lastPin != UNSET) {
nrfx_timer_disable(&timer);
nrfx_timer_clear(&timer);
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 (val == 0) {
if (rawBrightness == 0) {
nrf_gpio_pin_set(pin);
} else {
nrf_gpio_pin_clear(pin);
}
} else {
uint32_t newThreshold = nrfx_timer_us_to_ticks(&timer, val);
// 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) {
nrfx_timer_disable(&timer);
nrfx_timer_clear(&timer);
RTC->TASKS_STOP = 1;
nrf_delay_us(rtcStopTime);
nrf_ppi_channel_disable(ppiBacklightOff);
nrf_ppi_channel_disable(ppiBacklightOn);
nrfx_gpiote_out_uninit(lastPin);
Expand All @@ -73,33 +80,25 @@ void BrightnessController::ApplyBrightness(uint16_t val) {
APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg));
nrfx_gpiote_out_task_enable(pin);
nrf_ppi_channel_endpoint_setup(ppiBacklightOff,
nrfx_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0),
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[0]),
nrfx_gpiote_out_task_addr_get(pin));
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
nrfx_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE1),
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 {
// Pause the timer, check where it is before changing the threshold
// If we have already triggered CC0 and then move CC0 into the future
// without triggering CC1 first, the modulation will be inverted
// (e.g on 100us off 900us becomes off 100us on 900us)
nrfx_timer_pause(&timer);
uint32_t currentCycle = nrfx_timer_capture(&timer, NRF_TIMER_CC_CHANNEL2);
uint32_t oldThreshold = nrfx_timer_capture_get(&timer, NRF_TIMER_CC_CHANNEL0);
// If the new threshold now in future and we have already triggered the old threshold, switch backlight back on
// If we haven't yet triggered CC0 and then move CC0 into the past, we get the same issue: switch the backlight off
if ((currentCycle >= oldThreshold && newThreshold > currentCycle) || (currentCycle < oldThreshold && newThreshold <= currentCycle)) {
nrfx_gpiote_out_task_trigger(pin);
}
}
nrfx_timer_compare(&timer, NRF_TIMER_CC_CHANNEL0, newThreshold, false);
if (lastPin != pin) {
nrfx_timer_enable(&timer);
} else {
nrfx_timer_resume(&timer);
// 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) {
Expand All @@ -122,16 +121,16 @@ void BrightnessController::Set(BrightnessController::Levels level) {
switch (level) {
default:
case Levels::High:
ApplyBrightness(3000);
ApplyBrightness(3 * timerPeriod);
break;
case Levels::Medium:
ApplyBrightness(2000);
ApplyBrightness(2 * timerPeriod);
break;
case Levels::Low:
ApplyBrightness(1000);
ApplyBrightness(timerPeriod);
break;
case Levels::AlwaysOn:
ApplyBrightness(100);
ApplyBrightness(timerPeriod / 10);
break;
case Levels::Off:
ApplyBrightness(0);
Expand Down
11 changes: 9 additions & 2 deletions src/components/brightness/BrightnessController.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <cstdint>

#include "nrf_ppi.h"
#include "nrfx_timer.h"
#include "nrfx_gpiote.h"

namespace Pinetime {
Expand All @@ -26,7 +25,15 @@ namespace Pinetime {
Levels level = Levels::High;
static constexpr uint8_t UNSET = UINT8_MAX;
uint8_t lastPin = UNSET;
nrfx_timer_t timer;
// 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
Expand Down
6 changes: 3 additions & 3 deletions src/sdk_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4678,7 +4678,7 @@
// <q> NRFX_TIMER1_ENABLED - Enable TIMER1 instance

#ifndef NRFX_TIMER1_ENABLED
#define NRFX_TIMER1_ENABLED 1
#define NRFX_TIMER1_ENABLED 0
#endif

// <q> NRFX_TIMER2_ENABLED - Enable TIMER2 instance
Expand Down Expand Up @@ -6319,7 +6319,7 @@
// <e> TIMER_ENABLED - nrf_drv_timer - TIMER periperal driver - legacy layer
//==========================================================
#ifndef TIMER_ENABLED
#define TIMER_ENABLED 1
#define TIMER_ENABLED 0
#endif
// <o> TIMER_DEFAULT_CONFIG_FREQUENCY - Timer frequency if in Timer mode

Expand Down Expand Up @@ -6383,7 +6383,7 @@
// <q> TIMER1_ENABLED - Enable TIMER1 instance

#ifndef TIMER1_ENABLED
#define TIMER1_ENABLED 1
#define TIMER1_ENABLED 0
#endif

// <q> TIMER2_ENABLED - Enable TIMER2 instance
Expand Down

0 comments on commit e3afe07

Please sign in to comment.