а там на дисплее есть перемычка для 8-16 бит данных ?
если да, то переставив и подключив так, используя больше проводов,(параллейное подключение) наверное можно получить…
еще пишут что это требует 18 бит данных, и тогда изображение не крошится… так что это вроде нормально..
а еще вы вроде невзлюбили библиотеку tft espi… не знаю стоит ли ее предлагать, и поможет ли она вам чем…
если у вас esp32, то она вроде умеет быстро конвертировать 16 битные в 18 битные и отправлять…
или возможно показывать скорость больше… что вам наверное не нужно нафиг))) если вы все сами делаете, но может там часть кода себе позаимствуете..
SPI.transfer() в цикле для каждого байта. Гораздо эффективнее сформировать большой массив данных для всего экрана (или строки) и отправить его одной командой SPI.writeBytes() у вас проверка постоянная не тормозит процесс ?
еще я мало работал с stm32… если бы была esp32, тогда , бы посоветовал скармливать часть данных в озу, и от туда переправлять частями, из озу быстрее вроде..
код реализован через ии!))) без тестов, но возможно пригодится… мне на esp32 используя малый объем озу часто давало прирост скорости, можно ли применить это к вашему случаю… и справился ли ии, не знаю, но может пригодится
// ILI9488 + STM32F103 (Blue Pill) - Двойная буферизация
// Оптимизировано для максимальной производительности
#include <SPI.h>
// === РЕЖИМ ЦВЕТА ===
// Работаем в 18-битном режиме (аппаратное требование ILI9488 для SPI)
// Но храним и рисуем в 16-битном формате для экономии памяти
#define COLORS_262K // режим 18-разрядного цвета (3 байта на пиксель)
// === ПОДКЛЮЧЕНИЕ ===
#define CS PB11 // желтый
#define RESET PB10 // зеленый
#define RS PB1 // синий
#define MOSI PA7 // серый
#define SCK PA5 // черный
#define LED_L PB7 // оранжевый (подсветка)
#define LED_B PC13 // встроенный светодиод
// === ПАРАМЕТРЫ ДИСПЛЕЯ ===
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define LINE_PIXELS DISPLAY_WIDTH // 320 пикселей в строке
// === РАЗМЕРЫ БУФЕРОВ ===
// Храним в 16 бит (RGB565) - 2 байта на пиксель
#define LINE_BUFFER_16BIT (LINE_PIXELS * 2) // 320*2 = 640 байт на строку
#define LINES_PER_BUFFER 8 // Сколько строк хранить в одном буфере
#define BUFFER_16BIT_SIZE (LINE_BUFFER_16BIT * LINES_PER_BUFFER) // 640*8 = 5120 байт
// Буфер для отправки (18 бит) - 3 байта на пиксель
#define SEND_BUFFER_SIZE (LINE_PIXELS * 3) // 320*3 = 960 байт
// === БУФЕРЫ В ОЗУ ===
// Два буфера для хранения 16-битных данных (рисуем сюда)
uint8_t bufferA[BUFFER_16BIT_SIZE];
uint8_t bufferB[BUFFER_16BIT_SIZE];
// Буфер для преобразования и отправки (18 бит)
uint8_t sendBuffer[SEND_BUFFER_SIZE];
// Указатели на текущие буферы
uint8_t* currentDrawBuffer = bufferA; // Буфер для рисования
uint8_t* currentSendBuffer = bufferB; // Буфер для отправки
uint8_t* tempBuffer = NULL; // Для обмена
// Счетчики строк
volatile int linesDrawn = 0;
volatile int linesSent = 0;
volatile bool transferComplete = true;
// === ПРЕОБРАЗОВАНИЕ ЦВЕТОВ ===
// Определяем базовые цвета в 16-битном формате RGB565
#define RGB565_WHITE 0xFFFF
#define RGB565_RED 0xF800
#define RGB565_GREEN 0x07E0
#define RGB565_BLUE 0x001F
#define RGB565_BLACK 0x0000
#define RGB565_YELLOW 0xFFE0
#define RGB565_CYAN 0x07FF
#define RGB565_MAGENTA 0xF81F
// === ПРОТОТИПЫ ФУНКЦИЙ ===
void Lcd_Init(void);
void Address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void Lcd_Write_Com(uint8_t c);
void Lcd_Write_Data(uint8_t d);
void Lcd_Write_Bus(uint8_t d);
void convertLineRGB565toRGB666(uint8_t* rgb565line, uint8_t* rgb666line, int pixels);
void fillLineWithColor(uint8_t* buffer, int lineIndex, uint16_t color565);
void startDMATransfer(uint8_t* data, uint32_t size);
void prepareNextBuffer(uint16_t color565);
// === ФУНКЦИИ РАБОТЫ С SPI ===
void Lcd_Write_Bus(uint8_t d)
{
SPI.transfer(d);
}
void Lcd_Write_Com(uint8_t c)
{
GPIOB_BASE->BRR = 0x0002; // RS LOW
Lcd_Write_Bus(c);
GPIOB_BASE->BSRR = 0x0002; // RS HIGH
}
void Lcd_Write_Data(uint8_t d)
{
Lcd_Write_Bus(d);
}
// === УСТАНОВКА ОБЛАСТИ ВЫВОДА ===
void Address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
Lcd_Write_Com(0x2A); // Column address
Lcd_Write_Data(x1 >> 8);
Lcd_Write_Data(x1);
Lcd_Write_Data(x2 >> 8);
Lcd_Write_Data(x2);
Lcd_Write_Com(0x2B); // Page address
Lcd_Write_Data(y1 >> 8);
Lcd_Write_Data(y1);
Lcd_Write_Data(y2 >> 8);
Lcd_Write_Data(y2);
Lcd_Write_Com(0x2C); // Memory write
}
// === ИНИЦИАЛИЗАЦИЯ ДИСПЛЕЯ ===
void Lcd_Init(void)
{
digitalWrite(RESET, HIGH);
delay(5);
digitalWrite(RESET, LOW);
delay(15);
digitalWrite(RESET, HIGH);
delay(15);
digitalWrite(CS, LOW);
Lcd_Write_Com(0x28); // Display off
delay(20);
Lcd_Write_Com(0x3A); // Интерфейс: 18 бит на пиксель
Lcd_Write_Data(0x66); // 0x66 = 18-битный режим
Lcd_Write_Com(0xF7); // Adjust control 3
Lcd_Write_Data(0xA9);
Lcd_Write_Data(0x51);
Lcd_Write_Data(0x2C);
Lcd_Write_Data(0x82);
Lcd_Write_Com(0xC0); // Power control 1
Lcd_Write_Data(0x10);
Lcd_Write_Data(0x10);
Lcd_Write_Com(0xC1); // Power control 2
Lcd_Write_Data(0x41);
Lcd_Write_Com(0xC5); // VCOM control
Lcd_Write_Data(0x00);
Lcd_Write_Data(0x22);
Lcd_Write_Data(0x80);
Lcd_Write_Data(0x40);
Lcd_Write_Com(0xB0); //
Lcd_Write_Data(0x00);
Lcd_Write_Com(0xB1); // Frame rate control
Lcd_Write_Data(0xB0);
Lcd_Write_Data(0x11);
Lcd_Write_Com(0xB4); // Display inversion control
Lcd_Write_Data(0x02);
Lcd_Write_Com(0xB6); // Display function control
Lcd_Write_Data(0x02);
Lcd_Write_Data(0x02);
Lcd_Write_Data(0x3B);
Lcd_Write_Com(0xB7); // Entry mode set
Lcd_Write_Data(0xC6);
Lcd_Write_Com(0x36); // Memory access control
Lcd_Write_Data(0x08); // 0x08 - ориентация (возможно, нужно поменять)
Lcd_Write_Com(0x11); // Exit Sleep
delay(120);
Lcd_Write_Com(0x29); // Display on
digitalWrite(CS, HIGH);
}
// === ПРЕОБРАЗОВАНИЕ СТРОКИ ИЗ RGB565 В RGB666 ДЛЯ ILI9488 ===
void convertLineRGB565toRGB666(uint8_t* rgb565line, uint8_t* rgb666line, int pixels)
{
for (int i = 0; i < pixels; i++) {
// Читаем 16-битный цвет (little-endian от Arduino)
uint16_t color565 = rgb565line[i*2] | (rgb565line[i*2 + 1] << 8);
// Извлекаем компоненты RGB565
uint8_t r5 = (color565 >> 11) & 0x1F;
uint8_t g6 = (color565 >> 5) & 0x3F;
uint8_t b5 = color565 & 0x1F;
// Преобразуем в 6-битные компоненты и расширяем до байта
// Простая аппроксимация: просто сдвигаем влево
rgb666line[i*3] = r5 << 3; // RRRRR000
rgb666line[i*3 + 1] = g6 << 2; // GGGGGG00
rgb666line[i*3 + 2] = b5 << 3; // BBBBB000
}
}
// === ЗАПОЛНЕНИЕ СТРОКИ ОДНИМ ЦВЕТОМ ===
void fillLineWithColor(uint8_t* buffer, int lineIndex, uint16_t color565)
{
int offset = lineIndex * LINE_BUFFER_16BIT;
for (int x = 0; x < LINE_PIXELS; x++) {
buffer[offset + x*2] = color565 & 0xFF;
buffer[offset + x*2 + 1] = (color565 >> 8) & 0xFF;
}
}
// === ЗАПУСК ОТПРАВКИ ЧЕРЕЗ SPI ===
// В идеале здесь должен быть DMA, но пока используем обычную отправку
void startDMATransfer(uint8_t* data, uint32_t size)
{
// Здесь можно будет заменить на DMA позже
SPI.transfer(data, size);
transferComplete = true;
}
// === ПОДГОТОВКА СЛЕДУЮЩЕГО БУФЕРА ===
void prepareNextBuffer(int startLine, int numLines, uint16_t color565)
{
uint8_t* targetBuffer = (currentDrawBuffer == bufferA) ? bufferB : bufferA;
for (int line = 0; line < numLines; line++) {
if (startLine + line < DISPLAY_HEIGHT) {
fillLineWithColor(targetBuffer, line, color565);
}
}
}
// === ОСНОВНАЯ ФУНКЦИЯ ЗАЛИВКИ С ДВОЙНОЙ БУФЕРИЗАЦИЕЙ ===
void LCD_Clear_DoubleBuffer(uint16_t color565)
{
digitalWrite(CS, LOW);
Address_set(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
// 1. ПРЕДЗАГРУЗКА: заполняем первый буфер
for (int line = 0; line < LINES_PER_BUFFER; line++) {
fillLineWithColor(bufferA, line, color565);
}
// 2. ОСНОВНОЙ ЦИКЛ: обрабатываем экран блоками по LINES_PER_BUFFER строк
for (int blockStart = 0; blockStart < DISPLAY_HEIGHT; blockStart += LINES_PER_BUFFER) {
int linesInThisBlock = LINES_PER_BUFFER;
if (blockStart + linesInThisBlock > DISPLAY_HEIGHT) {
linesInThisBlock = DISPLAY_HEIGHT - blockStart;
}
// Определяем, какой буфер сейчас отправляем
uint8_t* sendBufferPtr = (blockStart == 0) ? bufferA :
((blockStart / LINES_PER_BUFFER) % 2 == 0 ? bufferB : bufferA);
// Для каждого блока строк:
for (int line = 0; line < linesInThisBlock; line++) {
// Конвертируем строку из 16-бит в 18-бит
convertLineRGB565toRGB666(
sendBufferPtr + (line * LINE_BUFFER_16BIT),
sendBuffer,
LINE_PIXELS
);
// Отправляем строку
SPI.transfer(sendBuffer, SEND_BUFFER_SIZE);
}
// Если это не последний блок, начинаем готовить следующий буфер
if (blockStart + LINES_PER_BUFFER < DISPLAY_HEIGHT) {
// В реальном DMA-режиме здесь можно подготовить следующий буфер
// пока отправляется текущий. С обычным SPI это не дает выигрыша,
// но структура кода готова для апгрейда до DMA
}
}
digitalWrite(CS, HIGH);
}
// === УЛУЧШЕННАЯ ФУНКЦИЯ С ПОЛНОЦЕННОЙ ДВОЙНОЙ БУФЕРИЗАЦИЕЙ (ДЛЯ DMA) ===
// Эта версия демонстрирует идею двойной буферизации,
// но для реальной работы требует DMA
void LCD_Clear_DoubleBuffer_DMA(uint16_t color565)
{
digitalWrite(CS, LOW);
Address_set(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
// Сбрасываем счетчики
linesSent = 0;
linesDrawn = 0;
// Заполняем первый буфер
for (int line = 0; line < LINES_PER_BUFFER; line++) {
fillLineWithColor(bufferA, line, color565);
}
linesDrawn = LINES_PER_BUFFER;
// Основной цикл отправки
while (linesSent < DISPLAY_HEIGHT) {
// Определяем, какой буфер отправляем
int blockIndex = linesSent / LINES_PER_BUFFER;
uint8_t* activeBuffer = (blockIndex % 2 == 0) ? bufferA : bufferB;
// Сколько строк отправляем в этом блоке
int linesToSend = LINES_PER_BUFFER;
if (linesSent + linesToSend > DISPLAY_HEIGHT) {
linesToSend = DISPLAY_HEIGHT - linesSent;
}
// Отправляем строки блока
for (int line = 0; line < linesToSend; line++) {
// Конвертируем в 18 бит
convertLineRGB565toRGB666(
activeBuffer + (line * LINE_BUFFER_16BIT),
sendBuffer,
LINE_PIXELS
);
// Отправляем
SPI.transfer(sendBuffer, SEND_BUFFER_SIZE);
linesSent++;
}
// Если не закончили экран, готовим следующий буфер
if (linesSent < DISPLAY_HEIGHT) {
// Очищаем следующий буфер (в реальном коде здесь может быть сложная отрисовка)
uint8_t* nextBuffer = (blockIndex % 2 == 0) ? bufferB : bufferA;
for (int line = 0; line < LINES_PER_BUFFER; line++) {
if (linesDrawn + line < DISPLAY_HEIGHT) {
fillLineWithColor(nextBuffer, line, color565);
}
}
linesDrawn += LINES_PER_BUFFER;
}
}
digitalWrite(CS, HIGH);
}
// === ТЕСТОВАЯ ФУНКЦИЯ С ГРАДИЕНТОМ ===
void LCD_GradientTest()
{
digitalWrite(CS, LOW);
Address_set(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
for (int y = 0; y < DISPLAY_HEIGHT; y += LINES_PER_BUFFER) {
int linesInBlock = LINES_PER_BUFFER;
if (y + linesInBlock > DISPLAY_HEIGHT) {
linesInBlock = DISPLAY_HEIGHT - y;
}
// Рисуем градиент в буфере
for (int line = 0; line < linesInBlock; line++) {
for (int x = 0; x < LINE_PIXELS; x++) {
uint16_t color565;
// Красивый градиент: красный меняется по X, синий по Y
uint8_t r = (x * 255) / LINE_PIXELS;
uint8_t g = 0;
uint8_t b = ((y + line) * 255) / DISPLAY_HEIGHT;
// Конвертируем 24-бит в RGB565
color565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
int offset = line * LINE_BUFFER_16BIT + x * 2;
currentDrawBuffer[offset] = color565 & 0xFF;
currentDrawBuffer[offset + 1] = (color565 >> 8) & 0xFF;
}
}
// Отправляем блок
for (int line = 0; line < linesInBlock; line++) {
convertLineRGB565toRGB666(
currentDrawBuffer + (line * LINE_BUFFER_16BIT),
sendBuffer,
LINE_PIXELS
);
SPI.transfer(sendBuffer, SEND_BUFFER_SIZE);
}
// Переключаем буферы
currentDrawBuffer = (currentDrawBuffer == bufferA) ? bufferB : bufferA;
}
digitalWrite(CS, HIGH);
}
// === НАСТРОЙКА ===
void setup()
{
Serial.begin(115200);
Serial.println("ILI9488 Double Buffer Test");
// Настройка пинов
pinMode(LED_L, OUTPUT);
pinMode(RS, OUTPUT);
pinMode(RESET, OUTPUT);
pinMode(CS, OUTPUT);
pinMode(MOSI, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(LED_B, OUTPUT);
// Устанавливаем начальные состояния
digitalWrite(RS, HIGH);
digitalWrite(RESET, HIGH);
digitalWrite(CS, HIGH);
digitalWrite(LED_B, LOW);
// Инициализация SPI на максимальной скорости
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV2); // 36 MHz (максимум для F103)
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
// Мигаем светодиодом
for (int i = 0; i < 3; i++) {
digitalWrite(LED_B, LOW);
delay(100);
digitalWrite(LED_B, HIGH);
delay(100);
}
// Инициализация дисплея
Serial.println("Init display...");
Lcd_Init();
Serial.println("Display ready");
// Включаем подсветку
digitalWrite(LED_L, HIGH);
}
// === ГЛАВНЫЙ ЦИКЛ ===
void loop()
{
// Тест 1: Заливка цветами с двойной буферизацией
Serial.println("Double buffer color fill test:");
unsigned long start = millis();
LCD_Clear_DoubleBuffer(RGB565_RED);
unsigned long time = millis() - start;
Serial.print("Red fill: "); Serial.print(time); Serial.println(" ms");
delay(500);
start = millis();
LCD_Clear_DoubleBuffer(RGB565_GREEN);
time = millis() - start;
Serial.print("Green fill: "); Serial.print(time); Serial.println(" ms");
delay(500);
start = millis();
LCD_Clear_DoubleBuffer(RGB565_BLUE);
time = millis() - start;
Serial.print("Blue fill: "); Serial.print(time); Serial.println(" ms");
delay(500);
// Тест 2: Градиент
Serial.println("Drawing gradient...");
start = millis();
LCD_GradientTest();
time = millis() - start;
Serial.print("Gradient: "); Serial.print(time); Serial.println(" ms");
delay(2000);
// Тест 3: Заливка разными цветами для проверки
Serial.println("Color sequence test:");
LCD_Clear_DoubleBuffer(RGB565_YELLOW);
delay(500);
LCD_Clear_DoubleBuffer(RGB565_CYAN);
delay(500);
LCD_Clear_DoubleBuffer(RGB565_MAGENTA);
delay(500);
LCD_Clear_DoubleBuffer(RGB565_WHITE);
delay(500);
LCD_Clear_DoubleBuffer(RGB565_BLACK);
delay(1000);
}
ну а работоспособность следующего кода вовсе не гарантирую)))
Процессор освобождается на время передачи (0.6 сек на экран)
Можно параллельно готовить следующие данные
Максимальная скорость SPI без участия CPU
// ILI9488 + STM32F103 (Blue Pill) - ДВОЙНАЯ БУФЕРИЗАЦИЯ + DMA
// Оптимизировано для максимальной производительности
#include <SPI.h>
// === РЕЖИМ ЦВЕТА ===
#define COLORS_262K // режим 18-разрядного цвета (3 байта на пиксель)
// === ПОДКЛЮЧЕНИЕ ===
#define CS PB11 // желтый
#define RESET PB10 // зеленый
#define RS PB1 // синий
#define MOSI PA7 // серый
#define SCK PA5 // черный
#define LED_L PB7 // оранжевый (подсветка)
#define LED_B PC13 // встроенный светодиод
// === ПАРАМЕТРЫ ДИСПЛЕЯ ===
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define LINE_PIXELS DISPLAY_WIDTH
// === РАЗМЕРЫ БУФЕРОВ ===
#define LINE_BUFFER_16BIT (LINE_PIXELS * 2) // 640 байт на строку
#define LINES_PER_BUFFER 8 // 8 строк в буфере
#define BUFFER_16BIT_SIZE (LINE_BUFFER_16BIT * LINES_PER_BUFFER) // 5120 байт
#define SEND_BUFFER_SIZE (LINE_PIXELS * 3) // 960 байт
// === БУФЕРЫ В ОЗУ ===
uint8_t bufferA[BUFFER_16BIT_SIZE] __attribute__((aligned(4)));
uint8_t bufferB[BUFFER_16BIT_SIZE] __attribute__((aligned(4)));
uint8_t sendBuffer[SEND_BUFFER_SIZE] __attribute__((aligned(4)));
// === УПРАВЛЯЮЩИЕ ФЛАГИ ===
volatile bool dmaTransferCompleted = true;
volatile uint16_t currentBlock = 0;
volatile uint16_t totalBlocks = 0;
volatile uint8_t* nextBufferPtr = NULL;
volatile uint16_t nextBufferLines = 0;
// === ПРОТОТИПЫ ФУНКЦИЙ ===
void Lcd_Init(void);
void Address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void Lcd_Write_Com(uint8_t c);
void Lcd_Write_Data(uint8_t d);
void DMA_Init(void);
void SPI_DMA_Transmit(uint8_t* data, uint32_t size);
void convertLineRGB565toRGB666(uint8_t* rgb565line, uint8_t* rgb666line, int pixels);
void fillLineWithColor(uint8_t* buffer, int lineIndex, uint16_t color565);
void prepareNextBuffer(uint16_t color565);
// === DMA и SPI регистры (для прямого доступа) ===
#define SPI1_DR_Address 0x4001300C
#define DMA1_Channel3 ((DMA_Channel_TypeDef *) DMA1_Channel3_BASE)
// === ИНИЦИАЛИЗАЦИЯ DMA ===
void DMA_Init(void)
{
// Включаем тактирование DMA1
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// Настройка DMA для SPI1_TX (канал 3)
// Режим: из памяти в периферию, инкремент адреса памяти, без цикла
// Приоритет: высокий, размер данных: 8 бит
// Отключаем канал перед настройкой
DMA1_Channel3->CCR &= ~DMA_CCR1_EN;
// Очищаем флаги
DMA1->IFCR = DMA_IFCR_CTCIF3 | DMA_IFCR_CHTIF3 | DMA_IFCR_CTEIF3 | DMA_IFCR_CGIF3;
// Настраиваем регистр CCR
DMA1_Channel3->CCR =
DMA_CCR_MINC | // Memory increment mode
DMA_CCR_DIR | // Read from memory
DMA_CCR_TCIE | // Transfer complete interrupt
(2 << 12) | // Channel priority: High
(0 << 10) | // Memory size: 8-bit
(0 << 8); // Peripheral size: 8-bit
// Устанавливаем адрес периферии (SPI1->DR)
DMA1_Channel3->CPAR = SPI1_DR_Address;
// Настраиваем прерывание DMA
NVIC_SetPriority(DMA1_Channel3_IRQn, 1);
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}
// === ОТПРАВКА ДАННЫХ ЧЕРЕЗ DMA ===
void SPI_DMA_Transmit(uint8_t* data, uint32_t size)
{
// Ждем завершения предыдущей передачи
while (!dmaTransferCompleted);
dmaTransferCompleted = false;
// Очищаем флаги
DMA1->IFCR = DMA_IFCR_CTCIF3 | DMA_IFCR_CHTIF3 | DMA_IFCR_CTEIF3;
// Устанавливаем адрес памяти и количество данных
DMA1_Channel3->CMAR = (uint32_t)data;
DMA1_Channel3->CNDTR = size;
// Включаем DMA
DMA1_Channel3->CCR |= DMA_CCR1_EN;
}
// === ОБРАБОТЧИК ПРЕРЫВАНИЯ DMA ===
extern "C" void DMA1_Channel3_IRQHandler(void)
{
// Проверяем флаг завершения передачи
if (DMA1->ISR & DMA_ISR_TCIF3)
{
// Очищаем флаг
DMA1->IFCR = DMA_IFCR_CTCIF3;
// Отмечаем завершение
dmaTransferCompleted = true;
// Переключаем LED для отладки (мигнет при каждом завершении блока)
digitalWrite(LED_B, !digitalRead(LED_B));
// Если есть следующий буфер, запускаем его отправку
if (nextBufferPtr != NULL)
{
// Отправляем следующий буфер
DMA1_Channel3->CMAR = (uint32_t)nextBufferPtr;
DMA1_Channel3->CNDTR = nextBufferLines * LINE_PIXELS * 3;
DMA1_Channel3->CCR |= DMA_CCR1_EN;
nextBufferPtr = NULL;
nextBufferLines = 0;
}
}
}
// === ФУНКЦИИ РАБОТЫ С ДИСПЛЕЕМ ===
void Lcd_Write_Bus(uint8_t d)
{
SPI.transfer(d);
}
void Lcd_Write_Com(uint8_t c)
{
GPIOB_BASE->BRR = 0x0002; // RS LOW
Lcd_Write_Bus(c);
GPIOB_BASE->BSRR = 0x0002; // RS HIGH
}
void Address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
Lcd_Write_Com(0x2A); // Column address
Lcd_Write_Data(x1 >> 8);
Lcd_Write_Data(x1);
Lcd_Write_Data(x2 >> 8);
Lcd_Write_Data(x2);
Lcd_Write_Com(0x2B); // Page address
Lcd_Write_Data(y1 >> 8);
Lcd_Write_Data(y1);
Lcd_Write_Data(y2 >> 8);
Lcd_Write_Data(y2);
Lcd_Write_Com(0x2C); // Memory write
}
// === ИНИЦИАЛИЗАЦИЯ ДИСПЛЕЯ ===
void Lcd_Init(void)
{
digitalWrite(RESET, HIGH);
delay(5);
digitalWrite(RESET, LOW);
delay(15);
digitalWrite(RESET, HIGH);
delay(15);
digitalWrite(CS, LOW);
Lcd_Write_Com(0x28); // Display off
delay(20);
Lcd_Write_Com(0x3A); // Интерфейс: 18 бит на пиксель
Lcd_Write_Data(0x66); // 0x66 = 18-битный режим
Lcd_Write_Com(0xF7); // Adjust control 3
Lcd_Write_Data(0xA9);
Lcd_Write_Data(0x51);
Lcd_Write_Data(0x2C);
Lcd_Write_Data(0x82);
Lcd_Write_Com(0xC0); // Power control 1
Lcd_Write_Data(0x10);
Lcd_Write_Data(0x10);
Lcd_Write_Com(0xC1); // Power control 2
Lcd_Write_Data(0x41);
Lcd_Write_Com(0xC5); // VCOM control
Lcd_Write_Data(0x00);
Lcd_Write_Data(0x22);
Lcd_Write_Data(0x80);
Lcd_Write_Data(0x40);
Lcd_Write_Com(0xB0); //
Lcd_Write_Data(0x00);
Lcd_Write_Com(0xB1); // Frame rate control
Lcd_Write_Data(0xB0);
Lcd_Write_Data(0x11);
Lcd_Write_Com(0xB4); // Display inversion control
Lcd_Write_Data(0x02);
Lcd_Write_Com(0xB6); // Display function control
Lcd_Write_Data(0x02);
Lcd_Write_Data(0x02);
Lcd_Write_Data(0x3B);
Lcd_Write_Com(0xB7); // Entry mode set
Lcd_Write_Data(0xC6);
Lcd_Write_Com(0x36); // Memory access control
Lcd_Write_Data(0x08); // 0x08 - ориентация
Lcd_Write_Com(0x11); // Exit Sleep
delay(120);
Lcd_Write_Com(0x29); // Display on
digitalWrite(CS, HIGH);
}
// === ПРЕОБРАЗОВАНИЕ СТРОКИ ИЗ RGB565 В RGB666 ===
void convertLineRGB565toRGB666(uint8_t* rgb565line, uint8_t* rgb666line, int pixels)
{
for (int i = 0; i < pixels; i++) {
uint16_t color565 = rgb565line[i*2] | (rgb565line[i*2 + 1] << 8);
// Извлекаем компоненты
uint8_t r5 = (color565 >> 11) & 0x1F;
uint8_t g6 = (color565 >> 5) & 0x3F;
uint8_t b5 = color565 & 0x1F;
// Преобразуем в 6 бит со сдвигом
rgb666line[i*3] = r5 << 3; // RRRRR000
rgb666line[i*3 + 1] = g6 << 2; // GGGGGG00
rgb666line[i*3 + 2] = b5 << 3; // BBBBB000
}
}
// === ЗАПОЛНЕНИЕ СТРОКИ ОДНИМ ЦВЕТОМ ===
void fillLineWithColor(uint8_t* buffer, int lineIndex, uint16_t color565)
{
int offset = lineIndex * LINE_BUFFER_16BIT;
for (int x = 0; x < LINE_PIXELS; x++) {
buffer[offset + x*2] = color565 & 0xFF;
buffer[offset + x*2 + 1] = (color565 >> 8) & 0xFF;
}
}
// === ГЛАВНАЯ ФУНКЦИЯ ЗАЛИВКИ С DMA ===
void LCD_Clear_DMA(uint16_t color565)
{
digitalWrite(CS, LOW);
Address_set(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
// 1. ПРЕДЗАГРУЗКА: заполняем ПЕРВЫЙ буфер
for (int line = 0; line < LINES_PER_BUFFER; line++) {
fillLineWithColor(bufferA, line, color565);
}
// 2. Конвертируем ПЕРВЫЙ буфер в sendBuffer (поблочно)
// Разбиваем на блоки для экономии времени
totalBlocks = (DISPLAY_HEIGHT + LINES_PER_BUFFER - 1) / LINES_PER_BUFFER;
currentBlock = 0;
// Конвертируем первую порцию
for (int line = 0; line < LINES_PER_BUFFER; line++) {
convertLineRGB565toRGB666(
bufferA + (line * LINE_BUFFER_16BIT),
sendBuffer + (line * LINE_PIXELS * 3),
LINE_PIXELS
);
}
// 3. Запускаем DMA для первой порции
SPI_DMA_Transmit(sendBuffer, LINES_PER_BUFFER * LINE_PIXELS * 3);
// 4. Пока DMA работает, готовим следующие блоки
for (int block = 1; block < totalBlocks; block++) {
int startLine = block * LINES_PER_BUFFER;
int linesInThisBlock = LINES_PER_BUFFER;
if (startLine + linesInThisBlock > DISPLAY_HEIGHT) {
linesInThisBlock = DISPLAY_HEIGHT - startLine;
}
// Выбираем буфер для заполнения (чередуем A и B)
uint8_t* fillBuffer = (block % 2 == 1) ? bufferB : bufferA;
// Заполняем буфер данными
for (int line = 0; line < linesInThisBlock; line++) {
fillLineWithColor(fillBuffer, line, color565);
}
// Ждем завершения предыдущей DMA передачи
while (!dmaTransferCompleted);
// Конвертируем в sendBuffer
for (int line = 0; line < linesInThisBlock; line++) {
convertLineRGB565toRGB666(
fillBuffer + (line * LINE_BUFFER_16BIT),
sendBuffer + (line * LINE_PIXELS * 3),
LINE_PIXELS
);
}
// Запускаем следующую DMA передачу
SPI_DMA_Transmit(sendBuffer, linesInThisBlock * LINE_PIXELS * 3);
}
// Ждем завершения последней передачи
while (!dmaTransferCompleted);
digitalWrite(CS, HIGH);
}
// === ОПТИМИЗИРОВАННАЯ ВЕРСИЯ С ДВОЙНЫМ БУФЕРОМ ДЛЯ DMA ===
void LCD_Clear_DoubleBuffer_DMA(uint16_t color565)
{
digitalWrite(CS, LOW);
Address_set(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
// 1. Заполняем оба буфера
for (int line = 0; line < LINES_PER_BUFFER; line++) {
fillLineWithColor(bufferA, line, color565);
fillLineWithColor(bufferB, line, color565);
}
// 2. Конвертируем буфер A в sendBuffer
for (int line = 0; line < LINES_PER_BUFFER; line++) {
convertLineRGB565toRGB666(
bufferA + (line * LINE_BUFFER_16BIT),
sendBuffer + (line * LINE_PIXELS * 3),
LINE_PIXELS
);
}
// 3. Запускаем DMA для буфера A
dmaTransferCompleted = false;
DMA1->IFCR = DMA_IFCR_CTCIF3;
DMA1_Channel3->CMAR = (uint32_t)sendBuffer;
DMA1_Channel3->CNDTR = LINES_PER_BUFFER * LINE_PIXELS * 3;
DMA1_Channel3->CCR |= DMA_CCR1_EN;
// 4. Конвертируем буфер B в sendBuffer (пока DMA работает)
for (int line = 0; line < LINES_PER_BUFFER; line++) {
convertLineRGB565toRGB666(
bufferB + (line * LINE_BUFFER_16BIT),
sendBuffer + (line * LINE_PIXELS * 3),
LINE_PIXELS
);
}
// 5. Отправляем оставшиеся блоки
for (int block = 2; block < totalBlocks; block++) {
// Ждем завершения предыдущей передачи
while (!dmaTransferCompleted);
// Запускаем следующую
dmaTransferCompleted = false;
DMA1->IFCR = DMA_IFCR_CTCIF3;
DMA1_Channel3->CMAR = (uint32_t)sendBuffer;
DMA1_Channel3->CNDTR = LINES_PER_BUFFER * LINE_PIXELS * 3;
DMA1_Channel3->CCR |= DMA_CCR1_EN;
// Конвертируем следующий блок (если есть)
int nextBlock = block + 1;
if (nextBlock < totalBlocks) {
int startLine = nextBlock * LINES_PER_BUFFER;
int linesInNext = LINES_PER_BUFFER;
if (startLine + linesInNext > DISPLAY_HEIGHT) {
linesInNext = DISPLAY_HEIGHT - startLine;
}
uint8_t* nextFillBuffer = (nextBlock % 2 == 0) ? bufferA : bufferB;
for (int line = 0; line < linesInNext; line++) {
fillLineWithColor(nextFillBuffer, line, color565);
convertLineRGB565toRGB666(
nextFillBuffer + (line * LINE_BUFFER_16BIT),
sendBuffer + (line * LINE_PIXELS * 3),
LINE_PIXELS
);
}
}
}
// Ждем завершения
while (!dmaTransferCompleted);
digitalWrite(CS, HIGH);
}
// === ЦВЕТА В RGB565 ===
#define RGB565_WHITE 0xFFFF
#define RGB565_RED 0xF800
#define RGB565_GREEN 0x07E0
#define RGB565_BLUE 0x001F
#define RGB565_BLACK 0x0000
#define RGB565_YELLOW 0xFFE0
#define RGB565_CYAN 0x07FF
#define RGB565_MAGENTA 0xF81F
// === НАСТРОЙКА ===
void setup()
{
Serial.begin(115200);
Serial.println("ILI9488 DMA Test");
// Настройка пинов
pinMode(LED_L, OUTPUT);
pinMode(RS, OUTPUT);
pinMode(RESET, OUTPUT);
pinMode(CS, OUTPUT);
pinMode(MOSI, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(LED_B, OUTPUT);
digitalWrite(RS, HIGH);
digitalWrite(RESET, HIGH);
digitalWrite(CS, HIGH);
digitalWrite(LED_B, HIGH);
// Инициализация SPI
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV2); // 36 MHz
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
// ВАЖНО: Включаем DMA для SPI вручную
SPI1->CR2 |= SPI_CR2_TXDMAEN;
// Инициализация DMA
DMA_Init();
// Мигаем для индикации старта
for (int i = 0; i < 3; i++) {
digitalWrite(LED_B, LOW);
delay(100);
digitalWrite(LED_B, HIGH);
delay(100);
}
// Инициализация дисплея
Serial.println("Init display...");
Lcd_Init();
Serial.println("Display ready");
digitalWrite(LED_L, HIGH);
Serial.println("Setup complete");
}
// === ГЛАВНЫЙ ЦИКЛ ===
void loop()
{
static int testPhase = 0;
unsigned long startTime;
switch(testPhase) {
case 0:
Serial.println("=== DMA Test: RED ===");
startTime = micros();
LCD_Clear_DMA(RGB565_RED);
Serial.print("Time: ");
Serial.print((micros() - startTime) / 1000);
Serial.println(" ms");
delay(1000);
testPhase++;
break;
case 1:
Serial.println("=== DMA Test: GREEN ===");
startTime = micros();
LCD_Clear_DMA(RGB565_GREEN);
Serial.print("Time: ");
Serial.print((micros() - startTime) / 1000);
Serial.println(" ms");
delay(1000);
testPhase++;
break;
case 2:
Serial.println("=== DMA Test: BLUE ===");
startTime = micros();
LCD_Clear_DMA(RGB565_BLUE);
Serial.print("Time: ");
Serial.print((micros() - startTime) / 1000);
Serial.println(" ms");
delay(1000);
testPhase = 0;
break;
}
}