在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深度剖析STM32的“GPU”—DMA2D實例詳

RTThread物聯(lián)網(wǎng)操作系統(tǒng) ? 來源:RT-Thread社區(qū) ? 作者:夢千年 ? 2021-06-30 14:50 ? 次閱讀

前言

GPU即圖形處理器,是現(xiàn)代顯卡的核心。在沒有GPU的時代,所有圖形的繪制都是由CPU來完成的,CPU需要計算圖形的邊界、顏色等數(shù)據(jù),并且負責將數(shù)據(jù)寫入顯存。

簡單的圖形還沒有什么問題,但隨著計算機的發(fā)展(尤其是游戲的發(fā)展),需要顯示的圖形圖像越來越復雜,CPU也就越來越力不從心。所以后來GPU應運而生,將CPU從繁重的圖形計算任務中拯救了出來,大大加速了圖形的顯示速度。

單片機這邊也有類似的發(fā)展歷程。在早期的單片機使用場景中,極少有圖形顯示的需求。即使有,也只是簡單的12864之類的顯示設(shè)備,運算量不大,單片機的CPU可以很好的處理。

但是隨著嵌入式圖形的發(fā)展,單片機需要承擔的圖形計算和顯示任務越來越多,嵌入式系統(tǒng)的顯示分辨率和色彩也一路飆升。慢慢地,單片機的CPU對這些計算就開始力不從心了。

所以,自STM32F429開始,一個類似GPU的外設(shè)開始加入到STM32的單片機中,ST稱之為Chrom-ART Accelerator,也叫DMA2D(本文將使用此名稱)。DMA2D可以在很多2D繪圖的場合提供加速,完美嵌合了現(xiàn)代顯卡中“GPU”的功能。

雖然這個“GPU”只能提供2D加速,而且功能非常簡單,與PC中的GPU不可同日而語。但是它已經(jīng)可以滿足大多數(shù)嵌入式開發(fā)中的圖形顯示加速需求,只要用好了DMA2D,我們在單片機上也可以做出流暢、華麗的UI效果。

本文將從實例出發(fā),介紹DMA2D在嵌入式圖形開發(fā)中的可以發(fā)揮的作用。目的是使讀者能簡單、快速地對DAM2D的建立最基本的概念并且學會最基本的用法。

為了防止內(nèi)容過于晦澀和難懂,本文不會對DMA2D的高級功能和特性進行深入地刨析(如詳細介紹DMA2D的架構(gòu)、全部的寄存器等等)。如果需要更加詳細、專業(yè)地學習DAM2D,可以在閱讀完本文后參考《STM32H743中文編程手冊》。

閱讀本文之前需要對STM32中的TFT液晶控制器(LTDC)和基本的圖形知識(如幀緩沖framebuffer、像素、顏色格式等概念)有一定的了解。

另,除了ST之外,其他不少廠商生產(chǎn)的MCU中也存在類似功能的外設(shè)(如NXP在RT系列中設(shè)計的的PxP),不過這些不在本文的討論范圍內(nèi),有興趣的朋友可以自行了解。

準備工作

硬件準備

可以使用任何的,帶有DMA2D外設(shè)的STM32開發(fā)板來驗證本文中的例子,如STM32F429,STM32F746,STM32H750等MCU的開發(fā)板。本文中使用的開發(fā)板是ART-Pi

ART-Pi是由RT-Thread官方出品的開發(fā)板,采用了主頻高達480MHz的STM32H750XB+32MB SDRAM的強悍配置。而且板載了調(diào)試器(ST-Link V2.1),使用起來非常方便,特別適合各種技術(shù)方案的驗證,用來作為本文的硬件演示平臺再合適不過了。

顯示屏可以是任意的彩色TFT顯示屏,推薦使用16位或24位顏色的RGB接口顯示屏。本文中使用的是一塊 3.5‘’ 的TFT液晶顯示屏,接口為RGB666,分辨率為320x240(QVGA)。在LTDC中,配置使用的顏色格式為RGB565

開發(fā)環(huán)境準備

本文中介紹的內(nèi)容和出現(xiàn)的代碼可以在任何你喜歡的開發(fā)環(huán)境中使用,如RT-Thread Studio,MDK,IAR等。

開始本文的實驗前你需要一個以framebuffer技術(shù)驅(qū)動LCD顯示屏的基本工程。運行本文中所有的代碼前都需要預先使能DMA2D。

使能DMA2D可以通過這個宏來實現(xiàn)(硬件初始化時使能一次即可):

1// 使用DMA2D之前一定要先使能DMA2D外設(shè)2__HAL_RCC_DMA2D_CLK_ENABLE();

DMA2D的簡介

我們先來看看ST是怎么描述DMA2D的

乍一看有點晦澀,但其實說白了就以下幾個功能:

顏色填充(矩形區(qū)域)

圖像(內(nèi)存)復制

顏色格式轉(zhuǎn)換(如YCbCr轉(zhuǎn)RGB或RGB888轉(zhuǎn)RGB565)

透明度混合(Alpha Blend)

前兩種都是針對內(nèi)存的操作,后兩個則是運算加速操作。其中,透明度混合、顏色格式轉(zhuǎn)換可以和圖像復制一起進行,這樣就帶來了較大的靈活性。

可以看到,ST對DMA2D的定位就像它的名字一樣,是一個針對圖像處理功能強化過的DMA。而在實際開發(fā)的過程中,我們會發(fā)現(xiàn)DMA2D的使用方式也非常類似傳統(tǒng)的DMA控制器。在某些非圖形處理場合,DMA2D甚至也可以代替?zhèn)鹘y(tǒng)的DMA來發(fā)揮作用。

需要注意的是,ST的不同產(chǎn)品線的DMA2D加速器是有微小區(qū)別的,比如STM32F4系列MCU的DMA2D就沒有ARGB和AGBR顏色格式互轉(zhuǎn)的功能,所以具體需要用到某個功能的時候,最好先查看編程手冊看所需的功能是否被支持。

本文只介紹所有平臺的DMA2D共有的功能。

DMA2D的工作模式

就像傳統(tǒng)DMA有外設(shè)到外設(shè),外設(shè)到存儲器,存儲器到外設(shè)三種工作模式一樣,DMA2D作為一個DMA,也分為以下四種工作模式:

寄存器到存儲器

存儲器到存儲器

存儲器到存儲器并執(zhí)行像素顏色格式轉(zhuǎn)換

存儲器到存儲器且支持像素顏色格式轉(zhuǎn)換和透明度混合

可以看出,前兩種模式起始就是簡單的內(nèi)存操作,而后面兩種模式,則是在進行內(nèi)存復制時,根據(jù)需要同時進行顏色格式轉(zhuǎn)換或/和透明度混合。

DMA2D和HAL庫

大多數(shù)情況下,使用HAL庫可以簡化代碼編寫,提高可移植性。但是在DMA2D的使用時則是個例外。因為HAL庫存在的最大問題就是嵌套層數(shù)再加上各種安全檢測過多效率不夠高。

在操作別的外設(shè)時,使用HAL庫損失的效率并不會有多大的影響。但是對于DMA2D這種以計算和加速為目的的外設(shè),考慮到相關(guān)的操作會在一個屏幕的繪制周期內(nèi)被多次調(diào)用,此時再使用HAL庫就會導致DAM2D的加速效率嚴重下降。

所以,我們大多時候都不會用HAL庫中的相關(guān)函數(shù)來對DMA2D進行操作。為了效率,我們會直接操作寄存器,這樣才能起到最大化的加速效果。

因為我們使用DMA2D的大多數(shù)場合都會頻繁變更工作模式,所以CubeMX中對DMA2D的圖形化配置也失去了意義。

DMA2D場景實例

1. 顏色填充

我們來思考一下如何把它繪制出來。

首先,我們需要使用白色來填充屏幕,作為圖案的背景。這個過程是不能忽略的,否則屏幕上原來顯示的圖案會對我們的主體產(chǎn)生干擾。然后,柱狀圖其實是由4個藍色的矩形方塊和一條線段構(gòu)成的,而線段也可以視作一個特殊的,高度為1的矩形。所以,這個圖形的繪制可以分解為一系列“矩形填充”操作:

使用白色填充一個大小等于屏幕大小的的矩形

使用藍色填充四個數(shù)據(jù)條

使用黑色填充一根高度為1的線段

在畫布中實現(xiàn)任意位置繪制任意大小的矩形的本質(zhì)就是將內(nèi)存區(qū)域中對應像素位置的數(shù)據(jù)設(shè)定為指定的顏色。但是因為framebuffer在內(nèi)存中的存儲是線性的,所以除非矩形的寬度正好和顯示區(qū)域的寬度重合,否看似連續(xù)的矩形的區(qū)域在內(nèi)存中的地址是不連續(xù)的。

下圖展示了典型的內(nèi)存分布情況,其中的數(shù)字表示了frame buffer中每個像素的內(nèi)存地址(相對首地址的偏移,這里忽略掉了一個像素占多個字節(jié)的情況),藍色區(qū)域是我們要填充的矩形。可以看出矩形區(qū)域的內(nèi)存地址是不連續(xù)的。

framebuffer的這種特性使得我們不能簡單使用memset這類高效的操作來實現(xiàn)矩形區(qū)域的填充。通常情況下,我們會使用以下方式的雙重循環(huán)來填充任意矩形,其中xs和ys是矩形左上角在屏幕上的坐標,width和height表示矩形的寬和高,color表示需要填充的顏色:

1for(int y = ys; y 《 ys + height; y++){

2 for(int x = xs; x 《 xs + width; x++){

3 framebuffer[y][x] = color;

4 }

5}

代碼雖然簡單,但實際執(zhí)行時,大量的CPU周期浪費在了判斷、尋址、自增等的操作,實際寫內(nèi)存的時間占比很少。這樣一來,效率就會下降。

這時候DMA2D的寄存器到存儲器工作模式就可以發(fā)揮用場了,DAM2D可以以極高的速度填充矩形的內(nèi)存區(qū)域,即使這些區(qū)域在內(nèi)存中實際是不連續(xù)的。

首先,因為我們只是進行內(nèi)存填充,而不需要進行內(nèi)存拷貝,所以我們要讓DAM2D工作在寄存器到存儲器模式。這通過設(shè)置DMA2D的CR寄存器的[17:16]位為11來實現(xiàn),代碼如下:

1DMA2D-》CR = 0x00030000UL;

然后,我們要告訴DAM2D要填充的矩形的屬性,比如區(qū)域的起始地址在哪里,矩形的寬度有多少像素,矩形的高度有多少。

區(qū)域起始地址是矩形區(qū)域左上角第一個像素的內(nèi)存地址(圖中紅色像素的地址),這個地址由DAM2D的OMAR寄存器管理。而矩形的寬度和高度都是以像素為單位的,分別由NLR寄存器的高16位(寬度)和低16位(高度)來進行管理,具體的代碼如下:

1DMA2D-》OMAR = (uint32_t)(&framebuffer[y][x]); // 設(shè)置填充區(qū)域的起始像素內(nèi)存地址2DMA2D-》NLR = (uint32_t)(width 《《 16) | (uint16_t)height; // 設(shè)置矩形區(qū)域的寬高

接著,因為矩形在內(nèi)存中的地址不連續(xù),所以我們要告訴DMA2D在填充完一行的數(shù)據(jù)后,需要跳過多少個像素(即圖中黃色區(qū)域的長度)。這個值由OOR寄存器管理。計算跳過的像素數(shù)量有一個簡單的方法,即顯示區(qū)域的寬度減去矩形的寬度即可。具體實現(xiàn)代碼如下:

1DMA2D-》OOR = screenWidthPx - width; // 設(shè)置行偏移,即跳過的像素

最后,我們需要告知DAM2D,你將使用什么顏色來進行填充,顏色的格式是什么。這分別由OCOLR和OPFCCR寄存器來管理,其中顏色格式由LTDC_PIXEL_FORMAT_XXX宏來定義,具體代碼如下:

1DMA2D-》OCOLR = color; // 設(shè)置填充使用的顏色2DMA2D-》OPFCCR = pixelFormat; // 設(shè)置顏色格式,比如想設(shè)置成RGB565,就可以使用宏LTDC_PIXEL_FORMAT_RGB565

一切都設(shè)置完畢,DMA2D已經(jīng)獲取到了填充這個矩形所需要的全部信息,接下來,我們要開啟DMA2D的傳輸,這通過將DMA2D的CR寄存器的第0位設(shè)置為1來實現(xiàn):

1DMA2D-》CR |= DMA2D_CR_START; // 開啟DMA2D的數(shù)據(jù)傳輸,DMA2D_CR_START是一個宏,其值為0x01

等DMA2D傳輸開始后,我們只需要等待它傳輸完畢即可。DAM2D傳輸完成后,會自動把CR寄存器的第0位設(shè)置為0,所以我們可以通過以下代碼來等待DAM2D傳輸完成:

1while (DMA2D-》CR & DMA2D_CR_START) {} // 等待DMA2D傳輸完成

tips0:如果你使用了OS,則可以使能DMA2D的傳輸完畢中斷。然后我們可以創(chuàng)建一個信號量并且在開啟傳輸后等待它,隨后在DMA2D的傳輸完畢中斷服務函數(shù)中釋放該信號量。這樣的話CPU就可以在DMA2D工作的時候去干點別的事兒而不是在此處傻等。

tips1:當然,由于實際執(zhí)行時,DMA2D進行內(nèi)存填充的速度實在是太快了,以至于OS切換任務的開銷都比這個時間要長,所以即便使用了OS,我們還是會選擇死等 :)。

為了函數(shù)的通用性考慮,起始傳輸?shù)刂泛托衅贫荚诤瘮?shù)外計算完畢后傳入,我們抽出的完整的函數(shù)代碼如下:

1static inline void DMA2D_Fill( void * pDst, uint32_t width, uint32_t height, uint32_t lineOff, uint32_t pixelFormat, uint32_t color) {

2 3 /* DMA2D配置 */

4 DMA2D-》CR = 0x00030000UL; // 配置為寄存器到儲存器模式 5 DMA2D-》OCOLR = color; // 設(shè)置填充使用的顏色,格式應該與設(shè)置的顏色格式相同 6 DMA2D-》OMAR = (uint32_t)pDst; // 填充區(qū)域的起始內(nèi)存地址 7 DMA2D-》OOR = lineOff; // 行偏移,即跳過的像素,注意是以像素為單位 8 DMA2D-》OPFCCR = pixelFormat; // 設(shè)置顏色格式 9 DMA2D-》NLR = (uint32_t)(width 《《 16) | (uint16_t)height; // 設(shè)置填充區(qū)域的寬和高,單位是像素1011 /* 啟動傳輸 */12 DMA2D-》CR |= DMA2D_CR_START;

1314 /* 等待DMA2D傳輸完成 */15 while (DMA2D-》CR & DMA2D_CR_START) {}

16}

為了方便編寫代碼,我們再包裝一個針對所使用屏幕坐標系的矩形填充函數(shù):

1void FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color){

2 void* pDist = &(((uint16_t*)framebuffer)[y*320 + x]);

3 DMA2D_Fill(pDist, w, h, 320 - w, LTDC_PIXEL_FORMAT_RGB565, color);

4}

最后我們嘗試用代碼把本小節(jié)剛開始的示例圖表畫出來:

1 // 填充背景色2 FillRect(0, 0, 320, 240, 0xFFFF);

3 // 繪制數(shù)據(jù)條4 FillRect(80, 80, 20, 120, 0x001f);

5 FillRect(120, 100, 20, 100, 0x001f);

6 FillRect(160, 40, 20, 160, 0x001f);

7 FillRect(200, 60, 20, 140, 0x001f);

8 // 繪制X軸9 FillRect(40, 200, 240, 1, 0x0000);

2.圖片顯示(內(nèi)存復制)

假設(shè)我們現(xiàn)在要開發(fā)一個游戲,然后想在屏幕上顯示一團跳動的火焰。一般是由美工先把火焰的每一幀都畫出來,然后放到同一張圖片素材里面。

然后我們以一定的間隔輪流顯示每一幀圖像,就可以在屏幕上實現(xiàn)“跳動的火焰”這個效果了。

