Перенесён из KONOR_ds18b20/lib/st7789; в OpticalTester лежала побайтово такая же копия. Ядро не включает заголовки МК и не заводит кадрового буфера: примитивы пишут пиксели потоком, поэтому драйвер работает на МК с единицами килобайт ОЗУ. Платформа подключается через ST7789_Io: обязательны write, set_command и delay_ms, остальное — по разводке платы.
625 lines
22 KiB
C
625 lines
22 KiB
C
/**
|
||
* @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);
|
||
}
|