Проект DMA2D в области встроенной графики

Что такое DMA2D?

С развитием встроенной графики микроконтроллеры берут на себя все более сложные задачи по графическим вычислениям и отображению. Однако вычислительной мощности ЦП может оказаться недостаточно для обработки графики с высоким разрешением и яркими цветами. К счастью, начиная с STM32F429, компания ST внедрила в микроконтроллеры STM32 внешнее периферийное устройство, схожее с GPU, известное как Chrom-ART Accelerator или DMA2D. DMA2D обеспечивает ускорение во многих сценариях 2D-графики и эффективно интегрирует функции, аналогичные «GPU», которые можно найти в современных графических картах.

Хотя DMA2D предлагает только 2D-ускорение и его возможности относительно просты по сравнению с GPU в ПК, он может удовлетворить большинство требований к ускорению отображения графики в встраиваемых системах. Эффективно используя DMA2D, мы можем добиться плавных и впечатляющих эффектов пользовательского интерфейса на микроконтроллерах.

Функции DMA2D

  1. Заливка цветом (прямоугольные области)
  2. Копирование изображения (памяти)
  3. Преобразование цветового формата (например, YCbCr в RGB или RGB888 в RGB565)
  4. Смешивание прозрачности (альфа-смешивание)

Первые две операции выполняются на основе памяти, а две последние — с использованием вычислительного ускорения. Смешивание прозрачности и преобразование цветового формата можно комбинировать с копированием изображений, что обеспечивает значительную гибкость.

В практической разработке использование DMA2D сходно с традиционными контроллерами DMA. В некоторых неграфических сценариях DMA2D может даже заменить обычный DMA для определенных задач.

Важно отметить, что ускорители DMA2D в разных линейках продуктов ST могут иметь небольшие различия. Например, DMA2D в микроконтроллерах серии STM32F4 не имеет возможности конвертировать между форматами цвета ARGB и AGBR. Поэтому, когда требуется определенная функциональность, рекомендуется обратиться к руководству по программированию, чтобы убедиться в ее поддержке.

Режимы работы DMA2D

Подобно тому, как традиционный DMA имеет режимы «от периферийного устройства к периферийному устройству», «от периферийного устройства к памяти» и «от памяти к периферийному устройству», DMA2D, как компонент DMA, также имеет четыре режима работы:

  1. Регистр-память
  2. Из памяти в память
  3. Из памяти в память с преобразованием формата цвета пикселей
  4. Из памяти в память с преобразованием формата цвета пикселей и смешением прозрачности

Первые два режима включают простые операции с памятью, а два последних режима выполняют копирование памяти с одновременной обработкой преобразования формата цвета и/или смешивания прозрачности по мере необходимости.

DMA2D и библиотека HAL

Во многих случаях использование библиотеки HAL упрощает написание кода и повышает его переносимость. Однако есть исключение, когда речь идет об использовании DMA2D. Основная проблема HAL заключается в чрезмерном вложенности и проверках безопасности, которые снижают эффективность. Хотя потеря эффективности при работе с другими периферийными устройствами может быть несущественной, для DMA2D — ускорителя, ориентированного на вычисления и скорость — использование библиотеки HAL может значительно снизить эффективность ускорения.

Следовательно, мы часто избегаем использования соответствующих функций HAL для операций DMA2D. В целях эффективности используется прямое манипулирование регистрами, что обеспечивает максимальные преимущества ускорения.

Поскольку в большинстве случаев использования DMA2D часто меняются режимы работы, графическая конфигурация DMA2D в CubeMX теряет свою практичность.

Применение DMA2D в разработке встроенной графики

Необходимые инструменты

  • Плата разработчика STM32 с периферийным устройством DMA2D x1
  • Цветной TFT-дисплей x1

В этом примере мы используем плату разработчика ART-Pi от RT-Thread, оснащенную микроконтроллером STM32H750XB с тактовой частотой до 480 МГц и 32 МБ SDRAM. Она также включает отладчик (ST-Link V2.1). Используется 3,5-дюймовый TFT-дисплей с интерфейсом RGB666 и разрешением 320×240 (QVGA).

ART-Pi Development Board-Produced by RT-Thread (with TFT LCD Display)
ART-Pi Development Board-Produced by RT-Thread (with TFT LCD Display)

