How the 160x80 LCD Backlight Is Managed in the RPi Pico WAV Player
The backlight is controlled via PWM on the BLK pin, dynamically switching between high and low brightness levels based on UI idle time and user-configurable thresholds.
The elehobica/rpi_pico_wav_player project drives a Waveshare 160x80 LCD display (RP2040-LCD-0.96) using a sophisticated PWM-based backlight management system. This implementation automatically dims the display after periods of inactivity while providing full user control over brightness levels and timeout durations through the configuration menu.
PWM-Based Backlight Control Architecture
GPIO Pin Assignment in LcdCanvas.cpp
During initialization, the driver selects the correct backlight pin based on the board variant. In src/LcdCanvas.cpp, the code assigns pin_blk to the appropriate GPIO for Waveshare displays:
// LcdCanvas.cpp – board-specific pin mapping
case WAVESHARE_RP2040_LCD_096: // fall-through
case WAVESHARE_RP2350_LCD_096:
pin_blk = PIN_LCD_BLK_WAVESHARE; // backlight pin for Waveshare LCD
break;
default:
pin_blk = PIN_LCD_BLK_DEFAULT; // fallback for other boards
(See src/LcdCanvas.cpp lines 81-91)
Hardware PWM Interface
The actual PWM output is handled by the OLED_BLK_Set_PWM(uint16_t val) function provided by the LCD driver. This hardware abstraction layer allows the power management logic to set duty cycles from 0 (off) to 255 (full brightness) without directly manipulating GPIO registers.
Dynamic Brightness Management
Idle Detection and Auto-Dimming Logic
The core brightness algorithm resides in pm_backlight_update() within src/power_manage.cpp. This function compares the current UI idle count against the user-configured timeout to determine backlight intensity:
void pm_backlight_update()
{
const int LoopCycleMs = UIMode::UpdateCycleMs; // 50 ms per UI loop
const int OneSec = 1000 / LoopCycleMs;
uint32_t bl_val;
ConfigMenu& cfg = ConfigMenu::instance();
if (ui_get_idle_count() < cfg.get(ConfigMenuId::DISPLAY_TIME_TO_BACKLIGHT_LOW) * OneSec)
bl_val = cfg.get(ConfigMenuId::DISPLAY_BACKLIGHT_HIGH_LEVEL);
else
bl_val = cfg.get(ConfigMenuId::DISPLAY_BACKLIGHT_LOW_LEVEL);
OLED_BLK_Set_PWM(bl_val); // set PWM on BLK pin
}
(See src/power_manage.cpp lines 108-119)
The logic calculates idle time using a 50-millisecond UI loop cycle. When user activity is detected within the timeout window, the system applies the high brightness PWM value; once idle time exceeds the threshold, it transitions automatically to the low brightness level.
User-Configurable Parameters
Brightness levels and timing are exposed through the ConfigMenu class. The system stores three critical parameters identified by ConfigMenuId enumerations:
- DISPLAY_BACKLIGHT_HIGH_LEVEL: PWM duty cycle for active use (default typically 200/255)
- DISPLAY_BACKLIGHT_LOW_LEVEL: PWM duty cycle for idle state (default typically 30/255)
- DISPLAY_TIME_TO_BACKLIGHT_LOW: Timeout in seconds before dimming occurs
Power State Transitions
Initialization to High Brightness
During power-up sequence in src/power_manage.cpp, the backlight initializes immediately to the configured high level to ensure visibility during boot:
ConfigMenu& cfg = ConfigMenu::instance();
OLED_BLK_Set_PWM(cfg.get(ConfigMenuId::DISPLAY_BACKLIGHT_HIGH_LEVEL));
(See src/power_manage.cpp lines 196-200)
Dormant Mode Shutdown
When the device enters low-power dormant sleep mode, the system forces the backlight completely off to minimize current draw:
OLED_BLK_Set_PWM(0);
(See src/power_manage.cpp lines 299-300)
This ensures the 160x80 LCD consumes zero backlight power during extended sleep periods.
Implementation Examples
Updating Backlight in the Main UI Loop
Integrate automatic brightness control by calling the update function within the primary execution loop:
// Typical UI loop implementation
while (true) {
// Handle user input and display updates
process_ui_events();
// Adjust PWM based on idle time
pm_backlight_update();
sleep_ms(50); // UIMode::UpdateCycleMs
}
Manual PWM Override
For testing or custom brightness profiles, bypass the idle detection and write PWM values directly:
#include "power_manage.h"
// Force maximum brightness (100% duty cycle)
OLED_BLK_Set_PWM(255);
// Force minimum visible brightness (~10% duty cycle)
OLED_BLK_Set_PWM(25);
Modifying Configuration via ConfigMenu
Programmatically adjust user preferences for backlight behavior:
ConfigMenu& cfg = ConfigMenu::instance();
// Set high brightness to ~78% duty cycle (200/255)
cfg.set(ConfigMenuId::DISPLAY_BACKLIGHT_HIGH_LEVEL, 200);
// Set dimmed brightness to ~12% duty cycle (30/255)
cfg.set(ConfigMenuId::DISPLAY_BACKLIGHT_LOW_LEVEL, 30);
// Set 5-second timeout before auto-dimming
cfg.set(ConfigMenuId::DISPLAY_TIME_TO_BACKLIGHT_LOW, 5);
Summary
- The 160x80 LCD backlight is driven by PWM on the BLK pin assigned in
LcdCanvas.cppbased on board variant detection. - Dynamic brightness is managed by
pm_backlight_update()inpower_manage.cpp, which monitorsui_get_idle_count()against configurable timeout thresholds. - Three configuration parameters control the behavior: high level PWM, low level PWM, and seconds-to-dim timeout via
ConfigMenu. - The system initializes to high brightness on boot and forces PWM to zero when entering dormant sleep mode.
- Hardware abstraction through
OLED_BLK_Set_PWM()allows duty cycle control from 0-255 without direct GPIO manipulation.
Frequently Asked Questions
Which GPIO pin controls the 160x80 LCD backlight?
The backlight uses the BLK pin defined as PIN_LCD_BLK_WAVESHARE for Waveshare RP2040/RP2350 LCD boards, or PIN_LCD_BLK_DEFAULT for other configurations. This assignment occurs during LCD initialization in src/LcdCanvas.cpp at lines 81-91 according to the specific board variant detected at compile time.
How does the backlight automatically dim after inactivity?
The pm_backlight_update() function checks ui_get_idle_count() against the user-configured DISPLAY_TIME_TO_BACKLIGHT_LOW value. If the idle counter exceeds the timeout threshold multiplied by the 50-millisecond UI loop cycle, the system switches from DISPLAY_BACKLIGHT_HIGH_LEVEL to DISPLAY_BACKLIGHT_LOW_LEVEL PWM values. This logic runs continuously in the main UI loop.
Can I disable the auto-dimming feature entirely?
Yes. Set DISPLAY_TIME_TO_BACKLIGHT_LOW to a very large value (e.g., 3600 seconds) via the ConfigMenu, or modify pm_backlight_update() to always use the high brightness level. Alternatively, you can call OLED_BLK_Set_PWM() directly with a constant value to override the automatic management.
What happens to the backlight when the device enters sleep mode?
When the Raspberry Pi Pico enters dormant sleep mode through the power management system, the code explicitly calls OLED_BLK_Set_PWM(0) in src/power_manage.cpp (lines 299-300). This forces the backlight completely off to maximize power savings during sleep, regardless of previous brightness settings.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →