А есть ли у ILI9488 режим 16-разрядного цвета?

Сейчас вожусь с таким дисплеем:
https://www.ozon.ru/product/3-5-dyuymovyy-tft-zhk-ekran-spi-serial-lcd-module-480x320-driver-ic-ili9488-podderzhka-emkostnogo-1949028523/
На самом изделии тип контроллера не указан, но продавец утверждает, что это ILI9488.
В рабочий режим его вогнать можно, т.е. последовательность команд инициализации для ILI9488 приводит этот дисплей в чувство.
Но!
По дэйташиту дисплей должен поддерживать следующие режимы:

  • 24-разрядная глубина цвета, 16 млн. цветов.
  • 18-разрядная глубина цвета, 262 тыс. цветов.
  • 16-разрядная глубина цвета, 65 тыс. цветов.
  • 3-разрядная глубина цвета, 8 цветов.

24-разрядный режим я не пробовал, поддерживаются 18-разрядный и 3-разрядный, а в режим 64к цветов мне его вогнать не удалось.

Исходник - ниже, написан для Blue Pill, т.к. приемлемую скорость работы с таким количеством точек на AVR получить невозможно.

В строках 4-7 устанавливается режим работы. Две из трех этих строк должны быть закомментированы.
Подключение описано в строках 8-13.
Убрал из исходника все лишнее, оставил только заливку экрана: белым, красным, зеленым, синим и черным цветами.
В функции Lcd_Init много строк закомментировано. Если их все или поблочно раскомментировать, визуальных изменений не происходит. Если заменить данные команд на то, что указано в комментариях к соответствующей строке - тоже.
Время заливки экрана на максимальной скорости SPI в режиме 24 разрядов составляет почти 0.6 с. При максимально оптимизированном soft-SPI - примерно столько же.
Хотелось все-таки перевести дисплей в 16-разрядный режим, чтобы уменьшить это время в полтора раза.
Можно, конечно, установить и 8-цветный режим, у него заливка составляет менее 0.1 с. Но с одной стороны, 8 цветов - это отнюдь не предел мечтаний, а с другой - в одном байте содержится 2 пикселя, что очень неудобно для пиксельной графики.

// ILI9488 + stm32f103 (BluePill)
#include <SPI.h>

//#define COLORS_8      // режим 3-разрядного цвета (два пикселя на байт, 8 цветов)
//#define COLORS_64K    // режим 16-разрядного цвета (2 байта на пиксель, 65536 цветов)
#define COLORS_262K   // режим 18-разрядного цвета (3 байта на пиксель, 262144 цвета)

#define CS    PB11  // yellow
#define RESET PB10  // green 
#define RS    PB1   // blue
#define MOSI  PA7   // grey
#define SCK   PA5   // black
#define LED_L PB7   // orange
#define LED_B PC13  // blink

#ifdef COLORS_262K  // блок задания констант для 18-разрядного цвета (3 байта на пиксель)

#define WHITE 0xFFFFFF
#define RED   0xFF0000
#define GREEN 0x00FF00
#define BLUE  0x0000FF
#define BLACK 0
#define SIZE (480*320)
#define DATA3A 0x66

#else
#ifdef COLORS_64K  // блок задания констант для 16-разрядного цвета (2 байта на пиксель)

#define WHITE 0xFFFF
#define RED   0xF800
#define GREEN 0x07E0
#define BLUE  0x001F
#define BLACK 0
#define SIZE (480*320)
#define DATA3A 0x55

#else
#ifdef COLORS_8  // блок задания констант для 3-разрядного цвета (два пикселя на байт)

#define WHITE 0x3F
#define RED   0x24
#define GREEN 0x12
#define BLUE  0x09
#define BLACK 0
#define SIZE (480*320/2)
#define DATA3A 0x11

#endif // COLORS_8
#endif // COLORS_64K
#endif // COLORS_262K      конец блока задания констант

void Lcd_Write_Bus(unsigned char d)
{
  SPI.transfer(d);
}

void Lcd_Write_Com(unsigned char c)  
{   
  GPIOB_BASE->BRR = 0x0002;     // пин PB1 : RS - LOW
  Lcd_Write_Bus(c);
  GPIOB_BASE->BSRR = 0x0002;    // пин PB1 : RS - HIGH
}

inline void Lcd_Write_Data(unsigned char d)
{
  Lcd_Write_Bus(d);
}

/*void Lcd_Write_Com_Data(unsigned char c,unsigned char d)
{
  Lcd_Write_Com(c);
  Lcd_Write_Data(d);
}*/

void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
        Lcd_Write_Com(0x2a);
	Lcd_Write_Data(x1>>8);
	Lcd_Write_Data(x1);
	Lcd_Write_Data(x2>>8);
	Lcd_Write_Data(x2);
        Lcd_Write_Com(0x2b);
	Lcd_Write_Data(y1>>8);
	Lcd_Write_Data(y1);
	Lcd_Write_Data(y2>>8);
	Lcd_Write_Data(y2);
	Lcd_Write_Com(0x2c); 							 
}

void Lcd_Init(void)
{
  digitalWrite(RESET,HIGH);
  delay(5); 
  digitalWrite(RESET,LOW);
  delay(15);
  digitalWrite(RESET,HIGH);
  delay(15);

  digitalWrite(CS,LOW);  //CS

    Lcd_Write_Com(0x28);  // display off
    delay(20);

    Lcd_Write_Com(0x3A);    // interface pixel format
    Lcd_Write_Data(DATA3A); // (0x66); 
    
    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); // 0x11
    Lcd_Write_Data(0x10); // 0x09

    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); // 0x0A
    Lcd_Write_Data(0x80);
    Lcd_Write_Data(0x40); // этого не было
 
    Lcd_Write_Com(0xB0);  //
    Lcd_Write_Data(0x00); 

    Lcd_Write_Com(0xB1);  // frame rate control (in normal mode/full colors)
    Lcd_Write_Data(0xB0); 
    Lcd_Write_Data(0x11); 

    Lcd_Write_Com(0xB4);  // Display invertion contrjl
    Lcd_Write_Data(0x02); 
  
    Lcd_Write_Com(0xB6);    // display function control 
    Lcd_Write_Data(0x02);
    Lcd_Write_Data(0x02);  // 0x22
    Lcd_Write_Data(0x3B); // этого не было
 
    Lcd_Write_Com(0xB7);    // entry mode set
    Lcd_Write_Data(0xC6);  

//    Lcd_Write_Com(0xBE);    // HS Lanes control
//    Lcd_Write_Data(0x00);   
//    Lcd_Write_Data(0x04); 
 
//    Lcd_Write_Com(0xE9);    // set image function
//    Lcd_Write_Data(0x00);   
 
    Lcd_Write_Com(0x36);  // memory access control
    Lcd_Write_Data(0x08);   // 0x08 - ориентация дисплея

/*
    Lcd_Write_Com(0xE0);    // positive gamma control 
    Lcd_Write_Data(0x00);  
    Lcd_Write_Data(0x07); 
    Lcd_Write_Data(0x10); 
    Lcd_Write_Data(0x09); 
    Lcd_Write_Data(0x17); 
    Lcd_Write_Data(0x0B); 
    Lcd_Write_Data(0x41); 
    Lcd_Write_Data(0x89); 
    Lcd_Write_Data(0x4B); 
    Lcd_Write_Data(0x0A); 
    Lcd_Write_Data(0x0C); 
    Lcd_Write_Data(0x0E); 
    Lcd_Write_Data(0x18); 
    Lcd_Write_Data(0x1B); 
    Lcd_Write_Data(0x0F); 

    Lcd_Write_Com(0xE1);    // negative gamma control
    Lcd_Write_Data(0x00);  
    Lcd_Write_Data(0x17); 
    Lcd_Write_Data(0x1A); 
    Lcd_Write_Data(0x04); 
    Lcd_Write_Data(0x0E); 
    Lcd_Write_Data(0x06); 
    Lcd_Write_Data(0x2F); 
    Lcd_Write_Data(0x45); 
    Lcd_Write_Data(0x43); 
    Lcd_Write_Data(0x02); 
    Lcd_Write_Data(0x0A); 
    Lcd_Write_Data(0x09); 
    Lcd_Write_Data(0x32); 
    Lcd_Write_Data(0x36); 
    Lcd_Write_Data(0x0F); 
*/
    Lcd_Write_Com(0x11);    //Exit Sleep 
    delay(120); 			
    Lcd_Write_Com(0x29);    //Display on 

    digitalWrite(CS,HIGH);
}