Среда разработки

Содержание и код, представленные в этой статье, могут быть использованы в различных средах разработки, таких как RT-Thread Studio, MDK, IAR и т. д.

Перед началом экспериментов, описанных в этой статье, вам понадобится базовый проект, который управляет ЖК-дисплеем с помощью технологии фреймбуфера. Перед запуском любого из представленных кодов необходимо включить DMA2D.

DMA2D можно включить с помощью этого макроса:

				
					__HAL_RCC_DMA2D_CLK_ENABLE();
				
			

Прикладной проект: Заполнение прямоугольника

Встроенная графика включает в себя различные типы операций, в том числе заливку прямоугольников, копирование памяти, смешивание прозрачности и т. д. В качестве примера мы будем использовать заливку прямоугольников. Цель состоит в том, чтобы создать простую гистограмму с помощью DMA2D для заливки прямоугольников:

A Simple Histogram
A Simple Histogram

Во-первых, нам нужно заполнить экран белым цветом, который будет служить фоном для узора. Этот шаг очень важен, так как существующий узор на экране может помешать нам создать желаемый дизайн. Затем строим гистограмму, используя четыре синих прямоугольных блока и отрезок линии, который можно считать специальным прямоугольным блоком с высотой 1. Таким образом, рисование этого графического объекта включает в себя серию операций «заполнения прямоугольника»:

  • Заполните прямоугольник белым цветом, покрывая весь экран.
  • Заполнить четыре полосы данных синим цветом.
  • Заполнить отрезок линии черным цветом с высотой 1.

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

На приведенном ниже схеме показана типичная структура памяти, где цифры обозначают адрес памяти каждого пикселя в буфере кадра (смещение относительно базового адреса, без учета многобайтовых пикселей). Синяя область представляет собой прямоугольник, который необходимо заполнить. Очевидно, что адреса памяти внутри прямоугольника не являются смежными.

Memory Distribution in Frame Buffer
Memory Distribution in Frame Buffer

Это свойство фреймбуфера не позволяет нам использовать эффективные операции, такие как memset, для заполнения прямоугольных областей. Обычно для заполнения любого прямоугольника мы используем подход с вложенными циклами, как показано ниже. Здесь xs и ys — это координаты левого верхнего угла прямоугольника на экране, width и height определяют размеры прямоугольника, а color задает цвет заполнения:

				
					for (int y = ys; y < ys + height; y++) {
    for (int x = xs; x < xs + width; x++) {
        framebuffer[y][x] = color;
    }
}

				
			

Хотя код может показаться простым, во время выполнения значительное количество циклов ЦП тратится на такие операции, как проверка условий, вычисление адресов и инкременты, при этом минимальная часть выделяется для фактической записи в память. Такая ситуация приводит к снижению эффективности.

В таких случаях вступает в действие режим работы DMA2D «регистр-память». DMA2D может быстро заполнить прямоугольную область памяти, даже если она не является непрерывной в памяти.

Используя пример, изображенный на картинке выше, давайте разберемся, как это достигается:

Illustration of DMA2D Filling Memory Area
Illustration of DMA2D Filling Memory Area

Во-первых, поскольку мы имеем дело исключительно с заполнением памяти, а не с копированием, нам необходимо, чтобы DMA2D работал в режиме «регистр-память». Это достигается путем установки битов [17:16] регистра CR DMA2D в значение «11», как показано в фрагменте кода:

				
					DMA2D->CR = 0x00030000UL;

				
			

Далее мы сообщаем DMA2D атрибуты прямоугольника, который необходимо заполнить, такие как начальный адрес области, ее ширина в пикселях и высота.

Начальный адрес области — это адрес в памяти левого верхнего пикселя прямоугольника (адрес красного пикселя на схеме), который управляется регистром OMAR DMA2D. Ширина и высота прямоугольника измеряются в пикселях и управляются старшими 16 битами (ширина) и младшими 16 битами (высота) регистра NLR. Код для установки этих значений выглядит следующим образом:

				
					DMA2D->OMAR = (uint32_t)(&framebuffer[y][x]); // Set the starting pixel memory address for filling
DMA2D->NLR  = (uint32_t)(width << 16) | (uint16_t)height; // Set the width and height of the rectangle

				
			

