From c7798c02dbc228cc0d120d202929a32a03b012d2 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Sun, 23 Aug 2026 01:15:11 +0300 Subject: [PATCH] =?UTF-8?q?feat(st7789):=20=D0=B4=D1=80=D0=B0=D0=B9=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=20TFT-=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=20ST7789V=20=D0=BF=D0=BE=D0=B2=D0=B5=D1=80=D1=85=20SPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Перенесён из KONOR_ds18b20/lib/st7789; в OpticalTester лежала побайтово такая же копия. Ядро не включает заголовки МК и не заводит кадрового буфера: примитивы пишут пиксели потоком, поэтому драйвер работает на МК с единицами килобайт ОЗУ. Платформа подключается через ST7789_Io: обязательны write, set_command и delay_ms, остальное — по разводке платы. --- c/st7789/README.md | 65 +++++ c/st7789/st7789.c | 624 +++++++++++++++++++++++++++++++++++++++++ c/st7789/st7789.h | 280 ++++++++++++++++++ c/st7789/st7789_font.c | 110 ++++++++ c/st7789/st7789_font.h | 27 ++ 5 files changed, 1106 insertions(+) create mode 100644 c/st7789/README.md create mode 100644 c/st7789/st7789.c create mode 100644 c/st7789/st7789.h create mode 100644 c/st7789/st7789_font.c create mode 100644 c/st7789/st7789_font.h diff --git a/c/st7789/README.md b/c/st7789/README.md new file mode 100644 index 0000000..6d0ed0d --- /dev/null +++ b/c/st7789/README.md @@ -0,0 +1,65 @@ +# st7789 + +Драйвер TFT-панелей на контроллере **ST7789V** (SPI, цвет RGB565). + +Ядро на C99: не включает заголовки микроконтроллера, не трогает регистры, +не заводит кадровый буфер. Примитивы пишут пиксели в контроллер потоком, +поэтому драйвер живёт на МК с единицами килобайт ОЗУ. + +``` + приложение + │ + st7789.c окно вывода, примитивы, текст + st7789_font.c шрифт 6x8, масштаб 1..4 + │ + ST7789_Io write, set_command, set_select, + set_reset, set_backlight, delay_ms + │ + порт платы SPI + GPIO +``` + +## Состав + +| Файл | Что делает | Зависимости | +|---|---|---| +| `st7789.h`, `st7789.c` | инициализация панели, поворот, заливка, пиксели, линии, текст | `stdint.h` | +| `st7789_font.h`, `st7789_font.c` | растровый шрифт 6x8 с масштабированием, символ градуса `0x7F` | `stdint.h` | + +## Что нужно от платформы + +Заполнить `ST7789_Io`. Обязательны три вызова: + +```c +void write(void *ctx, const uint8_t *data, uint32_t size); /* SPI, MSB первым */ +void set_command(void *ctx, uint8_t is_command); /* линия DC */ +void delay_ms(void *ctx, uint32_t ms); +``` + +Необязательные: `set_select` (без него CS считается посаженным на землю), +`set_reset` (без него выполняется программный SWRESET), `set_backlight`. + +## Быстрый старт + +```c +ST7789_Display display; +ST7789_Config config; + +ST7789_ConfigDefault(&config, 240, 320); /* геометрия конкретного модуля */ +config.rotation = ST7789_ROTATION_90; + +ST7789_Io io = { .write = spi_write, .set_command = dc_set, + .delay_ms = board_delay, .context = &board }; + +ST7789_Init(&display, &io, &config); +ST7789_FillScreen(&display, ST7789_BLACK); +ST7789_DrawText(&display, 4, 4, "SET", ST7789_WHITE, ST7789_BLACK, 2); +``` + +Геометрия зависит от модуля: у панелей 240x240 и 240x320 смещений нет, +у 135x240 видимая область сдвинута — значения берутся из документации модуля +и задаются полями `offset_x` / `offset_y`. + +## Проверено в проектах + +`KONOR_ds18b20`, `OpticalTester` — обе платы на STM32F103C8T6, порт занимает +около сорока строк поверх SPI1 и трёх выводов GPIO. diff --git a/c/st7789/st7789.c b/c/st7789/st7789.c new file mode 100644 index 0000000..0fcb65f --- /dev/null +++ b/c/st7789/st7789.c @@ -0,0 +1,624 @@ +/** + * @file st7789.c + * @brief Реализация драйвера ST7789V: инициализация панели и графика. + * + * Кадрового буфера нет: заливки и текст выводятся потоком через окно RAMWR, + * а посредником служит буфер на ST7789_CHUNK_PIXELS пикселей. Обращений к + * регистрам микроконтроллера в файле нет, поэтому он переносится между + * платформами без правок — меняется только порт, заполняющий ST7789_Io. + */ + +#include "st7789.h" + +#include "st7789_font.h" + +#define ST_CMD_SWRESET 0x01U /**< Программный сброс контроллера. */ +#define ST_CMD_SLPIN 0x10U /**< Переход в спящий режим. */ +#define ST_CMD_SLPOUT 0x11U /**< Выход из спящего режима. */ +#define ST_CMD_NORON 0x13U /**< Обычный режим вывода. */ +#define ST_CMD_INVOFF 0x20U /**< Отключение инверсии. */ +#define ST_CMD_INVON 0x21U /**< Включение инверсии. */ +#define ST_CMD_DISPON 0x29U /**< Включение изображения. */ +#define ST_CMD_CASET 0x2AU /**< Окно по столбцам. */ +#define ST_CMD_RASET 0x2BU /**< Окно по строкам. */ +#define ST_CMD_RAMWR 0x2CU /**< Запись в память изображения. */ +#define ST_CMD_MADCTL 0x36U /**< Порядок доступа к памяти и поворот. */ +#define ST_CMD_COLMOD 0x3AU /**< Формат пикселя. */ + +#define ST_MADCTL_MY 0x80U /**< Отражение по строкам. */ +#define ST_MADCTL_MX 0x40U /**< Отражение по столбцам. */ +#define ST_MADCTL_MV 0x20U /**< Обмен строк и столбцов. */ +#define ST_MADCTL_BGR 0x08U /**< Порядок субпикселей BGR. */ + +/** Растр контроллера ST7789V, в который вписана видимая область панели. */ +#define ST_PANEL_WIDTH 240U +#define ST_PANEL_HEIGHT 320U + +#if ST7789_CHUNK_PIXELS < (ST7789_FONT_WIDTH * ST7789_FONT_MAX_SCALE) +#error "ST7789_CHUNK_PIXELS слишком мал для строки знакоместа шрифта" +#endif + +/** Буфер потоковой передачи пикселей; драйвер блокирующий, поэтому общий. */ +static uint8_t g_chunk[ST7789_CHUNK_PIXELS * 2U]; + +/** + * @brief Открывает транзакцию SPI, если вывод CS разведён. + * + * @param display Дисплей с заполненной таблицей вызовов. + */ +static void st_select(ST7789_Display *display) +{ + if (display->io.set_select != 0) { + display->io.set_select(display->io.context, 1U); + } +} + +/** + * @brief Закрывает транзакцию SPI. + * + * @param display Дисплей с заполненной таблицей вызовов. + */ +static void st_deselect(ST7789_Display *display) +{ + if (display->io.set_select != 0) { + display->io.set_select(display->io.context, 0U); + } +} + +/** + * @brief Передаёт код команды с активной линией DC. + * + * @param display Дисплей. + * @param command Код команды контроллера. + */ +static void st_write_command(ST7789_Display *display, uint8_t command) +{ + display->io.set_command(display->io.context, 1U); + display->io.write(display->io.context, &command, 1U); + display->io.set_command(display->io.context, 0U); +} + +/** + * @brief Передаёт блок данных команды. + * + * @param display Дисплей. + * @param data Передаваемые байты. + * @param size Количество байтов; ноль допустим. + */ +static void st_write_data(ST7789_Display *display, const uint8_t *data, uint32_t size) +{ + if (size != 0U) { + display->io.write(display->io.context, data, size); + } +} + +/** + * @brief Собирает байт MADCTL для текущего поворота. + * + * @param display Дисплей с заполненной конфигурацией. + * @return Значение регистра MADCTL. + */ +static uint8_t st_madctl(const ST7789_Display *display) +{ + uint8_t value; + + switch (display->config.rotation) { + case ST7789_ROTATION_90: + value = (uint8_t)(ST_MADCTL_MV | ST_MADCTL_MY); + break; + case ST7789_ROTATION_180: + value = (uint8_t)(ST_MADCTL_MX | ST_MADCTL_MY); + break; + case ST7789_ROTATION_270: + value = (uint8_t)(ST_MADCTL_MV | ST_MADCTL_MX); + break; + case ST7789_ROTATION_0: + default: + value = 0U; + break; + } + if (display->config.bgr != 0U) { + value = (uint8_t)(value | ST_MADCTL_BGR); + } + return value; +} + +/** + * @brief Пересчитывает размеры изображения и смещения окна под поворот. + * + * При повороте на 90 и 270 градусов оси меняются местами, а смещение видимой + * области отсчитывается от противоположного края растра контроллера. + * + * @param display Дисплей с заполненной конфигурацией. + */ +static void st_apply_geometry(ST7789_Display *display) +{ + const uint16_t width = display->config.width; + const uint16_t height = display->config.height; + const uint16_t offset_x = display->config.offset_x; + const uint16_t offset_y = display->config.offset_y; + const uint16_t slack_x = (uint16_t)(ST_PANEL_WIDTH - (width + offset_x)); + const uint16_t slack_y = (uint16_t)(ST_PANEL_HEIGHT - (height + offset_y)); + + switch (display->config.rotation) { + case ST7789_ROTATION_90: + display->width = height; + display->height = width; + display->offset_x = offset_y; + display->offset_y = slack_x; + break; + case ST7789_ROTATION_180: + display->width = width; + display->height = height; + display->offset_x = slack_x; + display->offset_y = slack_y; + break; + case ST7789_ROTATION_270: + display->width = height; + display->height = width; + display->offset_x = slack_y; + display->offset_y = offset_x; + break; + case ST7789_ROTATION_0: + default: + display->width = width; + display->height = height; + display->offset_x = offset_x; + display->offset_y = offset_y; + break; + } +} + +/** + * @brief Задаёт окно вывода и оставляет контроллер в режиме записи RAMWR. + * + * @param display Дисплей. + * @param x Левая граница окна в координатах изображения. + * @param y Верхняя граница окна. + * @param width Ширина окна, больше нуля. + * @param height Высота окна, больше нуля. + */ +static void st_set_window(ST7789_Display *display, uint16_t x, uint16_t y, + uint16_t width, uint16_t height) +{ + const uint16_t x0 = (uint16_t)(x + display->offset_x); + const uint16_t y0 = (uint16_t)(y + display->offset_y); + const uint16_t x1 = (uint16_t)(x0 + width - 1U); + const uint16_t y1 = (uint16_t)(y0 + height - 1U); + uint8_t bounds[4]; + + bounds[0] = (uint8_t)(x0 >> 8U); + bounds[1] = (uint8_t)(x0 & 0xFFU); + bounds[2] = (uint8_t)(x1 >> 8U); + bounds[3] = (uint8_t)(x1 & 0xFFU); + st_write_command(display, ST_CMD_CASET); + st_write_data(display, bounds, sizeof(bounds)); + + bounds[0] = (uint8_t)(y0 >> 8U); + bounds[1] = (uint8_t)(y0 & 0xFFU); + bounds[2] = (uint8_t)(y1 >> 8U); + bounds[3] = (uint8_t)(y1 & 0xFFU); + st_write_command(display, ST_CMD_RASET); + st_write_data(display, bounds, sizeof(bounds)); + + st_write_command(display, ST_CMD_RAMWR); +} + +/** + * @brief Передаёт в открытое окно заданное число пикселей одного цвета. + * + * @param display Дисплей. + * @param color Цвет RGB565. + * @param count Количество пикселей. + */ +static void st_push_color(ST7789_Display *display, uint16_t color, uint32_t count) +{ + const uint8_t high = (uint8_t)(color >> 8U); + const uint8_t low = (uint8_t)(color & 0xFFU); + uint32_t filled = 0U; + uint32_t index; + + while (filled < (uint32_t)ST7789_CHUNK_PIXELS) { + g_chunk[filled * 2U] = high; + g_chunk[(filled * 2U) + 1U] = low; + filled++; + } + while (count != 0U) { + index = (count > (uint32_t)ST7789_CHUNK_PIXELS) ? (uint32_t)ST7789_CHUNK_PIXELS : count; + display->io.write(display->io.context, g_chunk, index * 2U); + count -= index; + } +} + +/** + * @brief Обрезает прямоугольник по границам экрана. + * + * @param display Дисплей. + * @param x Левая граница; корректируется по месту. + * @param y Верхняя граница; корректируется по месту. + * @param width Ширина; корректируется по месту. + * @param height Высота; корректируется по месту. + * @return 1, если после обрезки область видима, иначе 0. + */ +static uint8_t st_clip(const ST7789_Display *display, int16_t *x, int16_t *y, + uint16_t *width, uint16_t *height) +{ + int32_t left = *x; + int32_t top = *y; + int32_t right = left + (int32_t)*width; + int32_t bottom = top + (int32_t)*height; + + if ((*width == 0U) || (*height == 0U)) { + return 0U; + } + if (left < 0) { + left = 0; + } + if (top < 0) { + top = 0; + } + if (right > (int32_t)display->width) { + right = (int32_t)display->width; + } + if (bottom > (int32_t)display->height) { + bottom = (int32_t)display->height; + } + if ((right <= left) || (bottom <= top)) { + return 0U; + } + *x = (int16_t)left; + *y = (int16_t)top; + *width = (uint16_t)(right - left); + *height = (uint16_t)(bottom - top); + return 1U; +} + +void ST7789_ConfigDefault(ST7789_Config *config, uint16_t width, uint16_t height) +{ + if (config == 0) { + return; + } + config->width = width; + config->height = height; + config->offset_x = 0U; + config->offset_y = 0U; + config->rotation = ST7789_ROTATION_0; + config->invert = 1U; + config->bgr = 0U; +} + +uint8_t ST7789_Init(ST7789_Display *display, const ST7789_Io *io, + const ST7789_Config *config) +{ + /* Порядок команд соответствует рекомендациям Sitronix для ST7789V. */ + static const uint8_t init_porch[] = { 0x0CU, 0x0CU, 0x00U, 0x33U, 0x33U }; + static const uint8_t init_gamma_p[] = { + 0xD0U, 0x04U, 0x0DU, 0x11U, 0x13U, 0x2BU, 0x3FU, + 0x54U, 0x4CU, 0x18U, 0x0DU, 0x0BU, 0x1FU, 0x23U + }; + static const uint8_t init_gamma_n[] = { + 0xD0U, 0x04U, 0x0CU, 0x11U, 0x13U, 0x2CU, 0x3FU, + 0x44U, 0x51U, 0x2FU, 0x1FU, 0x1FU, 0x20U, 0x23U + }; + static const uint8_t init_power1[] = { 0xA4U, 0xA1U }; + uint8_t value; + + if ((display == 0) || (io == 0) || (config == 0)) { + return 0U; + } + if ((io->write == 0) || (io->set_command == 0) || (io->delay_ms == 0)) { + return 0U; + } + if ((config->width == 0U) || (config->height == 0U) + || ((uint32_t)config->width + config->offset_x > ST_PANEL_WIDTH) + || ((uint32_t)config->height + config->offset_y > ST_PANEL_HEIGHT)) { + return 0U; + } + + display->io = *io; + display->config = *config; + display->ready = 0U; + st_apply_geometry(display); + + if (display->io.set_reset != 0) { + display->io.set_reset(display->io.context, 1U); + display->io.delay_ms(display->io.context, 10U); + display->io.set_reset(display->io.context, 0U); + display->io.delay_ms(display->io.context, 120U); + } + + st_select(display); + if (display->io.set_reset == 0) { + st_write_command(display, ST_CMD_SWRESET); + display->io.delay_ms(display->io.context, 120U); + } + st_write_command(display, ST_CMD_SLPOUT); + display->io.delay_ms(display->io.context, 120U); + + value = 0x55U; /* 16 бит на пиксель для интерфейса и памяти. */ + st_write_command(display, ST_CMD_COLMOD); + st_write_data(display, &value, 1U); + + value = st_madctl(display); + st_write_command(display, ST_CMD_MADCTL); + st_write_data(display, &value, 1U); + + st_write_command(display, 0xB2U); /* PORCTRL: длительности кадровых интервалов. */ + st_write_data(display, init_porch, sizeof(init_porch)); + + value = 0x35U; /* GCTRL: уровни VGH 13.26 В и VGL -10.43 В. */ + st_write_command(display, 0xB7U); + st_write_data(display, &value, 1U); + + value = 0x19U; /* VCOMS: 0.725 В. */ + st_write_command(display, 0xBBU); + st_write_data(display, &value, 1U); + + value = 0x2CU; /* LCMCTRL: типовое управление ЖК. */ + st_write_command(display, 0xC0U); + st_write_data(display, &value, 1U); + + value = 0x01U; /* VDVVRHEN: VDV и VRH задаются командами. */ + st_write_command(display, 0xC2U); + st_write_data(display, &value, 1U); + + value = 0x12U; /* VRHS: 4.45 В. */ + st_write_command(display, 0xC3U); + st_write_data(display, &value, 1U); + + value = 0x20U; /* VDVS: 0 В. */ + st_write_command(display, 0xC4U); + st_write_data(display, &value, 1U); + + value = 0x0FU; /* FRCTRL2: частота кадров 60 Гц. */ + st_write_command(display, 0xC6U); + st_write_data(display, &value, 1U); + + st_write_command(display, 0xD0U); /* PWCTRL1: источники AVDD и AVCL. */ + st_write_data(display, init_power1, sizeof(init_power1)); + + st_write_command(display, 0xE0U); /* PVGAMCTRL: положительная гамма. */ + st_write_data(display, init_gamma_p, sizeof(init_gamma_p)); + + st_write_command(display, 0xE1U); /* NVGAMCTRL: отрицательная гамма. */ + st_write_data(display, init_gamma_n, sizeof(init_gamma_n)); + + st_write_command(display, + (uint8_t)((display->config.invert != 0U) ? ST_CMD_INVON : ST_CMD_INVOFF)); + st_write_command(display, ST_CMD_NORON); + display->io.delay_ms(display->io.context, 10U); + st_write_command(display, ST_CMD_DISPON); + display->io.delay_ms(display->io.context, 20U); + st_deselect(display); + + display->ready = 1U; + ST7789_FillScreen(display, ST7789_BLACK); + ST7789_Backlight(display, 1U); + return 1U; +} + +void ST7789_SetRotation(ST7789_Display *display, ST7789_Rotation rotation) +{ + uint8_t value; + + if ((display == 0) || (display->ready == 0U)) { + return; + } + display->config.rotation = rotation; + st_apply_geometry(display); + value = st_madctl(display); + st_select(display); + st_write_command(display, ST_CMD_MADCTL); + st_write_data(display, &value, 1U); + st_deselect(display); +} + +void ST7789_Backlight(ST7789_Display *display, uint8_t on) +{ + if ((display == 0) || (display->io.set_backlight == 0)) { + return; + } + display->io.set_backlight(display->io.context, (uint8_t)((on != 0U) ? 1U : 0U)); +} + +void ST7789_Sleep(ST7789_Display *display, uint8_t sleep) +{ + if ((display == 0) || (display->ready == 0U)) { + return; + } + st_select(display); + st_write_command(display, (uint8_t)((sleep != 0U) ? ST_CMD_SLPIN : ST_CMD_SLPOUT)); + st_deselect(display); + display->io.delay_ms(display->io.context, 120U); +} + +void ST7789_FillScreen(ST7789_Display *display, uint16_t color) +{ + if ((display == 0) || (display->ready == 0U)) { + return; + } + ST7789_FillRect(display, 0, 0, display->width, display->height, color); +} + +void ST7789_FillRect(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, uint16_t color) +{ + if ((display == 0) || (display->ready == 0U)) { + return; + } + if (st_clip(display, &x, &y, &width, &height) == 0U) { + return; + } + st_select(display); + st_set_window(display, (uint16_t)x, (uint16_t)y, width, height); + st_push_color(display, color, (uint32_t)width * height); + st_deselect(display); +} + +void ST7789_DrawRect(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, uint16_t color) +{ + if ((display == 0) || (width == 0U) || (height == 0U)) { + return; + } + ST7789_FillRect(display, x, y, width, 1U, color); + ST7789_FillRect(display, x, (int16_t)(y + (int16_t)height - 1), width, 1U, color); + ST7789_FillRect(display, x, y, 1U, height, color); + ST7789_FillRect(display, (int16_t)(x + (int16_t)width - 1), y, 1U, height, color); +} + +void ST7789_DrawPixel(ST7789_Display *display, int16_t x, int16_t y, uint16_t color) +{ + ST7789_FillRect(display, x, y, 1U, 1U, color); +} + +void ST7789_DrawBitmap(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, const uint16_t *pixels) +{ + uint32_t total; + uint32_t sent = 0U; + uint32_t index; + + if ((display == 0) || (display->ready == 0U) || (pixels == 0)) { + return; + } + if ((width == 0U) || (height == 0U) || (x < 0) || (y < 0) + || (((int32_t)x + width) > (int32_t)display->width) + || (((int32_t)y + height) > (int32_t)display->height)) { + return; + } + + total = (uint32_t)width * height; + st_select(display); + st_set_window(display, (uint16_t)x, (uint16_t)y, width, height); + while (sent < total) { + uint32_t block = total - sent; + + if (block > (uint32_t)ST7789_CHUNK_PIXELS) { + block = (uint32_t)ST7789_CHUNK_PIXELS; + } + for (index = 0U; index < block; index++) { + g_chunk[index * 2U] = (uint8_t)(pixels[sent + index] >> 8U); + g_chunk[(index * 2U) + 1U] = (uint8_t)(pixels[sent + index] & 0xFFU); + } + display->io.write(display->io.context, g_chunk, block * 2U); + sent += block; + } + st_deselect(display); +} + +void ST7789_DrawChar(ST7789_Display *display, int16_t x, int16_t y, char symbol, + uint16_t color, uint16_t background, uint8_t scale) +{ + const uint8_t *glyph; + uint16_t code = (uint16_t)(uint8_t)symbol; + uint8_t row; + uint8_t column; + uint8_t repeat; + uint16_t cell_width; + uint16_t index; + + if ((display == 0) || (display->ready == 0U)) { + return; + } + if (scale == 0U) { + scale = 1U; + } + if (scale > ST7789_FONT_MAX_SCALE) { + scale = ST7789_FONT_MAX_SCALE; + } + if ((code < ST7789_FONT_FIRST_CHAR) + || (code >= (ST7789_FONT_FIRST_CHAR + ST7789_FONT_GLYPHS))) { + code = ST7789_FONT_FIRST_CHAR; + } + glyph = ST7789_Font5x7[code - ST7789_FONT_FIRST_CHAR]; + cell_width = (uint16_t)(ST7789_FONT_WIDTH * scale); + + /* Знакоместо целиком за экраном не рисуется; частичное — обрезается фоном. */ + if ((x >= (int16_t)display->width) || (y >= (int16_t)display->height) + || ((x + (int16_t)cell_width) <= 0) + || ((y + (int16_t)(ST7789_FONT_HEIGHT * scale)) <= 0)) { + return; + } + if ((x < 0) || (y < 0) || ((x + (int16_t)cell_width) > (int16_t)display->width) + || ((y + (int16_t)(ST7789_FONT_HEIGHT * scale)) > (int16_t)display->height)) { + /* Частично видимый символ выводится через обрезаемые заливки строк. */ + for (row = 0U; row < ST7789_FONT_HEIGHT; row++) { + for (column = 0U; column < ST7789_FONT_WIDTH; column++) { + const uint8_t bits = (column < ST7789_FONT_GLYPH_BYTES) ? glyph[column] : 0U; + const uint16_t pixel = (uint16_t)(((bits >> row) & 1U) != 0U ? color : background); + + ST7789_FillRect(display, (int16_t)(x + (int16_t)(column * scale)), + (int16_t)(y + (int16_t)(row * scale)), scale, scale, pixel); + } + } + return; + } + + st_select(display); + st_set_window(display, (uint16_t)x, (uint16_t)y, cell_width, + (uint16_t)(ST7789_FONT_HEIGHT * scale)); + for (row = 0U; row < ST7789_FONT_HEIGHT; row++) { + index = 0U; + for (column = 0U; column < ST7789_FONT_WIDTH; column++) { + const uint8_t bits = (column < ST7789_FONT_GLYPH_BYTES) ? glyph[column] : 0U; + const uint16_t pixel = (uint16_t)(((bits >> row) & 1U) != 0U ? color : background); + + for (repeat = 0U; repeat < scale; repeat++) { + g_chunk[index * 2U] = (uint8_t)(pixel >> 8U); + g_chunk[(index * 2U) + 1U] = (uint8_t)(pixel & 0xFFU); + index++; + } + } + for (repeat = 0U; repeat < scale; repeat++) { + display->io.write(display->io.context, g_chunk, (uint32_t)index * 2U); + } + } + st_deselect(display); +} + +int16_t ST7789_DrawString(ST7789_Display *display, int16_t x, int16_t y, + const char *text, uint16_t color, uint16_t background, + uint8_t scale) +{ + uint16_t step; + + if ((display == 0) || (text == 0)) { + return x; + } + if (scale == 0U) { + scale = 1U; + } + if (scale > ST7789_FONT_MAX_SCALE) { + scale = ST7789_FONT_MAX_SCALE; + } + step = (uint16_t)(ST7789_FONT_WIDTH * scale); + while (*text != '\0') { + if (x >= (int16_t)display->width) { + break; + } + ST7789_DrawChar(display, x, y, *text, color, background, scale); + x = (int16_t)(x + (int16_t)step); + text++; + } + return x; +} + +uint16_t ST7789_TextWidth(const char *text, uint8_t scale) +{ + uint16_t count = 0U; + + if (text == 0) { + return 0U; + } + if (scale == 0U) { + scale = 1U; + } + if (scale > ST7789_FONT_MAX_SCALE) { + scale = ST7789_FONT_MAX_SCALE; + } + while (text[count] != '\0') { + count++; + } + return (uint16_t)(count * ST7789_FONT_WIDTH * scale); +} diff --git a/c/st7789/st7789.h b/c/st7789/st7789.h new file mode 100644 index 0000000..0960cc6 --- /dev/null +++ b/c/st7789/st7789.h @@ -0,0 +1,280 @@ +/** + * @file st7789.h + * @brief Портируемый драйвер TFT-дисплеев ST7789V (SPI, RGB565). + * + * Библиотека не включает заголовки микроконтроллера и не обращается к + * регистрам: весь обмен идёт через таблицу обратных вызовов ST7789_Io, + * которую заполняет порт платы. Кадрового буфера нет — примитивы пишут + * пиксели в контроллер потоком, поэтому драйвер работает на МК с единицами + * килобайт ОЗУ. + * + * Порядок использования: заполнить ST7789_Io, взять ST7789_ConfigDefault(), + * поправить геометрию и поворот, вызвать ST7789_Init(), далее рисовать. + */ + +#ifndef ST7789_H +#define ST7789_H + +#include + +/** Ширина знакоместа встроенного шрифта при масштабе 1, пикселей. */ +#define ST7789_FONT_WIDTH 6U + +/** Высота знакоместа встроенного шрифта при масштабе 1, пикселей. */ +#define ST7789_FONT_HEIGHT 8U + +/** Максимальный целочисленный масштаб шрифта. */ +#define ST7789_FONT_MAX_SCALE 4U + +/** Код знака градуса во встроенном шрифте. */ +#define ST7789_CHAR_DEGREE 0x7FU + +/** Размер потокового буфера в пикселях; определяет расход ОЗУ (по 2 байта). */ +#ifndef ST7789_CHUNK_PIXELS +#define ST7789_CHUNK_PIXELS 64U +#endif + +/** + * @brief Собирает цвет RGB565 из восьмибитных компонент. + * + * @param r Красная компонента 0..255. + * @param g Зелёная компонента 0..255. + * @param b Синяя компонента 0..255. + * @return Значение цвета для примитивов драйвера. + */ +#define ST7789_RGB(r, g, b) ((uint16_t)((((uint16_t)(r) & 0xF8U) << 8) \ + | (((uint16_t)(g) & 0xFCU) << 3) \ + | ((uint16_t)(b) >> 3))) + +#define ST7789_BLACK 0x0000U /**< Чёрный. */ +#define ST7789_WHITE 0xFFFFU /**< Белый. */ +#define ST7789_RED 0xF800U /**< Красный. */ +#define ST7789_GREEN 0x07E0U /**< Зелёный. */ +#define ST7789_BLUE 0x001FU /**< Синий. */ +#define ST7789_YELLOW 0xFFE0U /**< Жёлтый. */ +#define ST7789_CYAN 0x07FFU /**< Голубой. */ +#define ST7789_MAGENTA 0xF81FU /**< Пурпурный. */ +#define ST7789_GRAY 0x8410U /**< Серый 50 %. */ +#define ST7789_DARKGRAY 0x4208U /**< Тёмно-серый. */ +#define ST7789_ORANGE 0xFD20U /**< Оранжевый. */ +#define ST7789_NAVY 0x000FU /**< Тёмно-синий. */ + +/** + * @brief Обратные вызовы платы, через которые драйвер работает с дисплеем. + * + * Обязательны только @c write, @c set_command и @c delay_ms. Остальные поля + * допускают значение 0, если сигнал на плате не разведён: без @c set_reset + * драйвер выполняет программный сброс командой SWRESET, без @c set_select + * дисплей считается постоянно выбранным (вывод CS посажен на общий провод). + */ +typedef struct { + /** Передаёт блок байтов по SPI: 8 бит, старший бит первым. */ + void (*write)(void *context, const uint8_t *data, uint32_t size); + /** Задаёт уровень линии DC: 1 — команда, 0 — данные. */ + void (*set_command)(void *context, uint8_t is_command); + /** Управляет выбором кристалла: 1 — выбран (CS низкий). */ + void (*set_select)(void *context, uint8_t selected); + /** Управляет линией RES: 1 — сброс активен (низкий уровень). */ + void (*set_reset)(void *context, uint8_t asserted); + /** Включает и выключает подсветку. */ + void (*set_backlight)(void *context, uint8_t on); + /** Задержка в миллисекундах на время инициализации панели. */ + void (*delay_ms)(void *context, uint32_t milliseconds); + /** Указатель платы, передаваемый во все обратные вызовы. */ + void *context; +} ST7789_Io; + +/** @brief Ориентация растра относительно нулевого поворота панели. */ +typedef enum { + ST7789_ROTATION_0 = 0, /**< Портрет, шлейф снизу. */ + ST7789_ROTATION_90, /**< Ландшафт, поворот на 90 градусов. */ + ST7789_ROTATION_180, /**< Портрет, перевёрнутый. */ + ST7789_ROTATION_270 /**< Ландшафт, поворот на 270 градусов. */ +} ST7789_Rotation; + +/** + * @brief Параметры конкретной панели. + * + * Модули на ST7789V отличаются видимой областью и её положением в растре + * контроллера 240x320: у панелей 240x240 и 240x320 смещений нет, у 135x240 + * видимая область сдвинута. Значения берутся из документации модуля. + */ +typedef struct { + uint16_t width; /**< Видимая ширина при нулевом повороте, пикселей. */ + uint16_t height; /**< Видимая высота при нулевом повороте, пикселей. */ + uint16_t offset_x; /**< Смещение видимой области по X при повороте 0. */ + uint16_t offset_y; /**< Смещение видимой области по Y при повороте 0. */ + ST7789_Rotation rotation; /**< Требуемая ориентация изображения. */ + uint8_t invert; /**< 1 — инверсия (обычные IPS-модули ST7789V). */ + uint8_t bgr; /**< 1 — порядок субпикселей BGR вместо RGB. */ +} ST7789_Config; + +/** + * @brief Состояние дисплея; принадлежит вызывающему коду. + * + * Поля @c width и @c height учитывают поворот, поэтому примитивы принимают + * координаты в системе видимого изображения. + */ +typedef struct { + ST7789_Io io; /**< Копия обратных вызовов платы. */ + ST7789_Config config; /**< Копия параметров панели. */ + uint16_t width; /**< Ширина изображения с учётом поворота. */ + uint16_t height; /**< Высота изображения с учётом поворота. */ + uint16_t offset_x; /**< Смещение окна по X с учётом поворота. */ + uint16_t offset_y; /**< Смещение окна по Y с учётом поворота. */ + uint8_t ready; /**< 1 после успешной инициализации. */ +} ST7789_Display; + +/** + * @brief Заполняет конфигурацию значениями типового модуля на ST7789V. + * + * Инверсия включена, порядок RGB, смещения нулевые, поворот нулевой. + * + * @param config Конфигурация, принадлежащая вызывающему коду. + * @param width Видимая ширина панели при нулевом повороте. + * @param height Видимая высота панели при нулевом повороте. + */ +void ST7789_ConfigDefault(ST7789_Config *config, uint16_t width, uint16_t height); + +/** + * @brief Инициализирует панель и включает вывод изображения. + * + * Выполняет аппаратный либо программный сброс, задаёт формат пикселя RGB565, + * гамму, ориентацию и включает подсветку, если её вывод разведён. + * + * @param display Состояние дисплея. + * @param io Обратные вызовы платы; копируются внутрь состояния. + * @param config Параметры панели; копируются внутрь состояния. + * @return 1 при успешной инициализации, 0 при неполной таблице вызовов. + */ +uint8_t ST7789_Init(ST7789_Display *display, const ST7789_Io *io, + const ST7789_Config *config); + +/** + * @brief Меняет ориентацию изображения без повторной инициализации. + * + * @param display Инициализированный дисплей. + * @param rotation Требуемая ориентация. + */ +void ST7789_SetRotation(ST7789_Display *display, ST7789_Rotation rotation); + +/** + * @brief Включает или выключает подсветку панели. + * + * @param display Инициализированный дисплей. + * @param on 1 — включить, 0 — выключить. + * @note При отсутствии обратного вызова платы функция ничего не делает. + */ +void ST7789_Backlight(ST7789_Display *display, uint8_t on); + +/** + * @brief Переводит панель в спящий режим и обратно. + * + * @param display Инициализированный дисплей. + * @param sleep 1 — сон (SLPIN), 0 — рабочий режим (SLPOUT). + */ +void ST7789_Sleep(ST7789_Display *display, uint8_t sleep); + +/** + * @brief Заливает весь экран одним цветом. + * + * @param display Инициализированный дисплей. + * @param color Цвет RGB565. + */ +void ST7789_FillScreen(ST7789_Display *display, uint16_t color); + +/** + * @brief Заливает прямоугольник одним цветом. + * + * Область обрезается по границам экрана; нулевые размеры игнорируются. + * + * @param display Инициализированный дисплей. + * @param x Левая граница. + * @param y Верхняя граница. + * @param width Ширина области. + * @param height Высота области. + * @param color Цвет RGB565. + */ +void ST7789_FillRect(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, uint16_t color); + +/** + * @brief Рисует рамку прямоугольника толщиной один пиксель. + * + * @param display Инициализированный дисплей. + * @param x Левая граница. + * @param y Верхняя граница. + * @param width Ширина области. + * @param height Высота области. + * @param color Цвет RGB565. + */ +void ST7789_DrawRect(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, uint16_t color); + +/** + * @brief Закрашивает один пиксель. + * + * @param display Инициализированный дисплей. + * @param x Координата по горизонтали. + * @param y Координата по вертикали. + * @param color Цвет RGB565. + */ +void ST7789_DrawPixel(ST7789_Display *display, int16_t x, int16_t y, uint16_t color); + +/** + * @brief Выводит готовый растр RGB565 построчно. + * + * @param display Инициализированный дисплей. + * @param x Левая граница области. + * @param y Верхняя граница области. + * @param width Ширина растра. + * @param height Высота растра. + * @param pixels Массив width*height пикселей, порядок — строками. + * @note Область должна целиком попадать на экран, иначе вывод отменяется. + */ +void ST7789_DrawBitmap(ST7789_Display *display, int16_t x, int16_t y, + uint16_t width, uint16_t height, const uint16_t *pixels); + +/** + * @brief Выводит один символ встроенного шрифта с непрозрачным фоном. + * + * @param display Инициализированный дисплей. + * @param x Левая граница знакоместа. + * @param y Верхняя граница знакоместа. + * @param symbol Код символа 0x20..0x7F; прочие выводятся как пробел. + * @param color Цвет штриха. + * @param background Цвет фона знакоместа. + * @param scale Целочисленный масштаб 1..ST7789_FONT_MAX_SCALE. + */ +void ST7789_DrawChar(ST7789_Display *display, int16_t x, int16_t y, char symbol, + uint16_t color, uint16_t background, uint8_t scale); + +/** + * @brief Выводит строку встроенным шрифтом с непрозрачным фоном. + * + * Символы за правой границей экрана отбрасываются, перевод строки не + * обрабатывается. + * + * @param display Инициализированный дисплей. + * @param x Левая граница первого знакоместа. + * @param y Верхняя граница строки. + * @param text Строка, завершённая нулём. + * @param color Цвет штриха. + * @param background Цвет фона знакомест. + * @param scale Целочисленный масштаб 1..ST7789_FONT_MAX_SCALE. + * @return Координата X сразу за последним выведенным знакоместом. + */ +int16_t ST7789_DrawString(ST7789_Display *display, int16_t x, int16_t y, + const char *text, uint16_t color, uint16_t background, + uint8_t scale); + +/** + * @brief Считает ширину строки в пикселях для заданного масштаба. + * + * @param text Строка, завершённая нулём. + * @param scale Масштаб шрифта. + * @return Ширина в пикселях. + */ +uint16_t ST7789_TextWidth(const char *text, uint8_t scale); + +#endif /* ST7789_H */ diff --git a/c/st7789/st7789_font.c b/c/st7789/st7789_font.c new file mode 100644 index 0000000..fb96f19 --- /dev/null +++ b/c/st7789/st7789_font.c @@ -0,0 +1,110 @@ +/** + * @file st7789_font.c + * @brief Таблица знакогенератора 5x7 для кодов 0x20..0x7F. + * + * Глиф хранится пятью байтами столбцов слева направо; бит 0 каждого байта — + * верхняя строка. Шестой столбец знакоместа драйвер оставляет пустым, поэтому + * шаг символа равен ST7789_FONT_WIDTH. Код 0x7F занят знаком градуса, все + * остальные позиции совпадают с ASCII. + */ + +#include "st7789_font.h" + +const uint8_t ST7789_Font5x7[ST7789_FONT_GLYPHS][ST7789_FONT_GLYPH_BYTES] = { + { 0x00, 0x00, 0x00, 0x00, 0x00 }, /* 0x20 пробел */ + { 0x00, 0x00, 0x5F, 0x00, 0x00 }, /* 0x21 '!' */ + { 0x00, 0x07, 0x00, 0x07, 0x00 }, /* 0x22 '"' */ + { 0x14, 0x7F, 0x14, 0x7F, 0x14 }, /* 0x23 '#' */ + { 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, /* 0x24 '$' */ + { 0x23, 0x13, 0x08, 0x64, 0x62 }, /* 0x25 '%' */ + { 0x36, 0x49, 0x55, 0x22, 0x50 }, /* 0x26 '&' */ + { 0x00, 0x05, 0x03, 0x00, 0x00 }, /* 0x27 ''' */ + { 0x00, 0x1C, 0x22, 0x41, 0x00 }, /* 0x28 '(' */ + { 0x00, 0x41, 0x22, 0x1C, 0x00 }, /* 0x29 ')' */ + { 0x14, 0x08, 0x3E, 0x08, 0x14 }, /* 0x2A '*' */ + { 0x08, 0x08, 0x3E, 0x08, 0x08 }, /* 0x2B '+' */ + { 0x00, 0x50, 0x30, 0x00, 0x00 }, /* 0x2C ',' */ + { 0x08, 0x08, 0x08, 0x08, 0x08 }, /* 0x2D '-' */ + { 0x00, 0x60, 0x60, 0x00, 0x00 }, /* 0x2E '.' */ + { 0x20, 0x10, 0x08, 0x04, 0x02 }, /* 0x2F '/' */ + { 0x3E, 0x51, 0x49, 0x45, 0x3E }, /* 0x30 '0' */ + { 0x00, 0x42, 0x7F, 0x40, 0x00 }, /* 0x31 '1' */ + { 0x42, 0x61, 0x51, 0x49, 0x46 }, /* 0x32 '2' */ + { 0x21, 0x41, 0x45, 0x4B, 0x31 }, /* 0x33 '3' */ + { 0x18, 0x14, 0x12, 0x7F, 0x10 }, /* 0x34 '4' */ + { 0x27, 0x45, 0x45, 0x45, 0x39 }, /* 0x35 '5' */ + { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, /* 0x36 '6' */ + { 0x01, 0x71, 0x09, 0x05, 0x03 }, /* 0x37 '7' */ + { 0x36, 0x49, 0x49, 0x49, 0x36 }, /* 0x38 '8' */ + { 0x06, 0x49, 0x49, 0x29, 0x1E }, /* 0x39 '9' */ + { 0x00, 0x36, 0x36, 0x00, 0x00 }, /* 0x3A ':' */ + { 0x00, 0x56, 0x36, 0x00, 0x00 }, /* 0x3B ';' */ + { 0x08, 0x14, 0x22, 0x41, 0x00 }, /* 0x3C '<' */ + { 0x14, 0x14, 0x14, 0x14, 0x14 }, /* 0x3D '=' */ + { 0x00, 0x41, 0x22, 0x14, 0x08 }, /* 0x3E '>' */ + { 0x02, 0x01, 0x51, 0x09, 0x06 }, /* 0x3F '?' */ + { 0x32, 0x49, 0x79, 0x41, 0x3E }, /* 0x40 '@' */ + { 0x7E, 0x11, 0x11, 0x11, 0x7E }, /* 0x41 'A' */ + { 0x7F, 0x49, 0x49, 0x49, 0x36 }, /* 0x42 'B' */ + { 0x3E, 0x41, 0x41, 0x41, 0x22 }, /* 0x43 'C' */ + { 0x7F, 0x41, 0x41, 0x22, 0x1C }, /* 0x44 'D' */ + { 0x7F, 0x49, 0x49, 0x49, 0x41 }, /* 0x45 'E' */ + { 0x7F, 0x09, 0x09, 0x09, 0x01 }, /* 0x46 'F' */ + { 0x3E, 0x41, 0x49, 0x49, 0x7A }, /* 0x47 'G' */ + { 0x7F, 0x08, 0x08, 0x08, 0x7F }, /* 0x48 'H' */ + { 0x00, 0x41, 0x7F, 0x41, 0x00 }, /* 0x49 'I' */ + { 0x20, 0x40, 0x41, 0x3F, 0x01 }, /* 0x4A 'J' */ + { 0x7F, 0x08, 0x14, 0x22, 0x41 }, /* 0x4B 'K' */ + { 0x7F, 0x40, 0x40, 0x40, 0x40 }, /* 0x4C 'L' */ + { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, /* 0x4D 'M' */ + { 0x7F, 0x04, 0x08, 0x10, 0x7F }, /* 0x4E 'N' */ + { 0x3E, 0x41, 0x41, 0x41, 0x3E }, /* 0x4F 'O' */ + { 0x7F, 0x09, 0x09, 0x09, 0x06 }, /* 0x50 'P' */ + { 0x3E, 0x41, 0x51, 0x21, 0x5E }, /* 0x51 'Q' */ + { 0x7F, 0x09, 0x19, 0x29, 0x46 }, /* 0x52 'R' */ + { 0x46, 0x49, 0x49, 0x49, 0x31 }, /* 0x53 'S' */ + { 0x01, 0x01, 0x7F, 0x01, 0x01 }, /* 0x54 'T' */ + { 0x3F, 0x40, 0x40, 0x40, 0x3F }, /* 0x55 'U' */ + { 0x1F, 0x20, 0x40, 0x20, 0x1F }, /* 0x56 'V' */ + { 0x3F, 0x40, 0x38, 0x40, 0x3F }, /* 0x57 'W' */ + { 0x63, 0x14, 0x08, 0x14, 0x63 }, /* 0x58 'X' */ + { 0x07, 0x08, 0x70, 0x08, 0x07 }, /* 0x59 'Y' */ + { 0x61, 0x51, 0x49, 0x45, 0x43 }, /* 0x5A 'Z' */ + { 0x00, 0x7F, 0x41, 0x41, 0x00 }, /* 0x5B '[' */ + { 0x02, 0x04, 0x08, 0x10, 0x20 }, /* 0x5C '\' */ + { 0x00, 0x41, 0x41, 0x7F, 0x00 }, /* 0x5D ']' */ + { 0x04, 0x02, 0x01, 0x02, 0x04 }, /* 0x5E '^' */ + { 0x40, 0x40, 0x40, 0x40, 0x40 }, /* 0x5F '_' */ + { 0x00, 0x01, 0x02, 0x04, 0x00 }, /* 0x60 '`' */ + { 0x20, 0x54, 0x54, 0x54, 0x78 }, /* 0x61 'a' */ + { 0x7F, 0x48, 0x44, 0x44, 0x38 }, /* 0x62 'b' */ + { 0x38, 0x44, 0x44, 0x44, 0x20 }, /* 0x63 'c' */ + { 0x38, 0x44, 0x44, 0x48, 0x7F }, /* 0x64 'd' */ + { 0x38, 0x54, 0x54, 0x54, 0x18 }, /* 0x65 'e' */ + { 0x08, 0x7E, 0x09, 0x01, 0x02 }, /* 0x66 'f' */ + { 0x0C, 0x52, 0x52, 0x52, 0x3E }, /* 0x67 'g' */ + { 0x7F, 0x08, 0x04, 0x04, 0x78 }, /* 0x68 'h' */ + { 0x00, 0x44, 0x7D, 0x40, 0x00 }, /* 0x69 'i' */ + { 0x20, 0x40, 0x44, 0x3D, 0x00 }, /* 0x6A 'j' */ + { 0x7F, 0x10, 0x28, 0x44, 0x00 }, /* 0x6B 'k' */ + { 0x00, 0x41, 0x7F, 0x40, 0x00 }, /* 0x6C 'l' */ + { 0x7C, 0x04, 0x18, 0x04, 0x78 }, /* 0x6D 'm' */ + { 0x7C, 0x08, 0x04, 0x04, 0x78 }, /* 0x6E 'n' */ + { 0x38, 0x44, 0x44, 0x44, 0x38 }, /* 0x6F 'o' */ + { 0x7C, 0x14, 0x14, 0x14, 0x08 }, /* 0x70 'p' */ + { 0x08, 0x14, 0x14, 0x18, 0x7C }, /* 0x71 'q' */ + { 0x7C, 0x08, 0x04, 0x04, 0x08 }, /* 0x72 'r' */ + { 0x48, 0x54, 0x54, 0x54, 0x20 }, /* 0x73 's' */ + { 0x04, 0x3F, 0x44, 0x40, 0x20 }, /* 0x74 't' */ + { 0x3C, 0x40, 0x40, 0x20, 0x1C }, /* 0x75 'u' */ + { 0x1C, 0x20, 0x40, 0x20, 0x1C }, /* 0x76 'v' */ + { 0x3C, 0x40, 0x30, 0x40, 0x3C }, /* 0x77 'w' */ + { 0x44, 0x28, 0x10, 0x28, 0x44 }, /* 0x78 'x' */ + { 0x0C, 0x50, 0x50, 0x50, 0x3C }, /* 0x79 'y' */ + { 0x44, 0x64, 0x54, 0x4C, 0x44 }, /* 0x7A 'z' */ + { 0x00, 0x08, 0x36, 0x41, 0x00 }, /* 0x7B '{' */ + { 0x00, 0x00, 0x7F, 0x00, 0x00 }, /* 0x7C '|' */ + { 0x00, 0x41, 0x36, 0x08, 0x00 }, /* 0x7D '}' */ + { 0x08, 0x04, 0x08, 0x10, 0x08 }, /* 0x7E '~' */ + { 0x00, 0x06, 0x09, 0x09, 0x06 }, /* 0x7F знак градуса */ +}; diff --git a/c/st7789/st7789_font.h b/c/st7789/st7789_font.h new file mode 100644 index 0000000..3c6ec57 --- /dev/null +++ b/c/st7789/st7789_font.h @@ -0,0 +1,27 @@ +/** + * @file st7789_font.h + * @brief Знакогенератор 5x7 для драйвера ST7789V. + * + * Таблица не зависит от аппаратуры и подключается вместе с драйвером. Для + * замены шрифта достаточно предоставить другой массив того же формата: + * пять байтов столбцов на символ, бит 0 — верхняя строка растра. + */ + +#ifndef ST7789_FONT_H +#define ST7789_FONT_H + +#include + +/** Число описанных глифов: коды 0x20..0x7F. */ +#define ST7789_FONT_GLYPHS 96U + +/** Байтов на глиф: пять столбцов растра. */ +#define ST7789_FONT_GLYPH_BYTES 5U + +/** Первый описанный код символа. */ +#define ST7789_FONT_FIRST_CHAR 0x20U + +/** Растр символов 5x7; шестой столбец знакоместа рисует драйвер. */ +extern const uint8_t ST7789_Font5x7[ST7789_FONT_GLYPHS][ST7789_FONT_GLYPH_BYTES]; + +#endif /* ST7789_FONT_H */