void LCD_Clear(unsigned int c)                   
{	
  digitalWrite(CS,LOW);
  Address_set(0,0,320,480);
  for(unsigned int i = 0; i< SIZE; i++)
    {
#ifdef COLORS_262K
    Lcd_Write_Data((c >> 16) & 0xFF);
    Lcd_Write_Data((c >> 8) & 0xFF);
    Lcd_Write_Data((c) & 0xFF);
#else
#ifdef COLORS_64K
    Lcd_Write_Data((c >> 8) & 0xFF);
    Lcd_Write_Data((c) & 0xFF);
#else
#ifdef COLORS_8
    Lcd_Write_Data((c) & 0x3F);
#endif
#endif
#endif
  }
  delay(300); // что можно было рассмотреть в 8-цветном режиме
  digitalWrite(CS,HIGH);   
}

void setup()
{
  Serial.begin(115200);
  Serial.print("Start");
  Serial.print(" Phase 1 (PA7)");
  
  pinMode(LED_L,OUTPUT);
  pinMode(RS,OUTPUT);
  pinMode(RESET,OUTPUT);
  pinMode(CS,OUTPUT);
  pinMode(MOSI,OUTPUT);
  pinMode(SCK,OUTPUT);

  pinMode(LED_B,OUTPUT);

//  digitalWrite(LED_L, HIGH);
  digitalWrite(RS, HIGH);
  digitalWrite(RESET, HIGH);
  digitalWrite(CS, HIGH);
  digitalWrite(MOSI, HIGH);
  digitalWrite(SCK, HIGH);
  digitalWrite(LED_B, LOW);
  delay(100);
  digitalWrite(LED_B, HIGH);
  delay(100);
  digitalWrite(LED_B, LOW);
  delay(100);
  digitalWrite(LED_B, HIGH);
  delay(100);

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2); // 4 MHz (half speed)
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
    
  Serial.println("Before Init");
  Lcd_Init();
  Serial.print("After Init * ");
  Serial.println(DATA3A);
}

void loop() { 
   LCD_Clear(WHITE);
  digitalWrite(LED_B, LOW);
  unsigned long t3 = millis();
   LCD_Clear(RED);
  unsigned long t4 = millis();
  Serial.print("Draw ");
  Serial.print(t4-t3-300);
  Serial.print(" ms, ");
  Serial.print((320*480)/(t4-t3-300));
  Serial.print(" in 1 ms (");
  Serial.print((DATA3A >> 4) & 0x0F);
  Serial.print(DATA3A & 0x0F);
  Serial.println(")");
  digitalWrite(LED_B, HIGH);
   LCD_Clear(GREEN);
  digitalWrite(LED_B, LOW);
   LCD_Clear(BLUE);
  digitalWrite(LED_B, HIGH);
   LCD_Clear(BLACK);
  digitalWrite(LED_B, LOW);
  digitalWrite(LED_B, HIGH);
}

The ILI9488 controller natively supports 16-bit color (RGB565) primarily in parallel mode, but requires color conversion to 18-bit (RGB666) when using 4-wire SPI. RGB565 uses 5 bits for Red, 6 bits for Green, and 5 bits for Blue, offering 65K colors. SPI requires 3 bytes per pixel.

А откуда это?
У меня ни в дэйташите:
Version: V100
Document No: ILI9488_IDT_V100_20121128
ни в дэйташите на считающийся совместимым контроллер ST7796S такого нет.

Кроме того, на стр. 121, вроде бы, явно указано наличие 65K-Сolors, тогда как 24-разрядный режим отсутствует.

Отсюда:

А первоисточник не известен?
Личное мнение, конечно, хорошо, но хотелось бы более официальной информации. Я ведь тоже могу заявить, раз у меня не получилось, значит, это невозможно.

а там на дисплее есть перемычка для 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;
}
}

Нет, к сожалению. Но тут ведь вся соль в том, что эти ограничения определяются SPI, а не контроллером ili9488. А точнее их общим взаимодействием :slight_smile:

Что-то я этого не понял.
Можно понять, что для того, чтобы передать 18 бит, контроллер должен передать не меньше 24. Но почему нельзя передать 16?

И еще.
Вопрос по поводу источника я задал потому, что на вопрос по-русски (№1) получил ответ по-английски (№2). Думал, это цитата откуда-нибудь.

Это был текст от ИИ гугла, а в качестве источника он указал ссылку на гитхаб.

Из того что мне вчера гугл выдал я понял, что внутренняя структура хранения данных в видеопамяти у ili9488 является 18 битной. И поэтому когда по последовательному интерфейсу передаются данные он ожидает их именно в 18-битном формате.

Да, полезный дисплей, если удастся сделать библиотеку под уно читать-писать данные из-в него, обязательно публикуйте.

Но ведь трехбитные данные он воспринимает нормально.
Для того, чтобы он знал, как именно ему воспринимать данные, служит регистр 0x3A. Если туда записать 1, он знает, что в одном байте ему передаются два пикселя по три бита в каждом. Если ему в этот регистр записать 6, он знает, что ему передадут 3 байта, в старших 6 битах которых будут составляющие цвета. Если в этот регистр записать 7, он будет знать, что ему нужны все 24 бита из переданных трех байтов. А если в регистре число 5, нужно получать два байта и побитно распихивать их по трем цветам. Так написано в дэйташите. Почему так не работает, непонятно. Скорее всего, при разработке контроллера допущена ошибка, и этот факт неработоспособности описан где-то в errata, но мне это неизвестно.

Я такой вариант даже не рассматриваю - на AVR это будет работать непозволительно медленно. Мне на stm32 скорости не хватает. В принципе дисплей способен отображать не только 262 тысячи цветов, но и 16 миллионов. Но я бы предпочел ограничиться 65 тысячами, чтобы ускорить работу. Подумываю даже над переходом в 8-цветный режим.

А почему? Часть экрана память, часть - вывод графики из памяти. Или вот рисовалка на уно, рисует стилусом адекватно, но не может сохранить рисунок, а тут бац и в монитор порта списал данные по пикселям.

возьмите STM32 пожирнее.

P.S. Не знаю какова максимальная скорость у ILI9488 по параллельному интерфейсу, но например я на il9341 уперся в скорость SPI 138 МГц , пытаюсь дать больше - на дисплее сразу же каша/артефакты. В итоге остановился на 55 кадрах в секунду 320240RGB565.

А смысл, если более 80% времени SPI простаивает?
Я, кстати, писал, что soft-SPI работает практически на той же скорости, что и hard-SPI на своей максимальной частоте.

есть DMA, постоянно обновляющее дисплей, остается только в видеопамять писать для рисования картинки.

Так DMA именно тем и занимается, что переносит данные из оперативной памяти в видеопамять.
Или Вы что-то другое хотели сказать?

Я хотел сказать, что используя DMA, SPI простаивать не будет.

