Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F411, STM32F429, STM32F7, STM32H7 · уровень средний · 12 мин чтения
Открыть в интерактивном справочнике

22а. Графический интерфейс на дисплее: буфер кадра, LTDC, DMA2D и сенсор

Индикатор из раздела 22 показывает число. Дисплей с сенсором показывает интерфейс: кнопки, графики, меню — и здесь микроконтроллер впервые упирается не в периферию, а в память и полосу пропускания. Кадр 240×320 в 16-битном цвете — 153 600 байт, и его надо перерисовывать десятки раз в секунду. Раздел о том, где этот кадр живёт, как он попадает на стекло — через SPI или через контроллер LTDC, — чем помогает ускоритель DMA2D и как в это встраивается сенсор и библиотека интерфейса.

Устройство LTDC и DMA2D, формула смешения и пример таймингов взяты из справочного руководства на F405/407/427/429 (из пакета Keil); константы и структуры — из HAL 2.17.1; скорость SPI F411 — из его даташита. Размеры буферов, времена кадра, тайминги и смешение пересчитаны программой _verify/gui22a/gui_calc.c, собранной gcc на компьютере. Сверяет _verify/gui22a_check.py.

Как это называется в документации

Оригинал Перевод
frame buffer буфер кадра — массив пикселей в памяти
LTDC (LCD-TFT display controller) контроллер, который сам выдаёт кадр на параллельный RGB-дисплей
DMA2D (Chrom-ART) двумерный DMA: заливка, копирование, преобразование формата, смешение
pixel clock (LCD_CLK) тактовая частота пикселей
HSYNC, VSYNC, back porch, front porch синхроимпульсы и «поля» до и после активной области
layer слой LTDC — до двух, смешиваются снизу вверх
PFC (pixel format converter) преобразователь формата пикселя
CLUT таблица цветов для индексных форматов
line offset, pitch сколько байт от начала одной строки до начала следующей
blending, alpha смешение по прозрачности
tearing разрыв кадра: показ наполовину старого, наполовину нового

22а.1 Два пути к стеклу

Дисплей со своим контроллером. Небольшие TFT (240×240, 240×320) несут на плате микросхему с собственной памятью кадра. Микроконтроллер по SPI задаёт окно и шлёт пиксели; дальше дисплей показывает их сам, без участия хозяина. Память кадра у микроконтроллера не обязательна — рисовать можно кусками. Это путь F411, F103 и любого камня с SPI (шина — раздел 18, практика — 83.3).

Тот же дисплей со своим контроллером бывает и с параллельной шиной 8080 (8 или 16 бит). Её подключают к FSMC/FMC, и для микроконтроллера дисплей становится внешней памятью: команда и пиксель — запись по одному из двух адресов. Так делают на F103 высокой плотности и F407, у которых LTDC нет.

Дисплей без контроллера — параллельный RGB. Панель 480×272 или 800×480 с 40-контактным шлейфом сама ничего не хранит: ей нужно непрерывно подавать пиксели с тактом, синхроимпульсами и полями, 60 раз в секунду, как монитору. Этим занимается LTDC — он читает буфер кадра из памяти и гонит его на выводы, пока включён. У F411 такого блока нет (в даташите в списке интерфейсов его нет), он есть у F429/F439, F7 и H7 — и у них же есть DMA2D.

Выбор пути делает не библиотека, а размер экрана: до 320×240 обычно SPI, дальше — параллельная шина: 8080 через FSMC к дисплею со своим контроллером или RGB через LTDC к панели без него, потому что SPI не успевает (22а.3). У F469, F769 и H747 есть ещё MIPI DSI — быстрый последовательный интерфейс к панелям.


22а.2 Буфер кадра: сколько памяти

Экран Формат Байт
240×240 RGB565, 2 байта 115 200
240×320 RGB565 153 600
480×272 RGB565 261 120
480×272 ARGB8888, 4 байта 522 240
800×480 RGB565 768 000
800×480 ARGB8888 1 536 000

У F411 128 КБ ОЗУ — это 131 072 байта: кадр 240×320 не помещается, 240×240 помещается с трудом. У F429 внутренней памяти 256 КБ, из них 64 — только для ядра; кадр 480×272 в 16 битах туда не входит. Поэтому платы с LTDC несут внешнюю SDRAM на шине FMC (раздел 41), а буфер кадра лежит там, и LTDC читает его через шину непрерывно.

Когда буфера нет или он мал, рисуют полосами: библиотека отрисовывает прямоугольник в небольшой буфер — например, десять строк, — и отправляет его дисплею со своим контроллером, затем следующий. Два таких буфера по очереди позволяют DMA слать один, пока процессор рисует второй.


22а.3 Кадр по SPI: сколько это времени

Кадр 240×320 в RGB565 — 1 228 800 бит. У F411 SPI разгоняется до 50 Мбит/с (даташит), но на макетных проводах выше 8–10 МГц не работает (раздел 18):

SPI Кадр Кадров в секунду
50 Мбит/с 24,6 мс 40,7
36 Мбит/с 34,1 мс 29,3
25 Мбит/с 49,2 мс 20,3
9 Мбит/с 136,5 мс 7,3

