From f9d7314b911c8ea3f9df2e7b5c3611fb9f930bd6 Mon Sep 17 00:00:00 2001 From: Ivan Podogov Date: Tue, 2 Jun 2026 14:54:43 +0100 Subject: [PATCH] Add ESP32-S3 4848S040 board --- firmware/platformio.ini | 51 +++++ firmware/src/boards/esp32_s3_4848s040/board.h | 68 ++++++ .../boards/esp32_s3_4848s040/board_init.cpp | 7 + .../src/boards/esp32_s3_4848s040/caps.cpp | 14 ++ .../src/boards/esp32_s3_4848s040/display.cpp | 203 ++++++++++++++++++ firmware/src/boards/esp32_s3_4848s040/imu.cpp | 6 + .../src/boards/esp32_s3_4848s040/input.cpp | 4 + .../src/boards/esp32_s3_4848s040/power.cpp | 18 ++ .../src/boards/esp32_s3_4848s040/touch.cpp | 88 ++++++++ firmware/src/main.cpp | 3 + 10 files changed, 462 insertions(+) create mode 100644 firmware/src/boards/esp32_s3_4848s040/board.h create mode 100644 firmware/src/boards/esp32_s3_4848s040/board_init.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/caps.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/display.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/imu.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/input.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/power.cpp create mode 100644 firmware/src/boards/esp32_s3_4848s040/touch.cpp diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 560cbd30e..3a6849a9b 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -177,3 +177,54 @@ lib_deps = lvgl/lvgl@^9.2.0 bblanchon/ArduinoJson@^7.0.0 h2zero/NimBLE-Arduino@^2.1.1 + + +[env:esp32_s3_4848s040] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip +board = esp32-s3-devkitc-1 +framework = arduino +board_build.arduino.memory_type = qio_opi +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +board_build.partitions = default_16MB.csv +upload_speed = 921600 +monitor_speed = 115200 + +build_src_filter = + +<*> + - + + + +build_flags = + -DBOARD_4848S040 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DBOARD_HAS_PSRAM + ; NimBLE config — peripheral only, 2 connections (HID + data service) + -DCONFIG_BT_NIMBLE_ROLE_BROADCASTER=1 + -DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL=0 + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER=0 + -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=2 + ; LVGL config via build flags + -DLV_CONF_SKIP + -DLV_COLOR_DEPTH=16 + -DLV_USE_LOG=0 + -DLV_USE_BAR=1 + -DLV_USE_LABEL=1 + -DLV_USE_ARC=1 + -DLV_USE_BTN=1 + -DLV_USE_LINE=1 + -DLV_USE_OBJ=1 + -DLV_USE_IMG=1 + -DLV_USE_IMAGE=1 + -DLV_USE_ANIMIMG=0 + -DLV_TICK_CUSTOM=1 + -DLV_USE_SNAPSHOT=1 + +lib_deps = + ; 1.6.4+ includes Arduino_RGB_Display and Arduino_ESP32RGBPanel in mainline + moononournation/GFX Library for Arduino@^1.6.4 + lvgl/lvgl@^9.2.0 + bblanchon/ArduinoJson@^7.0.0 + h2zero/NimBLE-Arduino@^2.1.1 + ; No SensorLib / XPowersLib — this board has no IMU or PMU diff --git a/firmware/src/boards/esp32_s3_4848s040/board.h b/firmware/src/boards/esp32_s3_4848s040/board.h new file mode 100644 index 000000000..03e991c69 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/board.h @@ -0,0 +1,68 @@ +#pragma once + +#define BOARD_NAME "ESP32-S3 4848S040" + +// ---- Display geometry ---- +#define LCD_WIDTH 480 +#define LCD_HEIGHT 480 + +// ---- ST7701S: 3-wire SPI for init commands ---- +#define TFT_CS 39 +#define TFT_SCLK 48 +#define TFT_MOSI 47 + +// ---- ST7701S: RGB parallel pixel interface ---- +#define TFT_DE 18 +#define TFT_VSYNC 17 +#define TFT_HSYNC 16 +#define TFT_PCLK 21 +// Red channel (bit 0 = LSB) +#define TFT_R0 11 +#define TFT_R1 12 +#define TFT_R2 13 +#define TFT_R3 14 +#define TFT_R4 0 +// Green channel +#define TFT_G0 8 +#define TFT_G1 20 +#define TFT_G2 3 +#define TFT_G3 46 +#define TFT_G4 9 +#define TFT_G5 10 +// Blue channel +#define TFT_B0 4 +#define TFT_B1 5 +#define TFT_B2 6 +#define TFT_B3 7 +#define TFT_B4 15 + +// ---- Sync timing (from Generic Guition datasheet / OpenHASP reference) ---- +#define TFT_HSYNC_POLARITY 1 +#define TFT_HSYNC_FRONT_PORCH 10 +#define TFT_HSYNC_PULSE_WIDTH 8 +#define TFT_HSYNC_BACK_PORCH 50 +#define TFT_VSYNC_POLARITY 1 +#define TFT_VSYNC_FRONT_PORCH 10 +#define TFT_VSYNC_PULSE_WIDTH 8 +#define TFT_VSYNC_BACK_PORCH 20 +#define TFT_PCLK_ACTIVE_NEG 1 +#define TFT_PREFER_SPEED 12000000L + +// ---- Backlight (PWM, active-high) ---- +#define TFT_BCKL 38 + +// ---- I2C bus (touch only on this board) ---- +#define IIC_SDA 19 +#define IIC_SCL 45 + +// ---- Touch: GT911 ---- +// No IRQ or RST pins connected; probe both possible addresses at init. +#define GT911_ADDR_PRIMARY 0x5D +#define GT911_ADDR_ALT 0x14 + +// ---- Capability flags ---- +#define BOARD_HAS_SECONDARY_BUTTON 0 +#define BOARD_HAS_ROTATION 0 +#define BOARD_HAS_IMU 0 +#define BOARD_HAS_BATTERY 0 +#define BOARD_HAS_IO_EXPANDER 0 diff --git a/firmware/src/boards/esp32_s3_4848s040/board_init.cpp b/firmware/src/boards/esp32_s3_4848s040/board_init.cpp new file mode 100644 index 000000000..c852178a9 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/board_init.cpp @@ -0,0 +1,7 @@ +#include "board.h" +#include +#include + +extern "C" void board_init(void) { + Wire.begin(IIC_SDA, IIC_SCL, 400000UL); +} diff --git a/firmware/src/boards/esp32_s3_4848s040/caps.cpp b/firmware/src/boards/esp32_s3_4848s040/caps.cpp new file mode 100644 index 000000000..16b3990e4 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/caps.cpp @@ -0,0 +1,14 @@ +#include "../../hal/board_caps.h" +#include "board.h" + +static const BoardCaps caps = { + .name = BOARD_NAME, + .width = LCD_WIDTH, + .height = LCD_HEIGHT, + .button_count = 0, + .has_rotation = false, + .has_battery = false, + .has_imu = false, +}; + +const BoardCaps& board_caps(void) { return caps; } diff --git a/firmware/src/boards/esp32_s3_4848s040/display.cpp b/firmware/src/boards/esp32_s3_4848s040/display.cpp new file mode 100644 index 000000000..676d5115a --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/display.cpp @@ -0,0 +1,203 @@ +#include "../../hal/display_hal.h" +#include "board.h" +#include +#include + +// ST7701S uses 3-wire SPI (CS/SCLK/MOSI) for the register init sequence and +// a 16-bit RGB parallel interface (DE/HSYNC/VSYNC/PCLK + R[4:0]/G[5:0]/B[4:0]) +// for pixel data. The ESP32-S3 LCD DMA controller drives the RGB side via a +// full-screen framebuffer in PSRAM — it refreshes the panel continuously, so +// partial LVGL flush writes go straight into that framebuffer and are visible +// on the very next DMA cycle. No explicit flush management needed. +// +// setBrightness() is not available on RGB displays; backlight is PWM on GPIO 38. + +static Arduino_DataBus* spi_bus = nullptr; +static Arduino_ESP32RGBPanel* panel = nullptr; +static Arduino_RGB_Display* gfx = nullptr; + +// ST7701S init sequence for the Generic Guition 4848S040, sourced from the +// OpenHASP project (lib/Arduino_RPi_DPI_RGBPanel_mod/Arduino_RGB_Display_mod.h). +// Enum values (BEGIN_WRITE, WRITE_COMMAND_8, …) come from Arduino_DataBus.h. +static const uint8_t st7701_init_ops[] = { + BEGIN_WRITE, + WRITE_COMMAND_8, 0xFF, + WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x10, + + WRITE_C8_D16, 0xC0, 0x3B, 0x00, + WRITE_C8_D16, 0xC1, 0x0D, 0x02, + WRITE_C8_D16, 0xC2, 0x31, 0x05, + WRITE_C8_D8, 0xCD, 0x00, + + WRITE_COMMAND_8, 0xB0, // Positive Voltage Gamma + WRITE_BYTES, 16, + 0x00, 0x11, 0x18, 0x0E, + 0x11, 0x06, 0x07, 0x08, + 0x07, 0x22, 0x04, 0x12, + 0x0F, 0xAA, 0x31, 0x18, + + WRITE_COMMAND_8, 0xB1, // Negative Voltage Gamma + WRITE_BYTES, 16, + 0x00, 0x11, 0x19, 0x0E, + 0x12, 0x07, 0x08, 0x08, + 0x08, 0x22, 0x04, 0x11, + 0x11, 0xA9, 0x32, 0x18, + + // PAGE1 + WRITE_COMMAND_8, 0xFF, + WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x11, + + WRITE_C8_D8, 0xB0, 0x60, // Vop = 4.7375 V + WRITE_C8_D8, 0xB1, 0x32, // VCOM + WRITE_C8_D8, 0xB2, 0x07, // VGH = 15 V + WRITE_C8_D8, 0xB3, 0x80, + WRITE_C8_D8, 0xB5, 0x49, // VGL = −10.17 V + WRITE_C8_D8, 0xB7, 0x85, + WRITE_C8_D8, 0xB8, 0x21, // AVDD = 6.6 V, AVCL = −4.6 V + WRITE_C8_D8, 0xC1, 0x78, + WRITE_C8_D8, 0xC2, 0x78, + + WRITE_COMMAND_8, 0xE0, + WRITE_BYTES, 3, 0x00, 0x1B, 0x02, + + WRITE_COMMAND_8, 0xE1, + WRITE_BYTES, 11, + 0x08, 0xA0, 0x00, 0x00, + 0x07, 0xA0, 0x00, 0x00, + 0x00, 0x44, 0x44, + + WRITE_COMMAND_8, 0xE2, + WRITE_BYTES, 12, + 0x11, 0x11, 0x44, 0x44, + 0xED, 0xA0, 0x00, 0x00, + 0xEC, 0xA0, 0x00, 0x00, + + WRITE_COMMAND_8, 0xE3, + WRITE_BYTES, 4, 0x00, 0x00, 0x11, 0x11, + + WRITE_C8_D16, 0xE4, 0x44, 0x44, + + WRITE_COMMAND_8, 0xE5, + WRITE_BYTES, 16, + 0x0A, 0xE9, 0xD8, 0xA0, + 0x0C, 0xEB, 0xD8, 0xA0, + 0x0E, 0xED, 0xD8, 0xA0, + 0x10, 0xEF, 0xD8, 0xA0, + + WRITE_COMMAND_8, 0xE6, + WRITE_BYTES, 4, 0x00, 0x00, 0x11, 0x11, + + WRITE_C8_D16, 0xE7, 0x44, 0x44, + + WRITE_COMMAND_8, 0xE8, + WRITE_BYTES, 16, + 0x09, 0xE8, 0xD8, 0xA0, + 0x0B, 0xEA, 0xD8, 0xA0, + 0x0D, 0xEC, 0xD8, 0xA0, + 0x0F, 0xEE, 0xD8, 0xA0, + + WRITE_COMMAND_8, 0xEB, + WRITE_BYTES, 7, + 0x02, 0x00, 0xE4, 0xE4, + 0x88, 0x00, 0x40, + + WRITE_C8_D16, 0xEC, 0x3C, 0x00, + + WRITE_COMMAND_8, 0xED, + WRITE_BYTES, 16, + 0xAB, 0x89, 0x76, 0x54, + 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x20, + 0x45, 0x67, 0x98, 0xBA, + + // VAP / VAN bank + WRITE_COMMAND_8, 0xFF, + WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x13, + WRITE_C8_D8, 0xE5, 0xE4, + + WRITE_COMMAND_8, 0xFF, + WRITE_BYTES, 5, 0x77, 0x01, 0x00, 0x00, 0x00, + + WRITE_COMMAND_8, 0x21, // IPS mode + WRITE_C8_D8, 0x3A, 0x60, // RGB666 pixel format + + WRITE_COMMAND_8, 0x11, // Sleep Out + END_WRITE, + + DELAY, 120, + + BEGIN_WRITE, + WRITE_COMMAND_8, 0x29, // Display On + END_WRITE, + + BEGIN_WRITE, + WRITE_COMMAND_8, 0x20, // Display Inversion Off (normal) + END_WRITE, +}; + +void display_hal_init(void) { + // 3-wire SPI bus: DC=-1 (not used for RGB init protocol), no MISO + spi_bus = new Arduino_SWSPI( + GFX_NOT_DEFINED /* DC */, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED /* MISO */); + + panel = new Arduino_ESP32RGBPanel( + TFT_DE, TFT_VSYNC, TFT_HSYNC, TFT_PCLK, + TFT_R0, TFT_R1, TFT_R2, TFT_R3, TFT_R4, + TFT_G0, TFT_G1, TFT_G2, TFT_G3, TFT_G4, TFT_G5, + TFT_B0, TFT_B1, TFT_B2, TFT_B3, TFT_B4, + TFT_HSYNC_POLARITY, TFT_HSYNC_FRONT_PORCH, TFT_HSYNC_PULSE_WIDTH, TFT_HSYNC_BACK_PORCH, + TFT_VSYNC_POLARITY, TFT_VSYNC_FRONT_PORCH, TFT_VSYNC_PULSE_WIDTH, TFT_VSYNC_BACK_PORCH, + TFT_PCLK_ACTIVE_NEG, TFT_PREFER_SPEED); + + gfx = new Arduino_RGB_Display( + LCD_WIDTH, LCD_HEIGHT, panel, + 0 /* rotation */, false /* auto_flush — LVGL drives flushing */, + spi_bus, GFX_NOT_DEFINED /* RST */, + st7701_init_ops, sizeof(st7701_init_ops)); +} + +void display_hal_begin(void) { + gfx->begin(GFX_NOT_DEFINED); // speed ignored for RGB panel + gfx->fillScreen(0x0000); + + // Backlight: Arduino 3.x LEDC API (pioarduino / ESP-IDF 5.x) + // NOTE: 100 Hz with 8-bit resolution overflows the 18-bit ESP32-S3 LEDC + // clock_divider register (needs div=800,000; max is 262,143 at 80 MHz APB). + // Minimum safe frequency at 8-bit/80 MHz is ~305 Hz; use 1 kHz. + ledcAttach(TFT_BCKL, 1000, 8); // 1 kHz, 8-bit resolution + ledcWrite(TFT_BCKL, 200); // default brightness (~78 %) +} + +void display_hal_set_brightness(uint8_t level) { + ledcWrite(TFT_BCKL, level); +} + +void display_hal_fill_screen(uint16_t color) { + if (gfx) gfx->fillScreen(color); +} + +void display_hal_draw_bitmap(int32_t x, int32_t y, int32_t w, int32_t h, + const uint16_t* pixels) { + if (!gfx) return; + // Use memcpy row-by-row directly into the DMA framebuffer instead of the + // GFX library's word-loop. gfx_draw_bitmap_to_framebuffer copies 2 pixels + // per iteration (~1.9 ms for a 480x40 strip); memcpy uses the compiler's + // optimised path (~0.5 ms), halving the window during which the DMA and + // CPU race over the same PSRAM region and reducing visible tearing. + uint16_t* fb = gfx->getFramebuffer(); + if (!fb) return; + for (int32_t row = 0; row < h; row++) { + memcpy(&fb[(y + row) * LCD_WIDTH + x], + &pixels[row * w], + (size_t)w * sizeof(uint16_t)); + } +} + +void display_hal_tick(void) { + // No rotation, no transition handling needed. +} + +void display_hal_round_area(int32_t* x1, int32_t* y1, int32_t* x2, int32_t* y2) { + // RGB parallel panel has no alignment constraint; this is a no-op. + (void)x1; (void)y1; (void)x2; (void)y2; +} diff --git a/firmware/src/boards/esp32_s3_4848s040/imu.cpp b/firmware/src/boards/esp32_s3_4848s040/imu.cpp new file mode 100644 index 000000000..2bc59a9e9 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/imu.cpp @@ -0,0 +1,6 @@ +#include "../../hal/imu_hal.h" + +// No IMU on this board; rotation is fixed at 0°. +void imu_hal_init(void) {} +void imu_hal_tick(void) {} +uint8_t imu_hal_rotation_quadrant(void) { return 0; } diff --git a/firmware/src/boards/esp32_s3_4848s040/input.cpp b/firmware/src/boards/esp32_s3_4848s040/input.cpp new file mode 100644 index 000000000..0f672f7f2 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/input.cpp @@ -0,0 +1,4 @@ +#include "../../hal/input_hal.h" + +void input_hal_init(void) {} +bool input_hal_is_held(InputButton btn) { (void)btn; return false; } diff --git a/firmware/src/boards/esp32_s3_4848s040/power.cpp b/firmware/src/boards/esp32_s3_4848s040/power.cpp new file mode 100644 index 000000000..9b59f2b6f --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/power.cpp @@ -0,0 +1,18 @@ +#include "../../hal/power_hal.h" +#include + +// No PMU, no battery, no dedicated power button on this board. +// IDLE_SLEEP_WHEN_CHARGING=false in idle_cfg.h means idle.cpp will call +// power_hal_is_vbus_in() to keep the display on while USB is connected. +// Return true unconditionally here so the device never auto-sleeps while +// plugged in — it has no way to detect VBUS without a PMU. + +void power_hal_init(void) {} +void power_hal_tick(void) {} + +int power_hal_battery_pct(void) { return -1; } +bool power_hal_is_charging(void) { return false; } +bool power_hal_is_vbus_in(void) { return true; } // always-on, no PMU +bool power_hal_pwr_pressed(void) { return false; } +bool power_hal_pwr_long_pressed(void) { return false; } +bool power_hal_pwr_released(void) { return false; } diff --git a/firmware/src/boards/esp32_s3_4848s040/touch.cpp b/firmware/src/boards/esp32_s3_4848s040/touch.cpp new file mode 100644 index 000000000..afd9855d6 --- /dev/null +++ b/firmware/src/boards/esp32_s3_4848s040/touch.cpp @@ -0,0 +1,88 @@ +#include "../../hal/touch_hal.h" +#include "board.h" +#include +#include + +// Minimal GT911 reader. No external library — avoids the GPL Goodix dependency. +// +// Register map: +// 0x8140 (4 bytes) — Product ID ("911\0" when alive) +// 0x814E (1 byte) — Status: bit7=buffer_ready, bits[3:0]=touch_count +// 0x8150 (8 bytes) — Point 1: trackId, x_l, x_h, y_l, y_h, sz_l, sz_h, reserved +// Write 0x00 to 0x814E after reading to clear the buffer-ready flag. +// +// I2C address is determined by the INT/RST pin state at power-on. With both +// pins unconnected (floating), 0x5D is the most common default; 0x14 is the +// alternative. We probe both at init and remember whichever responds. + +static uint8_t gt911_addr = GT911_ADDR_PRIMARY; +static bool gt911_ok = false; +static bool touch_pressed = false; +static uint16_t touch_x = 0; +static uint16_t touch_y = 0; + +static bool gt911_read(uint16_t reg, uint8_t* buf, size_t len) { + Wire.beginTransmission(gt911_addr); + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)(reg & 0xFF)); + // Use sendStop=true (stop-then-start) — the IDF 5.x i2c_master_ng driver + // leaves the bus in a non-IDLE state after endTransmission(false). + if (Wire.endTransmission(true) != 0) return false; + if (Wire.requestFrom(gt911_addr, (uint8_t)len) != len) return false; + for (size_t i = 0; i < len; i++) buf[i] = Wire.read(); + return true; +} + +static bool gt911_write(uint16_t reg, uint8_t val) { + Wire.beginTransmission(gt911_addr); + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)(reg & 0xFF)); + Wire.write(val); + return Wire.endTransmission() == 0; +} + +static bool gt911_probe(uint8_t addr) { + gt911_addr = addr; + uint8_t id[4] = {}; + if (!gt911_read(0x8140, id, 4)) return false; + return id[0] == '9' && id[1] == '1' && id[2] == '1'; +} + +void touch_hal_init(void) { + if (!gt911_probe(GT911_ADDR_PRIMARY) && !gt911_probe(GT911_ADDR_ALT)) { + log_e("GT911", "not found on 0x%02X or 0x%02X", GT911_ADDR_PRIMARY, GT911_ADDR_ALT); + return; + } + gt911_ok = true; + gt911_write(0x814E, 0x00); // clear any stale buffer-ready flag + log_i("GT911", "init OK (addr=0x%02X)", gt911_addr); +} + +void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) { + if (!gt911_ok) { *pressed = false; return; } + + uint8_t status; + if (!gt911_read(0x814E, &status, 1)) { + *x = touch_x; *y = touch_y; *pressed = touch_pressed; + return; + } + + if (status & 0x80) { // buffer_ready set: fresh data available + uint8_t count = status & 0x0F; + if (count > 0 && count <= 5) { + uint8_t buf[8]; + if (gt911_read(0x8150, buf, 8)) { + touch_x = (uint16_t)buf[1] | ((uint16_t)buf[2] << 8); + touch_y = (uint16_t)buf[3] | ((uint16_t)buf[4] << 8); + touch_pressed = true; + } + } else { + touch_pressed = false; + } + gt911_write(0x814E, 0x00); // release the buffer for next sample + } + + *x = touch_x; + *y = touch_y; + *pressed = touch_pressed; +} diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 1207553b5..4970265ea 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -358,6 +358,7 @@ void loop() { check_serial_cmd(); if (ble_has_data()) { + bool first = !usage.valid; if (parse_json(ble_get_data(), &usage)) { int g_before = usage_rate_group(); usage_rate_sample(usage.session_pct); @@ -367,6 +368,8 @@ void loop() { g_before, g_after, usage.session_pct); if (splash_is_active()) splash_pick_for_current_rate(); } + if (first && ui_get_current_screen() == SCREEN_SPLASH) + ui_show_screen(SCREEN_USAGE); ui_update(&usage); ble_send_ack(); } else {