首页 > 其他分享 >《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验

《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验

时间:2022-10-12 10:38:00浏览次数:44  
标签:MiniPRO FIFO MDMA 解码 JPEG jpeg H750 hjpgd

第四十九章硬件JPEG解码实验​


上一章,我们学习了图片解码,学会了使用软件解码显示bmp/jpg/jpeg/gif等格式的图片,但是软件解码速度都比较慢,本章我们将学习如何使用STM32H750自带的硬件JPEG编解码器,实现对JPG/JPEG图片的硬解码,从而大大提高解码速度。​

本章分为如下几个小节:​

49.1 硬件JPEG编解码器简介 ​

49.2 硬件设计​

49.3 程序设计​

49.4 下载验证​



49.1 硬件JPEG编解码器简介

STM32H750自带了硬件JPEG编解码器,可以实现快速JPG/JPEG编解码,本章我们仅使用JPG/JPEG解码器。STM32H7的JPEG编解码器具有如下特点: ​

  • 支持JPEG编码/解码​
  • 支持RGB、YCbCr、YCMK与BW(灰度)色彩空间​
  • 单周期解码/编码一个像素​
  • 支持JPEG头数据编解码​
  • 多达4个可编程量化表​
  • 单周期哈夫曼表编解码​
  • 完全可编程的哈弗曼表(AC和DC各2个)​
  • 完全可编程的最小编码单元(MCU) ​
  • 单周期哈弗曼编码/解码

49.1.1 JPEG编解码器框图

STM32H7的JPEG编解码器框图如图49.1.1.1所示:​

图49.1.1.1硬件JPEG编解码器框图​

图中,硬件JPEG编解码器的输入和输出数据流都是通过32位AHB总线访问,其它的JPEG内部信号如下表所示:​

信号名称​

信号类型​

说明​

jpeg_hclk​

数字输入​

JPEG 内核和寄存器接口时钟​

jpeg_it​

数字输出​

JPEG 全局中断​

jpeg_ift_trg​

数字输出​

用于 MDMA 的 JPEG 输入 FIFO 阈值信号​

jpeg_ifnf_trg​

数字输出​

用于 MDMA 的 JPEG 输入 FIFO 未满信号​

jpeg_oft_trg​

数字输出​

用于 MDMA 的 JPEG 输出 FIFO 阈值信号​

jpeg_ofne_trg​

数字输出​

用于 MDMA 的 JPEG 输出 FIFO 非空信号​

jpeg_oec_trg​

数字输出​

用于 MDMA 的 JPEG 转换结束信号​

表49.1.1.1 JPEG内部信号​

49.1.2 JPEG解码器功能

我们只需要对JPEG编解码器的相关寄存器进行设置,然后读写输入/输出FIFO,即可完成JPEG的编解码。本章,我们只介绍如何利用STM32H7的硬件JPEG解码器实现对JPG/JPEG图片的解码。​

硬件JPEG解码器,支持解码符合ISO/IEC10918-1协议规范的JPEG数据流,并且支持解码JPEG头(可配置),通过输入FIFO读取需要解码的JPEG数据,通过输出FIFO将解码完成的YUV数据传输给外部。​

注意硬件JPEG解码器解码完成后是YUV格式的数据,并不是RGB格式的数据,所以不能直接显示到LCD上面,必须经过YUVàRGB的转换,才可以显示在LCD上面。​

硬件JPEG解码时FIFO数据的处理(读取/写入)有两种方式:​

  1. 中断方式。​
  2. DMA方式(JPEG编解码内核使用的是MDMA,下同!)。​

为了达到最快的解码速度,我们一般使用DMA来处理FIFO数据。接下来,我们介绍一下硬件JPEG解码的数据处理过程。​

1、输入FIFO MDMA

通过jpeg_ift_trg信号(对应MDMA数据流17,见《STM32H7xx参考手册_V7(英文版).pdf》第597页,Table 100),可以触发JPEG输入FIFO的MDMA请求,当输入FIFO(总容量为64字节)至少半空的时候,将产生一个MDMA请求,读取32字节数据到输入FIFO。当MDMA不再继续往JPEG输入FIFO传送数据时,JPEG解码进程将自动暂停,因此,我们只需要控制MDMA的启停,就可以控制JPEG的解码进程,这个操作在MDMA传输完成,读取下一批JPEG数据的时候经常用到。​

注意:在当前图片解码完成后,开启下一张图片解码之前,需要先停止MDMA,然后对输入FIFO进行一次清空(设置JPEG_CR寄存器的IFF位),否则上一张图片的数据会影响到下一张图片的解码。​

2、输出FIFO MDMA

通过jpeg_oft_trg信号(对应MDMA数据流19,见《STM32H7xx参考手册_V7(英文 版).pdf》第597页,Table 100),可以触发JPEG输出FIFO的MDMA,当输出FIFO(总容量为64字节)至少半满的时候,将产生一个MDMA请求,可以从输出FIFO读取32字节数据。当MDMA不再读取JPEG输出FIFO的数据时,JPEG输出FIFO被暂停,这个操作在MDMA传输完成,执行YUVàRGB转换的时候经常用到。​

注意:当图片解码结束以后,输出FIFO里面可能还有数据,此时我们需要手动读取FIFO里面的数据,直到JPEG_SR寄存器的OFNEF位为0。​

3、JPEG头解码

通过设置JPEG_CONFR1寄存器的HDR位为1,可以使能JPEG头解码,通过设置JPEG_CR寄存器HPDIE位为1,可以使能JPEG头解码完成中断。在完成JPEG头解码之后,我们可以获取当前JPEG图片的很多参数,包括:颜色空间、色度抽样、高度、宽度和MCU总数等信息。这些参数对我们后面的解码和颜色转换(YUVàRGB)非常重要。​

硬件JPEG使用DMA实现JPG/JPEG图片解码的数据处理流程如图49.1.2.1所示:​

图49.1.2.1硬件JPEG解码数据处理流程(DMA方式)​

由图可知,数据处理主要由2个MDMA完成:输入MDMA和输出MDMA,分别处理硬件JPEG的输入FIFO和输出FIFO的数据。通过适当控制输入MDMA/输出MDMA的关闭和重启,从而控制整个数据处理的进程,关闭MDMA的时间越少,解码速度就越快。​

图中我们还用到了2个JPEG中断:JPEG头解析完成中断和JPEG解码完成中断,他们共用一个中断服务函数。JPEG头解析完成中断,在JPEG头解码完成后进入,此时我们可以获取JPG/JPEG图片的很多重要信息,方便后续解码。JPEG解码完成中断,在JPG/JPEG图片解码完成后进入,标志着整张图片解码完成。​

49.1.3 MDMA简介

MDMA属于STM32H7内部4个DMA控制器之一,它的主要特点有:​

  • 支持3种传输方式:内存à内存、内存à外设、外设à内存​
  • 支持多达16个通道​
  • 支持多达32个硬件触发源,且支持软件触发​
  • 所有通道都可以独立设置并连接到DMA1/DMA2或相关外设​
  • 一个256级深度buffer,被分成2个独立的128级buffer(先入/先出,即FIFO)​
  • 4个优先级可设置:非常高、高、中、低​
  • 源和目标的传输位宽可以独立设置(字节、半字、字、双字)​
  • 源地址/目标地址自增和大小都可以独立设置​

我们重点看一下MDMA和DMA1/2的差异(DMA1/DMA2的详细介绍请参考第三十章DMA实验),MDMA支持4种触发模式(通过TRGM[1:0]位设置),具体如下:​

1、缓冲传输模式(最多一次可以传输128字节)​

2、块传输模式(最多一次可以传输64K字节)​

3、重复块传输模式(每次传输后,可配置改变源/目标的起始地址)​

4、链表模式(直到各个信道数据传输完毕,比较复杂,一般不用)​

而DMA1/DMA2只支持单次传输和突发传输,且突发传输仅支持4、8和16等个传输长度,本实验我们用MDMA的缓冲传输模式,因为JPEG的输入/输出FIFO每次都是读取/输出32字节,因此我们用缓冲模式(TRGM[1:0]=00),并设置单次触发传输长度为32字节(TLEN[6:0]=32-1),就可以很好的和JPEG的输入/输出FIFO匹配了。​

关于MDMA的具体配置,我们在这里就不详细介绍了,MDMA的配置和DMA1/DMA2基本类似,不过稍微复杂一点。关于MDMA的详细介绍,请参考《STM32H7xx参考手册_V7(英文版).pdf》第14章。​

49.1.4 YUV转RGB操作​

在STM32F7系列MCU上面,要实现YUV到RGB的转换,必须使用纯软件的方式来实现,比较耗时间。但是到了H7系列MCU,ST添加了硬件YUV到RGB转换的功能,我们通过DMA2D可以很方便的实现YUV到RGB的硬件转换。

H7的DMA2D支持将:YCbCr 4:4:4(YUV444),4:2:2(YUV422)和4:2:0(YUV420)等三种抽样格式的颜色数据转换成RGB数据格式。DMA2D执行YCbCr数据格式转换的时候是以8*8的最小编码单元(MCU)为基础的,YCbCr数据在MCU内的排列顺序如下表所示:

《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_数据


图49.1.4.1 MCU中不同抽样方式下YCbCr的排列方式

由上表可知:

YCbCr4:4:4(YUV444)抽样,每个RGB像素占3个YCbCr字节

YCbCr4:2:2(YUV422)抽样,每个RGB像素占2个YCbCr字节

YCbCr4:2:0(YUV420)抽样,每个RGB像素占1.5个YCbCr字节

为了保障DMA2D能够正确的执行YCbCr到RGB的转换,当图片采用:YCbCr 4:4:4的抽样方式时,图片宽度(DMA2D_NLR寄存器的PL[13:0]定义)+行偏移(DMA2D_FGOR寄存器的LO[15:0]定义)必须是8的倍数。当图片采用:YCbCr 4:2:2或YCbCr 4:2:0的抽样方式时,图片宽度(DMA2D_NLR寄存器的PL[13:0]定义)+行偏移(DMA2D_FGOR寄存器的LO[15:0]定义)必须是16的倍数。为了简化处理和节省内存,我们采用单幅图像,多次解码的方式来完成解码,因此规定:对于硬件JPEG解码的图片,其宽度必须是16的倍数