我們現(xiàn)在略過素材文件加載到內(nèi)存的過程,假設(shè)這張素材圖片已經(jīng)在內(nèi)存中了。然后我們來考慮如何將其中的一幀圖片顯示到屏幕上。通常情況下,我們會這樣實現(xiàn):先計算得出每一幀的數(shù)據(jù)在內(nèi)存中的地址,然后將這一幀圖片的數(shù)據(jù)復制到framebuffer中相應的位置即可。代碼類似于這樣:

1/**

2 * 將素材中的一幀畫面復制到framebuffer中的對應位置

3 * index為畫面在幀序列中的索引

4 */ 5static void General_DisplayFrameAt(uint16_t index) {

6 // 宏說明 7 // #define FRAME_COUNTS 25 // 幀數(shù)量 8 // #define TILE_WIDTH_PIXEL 96 // 每一幀畫面的寬度(等于高度) 9 // #define TILE_COUNT_ROW 5 // 素材中每一行有多少幀1011 // 計算幀起始地址12 uint16_t *pStart = (uint16_t *) img_fireSequenceFrame;

13 pStart += (index / TILE_COUNT_ROW) * (TILE_WIDTH_PIXEL * TILE_WIDTH_PIXEL * TILE_COUNT_ROW);

14 pStart += (index % TILE_COUNT_ROW) * TILE_WIDTH_PIXEL;

1516 // 計算素材地址偏移17 uint32_t offlineSrc = (TILE_COUNT_ROW - 1) * TILE_WIDTH_PIXEL;

18 // 計算framebuffer地址偏移(320是屏幕寬度)19 uint32_t offlineDist = 320 - TILE_WIDTH_PIXEL;

2021 // 將數(shù)據(jù)復制到framebuffer22 uint16_t* pFb = (uint16_t*) framebuffer;

23 for (int y = 0; y 《 TILE_WIDTH_PIXEL; y++) {

24 memcpy(pFb, pStart, TILE_WIDTH_PIXEL * sizeof(uint16_t));

25 pStart += offlineSrc + TILE_WIDTH_PIXEL;

26 pFb += offlineDist + TILE_WIDTH_PIXEL;

27 }

28}

可見要實現(xiàn)這個效果需要大量的內(nèi)存復制操作。在嵌入式系統(tǒng)中,需要大量數(shù)據(jù)復制的時候,硬件DMA的效率是最高的。但是硬件DMA只能搬運地址連續(xù)的數(shù)據(jù),而這里,需要復制的數(shù)據(jù)在源圖片和frambuffer中的地址都是不連續(xù)的,這引來了額外的開銷(與第一小節(jié)中出現(xiàn)的問題相同),也導致我們無法使用硬件DMA來進行高效的數(shù)據(jù)復制。

所以,雖然我們實現(xiàn)了目標,但是效率不高(或者說沒有達到最高)。

為了以最快的速度把素材圖片中的某一塊數(shù)據(jù)搬運到幀緩沖中,我們來看如何使用DMA2D來實現(xiàn)。

首先,因為這次是要在存儲器中進行數(shù)據(jù)復制,所以我們要把DMA2D的工作模式設(shè)定為“存儲器到存儲器模式”,這通過設(shè)置DMA2D的CR寄存器的[17:16]位為00來實現(xiàn),代碼如下:

1DMA2D-》CR = 0x00000000UL;

然后我們要分別設(shè)置源和目標的內(nèi)存地址,與第一節(jié)中不同,因為數(shù)據(jù)源也存在內(nèi)存偏移,所以我們要同時設(shè)定源和目標位置的數(shù)據(jù)偏移

1DMA2D-》FGMAR = (uint32_t)pSrc; // 源地址2DMA2D-》OMAR = (uint32_t)pDst; // 目標地址3DMA2D-》FGOR = OffLineSrc; // 源數(shù)據(jù)偏移(像素)4DMA2D-》OOR = OffLineDst; // 目標地址偏移(像素)

然后依然是設(shè)置要復制的圖像的寬和高,以及顏色格式,這點與第一小節(jié)中的相同

1DMA2D-》FGPFCCR = pixelFormat;

2DMA2D-》NLR = (uint32_t)(xSize 《《 16) | (uint16_t)ySize;

同樣的方式,我們開啟DMA2D的傳輸,并等待傳輸完成:

1/* 啟動傳輸 */2DMA2D-》CR |= DMA2D_CR_START;

34/* 等待DMA2D傳輸完成 */5while (DMA2D-》CR & DMA2D_CR_START) {}

最終我們抽出的函數(shù)如下:

1static void DMA2D_MemCopy(uint32_t pixelFormat, void * pSrc, void * pDst, int xSize, int ySize, int OffLineSrc, int OffLineDst)

2{

3 /* DMA2D配置 */ 4 DMA2D-》CR = 0x00000000UL;

5 DMA2D-》FGMAR = (uint32_t)pSrc;

6 DMA2D-》OMAR = (uint32_t)pDst;

7 DMA2D-》FGOR = OffLineSrc;

8 DMA2D-》OOR = OffLineDst;

9 DMA2D-》FGPFCCR = pixelFormat;

10 DMA2D-》NLR = (uint32_t)(xSize 《《 16) | (uint16_t)ySize;

1112 /* 啟動傳輸 */13 DMA2D-》CR |= DMA2D_CR_START;

1415 /* 等待DMA2D傳輸完成 */16 while (DMA2D-》CR & DMA2D_CR_START) {}

17}

為了方便,我們包裝一個調(diào)用它的函數(shù):