Вернулся к этому вопросу.
Написал класс для работы с этим дисплеем для stm32f103 с использованием DMA.
Овчинка стоила выделки: если раньше для заливки экрана требовалось примерно 0.6 секунды (напомню, примерно одинаковая скорость как при максимально оптимизированном софтверном варианте, так и на максимальной частоте для аппаратного режима), то теперь это время уменьшилось в 6 раз (что и ожидалось, исходя из оптимистичных прогнозов) и составило порядка 0.1 сек.
Остальные цифры примерно такие (есть небольшой люфт в зависимости от способа измерения):

  1. передача 1 байта через SPI в однобайтовом режиме (SPI_1): 1.4 мкс,
  2. установка адресов окна отображения (11 байт) (SPI_1): 15 мкс,
  3. отображение символа 6*8 (SPI_1): 0.2 мс,
  4. отображение символа 12*16 (SPI_1): 0.8 мс,
  5. заливка всего экрана одним цветом (SPI_1): 600 мс,
  6. заливка всего экрана символами (SPI_1): 650 мс,
  7. передача 1 байта через SPI в режиме DMA (SPI-DMA), в среднем: 0.24 мкс,
  8. установка адресов окна отображения (11 байт) (SPI-DMA): 22 мкс,
  9. отображение символа 6*8 (SPI-DMA): 0.075 мс,
  10. отображение символа 12*16 (SPI-DMA): 0.18 мс,
  11. заливка всего экрана одним цветом (SPI-DMA): 110 мс,
  12. заливка всего экрана символами 6*8 (SPI-DMA): 240 мс,
  13. заливка всего экрана символами 12*16 (SPI-DMA): 145 мс,

Обращает на себя внимание сравнение строк 2 и 8: при длине передаваемой последовательности 4 байта накладные расходы при передаче 4-х байтов перекрывают выигрыш от увеличения скорости, а передавать бОльшими фрагментами нельзя, т.к. нужно неоднократно переключать линию команда/данные.
Строка 7 указана в предположении длинной последовательности. Передавать менее 8-10 байт через DMA нерационально.
Другими словами, функция рисования пикселя меньше чем в 20 мкс не укладывается, а если посчитать, заполнение экрана в таком режиме составит более трех секунд.
Собственно, при правильной организации процесса более чем в 90% случаев пиксельная графика и не нужна. Достаточно вывода текста, рисования горизонтальных и вертикальных линий, рамок и прямоугольников, а также произвольного битмапа, а все это может быть оптимально выведено через DMA.
В заключение следует сказать, что организация видеопамяти выполнена несколько необычно: порядок пикселей: в столбце сверху вниз, столбцы один за другим слева направо. Т.е. с точки зрения экрана он повернут на 90 градусов и зеркально отражен.

Итак, текст программы проверки (.ino):

Спойлер
// ILI9488 serial + stm32f103 (BluePill) DMA

#include <SPI.h>
#include "scr_ili9488.h"

scr_ili9488 Lcd;

char s[95+1]; // строка для вывода

void setup()
{
  Serial.begin(115200);
  delay(2000);
  Serial.print("\n\nStart ili9488 stm32f103 DMA");
  Serial.print(" Phase 1 (PA7)");

  Lcd.Init();

  for(int i = 0; i < 95; i++) s[i] = i+32; // готовим строку для текстового вывода (ASCII only)
  s[95] = 0;

  unsigned long td0 = micros();
  for(int i = 0; i < 1; i++) Lcd.Address_set(0,0,1,1);
  unsigned long td1 = micros();
  for(int i = 0; i < 1; i++) Lcd.putChar_x1('A', 0, 1);
  unsigned long td2 = micros();
  for(int i = 0; i < 1; i++) Lcd.putChar_x2('A', 0, 1);
  unsigned long td3 = micros();
  Serial.print("Addr(us): ");
  Serial.print(td1-td0);
  Serial.print(", char_1(us): ");
  Serial.print(td2-td1);
  Serial.print(", char_2(us): ");
  Serial.println(td3-td2);
}

void loop() { 
  static int mode = 0; // ориентация экрана
  Lcd.setOrientation(mode);
  mode = (mode + 1) & 3;
  Lcd.clearCoords(); // обнуляем координаты вывода текста

   Lcd.Clear(WHITE);
  digitalWrite(LED_B, LOW);
   Lcd.putChar_x1('A', 120, 80);
   Lcd.putChar_x1('B', 126, 80);
  unsigned long tt0 = micros();
   Lcd.putChar_x1('C', 132, 80);
  unsigned long tt1 = micros();
   Lcd.putChar_x2('A', 120, 96);
   Lcd.putChar_x2('B', 132, 96);
  unsigned long tt2 = micros();
   Lcd.putChar_x2('C', 144, 96);
  unsigned long tt3 = micros();
  Serial.print("ch_x1(us): ");
  Serial.print(tt1-tt0);
  Serial.print(", x2: ");
  Serial.print(tt3-tt2);
   Lcd.setCurrentX(120);
   Lcd.setCurrentY(120);
  Lcd.putString_x2("C");
   Lcd.setCurrentY(128);
  Lcd.putString_x1("2");
   Lcd.setCurrentY(120);
  Lcd.putString_x2("H");
   Lcd.setCurrentY(128);
  Lcd.putString_x1("5");
   Lcd.setCurrentY(120);
  Lcd.putString_x2("OH");
   Lcd.setCurrentX(0);
   Lcd.setCurrentY(0);
   delay(2000);
   unsigned long t0 = millis();
   unsigned int cc[4] = {RED, GREEN, BLUE, BLACK}; // для перебора цветов текста
   static int ncc = 0;
   for(int i = 0; i < 17; i++) {
     Lcd.putString_x1(s);
     Lcd.setColor(cc[++ncc & 3]);
   }
   unsigned long t1 = millis();
   Lcd.setCurrentX(0);
   Lcd.setCurrentY(Lcd.getHeight()/2);
   for(int i = 0; i < 5; i++) {
     Lcd.putString_x2(s);
     Lcd.setColor(cc[++ncc & 3]);
   }
   unsigned long t2 = millis();
  Serial.print(", str_x1(ms): ");
  Serial.print(t1-t0);
  Serial.print(", x2: ");
  Serial.print(t2-t1);
  delay(2000);
  unsigned long t3 = millis();
   Lcd.Clear(RED);
  unsigned long t4 = millis();
  Serial.print(", ClrScr: ");
  Serial.print(t4-t3);
  Serial.print(" ms, ");
  Serial.print(SIZE/(t4-t3));
  Serial.print(" in 1 ms, ");
  digitalWrite(LED_B, HIGH);
  Lcd.Bar(50, 50, Lcd.getWidth()-50, Lcd.getHeight()-50, {BLUE});
   Lcd.Clear(GREEN);
  digitalWrite(LED_B, LOW);
   Lcd.Clear(BLUE);
  digitalWrite(LED_B, HIGH);
   Lcd.Clear(BLACK);
  digitalWrite(LED_B, LOW);
  unsigned long tt5 = micros();
   Lcd.rectangle(17, 17, 20, 20, {WHITE});
  unsigned long tt6 = micros();
   Lcd.rectangle(15, 15, 22, 22, {WHITE});
  unsigned long tt7 = micros();
   Lcd.rectangle(13, 13, 28, 28, {WHITE});
  unsigned long tt8 = micros();
   Lcd.rectangle(11, 11, 42, 42, {WHITE});
  unsigned long tt9 = micros();
   Lcd.rectangle(9, 9, 72, 72, {WHITE});
  unsigned long ttA = micros();
   Lcd.rectangle(7, 7, 134, 134, {WHITE});
  unsigned long ttB = micros();
   Lcd.rectangle(5, 5, 260, 260, {WHITE});
  unsigned long ttC = micros();
  Serial.print("rect(us)[4]: ");
  Serial.print(tt6-tt5);
  Serial.print(", [8]: ");
  Serial.print(tt7-tt6);
  Serial.print(", [16]: ");
  Serial.print(tt8-tt7);
  Serial.print(", [32]: ");
  Serial.print(tt9-tt8);
  Serial.print(", [64]: ");
  Serial.print(ttA-tt9);
  Serial.print(", [128]: ");
  Serial.print(ttB-ttA);
  Serial.print(", [256]: ");
  Serial.println(ttC-ttB);
  Lcd.putExtBuffer(pict, 20, 202, 136, 93); // x, y, ширина, высота
   delay(3000);
  digitalWrite(LED_B, HIGH);
}

и сам класс
scr_ili9488.h:

Спойлер
#ifndef SCR_ILI9488H
#define SCR_ILI9488H

// ILI9488 serial + stm32f103 (BluePill) DMA