Впоследствии, поскольку адреса памяти прямоугольника не являются смежными, нам необходимо дать команду DMA2D пропустить определенное количество пикселей после заполнения одной строки данных (т. е. длину желтой области на схеме). Это значение управляется регистром OOR. Существует простой способ вычисления количества пикселей, которые необходимо пропустить: вычесть ширину прямоугольника из ширины области отображения. Код для реализации этого выглядит следующим образом:

				
					DMA2D->OOR = screenWidthPx - width; // Set the row offset, i.e., skip pixels

				
			

Наконец, нам нужно сообщить DMA2D о цвете, который будет использоваться для заливки, и о формате цвета. Они управляются регистрами OCOLR и OPFCCR соответственно. Формат цвета определяется макросами LTDC_PIXEL_FORMAT_XXX. Код выглядит следующим образом:

				
					DMA2D->OCOLR   = color; // Set the color for filling
DMA2D->OPFCCR  = pixelFormat; // Set the color format, e.g., use the macro LTDC_PIXEL_FORMAT_RGB565 for RGB565

				
			

После настройки всех параметров DMA2D получил всю необходимую информацию для заполнения прямоугольника. Чтобы инициировать передачу, мы устанавливаем бит 0 регистра CR DMA2D в «1»:

				
					DMA2D->CR |= DMA2D_CR_START; // Start DMA2D data transfer, where DMA2D_CR_START is a macro with the value 0x01

				
			

Как только начинается передача DMA2D, мы просто ждем ее завершения. После завершения передачи DMA2D автоматически сбрасывает бит 0 регистра CR в «0», что позволяет нам ждать завершения с помощью следующего кода:

				
					while (DMA2D->CR & DMA2D_CR_START) {} // Wait for DMA2D transfer completion

				
			

Совет: если вы используете операционную систему, вы можете включить прерывание завершения передачи DMA2D. Затем вы можете создать семафор, дождаться его после запуска передачи и освободить его в подпрограмме обслуживания прерывания завершения передачи DMA2D.

Для обеспечения универсальности функции начальный адрес передачи и смещение строки рассчитываются вне функции и передаются в нее. Вот полный код функции:

				
					static inline void DMA2D_Fill(void * pDst, uint32_t width, uint32_t height, uint32_t lineOff, uint32_t pixelFormat, uint32_t color) {
    
    /* Configure DMA2D */
    DMA2D->CR      = 0x00030000UL;                                  // Configure for register-to-memory mode
    DMA2D->OCOLR   = color;                                         // Set the color for filling (format should match the configured color format)
    DMA2D->OMAR    = (uint32_t)pDst;                                // Starting memory address of the fill region
    DMA2D->OOR     = lineOff;                                       // Row offset, i.e., skip pixels (in pixel units)
    DMA2D->OPFCCR  = pixelFormat;                                   // Set the color format
    DMA2D->NLR     = (uint32_t)(width << 16) | (uint16_t)height;    // Set the width and height of the fill region (in pixel units)

    /* Start transfer */
    DMA2D->CR   |= DMA2D_CR_START;

    /* Wait for DMA2D transfer completion */
    while (DMA2D->CR & DMA2D_CR_START) {}
}

				
			

Для удобства давайте обернем это в функцию заполнения прямоугольника, основанную на системе координат вашего экрана:

				
					void FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
    void* pDist = &(((uint16_t*)framebuffer)[y*320 + x]);
    DMA2D_Fill(pDist, w, h, 320 - w, LTDC_PIXEL_FORMAT_RGB565, color);
}

				
			

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

				
					// Fill background color
FillRect(0,   0,   320, 240,  0xFFFF);
// Draw data bars
FillRect(80,  80,  20,  120,  0x001F);
FillRect(120, 100, 20,  100,  0x001F);
FillRect(160, 40,  20,  160,  0x001F);
FillRect(200, 60,  20,  140,  0x001F);
// Draw X-axis
FillRect(40,  200, 240, 1,    0x0000);

				
			

Эффект работы кода следующий:

The Final Effect of DMA2D Rectangle Filling
The Final Effect of DMA2D Rectangle Filling

Поделиться:

Прокрутить вверх

Instant Quote