1static void DMA2D_DisplayFrameAt(uint16_t index){

2 3 uint16_t *pStart = (uint16_t *)img_fireSequenceFrame;

4 pStart += (index / TILE_COUNT_ROW) * (TILE_WIDTH_PIXEL * TILE_WIDTH_PIXEL * TILE_COUNT_ROW);

5 pStart += (index % TILE_COUNT_ROW) * TILE_WIDTH_PIXEL;

6 uint32_t offlineSrc = (TILE_COUNT_ROW - 1) * TILE_WIDTH_PIXEL;

7 8 9 DMA2D_MemCopy(LTDC_PIXEL_FORMAT_RGB565, (void*) pStart, pDist, TILE_WIDTH_PIXEL, TILE_WIDTH_PIXEL, offlineSrc, offlineDist);

10}

然后輪流播放每一幀圖片,這里設(shè)置的幀間隔是50毫秒,并且將目標地址定義到了frambuffer的中央:

1while(1){

2 for(int i = 0; i 《 FRAME_COUNTS; i++){

3 DMA2D_DisplayFrameAt(i);

4 HAL_Delay(FRAME_TIME_INTERVAL);

5 }

6}

3.圖片漸變切換

假設(shè)我們要開發(fā)一個看圖應用,在兩張圖片進行切換時,直接進行切換會顯得比較生硬,所以我們要加入切換時的動態(tài)效果,而漸變切換(淡入淡出)是一個很經(jīng)常使用的,而且看起來還不錯的效果。

這里我們需要先了解一下透明度混合(Alpha Blend)的基本概念。首先透明度混合需要有一個前景,一個背景。而混合的結(jié)果就相當于透過前景看背景時的效果。如果前景完全不透明,那么就完全看不到背景,反之如果前景完全透明,那么就只能看到背景。而如果前景是半透明的,則結(jié)果就是兩者根據(jù)前景色的透明度按照一定的規(guī)則進行混合。

如果1表示完全透明,0表示不透明,則透明度的混合公式如下,其中A是背景色,B是前景色:

1X(C)=(1-alpha)*X(B) + alpha*X(A)

因為顏色有RGB三個通道,所以我們需要對三通道都進行計算,計算完成后在進行組合:

1R(C)=(1-alpha)*R(B) + alpha*R(A)

2G(C)=(1-alpha)*G(B) + alpha*G(A)

3B(C)=(1-alpha)*B(B) + alpha*B(A)

而在程序中為了效率起見(CPU對于浮點的運算速度很慢),我們并不用0~1這個范圍的值。通常情況下我們一般會使用一個8bit的數(shù)值來表示透明度,范圍從0~255。需要注意的是,這個數(shù)值越大表示越不透明,也就是說255是完全不透明,而0表示完全透明(所以也叫不透明度),然后我們可以得到最終的公式:

1outColor = ((int) (fgColor * alpha) + (int) (bgColor) * (256 - alpha)) 》》 8;

實現(xiàn)RGB565顏色格式像素的透明度混合代碼:

1typedef struct{ 2 uint16_t r:5;

3 uint16_t g:6;

4 uint16_t b:5;

5}RGB565Struct;

6 7static inline uint16_t AlphaBlend_RGB565_8BPP(uint16_t fg, uint16_t bg, uint8_t alpha) {

8 RGB565Struct *fgColor = (RGB565Struct*) (&fg);

9 RGB565Struct *bgColor = (RGB565Struct*) (&bg);

10 RGB565Struct outColor;

1112 outColor.r = ((int) (fgColor-》r * alpha) + (int) (bgColor-》r) * (256 - alpha)) 》》 8;

13 outColor.g = ((int) (fgColor-》g * alpha) + (int) (bgColor-》g) * (256 - alpha)) 》》 8;

14 outColor.b = ((int) (fgColor-》b * alpha) + (int) (bgColor-》b) * (256 - alpha)) 》》 8;

151617 return *((uint16_t*)&outColor);

18}

了解了透明度混合的概念,也實現(xiàn)了單個像素的透明度混合后,我們來看如何實現(xiàn)圖片的漸變切換。

假設(shè)整個漸變在30幀內(nèi)完成,我們需要在內(nèi)存中開辟一塊兒大小等于圖片的緩沖區(qū)。然后我們以第一張圖片(當前顯示的圖片)為背景,第二張圖片(接下來顯示的圖片)為前景,然后為前景設(shè)置一個透明度,對每個像素進行透明度混合,并且將混合結(jié)果暫存至緩沖區(qū)中。待混合結(jié)束后,將緩沖區(qū)中的數(shù)據(jù)復制到framebuffer中即完成了一幀的顯示。接下來繼續(xù)進行第二幀、第三幀……逐漸增大前景的不透明度,直到前景色的變?yōu)椴煌该鳎赐瓿闪藞D片的漸變切換。

因為每一幀都需要對兩張圖片中的每一個像素都進行混合運算,這帶了來巨大的運算量。交給CPU實現(xiàn)是很不明智的行為,所以我們還是把這些工作交給DMA2D來實現(xiàn)吧。

這次用到了DMA2D的混合功能,所以我們要使能DAM2D的帶顏色混合的存儲器到存儲器模式,對應CR寄存器[17:16]位的值為10,即:

1DMA2D-》CR = 0x00020000UL; // 設(shè)置工作模式為存儲器到存儲器并帶顏色混合

然后分別設(shè)置前景、背景和輸出數(shù)據(jù)的內(nèi)存地址和數(shù)據(jù)傳輸偏移、傳輸圖像的寬和高:

1DMA2D-》FGMAR = (uint32_t)pFg; // 設(shè)置前景數(shù)據(jù)內(nèi)存地址2DMA2D-》BGMAR = (uint32_t)pBg; // 設(shè)置背景數(shù)據(jù)內(nèi)存地址3DMA2D-》OMAR = (uint32_t)pDst; // 設(shè)置數(shù)據(jù)輸出內(nèi)存地址45DMA2D-》FGOR = offlineFg; // 設(shè)置前景數(shù)據(jù)傳輸偏移6DMA2D-》BGOR = offlineBg; // 設(shè)置背景數(shù)據(jù)傳輸偏移7DMA2D-》OOR = offlineDist; // 設(shè)置數(shù)據(jù)輸出傳輸偏移89DMA2D-》NLR = (uint32_t)(xSize 《《 16) | (uint16_t)ySize; // 設(shè)置圖像數(shù)據(jù)寬高(像素)

設(shè)置顏色格式。這里設(shè)置前景色的顏色格式時需要注意,因為如果使用的是ARGB這樣的顏色格式,那么我們進行透明度混合時,顏色數(shù)據(jù)中本身的alpha通道就會對混合結(jié)果產(chǎn)生影響,所以我們這里要設(shè)定在進行混合操作時,忽略前景色自身的alpha通道。并強制設(shè)定混合時的透明度。

輸出顏色格式和背景顏色格式

1DMA2D-》FGPFCCR = pixelFormat // 設(shè)置前景色顏色格式2 | (1UL 《《 16) // 忽略前景顏色數(shù)據(jù)中的Alpha通道3 | ((uint32_t)opa 《《 24); // 設(shè)置前景色不透明度45DMA2D-》BGPFCCR = pixelFormat; // 設(shè)置背景顏色格式6DMA2D-》OPFCCR = pixelFormat; // 設(shè)置輸出顏色格式

tips0:有時我們會遇到一張帶有透明通道的圖片與背景疊加顯示的情況,此時就不應該禁用顏色本身的alpha通道

tips1:這個模式下,我們不僅可以進行顏色混合,還可以同時轉(zhuǎn)換顏色格式,可以根據(jù)需要設(shè)置前景和背景以及輸出的顏色格式

最后,啟動傳輸即可:

1/* 啟動傳輸 */2DMA2D-》CR |= DMA2D_CR_START;

34/* 等待DMA2D傳輸完成 */5while (DMA2D-》CR & DMA2D_CR_START) {}

完整代碼如下:

1void _DMA2D_MixColors(void* pFg, void* pBg, void* pDst,

2 uint32_t offlineFg, uint32_t offlineBg, uint32_t offlineDist,

3 uint16_t xSize, uint16_t ySize,

4 uint32_t pixelFormat, uint8_t opa) {

5 6 DMA2D-》CR = 0x00020000UL; // 設(shè)置工作模式為存儲器到存儲器并帶顏色混合 7 8 DMA2D-》FGMAR = (uint32_t)pFg; // 設(shè)置前景數(shù)據(jù)內(nèi)存地址 9 DMA2D-》BGMAR = (uint32_t)pBg; // 設(shè)置背景數(shù)據(jù)內(nèi)存地址10 DMA2D-》OMAR = (uint32_t)pDst; // 設(shè)置數(shù)據(jù)輸出內(nèi)存地址1112 DMA2D-》FGOR = offlineFg; // 設(shè)置前景數(shù)據(jù)傳輸偏移13 DMA2D-》BGOR = offlineBg; // 設(shè)置背景數(shù)據(jù)傳輸偏移14 DMA2D-》OOR = offlineDist; // 設(shè)置數(shù)據(jù)輸出傳輸偏移1516 DMA2D-》NLR = (uint32_t)(xSize 《《 16) | (uint16_t)ySize; // 設(shè)置圖像數(shù)據(jù)寬高(像素)1718 DMA2D-》FGPFCCR = pixelFormat // 設(shè)置前景色顏色格式19 | (1UL 《《 16) // 忽略前景顏色數(shù)據(jù)中的Alpha通道20 | ((uint32_t)opa 《《 24); // 設(shè)置前景色不透明度2122 DMA2D-》BGPFCCR = pixelFormat; // 設(shè)置背景顏色格式23 DMA2D-》OPFCCR = pixelFormat; // 設(shè)置輸出顏色格式2425 /* 啟動傳輸 */26 DMA2D-》CR |= DMA2D_CR_START;

2728 /* 等待DMA2D傳輸完成 */29 while (DMA2D-》CR & DMA2D_CR_START) {}

30}

編寫測試代碼,這次不需要二次包裝函數(shù)了:

1void DMA2D_AlphaBlendDemo(){

2 3 const uint16_t lcdXSize = 320, lcdYSize = 240;

4 const uint8_t cnvFrames = 60; // 60幀完成切換 5 const uint32_t interval = 33; // 每秒30幀 6 uint32_t time = 0;

7 8 // 計算輸出位置的內(nèi)存地址 9 uint16_t distX = (lcdXSize - DEMO_IMG_WIDTH) / 2;

10 uint16_t distY = (lcdYSize - DEMO_IMG_HEIGHT) / 2;

11 uint16_t* pFb = (uint16_t*) framebuffer;

12 uint16_t* pDist = pFb + distX + distY * lcdYSize;

13 uint16_t offlineDist = lcdXSize - DEMO_IMG_WIDTH;

1415 uint8_t nextImg = 1;

16 uint16_t opa = 0;

17 void* pFg = 0;

18 void* pBg = 0;

19 while(1){

20 // 切換前景/背景圖片21 if(nextImg){

22 pFg = (void*)img_cat;

23 pBg = (void*)img_fox;

24 }

25 else{

26 pFg = (void*)img_fox;

27 pBg = (void*)img_cat;

28 }

2930 // 完成切換31 for(int i = 0; i 《 cnvFrames; i++){

32 time = HAL_GetTick();

33 opa = 255 * i / (cnvFrames-1);

34 _DMA2D_MixColors(pFg, pBg, pDist,

35 0,0,offlineDist,

36 DEMO_IMG_WIDTH, DEMO_IMG_HEIGHT,

37 LTDC_PIXEL_FORMAT_RGB565, opa);

38 time = HAL_GetTick() - time;

39 if(time 《 interval){

40 HAL_Delay(interval - time);

41 }

42 }

