【第3版emWin教程】第25章 emWin6.x的JPEG图片显示(硬件解码)

2021-07-08 15:49:43 浏览数 (1)

教程不断更新中: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绘制重定向。

代码语言:javascript复制
/* 重定向JPEG绘制采用硬件JPEG */
    GUI_JPEG_SetpfDrawEx(JPEG_X_Draw);

25.3.2 硬件JPEG底层实现

底层实现放在了JPEGConf.c文件里面,代码如下:

代码语言:javascript复制
    /* 重定向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文件开头的宏定义配置即可:

代码语言:javascript复制
/*
*********************************************************************************************************
*                                           宏定义
*********************************************************************************************************
*/
#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绘制的参考代码:

代码语言:javascript复制
/*
*********************************************************************************************************
*    函 数 名: _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("速度 = %dmsrn", 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文件中的配置如下:

代码语言:javascript复制
#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文件中的配置如下:

代码语言:javascript复制
#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也是很方便的。

0 人点赞