Отсюда два правила. Первое: перерисовывать только то, что изменилось — библиотеки интерфейса ведут список «грязных» прямоугольников и шлют дисплею только их; цифра на часах меняется за доли миллисекунды, а не за 25. Второе: слать через DMA, иначе процессор всё время кадра занят перекладыванием байтов и интерфейс перестаёт отвечать на сенсор. И грабля из 83.3 никуда не делась: дисплей ждёт старший байт первым, а uint16_t в памяти лежит младшим вперёд — либо 16-битный режим SPI, либо перестановка байтов.


22а.4 LTDC: кадр как у монитора

Параллельной панели нужен не только поток пикселей, но и «рамка» вокруг каждой строки и каждого кадра — как у аналогового монитора: импульс синхронизации, заднее поле, активная область, переднее поле. Пример из руководства для 640×480: HSYNC 8 тактов, заднее поле 7, активных 640, переднее поле 6; по вертикали VSYNC 4 строки, поле 2, активных 480, поле 2. Всего строка — 661 такт, кадр — 488 строк, 322 568 тактов на кадр; чтобы показывать 60 кадров в секунду, тактовая частота пикселей должна быть 19,35 МГц. Её даёт отдельная ФАПЧ PLLSAI.

В регистры пишутся не сами поля, а накопленные суммы минус один — так удобнее счётчику:

LTDC_SSCR = 0x00070003   HSYNC − 1 = 7,             VSYNC − 1 = 3
LTDC_BPCR = 0x000E0005   HSYNC + HBP − 1 = 14,      VSYNC + VBP − 1 = 5
LTDC_AWCR = 0x028E01E5   … + 640 − 1 = 654,         … + 480 − 1 = 485
LTDC_TWCR = 0x029401E7   … + HFP − 1 = 660,         … + VFP − 1 = 487

Как и в остальных трёх, ширина и высота лежат в одном регистре: у TWCR полная ширина — в битах 27:16, полная высота — в битах 10:0. В примере руководства и на рисунке ниже вторая половина подписана THCR, но отдельного регистра с таким именем у LTDC нет: следом за TWCR по смещению 0x18 идёт GCR.

Кадр LTDC из примера руководства: активная область 640×480 в рамке из синхроимпульсов HSYNC 8 и VSYNC 4, задних полей HBP 7 и VBP 2 и передних HFP 6 и VFP 2; строка целиком 661 такт, кадр 488 строк, 322 568 тактов, для 60 Гц нужна тактовая пикселей 19,35 МГц; регистры SSCR, BPCR, AWCR, TWCR и THCR хранят накопленные суммы минус один

Что умеет контроллер: два слоя, которые смешиваются снизу вверх — сначала слой 1 с фоновым цветом, потом слой 2 с результатом; у каждого слоя своё окно, свой формат пикселя (8 форматов: ARGB8888, RGB888, RGB565, ARGB1555, ARGB4444, L8, AL44, AL88) и своя таблица цветов на 256 записей для индексных форматов; прозрачность — постоянная или попиксельная; цветовой ключ; сглаживание младших разрядов для 18-битных панелей. Часть регистров теневая: новые значения вступают в силу по перезагрузке — сразу или в вертикальном гашении, чтобы переключить буфер кадра между двумя кадрами, а не посреди строки.

Цена: LTDC читает память непрерывно. Панель 480×272 в RGB565 при 60 Гц — 15,7 МБ/с по шине; 800×480 в 32 битах — 92,2 МБ/с. Этот поток идёт по AHB всегда, и с ним конкурируют процессор, DMA2D и все остальные DMA; в SDRAM он упирается первым.


22а.5 DMA2D: рисует не процессор

Заливка прямоугольника, копирование картинки, вывод шрифта — это тысячи одинаковых обращений к памяти, и DMA2D делает их вместо ядра. Четыре режима:

Режим Что делает В HAL
регистр → память залить прямоугольник цветом DMA2D_R2M
память → память скопировать блок пикселей DMA2D_M2M
… с преобразованием формата RGB565-картинку положить в ARGB8888-буфер DMA2D_M2M_PFC
… с преобразованием и смешением наложить с прозрачностью на фон DMA2D_M2M_BLEND

Входные форматы — те же, что у LTDC, плюс чисто «прозрачностные» A8 и A4: в них нет цвета, только альфа, а цвет задаётся регистром — так хранят шрифты: глиф — маска прозрачности, цвет текста — константа. Формат без альфы (RGB565) получает альфу 255 автоматически; её можно подменить константой или умножить на неё.

Главная тонкость — смещение строки (line offset, в HAL OutputOffset и InputOffset): DMA2D рисует прямоугольник шириной Width внутри буфера, у которого строка длиннее. Смещение — сколько пикселей пропустить в конце каждой строки, то есть ширина буфера минус ширина прямоугольника. Забыть его — получить картинку, съезжающую наискось.