#include <SPI.h>
#include "a_Small_Rus.c"
#include "pict.c"

#define CS    PB11  // yellow
#define RESET PB10  // green 
#define RS    PB1   // blue
#define MOSI  PA7   // grey
#define SCK   PA5   // black
#define LED_L PB7   // orange
#define LED_B PC13  // blink

#define BITS_PER_BYTE 8 // количество битов в байте
#define WIDTH 480
#define HEIGHT 320
#define SIZE (WIDTH*HEIGHT)
#define CH_X1_WIDTH   6
#define CH_X1_HEIGHT  8
#define CH_X2_WIDTH  12
#define CH_X2_HEIGHT 16
#define CH_BUFF_LEN (CH_X2_WIDTH*CH_X2_HEIGHT*3) // [576] длина буфер для символа размера 2

#define DATA3A 0x66 // режим глубины цвета 18/24 разряда

union colorType {
  unsigned int d;
  struct {
    unsigned char b;
    unsigned char g;
    unsigned char r;
    unsigned char y;
  };
};

#define WHITE 0x00FFFFFF
#define RED   0x00FF0000
#define GREEN 0x0000FF00
#define BLUE  0x000000FF
#define BLACK 0

class scr_ili9488 {
public:
// методы в ccp файле

    void Init(void); // инициализация дисплея
    void setOrientation(int mode); // ориентация дисплея (от 0 до 3 через 90 градусов по часовой)
    void putChar_x1(unsigned char ch, unsigned int x, unsigned int y); // буфер 48 пикс, 144 байта; столбец 8 пикс, 24 байта
    void putChar_x2(unsigned char ch, unsigned int x, unsigned int y); // буф 192 пкс, 576 б; столб 32 пкс, 96 б; столб/2 16 п, 48б
    void putString_x1(char * ss);
    void putString_x2(char * ss);

    scr_ili9488() {
        width = WIDTH;
        height = HEIGHT;
        CurrentColor = {RED}; // цвет символа
        BackgroundColor = {WHITE}; // цвет фона
        CurrentX = 0;
        CurrentY = 0;
    };
    
    ~scr_ili9488() {};
    
    void Write_Bus(unsigned char d) { // передача 1 байта ни дисплей (~1.3 us)
      SPI.transfer(d);
    }

    void Write_Com(unsigned char c) { // дать команду на дисплей
      GPIOB_BASE->BRR = 0x0002;   // пин PB1 : RS - LOW
      Write_Bus(c);
      GPIOB_BASE->BSRR = 0x0002;  // пин PB1 : RS - HIGH
    }

    inline void Write_Data(unsigned char d) { // передать данные
      Write_Bus(d);
    }

    void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2) { // координаты окна вывода (~15 us)
        Write_Com(0x2a);
      Write_Data(y1 >> BITS_PER_BYTE);
      Write_Data(y1);
      Write_Data(y2 >> BITS_PER_BYTE);
      Write_Data(y2);
        Write_Com(0x2b);
      Write_Data(x1 >> BITS_PER_BYTE);
      Write_Data(x1);
      Write_Data(x2 >> BITS_PER_BYTE);
      Write_Data(x2);
        Write_Com(0x2c);               
    }

    void setColor(unsigned int c) { CurrentColor.d = c; }
    void setColor(colorType c) { CurrentColor = c; }
    void Background(unsigned int c) { BackgroundColor.d = c; }
    void Background(colorType c) { BackgroundColor = c; }

    void putChBuffer(unsigned int x, unsigned int y, unsigned int w, unsigned int h) { // x, y, ширина, высота
      digitalWrite(CS,LOW);
      Address_set(x, y, x + w - 1, y + h - 1);
      SPI.dmaSend(chBuffer, w*h*3);
      digitalWrite(CS,HIGH);   
    }

    void Bar_(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, colorType c) { // не используемый вариант для сравнения
      digitalWrite(CS,LOW);
      Address_set(x1, y1, x2, y2);
      for(unsigned int i = 0; i< (x2-x1+1)*(y2-y1+1); i++) {
        Write_Data(c.r);
        Write_Data(c.g);
        Write_Data(c.b);
      }
      digitalWrite(CS,HIGH);   
    }
    void putExtBuffer(const unsigned char * c, unsigned int x, unsigned int y, unsigned int w, unsigned int h) { // x, y, ширина, высота
      digitalWrite(CS,LOW);
      Address_set(x, y, x + w - 1, y + h - 1);
      SPI.dmaSend(c, w*h*3);
      digitalWrite(CS,HIGH);   
    }
    void Bar(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, colorType c) {
      GPIOB_BASE->BRR = 0x0800;   // пин PB11 : CS - LOW
      Address_set(x1, y1, x2, y2);
      unsigned int len = (x2-x1+1)*(y2-y1+1);
      unsigned int len3 = len*3;
      if(len3 <= CH_BUFF_LEN) {
        unsigned int j = 0;
        for(unsigned int i = 0; i < len; i++) {
          chBuffer[j+0] = c.r;
          chBuffer[j+1] = c.g;
          chBuffer[j+2] = c.b;
          j += 3;
        }
        SPI.dmaSend(chBuffer, len3);
      } else {
        unsigned int j = 0;
        for(unsigned int i = 0; i < (CH_BUFF_LEN / 3); i++) {
          chBuffer[j+0] = c.r;
          chBuffer[j+1] = c.g;
          chBuffer[j+2] = c.b;
          j += 3;
        }
        while (len3) {
          if(len3 >= CH_BUFF_LEN) {
            SPI.dmaSend(chBuffer, CH_BUFF_LEN);
            len3 -= CH_BUFF_LEN;
          } else {
            SPI.dmaSend(chBuffer, len3);
            len3 = 0;
          }
        }
      }
      GPIOB_BASE->BSRR = 0x0800;  // пин PB11 : CS - HIGH
    }

    void horizLine(unsigned int x1, unsigned int x2, unsigned int y, colorType c) {    Bar(x1, y, x2, y, c);    }
    void vertLine(unsigned int x, unsigned int y1, unsigned int y2, colorType c) {    Bar(x, y1, x, y2, c);    }
    void rectangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, colorType c) {
      horizLine(x1, x2, y1, c);
      vertLine(x1, y1, y2, c);
      horizLine(x1, x2, y2, c);
      vertLine(x2, y1, y2, c);
    }
    
    void Clear(unsigned int c) {  // очистка экрана выбранным цветом
      Bar(0, 0, width-1, height-1, {c});
    }

    void Clear(colorType c) {  // очистка экрана выбранным цветом
      Bar(0, 0, width-1, height-1, c);
    }

    unsigned int getWidth() {  return width;  }
    unsigned int getHeight() {  return height;  }

    void clearCoords() {  CurrentX = 0;  CurrentY = 0;  }
    void setCurrentX(unsigned int x) {    CurrentX = x;    }
    void setCurrentY(unsigned int y) {    CurrentY = y;    }

private:  
    void InitScreen(void); // 
    
    unsigned int width;  // текущие размеры экрана в пикселях (изменяются при повороте)
    unsigned int height;  // текущие размеры экрана в пикселях (изменяются при повороте)
    colorType CurrentColor; // цвет символа
    colorType BackgroundColor; // цвет фона
    unsigned int CurrentX;  // текущая позиция вывода текста в пикселях
    unsigned int CurrentY;  // текущая позиция вывода текста в пикселях
    byte chBuffer[CH_BUFF_LEN]; // [576] буфер для символа размера 2
};

#endif // SCR_ILI9488H

scr_ili9488.cpp

Спойлер
#include "scr_ili9488.h"

