1. 程式人生 > 其它 >【第3版emWin教程】第25章 emWin6.x的JPEG圖片顯示(硬體解碼)

【第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百科上面的介紹:

推薦初學者瞭解一下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硬解方式)

實驗目的:

  1. 學習emWin的JPEG圖片顯示。
  2. 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圖片顯示(裸機硬解方式)

實驗目的:

  1. 學習emWin的JPEG圖片顯示。
  2. 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也是很方便的。