DMA2D执行内存到内存的YCbCràRGB转换的简要设置(行偏移为0)如下:

  1. 设置DMA2D_CR寄存器的MODE[2:0]=001,DMA2D工作在带PFC的存储器到存储器模式。
  2. 设置DMA2D_OPFCCR的CM[2:0]=010,设置PFC输出图像格式为:RGB565。
  3. 设置DMA2D_FGPFCCR的CSS[1:0]为正确的抽样方式(YCbCr 4:4:4/4:2:2/4:2:0,根据实际情况设置),并设置CM[3:0]=1011,设置输入图像格式为:YCbCr。
  4. 设置DMA2D_NLR的PL[13:0]为图像宽度(必须为16的倍数),NL[15:0]为单次转换输出的图像高度(YCbCr 4:4:4和YCbCr 4:2:2每次输出8的倍数行,YCbCr 4:2:0每次输出16的倍数行)。
  5. 设置DMA2D_OMAR的MA[31:0]为输出图像数据的首地址。该地址需根据单次输出的图像高度和宽度,进行变化,以完成整幅图像的输出。
  6. 设置DMA2D_FGMAR的MA[31:0]为输入图像数据的首地址。另外,单次输入图像数据的大小,是有要求的。输入图像是YCbCr数据格式,根据前面的介绍,不同抽样率下,最小输入图像数据的大小为:单次输出行数*YCbCr每个像素所占字节数*图像宽度
    对于YCbCr 4:4:4,最小输入图像数据大小为:8*3*图像宽度
    对于YCbCr 4:2:2,最小输入图像数据大小为:8*2*图像宽度
    对于YCbCr 4:2:0,最小输入图像数据大小为:16*1.5*图像宽度
  7. 最后,设置DMA2D_CR寄存器的START位为1,使能DMA2D传输。然后等待传输完成就可以完成一次YCbCràRGB的转换。

以上,就是利用DMA2D进行YCbCr到RGB图像数据转换的简要设置,DMA2D的相关寄存器我们这里就不做介绍,详见《STM32H7xx参考手册_V7(英文版).pdf》第18章相关章节。​

49.1.5 JPEG编解码器寄存器​

下面介绍本实验需要用到的JPEG 编解码器寄存器。​

  • JPEG编解码器控制寄存器0(JPEG_CONFR0该寄存器仅最低位(START位)有效,设置该位为1,可以启动JPEG解码流程。通过设置该位为0,可以退出当前JPEG解码。​
  • JPEG编解码器配置寄存器1(JPEG_CONFR1JPEG编解码器配置寄存器1描述如图49.1.5.1所示:​
  • 《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_数据_02


  • 图49.1.5.1 JPEG_CONFR1寄存器​
    YSIZE[15:0]位,定义JPEG图片的高度,读取该寄存器可以获得图片高度(注意:需要在JPEG头解析成功以后,才可以读取该寄存器获取图片高度,下同)。​
    HDR位,用于设置是否使能JPEG头解码,我们一般设置为1,使能JPEG头解码。​
    DE位,用于设置硬件JPEG工作模式,我们设置为1,表示使用JPEG解码模式。​
    NF[1:0]位,这两个位用于定义色彩组成:00,表示灰度图片;01,未用到;10,表示YUV/RGB;11表示CYMK。
  • JPEG编解码器配置寄存器3JPEG_CONFR3JPEG编解码器配置寄存器3描述如图49.1.5.2所示:​
  • 《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_数据_03


  • 图49.1.5.2 JPEG_CONFR3寄存器​
    该寄存器仅高16位(YSIZE[15:0])有效,定义JPEG图片的宽度,读取该寄存器可以获得图片宽度。​
    另外,还有JPEG配置寄存器4~7:JPEG_CONFR4~7,这四个寄存器ST官方数据手册对其解释也不是很清楚,但是我们可以参考ST官方提供的参考代码,知道这四个寄存器的NB[3:0]位用来表示YUV的抽样方式(YUV422、YUV420、YUV444),详见本例程源码。​
  • JPEG控制寄存器(JPEG_CRJPEG控制寄存器描述如图49.1.5.3所示:​
  • 《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_数据_04


  • 图49.1.5.3 JPEG_CR寄存器​
    OFF位,用于清空输出FIFO,在启动新图片解码之前,需要对输出FIFO进行清空。​
    IFF位,用于清空输入FIFO,在启动新图片解码之前,需要对输入FIFO进行清空。​
    HPDIE位,用于使能JPEG头解码完成中断,我们设置为1,使能JPEG头解码完成中断,在中断服务函数里面读取JPEG的相关信息(长宽、颜色空间、色度抽样等),并根据色度抽样方式,获取对应的YUVàRGB转换函数。​
    EOCIE位,用于使能JPEG解码完成中断,我们设置为1,使能JPEG解码完成中断,在中断服务函数里面标记JPEG解码完成,以便结束JPEG解码流程。​
    JCEN位,用于使能硬件JPEG内核,我们必须设置此位为1,以启动硬件JPEG内核。​
  • JPEG状态寄存器(JPEG_SRJPEG状态寄存器描述如图49.1.5.4所示:​
  • 《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_2d_05


  • 图49.1.5.4 JPEG_SR寄存器​
    HPDF位,表示JPEG头解码完成的标志,当该位为1时,表示JPEG头解析成功,我们可以读取相关寄存器,获取JPEG图片的长宽、颜色空间和色度抽样等重要信息。向JPEG_FCR寄存器的CHPDF位写1,可以清零此位。​
    EOCF位,表示JPEG解码结束标志,该位为1时,表示一张JPEG图像解码完成。此时我们可以从输出FIFO读取最后的数据。向JPEG_FCR寄存器的CEOCF位写1,可以清零此位。​
  • JPEG清零标志寄存器(JPEG_CFR

JPEG清零标志寄存器描述如图49.1.5.5所示:​

《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_2d_06


图49.1.5.5 JPEG_CFR寄存器​

该寄存器,仅两位有效:CHPDF位和CEOCF位,向这两个位写入1,可以分别清除JPEG_SR寄存器的HPDF和EOCF位。​

最后是JPEG数据输入寄存器(JPEG_DIR)和JPEG数据输出寄存器(JPEG_DOR),这两个寄存器都是32位有效,前者用于往输入FIFO写入数据。后者用于读取输出FIFO的数据。​

49.2 硬件设计​

1. 例程功能​

1、本实验开机的时候先检测字库,然后检测SD卡是否存在,如果SD卡存在,则开始查找SD卡根目录下的PICTURE文件夹,如果找到则显示该文件夹下面的图片文件(支持bmp、jpg、jpeg或gif格式),循环显示,通过按KEY0和KEY1可以快速浏览下一张和上一张,KEY_UP按键用于暂停/继续播放,LED1用于指示当前是否处于暂停状态。如果未找到PICTURE文件夹/任何图片文件,则提示错误。​

注意:本例程的实验现象,同上一章(图片显示实验)完全一模一样,唯一的区别,就是JPEG解码速度 (要求图片分辨率小于等于LCD分辨率) 变快了很多。​

对比上一章的图片显示,大家可以利用USMART测试同一张JPEG图片,软件解码和硬件解码的时间差距。本实验也可以通过USMART调用ai_load_picfile和minibmp_decode解码任意指定路径的图片。​

2、LED0闪烁,提示程序运行。​

2. 硬件资源​

1)RGB灯​

RED :LED0 - PB4 ​

GREEN :LED1 - PE6​

2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)​

3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)​

4)独立按键 :KEY0 - PA1、KEY1 - PA15、WK_UP - PA0​

5)SD卡,通过SDMMC1(SDMMC_D0~D4(PC8~PC11),​

SDMMC_SCK(PC12),SDMMC_CMD(PD2))连接​

6)norflash(QSPI FLASH芯片,连接在QSPI上)​

7)硬件JPEG解码内核(STM32H750自带)​

49.3 程序设计​

49.3.1 硬件JPEG解码JPG/JPEG的简要步骤​

为了提高速度,我们直接操作寄存器,没有使用HAL库提供的函数。​

所以HAL库的内容就讲解了,接下来,我们看看在DMA模式下,使用STM32H7的硬件JPEG解码JPG/JPEG的简要步骤: ​

1)初始化硬件JPEG内核。

首先,我们通过设置AHB3ENR的bit5位为1,使能硬件JPEG内核时钟,然后通过JPEG_CR寄存器的JCEN位,使能硬件JPEG。通过清零JPEG_CONFR0寄存器的START位,停止JPEG编解码进程。通过设置JPEG_CONFR1寄存器的HDR位,使能JPEG头解码。最后设置JPEG中断服务函数的中断优先级,完成初始化硬件JPEG内核过程。 ​

2)初始化硬件JPEG解码。

在初始化硬件JPEG内核以后,我们配置JPEG内核工作在JPEG解码模式。通过设置JPEG_CONFR1寄存器的DE位,使能JPEG解码模式。然后设置JPEG_CR寄存器的OFF、IFF、HPDIE、EOCIE等位,清空输出/输入FIFO,并开启JPEG头解码完成和JPEG解码完成中断。最后,设置JPEG_CONFR0寄存器的START位,启动JPEG解码进程。​

注意:此时我并未开启JPEG的输入和输出MDMA,只要我们不往输入FIFO写入数据,JPEG内核就一直处于等待数据输入状态。 ​

3)配置硬件JPEG输入/输出MDMA。

这一步,我们将配置JPEG的输入MDMA和输出MDMA,分别负责JPEG输入FIFO和输出FIFO的数据传输。对于输入MDMA,目标地址为JPEG_DIR寄存器地址,源地址为一片内存区域,利用输入MDMA实现JPEG输入FIFO数据的自动填充。对于输出MDMA,目标地址为一片内存区域,源地址为JPEG_DOR寄存器地址,利用输出MDMA实现JPEG输出FIFO数据自动搬运到对应内存区域。对于输入MDMA和输出MDMA,我们都需要开启传输完成中断,并设置相关中断服务函数。在传输完成中断里面,实现对输入输出数据的处理。 ​