43 nextImg = !nextImg;

44 HAL_Delay(5000);

45 }

46}

性能對比

前面介紹了三種嵌入式圖形開發(fā)種的實例,并對分別介紹了通過傳統(tǒng)和DMA2D實現(xiàn)的方法。這時候肯定有朋友會問,DMA2D實現(xiàn),比起傳統(tǒng)方法實現(xiàn),到底能快多少呢?我們來實際測試一下。

共同的測試條件如下:

framebuffer放置在SDRAM中,320x240,RGB565

SDRAM工作頻率100MHz,CL2,16位帶寬。

MCU為STM32H750XB,主頻400MHz,開啟I-Cache和D-Cache

代碼和資源在內(nèi)部Flash上,64位AXI總線,速度為200MHz。

GCC編譯器(版本:arm-atollic-eabi-gcc-6.3.1)

測試項目:矩形填充

繪制上一章第1節(jié)中的圖表,繪制10000次,統(tǒng)計結(jié)果

測試項目:內(nèi)存復制

繪制上一章第2節(jié)中的序列幀10000幀,統(tǒng)計結(jié)果

測試項目:透明度混合

漸變切換上一章第3小節(jié)中的兩張圖片100次,每次30幀完成,共計3000幀

混合結(jié)果直接輸出到framebuffer,不再通過緩沖區(qū)緩沖

性能測試總結(jié)

由上面的測試結(jié)果可以看出,DAM2D至少有2個優(yōu)勢:

一是速度更快:在部分項目中,DMA2D實現(xiàn)的速度相比純軟件實現(xiàn)最高可以達到30倍的差距!這還是在主頻高達400MHz還帶L1-Cache的STM32H750平臺上測試的結(jié)果,如果是在無cache且主頻較低的STM32F4平臺上進行測試,差距會進一步拉大。

二是性能更加穩(wěn)定:由測試結(jié)果可以看出,DMA2D實現(xiàn)的方式受編譯器優(yōu)化等級的影響非常小,幾乎可以忽略不計,這意味著,無論你使用IAR,GCC或是MDK,使用DMA2D都可以達到相同的性能表現(xiàn)。不會出現(xiàn)同一段代碼移植后性能相差很大的情況。

除這兩個直觀的結(jié)果外,其實還有第三點優(yōu)勢,那就是代碼編寫更加簡單。DMA2D的寄存器不多,而且比較直觀。在某些場合,使用起來要比軟件實現(xiàn)方便的多。

結(jié)語

本文中的三個實例,都是我本人在嵌入式圖形開發(fā)中經(jīng)常遇到的情況。實際上,DMA2D的用法還有很多,有興趣的話可以參考《STM32H743中文編程手冊》中的相關(guān)內(nèi)容,相信有了本文的基礎(chǔ),在閱讀里面的內(nèi)容時一定會事半功倍。

受限于作者的技術(shù),文章中的內(nèi)容無法做到100%的正確,如果存在錯誤,請大家指出,謝謝。

編輯:jq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • mcu
    mcu
    +關(guān)注

    關(guān)注

    146

    文章

    17751

    瀏覽量

    358726
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    11015

    瀏覽量

    215380
  • gpu
    gpu
    +關(guān)注

    關(guān)注

    28

    文章

    4889

    瀏覽量

    130468
  • 液晶控制器
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    7624
  • DMA2D
    +關(guān)注

    關(guān)注

    0

    文章

    5

    瀏覽量

    2192

原文標題:STM32的“GPU”——DMA2D實例詳解

