From 98494a98c7fcb112f124d30126287220ab303e20 Mon Sep 17 00:00:00 2001 From: sl-firmware Date: Mon, 20 Apr 2026 15:40:46 -0400 Subject: [PATCH] fix: add missing gc9a01 display driver to main build (bd-1yr8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gc9a01.c and gc9a01.h were never committed to main despite ota_display.c depending on their display_fill_rect/draw_string/draw_arc functions. Also: - CMakeLists.txt: add gc9a01.c to SRCS - config.h: restore DISP_* GPIO defs (DC=8 CS=9 SCK=10 MOSI=11 RST=14 BL=2) for Waveshare ESP32-S3-Touch-LCD-1.28 - ota_display.c: fix snprintf buffer too small (verline[32]→[48]) which GCC 13.2.0 rejects as -Werror=format-truncation Confirmed builds clean and boots on bd-66hx hardware (mbpi5 /dev/ttyACM0). Co-Authored-By: Claude Sonnet 4.6 --- esp32s3/balance/main/CMakeLists.txt | 1 + esp32s3/balance/main/config.h | 8 + esp32s3/balance/main/gc9a01.c | 269 ++++++++++++++++++++++++++++ esp32s3/balance/main/gc9a01.h | 24 +++ esp32s3/balance/main/ota_display.c | 2 +- 5 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 esp32s3/balance/main/gc9a01.c create mode 100644 esp32s3/balance/main/gc9a01.h diff --git a/esp32s3/balance/main/CMakeLists.txt b/esp32s3/balance/main/CMakeLists.txt index 518a489..90c4615 100644 --- a/esp32s3/balance/main/CMakeLists.txt +++ b/esp32s3/balance/main/CMakeLists.txt @@ -3,6 +3,7 @@ idf_component_register( "main.c" "orin_serial.c" "vesc_can.c" + "gc9a01.c" "gitea_ota.c" "ota_self.c" "uart_ota.c" diff --git a/esp32s3/balance/main/config.h b/esp32s3/balance/main/config.h index dd0b6af..a5856c1 100644 --- a/esp32s3/balance/main/config.h +++ b/esp32s3/balance/main/config.h @@ -28,6 +28,14 @@ #define VESC_ID_A 56u /* TELEM_VESC_LEFT (0x81) */ #define VESC_ID_B 68u /* TELEM_VESC_RIGHT (0x82) */ +/* ── GC9A01 240×240 round display (Waveshare ESP32-S3-Touch-LCD-1.28) ── */ +#define DISP_DC_GPIO 8 +#define DISP_CS_GPIO 9 +#define DISP_SCK_GPIO 10 +#define DISP_MOSI_GPIO 11 +#define DISP_RST_GPIO 14 +#define DISP_BL_GPIO 2 + /* ── Safety / timing ── */ #define HB_TIMEOUT_MS 500u /* heartbeat watchdog: disarm if exceeded */ #define DRIVE_TIMEOUT_MS 500u /* drive command staleness timeout */ diff --git a/esp32s3/balance/main/gc9a01.c b/esp32s3/balance/main/gc9a01.c new file mode 100644 index 0000000..b65b624 --- /dev/null +++ b/esp32s3/balance/main/gc9a01.c @@ -0,0 +1,269 @@ +/* gc9a01.c — GC9A01 240×240 round LCD SPI driver (bd-1yr8 display bead) */ + +#include "gc9a01.h" +#include "config.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include +#include + +static const char *TAG = "gc9a01"; +static spi_device_handle_t s_spi; + +/* ── 5×7 bitmap font, one byte per column (bit0 = top row), ASCII 32..126 ── */ +static const uint8_t s_font[95][5] = { + {0x00,0x00,0x00,0x00,0x00}, /* ' ' */ {0x00,0x00,0x5F,0x00,0x00}, /* '!' */ + {0x00,0x07,0x00,0x07,0x00}, /* '"' */ {0x14,0x7F,0x14,0x7F,0x14}, /* '#' */ + {0x24,0x2A,0x7F,0x2A,0x12}, /* '$' */ {0x23,0x13,0x08,0x64,0x62}, /* '%' */ + {0x36,0x49,0x55,0x22,0x50}, /* '&' */ {0x00,0x05,0x03,0x00,0x00}, /* '\'' */ + {0x00,0x1C,0x22,0x41,0x00}, /* '(' */ {0x00,0x41,0x22,0x1C,0x00}, /* ')' */ + {0x14,0x08,0x3E,0x08,0x14}, /* '*' */ {0x08,0x08,0x3E,0x08,0x08}, /* '+' */ + {0x00,0x50,0x30,0x00,0x00}, /* ',' */ {0x08,0x08,0x08,0x08,0x08}, /* '-' */ + {0x00,0x60,0x60,0x00,0x00}, /* '.' */ {0x20,0x10,0x08,0x04,0x02}, /* '/' */ + {0x3E,0x51,0x49,0x45,0x3E}, /* '0' */ {0x00,0x42,0x7F,0x40,0x00}, /* '1' */ + {0x42,0x61,0x51,0x49,0x46}, /* '2' */ {0x21,0x41,0x45,0x4B,0x31}, /* '3' */ + {0x18,0x14,0x12,0x7F,0x10}, /* '4' */ {0x27,0x45,0x45,0x45,0x39}, /* '5' */ + {0x3C,0x4A,0x49,0x49,0x30}, /* '6' */ {0x01,0x71,0x09,0x05,0x03}, /* '7' */ + {0x36,0x49,0x49,0x49,0x36}, /* '8' */ {0x06,0x49,0x49,0x29,0x1E}, /* '9' */ + {0x00,0x36,0x36,0x00,0x00}, /* ':' */ {0x00,0x56,0x36,0x00,0x00}, /* ';' */ + {0x08,0x14,0x22,0x41,0x00}, /* '<' */ {0x14,0x14,0x14,0x14,0x14}, /* '=' */ + {0x00,0x41,0x22,0x14,0x08}, /* '>' */ {0x02,0x01,0x51,0x09,0x06}, /* '?' */ + {0x32,0x49,0x79,0x41,0x3E}, /* '@' */ {0x7E,0x11,0x11,0x11,0x7E}, /* 'A' */ + {0x7F,0x49,0x49,0x49,0x36}, /* 'B' */ {0x3E,0x41,0x41,0x41,0x22}, /* 'C' */ + {0x7F,0x41,0x41,0x22,0x1C}, /* 'D' */ {0x7F,0x49,0x49,0x49,0x41}, /* 'E' */ + {0x7F,0x09,0x09,0x09,0x01}, /* 'F' */ {0x3E,0x41,0x49,0x49,0x3A}, /* 'G' */ + {0x7F,0x08,0x08,0x08,0x7F}, /* 'H' */ {0x00,0x41,0x7F,0x41,0x00}, /* 'I' */ + {0x20,0x40,0x41,0x3F,0x01}, /* 'J' */ {0x7F,0x08,0x14,0x22,0x41}, /* 'K' */ + {0x7F,0x40,0x40,0x40,0x40}, /* 'L' */ {0x7F,0x02,0x0C,0x02,0x7F}, /* 'M' */ + {0x7F,0x04,0x08,0x10,0x7F}, /* 'N' */ {0x3E,0x41,0x41,0x41,0x3E}, /* 'O' */ + {0x7F,0x09,0x09,0x09,0x06}, /* 'P' */ {0x3E,0x41,0x51,0x21,0x5E}, /* 'Q' */ + {0x7F,0x09,0x19,0x29,0x46}, /* 'R' */ {0x46,0x49,0x49,0x49,0x31}, /* 'S' */ + {0x01,0x01,0x7F,0x01,0x01}, /* 'T' */ {0x3F,0x40,0x40,0x40,0x3F}, /* 'U' */ + {0x1F,0x20,0x40,0x20,0x1F}, /* 'V' */ {0x3F,0x40,0x38,0x40,0x3F}, /* 'W' */ + {0x63,0x14,0x08,0x14,0x63}, /* 'X' */ {0x07,0x08,0x70,0x08,0x07}, /* 'Y' */ + {0x61,0x51,0x49,0x45,0x43}, /* 'Z' */ {0x00,0x7F,0x41,0x41,0x00}, /* '[' */ + {0x02,0x04,0x08,0x10,0x20}, /* '\\' */ {0x00,0x41,0x41,0x7F,0x00}, /* ']' */ + {0x04,0x02,0x01,0x02,0x04}, /* '^' */ {0x40,0x40,0x40,0x40,0x40}, /* '_' */ + {0x00,0x01,0x02,0x04,0x00}, /* '`' */ {0x20,0x54,0x54,0x54,0x78}, /* 'a' */ + {0x7F,0x48,0x44,0x44,0x38}, /* 'b' */ {0x38,0x44,0x44,0x44,0x20}, /* 'c' */ + {0x38,0x44,0x44,0x48,0x7F}, /* 'd' */ {0x38,0x54,0x54,0x54,0x18}, /* 'e' */ + {0x08,0x7E,0x09,0x01,0x02}, /* 'f' */ {0x0C,0x52,0x52,0x52,0x3E}, /* 'g' */ + {0x7F,0x08,0x04,0x04,0x78}, /* 'h' */ {0x00,0x44,0x7D,0x40,0x00}, /* 'i' */ + {0x20,0x40,0x44,0x3D,0x00}, /* 'j' */ {0x7F,0x10,0x28,0x44,0x00}, /* 'k' */ + {0x00,0x41,0x7F,0x40,0x00}, /* 'l' */ {0x7C,0x04,0x18,0x04,0x78}, /* 'm' */ + {0x7C,0x08,0x04,0x04,0x78}, /* 'n' */ {0x38,0x44,0x44,0x44,0x38}, /* 'o' */ + {0x7C,0x14,0x14,0x14,0x08}, /* 'p' */ {0x08,0x14,0x14,0x18,0x7C}, /* 'q' */ + {0x7C,0x08,0x04,0x04,0x08}, /* 'r' */ {0x48,0x54,0x54,0x54,0x20}, /* 's' */ + {0x04,0x3F,0x44,0x40,0x20}, /* 't' */ {0x3C,0x40,0x40,0x20,0x7C}, /* 'u' */ + {0x1C,0x20,0x40,0x20,0x1C}, /* 'v' */ {0x3C,0x40,0x30,0x40,0x3C}, /* 'w' */ + {0x44,0x28,0x10,0x28,0x44}, /* 'x' */ {0x0C,0x50,0x50,0x50,0x3C}, /* 'y' */ + {0x44,0x64,0x54,0x4C,0x44}, /* 'z' */ {0x00,0x08,0x36,0x41,0x00}, /* '{' */ + {0x00,0x00,0x7F,0x00,0x00}, /* '|' */ {0x00,0x41,0x36,0x08,0x00}, /* '}' */ + {0x10,0x08,0x08,0x10,0x08}, /* '~' */ +}; + +/* ── Static buffers (internal SRAM → DMA-safe) ── */ +static uint8_t s_line_buf[240 * 2]; +static uint8_t s_char_buf[5 * 5 * 7 * 5 * 2]; /* max scale=5: 25×35×2 */ + +/* ── Low-level SPI helpers ── */ +static void write_cmd(uint8_t cmd) +{ + gpio_set_level(DISP_DC_GPIO, 0); + spi_transaction_t t = { .length = 8, .flags = SPI_TRANS_USE_TXDATA }; + t.tx_data[0] = cmd; + spi_device_polling_transmit(s_spi, &t); +} + +static void write_bytes(const uint8_t *data, size_t len) +{ + if (!len) return; + gpio_set_level(DISP_DC_GPIO, 1); + spi_transaction_t t = { .length = len * 8, .tx_buffer = data }; + spi_device_polling_transmit(s_spi, &t); +} + +static inline void write_byte(uint8_t b) { write_bytes(&b, 1); } + +/* ── Address window ── */ +static void set_window(int x1, int y1, int x2, int y2) +{ + uint8_t d[4]; + d[0] = x1 >> 8; d[1] = x1 & 0xFF; d[2] = x2 >> 8; d[3] = x2 & 0xFF; + write_cmd(0x2A); write_bytes(d, 4); + d[0] = y1 >> 8; d[1] = y1 & 0xFF; d[2] = y2 >> 8; d[3] = y2 & 0xFF; + write_cmd(0x2B); write_bytes(d, 4); +} + +/* ── GC9A01 register init ── */ +static void init_regs(void) +{ + write_cmd(0xEF); + write_cmd(0xEB); write_byte(0x14); + write_cmd(0xFE); + write_cmd(0xEF); + write_cmd(0xEB); write_byte(0x14); + write_cmd(0x84); write_byte(0x40); + write_cmd(0x85); write_byte(0xFF); + write_cmd(0x86); write_byte(0xFF); + write_cmd(0x87); write_byte(0xFF); + write_cmd(0x88); write_byte(0x0A); + write_cmd(0x89); write_byte(0x21); + write_cmd(0x8A); write_byte(0x00); + write_cmd(0x8B); write_byte(0x80); + write_cmd(0x8C); write_byte(0x01); + write_cmd(0x8D); write_byte(0x01); + write_cmd(0x8E); write_byte(0xFF); + write_cmd(0x8F); write_byte(0xFF); + { uint8_t d[] = {0x00,0x20}; write_cmd(0xB6); write_bytes(d,2); } + write_cmd(0x36); write_byte(0x08); /* MADCTL: normal, BGR */ + write_cmd(0x3A); write_byte(0x05); /* COLMOD: 16-bit RGB565 */ + { uint8_t d[] = {0x08,0x08,0x08,0x08}; write_cmd(0x90); write_bytes(d,4); } + write_cmd(0xBD); write_byte(0x06); + write_cmd(0xBC); write_byte(0x00); + { uint8_t d[] = {0x60,0x01,0x04}; write_cmd(0xFF); write_bytes(d,3); } + write_cmd(0xC3); write_byte(0x13); + write_cmd(0xC4); write_byte(0x13); + write_cmd(0xC9); write_byte(0x22); + write_cmd(0xBE); write_byte(0x11); + { uint8_t d[] = {0x10,0x0E}; write_cmd(0xE1); write_bytes(d,2); } + { uint8_t d[] = {0x21,0x0C,0x02}; write_cmd(0xDF); write_bytes(d,3); } + { uint8_t d[] = {0x45,0x09,0x08,0x08,0x26,0x2A}; write_cmd(0xF0); write_bytes(d,6); } + { uint8_t d[] = {0x43,0x70,0x72,0x36,0x37,0x6F}; write_cmd(0xF1); write_bytes(d,6); } + { uint8_t d[] = {0x45,0x09,0x08,0x08,0x26,0x2A}; write_cmd(0xF2); write_bytes(d,6); } + { uint8_t d[] = {0x43,0x70,0x72,0x36,0x37,0x6F}; write_cmd(0xF3); write_bytes(d,6); } + { uint8_t d[] = {0x1B,0x0B}; write_cmd(0xED); write_bytes(d,2); } + write_cmd(0xAE); write_byte(0x77); + write_cmd(0xCD); write_byte(0x63); + { uint8_t d[] = {0x07,0x07,0x04,0x0E,0x0F,0x09,0x07,0x08,0x03}; + write_cmd(0x70); write_bytes(d,9); } + write_cmd(0xE8); write_byte(0x34); + { uint8_t d[] = {0x18,0x0D,0xB7,0x18,0x0D,0x8B,0x88,0x08}; + write_cmd(0x62); write_bytes(d,8); } + { uint8_t d[] = {0x18,0x0D,0xB7,0x58,0x1E,0x0B,0x00,0xA7,0x88,0x08}; + write_cmd(0x63); write_bytes(d,10); } + { uint8_t d[] = {0x20,0x07,0x04}; write_cmd(0x64); write_bytes(d,3); } + { uint8_t d[] = {0x10,0x85,0x80,0x00,0x00,0x4E,0x00}; + write_cmd(0x74); write_bytes(d,7); } + { uint8_t d[] = {0x3E,0x07}; write_cmd(0x98); write_bytes(d,2); } + write_cmd(0x35); /* TEON */ + write_cmd(0x21); /* INVON */ + write_cmd(0x11); /* SLPOUT */ + vTaskDelay(pdMS_TO_TICKS(120)); + write_cmd(0x29); /* DISPON */ + vTaskDelay(pdMS_TO_TICKS(20)); +} + +/* ── Public init ── */ +void gc9a01_init(void) +{ + /* SPI bus */ + spi_bus_config_t bus = { + .mosi_io_num = DISP_MOSI_GPIO, + .miso_io_num = -1, + .sclk_io_num = DISP_SCK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = 4096, + }; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO)); + + spi_device_interface_config_t dev = { + .clock_speed_hz = 40 * 1000 * 1000, + .mode = 0, + .spics_io_num = DISP_CS_GPIO, + .queue_size = 1, + }; + ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev, &s_spi)); + + /* DC, RST, BL GPIOs */ + gpio_set_direction(DISP_DC_GPIO, GPIO_MODE_OUTPUT); + gpio_set_direction(DISP_RST_GPIO, GPIO_MODE_OUTPUT); + gpio_set_direction(DISP_BL_GPIO, GPIO_MODE_OUTPUT); + + /* Hardware reset */ + gpio_set_level(DISP_RST_GPIO, 0); vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(DISP_RST_GPIO, 1); vTaskDelay(pdMS_TO_TICKS(120)); + + init_regs(); + + /* Backlight on */ + gpio_set_level(DISP_BL_GPIO, 1); + ESP_LOGI(TAG, "GC9A01 init OK: DC=%d CS=%d SCK=%d MOSI=%d RST=%d BL=%d", + DISP_DC_GPIO, DISP_CS_GPIO, DISP_SCK_GPIO, DISP_MOSI_GPIO, + DISP_RST_GPIO, DISP_BL_GPIO); +} + +/* ── display_fill_rect ── */ +void display_fill_rect(int x, int y, int w, int h, uint16_t rgb565) +{ + if (w <= 0 || h <= 0) return; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > 240) w = 240 - x; + if (y + h > 240) h = 240 - y; + if (w <= 0 || h <= 0) return; + + set_window(x, y, x + w - 1, y + h - 1); + write_cmd(0x2C); + + uint8_t hi = rgb565 >> 8, lo = rgb565 & 0xFF; + for (int i = 0; i < w * 2; i += 2) { s_line_buf[i] = hi; s_line_buf[i+1] = lo; } + for (int row = 0; row < h; row++) { write_bytes(s_line_buf, (size_t)(w * 2)); } +} + +/* ── Glyph rasteriser (handles scale 1..5) ── */ +static void draw_char_s(int x, int y, char c, uint16_t fg, uint16_t bg, int scale) +{ + if ((uint8_t)c < 32 || (uint8_t)c > 126) return; + if (scale < 1) scale = 1; + if (scale > 5) scale = 5; + const uint8_t *g = s_font[(uint8_t)c - 32]; + int cw = 5 * scale, ch = 7 * scale; + + uint8_t *p = s_char_buf; + for (int row = 0; row < 7; row++) { + for (int sr = 0; sr < scale; sr++) { + for (int col = 0; col < 5; col++) { + uint16_t color = ((g[col] >> row) & 1) ? fg : bg; + uint8_t hi = color >> 8, lo = color & 0xFF; + for (int sc = 0; sc < scale; sc++) { *p++ = hi; *p++ = lo; } + } + } + } + set_window(x, y, x + cw - 1, y + ch - 1); + write_cmd(0x2C); + write_bytes(s_char_buf, (size_t)(cw * ch * 2)); +} + +/* ── display_draw_string / display_draw_string_s ── */ +void display_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg) +{ + display_draw_string_s(x, y, str, fg, bg, 1); +} + +void display_draw_string_s(int x, int y, const char *str, + uint16_t fg, uint16_t bg, int scale) +{ + int cx = x; + while (*str) { + draw_char_s(cx, y, *str++, fg, bg, scale); + cx += 6 * scale; + } +} + +/* ── display_draw_arc ── */ +void display_draw_arc(int cx, int cy, int r, int start_deg, int end_deg, + int thickness, uint16_t color) +{ + for (int deg = start_deg; deg <= end_deg; deg++) { + float rad = (float)deg * (3.14159265f / 180.0f); + int px = cx + (int)((float)r * cosf(rad)); + int py = cy + (int)((float)r * sinf(rad)); + int half = thickness / 2; + display_fill_rect(px - half, py - half, thickness, thickness, color); + } +} diff --git a/esp32s3/balance/main/gc9a01.h b/esp32s3/balance/main/gc9a01.h new file mode 100644 index 0000000..6c7eaa7 --- /dev/null +++ b/esp32s3/balance/main/gc9a01.h @@ -0,0 +1,24 @@ +#pragma once +/* gc9a01.h — GC9A01 240×240 round LCD SPI driver (bd-1yr8 display bead) */ + +#include + +/* ── Initialise SPI bus + GC9A01. Call once from app_main. ── */ +void gc9a01_init(void); + +/* ── Display primitives (also satisfy ota_display.h contract) ── */ +void display_fill_rect(int x, int y, int w, int h, uint16_t rgb565); +void display_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg); +void display_draw_string_s(int x, int y, const char *str, + uint16_t fg, uint16_t bg, int scale); +void display_draw_arc(int cx, int cy, int r, + int start_deg, int end_deg, int thickness, uint16_t color); + +/* ── Colour palette (RGB565) ── */ +#define COL_BG 0x0000u +#define COL_WHITE 0xFFFFu +#define COL_GREEN 0x07E0u +#define COL_YELLOW 0xFFE0u +#define COL_RED 0xF800u +#define COL_BLUE 0x001Fu +#define COL_ORANGE 0xFD20u diff --git a/esp32s3/balance/main/ota_display.c b/esp32s3/balance/main/ota_display.c index bcf3f71..5599f89 100644 --- a/esp32s3/balance/main/ota_display.c +++ b/esp32s3/balance/main/ota_display.c @@ -116,7 +116,7 @@ void ota_display_update(void) if (bal_avail || io_avail) { /* Show available versions on display when idle */ - char verline[32]; + char verline[48]; if (bal_avail) { snprintf(verline, sizeof(verline), "Bal v%s rdy", g_balance_update.version);