376 lines
13 KiB
C
376 lines
13 KiB
C
#include "../inc/DS_ST7789V.h"
|
||
|
||
/* Вспомогательные статические макросы для локального управления линиями GPIO */
|
||
#define CS_ACTIVE(lcd) \
|
||
HAL_GPIO_WritePin(lcd->CS.Port, lcd->CS.Pin, GPIO_PIN_RESET)
|
||
#define CS_IDLE(lcd) HAL_GPIO_WritePin(lcd->CS.Port, lcd->CS.Pin, GPIO_PIN_SET)
|
||
#define DC_COMMAND(lcd) \
|
||
HAL_GPIO_WritePin(lcd->DC.Port, lcd->DC.Pin, GPIO_PIN_RESET)
|
||
#define DC_DATA(lcd) HAL_GPIO_WritePin(lcd->DC.Port, lcd->DC.Pin, GPIO_PIN_SET)
|
||
#define RES_ACTIVE(lcd) \
|
||
HAL_GPIO_WritePin(lcd->RES.Port, lcd->RES.Pin, GPIO_PIN_RESET)
|
||
#define RES_IDLE(lcd) \
|
||
HAL_GPIO_WritePin(lcd->RES.Port, lcd->RES.Pin, GPIO_PIN_SET)
|
||
|
||
void DS_ST7789V_WriteCommand(DS_ST7789V *lcd, uint8_t cmd) {
|
||
DC_COMMAND(lcd);
|
||
CS_ACTIVE(lcd);
|
||
HAL_SPI_Transmit(lcd->hspi, &cmd, 1, HAL_MAX_DELAY);
|
||
CS_IDLE(lcd);
|
||
}
|
||
|
||
void DS_ST7789V_WriteData(DS_ST7789V *lcd, uint8_t *data, uint16_t size) {
|
||
DC_DATA(lcd);
|
||
CS_ACTIVE(lcd);
|
||
HAL_SPI_Transmit(lcd->hspi, data, size, HAL_MAX_DELAY);
|
||
CS_IDLE(lcd);
|
||
}
|
||
|
||
static void DS_ST7789V_SetAddressWindow(DS_ST7789V *lcd, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
|
||
uint8_t data[4];
|
||
|
||
/* Настройка столбцов (X) */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_CASET);
|
||
data[0] = (x0 >> 8) & 0xFF;
|
||
data[1] = x0 & 0xFF;
|
||
data[2] = (x1 >> 8) & 0xFF;
|
||
data[3] = x1 & 0xFF;
|
||
DS_ST7789V_WriteData(lcd, data, 4); // Передаем пачку строго из 4 байт
|
||
|
||
/* Настройка строк (Y) */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_RASET);
|
||
data[0] = (y0 >> 8) & 0xFF;
|
||
data[1] = y0 & 0xFF;
|
||
data[2] = (y1 >> 8) & 0xFF;
|
||
data[3] = y1 & 0xFF;
|
||
DS_ST7789V_WriteData(lcd, data, 4); // Передаем пачку строго из 4 байт
|
||
|
||
/* Готовность к записи в RAM дисплея */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_RAMWR);
|
||
}
|
||
|
||
|
||
void DS_ST7789V_Init(DS_ST7789V *lcd, SPI_HandleTypeDef *hspi,
|
||
GPIO_TypeDef *cs_port, uint16_t cs_pin,
|
||
GPIO_TypeDef *dc_port, uint16_t dc_pin,
|
||
GPIO_TypeDef *res_port, uint16_t res_pin)
|
||
{
|
||
lcd->hspi = hspi;
|
||
|
||
lcd->CS.Port = cs_port;
|
||
lcd->CS.Pin = cs_pin;
|
||
lcd->DC.Port = dc_port;
|
||
lcd->DC.Pin = dc_pin;
|
||
lcd->RES.Port = res_port;
|
||
lcd->RES.Pin = res_pin;
|
||
lcd->Width = DS_ST7789V_WIDTH;
|
||
lcd->Height = DS_ST7789V_HEIGHT;
|
||
|
||
/* Аппаратный сброс матрицы */
|
||
RES_ACTIVE(lcd);
|
||
HAL_Delay(25);
|
||
RES_IDLE(lcd);
|
||
HAL_Delay(120);
|
||
|
||
/* Программный сброс контроллера */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_SWRESET);
|
||
HAL_Delay(150);
|
||
|
||
/* Выход из спящего режима */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_SLPOUT);
|
||
HAL_Delay(120);
|
||
|
||
/* Установка цветового режима: 16-бит/пиксель (RGB565) */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_COLMOD);
|
||
uint8_t color_mode = 0x55;
|
||
DS_ST7789V_WriteData(lcd, &color_mode, 1);
|
||
|
||
/* Установка дефолтной ориентации */
|
||
DS_ST7789V_SetOrientation(lcd, DS_ST7789V_ORIENTATION_PORTRAIT);
|
||
|
||
/* Инверсия цвета дисплея (требуется для большинства IPS матриц этой серии) */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_INVON);
|
||
|
||
/* Включение дисплея */
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_DISPON);
|
||
HAL_Delay(50);
|
||
|
||
/* Первоначальная очистка экрана черным цветом */
|
||
DS_ST7789V_FillRect(lcd, 0, 0, lcd->Width, lcd->Height, 0x0000);
|
||
}
|
||
|
||
void DS_ST7789V_SetOrientation(DS_ST7789V *lcd,
|
||
DS_ST7789V_Orientation orientation) {
|
||
lcd->Orientation = orientation;
|
||
uint8_t madctl_val = 0;
|
||
switch (orientation) {
|
||
case DS_ST7789V_ORIENTATION_PORTRAIT:
|
||
madctl_val = 0x00; // Развертка сверху-вниз, слева-направо
|
||
lcd->Width = DS_ST7789V_WIDTH;
|
||
lcd->Height = DS_ST7789V_HEIGHT;
|
||
break;
|
||
case DS_ST7789V_ORIENTATION_LANDSCAPE:
|
||
madctl_val = 0x60; // Обмен строк и столбцов (поворот на 90°)
|
||
lcd->Width = DS_ST7789V_HEIGHT;
|
||
lcd->Height = DS_ST7789V_WIDTH;
|
||
break;
|
||
case DS_ST7789V_ORIENTATION_PORTRAIT_REV:
|
||
madctl_val = 0xC0; // Отражение по осям X и Y
|
||
lcd->Width = DS_ST7789V_WIDTH;
|
||
lcd->Height = DS_ST7789V_HEIGHT;
|
||
break;
|
||
case DS_ST7789V_ORIENTATION_LANDSCAPE_REV:
|
||
madctl_val = 0xA0; // Альтернативный альбомный режим
|
||
lcd->Width = DS_ST7789V_HEIGHT;
|
||
lcd->Height = DS_ST7789V_WIDTH;
|
||
break;
|
||
}
|
||
DS_ST7789V_WriteCommand(lcd, ST7789V_CMD_MADCTL);
|
||
DS_ST7789V_WriteData(lcd, &madctl_val, 1);
|
||
}
|
||
|
||
void DS_ST7789V_FillRect(DS_ST7789V *lcd, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
|
||
if ((x >= lcd->Width) || (y >= lcd->Height)) return;
|
||
if ((x + w) > lcd->Width) w = lcd->Width - x;
|
||
if ((y + h) > lcd->Height) h = lcd->Height - y;
|
||
|
||
DS_ST7789V_SetAddressWindow(lcd, x, y, x + w - 1, y + h - 1);
|
||
|
||
// Выделяем буфер под увеличенный блок (например, 20 строк экрана за раз)
|
||
// 240 пикселей * 20 строк * 2 байта = 9600 байт (у STM32G0B1 целых 144 КБ ОЗУ, это неощутимо)
|
||
#define CHUNK_ROWS 20
|
||
static uint8_t chunk_buffer[DS_ST7789V_HEIGHT * CHUNK_ROWS * 2];
|
||
|
||
uint8_t high_byte = (color >> 8) & 0xFF;
|
||
uint8_t low_byte = color & 0xFF;
|
||
|
||
// Заполняем этот большой буфер один раз
|
||
uint32_t pixels_in_chunk = w * CHUNK_ROWS;
|
||
uint32_t idx = 0;
|
||
for (uint32_t i = 0; i < pixels_in_chunk; i++) {
|
||
chunk_buffer[idx++] = high_byte;
|
||
chunk_buffer[idx++] = low_byte;
|
||
}
|
||
|
||
DC_DATA(lcd);
|
||
CS_ACTIVE(lcd);
|
||
|
||
uint32_t total_pixels = w * h;
|
||
uint32_t pixels_left = total_pixels;
|
||
|
||
// Отправляем данные огромными блоками
|
||
while (pixels_left > 0) {
|
||
uint32_t current_chunk_pixels = (pixels_left > pixels_in_chunk) ? pixels_in_chunk : pixels_left;
|
||
uint32_t bytes_to_send = current_chunk_pixels * 2;
|
||
|
||
|
||
HAL_SPI_Transmit(lcd->hspi, chunk_buffer, bytes_to_send, HAL_MAX_DELAY);
|
||
|
||
pixels_left -= current_chunk_pixels;
|
||
}
|
||
|
||
CS_IDLE(lcd);
|
||
}
|
||
|
||
|
||
void DS_ST7789V_DrawPixel(DS_ST7789V *lcd, uint16_t x, uint16_t y, uint16_t color) {
|
||
if ((x >= lcd->Width) || (y >= lcd->Height)) return;
|
||
|
||
DS_ST7789V_SetAddressWindow(lcd, x, y, x, y);
|
||
|
||
uint8_t color_bytes[2];
|
||
color_bytes[0] = (color >> 8) & 0xFF; // Старший байт (MSB)
|
||
color_bytes[1] = color & 0xFF; // Младший байт (LSB)
|
||
|
||
DC_DATA(lcd);
|
||
CS_ACTIVE(lcd);
|
||
|
||
// Передаем строго массив color_bytes размером 2 байта
|
||
HAL_SPI_Transmit(lcd->hspi, color_bytes, 2, HAL_MAX_DELAY);
|
||
|
||
CS_IDLE(lcd);
|
||
}
|
||
|
||
// Быстрая горизонтальная линия — это прямоугольник высотой в 1 пиксель
|
||
void DS_ST7789V_DrawHLine(DS_ST7789V *lcd, uint16_t x, uint16_t y, uint16_t length, uint16_t color) {
|
||
DS_ST7789V_FillRect(lcd, x, y, length, 1, color);
|
||
}
|
||
|
||
// Быстрая вертикальная линия — это прямоугольник шириной в 1 пиксель
|
||
void DS_ST7789V_DrawVLine(DS_ST7789V *lcd, uint16_t x, uint16_t y, uint16_t length, uint16_t color) {
|
||
DS_ST7789V_FillRect(lcd, x, y, 1, length, color);
|
||
}
|
||
|
||
void DS_ST7789V_DrawChar(DS_ST7789V *lcd, uint16_t x, uint16_t y, char ch, uint16_t color, uint16_t bg_color, uint8_t scale) {
|
||
if (ch < 0x20 || ch > 0x7E) return;
|
||
if (scale == 0) scale = 1; // Защита от нулевого масштаба
|
||
|
||
uint16_t font_index = (ch - 0x20) * 5;
|
||
|
||
for (uint8_t col = 0; col < 5; col++) {
|
||
uint8_t line = DS_Font5x7[font_index + col];
|
||
for (uint8_t row = 0; row < 7; row++) {
|
||
// Вычисляем координаты увеличенного "пикселя"
|
||
uint16_t px = x + (col * scale);
|
||
uint16_t py = y + (row * scale);
|
||
|
||
if (line & (1 << row)) {
|
||
// Вместо одной точки рисуем закрашенный квадрат размером scale x scale
|
||
DS_ST7789V_FillRect(lcd, px, py, scale, scale, color);
|
||
} else {
|
||
DS_ST7789V_FillRect(lcd, px, py, scale, scale, bg_color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void DS_ST7789V_DrawString(DS_ST7789V *lcd, uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg_color, uint8_t scale) {
|
||
if (scale == 0) scale = 1;
|
||
|
||
// Шаг смещения: ширина символа (5) * масштаб + 1 пиксель межсимвольного интервала
|
||
uint8_t char_width = 5 * scale;
|
||
uint8_t char_height = 7 * scale;
|
||
uint8_t step_x = char_width + scale;
|
||
|
||
while (*str) {
|
||
// Перенос строки при достижении правой границы экрана
|
||
if (x + char_width >= lcd->Width) {
|
||
x = 0;
|
||
y += char_height + scale; // Сдвиг вниз на высоту символа + межстрочный интервал
|
||
}
|
||
|
||
if (y + char_height >= lcd->Height) break;
|
||
|
||
DS_ST7789V_DrawChar(lcd, x, y, *str, color, bg_color, scale);
|
||
|
||
x += step_x;
|
||
str++;
|
||
}
|
||
}
|
||
|
||
void DS_ST7789V_DrawInt(DS_ST7789V *lcd, uint16_t x, uint16_t y, int32_t num, uint16_t color, uint16_t bg_color, uint8_t scale) {
|
||
char buf[16];
|
||
int i = 14;
|
||
buf[15] = '\0';
|
||
|
||
uint8_t is_negative = 0;
|
||
if (num < 0) {
|
||
is_negative = 1;
|
||
num = -num;
|
||
}
|
||
|
||
if (num == 0) {
|
||
buf[i--] = '0';
|
||
} else {
|
||
while (num > 0 && i > 0) {
|
||
buf[i--] = (num % 10) + '0';
|
||
num /= 10;
|
||
}
|
||
}
|
||
|
||
if (is_negative) {
|
||
buf[i--] = '-';
|
||
}
|
||
|
||
DS_ST7789V_DrawString(lcd, x, y, &buf[i + 1], color, bg_color, scale);
|
||
}
|
||
|
||
|
||
void DS_ST7789V_DrawFloat(DS_ST7789V *lcd, uint16_t x, uint16_t y, float num, uint8_t decimals, uint16_t color, uint16_t bg_color, uint8_t scale) {
|
||
char buf[32];
|
||
int i = 30;
|
||
buf[31] = '\0';
|
||
|
||
uint8_t is_negative = 0;
|
||
if (num < 0) {
|
||
is_negative = 1;
|
||
num = -num;
|
||
}
|
||
|
||
float rounding = 0.5f;
|
||
for (uint8_t d = 0; d < decimals; d++) rounding /= 10.0f;
|
||
num += rounding;
|
||
|
||
int32_t int_part = (int32_t)num;
|
||
float frac_part = num - (float)int_part;
|
||
|
||
if (decimals > 0) {
|
||
for (uint8_t d = 0; d < decimals; d++) {
|
||
frac_part *= 10.0f;
|
||
int32_t digit = (int32_t)frac_part;
|
||
buf[i--] = digit + '0';
|
||
frac_part -= digit;
|
||
}
|
||
|
||
int left = i + 1;
|
||
int right = 30;
|
||
while (left < right) {
|
||
char temp = buf[left];
|
||
buf[left] = buf[right];
|
||
buf[right] = temp;
|
||
left++; right--;
|
||
}
|
||
i = 30 - decimals;
|
||
buf[i--] = '.';
|
||
}
|
||
|
||
if (int_part == 0) {
|
||
buf[i--] = '0';
|
||
} else {
|
||
while (int_part > 0 && i > 0) {
|
||
buf[i--] = (int_part % 10) + '0';
|
||
int_part /= 10;
|
||
}
|
||
}
|
||
|
||
if (is_negative) {
|
||
buf[i--] = '-';
|
||
}
|
||
|
||
DS_ST7789V_DrawString(lcd, x, y, &buf[i + 1], color, bg_color, scale);
|
||
}
|
||
|
||
void DS_ST7789V_DrawCharTransparent(DS_ST7789V *lcd, uint16_t x, uint16_t y, char ch, uint16_t color, uint8_t scale) {
|
||
if (ch < 0x20 || ch > 0x7E) return;
|
||
if (scale == 0) scale = 1;
|
||
|
||
uint16_t font_index = (ch - 0x20) * 5;
|
||
|
||
for (uint8_t col = 0; col < 5; col++) {
|
||
uint8_t line = DS_Font5x7[font_index + col];
|
||
for (uint8_t row = 0; row < 7; row++) {
|
||
// Если бит установлен — отрисовываем масштабированный пиксель цвета текста
|
||
if (line & (1 << row)) {
|
||
uint16_t px = x + (col * scale);
|
||
uint16_t py = y + (row * scale);
|
||
DS_ST7789V_FillRect(lcd, px, py, scale, scale, color);
|
||
}
|
||
// Если бит равен 0 — просто ничего не делаем, пропуская пиксель фона
|
||
}
|
||
}
|
||
}
|
||
|
||
void DS_ST7789V_DrawStringTransparent(DS_ST7789V *lcd, uint16_t x, uint16_t y, const char *str, uint16_t color, uint8_t scale) {
|
||
if (scale == 0) scale = 1;
|
||
|
||
uint8_t char_width = 5 * scale;
|
||
uint8_t char_height = 7 * scale;
|
||
uint8_t step_x = char_width + scale;
|
||
|
||
while (*str) {
|
||
if (x + char_width >= lcd->Width) {
|
||
x = 0;
|
||
y += char_height + scale;
|
||
}
|
||
|
||
if (y + char_height >= lcd->Height) break;
|
||
|
||
DS_ST7789V_DrawCharTransparent(lcd, x, y, *str, color, scale);
|
||
|
||
x += step_x;
|
||
str++;
|
||
}
|
||
}
|
||
|
||
|