Add hardware power-off and retro prompt selector
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
# Waveshare ESP32-S3-Touch-LCD-3.49 - C64 READY
|
# Waveshare ESP32-S3-Touch-LCD-3.49 - C64 READY
|
||||||
|
|
||||||
PlatformIO starter showing Commodore 64-style READY. with a blinking cursor.
|
PlatformIO retro prompt selector with six classic computer looks and a blinking cursor.
|
||||||
|
|
||||||
|
- Tap **BOOT** to cycle through Commodore 64, Sepia UNIX, Amiga Workbench,
|
||||||
|
Apple II, MS-DOS, and BBC Micro prompts.
|
||||||
|
- Hold **PWR** for 1.2 seconds to turn the device off. Battery operation uses
|
||||||
|
the board's TCA9554 power latch; USB operation falls back to deep sleep.
|
||||||
|
- While connected to USB, tap **PWR** to wake from deep sleep.
|
||||||
|
|
||||||
Hardware: ESP32-S3R8, 16 MB flash, 8 MB PSRAM, 172x640 AXS15231B QSPI LCD.
|
Hardware: ESP32-S3R8, 16 MB flash, 8 MB PSRAM, 172x640 AXS15231B QSPI LCD.
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,7 @@ build_flags =
|
|||||||
-DLV_COLOR_DEPTH=16
|
-DLV_COLOR_DEPTH=16
|
||||||
-DLV_COLOR_16_SWAP=1
|
-DLV_COLOR_16_SWAP=1
|
||||||
-DLV_FONT_MONTSERRAT_48=1
|
-DLV_FONT_MONTSERRAT_48=1
|
||||||
|
-DLV_FONT_MONTSERRAT_24=1
|
||||||
|
-DLV_FONT_MONTSERRAT_20=1
|
||||||
lib_deps =
|
lib_deps =
|
||||||
lvgl/lvgl@8.4.0
|
lvgl/lvgl@8.4.0
|
||||||
|
|||||||
+90
-64
@@ -1,81 +1,107 @@
|
|||||||
#include "c64_ui.h"
|
#include "c64_ui.h"
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <stdint.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define C64_BLUE lv_color_make(64, 49, 141)
|
|
||||||
#define C64_LIGHT_BLUE lv_color_make(120, 105, 196)
|
|
||||||
#define PIXEL_SCALE 6
|
|
||||||
#define CELL_SIZE (8 * PIXEL_SCALE)
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char character;
|
const char *name;
|
||||||
uint8_t rows[8];
|
const char *prompt;
|
||||||
} c64_glyph_t;
|
uint32_t background;
|
||||||
|
uint32_t foreground;
|
||||||
|
} retro_theme_t;
|
||||||
|
|
||||||
/* Exact uppercase/graphics glyph rows from the C64 system character ROM. */
|
static const retro_theme_t themes[] = {
|
||||||
static const c64_glyph_t glyphs[] = {
|
{"COMMODORE 64", "READY.", 0x40318d, 0x7869c4},
|
||||||
{'R', {0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00}},
|
{"SEPIA UNIX", "Last login: Mon Aug 3 08:00:00\n\nhelios@unix:~$",
|
||||||
{'E', {0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00}},
|
0x302314, 0xe1bf85},
|
||||||
{'A', {0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00}},
|
{"AMIGA WORKBENCH 3.1", "AmigaDOS\nCopyright 1985-1993 Commodore-Amiga\n\n1.System3.1:>",
|
||||||
{'D', {0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00}},
|
0x0c2c7e, 0xffffff},
|
||||||
{'Y', {0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00}},
|
{"APPLE II", "APPLE ][\n\n]CALL -151\n*",
|
||||||
{'.', {0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00}},
|
0x041405, 0x52ff58},
|
||||||
|
{"MS-DOS 6.22", "Microsoft(R) MS-DOS(R) Version 6.22\n\nC:\\>",
|
||||||
|
0x000000, 0xc0c0c0},
|
||||||
|
{"BBC MICRO", "BBC Computer 32K\n\nBASIC\n>",
|
||||||
|
0x000000, 0xffffff},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t *find_glyph(char character) {
|
static unsigned theme_index;
|
||||||
for (unsigned i = 0; i < sizeof(glyphs) / sizeof(glyphs[0]); ++i) {
|
static lv_obj_t *cursor_label;
|
||||||
if (glyphs[i].character == character) return glyphs[i].rows;
|
static lv_timer_t *cursor_timer;
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void draw_glyph(lv_obj_t *parent, char character, lv_coord_t x, lv_coord_t y) {
|
|
||||||
const uint8_t *rows = find_glyph(character);
|
|
||||||
if (!rows) return;
|
|
||||||
|
|
||||||
for (int row = 0; row < 8; ++row) {
|
|
||||||
for (int column = 0; column < 8; ++column) {
|
|
||||||
if ((rows[row] & (0x80 >> column)) == 0) continue;
|
|
||||||
lv_obj_t *pixel = lv_obj_create(parent);
|
|
||||||
lv_obj_remove_style_all(pixel);
|
|
||||||
lv_obj_set_size(pixel, PIXEL_SCALE, PIXEL_SCALE);
|
|
||||||
lv_obj_set_pos(pixel, x + column * PIXEL_SCALE, y + row * PIXEL_SCALE);
|
|
||||||
lv_obj_set_style_bg_color(pixel, C64_LIGHT_BLUE, LV_PART_MAIN);
|
|
||||||
lv_obj_set_style_bg_opa(pixel, LV_OPA_COVER, LV_PART_MAIN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool cursor_visible = true;
|
static bool cursor_visible = true;
|
||||||
|
|
||||||
static void cursor_blink(lv_timer_t *timer) {
|
static void cursor_blink(lv_timer_t *timer)
|
||||||
|
{
|
||||||
|
(void)timer;
|
||||||
|
if (cursor_label == NULL) return;
|
||||||
cursor_visible = !cursor_visible;
|
cursor_visible = !cursor_visible;
|
||||||
lv_obj_t *cursor = (lv_obj_t *)timer->user_data;
|
lv_obj_set_style_text_opa(cursor_label,
|
||||||
lv_obj_set_style_bg_opa(cursor,
|
cursor_visible ? LV_OPA_COVER : LV_OPA_20,
|
||||||
cursor_visible ? LV_OPA_COVER : LV_OPA_TRANSP,
|
LV_PART_MAIN);
|
||||||
LV_PART_MAIN);
|
|
||||||
lv_timer_set_period(timer, 400);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void c64_ui_create(void) {
|
static void render_theme(void)
|
||||||
static const char text[] = "READY.";
|
{
|
||||||
|
const retro_theme_t *theme = &themes[theme_index];
|
||||||
lv_obj_t *screen = lv_scr_act();
|
lv_obj_t *screen = lv_scr_act();
|
||||||
lv_obj_set_style_bg_color(screen, C64_BLUE, LV_PART_MAIN);
|
if (cursor_timer != NULL) {
|
||||||
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, LV_PART_MAIN);
|
lv_timer_del(cursor_timer);
|
||||||
lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE);
|
cursor_timer = NULL;
|
||||||
|
|
||||||
const lv_coord_t start_x = 48;
|
|
||||||
const lv_coord_t text_y = 24;
|
|
||||||
for (unsigned i = 0; i < sizeof(text) - 1; ++i) {
|
|
||||||
draw_glyph(screen, text[i], start_x + (lv_coord_t)i * CELL_SIZE, text_y);
|
|
||||||
}
|
}
|
||||||
|
cursor_label = NULL;
|
||||||
|
lv_obj_clean(screen);
|
||||||
|
lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_set_style_bg_color(screen, lv_color_hex(theme->background), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, LV_PART_MAIN);
|
||||||
|
|
||||||
lv_obj_t *cursor = lv_obj_create(screen);
|
lv_obj_t *header = lv_label_create(screen);
|
||||||
lv_obj_remove_style_all(cursor);
|
lv_label_set_text(header, theme->name);
|
||||||
lv_obj_set_size(cursor, CELL_SIZE, CELL_SIZE);
|
lv_obj_set_pos(header, 28, 14);
|
||||||
lv_obj_set_pos(cursor, start_x, text_y + CELL_SIZE + 16);
|
lv_obj_set_style_text_color(header, lv_color_hex(theme->foreground), LV_PART_MAIN);
|
||||||
lv_obj_set_style_bg_color(cursor, C64_LIGHT_BLUE, LV_PART_MAIN);
|
lv_obj_set_style_text_font(header, &lv_font_montserrat_24, LV_PART_MAIN);
|
||||||
lv_obj_set_style_bg_opa(cursor, LV_OPA_COVER, LV_PART_MAIN);
|
|
||||||
|
|
||||||
lv_timer_create(cursor_blink, 400, cursor);
|
lv_obj_t *prompt = lv_label_create(screen);
|
||||||
|
lv_label_set_text(prompt, theme->prompt);
|
||||||
|
lv_obj_set_pos(prompt, 28, 50);
|
||||||
|
lv_obj_set_width(prompt, 580);
|
||||||
|
lv_obj_set_style_text_color(prompt, lv_color_hex(theme->foreground), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_text_font(prompt, &lv_font_montserrat_20, LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_text_line_space(prompt, 4, LV_PART_MAIN);
|
||||||
|
|
||||||
|
cursor_label = lv_label_create(screen);
|
||||||
|
lv_label_set_text(cursor_label, "_");
|
||||||
|
lv_obj_set_pos(cursor_label, 590, 128);
|
||||||
|
lv_obj_set_style_text_color(cursor_label, lv_color_hex(theme->foreground), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_text_font(cursor_label, &lv_font_montserrat_20, LV_PART_MAIN);
|
||||||
|
|
||||||
|
cursor_visible = true;
|
||||||
|
cursor_timer = lv_timer_create(cursor_blink, 500, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void c64_ui_create(void)
|
||||||
|
{
|
||||||
|
theme_index = 0;
|
||||||
|
render_theme();
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_ui_next(void)
|
||||||
|
{
|
||||||
|
theme_index = (theme_index + 1) % (sizeof(themes) / sizeof(themes[0]));
|
||||||
|
render_theme();
|
||||||
|
}
|
||||||
|
|
||||||
|
void retro_ui_show_powering_off(void)
|
||||||
|
{
|
||||||
|
lv_obj_t *screen = lv_scr_act();
|
||||||
|
if (cursor_timer != NULL) {
|
||||||
|
lv_timer_del(cursor_timer);
|
||||||
|
cursor_timer = NULL;
|
||||||
|
}
|
||||||
|
cursor_label = NULL;
|
||||||
|
lv_obj_clean(screen);
|
||||||
|
lv_obj_set_style_bg_color(screen, lv_color_black(), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, LV_PART_MAIN);
|
||||||
|
lv_obj_t *label = lv_label_create(screen);
|
||||||
|
lv_label_set_text(label, "POWERING OFF...");
|
||||||
|
lv_obj_center(label);
|
||||||
|
lv_obj_set_style_text_color(label, lv_color_white(), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_text_font(label, &lv_font_montserrat_24, LV_PART_MAIN);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
void c64_ui_create(void);
|
void c64_ui_create(void);
|
||||||
|
void retro_ui_next(void);
|
||||||
|
void retro_ui_show_powering_off(void);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
static i2c_master_bus_handle_t user_i2c_port0_handle = NULL;
|
static i2c_master_bus_handle_t user_i2c_port0_handle = NULL;
|
||||||
static i2c_master_bus_handle_t user_i2c_port1_handle = NULL;
|
static i2c_master_bus_handle_t user_i2c_port1_handle = NULL;
|
||||||
|
static i2c_master_dev_handle_t power_expander_handle = NULL;
|
||||||
i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
|
i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
|
||||||
i2c_master_dev_handle_t rtc_dev_handle = NULL;
|
i2c_master_dev_handle_t rtc_dev_handle = NULL;
|
||||||
i2c_master_dev_handle_t imu_dev_handle = NULL;
|
i2c_master_dev_handle_t imu_dev_handle = NULL;
|
||||||
@@ -52,6 +53,39 @@ void i2c_master_Init(void)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool power_latch_init(void)
|
||||||
|
{
|
||||||
|
if (power_expander_handle == NULL) {
|
||||||
|
i2c_device_config_t dev_cfg = {
|
||||||
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||||
|
.device_address = 0x20,
|
||||||
|
.scl_speed_hz = 300000,
|
||||||
|
};
|
||||||
|
if (i2c_master_bus_add_device(user_i2c_port0_handle, &dev_cfg,
|
||||||
|
&power_expander_handle) != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t output = 0xff;
|
||||||
|
uint8_t direction = 0xff;
|
||||||
|
i2c_read_buff(power_expander_handle, 0x01, &output, 1);
|
||||||
|
i2c_read_buff(power_expander_handle, 0x03, &direction, 1);
|
||||||
|
output |= (1U << 6);
|
||||||
|
direction &= ~(1U << 6);
|
||||||
|
return i2c_write_buff(power_expander_handle, 0x01, &output, 1) == ESP_OK &&
|
||||||
|
i2c_write_buff(power_expander_handle, 0x03, &direction, 1) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_latch_off(void)
|
||||||
|
{
|
||||||
|
if (power_expander_handle == NULL) return;
|
||||||
|
uint8_t output = 0xff;
|
||||||
|
i2c_read_buff(power_expander_handle, 0x01, &output, 1);
|
||||||
|
output &= ~(1U << 6);
|
||||||
|
i2c_write_buff(power_expander_handle, 0x01, &output, 1);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t i2c_write_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len)
|
uint8_t i2c_write_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len)
|
||||||
{
|
{
|
||||||
uint8_t ret;
|
uint8_t ret;
|
||||||
|
|||||||
+4
-1
@@ -1,5 +1,6 @@
|
|||||||
#ifndef I2C_BSP_H
|
#ifndef I2C_BSP_H
|
||||||
#define I2C_BSP_H
|
#define I2C_BSP_H
|
||||||
|
#include <stdbool.h>
|
||||||
#include "driver/i2c_master.h"
|
#include "driver/i2c_master.h"
|
||||||
|
|
||||||
extern i2c_master_dev_handle_t disp_touch_dev_handle;
|
extern i2c_master_dev_handle_t disp_touch_dev_handle;
|
||||||
@@ -11,6 +12,8 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void i2c_master_Init(void);
|
void i2c_master_Init(void);
|
||||||
|
bool power_latch_init(void);
|
||||||
|
void power_latch_off(void);
|
||||||
uint8_t i2c_write_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len);
|
uint8_t i2c_write_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len);
|
||||||
uint8_t i2c_master_write_read_dev(i2c_master_dev_handle_t dev_handle,uint8_t *writeBuf,uint8_t writeLen,uint8_t *readBuf,uint8_t readLen);
|
uint8_t i2c_master_write_read_dev(i2c_master_dev_handle_t dev_handle,uint8_t *writeBuf,uint8_t writeLen,uint8_t *readBuf,uint8_t readLen);
|
||||||
uint8_t i2c_read_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len);
|
uint8_t i2c_read_buff(i2c_master_dev_handle_t dev_handle,int reg,uint8_t *buf,uint8_t len);
|
||||||
@@ -20,4 +23,4 @@ uint8_t i2c_master_touch_write_read(i2c_master_dev_handle_t dev_handle,uint8_t *
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+7
-7
@@ -120,13 +120,13 @@ static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool example_lvgl_lock(int timeout_ms)
|
bool lvgl_port_lock(int timeout_ms)
|
||||||
{
|
{
|
||||||
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
|
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
|
||||||
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
|
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void example_lvgl_unlock(void)
|
void lvgl_port_unlock(void)
|
||||||
{
|
{
|
||||||
assert(lvgl_mux && "bsp_display_start must be called first");
|
assert(lvgl_mux && "bsp_display_start must be called first");
|
||||||
xSemaphoreGive(lvgl_mux);
|
xSemaphoreGive(lvgl_mux);
|
||||||
@@ -137,11 +137,11 @@ void example_lvgl_port_task(void *arg)
|
|||||||
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
|
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
|
||||||
for(;;)
|
for(;;)
|
||||||
{
|
{
|
||||||
if (example_lvgl_lock(-1))
|
if (lvgl_port_lock(-1))
|
||||||
{
|
{
|
||||||
task_delay_ms = lv_timer_handler();
|
task_delay_ms = lv_timer_handler();
|
||||||
//Release the mutex
|
//Release the mutex
|
||||||
example_lvgl_unlock();
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS)
|
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS)
|
||||||
{
|
{
|
||||||
@@ -260,9 +260,9 @@ void lvgl_port_init(void)
|
|||||||
lvgl_mux = xSemaphoreCreateMutex();
|
lvgl_mux = xSemaphoreCreateMutex();
|
||||||
assert(lvgl_mux);
|
assert(lvgl_mux);
|
||||||
xTaskCreatePinnedToCore(example_lvgl_port_task, "LVGL", 4000, NULL, 4, NULL,0); //运行于内核_0
|
xTaskCreatePinnedToCore(example_lvgl_port_task, "LVGL", 4000, NULL, 4, NULL,0); //运行于内核_0
|
||||||
if (example_lvgl_lock(-1))
|
if (lvgl_port_lock(-1))
|
||||||
{
|
{
|
||||||
c64_ui_create();
|
c64_ui_create();
|
||||||
example_lvgl_unlock();
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#ifndef LVGL_PORT_H
|
#ifndef LVGL_PORT_H
|
||||||
#define LVGL_PORT_H
|
#define LVGL_PORT_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -9,6 +10,8 @@ extern "C" {
|
|||||||
|
|
||||||
|
|
||||||
void lvgl_port_init(void);
|
void lvgl_port_init(void);
|
||||||
|
bool lvgl_port_lock(int timeout_ms);
|
||||||
|
void lvgl_port_unlock(void);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
+87
-2
@@ -1,6 +1,91 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "esp_sleep.h"
|
||||||
#include "i2c_bsp.h"
|
#include "i2c_bsp.h"
|
||||||
#include "lvgl_port.h"
|
#include "lvgl_port.h"
|
||||||
|
#include "c64_ui.h"
|
||||||
#include "lcd_bl_bsp/lcd_bl_pwm_bsp.h"
|
#include "lcd_bl_bsp/lcd_bl_pwm_bsp.h"
|
||||||
void setup(){Serial.begin(115200);i2c_master_Init();lvgl_port_init();lcd_bl_pwm_bsp_init(LCD_PWM_MODE_255);Serial.println("READY.");}
|
|
||||||
void loop(){delay(1000);}
|
static constexpr gpio_num_t BOOT_BUTTON = GPIO_NUM_0;
|
||||||
|
static constexpr gpio_num_t POWER_BUTTON = GPIO_NUM_16;
|
||||||
|
static constexpr uint32_t DEBOUNCE_MS = 35;
|
||||||
|
static constexpr uint32_t POWER_HOLD_MS = 1200;
|
||||||
|
|
||||||
|
struct ButtonState {
|
||||||
|
gpio_num_t pin;
|
||||||
|
bool raw;
|
||||||
|
bool stable;
|
||||||
|
uint32_t changed_at;
|
||||||
|
uint32_t pressed_at;
|
||||||
|
bool long_handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ButtonState boot_button{BOOT_BUTTON, true, true, 0, 0, false};
|
||||||
|
static ButtonState power_button{POWER_BUTTON, true, true, 0, 0, false};
|
||||||
|
|
||||||
|
static void shutdown_device()
|
||||||
|
{
|
||||||
|
if (lvgl_port_lock(250)) {
|
||||||
|
retro_ui_show_powering_off();
|
||||||
|
lvgl_port_unlock();
|
||||||
|
}
|
||||||
|
delay(250);
|
||||||
|
setUpduty(LCD_PWM_MODE_0);
|
||||||
|
power_latch_off();
|
||||||
|
|
||||||
|
// Battery power is now removed. On USB, wait for release and use deep sleep.
|
||||||
|
while (gpio_get_level(POWER_BUTTON) == 0) delay(10);
|
||||||
|
esp_sleep_enable_ext0_wakeup(POWER_BUTTON, 0);
|
||||||
|
esp_deep_sleep_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool update_button(ButtonState &button)
|
||||||
|
{
|
||||||
|
const uint32_t now = millis();
|
||||||
|
const bool raw = gpio_get_level(button.pin) != 0;
|
||||||
|
if (raw != button.raw) {
|
||||||
|
button.raw = raw;
|
||||||
|
button.changed_at = now;
|
||||||
|
}
|
||||||
|
if (raw != button.stable && now - button.changed_at >= DEBOUNCE_MS) {
|
||||||
|
button.stable = raw;
|
||||||
|
if (!raw) {
|
||||||
|
button.pressed_at = now;
|
||||||
|
button.long_handled = false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
i2c_master_Init();
|
||||||
|
power_latch_init();
|
||||||
|
pinMode(BOOT_BUTTON, INPUT_PULLUP);
|
||||||
|
pinMode(POWER_BUTTON, INPUT_PULLUP);
|
||||||
|
lvgl_port_init();
|
||||||
|
lcd_bl_pwm_bsp_init(LCD_PWM_MODE_255);
|
||||||
|
Serial.println("Retro prompt selector ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
const bool boot_released = update_button(boot_button);
|
||||||
|
update_button(power_button);
|
||||||
|
|
||||||
|
if (boot_released && !boot_button.long_handled) {
|
||||||
|
if (lvgl_port_lock(250)) {
|
||||||
|
retro_ui_next();
|
||||||
|
lvgl_port_unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!power_button.stable && !power_button.long_handled &&
|
||||||
|
millis() - power_button.pressed_at >= POWER_HOLD_MS) {
|
||||||
|
power_button.long_handled = true;
|
||||||
|
shutdown_device();
|
||||||
|
}
|
||||||
|
delay(5);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user