【第3版emWin教程】第25章 emWin6.x的JPEG圖片顯示(硬體解碼)
教程不斷更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第25章 emWin6.x的JPEG圖片顯示(硬體解碼)
本期主要講emWin支援的JPEG硬體解碼方式,相比於軟體解碼,硬體解碼要快很多。
25.1 初學者重要提示
25.2 JPEG圖片基礎知識
25.3 JPEG圖片的API函式及其顯示方法
25.4 實驗例程說明(RTOS)
25.5 實驗例程說明(裸機)
25.6 總結
25.1 初學者重要提示
1、 藉助STM32H7支援的硬體JPEG解碼,emWin底層使用硬體JPEG, 實現更簡單, 裸機800*480大小的JPEG圖片顯示需要20ms左右,加上emWin後多了一層顯示機制,現在需要30ms左右。簡單的圖片25ms左右就行。
2、 STM32H7的硬體JPEG講解在V7板子BSP驅動教程的第57和58章:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 。
3、 JPEG圖片顯示的所有API函式在emWin手冊中都有講解,下圖是中文版手冊裡面API函式的位置
下圖是英文版手冊裡面API函式的位置:
4、 本章教程使用的外部儲存器是SD卡,實際專案中使用任何其它型別的儲存器都可以的,支不支援檔案系統都沒有關係的,使用方法與本章教程一樣,使用者要做的就是把圖片從外部儲存器讀出即可。
25.2 JPEG圖片基礎知識
關於JPEG圖片格式方面的知識,推薦大家看wiki百科上面的介紹:
- https://en.wikipedia.org/wiki/JPEG 講解非常詳細。
- 如果覺得英文版讀起來比較吃力些,可以看wiki中文版,只是資料沒有英文的詳細:https://zh.wikipedia.org/wiki/JPEG 。
- 更多JPEG檔案的知識可以google或者百度進行了解。
推薦初學者瞭解一下JPEG檔案的格式,如果沒有了解也是沒有任何關係的,直接呼叫emWin的API函式就可以顯示JPEG圖片了。
----------------------------------------------------------------------------------------------------------
下面這點小知識還是要知道的:
JPEG 是Joint Photographic Experts Group(聯合影象專家小組)的縮寫,是第一個國際影象壓縮標準。JPEG影象壓縮演算法能夠在提供良好的壓縮效能的同時,具有比較好的重建質量,被廣泛應用於影象、視訊處理領域。由於JPEG優良的品質,使其在短短几年內獲得了成功,被廣泛應用於網際網路和數碼相機領域,網站上80%的影象都採用了JPEG壓縮標準。
這裡有一點要特別的注意:出於法律原因,不得分發JPEG編碼的程式碼。JPEG編碼似乎歸屬於IBM、AT&T和Mitsubishi所有的專利。因此,從法律上講,如未獲得一個或多個許可,則不能使用JPEG編碼。因此,emWin的API函式僅支援解碼,不支援編碼。
25.3 JPEG圖片的API函式及其顯示方法
當前emWin支援的API函式有如下6個:
從上面的表格中可以看出,emWin支援JPEG檔案顯示主要有兩種型別的函式,一類是以Ex結尾的函式,這種函式顯示JPEG圖片是一邊從外部儲存器載入資料一邊顯示,顯示速度相對較慢,適用於記憶體較小的場合。另一類是不以Ex結尾的函式,這種函式直接從指定的地址讀取資料進行顯示(注意,這裡的地址需是匯流排式地址,比如外部SDRAM,外部SRAM,內部Flash和內部SRAM都可以),顯示速度相對較快。
本章教程會對這兩種方式都進行說明:
- int GUI_JPEG_Draw(const void * pFileData, int DataSize, int x0, int y0);
此函式直接從地址pFileData讀取JPEG檔案資料,將圖片顯示到使用者設定的位置(x0, y0)。
- int GUI_JPEG_DrawEx(GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0);
此函式通過其回撥函式pfGetData實現邊讀取圖片資料邊顯示的功能,將圖片顯示到使用者設定的位置(x0, y0)。
另外還有一個知識點需要初學者瞭解,emWin解碼一張JPEG圖片需要多少RAM?這主要有兩部分組成,JPEG解碼本身需要大約33KB的RAM,外加圖片的不同長度對RAM需求的影響,具體公式如下:
大約RAM大小 = 影象的X大小* 80位元組 + 33KB。
不同長度的JPEG圖片的RAM需求取決於JPEG圖片壓縮型別,比如下面三種壓縮型別:
JPEG圖片解碼所需的記憶體由emWin動態分配。繪製JPEG影象後,將釋放整個RAM。這裡舉一個例子:比如要顯示800*480的JPEG圖片大約需要 800*80 位元組+ 33KB ,即97KB的記憶體。
25.3.1 硬體JPEG介面函式重定向
通過函式GUI_JPEG_SetpfDrawEx可以實現emWin的JPEG繪製重定向。
/* 重定向JPEG繪製採用硬體JPEG */ GUI_JPEG_SetpfDrawEx(JPEG_X_Draw);
25.3.2 硬體JPEG底層實現
底層實現放在了JPEGConf.c檔案裡面,程式碼如下:
/* 重定向JPEG繪製採用硬體JPEG */ GUI_JPEG_SetpfDrawEx(JPEG_X_Draw); /* ********************************************************************************************************* * 函 數 名: JPEG_X_Draw * 功能說明: 硬體JPEG繪製 * 形 參: --- * 返 回 值: 繪製是否成功 ********************************************************************************************************* */ int JPEG_X_Draw(GUI_GET_DATA_FUNC * pfGetData, void * p, int x0, int y0) { U8 *ppData; GUI_LOCK(); _Context.xPos = x0; _Context.yPos = y0; _Context.pfGetData = pfGetData; _Context.pVoid = p; _Context.Error = 0; /* 初始化硬體JPEG,並申請空間 */ if (_IsInitialized == 0) { _IsInitialized = 1; JPEG_Handle.Instance = JPEG; HAL_JPEG_Init(&JPEG_Handle); #if AutoMalloc == 0 /* 申請一塊記憶體空間,用於載入JPEG圖片 */ _Context.hWorkBuffer = GUI_ALLOC_AllocNoInit(LoadPicSize); _Context.pWorkBuffer = GUI_ALLOC_h2p(_Context.hWorkBuffer); /* 申請一塊記憶體空間,用於存放解碼完成的資料 */ _Context.hOutBuffer = GUI_ALLOC_AllocNoInit(DrawPicSize); _Context.pOutBuffer = GUI_ALLOC_h2p(_Context.hOutBuffer); #endif } #if AutoMalloc == 1 /* 申請一塊記憶體空間,用於載入JPEG圖片 */ _Context.hWorkBuffer = GUI_ALLOC_AllocNoInit(LoadPicSize); _Context.pWorkBuffer = GUI_ALLOC_h2p(_Context.hWorkBuffer); /* 申請一塊記憶體空間,用於存放解碼完成的資料 */ _Context.hOutBuffer = GUI_ALLOC_AllocNoInit(DrawPicSize); _Context.pOutBuffer = GUI_ALLOC_h2p(_Context.hOutBuffer); #endif /* 讀取JPEG資料,並解碼 */ _Context.NumBytesInBuffer = _Context.pfGetData(_Context.pVoid, (const U8 **)&ppData, LoadPicSize, 0); JPEG_Decode_DMA(&JPEG_Handle, (uint32_t)ppData, _Context.NumBytesInBuffer, (uint32_t)_Context.pWorkBuffer); /* 解碼完成 */ while(Jpeg_HWDecodingEnd == 0) { } /* 獲取JPEG圖片格式資訊後,做顏色格式轉換 */ HAL_JPEG_GetInfo(&JPEG_Handle, &JPEG_Info); DMA2D_Copy_YCbCr_To_RGB((uint32_t *)_Context.pWorkBuffer, (uint32_t *)_Context.pOutBuffer , 0, 0, JPEG_Info.ImageWidth, JPEG_Info.ImageHeight, PicPixelFormat, JPEG_Info.ChromaSubsampling); /* 繪製JPEG圖片 */ _DrawBitmap(_Context.xPos, _Context.yPos, (void const *)_Context.pOutBuffer , JPEG_Info.ImageWidth, JPEG_Info.ImageHeight, JPEG_Info.ImageWidth*2, 16); #if AutoMalloc == 1 /* 釋放動態記憶體hMem */ GUI_ALLOC_Free(_Context.hWorkBuffer); GUI_ALLOC_Free(_Context.hOutBuffer ); #endif GUI_UNLOCK(); return _Context.Error; }
程式碼中關於硬體JPEG的實現,在V7的BSP驅動手冊第57和58章有詳細說明。大家使用的時候,注意JPEGConf.c檔案開頭的巨集定義配置即可:
/* ********************************************************************************************************* * 巨集定義 ********************************************************************************************************* */ #define AutoMalloc 0 /* 0 申請後不釋放, 1 使用完畢後釋放 */ #define LoadPicSize 1024*600*4 /* 最大支援的載入的圖片大小 */ #define DrawPicSize 1024*600*4 /* 圖片解碼出來後,可以使用的緩衝大小 */ #define PicPixelFormat LTDC_PIXEL_FORMAT_RGB565 /* 當前顯示屏使用的顏色格式 */
25.3.3 硬體JPEG繪製
硬體JPEG底層重定向後,大家使用函式GUI_JPEG_Draw就可以繪製,下面是從SD卡載入JPEG後,採用硬體JPEG繪製的參考程式碼:
/* ********************************************************************************************************* * 函 數 名: _ShowJPEG2 * 功能說明: 顯示JPEG圖片 * 形 參: sFilename 要讀取的檔名 * x 要顯示的x軸座標位置 * y 要顯示的y軸座標位置 * 返 回 值: 返回繪製了JPEG圖片的記憶體裝置控制代碼。 ********************************************************************************************************* */ void _ShowJPEG2(const char *sFilename, int x, int y) { char *_acBuffer; GUI_HMEM hMem; uint32_t t0, t1, i, count = 0; char buf[50]; /* 開啟檔案 */ result = f_open(&file, sFilename, FA_OPEN_EXISTING | FA_READ | FA_OPEN_ALWAYS); if (result != FR_OK) { return; } /* 申請一塊記憶體空間 並且將其清零 */ hMem = GUI_ALLOC_AllocZero(file.obj.objsize); /* 將申請到記憶體的控制代碼轉換成指標型別 */ _acBuffer = GUI_ALLOC_h2p(hMem); /* 讀取檔案到動態記憶體 */ result = f_read(&file, _acBuffer, file.obj.objsize, &bw); if (result != FR_OK) { return; } /*重新整理20次,串列埠列印速度數值,時間單位ms */ for(i = 0; i < 20; i++) { t0 = GUI_GetTime(); GUI_JPEG_Draw(_acBuffer, file.obj.objsize, x, y); t1 = GUI_GetTime() - t0; printf("速度 = %dms\r\n", t1); count += t1; } /* 求出重新整理20次的平均速度 */ sprintf(buf, "speed = %dms/frame", count/i); GUI_DispStringAt(buf, 10, 10); /* 釋放動態記憶體hMem */ GUI_ALLOC_Free(hMem); /* 關閉檔案 */ f_close(&file); }
25.4 實驗例程說明(RTOS)
配套例子:
V7-530_emWin6.x實驗_JPEG圖片顯示(RTOS硬解方式)
實驗目的:
- 學習emWin的JPEG圖片顯示。
- emWin功能的實現在MainTask.c檔案裡面。
實驗內容:
1、K1按鍵按下,串列埠或者RTT列印任務執行情況(串列埠波特率115200,資料位8,奇偶校驗位無,停止位1)。
2、(1) 凡是用到printf函式的全部通過函式App_Printf實現。
(2) App_Printf函式做了訊號量的互斥操作,解決資源共享問題。
3、預設上電是通過串列埠列印資訊,如果使用RTT列印資訊:
MDK AC5,MDK AC6或IAR通過使能bsp.h檔案中的巨集定義為1即可
#define Enable_RTTViewer 1
4、各個任務實現的功能如下:
App Task Start 任務 :啟動任務,這裡用作BSP驅動包處理。
App Task MspPro任務 :訊息處理,這裡用作LED閃爍。
App Task UserIF 任務 :按鍵訊息處理。
App Task COM 任務 :暫未使用。
App Task GUI 任務 :GUI任務。
μCOS-III任務除錯資訊(按K1按鍵,串列埠列印):
RTT 列印資訊方式:
程式設計:
任務棧大小分配:
μCOS-III任務棧大小在app_cfg.h檔案中配置:
#define APP_CFG_TASK_START_STK_SIZE 512u
#define APP_CFG_TASK_MsgPro_STK_SIZE 2048u
#define APP_CFG_TASK_COM_STK_SIZE 512u
#define APP_CFG_TASK_USER_IF_STK_SIZE 512u
#define APP_CFG_TASK_GUI_STK_SIZE 2048u
任務棧大小的單位是4位元組,那麼每個任務的棧大小如下:
App Task Start 任務 :2048位元組。
App Task MspPro任務 :8192位元組。
App Task UserIF 任務 :2048位元組。
App Task COM 任務 :2048位元組。
App Task GUI 任務 :8192位元組。
系統棧大小分配:
μCOS-III的系統棧大小在os_cfg_app.h檔案中配置:
#define OS_CFG_ISR_STK_SIZE 512u
系統棧大小的單位是4位元組,那麼這裡就是配置系統棧大小為2KB
emWin動態記憶體配置:
GUIConf.c檔案中的配置如下:
#define EX_SRAM 1/*1 used extern sram, 0 used internal sram */ #if EX_SRAM #define GUI_NUMBYTES (1024*1024*24) #else #define GUI_NUMBYTES (100*1024) #endif
通過巨集定義來配置使用內部SRAM還是外部的SDRAM做為emWin的動態記憶體,當配置:
#define EX_SRAM 1 表示使用外部SDRAM作為emWin動態記憶體,大小24MB。
#define EX_SRAM 0 表示使用內部SRAM作為emWin動態記憶體,大小100KB。
預設情況下,本教程配套的所有emWin例子都是用外部SDRAM作為emWin動態記憶體。
emWin介面顯示效果:
800*480解析度介面效果。
25.5 實驗例程說明(裸機)
配套例子:
V7-529_emWin6.x實驗_JPEG圖片顯示(裸機硬解方式)
實驗目的:
- 學習emWin的JPEG圖片顯示。
- emWin功能的實現在MainTask.c檔案裡面。
emWin介面顯示效果:
800*480解析度介面效果。
emWin動態記憶體配置:
GUIConf.c檔案中的配置如下:
#define EX_SRAM 1/*1 used extern sram, 0 used internal sram */ #if EX_SRAM #define GUI_NUMBYTES (1024*1024*24) #else #define GUI_NUMBYTES (100*1024) #endif
通過巨集定義來配置使用內部SRAM還是外部的SDRAM做為emWin的動態記憶體,當配置:
#define EX_SRAM 1 表示使用外部SDRAM作為emWin動態記憶體,大小24MB。
#define EX_SRAM 0 表示使用內部SRAM作為emWin動態記憶體,大小100KB。
預設情況下,本教程配套的所有emWin例子都是用外部SDRAM作為emWin動態記憶體。
25.6 總結
總的來說,H7+32位SDRAM繪製JPEG圖片的效能已經比較給力,實際專案中推薦將JPEG圖片載入到emWin動態記憶體,然後繪製到記憶體裝置中,再通過記憶體裝置函式顯示此JPEG圖片的速度非常快,推薦專案中使用。
另外,由於JPEG圖片比較小,且V7板子使用的STM32H743XI有2MB的內部flash,所以使用Bin2C.exe軟體將JPEG圖片轉換成C檔案新增到MDK或者IAR工程裡面再下載到內部flash也是很方便的。