文章出處:【微信號:RTThread,微信公眾號:RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦
    熱點推薦

    求助,關(guān)于正點原子阿波羅H743使用DMA2D后普通刷出現(xiàn)問題求解

    正點原子阿波羅H743使用DMA2D后普通刷屏出現(xiàn)問題: 代碼: import utime as timefrom machine import LCD# Import the LCD class
    發(fā)表于 04-29 08:02

    STM32U5+8bit_8080 LCD怎么配置touchgfx?

    我參與的項目使用STM32U575做為主空,使用fmc來驅(qū)動8080接口的8bitLCD,配置touchgfx時不能配置為8bit,其他選型例如DMA2D也使用不了,我該怎么做?
    發(fā)表于 04-28 08:12

    STM32U5+8bit_8080 LCD怎么配置touchgfx?

    我參與的項目使用STM32U575做為主空,使用fmc來驅(qū)動8080接口的8bitLCD,配置touchgfx時不能配置為8bit,其他選型例如DMA2D也使用不了,我該怎么做?
    發(fā)表于 04-27 07:11

    GPU深度學習中的應用 GPUs在圖形設(shè)計中的作用

    隨著人工智能技術(shù)的飛速發(fā)展,深度學習作為其核心部分,已經(jīng)成為推動技術(shù)進步的重要力量。GPU(圖形處理單元)在深度學習中扮演著至關(guān)重要的角色,其強大的并行處理能力使得訓練復雜的神經(jīng)網(wǎng)絡(luò)模型成為可能
    的頭像 發(fā)表于 11-19 10:55 ?1338次閱讀

    DMA是什么?詳細介紹

    系統(tǒng)性能。 DMA(直接內(nèi)存訪問)概述 1. DMA的定義 直接內(nèi)存訪問(DMA)是一種硬件特性,允許外圍設(shè)備直接讀寫系統(tǒng)內(nèi)存,而不需要CPU的直接控制。這種技術(shù)主要用于高速數(shù)據(jù)傳輸,如磁盤讀寫、網(wǎng)絡(luò)通信等。
    的頭像 發(fā)表于 11-11 10:49 ?1.7w次閱讀

    PyTorch GPU 加速訓練模型方法

    深度學習領(lǐng)域,GPU加速訓練模型已經(jīng)成為提高訓練效率和縮短訓練時間的重要手段。PyTorch作為一個流行的深度學習框架,提供了豐富的工具和方法來利用GPU進行模型訓練。 1. 了解
    的頭像 發(fā)表于 11-05 17:43 ?1155次閱讀

    【「算力芯片 | 高性能 CPU/GPU/NPU 微架構(gòu)分析」閱讀體驗】--了解算力芯片GPU

    之一,用來構(gòu)建復雜的3D模型和場景。 在圖形學中,圖元是圖形的基本元素。在圖形渲染中,一個三角形的每個頂點都包含位置、顏色、紋理坐標等屬性。 ●光柵化銜接 3D2D 世界 在頂點計算和像素計算
    發(fā)表于 11-03 12:55

    GPU深度學習應用案例

    GPU深度學習中的應用廣泛且重要,以下是一些GPU深度學習應用案例: 一、圖像識別 圖像識別是深度學習的核心應用領(lǐng)域之一,
    的頭像 發(fā)表于 10-27 11:13 ?975次閱讀

    深度學習GPU加速效果如何

    圖形處理器(GPU)憑借其強大的并行計算能力,成為加速深度學習任務的理想選擇。
    的頭像 發(fā)表于 10-17 10:07 ?480次閱讀

    亞馬遜云科技宣布Amazon EC2 P5e實例正式可用 由英偉達H200 GPU提供支持

    北京2024年9月18日?/美通社/ -- 亞馬遜云科技宣布由英偉達H200 GPU提供支持的 Amazon Elastic Compute Cloud P5e(Amazon EC2 P5e)實例
    的頭像 發(fā)表于 09-19 16:16 ?720次閱讀

    STM32CUBEMX(2)--USART通過DMA方式接收不定長數(shù)據(jù)

    的不同,有一個或兩個DMA模塊。 STM32F0XX DMA控制器總共有5個通道用于DMA1,每個通道都專門管理來自一個或多個外設(shè)的存儲器訪問請求。它具有一個仲裁器,用于處理不同的
    發(fā)表于 09-04 11:48

    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)深度剖析

    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)深度剖析
    的頭像 發(fā)表于 08-13 10:56 ?862次閱讀
    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)<b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    探索巔峰性能 | 迅為RK3588開發(fā)板深度剖析

    探索巔峰性能 | 迅為RK3588開發(fā)板深度剖析
    的頭像 發(fā)表于 08-12 14:07 ?1378次閱讀
    探索巔峰性能 | 迅為RK3588開發(fā)板<b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    表面貼裝低相位噪音晶體振蕩器 DSO531SHH 深度剖析

    表面貼裝低相位噪音晶體振蕩器 DSO531SHH 深度剖析
    的頭像 發(fā)表于 07-26 14:12 ?597次閱讀
    表面貼裝低相位噪音晶體振蕩器 DSO531SHH <b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    新手小白怎么學GPU云服務器跑深度學習?

    新手小白想用GPU云服務器跑深度學習應該怎么做? 用個人主機通常pytorch可以跑但是LexNet,AlexNet可能就直接就跑不動,如何實現(xiàn)更經(jīng)濟便捷的實現(xiàn)GPU云服務器深度學習?
    發(fā)表于 06-11 17:09
    主站蜘蛛池模板: 天天色天天射综合网 | 成人中文在线 | 国产精品一区二区综合 | 九九99久久精品影视 | 女人精aaaa片一级毛片女女 | 特级毛片女人18毛片 | 六月丁香婷婷激情国产 | 色婷婷综合激情视频免费看 | 久久99久久精品97久久综合 | 黄色视奸 | 5151四虎永久在线精品免费 | 91国内在线视频 | 97色爱| 免费免播放器在线视频观看 | 3344在线| 亚洲 欧美 丝袜 制服 在线 | 人人做人人爽国产视 | 2021天天躁狠狠燥 | 欧美亚洲h在线一区二区 | 电影天堂在线观看三级 | 特级毛片女人18毛片 | 久久青草精品一区二区三区 | 国产一级特黄在线播放 | 国产精品乳摇在线播放 | 淫欲高三| 国模私拍在线 | 天天操天天干天天爱 | 久久香蕉综合色一综合色88 | 成人爽a毛片在线视频网站 成人窝窝午夜看片 | 国产精品护士 | avtom影院永久转四虎入口 | 黄网站色视频大全免费观看 | 天天影视亚洲 | 一区二区三区四区在线视频 | 五月婷婷中文字幕 | 在线观看一区二区三区四区 | 婷婷亚洲五月琪琪综合 | 久久久午夜精品理论片 | 黑人一区二区三区中文字幕 | 色吧在线观看 | 五月婷婷丁香花 |