From e3afe0764ff17344b8f2d9ee94edd6e7b2579eaa Mon Sep 17 00:00:00 2001 From: mark9064 <30447455+mark9064@users.noreply.github.com> Date: Sat, 13 Apr 2024 01:20:38 +0100 Subject: [PATCH] aod: RTC based backlight control --- src/CMakeLists.txt | 1 - .../brightness/BrightnessController.cpp | 99 +++++++++---------- .../brightness/BrightnessController.h | 11 ++- src/sdk_config.h | 6 +- 4 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a07a02aa0..fd8ece62a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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" diff --git a/src/components/brightness/BrightnessController.cpp b/src/components/brightness/BrightnessController.cpp index 89af72db83..4d1eba6ac5 100644 --- a/src/components/brightness/BrightnessController.cpp +++ b/src/components/brightness/BrightnessController.cpp @@ -2,8 +2,14 @@ #include #include "displayapp/screens/Symbols.h" #include "drivers/PinMap.h" +#include 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_RTC2_BASE); +} + void BrightnessController::Init() { nrf_gpio_cfg_output(PinMap::LcdBacklightLow); nrf_gpio_cfg_output(PinMap::LcdBacklightMedium); @@ -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); @@ -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(&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(&RTC->EVENTS_COMPARE[1]), nrfx_gpiote_out_task_addr_get(pin)); + nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast(&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) { @@ -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); diff --git a/src/components/brightness/BrightnessController.h b/src/components/brightness/BrightnessController.h index adf34cf2ea..650749a83c 100644 --- a/src/components/brightness/BrightnessController.h +++ b/src/components/brightness/BrightnessController.h @@ -3,7 +3,6 @@ #include #include "nrf_ppi.h" -#include "nrfx_timer.h" #include "nrfx_gpiote.h" namespace Pinetime { @@ -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 diff --git a/src/sdk_config.h b/src/sdk_config.h index fca63ec0f3..b42b39244f 100644 --- a/src/sdk_config.h +++ b/src/sdk_config.h @@ -4678,7 +4678,7 @@ // NRFX_TIMER1_ENABLED - Enable TIMER1 instance #ifndef NRFX_TIMER1_ENABLED - #define NRFX_TIMER1_ENABLED 1 + #define NRFX_TIMER1_ENABLED 0 #endif // NRFX_TIMER2_ENABLED - Enable TIMER2 instance @@ -6319,7 +6319,7 @@ // TIMER_ENABLED - nrf_drv_timer - TIMER periperal driver - legacy layer //========================================================== #ifndef TIMER_ENABLED - #define TIMER_ENABLED 1 + #define TIMER_ENABLED 0 #endif // TIMER_DEFAULT_CONFIG_FREQUENCY - Timer frequency if in Timer mode @@ -6383,7 +6383,7 @@ // TIMER1_ENABLED - Enable TIMER1 instance #ifndef TIMER1_ENABLED - #define TIMER1_ENABLED 1 + #define TIMER1_ENABLED 0 #endif // TIMER2_ENABLED - Enable TIMER2 instance