#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; /* Автоматическая привязка портов на основе сгенерированных CubeMX меток (User * Labels) */ 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; // Отправка всего блока на максимальной скорости аппаратного SPI 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]; // Явно объявляем массив из 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]; // ИСПРАВЛЕНО: жестко выделили массив на 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]; // ИСПРАВЛЕНО: жестко выделили массив на 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; // ИСПРАВЛЕНО: индекс конца массива изменен под размер 32 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++; } }