4)编写相关中断服务函数,启动MDMA。

我们总共开启了4个中断:JPEG头解码完成中断、JPEG解码完成中断、输入MDMA传输完成中断和输出MDMA传输完成中断。前两个和后两个中断分别共用一个中断服务函数,所以我们总共只需要编写2个中断服务函数。另外,我们采用回调函数的方式,对数据进行处理,总共需要编写4个回调函数,分别对应4个中断产生时的数据处理。在配置完这些以后,启动MDMA,开始执行JPEG解码。​

注意:输出MDMA的配置和启动,我们放在JPEG头解码完成中断回调函数里的,因为输出YCbCr数据的多少和单次输出行数,得根据抽样方式进行不同的设置,因此我们必须先等到解析完JPEG头以后,再来配置输出MDMA。​

5)处理JPEG数据输出数据,执行YUVàRGB转换,并送LCD显示。

最后,在主循环里面,根据输入MDMA和输出MDMA的数据处理情况,持续从源文件读取JPEG数据流,并利用DMA2D,将硬件JPEG解码完成的YCbCr(YUV)数据流转换成RGB格式。最后,在完成一张JPEG解码之后,将RGB数据直接一次性显示到LCD屏幕上,实现图片显示。 ​

49.3.2 程序流程图​


《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_寄存器_07


图49.3.2.1 硬件JPEG解码实验程序流程图​

49.3.3 程序解析​

1. JPEGCODEC驱动代码​

这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。JPEGCODEC驱动源码包括两个文件:jpegcodec.c和jpegcodec.h。 ​

jpegcodec.h头文件定义了两个结构体和一些宏定义,下面重点围绕jpeg_codec_typedef这个结构体介绍一下,jpegcodec.h的相关内容定义如下:​

#define JPEG_DMA_INBUF_LEN 4096  /* 单个MDMA IN BUF的大小 */​
#define JPEG_DMA_INBUF_NB 10 /* MDMA IN BUF的个数 */​
#define JPEG_DMA_OUTBUF_NB 2 /* MDMA OUT BUF的个数 */​

/* JPEG数据缓冲结构体 */​
typedef struct​
{​
uint8_t sta; /* 状态:0,无数据;1,有数据 */​
uint8_t *buf; /* JPEG数据缓冲区 */​
uint16_t size; /* JPEG数据长度 */​
} jpeg_databuf_type;​

#define JPEG_STATE_NOHEADER 0 /* HEADER未读取,初始状态 */​
#define JPEG_STATE_HEADEROK 1 /* HEADER读取成功 */​
#define JPEG_STATE_FINISHED 2 /* 解码完成 */​
#define JPEG_STATE_ERROR 3 /* 解码错误 */​

#define JPEG_YCBCR_COLORSPACE JPEG_CONFR1_COLORSPACE_0​
#define JPEG_CMYK_COLORSPACE JPEG_CONFR1_COLORSPACE​

/* jpeg编解码控制结构体 */​
/typedef struct​
{​
JPEG_ConfTypeDef Conf; /* 当前JPEG文件相关参数 */​
jpeg_databuf_type inbuf[JPEG_DMA_INBUF_NB]; /* MDMA IN buf */​
jpeg_databuf_type outbuf[JPEG_DMA_OUTBUF_NB]; /* MDMA OUT buf */​
volatile uint8_t inbuf_read_ptr; /* MDMA IN buf当前读取位置 */​
volatile uint8_t inbuf_write_ptr; /* MDMA IN buf当前写入位置 */​
volatile uint8_t indma_pause; /* 输入MDMA暂停状态标识 */​
volatile uint8_t outbuf_read_ptr; /* MDMA OUT buf当前读取位置 */​
volatile uint8_t outbuf_write_ptr; /* MDMA OUT buf当前写入位置 */​
volatile uint8_t outdma_pause; /* 输入MDMA暂停状态标识 */​
volatile uint8_t state;/* 解码状态;0,未识别到Header;1,识别到了Header;2,解码完成*/​

/* YUV输出的字节数,使得完成一次DMA2D YUV2RGB转换,刚好是图片宽度的整数倍​
图片,每个像素占1.5个YUV字节,每次输出16行,yuvblk_size=图片宽度*16*1.5​
图片,每个像素占2个YUV字节和RGB565一样,每次输出8行,yuvblk_size=图片宽度*8*2​
图片,每个像素占3个YUV字节,每次输出8行,yuvblk_size=图片宽度*8*3 */​
uint32_t yuvblk_size; ​

/* 每个YUV块输出像素的高度,对于YUV420,为16,对于YUV422/YUV444为8 */​
uint16_t yuvblk_height;​
uint16_t yuvblk_curheight; /* 当前输出高度,0~分辨率高度 */​
} jpeg_codec_typedef;​
该结构体用于控制整个JPEG解码,下面分别介绍一下它的成员:​
Conf用于存储当前JPEG文件的一些相关信息,其结构体类型定义如下:​
/* JPEG文件信息结构体 */​
typedef struct​
{​
u8 ColorSpace; /* 图像的颜色空间: gray-scale/YCBCR/RGB/CMYK */​
/* YCBCR/CMYK颜色空间的色度抽样情况: 0:4:4:4; 1:4:2:2; 2:4:1:1; 3:4:2:0 */​
u8 ChromaSubsampling;​
u32 ImageHeight; /* 图像高度 */ ​
u32 ImageWidth; /* 图像宽度 */ ​
u8 ImageQuality; /* 图像编码质量:1~100 */​
}JPEG_ConfTypeDef;

inbuf和outbuf分表代表输入MDMA FIFO和输出MDMA FIFO,使用FIFO来处理MDMA数据,可以提高读写效率。注意:这里的输入MDMA FIFO和输出MDMA FIFO同JPEG的输入FIFO和输出FIFO是不一样的,要注意区分。通过JPEG_DMA_INBUF_NB和JPEG_DMA_OUTBUF_NB宏定义,我们可以修改输入DMA FIFO和输出DMA FIFO的深度。​

其它,还有输入输出MDMA FIFO的读写位置、暂停状态、解码状态、单次YUV输出字节数、单次输出图像高度和当前输出高度等信息。​

下面开始介绍jpegcodec.c文件,首先是JPEG规范(ISO/IEC 10918-1标准)的样本量化表,其定义如下:​

/* JPEG规范(ISO/IEC 10918-1标准)的样本量化表​
获取JPEG图片质量时需要用到​
*/​
const uint8_t JPEG_LUM_QuantTable[JPEG_QUANT_TABLE_SIZE] =​
{​
16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55,​
14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62,​
18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92,​
49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99​
};​

const uint8_t JPEG_ZIGZAG_ORDER[JPEG_QUANT_TABLE_SIZE] =​
{​
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,​
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,​
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,​
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63​
};

这两个数组在后面的jpeg_get_quality函数,获取JPEG图片质量时需要用到。​

下面介绍的是JPEG硬件解码输入MDMA配置函数,其定义如下:​

/**​
硬件解码输入MDMA配置​
输入MDMA存储器地址​
输入MDMA数据长度,0~262143,以字节为单位​
无​
*/​
void jpeg_in_dma_init(uint32_t meminaddr, uint32_t meminsize)​
{​
uint32_t regval = 0;​
uint32_t addrmask = 0;​
RCC->AHB3ENR |= 1 << 0; /* 使能MDMA时钟 */​
MDMA_Channel7->CCR = 0; /* 输入MDMA清零 */​

while (MDMA_Channel7->CCR & 0X01); /* 等待MDMA_Channel7关闭完成 */​
MDMA_Channel7->CIFCR = 0X1F; /* 中断标志清零 */​
MDMA_Channel7->CCR |= 1 << 2; /* CTCIE=1,使能通道传输完成中断 */​
MDMA_Channel7->CCR |= 2 << 6; /* PL[1:0]=2,高优先级 */​
MDMA_Channel7->CBNDTR = meminsize; /* 传输长度为meminsize */​
MDMA_Channel7->CDAR = (uint32_t)&JPEG->DIR; /* 目标地址为:JPEG->DIR */​
MDMA_Channel7->CSAR = meminaddr; /* meminaddr作为源地址 */​
regval = 0 << 28; /* TRGM[1:0]=0,每个MDMA请求触发一次buffer传输 */​
regval |= 1 << 25; /* PKE=1,打包使能 */​
regval |= (32 - 1) << 18; /* TLEN[6:0]=31,buffer传输长度为32字节 */​
regval |= 4 << 15; /* DBURST[2:0]=4,目标突发传输长度为16 */​
regval |= 4 << 12; /* SBURST[2:0]=4,源突发传输长度为16 */​
regval |= 0 << 8; /* SINCOS[1:0]=0,源地址变化单位为8位(字节) */​
regval |= 2 << 6; /* DSIZE[1:0]=2,目标位宽为32位 */​
regval |= 0 << 4; /* SSIZE[1:0]=0,源位宽为8位 */​
regval |= 0 << 2; /* DINC[1:0]=0,目标地址固定 */​
regval |= 2 << 0; /* SINC[1:0]=2,源地址自增 */​
MDMA_Channel7->CTCR = regval; /* 设置CTCR寄存器 */​
/* MDMA的硬件触发通道17触发inmdma,通道17=JPEG input FIFO threshold​
详见< STM32H7xx参考手册_V7(英文版).pdf页,table 95 */​
MDMA_Channel7->CTBR = 17 << 0; ​
addrmask = meminaddr & 0XFF000000; /* 获取掩码 */​
/* 使用AHBS总线访问DTCM/ITCM */​
if (addrmask == 0X20000000 || addrmask == 0)MDMA_Channel7->CTBR |= 1 << 16; ​
HAL_NVIC_SetPriority(MDMA_IRQn,2,3);/* 设置中断优先级,抢占优先级2,子优先级3 */​
HAL_NVIC_EnableIRQ(MDMA_IRQn); /* 开启MDMA中断 */​
}

该函数用于初始化JPEG输入FIFO的MDMA通道,使用buffer传输,单次触发传输32字节,满足JPEG输入FIFO的传输要求。​

下面介绍的是JPEG硬件解码输出MDMA配置函数,其定义如下:​

/**​
硬件解码输出MDMA配置​
输出MDMA存储器地址​
输出MDMA数据长度,0~262143,以字节为单位​
无​
*/​
void jpeg_out_dma_init(uint32_t memoutaddr, uint32_t memoutsize)​
{​
uint32_t regval = 0;​
uint32_t addrmask = 0;​
RCC->AHB3ENR |= 1 << 0; /* 使能MDMA时钟 */​
MDMA_Channel6->CCR = 0; /* 输出MDMA清零 */​

while (MDMA_Channel6->CCR & 0X01); /* 等待MDMA_Channel6关闭完成 */​

MDMA_Channel6->CIFCR = 0X1F; /* 中断标志清零 */​
MDMA_Channel6->CCR |= 3 << 6; /* PL[1:0]=2,最高优先级 */​
MDMA_Channel6->CCR |= 1 << 2; /* CTCIE=1,使能通道传输完成中断 */​
MDMA_Channel6->CBNDTR = memoutsize; /* 传输长度为meminsize */​
MDMA_Channel6->CDAR = memoutaddr; /* 目标地址为:memoutaddr */​
MDMA_Channel6->CSAR = (uint32_t)&JPEG->DOR; /* JPEG->DOR作为源地址 */​
regval = 0 << 28; /* TRGM[1:0]=0,每个MDMA请求触发一次buffer传输 */​
regval |= 1 << 25; /* PKE=1,打包使能 */​
regval |= (32 - 1) << 18; /* TLEN[6:0]=31,buffer传输长度为32字节 */​
regval |= 4 << 15; /* DBURST[2:0]=4,目标突发传输长度为16 */​
regval |= 4 << 12; /* SBURST[2:0]=4,源突发传输长度为16 */​
regval |= 0 << 10; /* DINCOS[1:0]=0,目标地址变化单位为8位(字节) */​
regval |= 0 << 6; /* DSIZE[1:0]=0,目标位宽为8位 */​
regval |= 2 << 4; /* SSIZE[1:0]=2,源位宽为32位 */​
regval |= 2 << 2; /* DINC[1:0]=2,目标地址自增 */​
regval |= 0 << 0; /* SINC[1:0]=0,源地址固定 */​
MDMA_Channel6->CTCR = regval; /* 设置CTCR寄存器 */​
/* MDMA的硬件触发通道19触发outmdma,通道19=JPEG output FIFO threshold */​
MDMA_Channel6->CTBR = 19 << 0;​

/* 详见< STM32H7xx参考手册_V7(英文版).pdf页,table 95 */​
addrmask = memoutaddr & 0XFF000000; /* 获取掩码 */​
/* 使用AHBS总线访问DTCM/ITCM */​
if (addrmask == 0X20000000 || addrmask == 0)MDMA_Channel6->CTBR |= 1 << 17; ​

HAL_NVIC_SetPriority(MDMA_IRQn,2,3);/* 设置中断优先级,抢占优先级2,子优先级3 */​
HAL_NVIC_EnableIRQ(MDMA_IRQn); /* 开启MDMA中断 */​
}

该函数用于初始化JPEG输出FIFO的MDMA通道,使用buffer传输,单次触发传输32字节,满足JPEG输出FIFO的传输要求。​

下面介绍一些中断处理函数和回调函数,它们的定义分别如下:​

void (*jpeg_in_callback)(void);  /* JPEG MDMA输入回调函数 */​
void (*jpeg_out_callback)(void); /* JPEG MDMA输出 回调函数 */​
void (*jpeg_eoc_callback)(void); /* JPEG 解码完成 回调函数 */​
void (*jpeg_hdp_callback)(void); /* JPEG Header解码完成 回调函数 */​

/**​
中断服务函数​
处理硬件JPEG解码时输入/输出数据流​
无​
无​
*/​
void MDMA_IRQHandler(void)​
{​
if (MDMA_Channel7->CISR & (1 << 1)) /* CTCIF,通道7传输完成(输入) */​
if (MDMA_Channel7->CISR & (1 << 1)) /* CTCIF,通道7传输完成(输入) */​
{​
MDMA_Channel7->CIFCR |= 1 << 1; /* 清除通道传输完成中断 */​
JPEG->CR &= ~(0X7E); /* 关闭JPEG中断,防止被打断 */​
jpeg_in_callback(); /* 执行输入回调函数,继续读取数据 */​
JPEG->CR |= 3 << 5; /* 使能EOC和HPD中断 */​
}​
if (MDMA_Channel6->CISR & (1 << 1)) /* CTCIF,通道6传输完成(输出) */​
{​
MDMA_Channel6->CIFCR |= 1 << 1; /* 清除通道传输完成中断 */​
JPEG->CR &= ~(0X7E); /* 关闭JPEG中断,防止被打断 */​
jpeg_out_callback(); /* 执行输出回调函数,将数据转换成RGB */​
JPEG->CR |= 3 << 5; /* 使能EOC和HPD中断 */​
}​
}​

/**​
解码中断服务函数​
无​
无​
*/​
void JPEG_IRQHandler(void)​
{​
if (JPEG->SR & (1 << 6)) /* JPEG Header解码完成 */​
{​
jpeg_hdp_callback();​
JPEG->CR &= ~(1 << 6); /* 禁止Jpeg Header解码完成中断 */​
JPEG->CFR |= 1 << 6; /* 清除HPDF位(header解码完成位) */​
}​

if (JPEG->SR & (1 << 5)) /* JPEG解码完成 */​
{​
jpeg_dma_stop();​
jpeg_eoc_callback();​
JPEG->CFR |= 1 << 5; /* 清除EOC位(解码完成位) */​
MDMA_Channel6->CCR &= ~(1 << 0); /* 关闭MDMA通道6 */​
MDMA_Channel7->CCR &= ~(1 << 0); /* 关闭MDMA通道7 */​
}​
}

MDMA_IRQHandler中断服务函数,用于处理输入/输出FIFO MDMA的传输完成中断,当发生输入FIFO MDMA传输完成中断时,调用jpeg_in_callback回调函数,处理输入FIFO MDMA传输完成事务。当发生输出FIFO MDMA传输完成中断时,调用jpeg_out_callback回调函数,处理输出FIFO MDMA传输完成事务。​

JPEG_IRQHandler中断服务函数,根据JPEG_SR的状态标志位,分别处理JPEG头解码完成中断和JPEG文件解码完成中断。当JPEG头解码完成时,调用jpeg_hdp_callback回调函数处理相关事务。当JPEG文件解码完成时,调用jpeg_eoc_callback回调函数处理相关事务,同时停止MDMA传输。​

下面介绍的是初始化硬件JPEG内核函数,其定义如下:​

/**​
初始化硬件JPEG内核​
编解码控制结构体​
成功; 1, 失败;​
*/​
uint8_t jpeg_core_init(jpeg_codec_typedef *tjpeg)​
{​
uint8_t i;​
RCC->AHB3ENR |= 1 << 5; /* 使能硬件jpeg时钟 */​

for (i = 0; i < JPEG_DMA_INBUF_NB; i++)​
{​
tjpeg->inbuf[i].buf = mymalloc(SRAMDTCM, JPEG_DMA_INBUF_LEN);​

if (tjpeg->inbuf[i].buf == NULL)​
{​
jpeg_core_destroy(tjpeg);​
return 1;​
}​
}​
JPEG->CR = 0; /* 先清零 */​
JPEG->CR |= 1 << 0; /* 使能硬件JPEG */​
JPEG->CONFR0 &= ~(1 << 0); /* 停止JPEG编解码进程 */​
JPEG->CR |= 1 << 13; /* 清空输入fifo */​
JPEG->CR |= 1 << 14; /* 清空输出fifo */​
JPEG->CFR = 3 << 5; /* 清空标志 */​
HAL_NVIC_SetPriority(JPEG_IRQn,1,3);/* 设置中断优先级,抢占优先级1,子优先级3 */​
HAL_NVIC_EnableIRQ(JPEG_IRQn); /* 开启JPEG中断 */​
JPEG->CONFR1 |= 1 << 8; /* 使能header处理 */​
return 0;​
}

jpeg_core_init函数用于初始化硬件JPEG内核。在该函数里面,对tjpeg->inbuf[i].buf数组申请内存(tjpeg->outbuf[i]的内存申请,我们放到了jpeg_hdrover_callback函数里面)。tjpeg是jpeg_codec_typedef结构体类型变量,用于控制整个JPEG解码。jpeg_codec_typedef结构体我们在jpegcodec.h里面定义的,前面讲过的。​

下面介绍的是关闭硬件JPEG内核,并释放内存函数,其定义如下:​

/**​
关闭硬件JPEG内核,并释放内存​
编解码控制结构体​
无​
*/​
void jpeg_core_destroy(jpeg_codec_typedef *tjpeg)​
{​
uint8_t i;​
jpeg_dma_stop(); /* 停止MDMA传输 */​

for (i = 0; i < JPEG_DMA_INBUF_NB; i++)​
{​
myfree(SRAMDTCM, tjpeg->inbuf[i].buf); /* 释放内存 */​
}​
for (i = 0; i < JPEG_DMA_OUTBUF_NB; i++)​
{​
myfree(SRAMIN, tjpeg->outbuf[i].buf); /* 释放内存 */​
}​
}

该函数用于关闭JPEG处理(停止MDMA传输),并释放内存。​

下面介绍的是初始化硬件JPEG解码器函数,其定义如下:​

/**​
初始化硬件JPEG解码器​
编解码控制结构体​
无​
*/​
void jpeg_decode_init(jpeg_codec_typedef *tjpeg)​
{​
uint8_t i;​
tjpeg->inbuf_read_ptr = 0;​
tjpeg->inbuf_write_ptr = 0;​
tjpeg->indma_pause = 0;​
tjpeg->outbuf_read_ptr = 0;​
tjpeg->outbuf_write_ptr = 0;​
tjpeg->outdma_pause = 0;​
tjpeg->state = JPEG_STATE_NOHEADER; /* 图片解码结束标志 */​

for (i = 0; i < JPEG_DMA_INBUF_NB; i++)​
{​
tjpeg->inbuf[i].sta = 0;​
tjpeg->inbuf[i].size = 0;​
}​

for (i = 0; i < JPEG_DMA_OUTBUF_NB; i++)​
{​
tjpeg->outbuf[i].sta = 0;​
tjpeg->outbuf[i].size = 0;​
}​

MDMA_Channel6->CCR = 0; /* MDMA通道6禁止 */​
MDMA_Channel7->CCR = 0; /* MDMA通道7禁止 */​
MDMA_Channel6->CIFCR = 0X1F; /* 中断标志清零 */​
MDMA_Channel7->CIFCR = 0X1F; /* 中断标志清零 */​

JPEG->CONFR1 |= 1 << 3; /* 硬件JPEG解码模式 */​
JPEG->CONFR0 &= ~(1 << 0); /* 停止JPEG编解码进程 */​
JPEG->CR &= ~(0X3F << 1); /* 关闭所有中断 */​
JPEG->CR |= 1 << 13; /* 清空输入fifo */​
JPEG->CR |= 1 << 14; /* 清空输出fifo */​
JPEG->CR |= 1 << 6; /* 使能Jpeg Header解码完成中断 */​
JPEG->CR |= 1 << 5; /* 使能解码完成中断 */​
JPEG->CFR = 3 << 5; /* 清空标志 */​
JPEG->CONFR0 |= 1 << 0; /* 使能JPEG编解码进程 */​
}

该函数用于初始化硬件JPEG解码器,同时对输入MDMA FIFO和输出MDMA FIFO的相关标记进行清理处理,以便开始JPEG解码。 ​

下面介绍的是启动输入MDMA函数和启动输出MDMA函数,它们的定义如下:​

/**​
启动 jpeg in mdma, 开始解码JPEG​
无​
无​
*/​
void jpeg_in_dma_start(void)​
{​
MDMA_Channel7->CCR |= 1 << 0; /* 使能MDMA通道7的传输 */​
}​

/**​
启动 jpeg out mdma, 开始输出YUV数据​
无​
无​
*/​
void jpeg_out_dma_start(void)​
{​
MDMA_Channel6->CCR |= 1 << 0; /* 使能MDMA通道6的传输 */​
}

jpeg_in_dma_start函数启动 jpeg in mdma,开始解码JPEG。jpeg_out_dma_start函数启动 jpeg out mdma,开始输出YUV数据。​

下面介绍的是停止JPEG MDMA解码过程函数,其定义如下:​

/**​
停止JPEG MDMA解码过程​
无​
无​
*/​
void jpeg_dma_stop(void)​
{​
JPEG->CONFR0 &= ~(1 << 0); /* 停止JPEG编解码进程 */​
JPEG->CR &= ~(0X3F << 1); /* 关闭所有中断 */​
JPEG->CFR = 3 << 5; /* 清空标志 */​
}

该函数用于停止JPEG MDMA解码过程。 ​

下面介绍的是恢复MDMA IN过程函数和恢复MDMA OUT过程函数,它们的定义如下:​

/**​
恢复MDMA IN过程​
存储区首地址​
要传输数据长度(以字节为单位)​
无​
*/​
void jpeg_in_dma_resume(uint32_t memaddr, uint32_t memlen)​
{​
if (memlen % 4)memlen += 4 - memlen % 4; /* 扩展到4的倍数 */​

MDMA_Channel7->CIFCR = 0X1F; /* 中断标志清零 */​
MDMA_Channel7->CBNDTR = memlen; /* 传输长度为memlen */​
MDMA_Channel7->CSAR = memaddr; /* memaddr作为源地址 */​
MDMA_Channel7->CCR |= 1 << 0; /* 使能MDMA通道7的传输 */​
}​

/**​
恢复MDMA OUT过程​
存储区首地址​
要传输数据长度(以字节为单位)​
无​
*/​
void jpeg_out_dma_resume(uint32_t memaddr, uint32_t memlen)​
{​
if (memlen % 4)memlen += 4 - memlen % 4; /* 扩展到4的倍数 */​

MDMA_Channel6->CIFCR = 0X1F; /* 中断标志清零 */​
MDMA_Channel6->CBNDTR = memlen; /* 传输长度为memlen */​
MDMA_Channel6->CDAR = memaddr; /* memaddr作为源地址 */​
MDMA_Channel6->CCR |= 1 << 0; /* 使能MDMA通道6的传输 */​
}

jpeg_in_dma_resume函数用于重启启动输入MDMA。jpeg_out_dma_resume函数用于重启启动输出MDMA。​

下面介绍的是获取图像信息函数,其定义如下:​

/**​
获取图像信息​
编解码控制结构体​
无​
*/​
void jpeg_get_info(jpeg_codec_typedef *tjpeg)​
{​
uint32_t yblockNb, cBblockNb, cRblockNb;​

switch (JPEG->CONFR1 & 0X03)​
{​
case 0:/* grayscale,1 color component */​
tjpeg->Conf.ColorSpace = JPEG_GRAYSCALE_COLORSPACE;​
break;​

case 2:/* YUV/RGB,3 color component */​
tjpeg->Conf.ColorSpace = JPEG_YCBCR_COLORSPACE;​
break;​

case 3:/* CMYK,4 color component */​
tjpeg->Conf.ColorSpace = JPEG_CMYK_COLORSPACE;​
break;​
}​

tjpeg->Conf.ImageHeight = (JPEG->CONFR1 & 0XFFFF0000) >> 16;/* 获得图像高度 */​
tjpeg->Conf.ImageWidth = (JPEG->CONFR3 & 0XFFFF0000) >> 16; /* 获得图像宽度 */​

if ((tjpeg->Conf.ColorSpace == JPEG_YCBCR_COLORSPACE) ​
|| (tjpeg->Conf.ColorSpace == JPEG_CMYK_COLORSPACE))​
{​
yblockNb = (JPEG->CONFR4 & (0XF << 4)) >> 4;​
cBblockNb = (JPEG->CONFR5 & (0XF << 4)) >> 4;​
cRblockNb = (JPEG->CONFR6 & (0XF << 4)) >> 4;​

if ((yblockNb == 1) && (cBblockNb == 0) && (cRblockNb == 0))​
{​
tjpeg->Conf.ChromaSubsampling = JPEG_422_SUBSAMPLING;/* 16x8 block */​
}​
else if ((yblockNb == 0) && (cBblockNb == 0) && (cRblockNb == 0))​
{​
tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING;​
}​
else if ((yblockNb == 3) && (cBblockNb == 0) && (cRblockNb == 0))​
{​
tjpeg->Conf.ChromaSubsampling = JPEG_420_SUBSAMPLING;​
}​
else​
{​
tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING;​
}​
}​
else​
{​
tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING; /* 默认用4:4:4 */​
}​
/* 图像质量参数在整个图片的最末尾,刚开始的时候,是无法获取的,所以直接设置为0 */​
tjpeg->Conf.ImageQuality = 0; ​
}

JPEG_Get_Info函数,用于获取JPEG图像信息,在JPEG头解码完成后,被调用。该函数可以获取JPEG图片的宽度、高度、颜色空间和色度抽样等重要信息。​

下面介绍的是得到JPEG图像质量函数,其定义如下:​

/**​
得到JPEG图像质量​
在解码完成后,可以调用并获得正确的结果.​
无​
图像质量, 0~100​
*/​
uint8_t jpeg_get_quality(void)​
{​
uint32_t quality = 0;​
uint32_t quantRow, quantVal, scale, i, j;​
uint32_t *tableAddress = (uint32_t *)JPEG->QMEM0;​
i = 0;​
while (i < JPEG_QUANT_TABLE_SIZE)​
{​
quantRow = *tableAddress;​

for (j = 0; j < 4; j++)​
{​
quantVal = (quantRow >> (8 * j)) & 0xFF;​
if (quantVal == 1)quality += 100; /* 100% */​
else​
{​
scale = (quantVal * 100) ​
/ ((uint32_t)JPEG_LUM_QuantTable[JPEG_ZIGZAG_ORDER[i + j]]);​

if (scale <= 100)​
{​
quality += (200 - scale) / 2;​
}​
else​
{​
quality += 5000 / scale;​
}​
}​
}​
i += 4;​
tableAddress++;​
}​
return (quality / ((uint32_t)64));​
}

该函数用于获取当前JPEG图像的质量,返回值越大,说明图像质量越好,解码所要耗费的时间就越多。该函数我们一般用不到。​

最后介绍的是将YUV数据转换成RGB数据函数,其定义如下:​

/**​
将YUV数据转换成RGB数据​
利用DMA2D, 将JPEG解码的YUV数据转换成RGB数据, 全硬件完成, 速度非常快​
编解码控制结构体​
输出数组首地址​
成功; 1, 超时,失败;​
*/​
uint8_t jpeg_dma2d_yuv2rgb_conversion(jpeg_codec_typedef *tjpeg,uint32_t *pdst)​
{​
uint32_t regval = 0;​
uint32_t cm = 0; /* 采样方式n */​
uint32_t timeout = 0;​

if (tjpeg->Conf.ChromaSubsampling == JPEG_420_SUBSAMPLING)​
{​
cm = DMA2D_CSS_420; /* YUV420转RGB */​
}​
else if (tjpeg->Conf.ChromaSubsampling == JPEG_422_SUBSAMPLING)​
{​
cm = DMA2D_CSS_422; /* YUV422转RGB */​
}​
else if (tjpeg->Conf.ChromaSubsampling == JPEG_444_SUBSAMPLING)​
{​
cm = DMA2D_NO_CSS; /* YUV444转RGB */​
}​
RCC->AHB3ENR |= 1 << 4; /* 使能DMA2D时钟 */​
RCC->AHB3RSTR |= 1 << 4; /* 复位DMA2D */​
RCC->AHB3RSTR &= ~(1 << 4); /* 结束复位 */​
DMA2D->CR &= ~(1 << 0); /* 先停止DMA2D */​
DMA2D->CR = 1 << 16; /* MODE[2:0]=001,存储器到存储器,带PFC模式 */​
DMA2D->OPFCCR = 2 << 0; /* CM[2:0]=010,输出为RGB565格式 */​
DMA2D->OOR = 0; /* 设置行偏移为0 */​
DMA2D->IFCR |= 1 << 1; /* 清除传输完成标志 */​
regval = 11 << 0; /* CM[3:0]=1011,输入数据为YCbCr格式 */​
/* CSS[1:0]=cm,Chroma Sub-Sampling:0,4:4:4;1,4:2:2;2,4:2:0 */​
regval |= cm << 18;​
DMA2D->FGPFCCR = regval; /* 设置FGPCCR寄存器 */​
DMA2D->FGOR = 0; /* 前景层行偏移为0 */​
/* 设定行数寄存器 */​
DMA2D->NLR = tjpeg->yuvblk_height | (tjpeg->Conf.ImageWidth << 16); ​
DMA2D->OMAR = (uint32_t)pdst; /* 输出存储器地址 */​
DMA2D->FGMAR = (uint32_t)tjpeg->outbuf[tjpeg->outbuf_read_ptr].buf;/*源地址*/​
DMA2D->CR |= 1 << 0; /* 启动DMA2D */​
while ((DMA2D->ISR & (1 << 1)) == 0) /* 等待传输完成 */​
{​
timeout++;​

if (timeout > 0X1FFFFFF)break; /* 超时退出 */​
}​
/* YUV2RGB转码结束后,再复位一次DMA2D */​
RCC->AHB3RSTR |= 1 << 4; /* 复位DMA2D */​
RCC->AHB3RSTR &= ~(1 << 4); /* 结束复位 */​
if (timeout > 0X1FFFFFF)return 1;​
return 0;​
}

该函数使用硬件DMA2D实现YCbCr(YUV)图像数据到RGB图像数据的格式转换,可以快速实现YUVàRGB数据的转换。具体的实现原理,我们在前面已经介绍过了。​

JPEGCODEC驱动代码就介绍到这里。​

2. PICTURE驱动代码​

PICTURE驱动代码,本实验我们添加了hjpgd.c和hjpgd.h,用于实现JPG/JPEG图片的硬件JPEG解码。​

hjpgd.h头文件中只是一些函数声明,下面直接介绍hjpgd.c文件的代码,首先是JPEG输入数据流回调函数,其定义如下:​

jpeg_codec_typedef hjpgd; /* JPEG硬件解码结构体 */​

/**​
输入数据流回调函数​
用于获取JPEG文件原始数据, 每当JPEG DMA IN BUF为空的时候,调用该函数​
无​
无​
*/​
void jpeg_dma_in_callback(void)​
{​
hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta = 0; /* 此buf已经处理完了 */​
hjpgd.inbuf[hjpgd.inbuf_read_ptr].size = 0; /* 此buf已经处理完了 */​
hjpgd.inbuf_read_ptr++; /* 指向下一个buf */​
/* 归零 */​
if (hjpgd.inbuf_read_ptr >= JPEG_DMA_INBUF_NB) hjpgd.inbuf_read_ptr = 0; ​

if (hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta == 0) /* 无有效buf */​
{​
hjpgd.indma_pause = 1; /* 标记暂停 */​
}​
else /* 有效的buf */​
{​
/* 继续下一次DMA传输 */​
jpeg_in_dma_resume((uint32_t)hjpgd.inbuf[hjpgd.inbuf_read_ptr].buf, ​
hjpgd.inbuf[hjpgd.inbuf_read_ptr].size);​
}​
}

该函数用于处理JPEG输入数据流,当JPEG输入MDMA传输完成时,调用该函数。对已处理的buf标记清零,然后切换到下一个buf。当buf不够时,暂停JPEG输入FIFO获取数据,并标记暂停;当buf足够时,切换到下一个buf,继续传输。​

下面介绍的是JPEG输出数据流(YCBCR)回调函数,其定义如下:​

/**​
输出数据流(YCBCR)回调函数​
用于输出YCbCr数据流(YUV)​
无​
无​
*/​
void jpeg_dma_out_callback(void)​
{​
uint32_t *pdata = 0;​
hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta = 1; /* 此buf已满 */​
hjpgd.outbuf[hjpgd.outbuf_write_ptr].size = hjpgd.yuvblk_size ​
- (MDMA_Channel6->CBNDTR & 0X1FFFF); /* 此buf里面数据的长度 */​
/* 如果文件已经解码完成,需要读取DOR最后的数据(<=32字节) */​
if (hjpgd.state == JPEG_STATE_FINISHED) ​
{​
pdata = (uint32_t *)(hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf ​
+ hjpgd.outbuf[hjpgd.outbuf_write_ptr].size);​

while (JPEG->SR & (1 << 4))​
{​
*pdata = JPEG->DOR;​
pdata++;​
hjpgd.outbuf[hjpgd.outbuf_write_ptr].size += 4;​
}​
}​
hjpgd.outbuf_write_ptr++; /* 指向下一个buf */​
/* 归零 */​
if (hjpgd.outbuf_write_ptr >= JPEG_DMA_OUTBUF_NB)hjpgd.outbuf_write_ptr = 0; ​

if (hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta == 1) /* 无有效buf */​
{​
hjpgd.outdma_pause = 1; /* 标记暂停 */​
}​
else /* 有效的buf */​
{​
/* 继续下一次DMA传输 */​
jpeg_out_dma_resume((uint32_t)hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf, ​
hjpgd.yuvblk_size);​
}​
}

该函数用于处理JPEG输出数据流,当JPEG输出MDMA传输完成时,调用该函数。对已满的buf标记满,并标记容量,然后切换到下一个buf。当buf不够时,暂停获取JPEG输出FIFO的数据,并标记暂停;当buf足够时,切换到下一个buf,继续传输。当解码状态结束时,需要手动读取JPEG_DOR寄存器的数据。​

下面介绍的是JPEG整个文件解码完成回调函数,其定义如下:​

/**​
整个文件解码完成回调函数​
无​
无​
*/​
void jpeg_endofcovert_callback(void)​
{​
hjpgd.state = JPEG_STATE_FINISHED; /* 标记JPEG解码完成 */​
}

该函数在JPG/JPEG文件解码结束时调用。该函数处理非常简单,直接将当前解码状态标记为:JPEG解码完成(JPEG_STATE_FINISHED)即可。​

下面介绍的是JPEG header解析成功回调函数,其定义如下:​

/**​
解析成功回调函数​
无​
无​
*/​
void jpeg_hdrover_callback(void)​
{​
uint8_t i = 0;​
hjpgd.state = JPEG_STATE_HEADEROK;/* HEADER获取成功 */​
jpeg_get_info(&hjpgd); /* 获取JPEG相关信息,包括大小,色彩空间,抽样等 */​
picinfo.ImgWidth = hjpgd.Conf.ImageWidth;​
picinfo.ImgHeight = hjpgd.Conf.ImageHeight;​

/* 需要获取JPEG基本信息以后,才能根据jpeg输出大小和采样方式,来计算输出缓冲大小,​
并启动输出MDMA */​
switch (hjpgd.Conf.ChromaSubsampling)​
{​
case JPEG_420_SUBSAMPLING:​
/* YUV420,每个YUV像素占1.5个字节.每次输出16行.16*1.5=24 */​
hjpgd.yuvblk_size = 24 * hjpgd.Conf.ImageWidth; ​
hjpgd.yuvblk_height = 16; /* 每次输出16行 */​
break;​

case JPEG_422_SUBSAMPLING:​
/* YUV422,每个YUV像素占2个字节.每次输出8行.8*2=16 */ ​
hjpgd.yuvblk_size = 16 * hjpgd.Conf.ImageWidth; ​
hjpgd.yuvblk_height = 8; /* 每次输出8行 */​
break;​

case JPEG_444_SUBSAMPLING:​
/* YUV444,每个YUV像素占3个字节.每次输出8行.8*3=24 */​
hjpgd.yuvblk_size = 24 * hjpgd.Conf.ImageWidth; ​
hjpgd.yuvblk_height = 8; /* 每次输出8行 */​
break;​
}​
hjpgd.yuvblk_curheight = 0; /* 当前行计数器清零 */​

for (i = 0; i < JPEG_DMA_OUTBUF_NB; i++)​
{​
/* 有可能会多需要32字节内存 */​
hjpgd.outbuf[i].buf = mymalloc(SRAMIN, hjpgd.yuvblk_size + 32); ​

if (hjpgd.outbuf[i].buf == NULL)​
{​
hjpgd.state = JPEG_STATE_ERROR; /* HEADER获取失败 */​
}​
}​

if (hjpgd.outbuf[JPEG_DMA_OUTBUF_NB - 1].buf != NULL) /* 所有buf都申请OK */​
{​
/* 配置输出DMA */​
jpeg_out_dma_init((uint32_t)hjpgd.outbuf[0].buf, hjpgd.yuvblk_size);​
jpeg_out_dma_start(); /* 启动DMA OUT传输,开始接收JPEG解码数据流 */​
}​
piclib_ai_draw_init();​
}

该函数在JPEG头解码成功后调用。该函数先标记状态为JPEG头解码成功(JPEG_STATE_HEADEROK),然后调用JPEG_Get_Info函数获取JPEG相关信息。在得到JPEG文件的抽样方式、图库宽度等信息后,计算:yuvblk_size和yuvblk_height这两个关键参数,然后对申请outbuf内存并初始化输出FIFO MDMA,最后启动输出FIFO MDMA,开始接收硬件JPEG解码后的数据。​

这里需要注意:因为JPEG 输出FIFO的MDMA是在JPEG头解码成功回调函数里面才申请内存并初始化的,因此输入FIFO的MDMA必须先输入一部分数据给JPEG解码内核,才可以执行输出FIFO MDMA初始化。但是如果整张图片的大小,都不够一次输入FIFO MDMA的传输的话,那么可能会出现解码无法完成的情况,出现这种情况很好解决,就是把输入FIFO MDMA的大小改小一点,即JPEG_DMA_INBUF_LEN的大小改小一点。​

