Урок 76
HAL. LTDC. EmWin. MultiLayer. Transparency
Продолжаем работу с библиотекой emWin.
Работаем мы также с платой STM32F746-DISCOVERY.
Сегодня мы научися работать со слоями и с их прозрачностью, что позволит нам добиться ещё некоторых интересных эффектов и результатов в графическом отображении информации.
Мы не будем тратить время на объяснение терминологии слоёв и прозрачности, так как мы этим уже занимались в прошлом, работая со своими библиотечными функциями вывода изображений и другой информации без использования библиотеки emWin, а теперь наша задача — понять и применить механизмы, предоставленные именно данной библиотекой для достижения подобных задач.
Как настроить и использовать слои и прозрачность, также очень хорошо описано в документации на библиотеку, а также там даны некоторые примеры для этого.
Проект будет создан из проекта предыдущего 75 урока и вместо LTDC_EMWIN_MB он будет иметь имя EMWIN_MultiLayer.
Запустим наш проект Cube MX и, ничего там не трогая, просто сгенерируем проект для System Workbench.
Откроем System Workbench и подключим там наш проект.
Настройки проекта делаем точно такие же, как и в уроке 73.
Соберём проект и запустим его, чтобы проверить, что он работает.
Слоёв будет у нас всего два, для того, чтобы библиотека поняла, что их будет именно два, в файле LCDConf.c мы внесём определённые поправки.
Во-первых, экранных буфера у нас будет теперь два, поэтому исправим объявление буфера
#define FRAME_BUFFER_ADDRESS_LAYER_0 0xC0200000 /* Address in SDRAM for frame buffer */
#define FRAME_BUFFER_ADDRESS_LAYER_1 0xC0400000 /* Address in SDRAM for frame buffer */
Также добавим макрос объявляющий количество слоёв
#define FRAME_BUFFER_ADDRESS_LAYER_1 0xC0400000 /* Address in SDRAM for frame buffer */
#define GUI_NUM_LAYERS 2
Также глобальных структур для слоёв тоже будет две
LTDC_LayerCfgTypeDef pLayerCfg0;
LTDC_LayerCfgTypeDef pLayerCfg1;
Исправим функцию LayerInit. Теперь здесь будет входящий аргумент с номером слоя
void Layer_init(unsigned LayerIndex) {
if (LayerIndex == 0) {
pLayerCfg0.WindowX0 = 0;
pLayerCfg0.WindowX1 = 480;
pLayerCfg0.WindowY0 = 0;
pLayerCfg0.WindowY1 = 272;
pLayerCfg0.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888;
pLayerCfg0.Alpha = 255;
pLayerCfg0.Alpha0 = 0;
pLayerCfg0.Backcolor.Blue = 0;
pLayerCfg0.Backcolor.Green = 0;
pLayerCfg0.Backcolor.Red = 0;
pLayerCfg0.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
pLayerCfg0.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
pLayerCfg0.ImageWidth = 480;
pLayerCfg0.ImageHeight = 272;
pLayerCfg0.FBStartAdress = (uint32_t) FRAME_BUFFER_ADDRESS_LAYER_0;
HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg0, 0);
}
if (LayerIndex == 1) {
pLayerCfg1.WindowX0 = 0;
pLayerCfg1.WindowX1 = 480;
pLayerCfg1.WindowY0 = 0;
pLayerCfg1.WindowY1 = 272;
pLayerCfg1.PixelFormat = LTDC_PIXEL_FORMAT_ARGB1555;
pLayerCfg1.Alpha = 255;
pLayerCfg1.Alpha0 = 0;
pLayerCfg1.Backcolor.Blue = 0;
pLayerCfg1.Backcolor.Green = 0;
pLayerCfg1.Backcolor.Red = 0;
pLayerCfg1.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
pLayerCfg1.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
pLayerCfg1.ImageWidth = 480;
pLayerCfg1.ImageHeight = 272;
pLayerCfg1.FBStartAdress = (uint32_t) FRAME_BUFFER_ADDRESS_LAYER_1;
HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg1, 1);
}
}
Как мы видим, на второй слой мы включаем формат пикселя поменьше, ибо так написано в документации, поэтому мы не можем применить полноцветный 32-битный вывод к обоим слоям. На первом оставляем полноцветный
Переходим к функции LCD_X_Config. Во первых, подправим имя буфера для первого слоя
LCD_SetVRAMAddrEx(0, (void *) FRAME_BUFFER_ADDRESS_LAYER_0);
И допишем весь аналогичный код тела функции ещё и для второго слоя
LCD_SetDevFunc(0, LCD_DEVFUNC_DRAWBMP_32BPP, (void (*)(void)) CUSTOM_DrawBitmap32bpp);
/* For multiple buffers */
GUI_MULTIBUF_ConfigEx(1, NUM_BUFFERS);
/* Set display driver and color conversion for the 1st layer */
GUI_DEVICE_CreateAndLink(GUIDRV_LIN_16, GUICC_M1555I, 0, 1);
/* Sets the size of the virtual display area for the 1st layer*/
LCD_SetVSizeEx(1, XSIZE_PHYS, YSIZE_PHYS);
/* Sets the size of the virtual display area for the 1st layer*/
LCD_SetSizeEx(1, XSIZE_PHYS, YSIZE_PHYS);
/* Sets the address of the VRAM for the 2nd layer*/
LCD_SetVRAMAddrEx(1, (void *) FRAME_BUFFER_ADDRESS_LAYER_1);
/* Set custom functions for several operations */
LCD_SetDevFunc(1, LCD_DEVFUNC_COPYBUFFER, (void (*)(void)) CUSTOM_CopyBuffer);
LCD_SetDevFunc(1, LCD_DEVFUNC_COPYRECT, (void (*)(void)) CUSTOM_CopyRect);
LCD_SetDevFunc(1, LCD_DEVFUNC_FILLRECT, (void (*)(void)) CUSTOM_FillRect);
}
Далее идём в функцию LCD_X_DisplayDriver.
В конце каждого кейса вместо return 0 напишем break, а то непривычно как-то, хоть и работает. Лишние фигурные скобки тоже поубираем
return 0;
break;
Тогда переменную r проинициализируем нулём, хотя по идее она и так ноль, но лучше всё-таки сделать это явно.
int r
=0
;
Подкорректируем вызов функции инициализации слоя, добавив туда аргумент
case LCD_X_INITCONTROLLER: {
Layer_init(LayerIndex);
В следующем кейсе также разделимся по слоям
case LCD_X_SETORG: {
if (LayerIndex == 0) {
HAL_LTDC_SetAddress(&hltdc, FRAME_BUFFER_ADDRESS_LAYER_0, 0);
} else {
HAL_LTDC_SetAddress(&hltdc, FRAME_BUFFER_ADDRESS_LAYER_1, 1);
}
break;
Глобальные переменные для обычного буфера, а также буфера ожидания также разделим по слоям
int32_t bufferIndex_layer_0 = 0;
int32_t bufferIndex_layer_1 = 0;
int32_t pending_buffer_layer_0 = -1;
int32_t pending_buffer_layer_1 = -1;
Вернёмся в функцию LCD_X_DisplayDriver и подправим следующий кейс, также распределив код по слоям
case LCD_X_SHOWBUFFER:
if (LayerIndex == 0) {
pending_buffer_layer_0 = ((LCD_X_SHOWBUFFER_INFO *) pData)->Index;
} else {
pending_buffer_layer_1 = ((LCD_X_SHOWBUFFER_INFO *) pData)->Index;
}
break;
Здесь уберём инициализацию LTDC, так как она у нас уже инициализирована
case LCD_X_ON:
//__HAL_LTDC_ENABLE(&hltdc);
break;
Код следующего кейса мы также распределим по слоям
case LCD_X_SETSIZE:
if (LayerIndex == 0) {
GUI_GetLayerPosEx(0, &xPos, &yPos);
HAL_LTDC_SetWindowPosition(&hltdc, xPos, yPos, 0);
} else {
GUI_GetLayerPosEx(1, &xPos, &yPos);
HAL_LTDC_SetWindowPosition(&hltdc, xPos, yPos, 1);
}
break;
Также добавим следующий кейс для включения и отключения слоёв, ну или другими словами для визуализации определённого слоя в реальном времени
case LCD_X_SETVIS:
if (((LCD_X_SETVIS_INFO *) pData)->OnOff == 1) {
__HAL_LTDC_LAYER_ENABLE(&hltdc, LayerIndex);
} else {
__HAL_LTDC_LAYER_DISABLE(&hltdc, LayerIndex);
}
__HAL_LTDC_RELOAD_CONFIG(&hltdc);
break;
Добавим идентификатор слоя в две функции. И в прототипах и в реализации
static void DMA2D_CopyBuffer(int LayerIndex, void * pSrc, void * pDst, U32 xSize, U32 ySize, U32 OffLineSrc, U32 OffLineDst)
static void DMA2D_FillBuffer(int LayerIndex, void * pDst, U32 xSize, U32 ySize, U32 OffLine, U32 ColorIndex)
Также внесём изменения в разрезе слоёв в пользовательскую функцию копирования буфера
static void CUSTOM_CopyBuffer(int LayerIndex, int IndexSrc, int IndexDst) {
U32 BufferSize, AddrSrc, AddrDst;
if (LayerIndex == 0) {
BufferSize = XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(0) >> 3); //in bytes
AddrSrc = FRAME_BUFFER_ADDRESS_LAYER_0 + BufferSize * IndexSrc;
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_0 + BufferSize * IndexDst;
bufferIndex_layer_0 = IndexDst;
} else {
BufferSize = XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(1) >> 3); //in bytes
AddrSrc = FRAME_BUFFER_ADDRESS_LAYER_1 + BufferSize * IndexSrc;
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_1 + BufferSize * IndexDst;
bufferIndex_layer_1 = IndexDst;
}
DMA2D_CopyBuffer(LayerIndex, (void *) AddrSrc, (void *) AddrDst, XSIZE_PHYS, YSIZE_PHYS, 0, 0);
}
В следующей функции CUSTOM_CopyRect проделаем приблизительно то же самое
static void CUSTOM_CopyRect(int LayerIndex, int x0, int y0, int x1, int y1, int xSize, int ySize) {
U32 AddrSrc, AddrDst;
if (LayerIndex == 0) {
AddrSrc = FRAME_BUFFER_ADDRESS_LAYER_0 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixel() >> 3) * bufferIndex_layer_0) + (y0 * XSIZE_PHYS + x0) * (LCD_GetBitsPerPixel() >> 3);
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_0 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixel() >> 3) * bufferIndex_layer_0) + (y1 * XSIZE_PHYS + x1) * (LCD_GetBitsPerPixel() >> 3);
} else {
AddrSrc = FRAME_BUFFER_ADDRESS_LAYER_1 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixel() >> 3) * bufferIndex_layer_0) + (y0 * XSIZE_PHYS + x0) * (LCD_GetBitsPerPixel() >> 3);
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_1 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixel() >> 3) * bufferIndex_layer_0) + (y1 * XSIZE_PHYS + x1) * (LCD_GetBitsPerPixel() >> 3);
}
DMA2D_CopyBuffer(LayerIndex, (void *) AddrSrc, (void *) AddrDst, xSize, ySize, (XSIZE_PHYS - xSize), (XSIZE_PHYS - xSize));
}
Далее CUSTOM_FillRect
static void CUSTOM_FillRect(int LayerIndex, int x0, int y0, int x1, int y1, U32 PixelIndex) {
U32 AddrDst;
int xSize, ySize;
xSize = x1 - x0 + 1;
ySize = y1 - y0 + 1;
if (LayerIndex == 0) {
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_0 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(0) >> 3) * bufferIndex_layer_0) + (y0 * XSIZE_PHYS + x0) * (LCD_GetBitsPerPixelEx(0) >> 3);
} else {
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_1 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(1) >> 3) * bufferIndex_layer_1) + (y0 * XSIZE_PHYS + x0) * (LCD_GetBitsPerPixelEx(1) >> 3);
}
DMA2D_FillBuffer(LayerIndex, (void *) AddrDst, xSize, ySize, XSIZE_PHYS - xSize, PixelIndex);
}
Следующая функция — CUSTOM_DrawBitmap32bpp
static void CUSTOM_DrawBitmap32bpp(int LayerIndex, int x, int y, U8 const * p, int xSize, int ySize, int BytesPerLine) {
U32 AddrDst;
int OffLineSrc, OffLineDst;
if (LayerIndex == 0) {
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_0 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(0) >> 3) * bufferIndex_layer_0) + (y * XSIZE_PHYS + x) * (LCD_GetBitsPerPixelEx(0) >> 3);
} else {
AddrDst = FRAME_BUFFER_ADDRESS_LAYER_1 + (XSIZE_PHYS * YSIZE_PHYS * (LCD_GetBitsPerPixelEx(1) >> 3) * bufferIndex_layer_1) + (y * XSIZE_PHYS + x) * (LCD_GetBitsPerPixelEx(1) >> 3);
}
OffLineSrc = (BytesPerLine / 4) - xSize;
OffLineDst = XSIZE_PHYS - xSize;
DMA2D_CopyBuffer(0, (void *) p, (void *) AddrDst, xSize, ySize, OffLineSrc, OffLineDst);
}
В функции DMA2D_CopyBuffer за исключением добавления входного аргумента изменения произойдут только в одном месте
DMA2D->OOR = OffLineDst;
if (LayerIndex == 0) {
DMA2D->FGPFCCR = LTDC_PIXEL_FORMAT_ARGB8888;
} else {
DMA2D->FGPFCCR = LTDC_PIXEL_FORMAT_ARGB1555;
}
DMA2D->NLR = (U32) (xSize << 16) | (U16) ySize;
То же самое и с функцией DMA2D_FillBuffer
DMA2D->OOR = OffLine;
if (LayerIndex == 0) {
DMA2D->OPFCCR = LTDC_PIXEL_FORMAT_ARGB8888;
} else {
DMA2D->OPFCCR = LTDC_PIXEL_FORMAT_ARGB1555;
}
DMA2D->NLR = (U32) (xSize << 16) | (U16) ySize;
Аналогичные поправки проделаем и в функции HAL_LTDC_LineEvenCallback
void HAL_LTDC_LineEvenCallback(LTDC_HandleTypeDef *hltdc) {
U32 Addr;
if (pending_buffer_layer_0 >= 0) {
Addr = FRAME_BUFFER_ADDRESS_LAYER_0 + XSIZE_PHYS * YSIZE_PHYS * pending_buffer_layer_0 * (LCD_GetBitsPerPixelEx(0) >> 3);
__HAL_LTDC_LAYER(hltdc, 0)->CFBAR = Addr;
__HAL_LTDC_RELOAD_CONFIG(hltdc);
GUI_MULTIBUF_ConfirmEx(0, pending_buffer_layer_0);
pending_buffer_layer_0 = -1;
}
if (pending_buffer_layer_1 >= 0) {
Addr = FRAME_BUFFER_ADDRESS_LAYER_1 + XSIZE_PHYS * YSIZE_PHYS * pending_buffer_layer_1 * (LCD_GetBitsPerPixelEx(1) >> 3);
__HAL_LTDC_LAYER(hltdc, 1)->CFBAR = Addr;
__HAL_LTDC_RELOAD_CONFIG(hltdc);
GUI_MULTIBUF_ConfirmEx(1, pending_buffer_layer_1);
pending_buffer_layer_1 = -1;
}
HAL_LTDC_ProgramLineEvent(hltdc, 0); //Define the position of the line interrupt
}
С конфигурацией закончено. Теперь, если мы попытаемся собрать код и запустить проект, то мы, скорей всего увидим вот такие артефакты в лучшем случае, а то и вообще ничего не увидим
Основной слой у нас первый, поэтому надо выбрать его в функции main() вот тут
GUI_Init();
GUI_SelectLayer(1);
GUI_SetBkColor(GUI_DARKBLUE);
Также единички вместо нулей нужно проставить везде в функциях включения и отключения мультибуферного вывода
GUI_MULTIBUF_BeginEx(1);
...
GUI_MULTIBUF_EndEx(1);
Теперь после сборки и прошивки кода всё будет нормально. Один слой у нас работает
Далее давайте попробуем поработать с двумя слоями. Для начала можно попробовать пример из документации, немного изменив в нём координаты, так как там, видимо, идёт работа с дисплеем другого разрешения.
Для этого вывод рисунков пока удалим, также удалим вот этот код
GUI_SelectLayer(1);
GUI_SetBkColor(GUI_DARKBLUE);
GUI_Clear();
А добавим вот такой:
Error_Handler();
}
GUI_SelectLayer(0);
GUI_SetColor(GUI_BLACK);
GUI_Clear();
GUI_SetColor(GUI_RED);
GUI_FillRect(0, 0, 480, 90);
GUI_SetColor(GUI_GREEN);
GUI_FillRect(0, 91, 480, 180);
GUI_SetColor(GUI_BLUE);
GUI_FillRect(0, 181, 480, 271);
GUI_SetBkColor(GUI_GREEN);
GUI_SetFont(GUI_FONT_24B_1);
GUI_DispStringHCenterAt("Layer 0", 240, 124);
GUI_SelectLayer(1);
GUI_SetBkColor(GUI_WHITE);
GUI_Clear();
GUI_SetColor(GUI_BLACK);
GUI_DispStringHCenterAt("Layer 1", 240, 4);
GUI_SetColor(GUI_TRANSPARENT);
GUI_FillCircle(240, 136, 108);
GUI_FillRect(10, 10, 100, 262);
GUI_FillRect(380, 10, 470, 262);
Получится вот такая картина
Расскажу немного по коду.
Сначала выбираем слой 0. Окрашиваем в разные цвета три горизонтальных участка и в центре экрана добавляем надпись «Layer 0». Затем выбираем слой 1, окрашиваем экран в белый цвет, выбираем чёрный цвет, добавляем надпись сверху «Layer 1», устанавливаем прозрачный цвет, и этим прозрачным цветом рисуем два прямоугольника и круг, сквозь которые мы и видим нулевой слой. Также не забываем очищать сначала каждый слой, закрашивая его чёрным цветом (в примере этого нет, но на практике иногда остаются старые изображения на фоне). У нас данная установка работает только на слой 0, так как первый мы полностью окрашиваем в белый цвет.
Тем самым мы проверили, что у нас работают слои и частично испытали прозрачность.
Теперь другой тест уже на прозрачность. Для этого только что добавленный код мы закомментируем и добавим следующий, взятый также из примера с изменением координат под наш дисплей
Error_Handler();
}
GUI_SelectLayer(1);
GUI_SetColor(GUI_BLACK);
GUI_Clear();
GUI_SetColor(GUI_BLUE);
GUI_FillCircle(240, 136, 120);
GUI_SetColor(GUI_YELLOW);
for (i = 0; i < 271; i++) {
U8 Alpha;
Alpha = (i * 255 / 271);
GUI_SetAlpha(Alpha);
GUI_DrawHLine(i, 239 - (i*240/272), 239 + (i*240/272));
}
GUI_SetAlpha(0x80);
GUI_SetColor(GUI_MAGENTA);
GUI_SetFont(&GUI_Font32B_ASCII);
GUI_SetTextMode(GUI_TM_TRANS);
GUI_DispStringHCenterAt("Alphablending", 240, 5);
GUI_SetAlpha(0);
Соберём код и запустим его на выполнение.
На экране после этого, если мы до этого всё нормально написали, получим вот такое
Смысл кода такой.
Выбираем слой 1, рисуем на нем круг голубого цвета, затем выбираем желтый цвет и в цикле рисуем горизонтальные линии различной прозрачности и ширины, тем самым получая равнобедренный треугольник, который к низу экрана становится всё прозрачнее и прозрачнее. Далее выбираем прозрачность 128 (полупрозрачность), цвет MAGENTA и этим цветом пишем строку, включив перед этим режим GUI_TM_TRANS. Данный режим устанавливает вывод текста без фона. Ну и по окончанию кода мы возвращаем обычный непрозрачный режим.
Я думаю, с помощью данных простых примеров мы теперь научились использовать слои и прозрачность.
Далее нас уже ждёт работа с оконным режимом, я думаю многие его заждались.
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Почему не отображается нулевой слой. При работе с нулевым слоем, дисплей просто черный. Если нарисуем что нибудь на нулевом слое, затем выберем первый слой и окрасим его прозрачным цветом (GUI_TRANSPARENT), только тогда можно увидеть нулевой слой. Т.е. в память все уходит, а на дисплее не отображается. Все уже перепробовал. Может кто сталкивался. Первый слой "не полноценный" (16бит), и например градиент очень ступенчатый. Нужено работать и с нулевым слоем.
P. S. Спасибо за уроки.
Я понял, изображение на экране это комбинация двух слоёв, чтоб рисовать в первом слое надо что бы второй слой был прозрачным. Сначала подумал что слои как буферы, и каждый в отдельости выводится на экран. Можно удалять комментарии.
Я рад, что у Вас всё прояснилось.
А комментарии пусть живут — история!
Не отображается 0 слой, проверил конфигурацию — вроде все ОК. Незнаю где и копать дальше. Правда у меня плата stm32f429-disco. Но до этого все уроки работали.
Добрый день! Огромное спасибо за ваши уроки! А можете ли Вы провести урок по ToutchGFX, на данный момент эта среда бесплатная. Хотелось бы, чтобы показали, как работать с нею совместно с cubeMX и контроллерами STM32F7. Точнее как потом с помощью этого GUI управлять периферией. Заранее благодарен Вам!!
Спасибо!
Положил в пожелания.
Вообще, я тоже заинтересовался данной библиотекой. У знакомых коллег из компании STM узнаю по лицензии на неё и что она нам позволяет (использование в коммерческих целях и т.д.).
Пока данное пожелание единичное по данной теме, но на карандаш возьму.