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>
270 lines
12 KiB
C
270 lines
12 KiB
C
/* 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);
|
||
}
|
||
}
|