void scr_ili9488::Init(void) {
  pinMode(LED_L,OUTPUT);
  pinMode(RS,OUTPUT);
  pinMode(RESET,OUTPUT);
  pinMode(CS,OUTPUT);
  pinMode(MOSI,OUTPUT);
  pinMode(SCK,OUTPUT);

  pinMode(LED_B,OUTPUT);

//  digitalWrite(LED_L, HIGH);
  digitalWrite(RS, HIGH);
  digitalWrite(RESET, HIGH);
  digitalWrite(CS, HIGH);
  digitalWrite(MOSI, HIGH);
  digitalWrite(SCK, HIGH);
  digitalWrite(LED_B, LOW);
  delay(100);
  digitalWrite(LED_B, HIGH);
  delay(100);
  digitalWrite(LED_B, LOW);
  delay(100);
  digitalWrite(LED_B, HIGH);
  delay(100);

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2); // 4 MHz (half speed)
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
    
  Serial.println("Before Init");
  InitScreen();
  Serial.print("After Init * ");
  Serial.println(DATA3A);
  
}

void scr_ili9488::InitScreen(void) // инициализация дисплея
{
  digitalWrite(RESET,HIGH);
  delay(5); 
  digitalWrite(RESET,LOW);
  delay(15);
  digitalWrite(RESET,HIGH);
  delay(15);

  digitalWrite(CS,LOW);  //CS

    Write_Com(0x28);  // display off
    delay(20);

    Write_Com(0x3A);    // interface pixel format
    Write_Data(DATA3A); // (0x66); 
    
    Write_Com(0xF7);  // adjust control 3
    Write_Data(0xA9); 
    Write_Data(0x51); 
    Write_Data(0x2C); 
    Write_Data(0x82);  

    Write_Com(0xC0);  // power control 1
    Write_Data(0x10); // 0x11
    Write_Data(0x10); // 0x09

    Write_Com(0xC1);  // power control 2
    Write_Data(0x41); 

    Write_Com(0xC5);  // VCOM control
    Write_Data(0x00); 
    Write_Data(0x22); // 0x0A
    Write_Data(0x80);
    Write_Data(0x40); // этого не было
 
    Write_Com(0xB0);  //
    Write_Data(0x00); 

    Write_Com(0xB1);  // frame rate control (in normal mode/full colors)
    Write_Data(0xB0); 
    Write_Data(0x11); 

    Write_Com(0xB4);  // Display invertion contrjl
    Write_Data(0x02); 
  
    Write_Com(0xB6);    // display function control 
    Write_Data(0x02);   //0x02         | bypass | RCM | RM | DM | PTG[1] | PTG[0] | PT[1]  | PT[0] /// 82,22-не раб, 42,12,0a,06,00,01-без изм
    Write_Data(0x02);  // 0x22         |   0    | GS  | SS | SM | ISC[3] | ISC[2] | ISC[1] | ISC[0] /// SS-право/лево, GS-верх/низ /// 42-слева направо,22-снизу вверх,62-180 град,12-через строчку,0a,06,00,01-без изм
    Write_Data(0x3B); // этого не было |   0    |  0  |  NL[5:0]
 
    Write_Com(0xB7);    // entry mode set
    Write_Data(0xC6);  

//    Write_Com(0xBE);    // HS Lanes control
//    Write_Data(0x00);   
//    Write_Data(0x04); 
 
//    Write_Com(0xE9);    // set image function
//    Write_Data(0x00);   
 
    Write_Com(0x36);  // memory access control             | MY | MX | MV | ML | BGR | MH | - | - |
    Write_Data(0x08);   // 0x08(0x0a) - ориентация дисплея 88-справа налево(-),48-снизу вверх(-),c8-180град(*),a8-90по час.320х320справа(*),28-270по час.320х320слева(-),18-без.изм,e8-справа налево320х320(-),68-270град(*)

/*
    Write_Com(0xE0);    // positive gamma control 
    Write_Data(0x00);  
    Write_Data(0x07); 
    Write_Data(0x10); 
    Write_Data(0x09); 
    Write_Data(0x17); 
    Write_Data(0x0B); 
    Write_Data(0x41); 
    Write_Data(0x89); 
    Write_Data(0x4B); 
    Write_Data(0x0A); 
    Write_Data(0x0C); 
    Write_Data(0x0E); 
    Write_Data(0x18); 
    Write_Data(0x1B); 
    Write_Data(0x0F); 

    Write_Com(0xE1);    // negative gamma control
    Write_Data(0x00);  
    Write_Data(0x17); 
    Write_Data(0x1A); 
    Write_Data(0x04); 
    Write_Data(0x0E); 
    Write_Data(0x06); 
    Write_Data(0x2F); 
    Write_Data(0x45); 
    Write_Data(0x43); 
    Write_Data(0x02); 
    Write_Data(0x0A); 
    Write_Data(0x09); 
    Write_Data(0x32); 
    Write_Data(0x36); 
    Write_Data(0x0F); 
*/
    Write_Com(0x11);    //Exit Sleep 
    delay(120);       
    Write_Com(0x29);    //Display on 

    digitalWrite(CS,HIGH);
}

void scr_ili9488::setOrientation(int mode) {
  digitalWrite(CS,LOW);  //CS
  Write_Com(0x28);  // display off
  delay(20);
  switch(mode) {
    case 0: 
      Write_Com(0x36);
      Write_Data(0x08);
      width = WIDTH;
      height = HEIGHT;
      break;
    case 1: 
      Write_Com(0x36);
      Write_Data(0xa8);
      width = HEIGHT;
      height = WIDTH;
      break;
    case 2: 
      Write_Com(0x36);
      Write_Data(0xc8);
      width = WIDTH;
      height = HEIGHT;
      break;
    case 3: 
      Write_Com(0x36);
      Write_Data(0x68);
      width = HEIGHT;
      height = WIDTH;
      break;
  }
    delay(120);       
    Write_Com(0x29);    //Display on 
    digitalWrite(CS,HIGH);
}

void scr_ili9488::putChar_x1(unsigned char ch, unsigned int x, unsigned int y) { // буфер 48 пикс, 144 байта; столбец 8 пикс, 24 байта
  int k = 0; // адрес внутри буфера chBuffer
  for(int i = 0; i < 5; i++) {
    unsigned char c = SmallFont[4 + (ch - 32)*5 + i];
    for(int j = 0; j < BITS_PER_BYTE; j++) { // первые 5 столбцов
      if(c & 1) {
        chBuffer[k]   = CurrentColor.r;
        chBuffer[k+1] = CurrentColor.g;
        chBuffer[k+2] = CurrentColor.b;
      } else {
        chBuffer[k]   = BackgroundColor.r;
        chBuffer[k+1] = BackgroundColor.g;
        chBuffer[k+2] = BackgroundColor.b;
      }
      c /= 2;
      k += 3;
    }
  }
  for(int i = 0; i < BITS_PER_BYTE; i++) { 
    chBuffer[k]   = BackgroundColor.r; // последний (6-й) столбец
    chBuffer[k+1] = BackgroundColor.g;
    chBuffer[k+2] = BackgroundColor.b;
    k += 3;
  }
  putChBuffer(x, y, CH_X1_WIDTH, CH_X1_HEIGHT);
}