下面介绍的是JPEG硬件解码图片函数,其定义如下:​

/**​
硬件解码图片​
注意, 请保证:​
待解吗图片的分辨率,必须小于等于屏幕的分辨率!​
请保证图片的宽度是16的倍数,否则解码出错!​
*​
包含路径的文件名(.jpeg/jpg)​
操作结果​
成功​
其他, 错误码​
*/​
uint8_t hjpgd_decode(uint8_t *filename)​
{​
FIL *ftemp;​
uint16_t *rgb565buf = 0;​
volatile uint32_t timecnt = 0;​
uint32_t br = 0;​
uint8_t fileover = 0;​
uint8_t i = 0;​
uint8_t res;​
res = jpeg_core_init(&hjpgd); /* 初始化JPEG内核 */​

if (res)return 1;​

ftemp = (FIL *)mymalloc(SRAMITCM, sizeof(FIL)); /* 申请内存 */​

if (f_open(ftemp, (char *)filename, FA_READ) != FR_OK) /* 打开图片失败 */​
{​
jpeg_core_destroy(&hjpgd);​
myfree(SRAMITCM, ftemp); /* 释放内存 */​
return 2;​
}​

jpeg_decode_init(&hjpgd); /* 初始化硬件JPEG解码器 */​

for (i = 0; i < JPEG_DMA_INBUF_NB; i++)​
{​
/* 填满所有输入数据缓冲区 */​
res = f_read(ftemp, hjpgd.inbuf[i].buf, JPEG_DMA_INBUF_LEN, &br); ​

if (res == FR_OK && br)​
{​
hjpgd.inbuf[i].size = br; /* 读取 */​
hjpgd.inbuf[i].sta = 1; /* 标记buf满 */​
}​
if (br == 0)break;​
}​
/* 配置输入DMA */​
jpeg_in_dma_init((uint32_t)hjpgd.inbuf[0].buf, hjpgd.inbuf[0].size);​
jpeg_in_callback = jpeg_dma_in_callback; /* JPEG DMA读取数据回调函数 */​
jpeg_out_callback = jpeg_dma_out_callback; /* JPEG DMA输出数据回调函数 */​
jpeg_eoc_callback = jpeg_endofcovert_callback; /* JPEG 解码结束回调函数 */​
jpeg_hdp_callback = jpeg_hdrover_callback; /* JPEG Header解码完成回调函数 */​
jpeg_in_dma_start(); /* 启动DMA IN传输,开始解码JPEG图片 */​

while (1)​
{​
/* rgb565buf空,且JPEG HEAD解码完成 */​
if (rgb565buf == 0 && hjpgd.state == JPEG_STATE_HEADEROK) ​
{​
rgb565buf = mymalloc(SRAMIN, hjpgd.Conf.ImageWidth ​
* hjpgd.yuvblk_height * 2 + 32); /* 申请单次输出缓冲内存 */​
}​
/* 有buf为空 */​
if (hjpgd.inbuf[hjpgd.inbuf_write_ptr].sta == 0 && fileover == 0) ​
{​
res = f_read(ftemp, hjpgd.inbuf[hjpgd.inbuf_write_ptr].buf, ​
JPEG_DMA_INBUF_LEN, &br); /* 填满一个缓冲区 */​

if (res == FR_OK && br)​
{​
hjpgd.inbuf[hjpgd.inbuf_write_ptr].size = br; /* 读取 */​
hjpgd.inbuf[hjpgd.inbuf_write_ptr].sta = 1; /* buf满 */​
}​
else if (br == 0)​
{​
timecnt = 0; /* 清零计时器 */​
fileover = 1; /* 文件结束了 */​
}​
if (hjpgd.indma_pause == 1 && hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta ​
== 1) /* 之前是暂停的了,继续传输 */​
{​
jpeg_in_dma_resume((uint32_t)hjpgd.inbuf[hjpgd.inbuf_read_ptr].buf, ​
hjpgd.inbuf[hjpgd.inbuf_read_ptr].size); /* 继续下一次DMA传输 */​
hjpgd.indma_pause = 0;​
}​
hjpgd.inbuf_write_ptr++;​
if (hjpgd.inbuf_write_ptr >= JPEG_DMA_INBUF_NB)​
hjpgd.inbuf_write_ptr = 0;​
}​
if (hjpgd.outbuf[hjpgd.outbuf_read_ptr].sta == 1) /* buf里面有数据要处理 */​
{​
SCB_CleanInvalidateDCache(); /* 清空D catch */​
/* 利用DMA2D,将YUV图像转成RGB565图像 */​
jpeg_dma2d_yuv2rgb_conversion(&hjpgd, (uint32_t *)rgb565buf); ​

/* 输出到LCD屏幕上面 */​
pic_phy.fillcolor(picinfo.S_XOFF, picinfo.S_YOFF ​
+ hjpgd.yuvblk_curheight, hjpgd.Conf.ImageWidth, ​
hjpgd.yuvblk_height, rgb565buf);​
hjpgd.yuvblk_curheight += hjpgd.yuvblk_height; /* 列偏移 */​

//SCB_CleanInvalidateDCache(); /* 清空D catch */​
hjpgd.outbuf[hjpgd.outbuf_read_ptr].sta = 0; /* 标记buf为空 */​
hjpgd.outbuf[hjpgd.outbuf_read_ptr].size = 0; /* 数据量清空 */​
hjpgd.outbuf_read_ptr++;​

if (hjpgd.outbuf_read_ptr >= JPEG_DMA_OUTBUF_NB)​
hjpgd.outbuf_read_ptr = 0; /* 限制范围 */​
/* 当前高度等于或者超过图片分辨率的高度,则说明解码完成了,直接退出 */​
if (hjpgd.yuvblk_curheight >= hjpgd.Conf.ImageHeight)break; ​
}​
/* out暂停,且当前writebuf已经为空了,则恢复out输出 */​
else if (hjpgd.outdma_pause == 1 ​
&& hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta == 0) ​
{​
jpeg_out_dma_resume((uint32_t)hjpgd.outbuf[hjpgd.outbuf_write_ptr] ​
.buf, hjpgd.yuvblk_size); /* 继续下一次DMA传输 */​
hjpgd.outdma_pause = 0;​
}​
timecnt++;​
if (hjpgd.state == JPEG_STATE_ERROR) /* 解码出错,直接退出 */​
{​
res = 2;​
break;​
}​

if (fileover) /* 文件结束后,及时退出,防止死循环 */​
{​
/* 当前处于暂停状态,且没有解析到JPEG头 */​
if (hjpgd.state == JPEG_STATE_NOHEADER && hjpgd.indma_pause == 1) ​
{​
break; /* 解码JPEG头失败了 */​
}​
if (timecnt > 0X3FFFF)break; /* 超时退出 */​
}​
}​
f_close(ftemp); /* 关闭文件 */​
myfree(SRAMITCM, ftemp); /* 释放申请的内存 */​
myfree(SRAMIN, rgb565buf); /* 释放内存 */​
jpeg_core_destroy(&hjpgd); /* 结束JPEG解码,释放内存 */​
return res;​
}

该函数用于解码一张JPG/JPEG图片。该函数采用的思路就是按照我们在49.3.1节介绍的步骤来解码JPG/JPEG图片。请大家参考前面的介绍和源码进行理解。​

另外,我们需要将hjpgd_decode函数加入到图片解码库里面,修改piclib_ai_load_picfile函数代码,具体如下:​

/**​
智能画图​
图片仅在x,y和width, height限定的区域内显示.​
*​
包含路径的文件名(.bmp/.jpg/.jpeg/.gif等)​
起始坐标​
显示区域 ​
使能快速解码​
不使能​
使能​
图片尺寸小于等于液晶分辨率,才支持快速解码​
无​
*/​
uint8_t piclib_ai_load_picfile(const uint8_t *filename, uint16_t x, uint16_t y, ​
uint16_t width, uint16_t height, uint8_t fast)​
{​
uint8_t res;/* 返回值 */​
uint8_t temp;​

if ((x + width) > picinfo.lcdwidth)return PIC_WINDOW_ERR; /* x坐标超范围了 */​
if ((y + height) > picinfo.lcdheight)return PIC_WINDOW_ERR;/* y坐标超范围了 */​
/* 得到显示方框大小 */​
if (width == 0 || height == 0)return PIC_WINDOW_ERR; /* 窗口设定错误 */​

picinfo.S_Height = height;​
picinfo.S_Width = width;​

/* 显示区域无效 */​
if (picinfo.S_Height == 0 || picinfo.S_Width == 0)​
{​
picinfo.S_Height = lcddev.height;​
picinfo.S_Width = lcddev.width;​
return FALSE;​
}​

if (pic_phy.fillcolor == NULL)fast = 0; /* 颜色填充函数未实现,不能快速显示 */​
/* 显示的开始坐标点 */​
picinfo.S_YOFF = y;​
picinfo.S_XOFF = x;​
/* 文件名传递 */​
temp = exfuns_file_type((uint8_t *)filename); /* 得到文件的类型 */​
switch (temp)​
{​
case T_BMP:​
res = stdbmp_decode(filename); /* 解码bmp */​
break;​
case T_JPG:​
case T_JPEG:​
if (fast) /* 可能需要硬件解码 */​
{​
res = jpg_get_size(filename, &picinfo.ImgWidth, ​
&picinfo.ImgHeight);​

if (res == 0)​
{​
/* 满足分辨率小于等于屏幕分辨率、满足图片宽度为16的整数倍,则可以硬件解码​
if (picinfo.ImgWidth <= lcddev.width && picinfo.ImgHeight ​
<= lcddev.height && picinfo.ImgWidth <= picinfo.S_Width ​
&& picinfo.ImgHeight <= picinfo.S_Height && ​
(picinfo.ImgWidth % 16) == 0) ​
{​
/* 采用硬解码JPG/JPEG */​
res = hjpgd_decode((uint8_t *)filename);​
}​
else​
{​
res = jpg_decode(filename, fast); /* 采用软件解码JPG/JPEG */​
}​
}​
}​
else​
{​
res = jpg_decode(filename, fast); /* 统一采用软件解码JPG/JPEG */​
}​
break;​
case T_GIF:​
res = gif_decode(filename, x, y, width, height); /* 解码gif */​
break;​
default:​
res = PIC_FORMAT_ERR; /* 非图片格式!!! */​
break;​
}​
return res;​
}

