sl-firmware 98494a98c7 fix: add missing gc9a01 display driver to main build (bd-1yr8)
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 <noreply@anthropic.com>
2026-04-20 15:40:46 -04:00

270 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 <string.h>
#include <math.h>
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);
}
}