void scr_ili9488::putChar_x2(unsigned char ch, unsigned int x, unsigned int y) { // буф 192 пкс, 576 б; столб 32 пкс, 96 б; столб/2 16 п, 48б
  int k = 0; // адрес внутри буфера chBuffer
  for(int i = 0; i < 5; i++) {
    unsigned char c = SmallFont[4 + (ch - 32)*5 + i];
    for(int j = 0; j < BITS_PER_BYTE; j++) { // первые 5 столбцов
      if(c & 1) {
        chBuffer[k]   = CurrentColor.r;    // заполняем блок из 4-х смежных пикселей
        chBuffer[k+1] = CurrentColor.g;
        chBuffer[k+2] = CurrentColor.b;
        chBuffer[k+3] = CurrentColor.r;    // соседний пиксель снизу (3 байта на пиксель)
        chBuffer[k+4] = CurrentColor.g;
        chBuffer[k+5] = CurrentColor.b;
        chBuffer[k+48] = CurrentColor.r;   // соседний пиксель справа (16 пикселей по 3 байта)
        chBuffer[k+49] = CurrentColor.g;  
        chBuffer[k+50] = CurrentColor.b;
        chBuffer[k+51] = CurrentColor.r;   // соседний пиксель снизу-справа (16*3 + 3)
        chBuffer[k+52] = CurrentColor.g;
        chBuffer[k+53] = CurrentColor.b;
      } else {
        chBuffer[k]   = BackgroundColor.r;
        chBuffer[k+1] = BackgroundColor.g;
        chBuffer[k+2] = BackgroundColor.b;
        chBuffer[k+3] = BackgroundColor.r;
        chBuffer[k+4] = BackgroundColor.g;
        chBuffer[k+5] = BackgroundColor.b;
        chBuffer[k+48] = BackgroundColor.r;
        chBuffer[k+49] = BackgroundColor.g;
        chBuffer[k+50] = BackgroundColor.b;
        chBuffer[k+51] = BackgroundColor.r;
        chBuffer[k+52] = BackgroundColor.g;
        chBuffer[k+53] = BackgroundColor.b;
      }
      c /= 2;
      k += 6; // два пикселя по два байта (всего в цикле сдвигается на 48 байт)
    }
    k += 48;  // 16 пикселей по 3 байта 
  }
  for(int i = 0; i < BITS_PER_BYTE; i++) {
    chBuffer[k]   = BackgroundColor.r; // последний (6-й) столбец
    chBuffer[k+1] = BackgroundColor.g;
    chBuffer[k+2] = BackgroundColor.b;
    chBuffer[k+3] = BackgroundColor.r; // последний (6-й) столбец
    chBuffer[k+4] = BackgroundColor.g;
    chBuffer[k+5] = BackgroundColor.b;
    chBuffer[k+48] = BackgroundColor.r; // последний (6-й) столбец
    chBuffer[k+49] = BackgroundColor.g;
    chBuffer[k+50] = BackgroundColor.b;
    chBuffer[k+51] = BackgroundColor.r; // последний (6-й) столбец
    chBuffer[k+52] = BackgroundColor.g;
    chBuffer[k+53] = BackgroundColor.b;
    k += 6; // два пикселя по два байта
  }
  putChBuffer(x, y, CH_X2_WIDTH, CH_X2_HEIGHT);
}

void scr_ili9488::putString_x1(char * ss) {
  char * p = ss;
  while(*p) {
    if (CurrentX > (width - CH_X1_WIDTH)) {
      CurrentX = 0;
      CurrentY += CH_X1_HEIGHT;
    }
    if (CurrentY <= (height - CH_X1_HEIGHT)) {
      putChar_x1(*p, CurrentX, CurrentY);
    }
    p++;
    CurrentX += CH_X1_WIDTH;
  }
}

void scr_ili9488::putString_x2(char * ss) {
  char * p = ss;
  while(*p) {
    if (CurrentX > (width - CH_X2_WIDTH)) {
      CurrentX = 0;
      CurrentY += CH_X2_HEIGHT;
    }
    if (CurrentY <= (height - CH_X2_HEIGHT)) {
      putChar_x2(*p, CurrentX, CurrentY);
    }
    p++;
    CurrentX += CH_X2_WIDTH;
  }
}

еще используются два файла, в первом лежат таблицы знакогенераnора:
a_Small_Rus.c

Спойлер
#if defined(__AVR__)
  #include <avr/pgmspace.h>
  #define fontdatatype const uint8_t
#elif defined(__PIC32MX__)
  #define PROGMEM
  #define fontdatatype const unsigned char
#elif defined(__arm__)
  #define PROGMEM
  #define fontdatatype const unsigned char
#endif

// New font 177 symbols 5x8 = 885+4 bytes