Смещение строки DMA2D: прямоугольник 100×40 в буфере шириной 480 — после каждой строки DMA2D пропускает OutputOffset = 480 − 100 = 380 пикселей, хвост этой строки и начало следующей; без смещения 40 строк ложатся вплотную — 4000 пикселей подряд, 8 строк буфера и ещё 160, полоса вместо прямоугольника

hdma2d.Init.Mode         = DMA2D_R2M;           /* заливка */
hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
hdma2d.Init.OutputOffset = 480 - 100;           /* буфер 480 в ширину, прямоугольник 100 */
HAL_DMA2D_Init(&hdma2d);
HAL_DMA2D_Start(&hdma2d, 0xFFFF0000u, (uint32_t)(fb + y * 480 + x), 100, 40);   /* красный, 100×40 */
HAL_DMA2D_PollForTransfer(&hdma2d, 10);

Цвет заливки HAL принимает всегда в ARGB8888 и сам переводит в формат выхода: из 0xFFFF0000 для RGB565 получается 0xF800. RGB565-значение сюда передавать нельзя — 0xF800 HAL прочтёт как зелёный 0xF8 и зальёт прямоугольник цветом 0x07C0. Адрес назначения — число uint32_t, поэтому указатель на буфер приводят явно.

Смешение считается по формуле из руководства: αMult = αFG · αBG / 255, αOUT = αFG + αBG − αMult, C = (CFG · αFG + CBG · αBG − CBG · αMult) / αOUT для каждой из R, G, B. На числах: передний план 200 с альфой 255 поверх фона 50 — итог 200 (непрозрачный закрывает всё); альфа 0 — итог 50 (фон); альфа 128 поверх непрозрачного фона — 125, ровно середина; две полупрозрачные (128 и 128) — альфа 192 и цвет 150. Есть и «мёртвое время» между обращениями DMA2D к шине — регулятор, чтобы ускоритель не съедал всю полосу у LTDC.


22а.6 Сенсор: откуда берутся касания

Резистивный сенсор — четыре провода на АЦП: поочерёдно подать питание на одну пару, измерить напряжение на другой — координата. Дёшево, работает в перчатках, одно касание, нужна калибровка.

Ёмкостный — отдельный контроллер на стекле, по I²C (раздел 17): он сам считает касания и отдаёт готовые координаты, а вывод INT сообщает, что данные есть. Опрашивать по прерыванию, а не в цикле: пока пальца нет, шина свободна.

В обоих случаях координаты сенсора — не координаты экрана: оси могут быть перевёрнуты или переставлены относительно буфера кадра, а размеры отличаются. Пересчёт — одно умножение и сложение на ось, и его подбирают на первом же запуске по углам экрана. Касание, дребезг и «отпускание» превращаются в события для библиотеки: нажал, ведёт, отпустил.


22а.7 Библиотека интерфейса и что она просит от вас

Кнопки, списки, шрифты, анимации пишут не сами — берут библиотеку: фирменную от производителя или свободную. Любая из них хочет от программы одного и того же:

  1. Куда рисовать — буфер кадра целиком (LTDC) или буфер полосы (SPI-дисплей).
  2. Как отдать нарисованное — функцию «прямоугольник готов»: для SPI-дисплея это отправка через DMA с последующим «готово», для LTDC — переключение адреса слоя в вертикальном гашении.
  3. Время — вызов раз в миллисекунду, от него живут анимации и таймауты.
  4. Ввод — функцию «есть ли касание и где».

Под RTOS (часть IV) отрисовку кладут в отдельную задачу с невысоким приоритетом: пусть кадр дорисовывается на 10 мс позже, чем датчик пропустит отсчёт. Сенсор и связь — выше. Между задачами — очередь событий, а не общие переменные (раздел 27).


22а.8 Грабли

Симптом Причина
цвета «поплыли», красный стал синим порядок байт RGB565 по SPI (83.3): нужен 16-битный режим или перестановка
картинка съехала наискось не задано смещение строки в DMA2D или длина строки слоя LTDC
по экрану бежит горизонтальный разрыв буфер меняется во время вывода: переключать в вертикальном гашении, второй буфер
панель мерцает или не синхронизируется тактовая пикселей не та: посчитать из полных размеров с полями, как в 22а.4
интерфейс тормозит на F411 перерисовка всего кадра целиком: только изменившиеся прямоугольники и DMA
буфер кадра «не влезает» 240×320 в 16 битах — 153 600 байт больше 128 КБ: полосы или другой камень
на F7/H7 DMA2D рисует старое кэш данных: чистить строки перед DMA2D и сбрасывать после
текст без сглаживания или с фоном шрифт положен как картинка, а не как маска A8/A4 с цветом из регистра
сенсор «нажимает» не туда оси сенсора не совпадают с осями буфера — пересчёт и калибровка по углам
касания теряются при анимации сенсор опрашивается из задачи отрисовки: отдельная задача и прерывание по INT
всё стало медленным после добавления LTDC его поток 15–90 МБ/с делит шину с остальными: SDRAM и DMA2D под ним

22а.9 Итоги

Дальше: раздел 22б «Камера по DCMI: кадр в память через DMA» — обратный путь: кадр с камеры в память через DCMI и DMA.

Открыть в интерактивном справочнике