Main
Source: src/main.c · 2070 lines · 16 functions.
Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
set_backlight_brightness() · source line 131
static void set_backlight_brightness(uint8_t duty)
- Execute
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty)); - Execute
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
Read original function source
static void set_backlight_brightness(uint8_t duty)
{
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
}display_color_transfer_done() · source line 144
static bool display_color_transfer_done(esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t *event,
void *user_context)- Execute
(void)panel_io; - Execute
(void)event; - Execute
BaseType_t task_woken = pdFALSE; - Execute
xSemaphoreGiveFromISR((SemaphoreHandle_t)user_context, &task_woken); - Exit this function
return task_woken == pdTRUE;
Read original function source
static bool display_color_transfer_done(esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t *event,
void *user_context)
{
(void)panel_io;
(void)event;
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR((SemaphoreHandle_t)user_context, &task_woken);
return task_woken == pdTRUE;
}initialize_display() · source line 155
static esp_lcd_panel_handle_t initialize_display(void)
Calls into this documentation:
- Execute
const gpio_config_t output_config = { .pin_bit_mask = (1ULL << PIN_POWER_ON) | (1ULL << PIN_LCD_BL) | (1ULL << PIN_LCD_RD), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; - Execute
ESP_ERROR_CHECK(gpio_config(&output_config)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_POWER_ON, 1)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_BL, 0)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_RD, 1)); - Execute
const ledc_timer_config_t backlight_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_8_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = LCD_BACKLIGHT_PWM_HZ, .clk_cfg = LEDC_AUTO_CLK, }; - Execute
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); - Execute
const ledc_channel_config_t backlight_channel = { .gpio_num = PIN_LCD_BL, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .intr_type = LEDC_INTR_DISABLE, .timer_sel = LEDC_TIMER_0, .duty = 0, .hpoint = 0, }; - Execute
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); - Execute
const esp_lcd_i80_bus_config_t bus_config = { .dc_gpio_num = PIN_LCD_DC, .wr_gpio_num = PIN_LCD_WR, .clk_src = LCD_CLK_SRC_DEFAULT, .data_gpio_nums = { PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7, }, .bus_width = 8, .max_transfer_bytes = FRAME_BUFFER_BYTES, }; - Execute
esp_lcd_i80_bus_handle_t bus = NULL; - Execute
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &bus)); - Execute
const esp_lcd_panel_io_i80_config_t io_config = { .cs_gpio_num = PIN_LCD_CS, .pclk_hz = LCD_PIXEL_CLOCK_HZ, .trans_queue_depth = 4, .on_color_trans_done = display_color_transfer_done, .user_ctx = display_transfer_done, .dc_levels = { .dc_idle_level = 0, .dc_cmd_level = 0, .dc_dummy_level = 0, .dc_data_level = 1, }, .lcd_cmd_bits = 8, .lcd_param_bits = 8, .flags = { .swap_color_bytes = true, }, }; - Execute
esp_lcd_panel_io_handle_t panel_io = NULL; - Execute
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(bus, &io_config, &panel_io)); - Execute
const esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = PIN_LCD_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, .bits_per_pixel = 16, }; - Execute
esp_lcd_panel_handle_t panel = NULL; - Execute
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 35)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - Execute
set_backlight_brightness(LCD_BACKLIGHT_DUTY); - Exit this function
return panel;
Read original function source
static esp_lcd_panel_handle_t initialize_display(void)
{
const gpio_config_t output_config = {
.pin_bit_mask = (1ULL << PIN_POWER_ON) | (1ULL << PIN_LCD_BL) |
(1ULL << PIN_LCD_RD),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&output_config));
ESP_ERROR_CHECK(gpio_set_level(PIN_POWER_ON, 1));
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_BL, 0));
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_RD, 1));
const ledc_timer_config_t backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_8_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = LCD_BACKLIGHT_PWM_HZ,
.clk_cfg = LEDC_AUTO_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
const ledc_channel_config_t backlight_channel = {
.gpio_num = PIN_LCD_BL,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0,
};
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
const esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = PIN_LCD_DC,
.wr_gpio_num = PIN_LCD_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
PIN_LCD_D0,
PIN_LCD_D1,
PIN_LCD_D2,
PIN_LCD_D3,
PIN_LCD_D4,
PIN_LCD_D5,
PIN_LCD_D6,
PIN_LCD_D7,
},
.bus_width = 8,
.max_transfer_bytes = FRAME_BUFFER_BYTES,
};
esp_lcd_i80_bus_handle_t bus = NULL;
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &bus));
const esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = PIN_LCD_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 4,
.on_color_trans_done = display_color_transfer_done,
.user_ctx = display_transfer_done,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.swap_color_bytes = true,
},
};
esp_lcd_panel_io_handle_t panel_io = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(bus, &io_config, &panel_io));
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
esp_lcd_panel_handle_t panel = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 35));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
set_backlight_brightness(LCD_BACKLIGHT_DUTY);
return panel;
}draw_bob_label() · source line 253
static void draw_bob_label(uint16_t *frame)
- Execute
static const uint8_t glyphs[2][7] = { {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // O }; - Execute
static const uint8_t bob_glyphs[] = {0, 1, 0}; - Execute
const int scale = 3; - Execute
const int label_x = LCD_WIDTH - 55; - Execute
const int label_y = 6; - Execute
const int label_width = 51; - Execute
const int label_height = 21; - FOR loop
int y = label_y - 2; y < label_y + label_height + 2; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
int x = label_x - 2; x < label_x + label_width + 2; ++xRepeat body while the loop condition permits; then continue below.
- Execute
frame[(size_t)y * LCD_WIDTH + x] = RGB565(0, 0, 0);
- FOR loop
size_t character = 0; character < 3; ++characterRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t *glyph = glyphs[bob_glyphs[character]]; - Execute
const int character_x = label_x + (int)character * (5 * scale + scale); - FOR loop
int row = 0; row < 7; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
int column = 0; column < 5; ++columnRepeat body while the loop condition permits; then continue below.
- Decision
(glyph[row] & (1U << (4 - column))) == 0True- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int pixel_y = 0; pixel_y < scale; ++pixel_yRepeat body while the loop condition permits; then continue below.
- FOR loop
int pixel_x = 0; pixel_x < scale; ++pixel_xRepeat body while the loop condition permits; then continue below.
- Execute
const int x = character_x + column * scale + pixel_x; - Execute
const int y = label_y + row * scale + pixel_y; - Execute
frame[(size_t)y * LCD_WIDTH + x] = RGB565(255, 255, 255);
Read original function source
static void draw_bob_label(uint16_t *frame)
{
static const uint8_t glyphs[2][7] = {
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // O
};
static const uint8_t bob_glyphs[] = {0, 1, 0};
const int scale = 3;
const int label_x = LCD_WIDTH - 55;
const int label_y = 6;
const int label_width = 51;
const int label_height = 21;
for (int y = label_y - 2; y < label_y + label_height + 2; ++y) {
for (int x = label_x - 2; x < label_x + label_width + 2; ++x) {
frame[(size_t)y * LCD_WIDTH + x] = RGB565(0, 0, 0);
}
}
for (size_t character = 0; character < 3; ++character) {
const uint8_t *glyph = glyphs[bob_glyphs[character]];
const int character_x = label_x + (int)character * (5 * scale + scale);
for (int row = 0; row < 7; ++row) {
for (int column = 0; column < 5; ++column) {
if ((glyph[row] & (1U << (4 - column))) == 0) {
continue;
}
for (int pixel_y = 0; pixel_y < scale; ++pixel_y) {
for (int pixel_x = 0; pixel_x < scale; ++pixel_x) {
const int x = character_x + column * scale + pixel_x;
const int y = label_y + row * scale + pixel_y;
frame[(size_t)y * LCD_WIDTH + x] = RGB565(255, 255, 255);
}
}
}
}
}
}fill_color_bars() · source line 294
static void fill_color_bars(uint16_t *frame)
Calls into this documentation:
- Execute
static const uint16_t colors[] = { RGB565(255, 0, 0), RGB565(0, 255, 0), RGB565(0, 0, 0), RGB565(0, 0, 255), RGB565(255, 255, 255), }; - FOR loop
int y = 0; y < LCD_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- Execute
const size_t color_index = (size_t)y * 5U / LCD_HEIGHT; - FOR loop
int x = 0; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
frame[(size_t)y * LCD_WIDTH + x] = colors[color_index];
- Execute
draw_bob_label(frame);
Read original function source
static void fill_color_bars(uint16_t *frame)
{
static const uint16_t colors[] = {
RGB565(255, 0, 0),
RGB565(0, 255, 0),
RGB565(0, 0, 0),
RGB565(0, 0, 255),
RGB565(255, 255, 255),
};
for (int y = 0; y < LCD_HEIGHT; ++y) {
const size_t color_index = (size_t)y * 5U / LCD_HEIGHT;
for (int x = 0; x < LCD_WIDTH; ++x) {
frame[(size_t)y * LCD_WIDTH + x] = colors[color_index];
}
}
draw_bob_label(frame);
}draw_frame() · source line 314
static void draw_frame(esp_lcd_panel_handle_t panel, const uint16_t *frame)
Calls into this documentation:
- Execute
const uint16_t *const presentation_frame = globalOverlayCompose(frame); - Execute
screenshotCapturePresentation(presentation_frame); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap( panel, 0, 0, LCD_WIDTH, LCD_HEIGHT, presentation_frame)); - Decision
xSemaphoreTake(display_transfer_done, pdMS_TO_TICKS(1000)) != pdTRUETrue- Execute
ESP_LOGE(TAG, "LCD transfer timed out"); - Execute
ESP_ERROR_CHECK(ESP_ERR_TIMEOUT);
FalseContinue after this decision.
Read original function source
static void draw_frame(esp_lcd_panel_handle_t panel, const uint16_t *frame)
{
// The global overlay is composed last without changing the caller's scene framebuffer.
// updateDoomCountdownOverlay();
const uint16_t *const presentation_frame = globalOverlayCompose(frame);
// Send the fully composed image, including any global overlay, before LCD DMA uses it.
screenshotCapturePresentation(presentation_frame);
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(
panel, 0, 0, LCD_WIDTH, LCD_HEIGHT, presentation_frame));
// The framebuffer must stay unchanged until the LCD DMA transfer finishes.
if (xSemaphoreTake(display_transfer_done, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGE(TAG, "LCD transfer timed out");
ESP_ERROR_CHECK(ESP_ERR_TIMEOUT);
}
}joystick_view_offset() · source line 331
static int joystick_view_offset(int raw, int centre, int travel)
- Decision
raw < 0True- Execute
raw = 0;
False- Decision
raw > 4095True- Execute
raw = 4095;
FalseContinue after this decision.
- Execute
const int span = raw >= centre ? 4095 - centre : centre; - Exit this function
return (raw - centre) * travel / span;
Read original function source
static int joystick_view_offset(int raw, int centre, int travel)
{
if (raw < 0) {
raw = 0;
} else if (raw > 4095) {
raw = 4095;
}
const int span = raw >= centre ? 4095 - centre : centre;
return (raw - centre) * travel / span;
}draw_controller_view() · source line 342
static void draw_controller_view(uint16_t *frame, const joystick_state_t *joystick,
bool joystick_valid, const int *button_levels)Calls into this documentation:
- Execute
const uint16_t background = RGB565(10, 16, 26); - Execute
const uint16_t border = RGB565(76, 94, 115); - Execute
const uint16_t white = RGB565(235, 241, 247); - Execute
const uint16_t green = RGB565(70, 245, 130); - Execute
const uint16_t cyan = RGB565(50, 205, 255); - Execute
const uint16_t red = RGB565(255, 80, 65); - Execute
const uint16_t amber = RGB565(255, 185, 60); - Execute
static const int button_x[6] = {80, 108, 80, 52, 20, 20}; - Execute
static const int button_y[6] = {98, 124, 150, 124, 108, 80}; - Execute
static const char *button_names[7] = {"A", "B", "C", "D", "E", "F", "K"}; - Execute
clear(frame, background); - Execute
fill_rect(frame, 5, 5, 130, 160, RGB565(63, 26, 35)); - Execute
fill_rect(frame, 142, 8, 1, 154, border); - Execute
const uint16_t stick_color = button_levels[6] == 0 ? green : button_levels[6] < 0 ? amber : border; - Execute
fill_circle(frame, 80, 44, 35, stick_color); - Execute
fill_circle(frame, 80, 44, 31, background); - Execute
fill_rect(frame, 53, 44, 55, 1, border); - Execute
fill_rect(frame, 80, 17, 1, 55, border); - Execute
draw_text(frame, 77, 19, "K", 1, stick_color); - Decision
joystick_validTrue- Execute
const int dot_x = 80 + joystick_view_offset(joystick->x_raw, JOYSTICK_CENTRE_X, 19); - Execute
const int dot_y = 44 - joystick_view_offset(joystick->y_raw, JOYSTICK_CENTRE_Y, 19); - Execute
fill_circle(frame, dot_x, dot_y, 5, background); - Execute
fill_circle(frame, dot_x, dot_y, 4, cyan);
False- Execute
draw_text(frame, 72, 38, "ERR", 1, red);
- FOR loop
size_t button = 0; button < 6; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
const uint16_t color = button_levels[button] == 0 ? green : button_levels[button] < 0 ? amber : border; - Execute
fill_circle(frame, button_x[button], button_y[button], 12, color); - Decision
button_levels[button] != 0True- Execute
fill_circle(frame, button_x[button], button_y[button], 10, background);
FalseContinue after this decision.
- Execute
draw_text(frame, button_x[button] - 5, button_y[button] - 7, button_names[button], 2, button_levels[button] == 0 ? background : white);
- Execute
draw_text(frame, 154, 10, "LIVE INPUT", 2, white); - Execute
draw_text(frame, 154, 33, joystick_valid ? "JOYSTICK OK" : "ADC READ ERROR", 1, joystick_valid ? cyan : red); - Decision
joystick_validTrue- Execute
char reading[16]; - Execute
snprintf(reading, sizeof(reading), "X %4d", joystick->x_raw); - Execute
draw_text(frame, 154, 51, reading, 2, white); - Execute
snprintf(reading, sizeof(reading), "Y %4d", joystick->y_raw); - Execute
draw_text(frame, 154, 73, reading, 2, white);
FalseContinue after this decision.
- FOR loop
size_t button = 0; button < 7; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
const int x = 154 + (int)button * 23; - Execute
const uint16_t color = button_levels[button] == 0 ? green : button_levels[button] < 0 ? amber : border; - Execute
fill_rect(frame, x, 103, 20, 23, color); - Execute
draw_text(frame, x + 5, 108, button_names[button], 2, button_levels[button] == 0 ? background : white);
- Execute
draw_text(frame, 154, 139, "GREEN MEANS PRESSED", 1, green); - Execute
draw_text(frame, 154, 154, "BOOT BACK USER SHOT", 1, white);
Read original function source
static void draw_controller_view(uint16_t *frame, const joystick_state_t *joystick,
bool joystick_valid, const int *button_levels)
{
const uint16_t background = RGB565(10, 16, 26);
const uint16_t border = RGB565(76, 94, 115);
const uint16_t white = RGB565(235, 241, 247);
const uint16_t green = RGB565(70, 245, 130);
const uint16_t cyan = RGB565(50, 205, 255);
const uint16_t red = RGB565(255, 80, 65);
const uint16_t amber = RGB565(255, 185, 60);
static const int button_x[6] = {80, 108, 80, 52, 20, 20};
static const int button_y[6] = {98, 124, 150, 124, 108, 80};
static const char *button_names[7] = {"A", "B", "C", "D", "E", "F", "K"};
clear(frame, background);
fill_rect(frame, 5, 5, 130, 160, RGB565(63, 26, 35));
fill_rect(frame, 142, 8, 1, 154, border);
// Stick at top, F/E left; A north, B east, C south, D west.
const uint16_t stick_color = button_levels[6] == 0 ? green :
button_levels[6] < 0 ? amber : border;
fill_circle(frame, 80, 44, 35, stick_color);
fill_circle(frame, 80, 44, 31, background);
fill_rect(frame, 53, 44, 55, 1, border);
fill_rect(frame, 80, 17, 1, 55, border);
draw_text(frame, 77, 19, "K", 1, stick_color);
if (joystick_valid) {
const int dot_x = 80 + joystick_view_offset(joystick->x_raw, JOYSTICK_CENTRE_X, 19);
const int dot_y = 44 - joystick_view_offset(joystick->y_raw, JOYSTICK_CENTRE_Y, 19);
fill_circle(frame, dot_x, dot_y, 5, background);
fill_circle(frame, dot_x, dot_y, 4, cyan);
} else {
draw_text(frame, 72, 38, "ERR", 1, red);
}
for (size_t button = 0; button < 6; ++button) {
const uint16_t color = button_levels[button] == 0 ? green :
button_levels[button] < 0 ? amber : border;
fill_circle(frame, button_x[button], button_y[button], 12, color);
if (button_levels[button] != 0) {
fill_circle(frame, button_x[button], button_y[button], 10, background);
}
draw_text(frame, button_x[button] - 5, button_y[button] - 7,
button_names[button], 2,
button_levels[button] == 0 ? background : white);
}
draw_text(frame, 154, 10, "LIVE INPUT", 2, white);
draw_text(frame, 154, 33, joystick_valid ? "JOYSTICK OK" : "ADC READ ERROR",
1, joystick_valid ? cyan : red);
if (joystick_valid) {
char reading[16];
snprintf(reading, sizeof(reading), "X %4d", joystick->x_raw);
draw_text(frame, 154, 51, reading, 2, white);
snprintf(reading, sizeof(reading), "Y %4d", joystick->y_raw);
draw_text(frame, 154, 73, reading, 2, white);
}
for (size_t button = 0; button < 7; ++button) {
const int x = 154 + (int)button * 23;
const uint16_t color = button_levels[button] == 0 ? green :
button_levels[button] < 0 ? amber : border;
fill_rect(frame, x, 103, 20, 23, color);
draw_text(frame, x + 5, 108, button_names[button], 2,
button_levels[button] == 0 ? background : white);
}
draw_text(frame, 154, 139, "GREEN MEANS PRESSED", 1, green);
draw_text(frame, 154, 154, "BOOT BACK USER SHOT", 1, white);
}draw_battery_view() · source line 413
static void draw_battery_view(uint16_t *frame)
Calls into this documentation:
- Execute
const uint16_t background = RGB565(10, 16, 26); - Execute
const uint16_t white = RGB565(235, 241, 247); - Execute
const uint16_t cyan = RGB565(50, 205, 255); - Execute
const uint16_t green = RGB565(70, 245, 130); - Execute
const uint16_t red = RGB565(255, 80, 65); - Execute
char text[32]; - Execute
clear(frame, background); - Execute
draw_text_centred(frame, 160, 16, "BATTERY", 3, cyan); - Decision
batt_has_reading()True- Execute
snprintf(text, sizeof(text), "%.1f V", (double)batt_voltage()); - Execute
draw_text_centred(frame, 160, 62, text, 4, white); - Execute
draw_text_centred(frame, 160, 112, batt_external_power_connected() ? "EXTERNAL POWER" : "BATTERY POWER", 2, batt_external_power_connected() ? green : cyan); - Execute
draw_text_centred(frame, 160, 140, "UPDATES EVERY 15 SEC", 1, white);
False- Execute
draw_text_centred(frame, 160, 80, "BATTERY ADC ERROR", 2, red);
- Execute
draw_text_centred(frame, 160, 157, "BOOT BACK", 1, white);
Read original function source
static void draw_battery_view(uint16_t *frame)
{
const uint16_t background = RGB565(10, 16, 26);
const uint16_t white = RGB565(235, 241, 247);
const uint16_t cyan = RGB565(50, 205, 255);
const uint16_t green = RGB565(70, 245, 130);
const uint16_t red = RGB565(255, 80, 65);
char text[32];
clear(frame, background);
draw_text_centred(frame, 160, 16, "BATTERY", 3, cyan);
if (batt_has_reading()) {
snprintf(text, sizeof(text), "%.1f V", (double)batt_voltage());
draw_text_centred(frame, 160, 62, text, 4, white);
draw_text_centred(frame, 160, 112,
batt_external_power_connected() ? "EXTERNAL POWER" : "BATTERY POWER",
2, batt_external_power_connected() ? green : cyan);
draw_text_centred(frame, 160, 140, "UPDATES EVERY 15 SEC", 1, white);
} else {
draw_text_centred(frame, 160, 80, "BATTERY ADC ERROR", 2, red);
}
draw_text_centred(frame, 160, 157, "BOOT BACK", 1, white);
}initialize_buttons() · source line 572
static void initialize_buttons(void)
- Execute
(void)gpio_hold_dis(PIN_BUTTON_BOOT); - Execute
(void)gpio_hold_dis(PIN_BUTTON_USER); - Execute
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_BOOT)); - Execute
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_USER)); - Execute
const gpio_config_t button_config = { .pin_bit_mask = (1ULL << PIN_BUTTON_BOOT) | (1ULL << PIN_BUTTON_USER), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; - Execute
ESP_ERROR_CHECK(gpio_config(&button_config));
Read original function source
static void initialize_buttons(void)
{
// A software reset after light sleep can retain a pad's prior sleep state.
// Clear it before restoring the board buttons as active-low pull-up inputs.
(void)gpio_hold_dis(PIN_BUTTON_BOOT);
(void)gpio_hold_dis(PIN_BUTTON_USER);
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_BOOT));
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_USER));
const gpio_config_t button_config = {
.pin_bit_mask = (1ULL << PIN_BUTTON_BOOT) | (1ULL << PIN_BUTTON_USER),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&button_config));
}initialize_audio_shutdown() · source line 590
static void initialize_audio_shutdown(void)
- Execute
(void)gpio_hold_dis(PIN_AUDIO_SHUTDOWN); - Execute
ESP_ERROR_CHECK(gpio_reset_pin(PIN_AUDIO_SHUTDOWN)); - Execute
const gpio_config_t shutdown_config = { .pin_bit_mask = 1ULL << PIN_AUDIO_SHUTDOWN, .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; - Execute
ESP_ERROR_CHECK(gpio_config(&shutdown_config)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1)); - Execute
ESP_LOGI(TAG, "Amplifier shutdown control ready on GPIO%d", PIN_AUDIO_SHUTDOWN);
Read original function source
static void initialize_audio_shutdown(void)
{
(void)gpio_hold_dis(PIN_AUDIO_SHUTDOWN);
ESP_ERROR_CHECK(gpio_reset_pin(PIN_AUDIO_SHUTDOWN));
const gpio_config_t shutdown_config = {
.pin_bit_mask = 1ULL << PIN_AUDIO_SHUTDOWN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&shutdown_config));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1));
ESP_LOGI(TAG, "Amplifier shutdown control ready on GPIO%d", PIN_AUDIO_SHUTDOWN);
}restore_light_sleep_button_inputs() · source line 606
static void restore_light_sleep_button_inputs(void)
Calls into this documentation:
- Execute
(void)gpio_hold_dis(PIN_BUTTON_BOOT); - Execute
(void)gpio_hold_dis(PIN_BUTTON_USER); - Execute
(void)gpio_hold_dis(PIN_LIGHT_SLEEP_WAKE); - Execute
initialize_buttons(); - Execute
const gpio_config_t wake_button_config = { .pin_bit_mask = 1ULL << PIN_LIGHT_SLEEP_WAKE, .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; - Execute
ESP_ERROR_CHECK(gpio_config(&wake_button_config)); - Execute
reset_button_debounce_after_sleep = true;
Read original function source
static void restore_light_sleep_button_inputs(void)
{
(void)gpio_hold_dis(PIN_BUTTON_BOOT);
(void)gpio_hold_dis(PIN_BUTTON_USER);
(void)gpio_hold_dis(PIN_LIGHT_SLEEP_WAKE);
initialize_buttons();
const gpio_config_t wake_button_config = {
.pin_bit_mask = 1ULL << PIN_LIGHT_SLEEP_WAKE,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&wake_button_config));
reset_button_debounce_after_sleep = true;
}initialize_tf_card() · source line 624
static bool initialize_tf_card(void)
- Execute
ESP_LOGI(TAG, "Initializing TF card on CS=%d, MOSI=%d, SCK=%d, MISO=%d", PIN_TF_CS, PIN_TF_MOSI, PIN_TF_SCK, PIN_TF_MISO); - Execute
sdmmc_host_t host = SDSPI_HOST_DEFAULT(); - Execute
host.slot = SPI2_HOST; - Execute
host.max_freq_khz = TF_MAX_FREQUENCY_KHZ; - Execute
const spi_bus_config_t bus_config = { .mosi_io_num = PIN_TF_MOSI, .miso_io_num = PIN_TF_MISO, .sclk_io_num = PIN_TF_SCK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4096, }; - Execute
esp_err_t result = spi_bus_initialize(host.slot, &bus_config, SPI_DMA_CH_AUTO); - Decision
result != ESP_OKTrue- Execute
ESP_LOGE(TAG, "TF SPI bus initialization failed: %s", esp_err_to_name(result)); - Exit this function
return false;
FalseContinue after this decision.
- Execute
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); - Execute
slot_config.gpio_cs = PIN_TF_CS; - Execute
slot_config.host_id = host.slot; - Execute
const esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024, }; - Execute
result = esp_vfs_fat_sdspi_mount( TF_MOUNT_POINT, &host, &slot_config, &mount_config, &tf_card); - Decision
result != ESP_OKTrue- Execute
ESP_LOGE(TAG, "TF card mount failed: %s", esp_err_to_name(result)); - Execute
spi_bus_free(host.slot); - Exit this function
return false;
FalseContinue after this decision.
- Execute
ESP_LOGI(TAG, "TF card mounted read/write at %s", TF_MOUNT_POINT); - Execute
sdmmc_card_print_info(stdout, tf_card); - Execute
struct statvfs filesystem = {0}; - Decision
statvfs(TF_MOUNT_POINT, &filesystem) == 0True- Execute
const uint64_t total_bytes = (uint64_t)filesystem.f_blocks * filesystem.f_frsize; - Execute
const uint64_t free_bytes = (uint64_t)filesystem.f_bfree * filesystem.f_frsize; - Execute
ESP_LOGI(TAG, "TF filesystem total/free: %" PRIu64 " / %" PRIu64 " bytes", total_bytes, free_bytes);
False- Execute
ESP_LOGW(TAG, "TF card mounted, but filesystem space could not be read");
- Exit this function
return true;
Read original function source
static bool initialize_tf_card(void)
{
ESP_LOGI(TAG, "Initializing TF card on CS=%d, MOSI=%d, SCK=%d, MISO=%d",
PIN_TF_CS, PIN_TF_MOSI, PIN_TF_SCK, PIN_TF_MISO);
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = SPI2_HOST;
host.max_freq_khz = TF_MAX_FREQUENCY_KHZ;
const spi_bus_config_t bus_config = {
.mosi_io_num = PIN_TF_MOSI,
.miso_io_num = PIN_TF_MISO,
.sclk_io_num = PIN_TF_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096,
};
esp_err_t result = spi_bus_initialize(host.slot, &bus_config, SPI_DMA_CH_AUTO);
if (result != ESP_OK) {
ESP_LOGE(TAG, "TF SPI bus initialization failed: %s", esp_err_to_name(result));
return false;
}
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_TF_CS;
slot_config.host_id = host.slot;
const esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024,
};
result = esp_vfs_fat_sdspi_mount(
TF_MOUNT_POINT, &host, &slot_config, &mount_config, &tf_card);
if (result != ESP_OK) {
ESP_LOGE(TAG, "TF card mount failed: %s", esp_err_to_name(result));
spi_bus_free(host.slot);
return false;
}
ESP_LOGI(TAG, "TF card mounted read/write at %s", TF_MOUNT_POINT);
sdmmc_card_print_info(stdout, tf_card);
struct statvfs filesystem = {0};
if (statvfs(TF_MOUNT_POINT, &filesystem) == 0) {
const uint64_t total_bytes =
(uint64_t)filesystem.f_blocks * filesystem.f_frsize;
const uint64_t free_bytes =
(uint64_t)filesystem.f_bfree * filesystem.f_frsize;
ESP_LOGI(TAG, "TF filesystem total/free: %" PRIu64 " / %" PRIu64 " bytes",
total_bytes, free_bytes);
} else {
ESP_LOGW(TAG, "TF card mounted, but filesystem space could not be read");
}
return true;
}enter_light_sleep() · source line 685
static bool enter_light_sleep(esp_lcd_panel_handle_t panel, bool *tf_available)
Calls into this documentation:
- Preprocessor directive
#if !TF_CARD_ENABLED - Execute
(void)tf_available; - Preprocessor directive
#endif - Execute
const int wake_button_level = gpio_get_level(PIN_LIGHT_SLEEP_WAKE); - Decision
wake_button_level == 0True- Execute
ESP_LOGW(TAG, "Light sleep cancelled; release shield B (GPIO%d)", PIN_LIGHT_SLEEP_WAKE); - Exit this function
return false;
FalseContinue after this decision.
- Execute
const uint64_t wake_pins = 1ULL << PIN_LIGHT_SLEEP_WAKE; - Execute
const esp_err_t wake_result = esp_sleep_enable_ext1_wakeup( wake_pins, ESP_EXT1_WAKEUP_ANY_LOW); - Decision
wake_result != ESP_OKTrue- Execute
ESP_LOGE(TAG, "Light-sleep wake configuration failed: %s", esp_err_to_name(wake_result)); - Exit this function
return false;
FalseContinue after this decision.
- Execute
timeSyncStop(); - Execute
(void)wifiServiceStop(); - Preprocessor directive
#if TF_CARD_ENABLED - Decision
*tf_available && tf_card != NULLTrue- Execute
const esp_err_t unmount_result = esp_vfs_fat_sdcard_unmount(TF_MOUNT_POINT, tf_card); - Decision
unmount_result == ESP_OKTrue- Execute
tf_card = NULL; - Execute
*tf_available = false; - Execute
const esp_err_t bus_result = spi_bus_free(SPI2_HOST); - Decision
bus_result != ESP_OKTrue- Execute
ESP_LOGW(TAG, "TF SPI release failed: %s", esp_err_to_name(bus_result));
FalseContinue after this decision.
False- Execute
ESP_LOGW(TAG, "TF card unmount failed: %s", esp_err_to_name(unmount_result));
FalseContinue after this decision.
- Preprocessor directive
#endif - Execute
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0)); - Execute
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0)); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, false)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 0)); - Execute
ESP_ERROR_CHECK(gpio_hold_en(PIN_AUDIO_SHUTDOWN)); - Execute
ESP_LOGI(TAG, "Entering light sleep; press shield B to wake"); - Execute
const esp_err_t sleep_result = esp_light_sleep_start(); - Decision
sleep_result != ESP_OKTrue- Execute
ESP_LOGE(TAG, "Light sleep failed: %s", esp_err_to_name(sleep_result));
FalseContinue after this decision.
- Execute
ESP_ERROR_CHECK(esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1)); - Execute
ESP_ERROR_CHECK(gpio_hold_dis(PIN_AUDIO_SHUTDOWN)); - Execute
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1)); - Execute
restore_light_sleep_button_inputs(); - Execute
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - Execute
set_backlight_brightness(settingsBrightness()); - Preprocessor directive
#if TF_CARD_ENABLED - Decision
!*tf_availableTrue- Execute
*tf_available = initialize_tf_card(); - Execute
ESP_LOGI(TAG, "TF CARD AFTER WAKE: %s", *tf_available ? "READY" : "NOT AVAILABLE");
FalseContinue after this decision.
- Preprocessor directive
#endif - Execute
ESP_LOGI(TAG, "Resumed from light sleep"); - Exit this function
return sleep_result == ESP_OK;
Read original function source
static bool enter_light_sleep(esp_lcd_panel_handle_t panel, bool *tf_available)
{
#if !TF_CARD_ENABLED
(void)tf_available;
#endif
const int wake_button_level = gpio_get_level(PIN_LIGHT_SLEEP_WAKE);
if (wake_button_level == 0) {
ESP_LOGW(TAG, "Light sleep cancelled; release shield B (GPIO%d)",
PIN_LIGHT_SLEEP_WAKE);
return false;
}
const uint64_t wake_pins = 1ULL << PIN_LIGHT_SLEEP_WAKE;
const esp_err_t wake_result = esp_sleep_enable_ext1_wakeup(
wake_pins, ESP_EXT1_WAKEUP_ANY_LOW);
if (wake_result != ESP_OK) {
ESP_LOGE(TAG, "Light-sleep wake configuration failed: %s",
esp_err_to_name(wake_result));
return false;
}
timeSyncStop();
(void)wifiServiceStop();
#if TF_CARD_ENABLED
if (*tf_available && tf_card != NULL) {
const esp_err_t unmount_result = esp_vfs_fat_sdcard_unmount(TF_MOUNT_POINT, tf_card);
if (unmount_result == ESP_OK) {
tf_card = NULL;
*tf_available = false;
const esp_err_t bus_result = spi_bus_free(SPI2_HOST);
if (bus_result != ESP_OK) {
ESP_LOGW(TAG, "TF SPI release failed: %s", esp_err_to_name(bus_result));
}
} else {
ESP_LOGW(TAG, "TF card unmount failed: %s", esp_err_to_name(unmount_result));
}
}
#endif
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, false));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 0));
ESP_ERROR_CHECK(gpio_hold_en(PIN_AUDIO_SHUTDOWN));
ESP_LOGI(TAG, "Entering light sleep; press shield B to wake");
const esp_err_t sleep_result = esp_light_sleep_start();
if (sleep_result != ESP_OK) {
ESP_LOGE(TAG, "Light sleep failed: %s", esp_err_to_name(sleep_result));
}
ESP_ERROR_CHECK(esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1));
ESP_ERROR_CHECK(gpio_hold_dis(PIN_AUDIO_SHUTDOWN));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1));
restore_light_sleep_button_inputs();
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
set_backlight_brightness(settingsBrightness());
#if TF_CARD_ENABLED
if (!*tf_available) {
*tf_available = initialize_tf_card();
ESP_LOGI(TAG, "TF CARD AFTER WAKE: %s",
*tf_available ? "READY" : "NOT AVAILABLE");
}
#endif
ESP_LOGI(TAG, "Resumed from light sleep");
return sleep_result == ESP_OK;
}app_main() · source line 752
void app_main(void)
Calls into this documentation:
- Execute
esp_chip_info_t chip_info = {0}; - Execute
uint32_t flash_size = 0; - Execute
esp_chip_info(&chip_info); - Execute
const esp_err_t flash_result = esp_flash_get_size(NULL, &flash_size); - Execute
const bool psram_initialized = esp_psram_is_initialized(); - Execute
const size_t psram_chip_size = esp_psram_get_size(); - Execute
const size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); - Execute
const size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); - Execute
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic"); - Execute
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision, chip_info.cores); - Execute
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ); - Decision
flash_result == ESP_OKTrue- Execute
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
False- Execute
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
- Execute
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no"); - Execute
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size); - Execute
ESP_LOGI(TAG, "PSRAM heap total/free: %u / %u bytes", (unsigned)psram_total, (unsigned)psram_free); - Execute
const bool memory_ok = flash_result == ESP_OK && flash_size == EXPECTED_FLASH_BYTES && psram_initialized && psram_chip_size >= EXPECTED_PSRAM_BYTES; - Decision
!memory_okTrue- Execute
ESP_LOGE(TAG, "Memory check FAILED: expected 16 MB flash and 8 MB PSRAM");
FalseContinue after this decision.
- Execute
uint16_t *frame = heap_caps_malloc( FRAME_BUFFER_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); - Decision
frame == NULLTrue- Execute
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM display buffer", (unsigned)FRAME_BUFFER_BYTES); - Exit this function
return;
FalseContinue after this decision.
- Execute
ESP_LOGI(TAG, "PSRAM display buffer allocated: %u bytes", (unsigned)FRAME_BUFFER_BYTES); - Decision
!globalOverlayInitialize()True- Execute
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM overlay presentation buffer", (unsigned)FRAME_BUFFER_BYTES); - Execute
heap_caps_free(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
display_transfer_done = xSemaphoreCreateBinary(); - Decision
display_transfer_done == NULLTrue- Execute
ESP_LOGE(TAG, "Unable to allocate LCD transfer semaphore"); - Execute
globalOverlayDeinitialize(); - Execute
heap_caps_free(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
initialize_buttons(); - Execute
initialize_audio_shutdown(); - Execute
esp_lcd_panel_handle_t panel = initialize_display(); - Decision
memory_okTrue- Execute
clear(frame, RGB565(0, 0, 0));
False- Execute
clear(frame, RGB565(255, 0, 255)); - Execute
ESP_LOGE(TAG, "Magenta screen indicates a memory diagnostic failure");
- Execute
draw_frame(panel, frame); - Execute
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic report"); - Execute
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision, chip_info.cores); - Execute
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ); - Decision
flash_result == ESP_OKTrue- Execute
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
False- Execute
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
- Execute
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no"); - Execute
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size); - Execute
ESP_LOGI(TAG, "PSRAM heap total/free at startup: %u / %u bytes", (unsigned)psram_total, (unsigned)psram_free); - Execute
ESP_LOGI(TAG, "PSRAM display buffer: %u bytes", (unsigned)FRAME_BUFFER_BYTES); - Decision
memory_okTrue- Execute
ESP_LOGI(TAG, "BOARD DIAGNOSTIC: PASS");
False- Execute
ESP_LOGE(TAG, "BOARD DIAGNOSTIC: FAIL");
- Execute
bool tf_available = false; - Preprocessor directive
#if TF_CARD_ENABLED - Execute
tf_available = initialize_tf_card(); - Execute
ESP_LOGI(TAG, "TF CARD: %s", tf_available ? "READY" : "NOT AVAILABLE"); - Preprocessor directive
#else - Execute
ESP_LOGI(TAG, "TF CARD: DISABLED"); - Preprocessor directive
#endif - Execute
const bool dungeon_files_ready = dungeonFilesInitialize(tf_available); - Execute
ESP_LOGI(TAG, "DUNGEON DATA: %s", dungeon_files_ready ? "READY" : "NOT AVAILABLE"); - Preprocessor directive
#if RAM_MONITOR_ENABLED - Decision
dungeon_files_readyTrue- Execute
ramMonitorInitialize();
False- Execute
ESP_LOGW(TAG, "RAM MONITOR: DISABLED (SPIFFS NOT AVAILABLE)");
- Preprocessor directive
#else - Execute
ESP_LOGI(TAG, "RAM MONITOR: DISABLED (FEATURE SWITCH)"); - Preprocessor directive
#endif - Execute
const bool storage_ready = game_storage_initialize(); - Execute
const bool gold_ready = storage_ready && initGold(); - Execute
const bool timing_ready = storage_ready && initAITiming(); - Execute
const bool settings_ready = storage_ready && settingsInitialize(); - Execute
const bool clock_time_ready = storage_ready && timeSyncInitialize(); - Execute
const bool audio_ready = audioInitialize(); - Execute
set_backlight_brightness(settingsBrightness()); - Execute
ESP_LOGI(TAG, "GAME STORAGE: %s; GOLD: %s; AI TIMING: %s; SETTINGS: %s; CLOCK: %s; AUDIO: %s", storage_ready ? "READY" : "FAILED", gold_ready ? "READY" : "FAILED", timing_ready ? "READY" : "DEFAULTS ONLY", settings_ready ? "READY" : "DEFAULTS ONLY", clock_time_ready ? "READY" : "DEFAULTS ONLY", audio_ready ? "READY" : "FAILED"); - Decision
storage_readyTrue- Execute
nvs_stats_t stats = {0}; - Execute
const esp_err_t stats_result = nvs_get_stats(NULL, &stats); - Decision
stats_result == ESP_OKTrue- Execute
ESP_LOGI(TAG, "NVS entries: used %u; available %u; total %u", (unsigned)stats.used_entries, (unsigned)stats.available_entries, (unsigned)stats.total_entries);
False- Execute
ESP_LOGW(TAG, "Cannot read NVS capacity: %s", esp_err_to_name(stats_result));
FalseContinue after this decision.
- Execute
ESP_LOGI(TAG, "Initializing raw joystick test"); - Execute
ESP_LOGI(TAG, "Joystick X: GPIO1 / ADC1 channel 0"); - Execute
ESP_LOGI(TAG, "Joystick Y: GPIO2 / ADC1 channel 1"); - Execute
const esp_err_t joystick_result = controller_input_initialize(); - Decision
joystick_result != ESP_OKTrue- Execute
ESP_LOGE(TAG, "Joystick initialization failed: %s", esp_err_to_name(joystick_result)); - Exit this function
return;
FalseContinue after this decision.
- Execute
const bool battery_ready = batt_initialize(); - Execute
ESP_LOGI(TAG, "BATTERY ADC: %s", battery_ready ? "READY" : "FAILED"); - Execute
ESP_LOGI(TAG, "RAW JOYSTICK TEST: READY"); - Execute
ESP_LOGI(TAG, "USER short press captures a screenshot; hold USER in Demo opens its selector"); - Execute
const gpio_num_t shield_button_pins[] = { GPIO_NUM_16, GPIO_NUM_21, GPIO_NUM_17, GPIO_NUM_18, GPIO_NUM_44, GPIO_NUM_43, GPIO_NUM_3, }; - Execute
const char *shield_button_names[] = { "A", "B", "C", "D", "E", "F", "K", }; - Execute
const size_t shield_button_count = sizeof(shield_button_pins) / sizeof(shield_button_pins[0]); - Execute
int previous_shield_levels[7]; - Execute
int stable_shield_levels[7]; - Execute
TickType_t shield_changed_at[7]; - Execute
uint64_t shield_button_mask = 0; - FOR loop
size_t button = 0; button < shield_button_count; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
shield_button_mask |= 1ULL << shield_button_pins[button];
- Execute
const gpio_config_t shield_button_config = { .pin_bit_mask = shield_button_mask, .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; - Execute
ESP_ERROR_CHECK(gpio_config(&shield_button_config)); - FOR loop
size_t button = 0; button < shield_button_count; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
previous_shield_levels[button] = gpio_get_level(shield_button_pins[button]); - Execute
stable_shield_levels[button] = -1; - Execute
shield_changed_at[button] = xTaskGetTickCount();
- Execute
ESP_LOGI(TAG, "SHIELD BUTTON TEST: READY"); - Execute
int previous_user_level = gpio_get_level(PIN_BUTTON_USER); - Execute
int stable_user_level = previous_user_level; - Execute
TickType_t user_changed_at = xTaskGetTickCount(); - Execute
TickType_t user_pressed_at = user_changed_at; - Execute
bool user_long_sent = false; - Execute
bool user_capture_cancelled = false; - Execute
TickType_t last_controller_refresh = xTaskGetTickCount() - pdMS_TO_TICKS(CONTROLLER_REFRESH_MS); - Execute
TickType_t last_render_refresh = xTaskGetTickCount() - pdMS_TO_TICKS(RENDER_REFRESH_MS); - Execute
bool previous_joystick_valid = true; - Execute
joystick_state_t live_joystick = {0}; - Execute
bool joystick_valid = true; - Execute
bool joystick_validity_changed = true; - Execute
bool menu_redraw_pending = true; - Execute
int previous_boot_level = gpio_get_level(PIN_BUTTON_BOOT); - Execute
int stable_boot_level = previous_boot_level; - Execute
TickType_t boot_changed_at = xTaskGetTickCount(); - Execute
TickType_t boot_pressed_at = boot_changed_at; - Execute
bool boot_long_sent = false; - Execute
TickType_t full_reset_pressed_at = 0; - Execute
bool full_reset_armed = stable_boot_level != 0 || stable_user_level != 0; - Execute
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT, stable_boot_level == 0 ? "PRESSED" : "RELEASED"); - Execute
bool checkers_active = false; - Execute
bool othello_active = false; - Execute
bool connect_four_active = false; - Execute
bool battleships_active = false; - Execute
bool yahtzee_active = false; - Execute
bool blackjack_active = false; - Execute
bool poker_active = false; - Execute
bool poker_dice_active = false; - Execute
bool pig_active = false; - Execute
bool brightness_settings_active = false; - Execute
bool volume_settings_active = false; - Execute
bool wifi_settings_active = false; - Execute
bool esp_info_active = false; - Execute
bool ram_log_active = false; - Execute
bool power_off_settings_active = false; - Execute
bool restart_settings_active = false; - Execute
bool game_config_active = false; - Execute
bool font_test_active = false; - Execute
bool about_active = false; - Execute
bool matrix_active = false; - Execute
bool boing_ball_active = false; - Execute
bool demo_active = false; - Execute
bool simon_active = false; - Execute
bool calculator_active = false; - Execute
bool kaleidoscope_active = false; - Execute
bool warp_tunnel_active = false; - Execute
bool mandelbrot_active = false; - Execute
bool clock_active = false; - Execute
bool slideshow_active = false; - Execute
bool spiffs_test_active = false; - Execute
bool sound_test_active = false; - Execute
bool sd_card_test_active = false; - Execute
bool asteroids_active = false; - Execute
bool battle_zone_active = false; - Execute
bool boulder_dash_active = false; - Execute
bool tempest_active = false; - Execute
bool invaders_active = false; - Execute
bool missile_command_active = false; - Execute
bool breakout_active = false; - Execute
bool crawler_active = false; - Execute
bool maze_chase_active = false; - Execute
bool scramble_active = false; - Execute
bool defender_active = false; - Execute
bool galaga_active = false; - Execute
bool dungeon_active = false; - Execute
bool hall_of_fame_active = false; - Execute
bool game_paused = false; - Execute
bool pause_redraw = false; - Execute
static const uint16_t pause_colors[] = { COLOR_RED, COLOR_YELLOW, COLOR_CYAN, COLOR_WHITE, COLOR_BLACK, COLOR_MAGENTA }; - Execute
size_t pause_color_index = 0; - Execute
uint32_t pause_color_changed_ms = 0; - Execute
uint32_t game_clock_last_ms = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); - Execute
uint32_t game_now_ms = game_clock_last_ms; - Execute
menu_initialize((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS)); - Execute
ESP_LOGI(TAG, "MENU: %s", memory_ok ? "READY" : "DISABLED BY MEMORY CHECK"); - WHILE loop
trueRepeat body while the loop condition permits; then continue below.
- Decision
reset_button_debounce_after_sleepTrue- Execute
previous_boot_level = stable_boot_level = gpio_get_level(PIN_BUTTON_BOOT); - Execute
previous_user_level = stable_user_level = gpio_get_level(PIN_BUTTON_USER); - Execute
boot_changed_at = boot_pressed_at = user_changed_at = user_pressed_at = xTaskGetTickCount(); - Execute
boot_long_sent = false; - Execute
user_long_sent = false; - Execute
user_capture_cancelled = stable_boot_level == 0 && stable_user_level == 0; - Execute
full_reset_armed = stable_boot_level != 0 || stable_user_level != 0; - Execute
full_reset_pressed_at = 0; - FOR loop
size_t button = 0; button < shield_button_count; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
previous_shield_levels[button] = stable_shield_levels[button] = gpio_get_level(shield_button_pins[button]); - Execute
shield_changed_at[button] = xTaskGetTickCount();
- Execute
reset_button_debounce_after_sleep = false;
FalseContinue after this decision.
- Execute
const int user_level = gpio_get_level(PIN_BUTTON_USER); - Execute
const TickType_t now = xTaskGetTickCount(); - Execute
const uint32_t now_ms = (uint32_t)(now * portTICK_PERIOD_MS); - Preprocessor directive
#if RAM_MONITOR_ENABLED - Decision
dungeon_files_readyTrue- Execute
ramMonitorUpdate(now_ms);
FalseContinue after this decision.
- Preprocessor directive
#endif - Decision
!game_pausedTrue- Execute
game_now_ms += now_ms - game_clock_last_ms;
FalseContinue after this decision.
- Execute
game_clock_last_ms = now_ms; - Execute
bool k_pressed = false; - Execute
bool a_pressed = false; - Execute
bool b_pressed = false; - Execute
bool c_pressed = false; - Execute
bool d_pressed = false; - Execute
bool e_pressed = false; - Execute
bool f_pressed = false; - Execute
bool user_short_pressed = false; - Execute
bool user_long_pressed = false; - Execute
bool boot_short = false; - Execute
bool boot_long = false; - Execute
const int boot_level = gpio_get_level(PIN_BUTTON_BOOT); - Decision
boot_level != previous_boot_levelTrue- Execute
previous_boot_level = boot_level; - Execute
boot_changed_at = now;
FalseContinue after this decision.
- Decision
boot_level != stable_boot_level && (now - boot_changed_at) >= pdMS_TO_TICKS(30)True- Execute
stable_boot_level = boot_level; - Execute
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT, stable_boot_level == 0 ? "PRESSED" : "RELEASED"); - Decision
stable_boot_level == 0True- Execute
boot_pressed_at = now; - Execute
boot_long_sent = false;
False- Execute
boot_short = !boot_long_sent;
FalseContinue after this decision.
- Decision
stable_boot_level == 0 && !boot_long_sent && (now - boot_pressed_at) >= pdMS_TO_TICKS(800)True- Execute
boot_long_sent = true; - Execute
boot_long = true;
FalseContinue after this decision.
- Decision
user_level != previous_user_levelTrue- Execute
previous_user_level = user_level; - Execute
user_changed_at = now;
FalseContinue after this decision.
- Decision
user_level != stable_user_level && (now - user_changed_at) >= pdMS_TO_TICKS(30)True- Execute
stable_user_level = user_level; - Decision
stable_user_level == 0True- Execute
user_pressed_at = now; - Execute
user_long_sent = false; - Execute
user_capture_cancelled = stable_boot_level == 0;
False- Decision
!user_long_sent && !user_capture_cancelledTrue- Execute
user_short_pressed = true;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
stable_boot_level == 0 && stable_user_level == 0True- Execute
user_capture_cancelled = true;
FalseContinue after this decision.
- Decision
stable_user_level == 0 && stable_boot_level != 0 && !user_capture_cancelled && !user_long_sent && (now - user_pressed_at) >= pdMS_TO_TICKS(USER_LONG_PRESS_MS)True- Execute
user_long_sent = true; - Execute
user_long_pressed = true;
FalseContinue after this decision.
- FOR loop
size_t button = 0; button < shield_button_count; ++buttonRepeat body while the loop condition permits; then continue below.
- Execute
const int level = gpio_get_level(shield_button_pins[button]); - Decision
level != previous_shield_levels[button]True- Execute
previous_shield_levels[button] = level; - Execute
shield_changed_at[button] = now;
FalseContinue after this decision.
- Decision
level != stable_shield_levels[button] && (now - shield_changed_at[button]) >= pdMS_TO_TICKS(30)True- Decision
button == 6 && stable_shield_levels[button] == 1 && level == 0True- Execute
k_pressed = true;
FalseContinue after this decision.
- Decision
stable_shield_levels[button] == 1 && level == 0True- Decision
button == 0True- Execute
a_pressed = true;
FalseContinue after this decision.
- Decision
button == 1True- Execute
b_pressed = true;
FalseContinue after this decision.
- Decision
button == 2True- Execute
c_pressed = true;
FalseContinue after this decision.
- Decision
button == 3True- Execute
d_pressed = true;
FalseContinue after this decision.
- Decision
button == 4True- Execute
e_pressed = true;
FalseContinue after this decision.
- Decision
button == 5True- Execute
f_pressed = true;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
stable_shield_levels[button] = level; - Execute
ESP_LOGI(TAG, "Shield button %s (GPIO%d): %s", shield_button_names[button], (int)shield_button_pins[button], level == 0 ? "PRESSED" : "RELEASED");
FalseContinue after this decision.
- Decision
stable_boot_level != 0 || stable_user_level != 0True- Execute
full_reset_armed = true; - Execute
full_reset_pressed_at = 0;
False- Decision
full_reset_armed && full_reset_pressed_at == 0True- Execute
full_reset_pressed_at = now;
False- Decision
full_reset_armed && (now - full_reset_pressed_at) >= pdMS_TO_TICKS(FULL_RESET_HOLD_MS)True- Execute
ESP_LOGW(TAG, "BOOT and USER held: restarting ESP"); - Execute
esp_restart();
FalseContinue after this decision.
- Decision
user_short_pressedTrue- Execute
const char *const screenshot_source_label = demo_active ? demoScreenshotLabel() : hall_of_fame_active ? hallOfFameScreenshotLabel() : menuScreenshotLabel(); - Execute
screenshotRequest(screenshot_source_label); - Execute
menu_redraw_pending = true;
FalseContinue after this decision.
- Execute
const bool left_held = stable_shield_levels[3] == 0; - Execute
const bool right_held = stable_shield_levels[1] == 0; - Execute
const bool app_active = checkers_active || othello_active || connect_four_active || battleships_active || yahtzee_active || blackjack_active || poker_active || poker_dice_active || pig_active || brightness_settings_active || volume_settings_active || wifi_settings_active || esp_info_active || ram_log_active || power_off_settings_active || restart_settings_active || game_config_active || font_test_active || about_active || matrix_active || boing_ball_active || demo_active || simon_active || calculator_active || kaleidoscope_active || warp_tunnel_active || mandelbrot_active || clock_active || slideshow_active || spiffs_test_active || sound_test_active || sd_card_test_active || asteroids_active || battle_zone_active || boulder_dash_active || tempest_active || invaders_active || missile_command_active || breakout_active || crawler_active || maze_chase_active || scramble_active || defender_active || galaga_active || dungeon_active || hall_of_fame_active; - Decision
!app_active || boot_short || boot_longTrue- Execute
game_paused = false; - Execute
pause_redraw = false;
False- Decision
f_pressedTrue- Execute
game_paused = !game_paused; - Execute
pause_redraw = game_paused; - Execute
ESP_LOGI(TAG, "App %s", game_paused ? "PAUSED" : "RESUMED");
FalseContinue after this decision.
- Decision
boot_longTrue- Execute
ESP_LOGI(TAG, "BOOT HOME"); - Execute
menu_home(now_ms);
False- Decision
boot_shortTrue- Execute
ESP_LOGI(TAG, "BOOT BACK"); - Execute
menu_back(now_ms);
False- Decision
game_paused || (app_active && f_pressed)TrueFalse- Decision
highScoreEntryActive()True- Execute
highScoreEntryAction(a_pressed);
False- Decision
checkers_activeTrue- Execute
checkersAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
othello_activeTrue- Execute
othelloAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
connect_four_activeTrue- Execute
connectFourAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
battleships_activeTrue- Execute
battleshipsAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
yahtzee_activeTrue- Execute
yahtzeeAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
blackjack_activeTrue- Execute
blackjackAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
poker_activeTrue- Execute
pokerAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
False- Decision
poker_dice_activeTrue- Execute
pokerDiceAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
False- Decision
pig_activeTrue- Execute
pigAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
False- Decision
brightness_settings_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
volume_settings_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
wifi_settings_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
esp_info_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
ram_log_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
restart_settings_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
power_off_settings_activeTrue- Execute
settingsAction(k_pressed || a_pressed, now_ms);
False- Decision
game_config_activeTrue- Execute
gameConfigAction(a_pressed, k_pressed, now_ms);
False- Decision
font_test_activeTrue- Execute
fontTestAction(k_pressed || a_pressed);
False- Decision
about_activeTrue- Execute
aboutAction(a_pressed, f_pressed, now_ms);
False- Decision
matrix_activeTrue- Execute
matrixAction(a_pressed, e_pressed, now_ms);
False- Decision
boing_ball_activeTrue- Execute
boingBallAction(d_pressed, b_pressed, a_pressed, c_pressed, now_ms);
False- Decision
demo_activeTrue- Execute
demoAction(k_pressed || a_pressed, b_pressed, c_pressed, user_long_pressed, d_pressed, b_pressed, now_ms);
False- Decision
simon_activeTrue- Execute
simonAction(a_pressed, b_pressed, c_pressed, d_pressed, now_ms);
False- Decision
calculator_activeTrue- Execute
calculatorAction(k_pressed || a_pressed, c_pressed, d_pressed, b_pressed, now_ms);
False- Decision
kaleidoscope_activeTrue- Execute
kaleidoscopeAction(k_pressed || a_pressed, d_pressed, c_pressed, e_pressed, now_ms);
False- Decision
warp_tunnel_activeTrue- Execute
warpTunnelAction(k_pressed || a_pressed, c_pressed, e_pressed, now_ms);
False- Decision
mandelbrot_activeTrue- Execute
mandelbrotAction(k_pressed || a_pressed, c_pressed, b_pressed, e_pressed, now_ms);
False- Decision
slideshow_activeTrue- Execute
slideshowAction(k_pressed || a_pressed, c_pressed, now_ms);
False- Decision
spiffs_test_activeTrue- Execute
spiffsTestAction(k_pressed || a_pressed);
False- Decision
sound_test_activeTrue- Execute
soundTestAction(k_pressed || a_pressed, d_pressed, b_pressed, e_pressed);
False- Decision
sd_card_test_activeTrue- Execute
sdCardTestAction(k_pressed || a_pressed, c_pressed, now_ms);
False- Decision
asteroids_activeTrue- Execute
asteroidsAction(k_pressed || a_pressed, c_pressed, game_now_ms);
False- Decision
battle_zone_activeTrue- Execute
battleZoneAction(k_pressed || a_pressed, c_pressed, game_now_ms);
False- Decision
boulder_dash_activeTrue- Execute
boulderDashAction(k_pressed || a_pressed, game_now_ms);
False- Decision
tempest_activeTrue- Execute
tempestAction(k_pressed || a_pressed, e_pressed, game_now_ms);
False- Decision
invaders_activeTrue- Execute
invadersAction(k_pressed || a_pressed, c_pressed, game_now_ms);
False- Decision
missile_command_activeTrue- Execute
missileCommandAction(k_pressed || a_pressed, e_pressed, game_now_ms);
False- Decision
breakout_activeTrue- Execute
breakoutAction(k_pressed || a_pressed, e_pressed, game_now_ms);
False- Decision
crawler_activeTrue- Execute
crawlerAction(k_pressed || a_pressed, stable_shield_levels[0] == 0 || stable_shield_levels[6] == 0, game_now_ms);
False- Decision
maze_chase_activeTrue- Execute
mazeChaseAction(k_pressed || a_pressed, game_now_ms);
False- Decision
scramble_activeTrue- Execute
scrambleAction(k_pressed || a_pressed, game_now_ms);
False- Decision
defender_activeTrue- Execute
defenderAction(k_pressed || a_pressed, c_pressed, game_now_ms);
False- Decision
galaga_activeTrue- Execute
galagaAction(k_pressed || a_pressed, game_now_ms);
False- Decision
dungeon_activeTrue- Execute
dungeonAIControl(e_pressed, game_now_ms); - Execute
dungeonAction(k_pressed || a_pressed, b_pressed, c_pressed, d_pressed, game_now_ms);
False- Decision
k_pressed || a_pressedTrue- Execute
menu_select(now_ms);
FalseContinue after this decision.
- Execute
turboLoaderUpdate(menu_current_screen(), now_ms); - Execute
const bool checkers_requested = memory_ok && menu_current_screen() == SCREEN_CHECKERS; - Decision
checkers_requested && !checkers_active && !turboLoaderActive()True- Execute
startCheckers(game_now_ms); - Execute
checkers_active = true;
False- Decision
!checkers_requested && checkers_activeTrue- Execute
stopCheckers(); - Execute
checkers_active = false;
FalseContinue after this decision.
- Execute
const bool othello_requested = memory_ok && menu_current_screen() == SCREEN_OTHELLO; - Decision
othello_requested && !othello_active && !turboLoaderActive()True- Execute
startOthello(game_now_ms); - Execute
othello_active = true;
False- Decision
!othello_requested && othello_activeTrue- Execute
stopOthello(); - Execute
othello_active = false;
FalseContinue after this decision.
- Execute
const bool connect_four_requested = memory_ok && menu_current_screen() == SCREEN_CONNECT_FOUR; - Decision
connect_four_requested && !connect_four_active && !turboLoaderActive()True- Execute
startConnectFour(game_now_ms); - Execute
connect_four_active = true;
False- Decision
!connect_four_requested && connect_four_activeTrue- Execute
stopConnectFour(); - Execute
connect_four_active = false;
FalseContinue after this decision.
- Execute
const bool battleships_requested = memory_ok && menu_current_screen() == SCREEN_BATTLESHIPS; - Decision
battleships_requested && !battleships_active && !turboLoaderActive()True- Execute
startBattleships(game_now_ms); - Execute
battleships_active = true;
False- Decision
!battleships_requested && battleships_activeTrue- Execute
stopBattleships(); - Execute
battleships_active = false;
FalseContinue after this decision.
- Execute
const bool yahtzee_requested = memory_ok && menu_current_screen() == SCREEN_YAHTZEE; - Decision
yahtzee_requested && !yahtzee_active && !turboLoaderActive()True- Execute
startYahtzee(game_now_ms); - Execute
yahtzee_active = true;
False- Decision
!yahtzee_requested && yahtzee_activeTrue- Execute
stopYahtzee(); - Execute
yahtzee_active = false;
FalseContinue after this decision.
- Execute
const bool blackjack_requested = memory_ok && menu_current_screen() == SCREEN_BLACKJACK; - Decision
blackjack_requested && !blackjack_active && !turboLoaderActive()True- Execute
startBlackjack(game_now_ms); - Execute
blackjack_active = true;
False- Decision
!blackjack_requested && blackjack_activeTrue- Execute
stopBlackjack(); - Execute
blackjack_active = false;
FalseContinue after this decision.
- Execute
const bool poker_requested = memory_ok && menu_current_screen() == SCREEN_POKER; - Decision
poker_requested && !poker_active && !turboLoaderActive()True- Execute
startPoker(game_now_ms); - Execute
poker_active = true;
False- Decision
!poker_requested && poker_activeTrue- Execute
stopPoker(); - Execute
poker_active = false;
FalseContinue after this decision.
- Execute
const bool poker_dice_requested = memory_ok && menu_current_screen() == SCREEN_POKER_DICE; - Decision
poker_dice_requested && !poker_dice_active && !turboLoaderActive()True- Execute
startPokerDice(game_now_ms); - Execute
poker_dice_active = true;
False- Decision
!poker_dice_requested && poker_dice_activeTrue- Execute
stopPokerDice(); - Execute
poker_dice_active = false;
FalseContinue after this decision.
- Execute
const bool pig_requested = memory_ok && menu_current_screen() == SCREEN_PIG; - Decision
pig_requested && !pig_active && !turboLoaderActive()True- Execute
startPig(game_now_ms); - Execute
pig_active = true;
False- Decision
!pig_requested && pig_activeTrue- Execute
stopPig(); - Execute
pig_active = false;
FalseContinue after this decision.
- Execute
const bool brightness_settings_requested = memory_ok && menu_current_screen() == SCREEN_BRIGHTNESS; - Decision
brightness_settings_requested && !brightness_settings_activeTrue- Execute
settingsStart(SETTINGS_BRIGHTNESS); - Execute
brightness_settings_active = true;
False- Decision
!brightness_settings_requested && brightness_settings_activeTrue- Execute
settingsStop(); - Execute
brightness_settings_active = false;
FalseContinue after this decision.
- Execute
const bool volume_settings_requested = memory_ok && menu_current_screen() == SCREEN_VOLUME; - Decision
volume_settings_requested && !volume_settings_activeTrue- Execute
settingsStart(SETTINGS_VOLUME); - Execute
volume_settings_active = true;
False- Decision
!volume_settings_requested && volume_settings_activeTrue- Execute
settingsStop(); - Execute
volume_settings_active = false;
FalseContinue after this decision.
- Execute
const bool wifi_settings_requested = memory_ok && menu_current_screen() == SCREEN_WIFI; - Decision
wifi_settings_requested && !wifi_settings_activeTrue- Execute
settingsStart(SETTINGS_WIFI); - Execute
wifi_settings_active = true;
False- Decision
!wifi_settings_requested && wifi_settings_activeTrue- Execute
settingsStop(); - Execute
wifi_settings_active = false;
FalseContinue after this decision.
- Execute
const bool esp_info_requested = memory_ok && menu_current_screen() == SCREEN_ESP_INFO; - Decision
esp_info_requested && !esp_info_activeTrue- Execute
settingsStart(SETTINGS_ESP_INFO); - Execute
esp_info_active = true;
False- Decision
!esp_info_requested && esp_info_activeTrue- Execute
settingsStop(); - Execute
esp_info_active = false;
FalseContinue after this decision.
- Preprocessor directive
#if RAM_MONITOR_ENABLED - Execute
const bool ram_log_requested = memory_ok && menu_current_screen() == SCREEN_RAM_LOG; - Decision
ram_log_requested && !ram_log_activeTrue- Execute
settingsStart(SETTINGS_RAM_LOG); - Execute
ram_log_active = true;
False- Decision
!ram_log_requested && ram_log_activeTrue- Execute
settingsStop(); - Execute
ram_log_active = false;
FalseContinue after this decision.
- Preprocessor directive
#endif - Execute
const bool restart_settings_requested = memory_ok && menu_current_screen() == SCREEN_RESTART; - Decision
restart_settings_requested && !restart_settings_activeTrue- Execute
settingsStart(SETTINGS_RESTART); - Execute
restart_settings_active = true;
False- Decision
!restart_settings_requested && restart_settings_activeTrue- Execute
settingsStop(); - Execute
restart_settings_active = false;
FalseContinue after this decision.
- Decision
restart_settings_active && settingsRestartRequested()True- Execute
ESP_LOGI(TAG, "Settings restart confirmed"); - Execute
esp_restart();
FalseContinue after this decision.
- Execute
const bool power_off_settings_requested = memory_ok && menu_current_screen() == SCREEN_POWER_OFF; - Decision
power_off_settings_requested && !power_off_settings_activeTrue- Execute
settingsStart(SETTINGS_POWER_OFF); - Execute
power_off_settings_active = true;
False- Decision
!power_off_settings_requested && power_off_settings_activeTrue- Execute
settingsStop(); - Execute
power_off_settings_active = false;
FalseContinue after this decision.
- Decision
power_off_settings_active && settingsPowerOffRequested()True- Execute
const bool slept = enter_light_sleep(panel, &tf_available); - Execute
settingsCancelPowerOffRequest(); - Decision
sleptTrue- Execute
settingsStop(); - Execute
power_off_settings_active = false; - Execute
menu_home((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
const bool game_config_requested = memory_ok && menu_current_screen() == SCREEN_GAME_CONFIG; - Decision
game_config_requested && !game_config_activeTrue- Execute
startGameConfig(now_ms); - Execute
game_config_active = true;
False- Decision
!game_config_requested && game_config_activeTrue- Execute
stopGameConfig(); - Execute
game_config_active = false;
FalseContinue after this decision.
- Execute
const bool font_test_requested = memory_ok && menu_current_screen() == SCREEN_FONT_TEST; - Decision
font_test_requested && !font_test_activeTrue- Execute
startFontTest(); - Execute
font_test_active = true;
False- Decision
!font_test_requested && font_test_activeTrue- Execute
stopFontTest(); - Execute
font_test_active = false;
FalseContinue after this decision.
- Execute
const bool matrix_requested = memory_ok && menu_current_screen() == SCREEN_MATRIX; - Decision
matrix_requested && !matrix_activeTrue- Execute
startMatrix(now_ms); - Execute
matrix_active = true;
False- Decision
!matrix_requested && matrix_activeTrue- Execute
stopMatrix(); - Execute
matrix_active = false;
FalseContinue after this decision.
- Execute
const bool boing_ball_requested = memory_ok && menu_current_screen() == SCREEN_BOING_BALL; - Decision
boing_ball_requested && !boing_ball_activeTrue- Execute
startBoingBall(now_ms); - Execute
boing_ball_active = true;
False- Decision
!boing_ball_requested && boing_ball_activeTrue- Execute
stopBoingBall(); - Execute
boing_ball_active = false;
FalseContinue after this decision.
- Execute
const bool demo_requested = memory_ok && menu_current_screen() == SCREEN_ABOUT; - Decision
demo_requested && !demo_activeTrue- Execute
startDemo(now_ms); - Execute
demo_active = true;
False- Decision
!demo_requested && demo_activeTrue- Execute
stopDemo(); - Execute
demo_active = false;
FalseContinue after this decision.
- Execute
const bool simon_requested = memory_ok && menu_current_screen() == SCREEN_SIMON; - Decision
simon_requested && !simon_activeTrue- Execute
startSimon(now_ms); - Execute
simon_active = true;
False- Decision
!simon_requested && simon_activeTrue- Execute
stopSimon(); - Execute
simon_active = false;
FalseContinue after this decision.
- Execute
const bool calculator_requested = memory_ok && menu_current_screen() == SCREEN_CALCULATOR; - Decision
calculator_requested && !calculator_activeTrue- Execute
startCalculator(now_ms); - Execute
calculator_active = true;
False- Decision
!calculator_requested && calculator_activeTrue- Execute
stopCalculator(); - Execute
calculator_active = false;
FalseContinue after this decision.
- Execute
const bool kaleidoscope_requested = memory_ok && menu_current_screen() == SCREEN_KALEIDOSCOPE; - Decision
kaleidoscope_requested && !kaleidoscope_activeTrue- Execute
startKaleidoscope(now_ms); - Execute
kaleidoscope_active = true;
False- Decision
!kaleidoscope_requested && kaleidoscope_activeTrue- Execute
stopKaleidoscope(); - Execute
kaleidoscope_active = false;
FalseContinue after this decision.
- Execute
const bool warp_tunnel_requested = memory_ok && menu_current_screen() == SCREEN_WARP_TUNNEL; - Decision
warp_tunnel_requested && !warp_tunnel_activeTrue- Execute
startWarpTunnel(now_ms); - Execute
warp_tunnel_active = true;
False- Decision
!warp_tunnel_requested && warp_tunnel_activeTrue- Execute
stopWarpTunnel(); - Execute
warp_tunnel_active = false;
FalseContinue after this decision.
- Execute
const bool mandelbrot_requested = memory_ok && menu_current_screen() == SCREEN_MANDELBROT; - Decision
mandelbrot_requested && !mandelbrot_activeTrue- Execute
startMandelbrot(now_ms); - Execute
mandelbrot_active = true;
False- Decision
!mandelbrot_requested && mandelbrot_activeTrue- Execute
stopMandelbrot(); - Execute
mandelbrot_active = false;
FalseContinue after this decision.
- Execute
const bool clock_requested = memory_ok && menu_current_screen() == SCREEN_CLOCK; - Decision
clock_requested && !clock_activeTrue- Execute
startClock(now_ms); - Execute
clock_active = true;
False- Decision
!clock_requested && clock_activeTrue- Execute
stopClock(); - Execute
clock_active = false;
FalseContinue after this decision.
- Decision
storage_readyTrue- Execute
settingsClockAutoSyncUpdate(now_ms);
FalseContinue after this decision.
- Execute
const bool slideshow_requested = memory_ok && menu_current_screen() == SCREEN_SLIDESHOW; - Decision
slideshow_requested && !slideshow_activeTrue- Execute
startSlideshow(now_ms); - Execute
slideshow_active = true;
False- Decision
!slideshow_requested && slideshow_activeTrue- Execute
stopSlideshow(); - Execute
slideshow_active = false;
FalseContinue after this decision.
- Execute
const bool spiffs_test_requested = memory_ok && menu_current_screen() == SCREEN_SPIFFS_TEST; - Decision
spiffs_test_requested && !spiffs_test_activeTrue- Execute
startSpiffsTest(); - Execute
spiffs_test_active = true;
False- Decision
!spiffs_test_requested && spiffs_test_activeTrue- Execute
stopSpiffsTest(); - Execute
spiffs_test_active = false;
FalseContinue after this decision.
- Execute
const bool sound_test_requested = memory_ok && menu_current_screen() == SCREEN_SOUND_TEST; - Decision
sound_test_requested && !sound_test_activeTrue- Execute
startSoundTest(); - Execute
sound_test_active = true;
False- Decision
!sound_test_requested && sound_test_activeTrue- Execute
stopSoundTest(); - Execute
sound_test_active = false;
FalseContinue after this decision.
- Execute
const bool sd_card_test_requested = memory_ok && menu_current_screen() == SCREEN_SD_CARD_TEST; - Decision
sd_card_test_requested && !sd_card_test_activeTrue- Execute
startSdCardTest(now_ms); - Execute
sd_card_test_active = true;
False- Decision
!sd_card_test_requested && sd_card_test_activeTrue- Execute
stopSdCardTest(); - Execute
sd_card_test_active = false;
FalseContinue after this decision.
- Execute
const bool hall_of_fame_requested = memory_ok && menu_current_screen() == SCREEN_HALL_OF_FAME; - Decision
hall_of_fame_requested && !hall_of_fame_activeTrue- Execute
startHallOfFame(now_ms); - Execute
hall_of_fame_active = true;
False- Decision
!hall_of_fame_requested && hall_of_fame_activeTrue- Execute
stopHallOfFame(); - Execute
hall_of_fame_active = false;
FalseContinue after this decision.
- Execute
const bool asteroids_requested = memory_ok && menu_current_screen() == SCREEN_ASTEROIDS; - Decision
asteroids_requested && !asteroids_active && !turboLoaderActive()True- Execute
startAsteroids(game_now_ms); - Execute
asteroids_active = true;
False- Decision
!asteroids_requested && asteroids_activeTrue- Execute
stopAsteroids(); - Execute
asteroids_active = false;
FalseContinue after this decision.
- Execute
const bool battle_zone_requested = memory_ok && menu_current_screen() == SCREEN_BATTLE_ZONE; - Decision
battle_zone_requested && !battle_zone_active && !turboLoaderActive()True- Execute
startBattleZone(game_now_ms); - Execute
battle_zone_active = true;
False- Decision
!battle_zone_requested && battle_zone_activeTrue- Execute
stopBattleZone(); - Execute
battle_zone_active = false;
FalseContinue after this decision.
- Execute
const bool boulder_dash_requested = memory_ok && menu_current_screen() == SCREEN_BOULDER_DASH; - Decision
boulder_dash_requested && !boulder_dash_active && !turboLoaderActive()True- Execute
startBoulderDash(game_now_ms); - Execute
boulder_dash_active = true;
False- Decision
!boulder_dash_requested && boulder_dash_activeTrue- Execute
stopBoulderDash(); - Execute
boulder_dash_active = false;
FalseContinue after this decision.
- Execute
const bool tempest_requested = memory_ok && menu_current_screen() == SCREEN_TEMPEST; - Decision
tempest_requested && !tempest_active && !turboLoaderActive()True- Execute
startTempest(game_now_ms); - Execute
tempest_active = true;
False- Decision
!tempest_requested && tempest_activeTrue- Execute
stopTempest(); - Execute
tempest_active = false;
FalseContinue after this decision.
- Execute
const bool invaders_requested = memory_ok && menu_current_screen() == SCREEN_INVADERS; - Decision
invaders_requested && !invaders_active && !turboLoaderActive()True- Execute
startInvaders(game_now_ms); - Execute
invaders_active = true;
False- Decision
!invaders_requested && invaders_activeTrue- Execute
stopInvaders(); - Execute
invaders_active = false;
FalseContinue after this decision.
- Execute
const bool missile_command_requested = memory_ok && menu_current_screen() == SCREEN_MISSILE_COMMAND; - Decision
missile_command_requested && !missile_command_active && !turboLoaderActive()True- Execute
startMissileCommand(game_now_ms); - Execute
missile_command_active = true;
False- Decision
!missile_command_requested && missile_command_activeTrue- Execute
stopMissileCommand(); - Execute
missile_command_active = false;
FalseContinue after this decision.
- Execute
const bool breakout_requested = memory_ok && menu_current_screen() == SCREEN_BREAKOUT; - Decision
breakout_requested && !breakout_active && !turboLoaderActive()True- Execute
startBreakout(game_now_ms); - Execute
breakout_active = true;
False- Decision
!breakout_requested && breakout_activeTrue- Execute
stopBreakout(); - Execute
breakout_active = false;
FalseContinue after this decision.
- Execute
const bool crawler_requested = memory_ok && menu_current_screen() == SCREEN_CRAWLER; - Decision
crawler_requested && !crawler_active && !turboLoaderActive()True- Execute
startCrawler(game_now_ms); - Execute
crawler_active = true;
False- Decision
!crawler_requested && crawler_activeTrue- Execute
stopCrawler(); - Execute
crawler_active = false;
FalseContinue after this decision.
- Execute
const bool maze_chase_requested = memory_ok && menu_current_screen() == SCREEN_MAZE_CHASE; - Decision
maze_chase_requested && !maze_chase_active && !turboLoaderActive()True- Execute
startMazeChase(game_now_ms); - Execute
maze_chase_active = true;
False- Decision
!maze_chase_requested && maze_chase_activeTrue- Execute
stopMazeChase(); - Execute
maze_chase_active = false;
FalseContinue after this decision.
- Execute
const bool scramble_requested = memory_ok && menu_current_screen() == SCREEN_SCRAMBLE; - Decision
scramble_requested && !scramble_active && !turboLoaderActive()True- Execute
startScramble(game_now_ms); - Execute
scramble_active = true;
False- Decision
!scramble_requested && scramble_activeTrue- Execute
stopScramble(); - Execute
scramble_active = false;
FalseContinue after this decision.
- Execute
const bool defender_requested = memory_ok && menu_current_screen() == SCREEN_DEFENDER; - Decision
defender_requested && !defender_active && !turboLoaderActive()True- Execute
startDefender(game_now_ms); - Execute
defender_active = true;
False- Decision
!defender_requested && defender_activeTrue- Execute
stopDefender(); - Execute
defender_active = false;
FalseContinue after this decision.
- Execute
const bool galaga_requested = memory_ok && menu_current_screen() == SCREEN_GALAGA; - Decision
galaga_requested && !galaga_active && !turboLoaderActive()True- Execute
startGalaga(game_now_ms); - Execute
galaga_active = true;
False- Decision
!galaga_requested && galaga_activeTrue- Execute
stopGalaga(); - Execute
galaga_active = false;
FalseContinue after this decision.
- Execute
const bool dungeon_requested = memory_ok && menu_current_screen() == SCREEN_DUNGEON; - Decision
dungeon_requested && !dungeon_active && !turboLoaderActive()True- Execute
startDungeon(game_now_ms); - Execute
dungeon_active = true;
False- Decision
!dungeon_requested && dungeon_activeTrue- Execute
stopDungeon(game_now_ms); - Execute
dungeon_active = false;
FalseContinue after this decision.
- Decision
(now - last_controller_refresh) >= pdMS_TO_TICKS(CONTROLLER_REFRESH_MS)True- Execute
last_controller_refresh = now; - Execute
const esp_err_t live_result = controller_input_read(&live_joystick); - Execute
joystick_valid = live_result == ESP_OK; - Decision
!joystick_valid && previous_joystick_validTrue- Execute
ESP_LOGE(TAG, "Live joystick read failed: %s", esp_err_to_name(live_result));
False- Decision
joystick_valid && !previous_joystick_validTrue- Execute
ESP_LOGI(TAG, "Live joystick readings recovered");
FalseContinue after this decision.
- Execute
const bool validity_changed = joystick_valid != previous_joystick_valid; - Execute
previous_joystick_valid = joystick_valid; - Execute
joystick_validity_changed = joystick_validity_changed || validity_changed; - Execute
batt_update(now_ms); - Decision
!app_activeTrue- Decision
!menu_update_buttons(left_held, right_held, now_ms)True- Execute
menu_update_joystick(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms);
FalseContinue after this decision.
- Execute
menu_update(now_ms);
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
menu_redraw_pending = menu_redraw_pending || menu_take_redraw(); - Decision
(now - last_render_refresh) >= pdMS_TO_TICKS(RENDER_REFRESH_MS)True- Execute
last_render_refresh = now; - Execute
const bool menu_redraw = menu_redraw_pending; - Execute
const bool validity_changed = joystick_validity_changed; - Execute
menu_redraw_pending = false; - Execute
joystick_validity_changed = false; - Decision
memory_okTrue- Decision
turboLoaderActive()True- Execute
drawTurboLoader(frame, now_ms); - Execute
draw_frame(panel, frame);
False- Decision
game_pausedTrue- Decision
pause_redraw || now_ms - pause_color_changed_ms >= 500UTrue- Execute
pause_color_index = pause_redraw ? 0 : (pause_color_index + 1U) % (sizeof(pause_colors) / sizeof(pause_colors[0])); - Execute
draw_text_centred(frame, 160, 78, "PAUSED", 2, pause_colors[pause_color_index]); - Execute
draw_frame(panel, frame); - Execute
pause_color_changed_ms = now_ms; - Execute
pause_redraw = false;
FalseContinue after this decision.
False- Decision
checkers_activeTrue- Execute
updateCheckers(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawCheckers(frame); - Execute
draw_frame(panel, frame);
False- Decision
othello_activeTrue- Execute
updateOthello(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawOthello(frame); - Execute
draw_frame(panel, frame);
False- Decision
connect_four_activeTrue- Execute
updateConnectFour(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawConnectFour(frame); - Execute
draw_frame(panel, frame);
False- Decision
battleships_activeTrue- Execute
updateBattleships(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawBattleships(frame); - Execute
draw_frame(panel, frame);
False- Decision
yahtzee_activeTrue- Execute
updateYahtzee(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawYahtzee(frame); - Execute
draw_frame(panel, frame);
False- Decision
blackjack_activeTrue- Execute
updateBlackjack(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawBlackjack(frame); - Execute
draw_frame(panel, frame);
False- Decision
poker_activeTrue- Execute
updatePoker(frame, live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawPoker(frame); - Execute
draw_frame(panel, frame);
False- Decision
poker_dice_activeTrue- Execute
updatePokerDice(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawPokerDice(frame); - Execute
draw_frame(panel, frame);
False- Decision
pig_activeTrue- Execute
updatePig(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawPig(frame); - Execute
draw_frame(panel, frame);
False- Decision
brightness_settings_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
set_backlight_brightness(settingsBrightness()); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
volume_settings_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
wifi_settings_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
esp_info_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
ram_log_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
restart_settings_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
power_off_settings_activeTrue- Execute
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
settingsDraw(frame); - Execute
draw_frame(panel, frame);
False- Decision
game_config_activeTrue- Execute
updateGameConfig(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms); - Execute
drawGameConfig(frame); - Execute
draw_frame(panel, frame);
False- Decision
font_test_activeTrue- Execute
drawFontTest(frame); - Execute
draw_frame(panel, frame);
False- Decision
about_activeTrue- Execute
updateAbout(now_ms); - Execute
drawAbout(frame); - Execute
draw_frame(panel, frame);
False- Decision
matrix_activeTrue- Execute
updateMatrix(now_ms); - Decision
matrixTakeFrame()True- Execute
drawMatrix(frame); - Execute
draw_frame(panel, frame);
FalseContinue after this decision.
False- Decision
boing_ball_activeTrue- Execute
updateBoingBall(now_ms); - Execute
drawBoingBall(frame); - Execute
draw_frame(panel, frame);
False- Decision
demo_activeTrue- Execute
updateDemo(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms); - Execute
drawDemo(frame); - Execute
draw_frame(panel, frame);
False- Decision
simon_activeTrue- Execute
updateSimon(now_ms); - Execute
drawSimon(frame); - Execute
draw_frame(panel, frame);
False- Decision
calculator_activeTrue- Execute
updateCalculator(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms); - Execute
drawCalculator(frame); - Execute
draw_frame(panel, frame);
False- Decision
kaleidoscope_activeTrue- Execute
updateKaleidoscope(now_ms); - Execute
drawKaleidoscope(frame); - Execute
draw_frame(panel, frame);
False- Decision
warp_tunnel_activeTrue- Execute
updateWarpTunnel(now_ms); - Execute
drawWarpTunnel(frame); - Execute
draw_frame(panel, frame);
False- Decision
mandelbrot_activeTrue- Execute
updateMandelbrot(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms); - Decision
mandelbrotTakeFrame()True- Execute
drawMandelbrot(frame); - Execute
draw_frame(panel, frame);
FalseContinue after this decision.
False- Decision
clock_activeTrue- Execute
updateClock(now_ms); - Decision
clockTakeFrame()True- Execute
drawClock(frame); - Execute
draw_frame(panel, frame);
FalseContinue after this decision.
False- Decision
slideshow_activeTrue- Execute
updateSlideshow(frame, now_ms); - Execute
drawSlideshow(frame); - Execute
draw_frame(panel, frame);
False- Decision
spiffs_test_activeTrue- Execute
drawSpiffsTest(frame); - Execute
draw_frame(panel, frame);
False- Decision
sound_test_activeTrue- Execute
drawSoundTest(frame); - Execute
draw_frame(panel, frame);
False- Decision
sd_card_test_activeTrue- Execute
updateSdCardTest(frame, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms); - Execute
drawSdCardTest(frame); - Execute
draw_frame(panel, frame);
False- Decision
asteroids_activeTrue- Execute
updateAsteroids(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawAsteroids(frame); - Execute
draw_frame(panel, frame);
False- Decision
battle_zone_activeTrue- Execute
updateBattleZone(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawBattleZone(frame); - Execute
draw_frame(panel, frame);
False- Decision
boulder_dash_activeTrue- Execute
updateBoulderDash(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawBoulderDash(frame); - Execute
draw_frame(panel, frame);
False- Decision
tempest_activeTrue- Execute
updateTempest(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawTempest(frame); - Execute
draw_frame(panel, frame);
False- Decision
invaders_activeTrue- Execute
updateInvaders(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawInvaders(frame); - Execute
draw_frame(panel, frame);
False- Decision
missile_command_activeTrue- Execute
updateMissileCommand(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, stable_shield_levels[0] == 0, right_held, game_now_ms); - Execute
drawMissileCommand(frame); - Execute
draw_frame(panel, frame);
False- Decision
breakout_activeTrue- Execute
updateBreakout(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawBreakout(frame); - Execute
draw_frame(panel, frame);
False- Decision
crawler_activeTrue- Execute
updateCrawler(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawCrawler(frame); - Execute
draw_frame(panel, frame);
False- Decision
maze_chase_activeTrue- Execute
updateMazeChase(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, game_now_ms); - Execute
drawMazeChase(frame); - Execute
draw_frame(panel, frame);
False- Decision
scramble_activeTrue- Execute
updateScramble(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, stable_shield_levels[0] == 0, stable_shield_levels[1] == 0, game_now_ms); - Execute
drawScramble(frame); - Execute
draw_frame(panel, frame);
False- Decision
defender_activeTrue- Execute
updateDefender(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, stable_shield_levels[0] == 0, stable_shield_levels[1] == 0, game_now_ms); - Execute
drawDefender(frame); - Execute
draw_frame(panel, frame);
False- Decision
galaga_activeTrue- Execute
updateGalaga(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, stable_shield_levels[0] == 0, game_now_ms); - Execute
drawGalaga(frame); - Execute
draw_frame(panel, frame);
False- Decision
dungeon_activeTrue- Execute
updateDungeon(frame, live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, game_now_ms); - Execute
drawDungeon(frame); - Execute
draw_frame(panel, frame);
False- Decision
hall_of_fame_activeTrue- Execute
updateHallOfFame(live_joystick.x_raw - JOYSTICK_CENTRE_X, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, left_held, right_held, now_ms); - Execute
drawHallOfFame(frame); - Execute
draw_frame(panel, frame);
False- Decision
menu_current_screen() == SCREEN_CONTROLLER_TESTTrue- Execute
draw_controller_view(frame, &live_joystick, joystick_valid, stable_shield_levels); - Execute
draw_frame(panel, frame);
False- Decision
menu_current_screen() == SCREEN_GRAPHICS_TESTTrue- Decision
menu_redrawTrue- Execute
fill_color_bars(frame); - Execute
draw_frame(panel, frame);
FalseContinue after this decision.
False- Decision
menu_current_screen() == SCREEN_BATTERYTrue- Execute
draw_battery_view(frame); - Execute
draw_frame(panel, frame);
False- Decision
menu_redraw || validity_changedTrue- Execute
draw_menu_view(frame, joystick_valid); - Execute
draw_frame(panel, frame);
FalseContinue after this decision.
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
vTaskDelay(pdMS_TO_TICKS(1));
Read original function source
void app_main(void)
{
esp_chip_info_t chip_info = {0};
uint32_t flash_size = 0;
esp_chip_info(&chip_info);
const esp_err_t flash_result = esp_flash_get_size(NULL, &flash_size);
const bool psram_initialized = esp_psram_is_initialized();
const size_t psram_chip_size = esp_psram_get_size();
const size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
const size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic");
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision,
chip_info.cores);
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
if (flash_result == ESP_OK) {
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
} else {
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
}
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no");
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size);
ESP_LOGI(TAG, "PSRAM heap total/free: %u / %u bytes", (unsigned)psram_total,
(unsigned)psram_free);
const bool memory_ok = flash_result == ESP_OK &&
flash_size == EXPECTED_FLASH_BYTES &&
psram_initialized &&
psram_chip_size >= EXPECTED_PSRAM_BYTES;
if (!memory_ok) {
ESP_LOGE(TAG, "Memory check FAILED: expected 16 MB flash and 8 MB PSRAM");
}
uint16_t *frame = heap_caps_malloc(
FRAME_BUFFER_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
if (frame == NULL) {
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM display buffer",
(unsigned)FRAME_BUFFER_BYTES);
return;
}
ESP_LOGI(TAG, "PSRAM display buffer allocated: %u bytes",
(unsigned)FRAME_BUFFER_BYTES);
if (!globalOverlayInitialize()) {
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM overlay presentation buffer",
(unsigned)FRAME_BUFFER_BYTES);
heap_caps_free(frame);
return;
}
display_transfer_done = xSemaphoreCreateBinary();
if (display_transfer_done == NULL) {
ESP_LOGE(TAG, "Unable to allocate LCD transfer semaphore");
globalOverlayDeinitialize();
heap_caps_free(frame);
return;
}
initialize_buttons();
initialize_audio_shutdown();
esp_lcd_panel_handle_t panel = initialize_display();
if (memory_ok) {
clear(frame, RGB565(0, 0, 0));
} else {
clear(frame, RGB565(255, 0, 255));
ESP_LOGE(TAG, "Magenta screen indicates a memory diagnostic failure");
}
draw_frame(panel, frame);
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic report");
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision,
chip_info.cores);
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
if (flash_result == ESP_OK) {
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
} else {
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
}
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no");
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size);
ESP_LOGI(TAG, "PSRAM heap total/free at startup: %u / %u bytes",
(unsigned)psram_total, (unsigned)psram_free);
ESP_LOGI(TAG, "PSRAM display buffer: %u bytes", (unsigned)FRAME_BUFFER_BYTES);
if (memory_ok) {
ESP_LOGI(TAG, "BOARD DIAGNOSTIC: PASS");
} else {
ESP_LOGE(TAG, "BOARD DIAGNOSTIC: FAIL");
}
bool tf_available = false;
#if TF_CARD_ENABLED
tf_available = initialize_tf_card();
ESP_LOGI(TAG, "TF CARD: %s", tf_available ? "READY" : "NOT AVAILABLE");
#else
ESP_LOGI(TAG, "TF CARD: DISABLED");
#endif
const bool dungeon_files_ready = dungeonFilesInitialize(tf_available);
ESP_LOGI(TAG, "DUNGEON DATA: %s", dungeon_files_ready ? "READY" : "NOT AVAILABLE");
#if RAM_MONITOR_ENABLED
if (dungeon_files_ready) ramMonitorInitialize();
else ESP_LOGW(TAG, "RAM MONITOR: DISABLED (SPIFFS NOT AVAILABLE)");
#else
ESP_LOGI(TAG, "RAM MONITOR: DISABLED (FEATURE SWITCH)");
#endif
const bool storage_ready = game_storage_initialize();
const bool gold_ready = storage_ready && initGold();
const bool timing_ready = storage_ready && initAITiming();
const bool settings_ready = storage_ready && settingsInitialize();
const bool clock_time_ready = storage_ready && timeSyncInitialize();
const bool audio_ready = audioInitialize();
set_backlight_brightness(settingsBrightness());
ESP_LOGI(TAG, "GAME STORAGE: %s; GOLD: %s; AI TIMING: %s; SETTINGS: %s; CLOCK: %s; AUDIO: %s",
storage_ready ? "READY" : "FAILED",
gold_ready ? "READY" : "FAILED",
timing_ready ? "READY" : "DEFAULTS ONLY",
settings_ready ? "READY" : "DEFAULTS ONLY", clock_time_ready ? "READY" : "DEFAULTS ONLY",
audio_ready ? "READY" : "FAILED");
if (storage_ready) {
nvs_stats_t stats = {0};
const esp_err_t stats_result = nvs_get_stats(NULL, &stats);
if (stats_result == ESP_OK) {
ESP_LOGI(TAG, "NVS entries: used %u; available %u; total %u",
(unsigned)stats.used_entries, (unsigned)stats.available_entries,
(unsigned)stats.total_entries);
} else {
ESP_LOGW(TAG, "Cannot read NVS capacity: %s", esp_err_to_name(stats_result));
}
}
ESP_LOGI(TAG, "Initializing raw joystick test");
ESP_LOGI(TAG, "Joystick X: GPIO1 / ADC1 channel 0");
ESP_LOGI(TAG, "Joystick Y: GPIO2 / ADC1 channel 1");
const esp_err_t joystick_result = controller_input_initialize();
if (joystick_result != ESP_OK) {
ESP_LOGE(TAG, "Joystick initialization failed: %s",
esp_err_to_name(joystick_result));
return;
}
const bool battery_ready = batt_initialize();
ESP_LOGI(TAG, "BATTERY ADC: %s", battery_ready ? "READY" : "FAILED");
ESP_LOGI(TAG, "RAW JOYSTICK TEST: READY");
ESP_LOGI(TAG, "USER short press captures a screenshot; hold USER in Demo opens its selector");
const gpio_num_t shield_button_pins[] = {
GPIO_NUM_16, GPIO_NUM_21, GPIO_NUM_17, GPIO_NUM_18,
GPIO_NUM_44, GPIO_NUM_43, GPIO_NUM_3,
};
const char *shield_button_names[] = {
"A", "B", "C", "D", "E", "F", "K",
};
const size_t shield_button_count =
sizeof(shield_button_pins) / sizeof(shield_button_pins[0]);
int previous_shield_levels[7];
int stable_shield_levels[7];
TickType_t shield_changed_at[7];
uint64_t shield_button_mask = 0;
for (size_t button = 0; button < shield_button_count; ++button) {
shield_button_mask |= 1ULL << shield_button_pins[button];
}
const gpio_config_t shield_button_config = {
.pin_bit_mask = shield_button_mask,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&shield_button_config));
for (size_t button = 0; button < shield_button_count; ++button) {
previous_shield_levels[button] =
gpio_get_level(shield_button_pins[button]);
stable_shield_levels[button] = -1;
shield_changed_at[button] = xTaskGetTickCount();
}
ESP_LOGI(TAG, "SHIELD BUTTON TEST: READY");
int previous_user_level = gpio_get_level(PIN_BUTTON_USER);
int stable_user_level = previous_user_level;
TickType_t user_changed_at = xTaskGetTickCount();
TickType_t user_pressed_at = user_changed_at;
bool user_long_sent = false;
bool user_capture_cancelled = false;
TickType_t last_controller_refresh =
xTaskGetTickCount() - pdMS_TO_TICKS(CONTROLLER_REFRESH_MS);
TickType_t last_render_refresh =
xTaskGetTickCount() - pdMS_TO_TICKS(RENDER_REFRESH_MS);
bool previous_joystick_valid = true;
joystick_state_t live_joystick = {0};
bool joystick_valid = true;
bool joystick_validity_changed = true;
bool menu_redraw_pending = true;
int previous_boot_level = gpio_get_level(PIN_BUTTON_BOOT);
int stable_boot_level = previous_boot_level;
TickType_t boot_changed_at = xTaskGetTickCount();
TickType_t boot_pressed_at = boot_changed_at;
bool boot_long_sent = false;
TickType_t full_reset_pressed_at = 0;
bool full_reset_armed = stable_boot_level != 0 || stable_user_level != 0;
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT,
stable_boot_level == 0 ? "PRESSED" : "RELEASED");
bool checkers_active = false;
bool othello_active = false;
bool connect_four_active = false;
bool battleships_active = false;
bool yahtzee_active = false;
bool blackjack_active = false;
bool poker_active = false;
bool poker_dice_active = false;
bool pig_active = false;
bool brightness_settings_active = false;
bool volume_settings_active = false;
bool wifi_settings_active = false;
bool esp_info_active = false;
bool ram_log_active = false;
bool power_off_settings_active = false;
bool restart_settings_active = false;
bool game_config_active = false;
bool font_test_active = false;
bool about_active = false;
bool matrix_active = false;
bool boing_ball_active = false;
bool demo_active = false;
bool simon_active = false;
bool calculator_active = false;
bool kaleidoscope_active = false;
bool warp_tunnel_active = false;
bool mandelbrot_active = false;
bool clock_active = false;
bool slideshow_active = false;
bool spiffs_test_active = false;
bool sound_test_active = false;
bool sd_card_test_active = false;
bool asteroids_active = false;
bool battle_zone_active = false;
bool boulder_dash_active = false;
bool tempest_active = false;
bool invaders_active = false;
bool missile_command_active = false;
bool breakout_active = false;
bool crawler_active = false;
bool maze_chase_active = false;
bool scramble_active = false;
bool defender_active = false;
bool galaga_active = false;
bool dungeon_active = false;
bool hall_of_fame_active = false;
bool game_paused = false;
bool pause_redraw = false;
static const uint16_t pause_colors[] = {
COLOR_RED, COLOR_YELLOW, COLOR_CYAN, COLOR_WHITE, COLOR_BLACK, COLOR_MAGENTA
};
size_t pause_color_index = 0;
uint32_t pause_color_changed_ms = 0;
uint32_t game_clock_last_ms = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
uint32_t game_now_ms = game_clock_last_ms;
menu_initialize((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
ESP_LOGI(TAG, "MENU: %s",
memory_ok ? "READY" : "DISABLED BY MEMORY CHECK");
while (true) {
if (reset_button_debounce_after_sleep) {
previous_boot_level = stable_boot_level = gpio_get_level(PIN_BUTTON_BOOT);
previous_user_level = stable_user_level = gpio_get_level(PIN_BUTTON_USER);
boot_changed_at = boot_pressed_at = user_changed_at = user_pressed_at =
xTaskGetTickCount();
boot_long_sent = false;
user_long_sent = false;
user_capture_cancelled = stable_boot_level == 0 && stable_user_level == 0;
full_reset_armed = stable_boot_level != 0 || stable_user_level != 0;
full_reset_pressed_at = 0;
for (size_t button = 0; button < shield_button_count; ++button) {
previous_shield_levels[button] = stable_shield_levels[button] =
gpio_get_level(shield_button_pins[button]);
shield_changed_at[button] = xTaskGetTickCount();
}
reset_button_debounce_after_sleep = false;
}
const int user_level = gpio_get_level(PIN_BUTTON_USER);
const TickType_t now = xTaskGetTickCount();
const uint32_t now_ms = (uint32_t)(now * portTICK_PERIOD_MS);
#if RAM_MONITOR_ENABLED
if (dungeon_files_ready) ramMonitorUpdate(now_ms);
#endif
// Games share a clock that excludes paused time. Hardware and menus use real time.
if (!game_paused) game_now_ms += now_ms - game_clock_last_ms;
game_clock_last_ms = now_ms;
bool k_pressed = false;
bool a_pressed = false;
bool b_pressed = false;
bool c_pressed = false;
bool d_pressed = false;
bool e_pressed = false;
bool f_pressed = false;
bool user_short_pressed = false;
bool user_long_pressed = false;
bool boot_short = false;
bool boot_long = false;
const int boot_level = gpio_get_level(PIN_BUTTON_BOOT);
if (boot_level != previous_boot_level) {
previous_boot_level = boot_level;
boot_changed_at = now;
}
if (boot_level != stable_boot_level &&
(now - boot_changed_at) >= pdMS_TO_TICKS(30)) {
stable_boot_level = boot_level;
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT,
stable_boot_level == 0 ? "PRESSED" : "RELEASED");
if (stable_boot_level == 0) {
boot_pressed_at = now;
boot_long_sent = false;
} else {
boot_short = !boot_long_sent;
}
}
if (stable_boot_level == 0 && !boot_long_sent &&
(now - boot_pressed_at) >= pdMS_TO_TICKS(800)) {
boot_long_sent = true;
boot_long = true;
}
if (user_level != previous_user_level) {
previous_user_level = user_level;
user_changed_at = now;
}
if (user_level != stable_user_level &&
(now - user_changed_at) >= pdMS_TO_TICKS(30)) {
stable_user_level = user_level;
if (stable_user_level == 0) {
user_pressed_at = now;
user_long_sent = false;
user_capture_cancelled = stable_boot_level == 0;
} else if (!user_long_sent && !user_capture_cancelled) {
user_short_pressed = true;
}
}
// The BOOT + USER restart chord always takes precedence over capture or Demo control.
if (stable_boot_level == 0 && stable_user_level == 0) {
user_capture_cancelled = true;
}
if (stable_user_level == 0 && stable_boot_level != 0 && !user_capture_cancelled &&
!user_long_sent &&
(now - user_pressed_at) >= pdMS_TO_TICKS(USER_LONG_PRESS_MS)) {
user_long_sent = true;
user_long_pressed = true;
}
for (size_t button = 0; button < shield_button_count; ++button) {
const int level = gpio_get_level(shield_button_pins[button]);
if (level != previous_shield_levels[button]) {
previous_shield_levels[button] = level;
shield_changed_at[button] = now;
}
if (level != stable_shield_levels[button] &&
(now - shield_changed_at[button]) >= pdMS_TO_TICKS(30)) {
if (button == 6 && stable_shield_levels[button] == 1 && level == 0) {
k_pressed = true;
}
if (stable_shield_levels[button] == 1 && level == 0) {
if (button == 0) a_pressed = true;
if (button == 1) b_pressed = true;
if (button == 2) c_pressed = true;
if (button == 3) d_pressed = true;
if (button == 4) e_pressed = true;
if (button == 5) f_pressed = true;
}
// C is game-specific; only the board's BOOT button navigates back/home.
stable_shield_levels[button] = level;
ESP_LOGI(TAG, "Shield button %s (GPIO%d): %s",
shield_button_names[button],
(int)shield_button_pins[button],
level == 0 ? "PRESSED" : "RELEASED");
}
}
// Both board buttons held together perform a normal ESP reboot.
// Require a release first so buttons held during startup cannot loop resets.
if (stable_boot_level != 0 || stable_user_level != 0) {
full_reset_armed = true;
full_reset_pressed_at = 0;
} else if (full_reset_armed && full_reset_pressed_at == 0) {
full_reset_pressed_at = now;
} else if (full_reset_armed &&
(now - full_reset_pressed_at) >= pdMS_TO_TICKS(FULL_RESET_HOLD_MS)) {
ESP_LOGW(TAG, "BOOT and USER held: restarting ESP");
esp_restart();
}
if (user_short_pressed) {
const char *const screenshot_source_label = demo_active ? demoScreenshotLabel() :
hall_of_fame_active ? hallOfFameScreenshotLabel() : menuScreenshotLabel();
screenshotRequest(screenshot_source_label);
// Static menu screens need one scheduled render before their queued capture can send.
menu_redraw_pending = true;
}
const bool left_held = stable_shield_levels[3] == 0; // D
const bool right_held = stable_shield_levels[1] == 0; // B
const bool app_active = checkers_active || othello_active || connect_four_active || battleships_active ||
yahtzee_active || blackjack_active || poker_active || poker_dice_active || pig_active ||
brightness_settings_active || volume_settings_active || wifi_settings_active || esp_info_active || ram_log_active ||
power_off_settings_active || restart_settings_active || game_config_active || font_test_active ||
about_active || matrix_active || boing_ball_active || demo_active || simon_active || calculator_active || kaleidoscope_active || warp_tunnel_active || mandelbrot_active ||
clock_active || slideshow_active || spiffs_test_active || sound_test_active || sd_card_test_active ||
asteroids_active || battle_zone_active || boulder_dash_active || tempest_active || invaders_active || missile_command_active ||
breakout_active || crawler_active || maze_chase_active || scramble_active || defender_active || galaga_active ||
dungeon_active || hall_of_fame_active;
if (!app_active || boot_short || boot_long) {
game_paused = false;
pause_redraw = false;
} else if (f_pressed) {
game_paused = !game_paused;
pause_redraw = game_paused;
ESP_LOGI(TAG, "App %s", game_paused ? "PAUSED" : "RESUMED");
}
if (boot_long) {
ESP_LOGI(TAG, "BOOT HOME");
menu_home(now_ms);
} else if (boot_short) {
ESP_LOGI(TAG, "BOOT BACK");
menu_back(now_ms);
} else if (game_paused || (app_active && f_pressed)) {
// Consume pause/resume and discard module actions while paused.
} else if (highScoreEntryActive()) {
highScoreEntryAction(a_pressed);
} else if (checkers_active) {
checkersAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (othello_active) {
othelloAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (connect_four_active) {
connectFourAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (battleships_active) {
battleshipsAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (yahtzee_active) {
yahtzeeAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (blackjack_active) {
blackjackAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (poker_active) {
pokerAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
} else if (poker_dice_active) {
pokerDiceAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
} else if (pig_active) {
pigAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (brightness_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (volume_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (wifi_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (esp_info_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (ram_log_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (restart_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (power_off_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (game_config_active) {
gameConfigAction(a_pressed, k_pressed, now_ms);
} else if (font_test_active) {
fontTestAction(k_pressed || a_pressed);
} else if (about_active) {
aboutAction(a_pressed, f_pressed, now_ms);
} else if (matrix_active) {
matrixAction(a_pressed, e_pressed, now_ms);
} else if (boing_ball_active) {
boingBallAction(d_pressed, b_pressed, a_pressed, c_pressed, now_ms);
} else if (demo_active) {
demoAction(k_pressed || a_pressed, b_pressed, c_pressed, user_long_pressed,
d_pressed, b_pressed, now_ms);
} else if (simon_active) {
simonAction(a_pressed, b_pressed, c_pressed, d_pressed, now_ms);
} else if (calculator_active) {
calculatorAction(k_pressed || a_pressed, c_pressed, d_pressed, b_pressed, now_ms);
} else if (kaleidoscope_active) {
kaleidoscopeAction(k_pressed || a_pressed, d_pressed, c_pressed, e_pressed, now_ms);
} else if (warp_tunnel_active) {
warpTunnelAction(k_pressed || a_pressed, c_pressed, e_pressed, now_ms);
} else if (mandelbrot_active) {
mandelbrotAction(k_pressed || a_pressed, c_pressed, b_pressed, e_pressed, now_ms);
} else if (slideshow_active) {
slideshowAction(k_pressed || a_pressed, c_pressed, now_ms);
} else if (spiffs_test_active) {
spiffsTestAction(k_pressed || a_pressed);
} else if (sound_test_active) {
soundTestAction(k_pressed || a_pressed, d_pressed, b_pressed, e_pressed);
} else if (sd_card_test_active) {
sdCardTestAction(k_pressed || a_pressed, c_pressed, now_ms);
} else if (asteroids_active) {
asteroidsAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (battle_zone_active) {
battleZoneAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (boulder_dash_active) {
boulderDashAction(k_pressed || a_pressed, game_now_ms);
} else if (tempest_active) {
tempestAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (invaders_active) {
invadersAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (missile_command_active) {
missileCommandAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (breakout_active) {
breakoutAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (crawler_active) {
crawlerAction(k_pressed || a_pressed,
stable_shield_levels[0] == 0 || stable_shield_levels[6] == 0,
game_now_ms);
} else if (maze_chase_active) {
mazeChaseAction(k_pressed || a_pressed, game_now_ms);
} else if (scramble_active) {
scrambleAction(k_pressed || a_pressed, game_now_ms);
} else if (defender_active) {
defenderAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (galaga_active) {
galagaAction(k_pressed || a_pressed, game_now_ms);
} else if (dungeon_active) {
dungeonAIControl(e_pressed, game_now_ms);
dungeonAction(k_pressed || a_pressed, b_pressed, c_pressed, d_pressed, game_now_ms);
} else if (k_pressed || a_pressed) {
menu_select(now_ms);
}
// One shared C64 Turbo Tape transition. It delays only a new game start;
// returning to the Games menu cancels it immediately through menu_back().
turboLoaderUpdate(menu_current_screen(), now_ms);
const bool checkers_requested = memory_ok && menu_current_screen() == SCREEN_CHECKERS;
if (checkers_requested && !checkers_active && !turboLoaderActive()) {
startCheckers(game_now_ms);
checkers_active = true;
} else if (!checkers_requested && checkers_active) {
stopCheckers();
checkers_active = false;
}
const bool othello_requested = memory_ok && menu_current_screen() == SCREEN_OTHELLO;
if (othello_requested && !othello_active && !turboLoaderActive()) {
startOthello(game_now_ms);
othello_active = true;
} else if (!othello_requested && othello_active) {
stopOthello();
othello_active = false;
}
const bool connect_four_requested =
memory_ok && menu_current_screen() == SCREEN_CONNECT_FOUR;
if (connect_four_requested && !connect_four_active && !turboLoaderActive()) {
startConnectFour(game_now_ms);
connect_four_active = true;
} else if (!connect_four_requested && connect_four_active) {
stopConnectFour();
connect_four_active = false;
}
const bool battleships_requested =
memory_ok && menu_current_screen() == SCREEN_BATTLESHIPS;
if (battleships_requested && !battleships_active && !turboLoaderActive()) {
startBattleships(game_now_ms);
battleships_active = true;
} else if (!battleships_requested && battleships_active) {
stopBattleships();
battleships_active = false;
}
const bool yahtzee_requested = memory_ok && menu_current_screen() == SCREEN_YAHTZEE;
if (yahtzee_requested && !yahtzee_active && !turboLoaderActive()) {
startYahtzee(game_now_ms);
yahtzee_active = true;
} else if (!yahtzee_requested && yahtzee_active) {
stopYahtzee();
yahtzee_active = false;
}
const bool blackjack_requested = memory_ok && menu_current_screen() == SCREEN_BLACKJACK;
if (blackjack_requested && !blackjack_active && !turboLoaderActive()) {
startBlackjack(game_now_ms);
blackjack_active = true;
} else if (!blackjack_requested && blackjack_active) {
stopBlackjack();
blackjack_active = false;
}
const bool poker_requested = memory_ok && menu_current_screen() == SCREEN_POKER;
if (poker_requested && !poker_active && !turboLoaderActive()) {
startPoker(game_now_ms);
poker_active = true;
} else if (!poker_requested && poker_active) {
stopPoker();
poker_active = false;
}
const bool poker_dice_requested = memory_ok && menu_current_screen() == SCREEN_POKER_DICE;
if (poker_dice_requested && !poker_dice_active && !turboLoaderActive()) {
startPokerDice(game_now_ms);
poker_dice_active = true;
} else if (!poker_dice_requested && poker_dice_active) {
stopPokerDice();
poker_dice_active = false;
}
const bool pig_requested = memory_ok && menu_current_screen() == SCREEN_PIG;
if (pig_requested && !pig_active && !turboLoaderActive()) {
startPig(game_now_ms);
pig_active = true;
} else if (!pig_requested && pig_active) {
stopPig();
pig_active = false;
}
const bool brightness_settings_requested =
memory_ok && menu_current_screen() == SCREEN_BRIGHTNESS;
if (brightness_settings_requested && !brightness_settings_active) {
settingsStart(SETTINGS_BRIGHTNESS);
brightness_settings_active = true;
} else if (!brightness_settings_requested && brightness_settings_active) {
settingsStop();
brightness_settings_active = false;
}
const bool volume_settings_requested = memory_ok && menu_current_screen() == SCREEN_VOLUME;
if (volume_settings_requested && !volume_settings_active) {
settingsStart(SETTINGS_VOLUME);
volume_settings_active = true;
} else if (!volume_settings_requested && volume_settings_active) {
settingsStop();
volume_settings_active = false;
}
const bool wifi_settings_requested = memory_ok && menu_current_screen() == SCREEN_WIFI;
if (wifi_settings_requested && !wifi_settings_active) {
settingsStart(SETTINGS_WIFI);
wifi_settings_active = true;
} else if (!wifi_settings_requested && wifi_settings_active) {
settingsStop();
wifi_settings_active = false;
}
const bool esp_info_requested = memory_ok && menu_current_screen() == SCREEN_ESP_INFO;
if (esp_info_requested && !esp_info_active) {
settingsStart(SETTINGS_ESP_INFO);
esp_info_active = true;
} else if (!esp_info_requested && esp_info_active) {
settingsStop();
esp_info_active = false;
}
#if RAM_MONITOR_ENABLED
const bool ram_log_requested = memory_ok && menu_current_screen() == SCREEN_RAM_LOG;
if (ram_log_requested && !ram_log_active) {
settingsStart(SETTINGS_RAM_LOG);
ram_log_active = true;
} else if (!ram_log_requested && ram_log_active) {
settingsStop();
ram_log_active = false;
}
#endif
const bool restart_settings_requested =
memory_ok && menu_current_screen() == SCREEN_RESTART;
if (restart_settings_requested && !restart_settings_active) {
settingsStart(SETTINGS_RESTART);
restart_settings_active = true;
} else if (!restart_settings_requested && restart_settings_active) {
settingsStop();
restart_settings_active = false;
}
if (restart_settings_active && settingsRestartRequested()) {
ESP_LOGI(TAG, "Settings restart confirmed");
esp_restart();
}
const bool power_off_settings_requested =
memory_ok && menu_current_screen() == SCREEN_POWER_OFF;
if (power_off_settings_requested && !power_off_settings_active) {
settingsStart(SETTINGS_POWER_OFF);
power_off_settings_active = true;
} else if (!power_off_settings_requested && power_off_settings_active) {
settingsStop();
power_off_settings_active = false;
}
if (power_off_settings_active && settingsPowerOffRequested()) {
const bool slept = enter_light_sleep(panel, &tf_available);
settingsCancelPowerOffRequest();
if (slept) {
settingsStop();
power_off_settings_active = false;
menu_home((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
}
}
const bool game_config_requested = memory_ok && menu_current_screen() == SCREEN_GAME_CONFIG;
if (game_config_requested && !game_config_active) {
startGameConfig(now_ms);
game_config_active = true;
} else if (!game_config_requested && game_config_active) {
stopGameConfig();
game_config_active = false;
}
const bool font_test_requested = memory_ok && menu_current_screen() == SCREEN_FONT_TEST;
if (font_test_requested && !font_test_active) {
startFontTest();
font_test_active = true;
} else if (!font_test_requested && font_test_active) {
stopFontTest();
font_test_active = false;
}
const bool matrix_requested = memory_ok && menu_current_screen() == SCREEN_MATRIX;
if (matrix_requested && !matrix_active) {
startMatrix(now_ms);
matrix_active = true;
} else if (!matrix_requested && matrix_active) {
stopMatrix();
matrix_active = false;
}
const bool boing_ball_requested = memory_ok && menu_current_screen() == SCREEN_BOING_BALL;
if (boing_ball_requested && !boing_ball_active) {
startBoingBall(now_ms);
boing_ball_active = true;
} else if (!boing_ball_requested && boing_ball_active) {
stopBoingBall();
boing_ball_active = false;
}
// The main-menu DEMO entry presents the current Demo visual module.
// Keep SCREEN_ABOUT as the route so Back returns to the main menu.
const bool demo_requested = memory_ok && menu_current_screen() == SCREEN_ABOUT;
if (demo_requested && !demo_active) {
startDemo(now_ms);
demo_active = true;
} else if (!demo_requested && demo_active) {
stopDemo();
demo_active = false;
}
const bool simon_requested = memory_ok && menu_current_screen() == SCREEN_SIMON;
if (simon_requested && !simon_active) {
startSimon(now_ms);
simon_active = true;
} else if (!simon_requested && simon_active) {
stopSimon();
simon_active = false;
}
const bool calculator_requested = memory_ok && menu_current_screen() == SCREEN_CALCULATOR;
if (calculator_requested && !calculator_active) {
startCalculator(now_ms);
calculator_active = true;
} else if (!calculator_requested && calculator_active) {
stopCalculator();
calculator_active = false;
}
const bool kaleidoscope_requested =
memory_ok && menu_current_screen() == SCREEN_KALEIDOSCOPE;
if (kaleidoscope_requested && !kaleidoscope_active) {
startKaleidoscope(now_ms);
kaleidoscope_active = true;
} else if (!kaleidoscope_requested && kaleidoscope_active) {
stopKaleidoscope();
kaleidoscope_active = false;
}
const bool warp_tunnel_requested =
memory_ok && menu_current_screen() == SCREEN_WARP_TUNNEL;
if (warp_tunnel_requested && !warp_tunnel_active) {
startWarpTunnel(now_ms);
warp_tunnel_active = true;
} else if (!warp_tunnel_requested && warp_tunnel_active) {
stopWarpTunnel();
warp_tunnel_active = false;
}
const bool mandelbrot_requested = memory_ok && menu_current_screen() == SCREEN_MANDELBROT;
if (mandelbrot_requested && !mandelbrot_active) {
startMandelbrot(now_ms);
mandelbrot_active = true;
} else if (!mandelbrot_requested && mandelbrot_active) {
stopMandelbrot();
mandelbrot_active = false;
}
const bool clock_requested = memory_ok && menu_current_screen() == SCREEN_CLOCK;
if (clock_requested && !clock_active) {
startClock(now_ms);
clock_active = true;
} else if (!clock_requested && clock_active) {
stopClock();
clock_active = false;
}
if (storage_ready) settingsClockAutoSyncUpdate(now_ms);
const bool slideshow_requested = memory_ok && menu_current_screen() == SCREEN_SLIDESHOW;
if (slideshow_requested && !slideshow_active) {
startSlideshow(now_ms);
slideshow_active = true;
} else if (!slideshow_requested && slideshow_active) {
stopSlideshow();
slideshow_active = false;
}
const bool spiffs_test_requested = memory_ok && menu_current_screen() == SCREEN_SPIFFS_TEST;
if (spiffs_test_requested && !spiffs_test_active) {
startSpiffsTest();
spiffs_test_active = true;
} else if (!spiffs_test_requested && spiffs_test_active) {
stopSpiffsTest();
spiffs_test_active = false;
}
const bool sound_test_requested = memory_ok && menu_current_screen() == SCREEN_SOUND_TEST;
if (sound_test_requested && !sound_test_active) {
startSoundTest();
sound_test_active = true;
} else if (!sound_test_requested && sound_test_active) {
stopSoundTest();
sound_test_active = false;
}
const bool sd_card_test_requested = memory_ok && menu_current_screen() == SCREEN_SD_CARD_TEST;
if (sd_card_test_requested && !sd_card_test_active) {
startSdCardTest(now_ms);
sd_card_test_active = true;
} else if (!sd_card_test_requested && sd_card_test_active) {
stopSdCardTest();
sd_card_test_active = false;
}
const bool hall_of_fame_requested =
memory_ok && menu_current_screen() == SCREEN_HALL_OF_FAME;
if (hall_of_fame_requested && !hall_of_fame_active) {
startHallOfFame(now_ms);
hall_of_fame_active = true;
} else if (!hall_of_fame_requested && hall_of_fame_active) {
stopHallOfFame();
hall_of_fame_active = false;
}
const bool asteroids_requested = memory_ok && menu_current_screen() == SCREEN_ASTEROIDS;
if (asteroids_requested && !asteroids_active && !turboLoaderActive()) {
startAsteroids(game_now_ms);
asteroids_active = true;
} else if (!asteroids_requested && asteroids_active) {
stopAsteroids();
asteroids_active = false;
}
const bool battle_zone_requested = memory_ok && menu_current_screen() == SCREEN_BATTLE_ZONE;
if (battle_zone_requested && !battle_zone_active && !turboLoaderActive()) {
startBattleZone(game_now_ms);
battle_zone_active = true;
} else if (!battle_zone_requested && battle_zone_active) {
stopBattleZone();
battle_zone_active = false;
}
const bool boulder_dash_requested = memory_ok && menu_current_screen() == SCREEN_BOULDER_DASH;
if (boulder_dash_requested && !boulder_dash_active && !turboLoaderActive()) {
startBoulderDash(game_now_ms);
boulder_dash_active = true;
} else if (!boulder_dash_requested && boulder_dash_active) {
stopBoulderDash();
boulder_dash_active = false;
}
const bool tempest_requested = memory_ok && menu_current_screen() == SCREEN_TEMPEST;
if (tempest_requested && !tempest_active && !turboLoaderActive()) {
startTempest(game_now_ms);
tempest_active = true;
} else if (!tempest_requested && tempest_active) {
stopTempest();
tempest_active = false;
}
const bool invaders_requested = memory_ok && menu_current_screen() == SCREEN_INVADERS;
if (invaders_requested && !invaders_active && !turboLoaderActive()) {
startInvaders(game_now_ms);
invaders_active = true;
} else if (!invaders_requested && invaders_active) {
stopInvaders();
invaders_active = false;
}
const bool missile_command_requested =
memory_ok && menu_current_screen() == SCREEN_MISSILE_COMMAND;
if (missile_command_requested && !missile_command_active && !turboLoaderActive()) {
startMissileCommand(game_now_ms);
missile_command_active = true;
} else if (!missile_command_requested && missile_command_active) {
stopMissileCommand();
missile_command_active = false;
}
const bool breakout_requested = memory_ok && menu_current_screen() == SCREEN_BREAKOUT;
if (breakout_requested && !breakout_active && !turboLoaderActive()) {
startBreakout(game_now_ms);
breakout_active = true;
} else if (!breakout_requested && breakout_active) {
stopBreakout();
breakout_active = false;
}
const bool crawler_requested = memory_ok && menu_current_screen() == SCREEN_CRAWLER;
if (crawler_requested && !crawler_active && !turboLoaderActive()) {
startCrawler(game_now_ms);
crawler_active = true;
} else if (!crawler_requested && crawler_active) {
stopCrawler();
crawler_active = false;
}
const bool maze_chase_requested = memory_ok && menu_current_screen() == SCREEN_MAZE_CHASE;
if (maze_chase_requested && !maze_chase_active && !turboLoaderActive()) {
startMazeChase(game_now_ms);
maze_chase_active = true;
} else if (!maze_chase_requested && maze_chase_active) {
stopMazeChase();
maze_chase_active = false;
}
const bool scramble_requested = memory_ok && menu_current_screen() == SCREEN_SCRAMBLE;
if (scramble_requested && !scramble_active && !turboLoaderActive()) {
startScramble(game_now_ms);
scramble_active = true;
} else if (!scramble_requested && scramble_active) {
stopScramble();
scramble_active = false;
}
const bool defender_requested = memory_ok && menu_current_screen() == SCREEN_DEFENDER;
if (defender_requested && !defender_active && !turboLoaderActive()) {
startDefender(game_now_ms);
defender_active = true;
} else if (!defender_requested && defender_active) {
stopDefender();
defender_active = false;
}
const bool galaga_requested = memory_ok && menu_current_screen() == SCREEN_GALAGA;
if (galaga_requested && !galaga_active && !turboLoaderActive()) {
startGalaga(game_now_ms);
galaga_active = true;
} else if (!galaga_requested && galaga_active) {
stopGalaga();
galaga_active = false;
}
const bool dungeon_requested = memory_ok && menu_current_screen() == SCREEN_DUNGEON;
if (dungeon_requested && !dungeon_active && !turboLoaderActive()) {
startDungeon(game_now_ms);
dungeon_active = true;
} else if (!dungeon_requested && dungeon_active) {
stopDungeon(game_now_ms);
dungeon_active = false;
}
if ((now - last_controller_refresh) >= pdMS_TO_TICKS(CONTROLLER_REFRESH_MS)) {
last_controller_refresh = now;
const esp_err_t live_result = controller_input_read(&live_joystick);
joystick_valid = live_result == ESP_OK;
if (!joystick_valid && previous_joystick_valid) {
ESP_LOGE(TAG, "Live joystick read failed: %s", esp_err_to_name(live_result));
} else if (joystick_valid && !previous_joystick_valid) {
ESP_LOGI(TAG, "Live joystick readings recovered");
}
const bool validity_changed = joystick_valid != previous_joystick_valid;
previous_joystick_valid = joystick_valid;
joystick_validity_changed = joystick_validity_changed || validity_changed;
batt_update(now_ms);
// An active module owns input; menu idle timeouts must not end a paused module.
if (!app_active) {
if (!menu_update_buttons(left_held, right_held, now_ms)) {
menu_update_joystick(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
}
menu_update(now_ms);
}
}
// Keep input at 20 ms, but render independently at a 60 fps target.
// One task remains the sole owner of the framebuffer and LCD transfer.
menu_redraw_pending = menu_redraw_pending || menu_take_redraw();
if ((now - last_render_refresh) >= pdMS_TO_TICKS(RENDER_REFRESH_MS)) {
last_render_refresh = now;
const bool menu_redraw = menu_redraw_pending;
const bool validity_changed = joystick_validity_changed;
menu_redraw_pending = false;
joystick_validity_changed = false;
if (memory_ok) {
if (turboLoaderActive()) {
drawTurboLoader(frame, now_ms);
draw_frame(panel, frame);
} else if (game_paused) {
if (pause_redraw || now_ms - pause_color_changed_ms >= 500U) {
pause_color_index = pause_redraw ? 0 :
(pause_color_index + 1U) % (sizeof(pause_colors) / sizeof(pause_colors[0]));
// Repaint only the same letter pixels; leave the frozen scene visible.
draw_text_centred(frame, 160, 78, "PAUSED", 2, pause_colors[pause_color_index]);
draw_frame(panel, frame);
pause_color_changed_ms = now_ms;
pause_redraw = false;
}
} else if (checkers_active) {
updateCheckers(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawCheckers(frame);
draw_frame(panel, frame);
} else if (othello_active) {
updateOthello(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawOthello(frame);
draw_frame(panel, frame);
} else if (connect_four_active) {
updateConnectFour(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawConnectFour(frame);
draw_frame(panel, frame);
} else if (battleships_active) {
updateBattleships(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBattleships(frame);
draw_frame(panel, frame);
} else if (yahtzee_active) {
updateYahtzee(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawYahtzee(frame);
draw_frame(panel, frame);
} else if (blackjack_active) {
updateBlackjack(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBlackjack(frame);
draw_frame(panel, frame);
} else if (poker_active) {
updatePoker(frame, live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPoker(frame);
draw_frame(panel, frame);
} else if (poker_dice_active) {
updatePokerDice(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPokerDice(frame);
draw_frame(panel, frame);
} else if (pig_active) {
updatePig(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPig(frame);
draw_frame(panel, frame);
} else if (brightness_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
set_backlight_brightness(settingsBrightness());
settingsDraw(frame);
draw_frame(panel, frame);
} else if (volume_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (wifi_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (esp_info_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (ram_log_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (restart_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (power_off_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (game_config_active) {
updateGameConfig(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawGameConfig(frame);
draw_frame(panel, frame);
} else if (font_test_active) {
drawFontTest(frame);
draw_frame(panel, frame);
} else if (about_active) {
updateAbout(now_ms);
drawAbout(frame);
draw_frame(panel, frame);
} else if (matrix_active) {
updateMatrix(now_ms);
if (matrixTakeFrame()) {
drawMatrix(frame);
draw_frame(panel, frame);
}
} else if (boing_ball_active) {
updateBoingBall(now_ms);
drawBoingBall(frame);
draw_frame(panel, frame);
} else if (demo_active) {
updateDemo(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawDemo(frame);
draw_frame(panel, frame);
} else if (simon_active) {
updateSimon(now_ms);
drawSimon(frame);
draw_frame(panel, frame);
} else if (calculator_active) {
updateCalculator(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawCalculator(frame);
draw_frame(panel, frame);
} else if (kaleidoscope_active) {
updateKaleidoscope(now_ms);
drawKaleidoscope(frame);
draw_frame(panel, frame);
} else if (warp_tunnel_active) {
updateWarpTunnel(now_ms);
drawWarpTunnel(frame);
draw_frame(panel, frame);
} else if (mandelbrot_active) {
updateMandelbrot(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
if (mandelbrotTakeFrame()) {
drawMandelbrot(frame);
draw_frame(panel, frame);
}
} else if (clock_active) {
updateClock(now_ms);
if (clockTakeFrame()) {
drawClock(frame);
draw_frame(panel, frame);
}
} else if (slideshow_active) {
updateSlideshow(frame, now_ms);
drawSlideshow(frame);
draw_frame(panel, frame);
} else if (spiffs_test_active) {
drawSpiffsTest(frame);
draw_frame(panel, frame);
} else if (sound_test_active) {
drawSoundTest(frame);
draw_frame(panel, frame);
} else if (sd_card_test_active) {
updateSdCardTest(frame, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms);
drawSdCardTest(frame);
draw_frame(panel, frame);
} else if (asteroids_active) {
updateAsteroids(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawAsteroids(frame);
draw_frame(panel, frame);
} else if (battle_zone_active) {
updateBattleZone(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawBattleZone(frame);
draw_frame(panel, frame);
} else if (boulder_dash_active) {
updateBoulderDash(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawBoulderDash(frame);
draw_frame(panel, frame);
} else if (tempest_active) {
updateTempest(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawTempest(frame);
draw_frame(panel, frame);
} else if (invaders_active) {
updateInvaders(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawInvaders(frame);
draw_frame(panel, frame);
} else if (missile_command_active) {
updateMissileCommand(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held,
stable_shield_levels[0] == 0, right_held, game_now_ms);
drawMissileCommand(frame);
draw_frame(panel, frame);
} else if (breakout_active) {
updateBreakout(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBreakout(frame);
draw_frame(panel, frame);
} else if (crawler_active) {
updateCrawler(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawCrawler(frame);
draw_frame(panel, frame);
} else if (maze_chase_active) {
updateMazeChase(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawMazeChase(frame);
draw_frame(panel, frame);
} else if (scramble_active) {
updateScramble(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0,
stable_shield_levels[1] == 0, game_now_ms);
drawScramble(frame);
draw_frame(panel, frame);
} else if (defender_active) {
updateDefender(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0,
stable_shield_levels[1] == 0, game_now_ms);
drawDefender(frame);
draw_frame(panel, frame);
} else if (galaga_active) {
updateGalaga(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0, game_now_ms);
drawGalaga(frame);
draw_frame(panel, frame);
} else if (dungeon_active) {
updateDungeon(frame,
live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, game_now_ms);
drawDungeon(frame);
draw_frame(panel, frame);
} else if (hall_of_fame_active) {
updateHallOfFame(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, now_ms);
drawHallOfFame(frame);
draw_frame(panel, frame);
} else if (menu_current_screen() == SCREEN_CONTROLLER_TEST) {
draw_controller_view(frame, &live_joystick, joystick_valid,
stable_shield_levels);
draw_frame(panel, frame);
} else if (menu_current_screen() == SCREEN_GRAPHICS_TEST) {
if (menu_redraw) {
fill_color_bars(frame);
draw_frame(panel, frame);
}
} else if (menu_current_screen() == SCREEN_BATTERY) {
draw_battery_view(frame);
draw_frame(panel, frame);
} else if (menu_redraw || validity_changed) {
draw_menu_view(frame, joystick_valid);
draw_frame(panel, frame);
}
}
}
// With the 1 kHz FreeRTOS tick, yield briefly without quantising a
// 16 ms render cadence onto the old 10 ms scheduler boundaries.
vTaskDelay(pdMS_TO_TICKS(1));
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#define PIN_LCD_BL 38
#define PIN_LCD_D0 39
#define PIN_LCD_D1 40
#define PIN_LCD_D2 41
#define PIN_LCD_D3 42
#define PIN_LCD_D4 45
#define PIN_LCD_D5 46
#define PIN_LCD_D6 47
#define PIN_LCD_D7 48
#define PIN_LCD_RST 5
#define PIN_LCD_CS 6
#define PIN_LCD_DC 7
#define PIN_LCD_WR 8
#define PIN_LCD_RD 9
#define PIN_POWER_ON 15
#define PIN_BUTTON_BOOT 0
#define PIN_BUTTON_USER 14
#define PIN_LIGHT_SLEEP_WAKE 21
#define PIN_AUDIO_SHUTDOWN 10
#define PIN_TF_CS 10
#define PIN_TF_MOSI 11
#define PIN_TF_SCK 12
#define PIN_TF_MISO 13
#define LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000)
#define LCD_BACKLIGHT_PWM_HZ 5000U
#define LCD_BACKLIGHT_DUTY 128U
#define FRAME_BUFFER_BYTES (LCD_WIDTH * LCD_HEIGHT * sizeof(uint16_t))
#define EXPECTED_FLASH_BYTES (16U * 1024U * 1024U)
#define EXPECTED_PSRAM_BYTES (8U * 1024U * 1024U)
#define TF_MOUNT_POINT "/sdcard"
#define TF_MAX_FREQUENCY_KHZ 20000
#define JOYSTICK_CENTRE_X 1933
#define JOYSTICK_CENTRE_Y 1915
#define CONTROLLER_REFRESH_MS 20
#define RENDER_REFRESH_MS 16
#define USER_LONG_PRESS_MS 800U
#define FULL_RESET_HOLD_MS 2000U
static void set_backlight_brightness(uint8_t duty)
{
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
}
static const char *TAG = "board_diagnostic";
#if TF_CARD_ENABLED
static sdmmc_card_t *tf_card = NULL;
#endif
static SemaphoreHandle_t display_transfer_done = NULL;
static bool reset_button_debounce_after_sleep;
static bool display_color_transfer_done(esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t *event,
void *user_context)
{
(void)panel_io;
(void)event;
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR((SemaphoreHandle_t)user_context, &task_woken);
return task_woken == pdTRUE;
}
static esp_lcd_panel_handle_t initialize_display(void)
{
const gpio_config_t output_config = {
.pin_bit_mask = (1ULL << PIN_POWER_ON) | (1ULL << PIN_LCD_BL) |
(1ULL << PIN_LCD_RD),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&output_config));
ESP_ERROR_CHECK(gpio_set_level(PIN_POWER_ON, 1));
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_BL, 0));
ESP_ERROR_CHECK(gpio_set_level(PIN_LCD_RD, 1));
const ledc_timer_config_t backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_8_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = LCD_BACKLIGHT_PWM_HZ,
.clk_cfg = LEDC_AUTO_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
const ledc_channel_config_t backlight_channel = {
.gpio_num = PIN_LCD_BL,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0,
};
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
const esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = PIN_LCD_DC,
.wr_gpio_num = PIN_LCD_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
PIN_LCD_D0,
PIN_LCD_D1,
PIN_LCD_D2,
PIN_LCD_D3,
PIN_LCD_D4,
PIN_LCD_D5,
PIN_LCD_D6,
PIN_LCD_D7,
},
.bus_width = 8,
.max_transfer_bytes = FRAME_BUFFER_BYTES,
};
esp_lcd_i80_bus_handle_t bus = NULL;
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &bus));
const esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = PIN_LCD_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 4,
.on_color_trans_done = display_color_transfer_done,
.user_ctx = display_transfer_done,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.swap_color_bytes = true,
},
};
esp_lcd_panel_io_handle_t panel_io = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(bus, &io_config, &panel_io));
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
esp_lcd_panel_handle_t panel = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 35));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
set_backlight_brightness(LCD_BACKLIGHT_DUTY);
return panel;
}
static void draw_bob_label(uint16_t *frame)
{
static const uint8_t glyphs[2][7] = {
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // B
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // O
};
static const uint8_t bob_glyphs[] = {0, 1, 0};
const int scale = 3;
const int label_x = LCD_WIDTH - 55;
const int label_y = 6;
const int label_width = 51;
const int label_height = 21;
for (int y = label_y - 2; y < label_y + label_height + 2; ++y) {
for (int x = label_x - 2; x < label_x + label_width + 2; ++x) {
frame[(size_t)y * LCD_WIDTH + x] = RGB565(0, 0, 0);
}
}
for (size_t character = 0; character < 3; ++character) {
const uint8_t *glyph = glyphs[bob_glyphs[character]];
const int character_x = label_x + (int)character * (5 * scale + scale);
for (int row = 0; row < 7; ++row) {
for (int column = 0; column < 5; ++column) {
if ((glyph[row] & (1U << (4 - column))) == 0) {
continue;
}
for (int pixel_y = 0; pixel_y < scale; ++pixel_y) {
for (int pixel_x = 0; pixel_x < scale; ++pixel_x) {
const int x = character_x + column * scale + pixel_x;
const int y = label_y + row * scale + pixel_y;
frame[(size_t)y * LCD_WIDTH + x] = RGB565(255, 255, 255);
}
}
}
}
}
}
static void fill_color_bars(uint16_t *frame)
{
static const uint16_t colors[] = {
RGB565(255, 0, 0),
RGB565(0, 255, 0),
RGB565(0, 0, 0),
RGB565(0, 0, 255),
RGB565(255, 255, 255),
};
for (int y = 0; y < LCD_HEIGHT; ++y) {
const size_t color_index = (size_t)y * 5U / LCD_HEIGHT;
for (int x = 0; x < LCD_WIDTH; ++x) {
frame[(size_t)y * LCD_WIDTH + x] = colors[color_index];
}
}
draw_bob_label(frame);
}
static void draw_frame(esp_lcd_panel_handle_t panel, const uint16_t *frame)
{
// The global overlay is composed last without changing the caller's scene framebuffer.
// updateDoomCountdownOverlay();
const uint16_t *const presentation_frame = globalOverlayCompose(frame);
// Send the fully composed image, including any global overlay, before LCD DMA uses it.
screenshotCapturePresentation(presentation_frame);
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(
panel, 0, 0, LCD_WIDTH, LCD_HEIGHT, presentation_frame));
// The framebuffer must stay unchanged until the LCD DMA transfer finishes.
if (xSemaphoreTake(display_transfer_done, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGE(TAG, "LCD transfer timed out");
ESP_ERROR_CHECK(ESP_ERR_TIMEOUT);
}
}
static int joystick_view_offset(int raw, int centre, int travel)
{
if (raw < 0) {
raw = 0;
} else if (raw > 4095) {
raw = 4095;
}
const int span = raw >= centre ? 4095 - centre : centre;
return (raw - centre) * travel / span;
}
static void draw_controller_view(uint16_t *frame, const joystick_state_t *joystick,
bool joystick_valid, const int *button_levels)
{
const uint16_t background = RGB565(10, 16, 26);
const uint16_t border = RGB565(76, 94, 115);
const uint16_t white = RGB565(235, 241, 247);
const uint16_t green = RGB565(70, 245, 130);
const uint16_t cyan = RGB565(50, 205, 255);
const uint16_t red = RGB565(255, 80, 65);
const uint16_t amber = RGB565(255, 185, 60);
static const int button_x[6] = {80, 108, 80, 52, 20, 20};
static const int button_y[6] = {98, 124, 150, 124, 108, 80};
static const char *button_names[7] = {"A", "B", "C", "D", "E", "F", "K"};
clear(frame, background);
fill_rect(frame, 5, 5, 130, 160, RGB565(63, 26, 35));
fill_rect(frame, 142, 8, 1, 154, border);
// Stick at top, F/E left; A north, B east, C south, D west.
const uint16_t stick_color = button_levels[6] == 0 ? green :
button_levels[6] < 0 ? amber : border;
fill_circle(frame, 80, 44, 35, stick_color);
fill_circle(frame, 80, 44, 31, background);
fill_rect(frame, 53, 44, 55, 1, border);
fill_rect(frame, 80, 17, 1, 55, border);
drawPet2001TextTransparent(frame, 77, 19, "K", stick_color, 1);
if (joystick_valid) {
const int dot_x = 80 + joystick_view_offset(joystick->x_raw, JOYSTICK_CENTRE_X, 19);
const int dot_y = 44 - joystick_view_offset(joystick->y_raw, JOYSTICK_CENTRE_Y, 19);
fill_circle(frame, dot_x, dot_y, 5, background);
fill_circle(frame, dot_x, dot_y, 4, cyan);
} else {
drawPet2001TextTransparent(frame, 72, 38, "ERR", red, 1);
}
for (size_t button = 0; button < 6; ++button) {
const uint16_t color = button_levels[button] == 0 ? green :
button_levels[button] < 0 ? amber : border;
fill_circle(frame, button_x[button], button_y[button], 12, color);
if (button_levels[button] != 0) {
fill_circle(frame, button_x[button], button_y[button], 10, background);
}
drawPet2001TextCentredTransparent(frame, button_x[button], button_y[button] - 8,
button_names[button],
button_levels[button] == 0 ? background : white, 2);
}
drawPet2001TextTransparent(frame, 154, 10, "LIVE INPUT", white, 2);
drawPet2001TextTransparent(frame, 154, 33,
joystick_valid ? "JOYSTICK OK" : "ADC READ ERROR",
joystick_valid ? cyan : red, 1);
if (joystick_valid) {
char reading[16];
snprintf(reading, sizeof(reading), "X %4d", joystick->x_raw);
drawPet2001TextTransparent(frame, 154, 51, reading, white, 2);
snprintf(reading, sizeof(reading), "Y %4d", joystick->y_raw);
drawPet2001TextTransparent(frame, 154, 73, reading, white, 2);
}
for (size_t button = 0; button < 7; ++button) {
const int x = 154 + (int)button * 23;
const uint16_t color = button_levels[button] == 0 ? green :
button_levels[button] < 0 ? amber : border;
fill_rect(frame, x, 103, 20, 23, color);
drawPet2001TextCentredTransparent(frame, x + 10, 107, button_names[button],
button_levels[button] == 0 ? background : white, 2);
}
drawPet2001TextTransparent(frame, 154, 139, "GREEN MEANS PRESSED", green, 1);
drawPet2001TextTransparent(frame, 154, 154, "BOOT BACK USER SHOT", white, 1);
}
static void draw_battery_view(uint16_t *frame)
{
const uint16_t background = RGB565(10, 16, 26);
const uint16_t white = RGB565(235, 241, 247);
const uint16_t cyan = RGB565(50, 205, 255);
const uint16_t green = RGB565(70, 245, 130);
const uint16_t red = RGB565(255, 80, 65);
char text[32];
clear(frame, background);
drawPet2001TextCentredTransparent(frame, 160, 16, "BATTERY", cyan, 3);
if (batt_has_reading()) {
snprintf(text, sizeof(text), "%.1f V", (double)batt_voltage());
drawPet2001TextCentredTransparent(frame, 160, 62, text, white, 4);
drawPet2001TextCentredTransparent(frame, 160, 112,
batt_external_power_connected() ? "EXTERNAL POWER" : "BATTERY POWER",
batt_external_power_connected() ? green : cyan, 2);
drawPet2001TextCentredTransparent(frame, 160, 140, "UPDATES EVERY 15 SEC", white, 1);
} else {
drawPet2001TextCentredTransparent(frame, 160, 80, "BATTERY ADC ERROR", red, 2);
}
drawPet2001TextCentredTransparent(frame, 160, 157, "BOOT BACK", white, 1);
}
static void draw_menu_view(uint16_t *frame, bool joystick_valid)
{
const uint16_t black = RGB565(0, 0, 0);
const uint16_t white = RGB565(235, 241, 247);
const uint16_t green = RGB565(70, 245, 130);
const uint16_t magenta = RGB565(235, 80, 225);
const uint16_t cyan = RGB565(50, 205, 255);
const uint16_t grey = RGB565(100, 115, 135);
const uint16_t red = RGB565(255, 80, 65);
const bool two_column_menu = menu_current_screen() == SCREEN_MENU ||
menu_current_screen() == SCREEN_GAMES ||
menu_current_screen() == SCREEN_SETTINGS ||
menu_current_screen() == SCREEN_MISC;
const size_t per_page = two_column_menu ? 8 : 9;
const size_t count = menu_count();
const size_t selected = menu_selected_item();
const size_t page = selected / per_page;
const size_t first = page * per_page;
const bool c64_menu = two_column_menu;
const size_t page_items = count - first < per_page ? count - first : per_page;
const size_t rows_per_column = two_column_menu && page_items != 0U ?
(page_items + 1U) / 2U : 1U;
const size_t selected_row = two_column_menu ? (selected - first) % rows_per_column : 0U;
const size_t selected_column = two_column_menu ?
(selected - first) / rows_per_column : 0U;
const bool selected_is_text = strcmp(menu_item_text(selected), "BACK") != 0;
const int selected_x = selected_column == 0U ? 10 : 166;
const int selected_y = 46 + (int)selected_row * 25;
const int selected_width = (int)strlen(menu_item_text(selected)) *
PET2001_FONT_GLYPH_WIDTH * 2;
char page_text[32];
clear(frame, c64_menu ? C64_COLOR_BLUE : black);
if (c64_menu)
drawPet2001Text(frame, 10, 10, menu_title(), false,
C64_COLOR_LIGHT_GREEN, C64_COLOR_BLUE, 2);
else drawPet2001TextTransparent(frame, 10, 10, menu_title(), cyan, 2);
drawGameGold(frame, 174, 3, 80, true);
snprintf(page_text, sizeof(page_text), "%u OF %u", (unsigned)(page + 1),
(unsigned)((count + per_page - 1) / per_page));
drawPet2001TextTransparent(frame, 260, 12, page_text,
c64_menu ? C64_COLOR_LIGHT_GREEN : grey, 1);
if (!joystick_valid) {
drawPet2001TextTransparent(frame, 10, 30, "ADC READ ERROR", red, 1);
} else if (!aiTimingStorageHealthy()) {
drawPet2001TextTransparent(frame, 10, 30, "AI TIMING STORAGE ERROR", red, 1);
} else if (menu_current_screen() == SCREEN_GAMES && count == 1) {
drawPet2001TextTransparent(frame, 10, 30, "NO GAMES YET", grey, 1);
}
for (size_t index = first; index < count && index < first + per_page; ++index) {
const size_t position = index - first;
const bool highlighted = index == selected;
const char *label = menu_item_text(index);
const bool back_item = strcmp(label, "BACK") == 0;
if (two_column_menu) {
const size_t column = position / rows_per_column;
const size_t row = position % rows_per_column;
const int x = column == 0U ? 10 : 166;
const int y = 43 + (int)row * 25;
const int normal_y = y + 7;
const int normal_width = (int)strlen(label) * PET2001_FONT_GLYPH_WIDTH;
const bool overlaps_selected = c64_menu && !highlighted && selected_is_text &&
x < selected_x + selected_width &&
selected_x < x + normal_width &&
normal_y < selected_y + 16 &&
selected_y < normal_y + 8;
if (overlaps_selected) {
continue;
}
if (back_item) {
const int scale = highlighted ? 2 : 1;
const int arrow_y = y + (22 - 8 * scale) / 2;
if (c64_menu) {
if (highlighted)
fill_rect(frame, x, arrow_y, 8 * scale, 8 * scale,
C64_COLOR_LIGHT_GREEN);
draw_bitmap_scaled(frame, x, arrow_y, back_arrow_bitmap, 8, 8,
8 * scale, 8 * scale,
highlighted ? C64_COLOR_BLUE : C64_COLOR_LIGHT_GREEN);
} else {
draw_bitmap_scaled(frame, x, arrow_y, back_arrow_bitmap, 8, 8,
8 * scale, 8 * scale, highlighted ? green : white);
}
} else {
if (c64_menu) {
const int scale = highlighted ? 2 : 1;
drawPet2001Text(frame, x, y + (highlighted ? 3 : 7), label, highlighted,
C64_COLOR_LIGHT_GREEN, C64_COLOR_BLUE, scale);
} else {
char clipped[16];
const int maximum = highlighted ? 9 : 15;
int length = 0;
while (label[length] != '\0' && length < maximum) {
clipped[length] = label[length];
++length;
}
clipped[length] = '\0';
drawPet2001TextTransparent(frame, x, y + (highlighted ? 3 : 7), clipped,
highlighted ? green : white,
highlighted ? 2 : 1);
}
}
} else {
const int x = 4 + (int)(position % 3) * 106;
const int y = 44 + (int)(position / 3) * 35;
const uint16_t accent = menu_item_is_submenu(index) ? magenta : green;
if (back_item) {
const int scale = highlighted ? 2 : 1;
const int size = 8 * scale;
draw_bitmap_scaled(frame, x + (100 - size) / 2, y + (30 - size) / 2,
back_arrow_bitmap, 8, 8, size, size, accent);
continue;
}
char clipped[17];
const int maximum = highlighted ? 6 : 12;
int length = 0;
while (label[length] != '\0' && length < maximum) {
clipped[length] = label[length];
++length;
}
clipped[length] = '\0';
const int scale = highlighted ? 2 : 1;
// PET cells are 8 pixels wide, so preserve each 100-pixel column's margins.
const int text_width = length * PET2001_FONT_GLYPH_WIDTH * scale;
drawPet2001TextTransparent(frame, x + (100 - text_width) / 2,
y + (30 - PET2001_FONT_GLYPH_HEIGHT * scale) / 2,
clipped, accent, scale);
}
}
drawPet2001TextTransparent(frame, 8, 156,
two_column_menu ? "D/B UP/DOWN JS A/K SELECT" :
"D/B/JS A/K SELECT",
c64_menu ? C64_COLOR_LIGHT_GREEN : grey, 1);
drawPet2001TextTransparent(frame, 250, 156, "BOOT BACK",
c64_menu ? C64_COLOR_LIGHT_GREEN : grey, 1);
}
static void initialize_buttons(void)
{
// A software reset after light sleep can retain a pad's prior sleep state.
// Clear it before restoring the board buttons as active-low pull-up inputs.
(void)gpio_hold_dis(PIN_BUTTON_BOOT);
(void)gpio_hold_dis(PIN_BUTTON_USER);
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_BOOT));
ESP_ERROR_CHECK(gpio_reset_pin(PIN_BUTTON_USER));
const gpio_config_t button_config = {
.pin_bit_mask = (1ULL << PIN_BUTTON_BOOT) | (1ULL << PIN_BUTTON_USER),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&button_config));
}
static void initialize_audio_shutdown(void)
{
(void)gpio_hold_dis(PIN_AUDIO_SHUTDOWN);
ESP_ERROR_CHECK(gpio_reset_pin(PIN_AUDIO_SHUTDOWN));
const gpio_config_t shutdown_config = {
.pin_bit_mask = 1ULL << PIN_AUDIO_SHUTDOWN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&shutdown_config));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1));
ESP_LOGI(TAG, "Amplifier shutdown control ready on GPIO%d", PIN_AUDIO_SHUTDOWN);
}
static void restore_light_sleep_button_inputs(void)
{
(void)gpio_hold_dis(PIN_BUTTON_BOOT);
(void)gpio_hold_dis(PIN_BUTTON_USER);
(void)gpio_hold_dis(PIN_LIGHT_SLEEP_WAKE);
initialize_buttons();
const gpio_config_t wake_button_config = {
.pin_bit_mask = 1ULL << PIN_LIGHT_SLEEP_WAKE,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&wake_button_config));
reset_button_debounce_after_sleep = true;
}
#if TF_CARD_ENABLED
static bool initialize_tf_card(void)
{
ESP_LOGI(TAG, "Initializing TF card on CS=%d, MOSI=%d, SCK=%d, MISO=%d",
PIN_TF_CS, PIN_TF_MOSI, PIN_TF_SCK, PIN_TF_MISO);
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = SPI2_HOST;
host.max_freq_khz = TF_MAX_FREQUENCY_KHZ;
const spi_bus_config_t bus_config = {
.mosi_io_num = PIN_TF_MOSI,
.miso_io_num = PIN_TF_MISO,
.sclk_io_num = PIN_TF_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096,
};
esp_err_t result = spi_bus_initialize(host.slot, &bus_config, SPI_DMA_CH_AUTO);
if (result != ESP_OK) {
ESP_LOGE(TAG, "TF SPI bus initialization failed: %s", esp_err_to_name(result));
return false;
}
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_TF_CS;
slot_config.host_id = host.slot;
const esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024,
};
result = esp_vfs_fat_sdspi_mount(
TF_MOUNT_POINT, &host, &slot_config, &mount_config, &tf_card);
if (result != ESP_OK) {
ESP_LOGE(TAG, "TF card mount failed: %s", esp_err_to_name(result));
spi_bus_free(host.slot);
return false;
}
ESP_LOGI(TAG, "TF card mounted read/write at %s", TF_MOUNT_POINT);
sdmmc_card_print_info(stdout, tf_card);
struct statvfs filesystem = {0};
if (statvfs(TF_MOUNT_POINT, &filesystem) == 0) {
const uint64_t total_bytes =
(uint64_t)filesystem.f_blocks * filesystem.f_frsize;
const uint64_t free_bytes =
(uint64_t)filesystem.f_bfree * filesystem.f_frsize;
ESP_LOGI(TAG, "TF filesystem total/free: %" PRIu64 " / %" PRIu64 " bytes",
total_bytes, free_bytes);
} else {
ESP_LOGW(TAG, "TF card mounted, but filesystem space could not be read");
}
return true;
}
#endif
static bool enter_light_sleep(esp_lcd_panel_handle_t panel, bool *tf_available)
{
#if !TF_CARD_ENABLED
(void)tf_available;
#endif
const int wake_button_level = gpio_get_level(PIN_LIGHT_SLEEP_WAKE);
if (wake_button_level == 0) {
ESP_LOGW(TAG, "Light sleep cancelled; release shield B (GPIO%d)",
PIN_LIGHT_SLEEP_WAKE);
return false;
}
const uint64_t wake_pins = 1ULL << PIN_LIGHT_SLEEP_WAKE;
const esp_err_t wake_result = esp_sleep_enable_ext1_wakeup(
wake_pins, ESP_EXT1_WAKEUP_ANY_LOW);
if (wake_result != ESP_OK) {
ESP_LOGE(TAG, "Light-sleep wake configuration failed: %s",
esp_err_to_name(wake_result));
return false;
}
timeSyncStop();
(void)wifiServiceStop();
#if TF_CARD_ENABLED
if (*tf_available && tf_card != NULL) {
const esp_err_t unmount_result = esp_vfs_fat_sdcard_unmount(TF_MOUNT_POINT, tf_card);
if (unmount_result == ESP_OK) {
tf_card = NULL;
*tf_available = false;
const esp_err_t bus_result = spi_bus_free(SPI2_HOST);
if (bus_result != ESP_OK) {
ESP_LOGW(TAG, "TF SPI release failed: %s", esp_err_to_name(bus_result));
}
} else {
ESP_LOGW(TAG, "TF card unmount failed: %s", esp_err_to_name(unmount_result));
}
}
#endif
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, false));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 0));
ESP_ERROR_CHECK(gpio_hold_en(PIN_AUDIO_SHUTDOWN));
ESP_LOGI(TAG, "Entering light sleep; press shield B to wake");
const esp_err_t sleep_result = esp_light_sleep_start();
if (sleep_result != ESP_OK) {
ESP_LOGE(TAG, "Light sleep failed: %s", esp_err_to_name(sleep_result));
}
ESP_ERROR_CHECK(esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1));
ESP_ERROR_CHECK(gpio_hold_dis(PIN_AUDIO_SHUTDOWN));
ESP_ERROR_CHECK(gpio_set_level(PIN_AUDIO_SHUTDOWN, 1));
restore_light_sleep_button_inputs();
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
set_backlight_brightness(settingsBrightness());
#if TF_CARD_ENABLED
if (!*tf_available) {
*tf_available = initialize_tf_card();
ESP_LOGI(TAG, "TF CARD AFTER WAKE: %s",
*tf_available ? "READY" : "NOT AVAILABLE");
}
#endif
ESP_LOGI(TAG, "Resumed from light sleep");
return sleep_result == ESP_OK;
}
void app_main(void)
{
esp_chip_info_t chip_info = {0};
uint32_t flash_size = 0;
esp_chip_info(&chip_info);
const esp_err_t flash_result = esp_flash_get_size(NULL, &flash_size);
const bool psram_initialized = esp_psram_is_initialized();
const size_t psram_chip_size = esp_psram_get_size();
const size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
const size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic");
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision,
chip_info.cores);
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
if (flash_result == ESP_OK) {
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
} else {
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
}
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no");
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size);
ESP_LOGI(TAG, "PSRAM heap total/free: %u / %u bytes", (unsigned)psram_total,
(unsigned)psram_free);
const bool memory_ok = flash_result == ESP_OK &&
flash_size == EXPECTED_FLASH_BYTES &&
psram_initialized &&
psram_chip_size >= EXPECTED_PSRAM_BYTES;
if (!memory_ok) {
ESP_LOGE(TAG, "Memory check FAILED: expected 16 MB flash and 8 MB PSRAM");
}
uint16_t *frame = heap_caps_malloc(
FRAME_BUFFER_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
if (frame == NULL) {
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM display buffer",
(unsigned)FRAME_BUFFER_BYTES);
return;
}
ESP_LOGI(TAG, "PSRAM display buffer allocated: %u bytes",
(unsigned)FRAME_BUFFER_BYTES);
if (!globalOverlayInitialize()) {
ESP_LOGE(TAG, "Unable to allocate the %u-byte PSRAM overlay presentation buffer",
(unsigned)FRAME_BUFFER_BYTES);
heap_caps_free(frame);
return;
}
display_transfer_done = xSemaphoreCreateBinary();
if (display_transfer_done == NULL) {
ESP_LOGE(TAG, "Unable to allocate LCD transfer semaphore");
globalOverlayDeinitialize();
heap_caps_free(frame);
return;
}
initialize_buttons();
initialize_audio_shutdown();
esp_lcd_panel_handle_t panel = initialize_display();
if (memory_ok) {
clear(frame, RGB565(0, 0, 0));
} else {
clear(frame, RGB565(255, 0, 255));
ESP_LOGE(TAG, "Magenta screen indicates a memory diagnostic failure");
}
draw_frame(panel, frame);
ESP_LOGI(TAG, "LilyGO T-Display-S3 V1.1 diagnostic report");
ESP_LOGI(TAG, "Chip: ESP32-S3, revision %d, %d CPU cores", chip_info.revision,
chip_info.cores);
ESP_LOGI(TAG, "Configured CPU frequency: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
if (flash_result == ESP_OK) {
ESP_LOGI(TAG, "Detected flash: %" PRIu32 " MB", flash_size / (1024U * 1024U));
} else {
ESP_LOGE(TAG, "Unable to read flash size: %s", esp_err_to_name(flash_result));
}
ESP_LOGI(TAG, "PSRAM initialized: %s", psram_initialized ? "yes" : "no");
ESP_LOGI(TAG, "PSRAM chip size: %u bytes", (unsigned)psram_chip_size);
ESP_LOGI(TAG, "PSRAM heap total/free at startup: %u / %u bytes",
(unsigned)psram_total, (unsigned)psram_free);
ESP_LOGI(TAG, "PSRAM display buffer: %u bytes", (unsigned)FRAME_BUFFER_BYTES);
if (memory_ok) {
ESP_LOGI(TAG, "BOARD DIAGNOSTIC: PASS");
} else {
ESP_LOGE(TAG, "BOARD DIAGNOSTIC: FAIL");
}
bool tf_available = false;
#if TF_CARD_ENABLED
tf_available = initialize_tf_card();
ESP_LOGI(TAG, "TF CARD: %s", tf_available ? "READY" : "NOT AVAILABLE");
#else
ESP_LOGI(TAG, "TF CARD: DISABLED");
#endif
const bool dungeon_files_ready = dungeonFilesInitialize(tf_available);
ESP_LOGI(TAG, "DUNGEON DATA: %s", dungeon_files_ready ? "READY" : "NOT AVAILABLE");
#if RAM_MONITOR_ENABLED
if (dungeon_files_ready) ramMonitorInitialize();
else ESP_LOGW(TAG, "RAM MONITOR: DISABLED (SPIFFS NOT AVAILABLE)");
#else
ESP_LOGI(TAG, "RAM MONITOR: DISABLED (FEATURE SWITCH)");
#endif
const bool storage_ready = game_storage_initialize();
const bool gold_ready = storage_ready && initGold();
const bool timing_ready = storage_ready && initAITiming();
const bool settings_ready = storage_ready && settingsInitialize();
const bool clock_time_ready = storage_ready && timeSyncInitialize();
const bool audio_ready = audioInitialize();
set_backlight_brightness(settingsBrightness());
ESP_LOGI(TAG, "GAME STORAGE: %s; GOLD: %s; AI TIMING: %s; SETTINGS: %s; CLOCK: %s; AUDIO: %s",
storage_ready ? "READY" : "FAILED",
gold_ready ? "READY" : "FAILED",
timing_ready ? "READY" : "DEFAULTS ONLY",
settings_ready ? "READY" : "DEFAULTS ONLY", clock_time_ready ? "READY" : "DEFAULTS ONLY",
audio_ready ? "READY" : "FAILED");
if (storage_ready) {
nvs_stats_t stats = {0};
const esp_err_t stats_result = nvs_get_stats(NULL, &stats);
if (stats_result == ESP_OK) {
ESP_LOGI(TAG, "NVS entries: used %u; available %u; total %u",
(unsigned)stats.used_entries, (unsigned)stats.available_entries,
(unsigned)stats.total_entries);
} else {
ESP_LOGW(TAG, "Cannot read NVS capacity: %s", esp_err_to_name(stats_result));
}
}
ESP_LOGI(TAG, "Initializing raw joystick test");
ESP_LOGI(TAG, "Joystick X: GPIO1 / ADC1 channel 0");
ESP_LOGI(TAG, "Joystick Y: GPIO2 / ADC1 channel 1");
const esp_err_t joystick_result = controller_input_initialize();
if (joystick_result != ESP_OK) {
ESP_LOGE(TAG, "Joystick initialization failed: %s",
esp_err_to_name(joystick_result));
return;
}
const bool battery_ready = batt_initialize();
ESP_LOGI(TAG, "BATTERY ADC: %s", battery_ready ? "READY" : "FAILED");
ESP_LOGI(TAG, "RAW JOYSTICK TEST: READY");
ESP_LOGI(TAG, "USER short press captures a screenshot; hold USER in Demo opens its selector");
const gpio_num_t shield_button_pins[] = {
GPIO_NUM_16, GPIO_NUM_21, GPIO_NUM_17, GPIO_NUM_18,
GPIO_NUM_44, GPIO_NUM_43, GPIO_NUM_3,
};
const char *shield_button_names[] = {
"A", "B", "C", "D", "E", "F", "K",
};
const size_t shield_button_count =
sizeof(shield_button_pins) / sizeof(shield_button_pins[0]);
int previous_shield_levels[7];
int stable_shield_levels[7];
TickType_t shield_changed_at[7];
uint64_t shield_button_mask = 0;
for (size_t button = 0; button < shield_button_count; ++button) {
shield_button_mask |= 1ULL << shield_button_pins[button];
}
const gpio_config_t shield_button_config = {
.pin_bit_mask = shield_button_mask,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&shield_button_config));
for (size_t button = 0; button < shield_button_count; ++button) {
previous_shield_levels[button] =
gpio_get_level(shield_button_pins[button]);
stable_shield_levels[button] = -1;
shield_changed_at[button] = xTaskGetTickCount();
}
ESP_LOGI(TAG, "SHIELD BUTTON TEST: READY");
int previous_user_level = gpio_get_level(PIN_BUTTON_USER);
int stable_user_level = previous_user_level;
TickType_t user_changed_at = xTaskGetTickCount();
TickType_t user_pressed_at = user_changed_at;
bool user_long_sent = false;
bool user_capture_cancelled = false;
TickType_t last_controller_refresh =
xTaskGetTickCount() - pdMS_TO_TICKS(CONTROLLER_REFRESH_MS);
TickType_t last_render_refresh =
xTaskGetTickCount() - pdMS_TO_TICKS(RENDER_REFRESH_MS);
bool previous_joystick_valid = true;
joystick_state_t live_joystick = {0};
bool joystick_valid = true;
bool joystick_validity_changed = true;
bool menu_redraw_pending = true;
int previous_boot_level = gpio_get_level(PIN_BUTTON_BOOT);
int stable_boot_level = previous_boot_level;
TickType_t boot_changed_at = xTaskGetTickCount();
TickType_t boot_pressed_at = boot_changed_at;
bool boot_long_sent = false;
TickType_t full_reset_pressed_at = 0;
bool full_reset_armed = stable_boot_level != 0 || stable_user_level != 0;
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT,
stable_boot_level == 0 ? "PRESSED" : "RELEASED");
bool checkers_active = false;
bool othello_active = false;
bool connect_four_active = false;
bool battleships_active = false;
bool yahtzee_active = false;
bool blackjack_active = false;
bool poker_active = false;
bool poker_dice_active = false;
bool pig_active = false;
bool brightness_settings_active = false;
bool volume_settings_active = false;
bool wifi_settings_active = false;
bool esp_info_active = false;
bool ram_log_active = false;
bool power_off_settings_active = false;
bool restart_settings_active = false;
bool game_config_active = false;
bool font_test_active = false;
bool about_active = false;
bool matrix_active = false;
bool boing_ball_active = false;
bool demo_active = false;
bool simon_active = false;
bool calculator_active = false;
bool kaleidoscope_active = false;
bool warp_tunnel_active = false;
bool mandelbrot_active = false;
bool clock_active = false;
bool slideshow_active = false;
bool spiffs_test_active = false;
bool sound_test_active = false;
bool sd_card_test_active = false;
bool asteroids_active = false;
bool battle_zone_active = false;
bool boulder_dash_active = false;
bool tempest_active = false;
bool invaders_active = false;
bool missile_command_active = false;
bool breakout_active = false;
bool crawler_active = false;
bool maze_chase_active = false;
bool scramble_active = false;
bool defender_active = false;
bool galaga_active = false;
bool dungeon_active = false;
bool hall_of_fame_active = false;
bool game_paused = false;
bool pause_redraw = false;
static const uint16_t pause_colors[] = {
COLOR_RED, COLOR_YELLOW, COLOR_CYAN, COLOR_WHITE, COLOR_BLACK, COLOR_MAGENTA
};
size_t pause_color_index = 0;
uint32_t pause_color_changed_ms = 0;
uint32_t game_clock_last_ms = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
uint32_t game_now_ms = game_clock_last_ms;
menu_initialize((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
ESP_LOGI(TAG, "MENU: %s",
memory_ok ? "READY" : "DISABLED BY MEMORY CHECK");
while (true) {
if (reset_button_debounce_after_sleep) {
previous_boot_level = stable_boot_level = gpio_get_level(PIN_BUTTON_BOOT);
previous_user_level = stable_user_level = gpio_get_level(PIN_BUTTON_USER);
boot_changed_at = boot_pressed_at = user_changed_at = user_pressed_at =
xTaskGetTickCount();
boot_long_sent = false;
user_long_sent = false;
user_capture_cancelled = stable_boot_level == 0 && stable_user_level == 0;
full_reset_armed = stable_boot_level != 0 || stable_user_level != 0;
full_reset_pressed_at = 0;
for (size_t button = 0; button < shield_button_count; ++button) {
previous_shield_levels[button] = stable_shield_levels[button] =
gpio_get_level(shield_button_pins[button]);
shield_changed_at[button] = xTaskGetTickCount();
}
reset_button_debounce_after_sleep = false;
}
const int user_level = gpio_get_level(PIN_BUTTON_USER);
const TickType_t now = xTaskGetTickCount();
const uint32_t now_ms = (uint32_t)(now * portTICK_PERIOD_MS);
#if RAM_MONITOR_ENABLED
if (dungeon_files_ready) ramMonitorUpdate(now_ms);
#endif
// Games share a clock that excludes paused time. Hardware and menus use real time.
if (!game_paused) game_now_ms += now_ms - game_clock_last_ms;
game_clock_last_ms = now_ms;
bool k_pressed = false;
bool a_pressed = false;
bool b_pressed = false;
bool c_pressed = false;
bool d_pressed = false;
bool e_pressed = false;
bool f_pressed = false;
bool user_short_pressed = false;
bool user_long_pressed = false;
bool boot_short = false;
bool boot_long = false;
const int boot_level = gpio_get_level(PIN_BUTTON_BOOT);
if (boot_level != previous_boot_level) {
previous_boot_level = boot_level;
boot_changed_at = now;
}
if (boot_level != stable_boot_level &&
(now - boot_changed_at) >= pdMS_TO_TICKS(30)) {
stable_boot_level = boot_level;
ESP_LOGI(TAG, "Board BOOT (GPIO%d): %s", PIN_BUTTON_BOOT,
stable_boot_level == 0 ? "PRESSED" : "RELEASED");
if (stable_boot_level == 0) {
boot_pressed_at = now;
boot_long_sent = false;
} else {
boot_short = !boot_long_sent;
}
}
if (stable_boot_level == 0 && !boot_long_sent &&
(now - boot_pressed_at) >= pdMS_TO_TICKS(800)) {
boot_long_sent = true;
boot_long = true;
}
if (user_level != previous_user_level) {
previous_user_level = user_level;
user_changed_at = now;
}
if (user_level != stable_user_level &&
(now - user_changed_at) >= pdMS_TO_TICKS(30)) {
stable_user_level = user_level;
if (stable_user_level == 0) {
user_pressed_at = now;
user_long_sent = false;
user_capture_cancelled = stable_boot_level == 0;
} else if (!user_long_sent && !user_capture_cancelled) {
user_short_pressed = true;
}
}
// The BOOT + USER restart chord always takes precedence over capture or Demo control.
if (stable_boot_level == 0 && stable_user_level == 0) {
user_capture_cancelled = true;
}
if (stable_user_level == 0 && stable_boot_level != 0 && !user_capture_cancelled &&
!user_long_sent &&
(now - user_pressed_at) >= pdMS_TO_TICKS(USER_LONG_PRESS_MS)) {
user_long_sent = true;
user_long_pressed = true;
}
for (size_t button = 0; button < shield_button_count; ++button) {
const int level = gpio_get_level(shield_button_pins[button]);
if (level != previous_shield_levels[button]) {
previous_shield_levels[button] = level;
shield_changed_at[button] = now;
}
if (level != stable_shield_levels[button] &&
(now - shield_changed_at[button]) >= pdMS_TO_TICKS(30)) {
if (button == 6 && stable_shield_levels[button] == 1 && level == 0) {
k_pressed = true;
}
if (stable_shield_levels[button] == 1 && level == 0) {
if (button == 0) a_pressed = true;
if (button == 1) b_pressed = true;
if (button == 2) c_pressed = true;
if (button == 3) d_pressed = true;
if (button == 4) e_pressed = true;
if (button == 5) f_pressed = true;
}
// C is game-specific; only the board's BOOT button navigates back/home.
stable_shield_levels[button] = level;
ESP_LOGI(TAG, "Shield button %s (GPIO%d): %s",
shield_button_names[button],
(int)shield_button_pins[button],
level == 0 ? "PRESSED" : "RELEASED");
}
}
// Both board buttons held together perform a normal ESP reboot.
// Require a release first so buttons held during startup cannot loop resets.
if (stable_boot_level != 0 || stable_user_level != 0) {
full_reset_armed = true;
full_reset_pressed_at = 0;
} else if (full_reset_armed && full_reset_pressed_at == 0) {
full_reset_pressed_at = now;
} else if (full_reset_armed &&
(now - full_reset_pressed_at) >= pdMS_TO_TICKS(FULL_RESET_HOLD_MS)) {
ESP_LOGW(TAG, "BOOT and USER held: restarting ESP");
esp_restart();
}
if (user_short_pressed) {
const char *const screenshot_source_label = demo_active ? demoScreenshotLabel() :
hall_of_fame_active ? hallOfFameScreenshotLabel() : menuScreenshotLabel();
screenshotRequest(screenshot_source_label);
// Static menu screens need one scheduled render before their queued capture can send.
menu_redraw_pending = true;
}
const bool left_held = stable_shield_levels[3] == 0; // D
const bool right_held = stable_shield_levels[1] == 0; // B
const bool app_active = checkers_active || othello_active || connect_four_active || battleships_active ||
yahtzee_active || blackjack_active || poker_active || poker_dice_active || pig_active ||
brightness_settings_active || volume_settings_active || wifi_settings_active || esp_info_active || ram_log_active ||
power_off_settings_active || restart_settings_active || game_config_active || font_test_active ||
about_active || matrix_active || boing_ball_active || demo_active || simon_active || calculator_active || kaleidoscope_active || warp_tunnel_active || mandelbrot_active ||
clock_active || slideshow_active || spiffs_test_active || sound_test_active || sd_card_test_active ||
asteroids_active || battle_zone_active || boulder_dash_active || tempest_active || invaders_active || missile_command_active ||
breakout_active || crawler_active || maze_chase_active || scramble_active || defender_active || galaga_active ||
dungeon_active || hall_of_fame_active;
if (!app_active || boot_short || boot_long) {
game_paused = false;
pause_redraw = false;
} else if (f_pressed) {
game_paused = !game_paused;
pause_redraw = game_paused;
ESP_LOGI(TAG, "App %s", game_paused ? "PAUSED" : "RESUMED");
}
if (boot_long) {
ESP_LOGI(TAG, "BOOT HOME");
menu_home(now_ms);
} else if (boot_short) {
ESP_LOGI(TAG, "BOOT BACK");
menu_back(now_ms);
} else if (game_paused || (app_active && f_pressed)) {
// Consume pause/resume and discard module actions while paused.
} else if (highScoreEntryActive()) {
highScoreEntryAction(a_pressed);
} else if (checkers_active) {
checkersAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (othello_active) {
othelloAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (connect_four_active) {
connectFourAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (battleships_active) {
battleshipsAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (yahtzee_active) {
yahtzeeAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (blackjack_active) {
blackjackAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (poker_active) {
pokerAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
} else if (poker_dice_active) {
pokerDiceAction(k_pressed || a_pressed, c_pressed, e_pressed, game_now_ms);
} else if (pig_active) {
pigAction(k_pressed || a_pressed, e_pressed, c_pressed, game_now_ms);
} else if (brightness_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (volume_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (wifi_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (esp_info_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (ram_log_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (restart_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (power_off_settings_active) {
settingsAction(k_pressed || a_pressed, now_ms);
} else if (game_config_active) {
gameConfigAction(a_pressed, k_pressed, now_ms);
} else if (font_test_active) {
fontTestAction(k_pressed || a_pressed);
} else if (about_active) {
aboutAction(a_pressed, f_pressed, now_ms);
} else if (matrix_active) {
matrixAction(a_pressed, e_pressed, now_ms);
} else if (boing_ball_active) {
boingBallAction(d_pressed, b_pressed, a_pressed, c_pressed, now_ms);
} else if (demo_active) {
demoAction(k_pressed || a_pressed, b_pressed, c_pressed, user_long_pressed,
d_pressed, b_pressed, now_ms);
} else if (simon_active) {
simonAction(a_pressed, b_pressed, c_pressed, d_pressed, now_ms);
} else if (calculator_active) {
calculatorAction(k_pressed || a_pressed, c_pressed, d_pressed, b_pressed, now_ms);
} else if (kaleidoscope_active) {
kaleidoscopeAction(k_pressed || a_pressed, d_pressed, c_pressed, e_pressed, now_ms);
} else if (warp_tunnel_active) {
warpTunnelAction(k_pressed || a_pressed, c_pressed, e_pressed, now_ms);
} else if (mandelbrot_active) {
mandelbrotAction(k_pressed || a_pressed, c_pressed, b_pressed, e_pressed, now_ms);
} else if (slideshow_active) {
slideshowAction(k_pressed || a_pressed, c_pressed, now_ms);
} else if (spiffs_test_active) {
spiffsTestAction(k_pressed || a_pressed);
} else if (sound_test_active) {
soundTestAction(k_pressed || a_pressed, d_pressed, b_pressed, e_pressed);
} else if (sd_card_test_active) {
sdCardTestAction(k_pressed || a_pressed, c_pressed, now_ms);
} else if (asteroids_active) {
asteroidsAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (battle_zone_active) {
battleZoneAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (boulder_dash_active) {
boulderDashAction(k_pressed || a_pressed, game_now_ms);
} else if (tempest_active) {
tempestAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (invaders_active) {
invadersAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (missile_command_active) {
missileCommandAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (breakout_active) {
breakoutAction(k_pressed || a_pressed, e_pressed, game_now_ms);
} else if (crawler_active) {
crawlerAction(k_pressed || a_pressed,
stable_shield_levels[0] == 0 || stable_shield_levels[6] == 0,
game_now_ms);
} else if (maze_chase_active) {
mazeChaseAction(k_pressed || a_pressed, game_now_ms);
} else if (scramble_active) {
scrambleAction(k_pressed || a_pressed, game_now_ms);
} else if (defender_active) {
defenderAction(k_pressed || a_pressed, c_pressed, game_now_ms);
} else if (galaga_active) {
galagaAction(k_pressed || a_pressed, game_now_ms);
} else if (dungeon_active) {
dungeonAIControl(e_pressed, game_now_ms);
dungeonAction(k_pressed || a_pressed, b_pressed, c_pressed, d_pressed, game_now_ms);
} else if (k_pressed || a_pressed) {
menu_select(now_ms);
}
// One shared C64 Turbo Tape transition. It delays only a new game start;
// returning to the Games menu cancels it immediately through menu_back().
turboLoaderUpdate(menu_current_screen(), now_ms);
const bool checkers_requested = memory_ok && menu_current_screen() == SCREEN_CHECKERS;
if (checkers_requested && !checkers_active && !turboLoaderActive()) {
startCheckers(game_now_ms);
checkers_active = true;
} else if (!checkers_requested && checkers_active) {
stopCheckers();
checkers_active = false;
}
const bool othello_requested = memory_ok && menu_current_screen() == SCREEN_OTHELLO;
if (othello_requested && !othello_active && !turboLoaderActive()) {
startOthello(game_now_ms);
othello_active = true;
} else if (!othello_requested && othello_active) {
stopOthello();
othello_active = false;
}
const bool connect_four_requested =
memory_ok && menu_current_screen() == SCREEN_CONNECT_FOUR;
if (connect_four_requested && !connect_four_active && !turboLoaderActive()) {
startConnectFour(game_now_ms);
connect_four_active = true;
} else if (!connect_four_requested && connect_four_active) {
stopConnectFour();
connect_four_active = false;
}
const bool battleships_requested =
memory_ok && menu_current_screen() == SCREEN_BATTLESHIPS;
if (battleships_requested && !battleships_active && !turboLoaderActive()) {
startBattleships(game_now_ms);
battleships_active = true;
} else if (!battleships_requested && battleships_active) {
stopBattleships();
battleships_active = false;
}
const bool yahtzee_requested = memory_ok && menu_current_screen() == SCREEN_YAHTZEE;
if (yahtzee_requested && !yahtzee_active && !turboLoaderActive()) {
startYahtzee(game_now_ms);
yahtzee_active = true;
} else if (!yahtzee_requested && yahtzee_active) {
stopYahtzee();
yahtzee_active = false;
}
const bool blackjack_requested = memory_ok && menu_current_screen() == SCREEN_BLACKJACK;
if (blackjack_requested && !blackjack_active && !turboLoaderActive()) {
startBlackjack(game_now_ms);
blackjack_active = true;
} else if (!blackjack_requested && blackjack_active) {
stopBlackjack();
blackjack_active = false;
}
const bool poker_requested = memory_ok && menu_current_screen() == SCREEN_POKER;
if (poker_requested && !poker_active && !turboLoaderActive()) {
startPoker(game_now_ms);
poker_active = true;
} else if (!poker_requested && poker_active) {
stopPoker();
poker_active = false;
}
const bool poker_dice_requested = memory_ok && menu_current_screen() == SCREEN_POKER_DICE;
if (poker_dice_requested && !poker_dice_active && !turboLoaderActive()) {
startPokerDice(game_now_ms);
poker_dice_active = true;
} else if (!poker_dice_requested && poker_dice_active) {
stopPokerDice();
poker_dice_active = false;
}
const bool pig_requested = memory_ok && menu_current_screen() == SCREEN_PIG;
if (pig_requested && !pig_active && !turboLoaderActive()) {
startPig(game_now_ms);
pig_active = true;
} else if (!pig_requested && pig_active) {
stopPig();
pig_active = false;
}
const bool brightness_settings_requested =
memory_ok && menu_current_screen() == SCREEN_BRIGHTNESS;
if (brightness_settings_requested && !brightness_settings_active) {
settingsStart(SETTINGS_BRIGHTNESS);
brightness_settings_active = true;
} else if (!brightness_settings_requested && brightness_settings_active) {
settingsStop();
brightness_settings_active = false;
}
const bool volume_settings_requested = memory_ok && menu_current_screen() == SCREEN_VOLUME;
if (volume_settings_requested && !volume_settings_active) {
settingsStart(SETTINGS_VOLUME);
volume_settings_active = true;
} else if (!volume_settings_requested && volume_settings_active) {
settingsStop();
volume_settings_active = false;
}
const bool wifi_settings_requested = memory_ok && menu_current_screen() == SCREEN_WIFI;
if (wifi_settings_requested && !wifi_settings_active) {
settingsStart(SETTINGS_WIFI);
wifi_settings_active = true;
} else if (!wifi_settings_requested && wifi_settings_active) {
settingsStop();
wifi_settings_active = false;
}
const bool esp_info_requested = memory_ok && menu_current_screen() == SCREEN_ESP_INFO;
if (esp_info_requested && !esp_info_active) {
settingsStart(SETTINGS_ESP_INFO);
esp_info_active = true;
} else if (!esp_info_requested && esp_info_active) {
settingsStop();
esp_info_active = false;
}
#if RAM_MONITOR_ENABLED
const bool ram_log_requested = memory_ok && menu_current_screen() == SCREEN_RAM_LOG;
if (ram_log_requested && !ram_log_active) {
settingsStart(SETTINGS_RAM_LOG);
ram_log_active = true;
} else if (!ram_log_requested && ram_log_active) {
settingsStop();
ram_log_active = false;
}
#endif
const bool restart_settings_requested =
memory_ok && menu_current_screen() == SCREEN_RESTART;
if (restart_settings_requested && !restart_settings_active) {
settingsStart(SETTINGS_RESTART);
restart_settings_active = true;
} else if (!restart_settings_requested && restart_settings_active) {
settingsStop();
restart_settings_active = false;
}
if (restart_settings_active && settingsRestartRequested()) {
ESP_LOGI(TAG, "Settings restart confirmed");
esp_restart();
}
const bool power_off_settings_requested =
memory_ok && menu_current_screen() == SCREEN_POWER_OFF;
if (power_off_settings_requested && !power_off_settings_active) {
settingsStart(SETTINGS_POWER_OFF);
power_off_settings_active = true;
} else if (!power_off_settings_requested && power_off_settings_active) {
settingsStop();
power_off_settings_active = false;
}
if (power_off_settings_active && settingsPowerOffRequested()) {
const bool slept = enter_light_sleep(panel, &tf_available);
settingsCancelPowerOffRequest();
if (slept) {
settingsStop();
power_off_settings_active = false;
menu_home((uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
}
}
const bool game_config_requested = memory_ok && menu_current_screen() == SCREEN_GAME_CONFIG;
if (game_config_requested && !game_config_active) {
startGameConfig(now_ms);
game_config_active = true;
} else if (!game_config_requested && game_config_active) {
stopGameConfig();
game_config_active = false;
}
const bool font_test_requested = memory_ok && menu_current_screen() == SCREEN_FONT_TEST;
if (font_test_requested && !font_test_active) {
startFontTest();
font_test_active = true;
} else if (!font_test_requested && font_test_active) {
stopFontTest();
font_test_active = false;
}
const bool matrix_requested = memory_ok && menu_current_screen() == SCREEN_MATRIX;
if (matrix_requested && !matrix_active) {
startMatrix(now_ms);
matrix_active = true;
} else if (!matrix_requested && matrix_active) {
stopMatrix();
matrix_active = false;
}
const bool boing_ball_requested = memory_ok && menu_current_screen() == SCREEN_BOING_BALL;
if (boing_ball_requested && !boing_ball_active) {
startBoingBall(now_ms);
boing_ball_active = true;
} else if (!boing_ball_requested && boing_ball_active) {
stopBoingBall();
boing_ball_active = false;
}
// The main-menu DEMO entry presents the current Demo visual module.
// Keep SCREEN_ABOUT as the route so Back returns to the main menu.
const bool demo_requested = memory_ok && menu_current_screen() == SCREEN_ABOUT;
if (demo_requested && !demo_active) {
startDemo(now_ms);
demo_active = true;
} else if (!demo_requested && demo_active) {
stopDemo();
demo_active = false;
}
const bool simon_requested = memory_ok && menu_current_screen() == SCREEN_SIMON;
if (simon_requested && !simon_active && !turboLoaderActive()) {
startSimon(now_ms);
simon_active = true;
} else if (!simon_requested && simon_active) {
stopSimon();
simon_active = false;
}
const bool calculator_requested = memory_ok && menu_current_screen() == SCREEN_CALCULATOR;
if (calculator_requested && !calculator_active) {
startCalculator(now_ms);
calculator_active = true;
} else if (!calculator_requested && calculator_active) {
stopCalculator();
calculator_active = false;
}
const bool kaleidoscope_requested =
memory_ok && menu_current_screen() == SCREEN_KALEIDOSCOPE;
if (kaleidoscope_requested && !kaleidoscope_active) {
startKaleidoscope(now_ms);
kaleidoscope_active = true;
} else if (!kaleidoscope_requested && kaleidoscope_active) {
stopKaleidoscope();
kaleidoscope_active = false;
}
const bool warp_tunnel_requested =
memory_ok && menu_current_screen() == SCREEN_WARP_TUNNEL;
if (warp_tunnel_requested && !warp_tunnel_active) {
startWarpTunnel(now_ms);
warp_tunnel_active = true;
} else if (!warp_tunnel_requested && warp_tunnel_active) {
stopWarpTunnel();
warp_tunnel_active = false;
}
const bool mandelbrot_requested = memory_ok && menu_current_screen() == SCREEN_MANDELBROT;
if (mandelbrot_requested && !mandelbrot_active) {
startMandelbrot(now_ms);
mandelbrot_active = true;
} else if (!mandelbrot_requested && mandelbrot_active) {
stopMandelbrot();
mandelbrot_active = false;
}
const bool clock_requested = memory_ok && menu_current_screen() == SCREEN_CLOCK;
if (clock_requested && !clock_active) {
startClock(now_ms);
clock_active = true;
} else if (!clock_requested && clock_active) {
stopClock();
clock_active = false;
}
if (storage_ready) settingsClockAutoSyncUpdate(now_ms);
const bool slideshow_requested = memory_ok && menu_current_screen() == SCREEN_SLIDESHOW;
if (slideshow_requested && !slideshow_active) {
startSlideshow(now_ms);
slideshow_active = true;
} else if (!slideshow_requested && slideshow_active) {
stopSlideshow();
slideshow_active = false;
}
const bool spiffs_test_requested = memory_ok && menu_current_screen() == SCREEN_SPIFFS_TEST;
if (spiffs_test_requested && !spiffs_test_active) {
startSpiffsTest();
spiffs_test_active = true;
} else if (!spiffs_test_requested && spiffs_test_active) {
stopSpiffsTest();
spiffs_test_active = false;
}
const bool sound_test_requested = memory_ok && menu_current_screen() == SCREEN_SOUND_TEST;
if (sound_test_requested && !sound_test_active) {
startSoundTest();
sound_test_active = true;
} else if (!sound_test_requested && sound_test_active) {
stopSoundTest();
sound_test_active = false;
}
const bool sd_card_test_requested = memory_ok && menu_current_screen() == SCREEN_SD_CARD_TEST;
if (sd_card_test_requested && !sd_card_test_active) {
startSdCardTest(now_ms);
sd_card_test_active = true;
} else if (!sd_card_test_requested && sd_card_test_active) {
stopSdCardTest();
sd_card_test_active = false;
}
const bool hall_of_fame_requested =
memory_ok && menu_current_screen() == SCREEN_HALL_OF_FAME;
if (hall_of_fame_requested && !hall_of_fame_active) {
startHallOfFame(now_ms);
hall_of_fame_active = true;
} else if (!hall_of_fame_requested && hall_of_fame_active) {
stopHallOfFame();
hall_of_fame_active = false;
}
const bool asteroids_requested = memory_ok && menu_current_screen() == SCREEN_ASTEROIDS;
if (asteroids_requested && !asteroids_active && !turboLoaderActive()) {
startAsteroids(game_now_ms);
asteroids_active = true;
} else if (!asteroids_requested && asteroids_active) {
stopAsteroids();
asteroids_active = false;
}
const bool battle_zone_requested = memory_ok && menu_current_screen() == SCREEN_BATTLE_ZONE;
if (battle_zone_requested && !battle_zone_active && !turboLoaderActive()) {
startBattleZone(game_now_ms);
battle_zone_active = true;
} else if (!battle_zone_requested && battle_zone_active) {
stopBattleZone();
battle_zone_active = false;
}
const bool boulder_dash_requested = memory_ok && menu_current_screen() == SCREEN_BOULDER_DASH;
if (boulder_dash_requested && !boulder_dash_active && !turboLoaderActive()) {
startBoulderDash(game_now_ms);
boulder_dash_active = true;
} else if (!boulder_dash_requested && boulder_dash_active) {
stopBoulderDash();
boulder_dash_active = false;
}
const bool tempest_requested = memory_ok && menu_current_screen() == SCREEN_TEMPEST;
if (tempest_requested && !tempest_active && !turboLoaderActive()) {
startTempest(game_now_ms);
tempest_active = true;
} else if (!tempest_requested && tempest_active) {
stopTempest();
tempest_active = false;
}
const bool invaders_requested = memory_ok && menu_current_screen() == SCREEN_INVADERS;
if (invaders_requested && !invaders_active && !turboLoaderActive()) {
startInvaders(game_now_ms);
invaders_active = true;
} else if (!invaders_requested && invaders_active) {
stopInvaders();
invaders_active = false;
}
const bool missile_command_requested =
memory_ok && menu_current_screen() == SCREEN_MISSILE_COMMAND;
if (missile_command_requested && !missile_command_active && !turboLoaderActive()) {
startMissileCommand(game_now_ms);
missile_command_active = true;
} else if (!missile_command_requested && missile_command_active) {
stopMissileCommand();
missile_command_active = false;
}
const bool breakout_requested = memory_ok && menu_current_screen() == SCREEN_BREAKOUT;
if (breakout_requested && !breakout_active && !turboLoaderActive()) {
startBreakout(game_now_ms);
breakout_active = true;
} else if (!breakout_requested && breakout_active) {
stopBreakout();
breakout_active = false;
}
const bool crawler_requested = memory_ok && menu_current_screen() == SCREEN_CRAWLER;
if (crawler_requested && !crawler_active && !turboLoaderActive()) {
startCrawler(game_now_ms);
crawler_active = true;
} else if (!crawler_requested && crawler_active) {
stopCrawler();
crawler_active = false;
}
const bool maze_chase_requested = memory_ok && menu_current_screen() == SCREEN_MAZE_CHASE;
if (maze_chase_requested && !maze_chase_active && !turboLoaderActive()) {
startMazeChase(game_now_ms);
maze_chase_active = true;
} else if (!maze_chase_requested && maze_chase_active) {
stopMazeChase();
maze_chase_active = false;
}
const bool scramble_requested = memory_ok && menu_current_screen() == SCREEN_SCRAMBLE;
if (scramble_requested && !scramble_active && !turboLoaderActive()) {
startScramble(game_now_ms);
scramble_active = true;
} else if (!scramble_requested && scramble_active) {
stopScramble();
scramble_active = false;
}
const bool defender_requested = memory_ok && menu_current_screen() == SCREEN_DEFENDER;
if (defender_requested && !defender_active && !turboLoaderActive()) {
startDefender(game_now_ms);
defender_active = true;
} else if (!defender_requested && defender_active) {
stopDefender();
defender_active = false;
}
const bool galaga_requested = memory_ok && menu_current_screen() == SCREEN_GALAGA;
if (galaga_requested && !galaga_active && !turboLoaderActive()) {
startGalaga(game_now_ms);
galaga_active = true;
} else if (!galaga_requested && galaga_active) {
stopGalaga();
galaga_active = false;
}
const bool dungeon_requested = memory_ok && menu_current_screen() == SCREEN_DUNGEON;
if (dungeon_requested && !dungeon_active && !turboLoaderActive()) {
startDungeon(game_now_ms);
dungeon_active = true;
} else if (!dungeon_requested && dungeon_active) {
stopDungeon(game_now_ms);
dungeon_active = false;
}
if ((now - last_controller_refresh) >= pdMS_TO_TICKS(CONTROLLER_REFRESH_MS)) {
last_controller_refresh = now;
const esp_err_t live_result = controller_input_read(&live_joystick);
joystick_valid = live_result == ESP_OK;
if (!joystick_valid && previous_joystick_valid) {
ESP_LOGE(TAG, "Live joystick read failed: %s", esp_err_to_name(live_result));
} else if (joystick_valid && !previous_joystick_valid) {
ESP_LOGI(TAG, "Live joystick readings recovered");
}
const bool validity_changed = joystick_valid != previous_joystick_valid;
previous_joystick_valid = joystick_valid;
joystick_validity_changed = joystick_validity_changed || validity_changed;
batt_update(now_ms);
// An active module owns input; menu idle timeouts must not end a paused module.
if (!app_active) {
if (!menu_update_buttons(left_held, right_held, now_ms)) {
menu_update_joystick(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
}
menu_update(now_ms);
}
}
// Keep input at 20 ms, but render independently at a 60 fps target.
// One task remains the sole owner of the framebuffer and LCD transfer.
menu_redraw_pending = menu_redraw_pending || menu_take_redraw();
if ((now - last_render_refresh) >= pdMS_TO_TICKS(RENDER_REFRESH_MS)) {
last_render_refresh = now;
const bool menu_redraw = menu_redraw_pending;
const bool validity_changed = joystick_validity_changed;
menu_redraw_pending = false;
joystick_validity_changed = false;
if (memory_ok) {
if (turboLoaderActive()) {
drawTurboLoader(frame, now_ms);
draw_frame(panel, frame);
} else if (game_paused) {
if (pause_redraw || now_ms - pause_color_changed_ms >= 500U) {
pause_color_index = pause_redraw ? 0 :
(pause_color_index + 1U) % (sizeof(pause_colors) / sizeof(pause_colors[0]));
// Repaint only the same letter pixels; leave the frozen scene visible.
drawPet2001TextCentredTransparent(frame, 160, 78, "PAUSED",
pause_colors[pause_color_index], 2);
draw_frame(panel, frame);
pause_color_changed_ms = now_ms;
pause_redraw = false;
}
} else if (checkers_active) {
updateCheckers(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawCheckers(frame);
draw_frame(panel, frame);
} else if (othello_active) {
updateOthello(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawOthello(frame);
draw_frame(panel, frame);
} else if (connect_four_active) {
updateConnectFour(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawConnectFour(frame);
draw_frame(panel, frame);
} else if (battleships_active) {
updateBattleships(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBattleships(frame);
draw_frame(panel, frame);
} else if (yahtzee_active) {
updateYahtzee(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawYahtzee(frame);
draw_frame(panel, frame);
} else if (blackjack_active) {
updateBlackjack(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBlackjack(frame);
draw_frame(panel, frame);
} else if (poker_active) {
updatePoker(frame, live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPoker(frame);
draw_frame(panel, frame);
} else if (poker_dice_active) {
updatePokerDice(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPokerDice(frame);
draw_frame(panel, frame);
} else if (pig_active) {
updatePig(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawPig(frame);
draw_frame(panel, frame);
} else if (brightness_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
set_backlight_brightness(settingsBrightness());
settingsDraw(frame);
draw_frame(panel, frame);
} else if (volume_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (wifi_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (esp_info_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (ram_log_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (restart_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (power_off_settings_active) {
settingsUpdate(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid,
left_held, right_held, now_ms);
settingsDraw(frame);
draw_frame(panel, frame);
} else if (game_config_active) {
updateGameConfig(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawGameConfig(frame);
draw_frame(panel, frame);
} else if (font_test_active) {
drawFontTest(frame);
draw_frame(panel, frame);
} else if (about_active) {
updateAbout(now_ms);
drawAbout(frame);
draw_frame(panel, frame);
} else if (matrix_active) {
updateMatrix(now_ms);
if (matrixTakeFrame()) {
drawMatrix(frame);
draw_frame(panel, frame);
}
} else if (boing_ball_active) {
updateBoingBall(now_ms);
drawBoingBall(frame);
draw_frame(panel, frame);
} else if (demo_active) {
updateDemo(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawDemo(frame);
draw_frame(panel, frame);
} else if (simon_active) {
updateSimon(now_ms);
drawSimon(frame);
draw_frame(panel, frame);
} else if (calculator_active) {
updateCalculator(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
drawCalculator(frame);
draw_frame(panel, frame);
} else if (kaleidoscope_active) {
updateKaleidoscope(now_ms);
drawKaleidoscope(frame);
draw_frame(panel, frame);
} else if (warp_tunnel_active) {
updateWarpTunnel(now_ms);
drawWarpTunnel(frame);
draw_frame(panel, frame);
} else if (mandelbrot_active) {
updateMandelbrot(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, now_ms);
if (mandelbrotTakeFrame()) {
drawMandelbrot(frame);
draw_frame(panel, frame);
}
} else if (clock_active) {
updateClock(now_ms);
if (clockTakeFrame()) {
drawClock(frame);
draw_frame(panel, frame);
}
} else if (slideshow_active) {
updateSlideshow(frame, now_ms);
drawSlideshow(frame);
draw_frame(panel, frame);
} else if (spiffs_test_active) {
drawSpiffsTest(frame);
draw_frame(panel, frame);
} else if (sound_test_active) {
drawSoundTest(frame);
draw_frame(panel, frame);
} else if (sd_card_test_active) {
updateSdCardTest(frame, live_joystick.y_raw - JOYSTICK_CENTRE_Y, joystick_valid, now_ms);
drawSdCardTest(frame);
draw_frame(panel, frame);
} else if (asteroids_active) {
updateAsteroids(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawAsteroids(frame);
draw_frame(panel, frame);
} else if (battle_zone_active) {
updateBattleZone(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawBattleZone(frame);
draw_frame(panel, frame);
} else if (boulder_dash_active) {
updateBoulderDash(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawBoulderDash(frame);
draw_frame(panel, frame);
} else if (tempest_active) {
updateTempest(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawTempest(frame);
draw_frame(panel, frame);
} else if (invaders_active) {
updateInvaders(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held,
stable_shield_levels[0] == 0, game_now_ms);
drawInvaders(frame);
draw_frame(panel, frame);
} else if (missile_command_active) {
updateMissileCommand(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held,
stable_shield_levels[0] == 0, right_held, game_now_ms);
drawMissileCommand(frame);
draw_frame(panel, frame);
} else if (breakout_active) {
updateBreakout(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawBreakout(frame);
draw_frame(panel, frame);
} else if (crawler_active) {
updateCrawler(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawCrawler(frame);
draw_frame(panel, frame);
} else if (maze_chase_active) {
updateMazeChase(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, game_now_ms);
drawMazeChase(frame);
draw_frame(panel, frame);
} else if (scramble_active) {
updateScramble(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0,
stable_shield_levels[1] == 0, game_now_ms);
drawScramble(frame);
draw_frame(panel, frame);
} else if (defender_active) {
updateDefender(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0,
stable_shield_levels[1] == 0, game_now_ms);
drawDefender(frame);
draw_frame(panel, frame);
} else if (galaga_active) {
updateGalaga(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, stable_shield_levels[0] == 0, game_now_ms);
drawGalaga(frame);
draw_frame(panel, frame);
} else if (dungeon_active) {
updateDungeon(frame,
live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, game_now_ms);
drawDungeon(frame);
draw_frame(panel, frame);
} else if (hall_of_fame_active) {
updateHallOfFame(live_joystick.x_raw - JOYSTICK_CENTRE_X,
live_joystick.y_raw - JOYSTICK_CENTRE_Y,
joystick_valid, left_held, right_held, now_ms);
drawHallOfFame(frame);
draw_frame(panel, frame);
} else if (menu_current_screen() == SCREEN_CONTROLLER_TEST) {
draw_controller_view(frame, &live_joystick, joystick_valid,
stable_shield_levels);
draw_frame(panel, frame);
} else if (menu_current_screen() == SCREEN_GRAPHICS_TEST) {
if (menu_redraw) {
fill_color_bars(frame);
draw_frame(panel, frame);
}
} else if (menu_current_screen() == SCREEN_BATTERY) {
draw_battery_view(frame);
draw_frame(panel, frame);
} else if (menu_redraw || validity_changed) {
draw_menu_view(frame, joystick_valid);
draw_frame(panel, frame);
}
}
}
// With the 1 kHz FreeRTOS tick, yield briefly without quantising a
// 16 ms render cadence onto the old 10 ms scheduler boundaries.
vTaskDelay(pdMS_TO_TICKS(1));
}
}