fontdatatype SmallFont[] /*PROGMEM*/ =
{
0x05, 0x08, 0x20, 0xb1,
0x00, 0x00, 0x00, 0x00, 0x00,   // sp
0x00, 0x00, 0x2f, 0x00, 0x00,   // !
0x00, 0x07, 0x00, 0x07, 0x00,   // "
0x14, 0x7f, 0x14, 0x7f, 0x14,   // #
0x24, 0x2a, 0x7f, 0x2a, 0x12,   // $
0x23, 0x13, 0x08, 0x64, 0x62,   // %
0x36, 0x49, 0x55, 0x22, 0x50,   // &
0x00, 0x05, 0x03, 0x00, 0x00,   // '
0x00, 0x1c, 0x22, 0x41, 0x00,   // (
0x00, 0x41, 0x22, 0x1c, 0x00,   // )
0x14, 0x08, 0x3E, 0x08, 0x14,   // *
0x08, 0x08, 0x3E, 0x08, 0x08,   // +
0x00, 0x00, 0xA0, 0x60, 0x00,   // ,
0x08, 0x08, 0x08, 0x08, 0x08,   // -
0x00, 0x60, 0x60, 0x00, 0x00,   // .
0x20, 0x10, 0x08, 0x04, 0x02,   // /

0x3E, 0x51, 0x49, 0x45, 0x3E,   // 0
0x00, 0x42, 0x7F, 0x40, 0x00,   // 1
0x42, 0x61, 0x51, 0x49, 0x46,   // 2
0x21, 0x41, 0x45, 0x4B, 0x31,   // 3
0x18, 0x14, 0x12, 0x7F, 0x10,   // 4
0x27, 0x45, 0x45, 0x45, 0x39,   // 5
0x3C, 0x4A, 0x49, 0x49, 0x30,   // 6
0x01, 0x71, 0x09, 0x05, 0x03,   // 7
0x36, 0x49, 0x49, 0x49, 0x36,   // 8
0x06, 0x49, 0x49, 0x29, 0x1E,   // 9
0x00, 0x36, 0x36, 0x00, 0x00,   // :
0x00, 0x56, 0x36, 0x00, 0x00,   // ;
0x08, 0x14, 0x22, 0x41, 0x00,   // <
0x14, 0x14, 0x14, 0x14, 0x14,   // =
0x00, 0x41, 0x22, 0x14, 0x08,   // >
0x02, 0x01, 0x51, 0x09, 0x06,   // ?

0x32, 0x49, 0x59, 0x51, 0x3E,   // @
0x7C, 0x12, 0x11, 0x12, 0x7C,   // A
0x7F, 0x49, 0x49, 0x49, 0x36,   // B
0x3E, 0x41, 0x41, 0x41, 0x22,   // C
0x7F, 0x41, 0x41, 0x22, 0x1C,   // D
0x7F, 0x49, 0x49, 0x49, 0x41,   // E
0x7F, 0x09, 0x09, 0x09, 0x01,   // F
0x3E, 0x41, 0x49, 0x49, 0x7A,   // G
0x7F, 0x08, 0x08, 0x08, 0x7F,   // H
0x00, 0x41, 0x7F, 0x41, 0x00,   // I
0x20, 0x40, 0x41, 0x3F, 0x01,   // J
0x7F, 0x08, 0x14, 0x22, 0x41,   // K
0x7F, 0x40, 0x40, 0x40, 0x40,   // L
0x7F, 0x02, 0x0C, 0x02, 0x7F,   // M
0x7F, 0x04, 0x08, 0x10, 0x7F,   // N
0x3E, 0x41, 0x41, 0x41, 0x3E,   // O

0x7F, 0x09, 0x09, 0x09, 0x06,   // P
0x3E, 0x41, 0x51, 0x21, 0x5E,   // Q
0x7F, 0x09, 0x19, 0x29, 0x46,   // R
0x46, 0x49, 0x49, 0x49, 0x31,   // S
0x01, 0x01, 0x7F, 0x01, 0x01,   // T
0x3F, 0x40, 0x40, 0x40, 0x3F,   // U
0x1F, 0x20, 0x40, 0x20, 0x1F,   // V
0x3F, 0x40, 0x38, 0x40, 0x3F,   // W
0x63, 0x14, 0x08, 0x14, 0x63,   // X
0x07, 0x08, 0x70, 0x08, 0x07,   // Y
0x61, 0x51, 0x49, 0x45, 0x43,   // Z
0x00, 0x7F, 0x41, 0x41, 0x00,   // [
0x01, 0x06, 0x08, 0x30, 0x40,   // Backslash
0x00, 0x41, 0x41, 0x7F, 0x00,   // ]
0x04, 0x02, 0x01, 0x02, 0x04,   // ^
0x40, 0x40, 0x40, 0x40, 0x40,   // _

0x00, 0x03, 0x05, 0x00, 0x00,   // `
0x20, 0x54, 0x54, 0x54, 0x78,   // a
0x7F, 0x48, 0x44, 0x44, 0x38,   // b
0x38, 0x44, 0x44, 0x44, 0x20,   // c
0x38, 0x44, 0x44, 0x48, 0x7F,   // d
0x38, 0x54, 0x54, 0x54, 0x18,   // e
0x08, 0x7E, 0x09, 0x01, 0x02,   // f
0x18, 0xA4, 0xA4, 0xA4, 0x7C,   // g
0x7F, 0x08, 0x04, 0x04, 0x78,   // h
0x00, 0x44, 0x7D, 0x40, 0x00,   // i
0x40, 0x80, 0x84, 0x7D, 0x00,   // j
0x7F, 0x10, 0x28, 0x44, 0x00,   // k
0x00, 0x41, 0x7F, 0x40, 0x00,   // l
0x7C, 0x04, 0x18, 0x04, 0x78,   // m
0x7C, 0x08, 0x04, 0x04, 0x78,   // n
0x38, 0x44, 0x44, 0x44, 0x38,   // o

0xFC, 0x24, 0x24, 0x24, 0x18,   // p
0x18, 0x24, 0x24, 0x18, 0xFC,   // q
0x7C, 0x08, 0x04, 0x04, 0x08,   // r
0x48, 0x54, 0x54, 0x54, 0x20,   // s
0x04, 0x3F, 0x44, 0x40, 0x20,   // t
0x3C, 0x40, 0x40, 0x20, 0x7C,   // u
0x1C, 0x20, 0x40, 0x20, 0x1C,   // v
0x3C, 0x40, 0x30, 0x40, 0x3C,   // w
0x44, 0x28, 0x10, 0x28, 0x44,   // x
0x1C, 0xA0, 0xA0, 0xA0, 0x7C,   // y
0x44, 0x64, 0x54, 0x4C, 0x44,   // z
0x00, 0x10, 0x7C, 0x82, 0x00,   // {
0x00, 0x00, 0xFF, 0x00, 0x00,   // |
0x00, 0x82, 0x7C, 0x10, 0x00,   // }
0x08, 0x04, 0x08, 0x10, 0x08,   // 7E    126   ~
0x7C, 0x12, 0x11, 0x12, 0x7C,   // А

0x7F, 0x49, 0x49, 0x49, 0x31,   // Б
0x7F, 0x45, 0x45, 0x45, 0x3A,   // В
0x7F, 0x01, 0x01, 0x01, 0x03,   // Г
0x60, 0x3F, 0x21, 0x3F, 0x60,   // Д
0x7F, 0x49, 0x49, 0x49, 0x41,   // Е
0x73, 0x0C, 0x7F, 0x0C, 0x73,   // Ж
0x21, 0x41, 0x49, 0x4D, 0x33,   // З
0x7F, 0x10, 0x08, 0x04, 0x7F,   // И
0x7E, 0x20, 0x11, 0x08, 0x7E,   // Й
0x7F, 0x08, 0x14, 0x22, 0x41,   // К
0x40, 0x3F, 0x01, 0x01, 0x7F,   // Л
0x7F, 0x06, 0x08, 0x06, 0x7F,   // М
0x7F, 0x08, 0x08, 0x08, 0x7F,   // Н
0x3E, 0x41, 0x41, 0x41, 0x3E,   // О
0x7F, 0x01, 0x01, 0x01, 0x7F,   // П
0x7F, 0x09, 0x09, 0x09, 0x06,   // Р

0x3E, 0x41, 0x41, 0x41, 0x22,   // С
0x03, 0x01, 0x7F, 0x01, 0x03,   // Т
0x61, 0x26, 0x18, 0x06, 0x01,   // У
0x1C, 0x22, 0x7F, 0x22, 0x1C,   // Ф
0x63, 0x14, 0x08, 0x14, 0x63,   // Х
0x3F, 0x20, 0x20, 0x3F, 0x60,   // Ц
0x07, 0x08, 0x08, 0x08, 0x7F,   // Ч
0x7F, 0x40, 0x7F, 0x40, 0x7F,   // Ш
0x3F, 0x20, 0x3F, 0x20, 0x7F,   // Щ
0x01, 0x7F, 0x48, 0x48, 0x30,   // Ъ
0x7F, 0x48, 0x78, 0x00, 0x7F,   // Ы
0x7F, 0x48, 0x48, 0x30, 0x00,   // Ь
0x41, 0x49, 0x49, 0x2A, 0x1C,   // Э
0x7F, 0x10, 0x3E, 0x41, 0x3E,   // Ю
0x66, 0x19, 0x09, 0x09, 0x7F,   // Я
0x20, 0x54, 0x54, 0x78, 0x40,   // а

0x3E, 0x49, 0x45, 0x45, 0x38,   // б
0x7E, 0x4A, 0x4A, 0x34, 0x00,   // в
0x7C, 0x04, 0x04, 0x0C, 0x00,   // г
0x38, 0x45, 0x45, 0x49, 0x3E,   // д
0x38, 0x54, 0x54, 0x54, 0x18,   // е
0x4C, 0x30, 0x7C, 0x30, 0x4C,   // ж
0x24, 0x42, 0x4A, 0x34, 0x00,   // з
0x7C, 0x20, 0x10, 0x7C, 0x00,   // и
0x7C, 0x21, 0x11, 0x7C, 0x00,   // й
0x7C, 0x10, 0x28, 0x44, 0x00,   // к
0x40, 0x3C, 0x04, 0x04, 0x7C,   // л
0x7C, 0x08, 0x10, 0x08, 0x7C,   // м
0x7C, 0x10, 0x10, 0x7C, 0x00,   // н
0x38, 0x44, 0x44, 0x44, 0x38,   // о
0x7C, 0x04, 0x04, 0x7C, 0x00,   // п
0xFC, 0x18, 0x24, 0x24, 0x18,   // р

0x38, 0x44, 0x44, 0x44, 0x28,   // с
0x04, 0x04, 0x7C, 0x04, 0x04,   // т
0x4C, 0x90, 0x90, 0x90, 0x7C,   // у
0x18, 0x24, 0x7E, 0x24, 0x18,   // ф
0x44, 0x28, 0x10, 0x28, 0x44,   // х
0x3C, 0x20, 0x20, 0x3C, 0x60,   // ц
0x1C, 0x10, 0x10, 0x7C, 0x00,   // ч
0x7C, 0x40, 0x7C, 0x40, 0x7C,   // ш
0x3C, 0x20, 0x3C, 0x20, 0x7C,   // щ
0x04, 0x7C, 0x50, 0x70, 0x00,   // ъ
0x7C, 0x50, 0x70, 0x00, 0x7C,   // ы
0x7C, 0x50, 0x70, 0x00, 0x00,   // ь
0x42, 0x42, 0x52, 0x52, 0x3C,   // э
0x7C, 0x10, 0x38, 0x44, 0x38,   // ю
0x40, 0x2C, 0x12, 0x7E, 0x00,   // я
0x7E, 0x4B, 0x4A, 0x4B, 0x42,   //            Ё   D0 81

0x38, 0x55, 0x54, 0x55, 0x18,   //            ё   D1 91
0x7C, 0x04, 0x05, 0x04, 0x00,   //81    129   Ѓ   D0 83
0x00, 0x78, 0x0A, 0x09, 0x00,   //83    131   ѓ   D1 93
0x3E, 0x49, 0x49, 0x41, 0x22,   //AA    170   Є   D0 84
0x38, 0x54, 0x54, 0x44, 0x28,   //BA    186   є   D1 94
0x00, 0x41, 0x7F, 0x41, 0x00,   //B2    178   І   D0 86
0x00, 0x44, 0x7D, 0x40, 0x00,   //B3    179   і   D1 96
0x00, 0x45, 0x7C, 0x45, 0x00,   //AF    175   Ї   D0 87
0x00, 0x45, 0x7C, 0x41, 0x00,   //BF    191   ї   D1 97
0x23, 0x44, 0x39, 0x04, 0x03,   //A1    161   Ў   D0 8E
0x24, 0x49, 0x32, 0x09, 0x04,   //A2    162   ў   D1 9E
0x7E, 0x02, 0x02, 0x02, 0x01,   //A5    165   Ґ   D2 90
0x7C, 0x04, 0x04, 0x02, 0x00,   //B4    180   ґ   D2 91
0x00, 0x4A, 0x55, 0x29, 0x00,   //A7    167   §   C2 A7
0x00, 0x06, 0x09, 0x09, 0x06,   //            °   C2 B0
0x44, 0x44, 0x5F, 0x44, 0x44,   //B1    177   ±   C2 B1

0x7C, 0x10, 0x10, 0x3C, 0x40,   //B5    181   µ   C2 B5
};