启用快速解码时,当JPG/JPEG图片尺寸满足小于等于屏幕分辨率,且图片宽度是16的倍数,则我们会通过调用hjpgd_decode函数实现硬件JPEG解码,从而大大提高速度。 ​

PICTURE驱动代码就介绍到这里。​

3. main.c代码​

pic_get_tnum函数,我们在上一个实验已将介绍过了,用来得到path路径下,所有有效文件(图片文件)的个数。接下来介绍的是main函数,其定义如下:​

int main(void)​
{​
uint8_t res;​
DIR picdir; /* 图片目录 */​
FILINFO *picfileinfo; /* 文件信息 */​
uint8_t *pname; /* 带路径的文件名 */​
uint16_t totpicnum; /* 图片文件总数 */​
uint16_t curindex; /* 图片当前索引 */​
uint8_t key; /* 键值 */​
uint8_t pause = 0; /* 暂停标记 */​
uint8_t t;​
uint16_t temp;​
uint32_t *picoffsettbl; /* 图片文件offset索引表 */​

sys_cache_enable(); /* 打开L1-Cache */​
HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */​
delay_init(480); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
usmart_dev.init(240); /* 初始化USMART */​
mpu_memory_protection(); /* 保护相关存储区域 */​
led_init(); /* 初始化LED */​
lcd_init(); /* 初始化LCD */​
key_init(); /* 初始化按键 */​
my_mem_init(SRAMIN); /* 初始化内部内存池(AXI) */​
my_mem_init(SRAM12); /* 初始化SRAM12内存池(SRAM1+SRAM2) */​
my_mem_init(SRAM4); /* 初始化SRAM4内存池(SRAM4) */​
my_mem_init(SRAMDTCM); /* 初始化DTCM内存池(DTCM) */​
my_mem_init(SRAMITCM); /* 初始化ITCM内存池(ITCM) */​
exfuns_init(); /* 为fatfs相关变量申请内存 */​
f_mount(fs[0], "0:", 1); /* 挂载SD卡 */​
f_mount(fs[1], "1:", 1); /* 挂载FLASH */​

while (fonts_init()) /* 检查字库 */​
{​
lcd_show_string(30, 50, 200, 16, 16, "Font Error!", RED);​
delay_ms(200);​
lcd_fill(30, 50, 240, 66, WHITE); /* 清除显示 */​
delay_ms(200);​
}​
text_show_string(30, 50, 200, 16, "正点原子STM32开发板", 16, 0, RED);​
text_show_string(30, 70, 200, 16, "硬件JPEG解码 实验", 16, 0, RED);​
text_show_string(30, 90, 200, 16, "KEY0:NEXT KEY1:PREV", 16, 0, RED);​
text_show_string(30, 110, 200, 16, "KEY_UP:PAUSE", 16, 0, RED);​
text_show_string(30, 130, 200, 16, " ATOM@ALIENTEK", 16, 0, RED);​

while (f_opendir(&picdir, "0:/PICTURE")) /* 打开图片文件夹 */​
{​
text_show_string(30, 170, 240, 16, "PICTURE文件夹错误!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​
totpicnum = pic_get_tnum((uint8_t*)"0:/PICTURE"); /* 得到总有效文件数 */​
while (totpicnum == NULL) /* 图片文件为0 */​
{​
text_show_string(30, 170, 240, 16, "没有图片文件!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​
picfileinfo = (FILINFO *)mymalloc(SRAMIN, sizeof(FILINFO)); /* 申请内存 */​
pname = mymalloc(SRAMIN, FF_MAX_LFN * 2 + 1); /* 为带路径的文件名分配内存 */​
/* 申请4*totpicnum个字节的内存,用于存放图片索引 */​
picoffsettbl = mymalloc(SRAMIN, 4 * totpicnum); ​
while (!picfileinfo || !pname || !picoffsettbl) /* 内存分配出错 */​
{​
text_show_string(30, 170, 240, 16, "内存分配失败!", 16, 0, RED);​
delay_ms(200);​
lcd_fill(30, 170, 240, 186, WHITE); /* 清除显示 */​
delay_ms(200);​
}​
/* 记录索引 */​
res = f_opendir(&picdir, "0:/PICTURE"); /* 打开目录 */​
if (res == FR_OK)​
{​
curindex = 0; /* 当前索引为0 */​
while (1) /* 全部查询一遍 */​
{​
temp = picdir.dptr; /* 记录当前dptr偏移 */​
res = f_readdir(&picdir, picfileinfo); /* 读取目录下的一个文件 */​
/* 错误了/到末尾了,退出 */​
if (res != FR_OK || picfileinfo->fname[0] == 0)break; ​
res = exfuns_file_type((uint8_t *)picfileinfo->fname);​
if ((res & 0XF0) == 0X50) /* 取高四位,看看是不是图片文件 */​
{​
picoffsettbl[curindex] = temp; /* 记录索引 */​
curindex++;​
}​
}​
}​
text_show_string(30, 170, 240, 16, "开始显示...", 16, 0, RED);​
delay_ms(1500);​
piclib_init(); /* 初始化画图 */​
curindex = 0; /* 从0开始显示 */​
res = f_opendir(&picdir, (const TCHAR *)"0:/PICTURE"); /* 打开目录 */​

while (res == FR_OK) /* 打开成功 */​
{​
dir_sdi(&picdir, picoffsettbl[curindex]); /* 改变当前目录索引 */​
res = f_readdir(&picdir, picfileinfo); * 读取目录下的一个文件 */​
/* 错误了/到末尾了,退出 */​
if (res != FR_OK || picfileinfo->fname[0] == 0)break;​
strcpy((char *)pname, "0:/PICTURE/"); /* 复制路径(目录) */​
/* 将文件名接在后面 */​
strcat((char *)pname, (const char *)picfileinfo->fname); ​
lcd_clear(BLACK);​
/* 显示图片 */​
piclib_ai_load_picfile(pname, 0, 0, lcddev.width, lcddev.height, 1); ​
/* 显示图片名字 */​
text_show_string(2, 2, lcddev.width, 16, (char*)pname, 16, 1, RED); ​
t = 0;​

while (1)​
{​
key = key_scan(0); /* 扫描按键 */​
if (t > 250)key = 1; /* 模拟一次按下KEY0 */​
if ((t % 20) == 0)​
{​
LED0_TOGGLE(); /* LED0闪烁,提示程序正在运行. */​
}​

if (key == KEY1_PRES) /* 上一张 */​
{​
if (curindex)​
{​
curindex--;​
}​
else​
{​
curindex = totpicnum - 1;​
}​

break;​
}​
else if (key == KEY0_PRES) /* 下一张 */​
{​
curindex++;​
if (curindex >= totpicnum)curindex = 0;/* 到末尾的时候,自动从头开始 */​
break;​
}​
else if (key == WKUP_PRES)​
{​
pause = !pause;​
LED1(!pause); /* 暂停的时候LED1亮. */​
}​
if (pause == 0)t++;​
delay_ms(10);​
}​
res = 0;​
}​

myfree(SRAMIN, picfileinfo); /* 释放内存 */​
myfree(SRAMIN, pname); /* 释放内存 */​
myfree(SRAMIN, picoffsettbl); /* 释放内存 */​
}

main函数里面我们通过读/写偏移量(图片文件在PICTURE文件夹下的读/写偏移位置,可以看做是一个索引),来查找上一个/下一个图片文件(使用dir_sdi函数)。通过piclib_ai_load_picfile函数,实现对JPG/JPEG图片的解码。这里将fast参数设置为1,当图片文件的分辨率小于等于液晶分辨率的时候,将使用硬件JPEG进行解码。​

至此本例程代码编写完成。最后,本实验可以通过USMART来调用相关函数,以对比性能。将mf_scan_files、piclib_ai_load_picfile和hjpgd_decode等函数添加到USMART管理,即可以通过串口调用这几个函数,测试对比软件JPEG解码和硬件JPEG解码的速度差别。​

49.4 下载验证​

将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示了一些实验信息之后就开始显示图片(假设SD卡及文件都准备好了,即:在SD卡根目录新建:PICTURE 文件夹,并存放一些图片文件(.bmp/.jpg/.gif)在该文件夹内),如图49.4.1所示:​

《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_数据_08


图49.4.1 硬件JPEG解码实验显示效果​

按KEY0和KEY1可以快速切换到下一张或上一张,KEY_UP按键可以暂停自动播放,同时LED1亮,指示处于暂停状态,再按一次KEY_UP则继续播放。对比上一章实验,我们可以发现,对于小尺寸的JPG/JPEG图片(小于液晶分辨率),本例程解码速度明显提升。​

我们通过USMART调用piclib_ai_load_picfile函数,对比测试同一张图片,使用硬件JPEG解码和不使用硬件JPEG解码,速度差别明显,如图49.4.2所示:​

《MiniPRO H750开发指南》第四十九章 硬件JPEG解码实验_2d_09


图49.4.2 硬件JPEG与软件JPEG解码速度对比​

上图,是我们使用4.3寸800*480分辨率的MCU屏做的测试,可以看出,对于同一张图片(图片分辨率:800*480),硬件JPEG解码,只需要41.3ms,软件JPEG解码,则需要453.6ms!硬件JPEG解码速度是软件JPEG解码的11倍左右!!可见,硬件JPEG解码大大提高了对JPG/JPEG图片的解码能力。​


标签:MiniPRO,FIFO,MDMA,解码,JPEG,jpeg,H750,hjpgd
From: https://blog.51cto.com/u_15046463/5748995

相关文章