diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d30f6..60d01e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ add_executable(basic1 games/lua_bindings.cpp games/lua_game_loader.cpp lib/st7796/st7796.c + lib/st7789/st7789.c lib/ft6336u/ft6336u.c lib/sd_card/sd_card.c display/low_level_render.cpp @@ -102,6 +103,7 @@ target_include_directories(basic1 PRIVATE ${CMAKE_CURRENT_LIST_DIR}/lib/lua ${CMAKE_CURRENT_LIST_DIR}/lib/fatfs/source ${CMAKE_CURRENT_LIST_DIR}/lib/st7796 + ${CMAKE_CURRENT_LIST_DIR}/lib/st7789 ${CMAKE_CURRENT_LIST_DIR}/lib/ft6336u ${CMAKE_CURRENT_LIST_DIR}/lib/sd_card ${CMAKE_CURRENT_LIST_DIR}/display diff --git a/basic1.cpp b/basic1.cpp index 4026387..dec24c1 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -160,56 +160,31 @@ struct GameConfig { #define DIM_TIMEOUT_MS (2 * 60 * 1000) // 2 minutes to dim #define SLEEP_TIMEOUT_MS (10 * 60 * 1000) // 10 minutes to sleep #define DIM_CHECK_INTERVAL_MS 10000 // Check every 10 seconds -#define DIM_BRIGHTNESS 5 // Dimmed brightness level (0-100) -#define NORMAL_BRIGHTNESS 100 // Normal brightness level (0-100) // Display dimming state static uint32_t last_interaction_time = 0; // Last time user interacted -static bool is_dimmed = false; // Current dimming state -static bool is_sleeping = false; // Current sleep state -static uint8_t saved_brightness = NORMAL_BRIGHTNESS; // Brightness before dimming +static bool is_idle_2min_triggered = false; // Flag for 2min trigger +static bool is_idle_10min_triggered = false; // Flag for 10min trigger static volatile bool dim_check_flag = false; // Flag set by timer to check dimming static LowLevelDisplay* global_display = nullptr; // Global display pointer for timer callback /** - * @brief Update last interaction time and restore display if dimmed + * @brief Update last interaction time and notify display driver * - * Call this whenever the user interacts with the device (touch, button press) - * to reset the dimming timer and restore display if it was dimmed/sleeping. - * - * For TFT displays: Restores brightness level - * For e-ink displays: Wakes display from sleep and refreshes screen + * Call this whenever the user interacts with the device (touch, button press). + * The display driver handles specific wake/restore logic. * * @param display Pointer to display interface */ static inline void record_user_interaction(LowLevelDisplay* display) { last_interaction_time = to_ms_since_boot(get_absolute_time()); - // If display was sleeping, wake it up - if (is_sleeping) { - if (display->get_type() == DISPLAY_TYPE_ST7796) { - LowLevelDisplayST7796* tft = static_cast(display); - tft->wake(); - tft->set_brightness(saved_brightness); - printf("TFT display woken from sleep\n"); - } else if (display->get_type() == DISPLAY_TYPE_EPAPER) { - printf("Waking e-paper display from sleep...\n"); - LowLevelDisplayEPaper* epaper = static_cast(display); - epaper->init(); - printf("E-paper display ready\n"); - } - is_sleeping = false; - is_dimmed = false; - } - // If display was dimmed, restore brightness - else if (is_dimmed) { - if (display->get_type() == DISPLAY_TYPE_ST7796) { - LowLevelDisplayST7796* tft = static_cast(display); - tft->set_brightness(saved_brightness); - printf("TFT display brightness restored\n"); - } - is_dimmed = false; - } + // Reset idle flags + is_idle_2min_triggered = false; + is_idle_10min_triggered = false; + + // Notify display driver of interaction + display->on_user_interaction(); } /** @@ -233,12 +208,10 @@ static int64_t dim_check_alarm_callback(alarm_id_t id, void *user_data) { } /** - * @brief Check if display should be dimmed and apply dimming if needed + * @brief Check if idle thresholds have been met and notify display driver * - * Checks if the timeout has elapsed since last interaction and dims/sleeps - * the display based on its type: - * - TFT displays (ST7796): Reduces brightness to minimum - * - E-ink displays: Puts display to sleep mode (turns off) + * Checks elapsed time since last interaction and calls the appropriate + * display driver methods (on_idle_2min or on_idle_10min). * * @param display Pointer to display interface */ @@ -246,33 +219,16 @@ static inline void check_and_apply_dimming(LowLevelDisplay* display) { uint32_t current_time = to_ms_since_boot(get_absolute_time()); uint32_t elapsed = current_time - last_interaction_time; - // Check for sleep timeout - if (!is_sleeping && elapsed >= SLEEP_TIMEOUT_MS) { - if (display->get_type() == DISPLAY_TYPE_ST7796) { - LowLevelDisplayST7796* tft = static_cast(display); - tft->sleep(); - printf("TFT display put to sleep after %d minutes of inactivity\n", - SLEEP_TIMEOUT_MS / 60000); - } else if (display->get_type() == DISPLAY_TYPE_EPAPER) { - LowLevelDisplayEPaper* epaper = static_cast(display); - epaper->sleep(); - printf("E-paper display put to sleep after %d minutes of inactivity\n", - SLEEP_TIMEOUT_MS / 60000); - } - is_sleeping = true; - is_dimmed = true; // Sleep implies dimmed state + // Check for 10 minute timeout (Sleep) + if (!is_idle_10min_triggered && elapsed >= SLEEP_TIMEOUT_MS) { + display->on_idle_10min(); + is_idle_10min_triggered = true; + is_idle_2min_triggered = true; // Implicitly triggered } - // Check for dim timeout - else if (!is_dimmed && !is_sleeping && elapsed >= DIM_TIMEOUT_MS) { - if (display->get_type() == DISPLAY_TYPE_ST7796) { - saved_brightness = display->get_brightness(); - LowLevelDisplayST7796* tft = static_cast(display); - tft->set_brightness(DIM_BRIGHTNESS); - printf("TFT display dimmed after %d minutes of inactivity\n", - DIM_TIMEOUT_MS / 60000); - } - // E-paper doesn't support dimming, so we skip it until sleep timeout - is_dimmed = true; + // Check for 2 minute timeout (Dim) + else if (!is_idle_2min_triggered && elapsed >= DIM_TIMEOUT_MS) { + display->on_idle_2min(); + is_idle_2min_triggered = true; } } @@ -582,11 +538,10 @@ int main() add_alarm_in_ms(DIM_CHECK_INTERVAL_MS, dim_check_alarm_callback, nullptr, true); if (display->get_type() == DISPLAY_TYPE_ST7796) { - printf("Power saving enabled: Dim after %d min, Sleep after %d min\n", + printf("Power saving: Dim at %d min, Sleep at %d min\n", DIM_TIMEOUT_MS / 60000, SLEEP_TIMEOUT_MS / 60000); - } else if (display->get_type() == DISPLAY_TYPE_EPAPER) { - printf("Auto-sleep enabled: E-paper will sleep after %d minutes of inactivity\n", - SLEEP_TIMEOUT_MS / 60000); + } else { + printf("Power saving: Sleep at %d min\n", SLEEP_TIMEOUT_MS / 60000); } printf("Dimming check timer set to %d seconds\n", DIM_CHECK_INTERVAL_MS / 1000); diff --git a/display/low_level_display.h b/display/low_level_display.h index 7f417c5..09d15ba 100644 --- a/display/low_level_display.h +++ b/display/low_level_display.h @@ -40,6 +40,11 @@ public: // Optional: Orientation control (not commonly needed for bitmap displays) virtual void set_rotation(uint8_t rotation) { (void)rotation; } + // Power saving hooks + virtual void on_idle_2min() {} + virtual void on_idle_10min() {} + virtual void on_user_interaction() {} + // Factory method - creates display based on type, using board_config.h for pins static LowLevelDisplay* create(DisplayType type, int width, int height); }; diff --git a/display/low_level_display_epaper.cpp b/display/low_level_display_epaper.cpp index 5d350df..133c3aa 100644 --- a/display/low_level_display_epaper.cpp +++ b/display/low_level_display_epaper.cpp @@ -141,3 +141,24 @@ void LowLevelDisplayEPaper::sleep() { printf("Putting e-paper display to sleep...\n"); EPD_4IN2_V2_Sleep(); } + +void LowLevelDisplayEPaper::on_idle_2min() { + // E-paper doesn't dim +} + +void LowLevelDisplayEPaper::on_idle_10min() { + if (!is_sleeping) { + sleep(); + is_sleeping = true; + printf("E-Paper: Entered sleep mode\n"); + } +} + +void LowLevelDisplayEPaper::on_user_interaction() { + if (is_sleeping) { + printf("E-Paper: Waking from sleep...\n"); + init(); // Re-initialize to wake up + is_sleeping = false; + printf("E-Paper: Ready\n"); + } +} diff --git a/display/low_level_display_epaper.h b/display/low_level_display_epaper.h index f619428..c28aca4 100644 --- a/display/low_level_display_epaper.h +++ b/display/low_level_display_epaper.h @@ -44,6 +44,14 @@ public: void clear_display(); // Full clear with refresh void full_refresh(); // Force full screen refresh (slower but removes ghosting) void sleep(); // Put display in low power mode + + // Power saving hooks + void on_idle_2min() override; + void on_idle_10min() override; + void on_user_interaction() override; + +private: + bool is_sleeping = false; }; #endif // LOW_LEVEL_DISPLAY_EPAPER_H diff --git a/display/low_level_display_st7789.cpp b/display/low_level_display_st7789.cpp index 0c48c67..80057ad 100644 --- a/display/low_level_display_st7789.cpp +++ b/display/low_level_display_st7789.cpp @@ -69,11 +69,51 @@ void LowLevelDisplayST7789::refresh() { } void LowLevelDisplayST7789::set_backlight(bool on) { - // TODO: Implement - (void)on; + set_brightness(on ? 100 : 0); +} + +void LowLevelDisplayST7789::set_brightness(uint8_t brightness) { + st7789_set_brightness(brightness); +} + +uint8_t LowLevelDisplayST7789::get_brightness() const { + return st7789_get_brightness(); } void LowLevelDisplayST7789::set_rotation(uint8_t rotation) { // TODO: Implement (void)rotation; } + +void LowLevelDisplayST7789::on_idle_2min() { + if (!is_dimmed && !is_sleeping) { + saved_brightness = get_brightness(); + set_brightness(5); // Dim to 5% + is_dimmed = true; + printf("ST7789: Dimmed to 5%%\n"); + } +} + +void LowLevelDisplayST7789::on_idle_10min() { + if (!is_sleeping) { + st7789_sleep(); + is_sleeping = true; + is_dimmed = true; // Sleep implies dimmed + printf("ST7789: Entered sleep mode\n"); + } +} + +void LowLevelDisplayST7789::on_user_interaction() { + if (is_sleeping) { + st7789_wake(); + // Restore brightness if we have a saved value, or default to 100 + set_brightness(saved_brightness > 0 ? saved_brightness : 100); + is_sleeping = false; + is_dimmed = false; + printf("ST7789: Woke from sleep\n"); + } else if (is_dimmed) { + set_brightness(saved_brightness > 0 ? saved_brightness : 100); + is_dimmed = false; + printf("ST7789: Restored brightness\n"); + } +} diff --git a/display/low_level_display_st7789.h b/display/low_level_display_st7789.h index d6b68d6..d2f7dbe 100644 --- a/display/low_level_display_st7789.h +++ b/display/low_level_display_st7789.h @@ -2,17 +2,7 @@ #define LOW_LEVEL_DISPLAY_ST7789_H #include "low_level_display.h" - -// ST7789 configuration structure (similar to ST7796) -struct st7789_config { - void* spi; // SPI instance - int gpio_din; // MOSI pin - int gpio_clk; // Clock pin - int gpio_cs; // Chip select pin - int gpio_dc; // Data/Command pin - int gpio_rst; // Reset pin - int gpio_bl; // Backlight pin -}; +#include "st7789.h" class LowLevelDisplayST7789 : public LowLevelDisplay { private: @@ -42,8 +32,22 @@ public: // Backlight control void set_backlight(bool on) override; + // Brightness control + void set_brightness(uint8_t brightness) override; + uint8_t get_brightness() const override; + // Orientation control void set_rotation(uint8_t rotation) override; + + // Power saving hooks + void on_idle_2min() override; + void on_idle_10min() override; + void on_user_interaction() override; + +private: + uint8_t saved_brightness = 100; + bool is_dimmed = false; + bool is_sleeping = false; }; #endif // LOW_LEVEL_DISPLAY_ST7789_H diff --git a/display/low_level_display_st7796.cpp b/display/low_level_display_st7796.cpp index e473605..2e170a5 100644 --- a/display/low_level_display_st7796.cpp +++ b/display/low_level_display_st7796.cpp @@ -94,3 +94,36 @@ void LowLevelDisplayST7796::set_rotation(uint8_t rotation) { // TODO: Add MADCTL register manipulation for rotation (void)rotation; } + +void LowLevelDisplayST7796::on_idle_2min() { + if (!is_dimmed && !is_sleeping) { + saved_brightness = get_brightness(); + set_brightness(5); // Dim to 5% + is_dimmed = true; + printf("TFT: Dimmed to 5%%\n"); + } +} + +void LowLevelDisplayST7796::on_idle_10min() { + if (!is_sleeping) { + sleep(); + is_sleeping = true; + is_dimmed = true; // Sleep implies dimmed + printf("TFT: Entered sleep mode\n"); + } +} + +void LowLevelDisplayST7796::on_user_interaction() { + if (is_sleeping) { + wake(); + // Restore brightness if we have a saved value, or default to 100 + set_brightness(saved_brightness > 0 ? saved_brightness : 100); + is_sleeping = false; + is_dimmed = false; + printf("TFT: Woke from sleep\n"); + } else if (is_dimmed) { + set_brightness(saved_brightness > 0 ? saved_brightness : 100); + is_dimmed = false; + printf("TFT: Restored brightness\n"); + } +} diff --git a/display/low_level_display_st7796.h b/display/low_level_display_st7796.h index a7098f5..d6beb19 100644 --- a/display/low_level_display_st7796.h +++ b/display/low_level_display_st7796.h @@ -47,6 +47,16 @@ public: // Color inversion control void set_invert_color(bool inv) { invert_color = inv; } bool get_invert_color() const { return invert_color; } + + // Power saving hooks + void on_idle_2min() override; + void on_idle_10min() override; + void on_user_interaction() override; + +private: + uint8_t saved_brightness = 100; + bool is_dimmed = false; + bool is_sleeping = false; }; #endif // LOW_LEVEL_DISPLAY_ST7796_H diff --git a/lib/st7789/st7789.c b/lib/st7789/st7789.c index f5c0cea..47d5e89 100644 --- a/lib/st7789/st7789.c +++ b/lib/st7789/st7789.c @@ -12,6 +12,7 @@ #include "hardware/gpio.h" #include "hardware/spi.h" +#include "hardware/pwm.h" #include "pico/binary_info.h" #include "pico/stdlib.h" @@ -60,6 +61,12 @@ static uint16_t height; static uint16_t x_offset; static uint16_t y_offset; +// Backlight control +static uint pwm_slice; +static uint pwm_channel; +static uint8_t current_brightness = 100; +static bool pwm_initialized = false; + static inline void cs_select() { if (config->gpio_cs >= 0) { asm volatile("nop \n nop \n nop"); @@ -173,10 +180,19 @@ void st7789_init(const struct st7789_config *c, uint16_t w, uint16_t h) { gpio_init(config->gpio_rst); gpio_set_dir(config->gpio_rst, GPIO_OUT); - // Initialize backlight pin - gpio_init(config->gpio_bl); - gpio_set_dir(config->gpio_bl, GPIO_OUT); - gpio_put(config->gpio_bl, 1); // Turn on backlight + // Initialize backlight pin with PWM + if (config->gpio_bl >= 0) { + gpio_set_function(config->gpio_bl, GPIO_FUNC_PWM); + pwm_slice = pwm_gpio_to_slice_num(config->gpio_bl); + pwm_channel = pwm_gpio_to_channel(config->gpio_bl); + + // Set PWM frequency to ~1kHz + pwm_set_wrap(pwm_slice, 65535); + pwm_set_chan_level(pwm_slice, pwm_channel, 65535); // 100% duty cycle + pwm_set_enabled(pwm_slice, true); + + pwm_initialized = true; + } // Reset display reset_pulse(); @@ -403,3 +419,48 @@ void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16 } } } + +void st7789_set_brightness(uint8_t brightness) { + if (!pwm_initialized || config->gpio_bl < 0) return; + + // Clamp brightness + if (brightness > 100) brightness = 100; + + current_brightness = brightness; + + // Convert 0-100 to 0-65535 + uint16_t level = (uint16_t)((brightness * 65535) / 100); + pwm_set_chan_level(pwm_slice, pwm_channel, level); +} + +uint8_t st7789_get_brightness(void) { + return current_brightness; +} + +void st7789_sleep(void) { + // Turn off backlight + if (pwm_initialized && config->gpio_bl >= 0) { + pwm_set_chan_level(pwm_slice, pwm_channel, 0); + } + + // Display off + write_command(ST7789_DISPOFF); + sleep_ms(10); + + // Sleep in + write_command(ST7789_SLPIN); + sleep_ms(120); +} + +void st7789_wake(void) { + // Sleep out + write_command(ST7789_SLPOUT); + sleep_ms(120); + + // Display on + write_command(ST7789_DISPON); + sleep_ms(10); + + // Restore brightness + st7789_set_brightness(current_brightness); +} diff --git a/lib/st7789/st7789.h b/lib/st7789/st7789.h index dbc83e2..7d38787 100644 --- a/lib/st7789/st7789.h +++ b/lib/st7789/st7789.h @@ -38,6 +38,11 @@ void st7789_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color); void st7789_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color); void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); +void st7789_sleep(void); +void st7789_wake(void); +void st7789_set_brightness(uint8_t brightness); +uint8_t st7789_get_brightness(void); + #ifdef __cplusplus } #endif