а в другом - выводимый битмап (практически максимальный для контроллера с 64к флеша):
pict.c

Спойлер

К сожалению файл имеет длину 144736 символов и не помещается в сообщение.

Работа с дисплеем продолжается, и тут наметился просто таки детективный сюжет.

В предыдущее сообщение си-файл с картинкой не поместился. Да и 64к памяти BluePill как-то не дают возможности держать в ней полноцветную картинку: размер 136*93 пикселя - менее 1/12 части экрана, а занимает во флеш около 40к.
Ну, опять же, перекодировка из стандартного битмапа в формат пригодный для отображения на дисплее - то еще приключение. Тут лучше один раз увидеть [исходник], чем 100 раз услышать [описание алгоритма].
Поэтому возникла идея отображения стандартного BMP файла записанного на карте. Благо кардридер к дисплею присобачен (правда, SD, а не microSD).
В общем, так это выглядит на экране:


Надо сказать, что на глаз она выглядит гораздо лучше - нет той ряби, которая возникает из-за интерференции частот дисплея и фотокамеры.
Углы обзора у дисплейчика убогие, вживую этот как-то не очень бросается в глаза, но все равно заметно, что правая сторона темнее левой (подозреваю, что дисплейчик разрабатывался, исходя из вертикальной ориентации, когда такой эффект не возникает).
А вот исходную BMP-шку форум почему-то разместить не дает - не понимает такой формат и конвертит ее в JPG.

Так что, если захотите проверить предлагаемый ниже скетч именно с этой картинкой, следует преобразовать ее в BMP с глубиной цвета 24 разряда, и записать на SD, вставляемую в разъем экрана.

Все это, конечно, хорошо, но прорисовка дисплея происходит очень неспешно - примерно так за 4 секунды. Понятно, что основное время приходится на SD карту, собственно, в расчете именно на это и скомпоновал алгоритм отображения, чтобы чтение с карты шло последовательно без скачков по файлу и повторов.

Но результат меня все равно удивил, оказалось, что распределение суммарного времени между операциями выглядит примерно так:

  1. Чтение с SD - 2500 мс.
  2. Пересчет в формат для отображения на экране - 40-50 мс.
  3. Пересылка буфера на экран - 1600 мс.

Последнее - это вместо ожидаемых ~100 мс. Несколько раз перепроверил - с разными размерами буфера - результат практически неизменен.

Выявил виновника такого безобразия, им оказалась библиотека SD (та, что входит в комплект поставки Arduino IDE). Она перед началом каждой операции устанавливает коэффициент деления для SPI на 32 и по завершении не восстанавливает его.

Я по быстрому поставил заплатку - в строке 47, но по-хорошему ее надо перенести в библиотеку - либо в начало метода Address_set (тогда она будет в одном месте), либо в каждую операцию с буфером перед digitalWrite(CS,LOW); или эквивалентной ей но более быстрой GPIOB_BASE->BRR = 0x0400;, считая, что #define CS PB10. Внимание, распиновка этого варианта немного отличается от предыдущего.
В любом случае после таких изменений время, затрачиваемое на работу с экраном сокращается до 100 мс, а общее время работы - до 2600-2700 мс.

// ILI9488 serial + stm32f103 (BluePill) DMA + SD

#include <SPI.h>
#include <SD.h>
#include "scr_ili9488.h"

#define SD_CS PA0
#define WID_FRM 120

scr_ili9488 Lcd;
File myFile;
unsigned char inBuffer[480 * 3 * 8]; // 11520 - количество байтов в 8 строках по 480 пикселей
unsigned char outBuffer[WID_FRM * 3 * 8]; // 2880 - фрагмент буфера для экрана

void setup()
{
  Serial.begin(115200);
  delay(2000);
  Serial.print("\n\nStart ili9488 stm32f103 DMA, WID_FRM ");
  Serial.println(WID_FRM);

  unsigned int t0, t1, t2, t3, t4, t5, t6, t7;

  Lcd.Init();
  Lcd.Clear(WHITE);
  Lcd.setCurrentX(120);
  Lcd.setCurrentY(120);
  Lcd.putString_x2(" File Kremlin4.bmp ");

  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);
  if (SD.begin(SD_CS)) {
    Serial.println("   SD OK!");
    myFile = SD.open("Kremlin4.bmp");
    if (myFile) {
      for (int i = 0; i < 54; i++) { // пропускаем заголовок 54 байта
        t2 =  myFile.read();
      }
      unsigned int sumSD = 0;
      unsigned int sumScr = 0;
      unsigned int sumCalc = 0;
      t0 = millis();
      for (int k = 0; k < 40; k++) { // перебор по горизонтальным 8-строчным блокам (480*8)
        t3 = millis();
        int n = myFile.read(inBuffer, 480 * 3 * 8);
        t4 = millis();
        SPI1_BASE->CR1 &= 0xffc7; //  !!! возвращаем частоту SPI к значению _DIV_2 после того, как SD устанавливает ее _DIV_32
        sumSD += t4 - t3;
        for (int m = 0; m < (480 / WID_FRM); m++) { // перебор по фрагментам (WID_FRM*8)
          t5 = millis();
          for (int i = 0; i < WID_FRM; i++) {  // перебор по 8-столбцам (1*8) внутри блока (WID_FRM*8)
            for (int j = 0; j < 8; j++) { // перебор по пикселям внутри вертикального 8-столбца (1*8)
              int ofs_in = i * 3 + (7 - j) * 480 * 3 + m * WID_FRM * 3;
              int ofs_out = j * 3 + i * 3 * 8;
              outBuffer[ofs_out + 0] = inBuffer[ofs_in + 2];
              outBuffer[ofs_out + 1] = inBuffer[ofs_in + 1];
              outBuffer[ofs_out + 2] = inBuffer[ofs_in + 0];
            }
          }
          t6 = millis();
          Lcd.putExtBuffer(outBuffer, m * WID_FRM, (39 - k) * 8, WID_FRM, 8); // x, y, ширина, высота
          t7 = millis();
          sumScr += t7 - t6;
          sumCalc += t6 - t5;
        } // m
      }  // k
      t1 = millis();
      Serial.println(t7 - t6);
      Serial.println((SPI1_BASE->CR1 >> 3) & 7);
      Serial.print("Elapsed time: ");
      Serial.print(t1 - t0);
      Serial.print(", SD: ");
      Serial.print(sumSD);
      Serial.print(", Screen: ");
      Serial.print(sumScr);
      Serial.print(", Calc: ");
      Serial.println(sumCalc);
      myFile.close();
    } else {
      // if the file didn't open, print an error:
      Serial.println("error opening test.txt");
      Lcd.setCurrentX(90);
      Lcd.setCurrentY(120);
      Lcd.putString_x2(" error opening Kremlin4.bmp ");
    }
  } else {
    Serial.println("   !!!   ERROR   !!!   SD not ready");
    Lcd.setCurrentX(100);
    Lcd.setCurrentY(120);
    Lcd.putString_x2(" ERROR   !!!   SD not ready ");
  }
}

void loop() {
}