OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 83|回复: 0

《STM32H7R7开发指南 V1.1 》第五十七章 硬件JPEG解码实验

[复制链接]

1346

主题

1362

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5747
金钱
5747
注册时间
2019-5-8
在线时间
1562 小时
发表于 昨天 09:55 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2026-6-5 09:55 编辑

第五十七章 硬件JPEG解码实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

上一章,我们学习了图片解码,学会了使用软件解码显示bmp/jpg/jpeg/gif等格式的图片,但是软件解码速度都比较慢,本章我们将学习如何使用STM32H7R7自带的硬件JPEG编解码器,实现对JPG/JPEG图片的硬解码,从而大大提高解码速度。
本章分为如下几个小节:
57.1 硬件JPEG编解码器简介
57.2 硬件设计
57.3 程序设计
57.4 下载验证


57.1 硬件JPEG编解码器简介
STM32H7R7自带了硬件JPEG编解码器,可以实现快速JPG/JPEG编解码,本章我们仅使用JPG/JPEG解码器。STM32H7的JPEG编解码器具有如下特点:
支持JPEG编码/解码
支持24位颜色深度(即RGB888)
单周期解码/编码一个像素
支持JPEG头数据编解码
多达4个可编程量化表
单周期哈夫曼表编解码
完全可编程的哈弗曼表(AC和DC各2个)
完全可编程的最小编码单元(MCU)
单周期哈弗曼编码/解码


57.1.1 JPEG编解码器框图
STM32H7R7的JPEG编解码器框图如图57.1.1.1所示:

第五十七章 硬件JPEG解码实验492.png
图57.1.1.1 硬件JPEG编解码器框图

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

1.png
表57.1.1.1 JPEG内部信号

57.1.2 JPEG解码器功能
我们只需要对JPEG编解码器的相关寄存器进行设置,然后读写输入/输出FIFO,即可完成JPEG的编解码。本章,我们只介绍如何利用STM32H7R7的硬件JPEG解码器实现对JPG/JPEG图片的解码。
硬件JPEG解码器,支持解码符合ISO/IEC10918-1协议规范的JPEG数据流,并且支持解码JPEG头(可配置),通过输入FIFO读取需要解码的JPEG数据,通过输出FIFO将解码完成的YUV数据传输给外部。
注意:硬件JPEG解码器解码完成后是YUV格式的数据,并不是RGB格式的数据,所以不能直接显示到LCD上面,必须经过YUVRGB的转换,才可以显示在LCD上面。
硬件JPEG解码时FIFO数据的处理(读取/写入)有两种方式:
1,中断方式。
2,DMA方式(JPEG编解码内核使用的是HPDMA,下同!)。
为了达到最快的解码速度,我们一般使用HPDMA来处理FIFO数据。接下来,我们介绍一下硬件JPEG解码的数据处理过程。
1、输入FIFO DMA
通过jpeg_ift_trg信号(见《STM32H7Rx参考手册_V6(英文版).pdf》第1408页),可以触发JPEG输入FIFO的HPDMA请求,当输入FIFO(总容量为64字节)至少半空的时候,将产生一个HPDMA请求,读取32字节数据到输入FIFO。当HPDMA不再继续往JPEG输入FIFO传送数据时,JPEG解码进程将自动暂停,因此,我们只需要控制HPDMA的启停,就可以控制JPEG的解码进程,这个操作在HPDMA传输完成,读取下一批JPEG数据的时候经常用到。
注意:在当前图片解码完成后,开启下一张图片解码之前,需要先停止HPDMA,然后对输入FIFO进行一次清空(设置JPEG_CR寄存器的IFF位),否则上一张图片的数据会影响到下一张图片的解码。
2、输出FIFO DMA
通过jpeg_oft_trg信号(见《STM32H7Rx参考手册_V6(英文版).pdf》第1410页),可以触发JPEG输出FIFO的HPDMA,当输出FIFO(总容量为64字节)至少半满的时候,将产生一个HPDMA请求,可以从输出FIFO读取32字节数据。当HPDMA不再读取JPEG输出FIFO的数据时,JPEG输出FIFO被暂停,这个操作在HPDMA传输完成,执行YUVRGB转换的时候经常用到。
注意:当图片解码结束以后,输出FIFO里面可能还有数据,此时我们需要手动读取FIFO里面的数据,直到JPEG_SR寄存器的OFNEF位为0。
3、JPEG头解码
通过设置JPEG_CONFR1寄存器的HDR位为1,可以使能JPEG头解码,通过设置JPEG_CR寄存器HPDIE位为1,可以使能JPEG头解码完成中断。在完成JPEG头解码之后,我们可以获取当前JPEG图片的很多参数,包括:颜色空间、色度抽样、高度、宽度和MCU总数等信息。这些参数对我们后面的解码和颜色转换(YUVRGB)非常重要。
我们还用到了1个JPEG中断: JPEG解码完成中断。此时我们可以获取JPG/JPEG图片的很多重要信息,方便后续解码。JPEG解码完成中断,在JPG/JPEG图片解码完成后进入,标志着整张图片解码完成。


57.1.3 HPDMA简介
HPDMA的主要特点有:
支持3种传输方式:内存内存、内存外设、外设内存
支持多达16个通道
所有通道都可以独立设置并连接到DMA1/DMA2或相关外设
一个256级深度buffer,被分成2个独立的128级buffer(先入/先出,即FIFO)
4个优先级可设置:非常高、高、中、低
源和目标的传输位宽可以独立设置(字节、半字、字、双字)
源地址/目标地址自增和大小都可以独立设置
关于HPDMA的具体配置,我们在这里就不详细介绍了,HPDMA的配置和DMA1/DMA2基本类似,不过稍微复杂一点。关于HPDMA的详细介绍,请参考《STM32H7Rx参考手册_V6(英文版).pdf》第13章。


57.1.4 YUV转RGB操作
在H7R7系列MCU,ST添加了硬件YUV到RGB转换的功能,我们通过DMA2D可以很方便的实现YUV到RGB的硬件转换。
H7R7的DMA2D支持将:YCbCr 4:4:4(YUV444),4:2:2(YUV422)和4:2:0(YUV420)等三种抽样格式的颜色数据转换成RGB数据格式。DMA2D执行YCbCr数据格式转换的时候是以8*8的最小编码单元(MCU)为基础的,YCbCr数据在MCU内的排列顺序如下表所示:


第五十七章 硬件JPEG解码实验2793.png
图57.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执行内存到内存的YCbCrRGB转换的简要设置(行偏移为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传输。然后等待传输完成就可以完成一次YCbCrRGB的转换。
以上,就是利用DMA2D进行YCbCr到RGB图像数据转换的简要设置,DMA2D的相关寄存器我们这里就不做介绍,详见《STM32H7Rx参考手册_V6(英文版).pdf》第15章相关章节。


57.1.5 JPEG编解码器寄存器
下面介绍本实验需要用到的JPEG 编解码器寄存器。
JPEG编解码器控制寄存器0(JPEG_CONFR0)
该寄存器仅最低位(START位)有效,设置该位为1,可以启动JPEG解码流程。通过设置该位为0,可以退出当前JPEG解码。
JPEG编解码器配置寄存器1(JPEG_CONFR1)
JPEG编解码器配置寄存器1描述如图57.1.5.1所示:


第五十七章 硬件JPEG解码实验4259.png
图57.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表示CMYK。
JPEG编解码器配置寄存器3(JPEG_CONFR3)
JPEG编解码器配置寄存器3描述如图57.1.5.2所示:


第五十七章 硬件JPEG解码实验4567.png
图57.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_CR)
JPEG控制寄存器描述如图57.1.5.3所示:


第五十七章 硬件JPEG解码实验4829.png
图57.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_SR)
JPEG状态寄存器描述如图57.1.5.4所示:


第五十七章 硬件JPEG解码实验5219.png
图57.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清零标志寄存器描述如图57.1.5.5所示:


第五十七章 硬件JPEG解码实验5491.png
图57.1.5.5 JPEG_CFR寄存器

该寄存器,仅两位有效:CHPDF位和CEOCF位,向这两个位写入1,可以分别清除JPEG_SR寄存器的HPDF和EOCF位。
最后是JPEG数据输入寄存器(JPEG_DIR)和JPEG数据输出寄存器(JPEG_DOR),这两个寄存器都是32位有效,前者用于往输入FIFO写入数据。后者用于读取输出FIFO的数据。


57.2 硬件设计

1. 例程功能
1、本实验开机的时候先检测字库,然后检测SD卡是否存在,如果SD卡存在,则开始查找SD卡根目录下的PICTURE文件夹,如果找到则显示该文件夹下面的图片文件(支持bmp、jpg、jpeg或gif格式),循环显示,通过按WKUP和KEY0可以快速浏览下一张和上一张。如果未找到PICTURE文件夹/任何图片文件,则提示错误。
注意:本例程的实验现象,同上一章(图片显示实验)完全一模一样,唯一的区别,就是JPEG解码速度 (要求图片分辨率小于等于LCD分辨率) 变快了很多。
2、LED0闪烁,提示程序运行。

2. 硬件资源
1)LED灯
       LED0 :LED0 – PD14
       LED1:LED1 – PC0
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)独立按键:KEY0 – PE9、WK_UP – PC13
5)SD卡,通过SDMMC1(SDMMC_D0~D4(PC8~PC11),
       SDMMC_SCK(PC12),SDMMC_CMD(PD2))连接
6)norflash
7)硬件JPEG解码内核


57.3 程序设计

57.3.1 硬件JPEG解码JPG/JPEG的简要步骤
为了提高速度,我们直接操作寄存器,没有使用HAL库提供的函数。
接下来,我们看看在DMA模式下,使用STM32H7R7的硬件JPEG解码JPG/JPEG的简要步骤:
1)初始化硬件JPEG内核。
首先,我们通过设置AHB3ENR的bit0位为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的输入和输出HPDMA,只要我们不往输入FIFO写入数据,JPEG内核就一直处于等待数据输入状态。  
3)配置硬件JPEG输入/输出HPDMA。
这一步,我们将配置JPEG的输入HPDMA和输出HPDMA,分别负责JPEG输入FIFO和输出FIFO的数据传输。对于输入HPDMA,目标地址为JPEG_DIR寄存器地址,源地址为一片内存区域,利用输入HPDMA实现JPEG输入FIFO数据的自动填充。对于输出HPDMA,目标地址为一片内存区域,源地址为JPEG_DOR寄存器地址,利用输出HPDMA实现JPEG输出FIFO数据自动搬运到对应内存区域。对于输入HPDMA和输出HPDMA,我们都需要开启传输完成中断,并设置相关中断服务函数。在传输完成中断里面,实现对输入输出数据的处理。
4)编写相关中断服务函数,启动HPDMA。
我们总共开启了3个中断: JPEG解码完成中断、输入HPDMA传输完成中断和输出HPDMA传输完成中断。后两个中断分别共用一个中断服务函数,所以我们总共只需要编写2个中断服务函数。另外,我们采用回调函数的方式,对数据进行处理,总共需要编写3个回调函数,分别对应3个中断产生时的数据处理。在配置完这些以后,启动HPDMA,开始执行JPEG解码。
注意:输出HPDMA的配置和启动,我们放在JPEG头解码完成中断回调函数里的,因为输出YCbCr数据的多少和单次输出行数,得根据抽样方式进行不同的设置,因此我们必须先等到解析完JPEG头以后,再来配置输出HPDMA。
5)处理JPEG数据输出数据,执行YUVRGB转换,并送LCD显示。
最后,在主循环里面,根据输入HPDMA和输出HPDMA的数据处理情况,持续从源文件读取JPEG数据流,并利用DMA2D,将硬件JPEG解码完成的YCbCr(YUV)数据流转换成RGB格式。最后,在完成一张JPEG解码之后,将RGB数据直接一次性显示到LCD屏幕上,实现图片显示。


57.3.2 程序解析

1. JPEGCODEC驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。JPEGCODEC驱动源码包括五个文件:jpegcodec.c、jpegcodec.h、jpeg_utils.c、jpeg_utils.h和jpeg_utils_tbl.h。
jpegcodec.h头文件定义了两个结构体和一些宏定义,下面重点围绕jpeg_codec_typedef这个结构体介绍一下,jpegcodec.h的相关内容定义如下:

  1. #define JPEG_DMA_INBUF_LEN          4096            /* 单个DMA IN  BUF的大小 */
  2. #define JPEG_DMA_INBUF_NB           10              /* DMA IN  BUF的个数 */
  3. #define JPEG_DMA_OUTBUF_NB          2               /* DMA OUT BUF的个数 */


  4. /* JPEG数据缓冲结构体 */
  5. typedef struct
  6. {
  7.     uint8_t sta;        /* 状态:0,无数据;1,有数据 */
  8.     uint8_t *buf;       /* JPEG数据缓冲区 */
  9.     uint16_t size;      /* JPEG数据长度 */
  10. } jpeg_databuf_type;

  11. #define JPEG_STATE_NOHEADER         0                /* HEADER未读取,初始状态 */
  12. #define JPEG_STATE_HEADEROK         1                /* HEADER读取成功 */
  13. #define JPEG_STATE_FINISHED         2                /* 解码完成 */
  14. #define JPEG_STATE_ERROR            3                /* 解码完成 */

  15. #define JPEG_YCBCR_COLORSPACE       JPEG_CONFR1_COLORSPACE_0
  16. #define JPEG_CMYK_COLORSPACE        JPEG_CONFR1_COLORSPACE

  17. /* jpeg编解码控制结构体 */
  18. typedef struct
  19. {
  20.     JPEG_ConfTypeDef Conf;                         /* 当前JPEG文件相关参数 */
  21.     jpeg_databuf_type inbuf[JPEG_DMA_INBUF_NB];    /* HPDMA IN buf */
  22.     jpeg_databuf_type outbuf[JPEG_DMA_OUTBUF_NB];  /* HPDMA OUT buf */
  23.     volatile uint8_t inbuf_read_ptr;               /* HPDMA IN buf当前读取位置 */
  24.     volatile uint8_t inbuf_write_ptr;              /* HPDMA IN buf当前写入位置 */
  25.     volatile uint8_t indma_pause;                  /* 输入HPDMA暂停状态标识 */
  26.     volatile uint8_t outbuf_read_ptr;          /* HPDMA OUT buf当前读取位置 */
  27.     volatile uint8_t outbuf_write_ptr;         /* HPDMA OUT buf当前写入位置 */
  28.     volatile uint8_t outdma_pause;             /* 输入HPDMA暂停状态标识 */
  29. volatile uint8_t state;                    
  30. /* 解码状态;0,未识别到Header;1,识别到了Header;2,解码完成; */
  31. uint32_t yuvblk_size;                           
  32. /* YUV输出的字节数,使得完成一次DMA2D YUV2RGB转换,刚好是图片宽度的整数倍
  33.      * YUV420图片,每个像素占1.5个YUV字节,每次输出16行,yuvblk_size=图片宽度*16*1.5
  34.      * YUV422图片,每个像素占2个YUV字节和RGB565一样,每次输出8行,yuvblk_size=图片宽
  35. * 度*8*2YUV444图片,每个像素占3个YUV字节,每次输出8行,yuvblk_size=图片宽度*8*3
  36.      */
  37. uint16_t yuvblk_height;
  38. /* 每个YUV块输出像素的高度,对于YUV420,为16,对于YUV422/YUV444为8 */
  39.     uint16_t yuvblk_curheight;                  /* 当前输出高度,0~分辨率高度 */
  40. } jpeg_codec_typedef;
复制代码
该结构体用于控制整个JPEG解码,下面分别介绍一下它的成员:
Conf用于存储当前JPEG文件的一些相关信息,其结构体类型定义如下:

  1. /* JPEG文件信息结构体 */
  2. typedef struct
  3. {
  4.     uint32_t  ColorSpace;        /* 图像的颜色空间: gray-scale/YCBCR/RGB/CMYK */
  5.    /* YCBCR/CMYK颜色空间的色度抽样情况:  0:4:4:4; 1:4:2:2; 2:4:1:1; 3:4:2:0 */
  6.     uint32_t  ChromaSubsampling;
  7.     uint32_t ImageHeight;        /* 图像高度 */
  8.     uint32_t ImageWidth;         /* 图像宽度 */  
  9.     uint32_t  ImageQuality;      /* 图像编码质量:1~100 */
  10. }JPEG_ConfTypeDef;  
复制代码
inbuf和outbuf分表代表输入DMA FIFO和输出DMA FIFO,使用FIFO来处理DMA数据,可以提高读写效率。注意:这里的输入DMA FIFO和输出DMA FIFO同JPEG的输入FIFO和输出FIFO是不一样的,要注意区分。通过JPEG_DMA_INBUF_NB和JPEG_DMA_OUTBUF_NB宏定义,我们可以修改输入DMA FIFO和输出DMA FIFO的深度。
其它,还有输入输出DMA FIFO的读写位置、暂停状态、解码状态、单次YUV输出字节数、单次输出图像高度和当前输出高度等信息。
下面开始介绍jpegcodec.c文件,首先是JPEG规范(ISO/IEC 10918-1标准)的样本量化表,其定义如下:

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

  11. const uint8_t JPEG_ZIGZAG_ORDER[JPEG_QUANT_TABLE_SIZE] =
  12. {
  13.     0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
  14.     12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
  15.     35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
  16.     58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
  17. };
复制代码
这两个数组在后面的jpeg_get_quality函数,获取JPEG图片质量时需要用到。
下面介绍的是JPEG硬件解码输入/输出DMA配置函数,其定义如下:

  1. /**
  2. * @brief       JPEG硬件解码输入MDMA配置
  3. * @param       meminaddr   : JPEG输入MDMA存储器地址
  4. * @param       meminsize   : 输入MDMA数据长度,0~262143,以字节为单位
  5. * @retval      无
  6. */
  7. void jpeg_in_dma_init(uint32_t meminaddr, uint32_t meminsize)
  8. {
  9.     if (meminsize % 4)
  10.     {
  11.         meminsize += 4 - meminsize % 4;     /* 扩展到4的倍数 */
  12.     }
  13.    
  14.     __HAL_RCC_HPDMA1_CLK_ENABLE();          /* 使能HPDMA时钟 */
  15.    
  16.     /* HPDMA1_REQUEST_JPEG_RX Init */
  17.     jpegdmain_handler.Instance = HPDMA1_Channel0;
  18.     jpegdmain_handler.Init.Request = HPDMA1_REQUEST_JPEG_RX;
  19.     jpegdmain_handler.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
  20.     jpegdmain_handler.Init.Direction = DMA_MEMORY_TO_PERIPH;
  21.     jpegdmain_handler.Init.SrcInc = DMA_SINC_INCREMENTED;
  22.     jpegdmain_handler.Init.DestInc = DMA_DINC_FIXED;
  23.     jpegdmain_handler.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
  24.     jpegdmain_handler.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
  25.     jpegdmain_handler.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
  26.     jpegdmain_handler.Init.SrcBurstLength = 8;
  27.     jpegdmain_handler.Init.DestBurstLength = 8;
  28. jpegdmain_handler.Init.TransferAllocatedPort =
  29. DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT1;
  30.     jpegdmain_handler.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
  31.     jpegdmain_handler.Init.Mode = DMA_NORMAL;
  32.     HAL_DMA_Init(&jpegdmain_handler);
  33.     __HAL_LINKDMA(&jpeg_handler, hdmain, jpegdmain_handler);

  34.     HAL_DMA_ConfigChannelAttributes(&jpegdmain_handler, DMA_CHANNEL_NPRIV);
  35.    
  36. HAL_DMA_Start(&jpegdmain_handler, meminaddr, (uint32_t)&JPEG->DIR,
  37. meminsize);  /* 开启DMA */
  38.     __HAL_DMA_ENABLE_IT(&jpegdmain_handler, DMA_IT_TC); /* 开启传输完成中断 */
  39.     HAL_NVIC_SetPriority(HPDMA1_Channel0_IRQn, 2, 3);/* 抢占2,子优先级3,组2 */
  40.     HAL_NVIC_EnableIRQ(HPDMA1_Channel0_IRQn);        /* 使能中断 */
  41. }

  42. /**
  43. * @brief       JPEG硬件解码输出HPDMA配置
  44. * @param       memoutaddr  : JPEG输出HPDMA存储器地址
  45. * @param       memoutsize  : 输出DMA数据长度,0~262143,以字节为单位
  46. * @retval      无
  47. */
  48. void jpeg_out_dma_init(uint32_t memoutaddr, uint32_t memoutsize)
  49. {
  50.     if (memoutsize % 4)
  51.     {
  52.         memoutsize += 4 - memoutsize % 4;   /* 扩展到4的倍数 */
  53.     }

  54.     __HAL_RCC_HPDMA1_CLK_ENABLE();          /* 使能HPDMA时钟 */

  55.     /* JPEG数据输出HPDMA通道配置 */
  56.     HPDMA1_Channel1->CCR = 0;               /* 清零CCR */
  57.     while (HPDMA1_Channel1->CCR & 0X01);    /* 等待HPDMA1_Channel1关闭完成 */
  58.    
  59.     jpegdmaout_handler.Instance = HPDMA1_Channel1;
  60.     jpegdmaout_handler.Init.Request = HPDMA1_REQUEST_JPEG_TX;
  61.     jpegdmaout_handler.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
  62.     jpegdmaout_handler.Init.Direction = DMA_PERIPH_TO_MEMORY;
  63.     jpegdmaout_handler.Init.SrcInc = DMA_SINC_FIXED;
  64.     jpegdmaout_handler.Init.DestInc = DMA_DINC_INCREMENTED;
  65.     jpegdmaout_handler.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
  66.     jpegdmaout_handler.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
  67.     jpegdmaout_handler.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
  68.     jpegdmaout_handler.Init.SrcBurstLength = 8;
  69.     jpegdmaout_handler.Init.DestBurstLength = 8;
  70. jpegdmaout_handler.Init.TransferAllocatedPort =
  71. DMA_SRC_ALLOCATED_PORT1|DMA_DEST_ALLOCATED_PORT0;
  72.     jpegdmaout_handler.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
  73.     jpegdmaout_handler.Init.Mode = DMA_NORMAL;
  74.     HAL_DMA_Init(&jpegdmaout_handler);

  75.     __HAL_LINKDMA(&jpeg_handler, hdmaout, jpegdmaout_handler);

  76.     HAL_DMA_ConfigChannelAttributes(&jpegdmaout_handler, DMA_CHANNEL_NPRIV);

  77. HAL_DMA_Start(&jpegdmaout_handler, (uint32_t)&JPEG->DOR, memoutaddr,
  78. memoutsize); /* 开启DMA */
  79. __HAL_DMA_ENABLE_IT(&jpegdmaout_handler, DMA_IT_TC);
  80. /* 开启传输完成中断 */
  81. HAL_NVIC_SetPriority(HPDMA1_Channel1_IRQn, 2, 3);
  82. /* 抢占2,子优先级3,组2 */
  83. HAL_NVIC_EnableIRQ(HPDMA1_Channel1_IRQn);  
  84. /* 使能中断 */
  85. }
复制代码
该函数用于初始化JPEG输入输出FIFO的DMA通道,使用buffer传输,单次触发传输32字节,满足JPEG输入FIFO的传输要求。
下面介绍一些中断处理函数和回调函数,它们的定义分别如下:

  1. void (*jpeg_in_callback)(void);         /* JPEG DMA输入回调函数 */
  2. void (*jpeg_out_callback)(void);        /* JPEG DMA输出 回调函数 */
  3. void (*jpeg_eoc_callback)(void);        /* JPEG 解码完成 回调函数 */
  4. void (*jpeg_hdp_callback)(void);        /* JPEG Header解码完成 回调函数 */

  5. /**
  6. * @brief       HPDMA中断服务函数
  7. * @note        处理硬件JPEG解码时输入/输出数据流
  8. * @param       无
  9. * @retval      无
  10. */
  11. void HPDMA1_Channel0_IRQHandler(void)
  12. {
  13. if (__HAL_DMA_GET_FLAG(&jpegdmain_handler, DMA_FLAG_TC) != RESET)   
  14. /* HPDMA传输完成 */
  15.     {
  16.         __HAL_DMA_CLEAR_FLAG(&jpegdmain_handler, DMA_FLAG_TC);         
  17. /* 清除HPDMA传输完成中断标志位 */
  18.         JPEG->CR &= ~(1 << 11);              /* 关闭JPEG的HPDMA IN */
  19.         JPEG->CR &= ~(0X7E);                 /* 关闭JPEG中断,防止被打断. */
  20.         jpeg_in_callback();                  /* 执行回调函数 */
  21.         JPEG->CR |= 3 << 5;                  /* 使能EOC和HPD中断. */
  22.     }
  23. }
  24. void HPDMA1_Channel1_IRQHandler(void)
  25. {
  26. if (__HAL_DMA_GET_FLAG(&jpegdmaout_handler, DMA_FLAG_TC) != RESET)  
  27. /* HPDMA传输完成 */
  28.     {
  29.         __HAL_DMA_CLEAR_FLAG(&jpegdmaout_handler, DMA_FLAG_TC);         
  30. /* 清除HPDMA传输完成中断标志位 */
  31.         JPEG->CR &= ~(1 << 12);   
  32.         JPEG->CR &= ~(0X7E);                 /* 关闭JPEG中断,防止被打断. */
  33.         jpeg_out_callback();                 /* 执行回调函数 */
  34.         JPEG->CR |= 3 << 5;                  /* 使能EOC和HPD中断. */
  35.     }
  36. }
  37. /**
  38. * @brief       JPEG解码中断服务函数
  39. * @param       无
  40. * @retval      无
  41. */
  42. void JPEG_IRQHandler(void)
  43. {
  44. if (__HAL_JPEG_GET_FLAG(&jpeg_handler, JPEG_FLAG_HPDF) != RESET)
  45. /* JPEG Header解码完成 */
  46.     {
  47.         jpeg_hdp_callback();
  48.         JPEG->CR &= ~(1 << 6);              /* 禁止Jpeg Header解码完成中断 */
  49.         JPEG->CFR |= 1 << 6;                /* 清除HPDF位(header解码完成位) */
  50.     }

  51. if (__HAL_JPEG_GET_FLAG(&jpeg_handler, JPEG_FLAG_EOCF) != RESET)
  52. /* JPEG解码完成 */
  53.     {
  54.         jpeg_dma_stop();
  55.         jpeg_eoc_callback();
  56.         JPEG->CFR |= 1 << 5;                   /* 清除EOC位(解码完成位) */
  57.         __HAL_DMA_DISABLE(&jpegdmain_handler); /* 关闭JPEG数据输入DMA */
  58.         __HAL_DMA_DISABLE(&jpegdmaout_handler);/* 关闭JPEG数据输出DMA */
  59.     }
  60. }
复制代码
HPDMA1_Channel1_IRQHandler中断服务函数,用于处理输入/输出FIFO DMA的传输完成中断,当发生输入/输出FIFO DMA传输完成中断时,调用jpeg_in_callback /jpeg_out_callback回调函数,处理输入/输出FIFO DMA传输完成事务。
JPEG_IRQHandler中断服务函数,根据JPEG_CR的状态标志位,分别处理JPEG头解码完成中断和JPEG文件解码完成中断。当JPEG头解码完成时,调用jpeg_hdp_callback回调函数处理相关事务。当JPEG文件解码完成时,调用jpeg_eoc_callback回调函数处理相关事务,同时停止DMA传输。
下面介绍的是初始化硬件JPEG内核函数,其定义如下:

  1. /**
  2. * @brief       初始化硬件JPEG内核
  3. * @param       tjpeg       : JPEG编解码控制结构体
  4. * @retval      0, 成功; 1, 失败;
  5. */
  6. uint8_t jpeg_core_init(jpeg_codec_typedef *tjpeg)
  7. {
  8.     uint8_t i;
  9.     __HAL_RCC_JPEG_CLK_ENABLE();        /* 使能JPEG时钟 */
  10.    
  11.     jpeg_handler.Instance = JPEG;
  12.     HAL_JPEG_Init(&jpeg_handler);       /* 初始化JPEG */

  13.     for (i = 0; i < JPEG_DMA_INBUF_NB; i++)
  14.     {
  15.         tjpeg->inbuf[i].buf = mymalloc(SRAMEX, JPEG_DMA_INBUF_LEN);

  16.         if (tjpeg->inbuf[i].buf == NULL)
  17.         {
  18.             jpeg_core_destroy(tjpeg);
  19.             return 1;
  20.         }
  21.     }

  22.     JPEG->CR = 0;                          /* 先清零 */
  23.     JPEG->CR |= 1 << 0;                    /* 使能硬件JPEG */
  24.     JPEG->CONFR0 &= ~(1 << 0);             /* 停止JPEG编解码进程 */
  25.     JPEG->CR |= 1 << 13;                   /* 清空输入fifo */
  26.     JPEG->CR |= 1 << 14;                    /* 清空输出fifo */
  27.     JPEG->CFR = 3 << 5;                     /* 清空标志 */
  28.     HAL_NVIC_SetPriority(JPEG_IRQn, 1, 3);  /* JPEG中断服务函数 1,3 */
  29.     HAL_NVIC_EnableIRQ(JPEG_IRQn);
  30.     JPEG->CONFR1 |= 1 << 8;                 /* 使能header处理 */
  31.     return 0;
  32. }
复制代码
jpeg_core_init函数用于初始化硬件JPEG内核。在该函数里面,对tjpeg->inbuf.buf数组申请内存(tjpeg->outbuf的内存申请,我们放到了hjpgd_decode函数里面)。tjpeg是jpeg_codec_typedef结构体类型变量,用于控制整个JPEG解码。jpeg_codec_typedef结构体我们在jpegcodec.h里面定义的,前面讲过的。
下面介绍的是关闭硬件JPEG内核,并释放内存函数,其定义如下:

  1. /**
  2. * @brief       关闭硬件JPEG内核,并释放内存
  3. * @param       tjpeg       : JPEG编解码控制结构体
  4. * @retval      无
  5. */
  6. void jpeg_core_destroy(jpeg_codec_typedef *tjpeg)
  7. {
  8.     uint8_t i;
  9.     jpeg_dma_stop();                            /* 停止MDMA传输 */

  10.     for (i = 0; i < JPEG_DMA_INBUF_NB; i++)
  11.     {
  12.         myfree(SRAMDTCM, tjpeg->inbuf[i].buf);  /* 释放内存 */
  13.     }

  14.     for (i = 0; i < JPEG_DMA_OUTBUF_NB; i++)
  15.     {
  16.         myfree(SRAMIN, tjpeg->outbuf[i].buf);   /* 释放内存 */
  17.     }
  18. }
复制代码
该函数用于关闭JPEG处理(停止DMA传输),并释放内存。
下面介绍的是初始化硬件JPEG解码器函数,其定义如下:

  1. /**
  2. * @brief       初始化硬件JPEG解码器
  3. * @param       tjpeg       : JPEG编解码控制结构体
  4. * @retval      无
  5. */
  6. void jpeg_decode_init(jpeg_codec_typedef *tjpeg)
  7. {
  8.     uint8_t i;
  9.     tjpeg->inbuf_read_ptr = 0;
  10.     tjpeg->inbuf_write_ptr = 0;
  11.     tjpeg->indma_pause = 0;
  12.     tjpeg->outbuf_read_ptr = 0;
  13.     tjpeg->outbuf_write_ptr = 0;
  14.     tjpeg->outdma_pause = 0;
  15.     tjpeg->state = JPEG_STATE_NOHEADER;     /* 图片解码结束标志 */

  16.     for (i = 0; i < JPEG_DMA_INBUF_NB; i++)
  17.     {
  18.         tjpeg->inbuf[i].sta = 0;
  19.         tjpeg->inbuf[i].size = 0;
  20.     }

  21.     for (i = 0; i < JPEG_DMA_OUTBUF_NB; i++)
  22.     {
  23.         tjpeg->outbuf[i].sta = 0;
  24.         tjpeg->outbuf[i].size = 0;
  25.     }

  26.     __HAL_DMA_DISABLE(&jpegdmain_handler);
  27.     __HAL_DMA_DISABLE(&jpegdmaout_handler);
  28.     jpegdmain_handler.Instance->CFCR |= (0X7F << 8);    /* 中断标志清零 */
  29.     jpegdmaout_handler.Instance->CFCR |= (0X7F << 8);   /* 中断标志清零 */

  30.     JPEG->CONFR1 |= 1 << 3;         /* 硬件JPEG解码模式 */
  31.     JPEG->CONFR0 &= ~(1 << 0);      /* 停止JPEG编解码进程 */
  32.     JPEG->CR &= ~(0X3F << 1);       /* 关闭所有中断 */
  33.     JPEG->CR |= 1 << 13;            /* 清空输入fifo */
  34.     JPEG->CR |= 1 << 14;            /* 清空输出fifo */
  35.     JPEG->CR |= 1 << 6;             /* 使能Jpeg Header解码完成中断 */
  36.     JPEG->CR |= 1 << 5;             /* 使能解码完成中断 */
  37.     JPEG->CFR = 3 << 5;             /* 清空标志 */
  38.     JPEG->CONFR0 |= 1 << 0;         /* 使能JPEG编解码进程 */
  39. }
复制代码
该函数用于初始化硬件JPEG解码器,同时对输入DMA FIFO和输出DMA FIFO的相关标记进行清理处理,以便开始JPEG解码。
下面介绍的是启动输入DMA函数和启动输出DMA函数,它们的定义如下:

  1. /**
  2. * @brief       启动 jpeg in hpdma, 开始解码JPEG
  3. * @param       无
  4. * @retval      无
  5. */
  6. void jpeg_in_dma_start(void)
  7. {
  8.     __HAL_DMA_ENABLE(&jpegdmain_handler);   /* 打开JPEG数据输入HPDMA */
  9.     JPEG->CR |= 1 << 11;                    /* JPEG IN HPDMA使能 */
  10. }

  11. /**
  12. * @brief       启动 jpeg out hpdma, 开始输出YUV数据
  13. * @param       无
  14. * @retval      无
  15. */
  16. void jpeg_out_dma_start(void)
  17. {
  18.     __HAL_DMA_ENABLE(&jpegdmaout_handler);  /* 打开JPEG数据输出HPDMA */
  19.     JPEG->CR |= 1 << 12;                    /* JPEG OUT HPDMA使能 */
  20. }
复制代码
jpeg_in_dma_start函数可以打开输入DMA,开始解码JPEG。jpeg_out_dma_start函数开启输出DMA,开始输出YUV数据。
下面介绍的是停止JPEG DMA解码过程函数,其定义如下:

  1. /**
  2. * @brief       停止JPEG HPDMA解码过程
  3. * @param       无
  4. * @retval      无
  5. */
  6. void jpeg_dma_stop(void)
  7. {
  8.     JPEG->CR &= ~(3 << 11);         /* JPEG IN&OUT HPDMA禁止 */
  9.     JPEG->CONFR0 &= ~(1 << 0);      /* 停止JPEG编解码进程 */
  10.     JPEG->CR &= ~(0X3F << 1);       /* 关闭所有中断 */
  11.     JPEG->CFR = 3 << 5;             /* 清空标志 */
  12. }
复制代码
该函数用于停止JPEG DMA解码过程。
下面介绍的是恢复DMA IN过程函数和恢复DMA OUT过程函数,它们的定义如下:

  1. /**
  2. * @brief       恢复HPDMA IN过程
  3. * @param       memaddr     : 存储区首地址
  4. * @param       memlen      : 要传输数据长度(以字节为单位)
  5. * @retval      无
  6. */
  7. void jpeg_in_dma_resume(uint32_t memaddr, uint32_t memlen)
  8. {
  9.     if (memlen % 4)
  10.     {
  11.         memlen += 4 - memlen % 4;                   /* 扩展到4的倍数 */
  12.     }
  13.     jpegdmain_handler.Instance->CFCR = (0X7F << 8); /* 中断标志清零 */
  14.     jpegdmain_handler.Instance->CBR1 = memlen;      /* 传输长度为memlen */
  15.     jpegdmain_handler.Instance->CSAR = memaddr;     /* memaddr作为源地址 */
  16.     jpegdmain_handler.Instance->CCR |= 1 << 0;      /* 使能HPDMA通道7的传输 */
  17.     JPEG->CR |= 1 << 11;                            /* 恢复JPEG HPDMA IN */
  18. }

  19. /**
  20. * @brief       恢复HPDMA OUT过程
  21. * @param       memaddr     : 存储区首地址
  22. * @param       memlen      : 要传输数据长度(以字节为单位)
  23. * @retval      无
  24. */
  25. void jpeg_out_dma_resume(uint32_t memaddr, uint32_t memlen)
  26. {
  27.     if (memlen % 4)
  28.     {
  29.         memlen += 4 - memlen % 4;                     /* 扩展到4的倍数 */
  30.     }
  31.     jpegdmaout_handler.Instance->CFCR = (0X7F << 8);  /* 中断标志清零 */
  32.     jpegdmaout_handler.Instance->CBR1 = memlen;       /* 传输长度为memlen */
  33.     jpegdmaout_handler.Instance->CDAR = memaddr;      /* memaddr作为源地址 */
  34.     jpegdmaout_handler.Instance->CCR |= 1 << 0;       /* 使能HPDMA通道6的传输 */
  35.     JPEG->CR |= 1 << 12;                              /* 恢复JPEG HPDMA OUT */
  36. }
复制代码
jpeg_in_dma_resume函数用于重启输入DMA。jpeg_out_dma_resume函数用于重启输出DMA。
下面介绍的是获取图像信息函数,其定义如下:

  1. /**
  2. * @brief       获取图像信息
  3. * @param       tjpeg       : JPEG编解码控制结构体
  4. * @retval      无
  5. */
  6. void jpeg_get_info(jpeg_codec_typedef *tjpeg)
  7. {
  8.     uint32_t yblockNb, cBblockNb, cRblockNb;

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

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

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

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

  25. if ((tjpeg->Conf.ColorSpace == JPEG_YCBCR_COLORSPACE) ||
  26. (tjpeg->Conf.ColorSpace == JPEG_CMYK_COLORSPACE))
  27.     {
  28.         yblockNb  = (JPEG->CONFR4 & (0XF << 4)) >> 4;
  29.         cBblockNb = (JPEG->CONFR5 & (0XF << 4)) >> 4;
  30.         cRblockNb = (JPEG->CONFR6 & (0XF << 4)) >> 4;

  31.         if ((yblockNb == 1) && (cBblockNb == 0) && (cRblockNb == 0))
  32.         {
  33.             tjpeg->Conf.ChromaSubsampling = JPEG_422_SUBSAMPLING;   
  34.         }
  35.         else if ((yblockNb == 0) && (cBblockNb == 0) && (cRblockNb == 0))
  36.         {
  37.             tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING;
  38.         }
  39.         else if ((yblockNb == 3) && (cBblockNb == 0) && (cRblockNb == 0))
  40.         {
  41.             tjpeg->Conf.ChromaSubsampling = JPEG_420_SUBSAMPLING;
  42.         }
  43.         else
  44.         {
  45.             tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING;
  46.         }
  47.     }
  48.     else
  49.     {
  50.         tjpeg->Conf.ChromaSubsampling = JPEG_444_SUBSAMPLING;  /* 默认用4:4:4 */
  51.     }
  52.    
  53. tjpeg->Conf.ImageQuality = 0;  
  54. /* 图像质量参数在整个图片的最末尾,刚开始的时候,是无法获取的,所以直接设置为0 */
  55. }
复制代码
jpeg_get_info函数,用于获取JPEG图像信息,在JPEG头解码完成后,被调用。该函数可以获取JPEG图片的宽度、高度、颜色空间和色度抽样等重要信息。
下面介绍的是得到JPEG图像质量函数,其定义如下:

  1. /**
  2. * @brief       得到JPEG图像质量
  3. * @note        在解码完成后,可以调用并获得正确的结果.
  4. * @param       无
  5. * @retval      图像质量, 0~100
  6. */
  7. uint8_t jpeg_get_quality(void)
  8. {
  9.     uint32_t quality = 0;
  10.     uint32_t quantRow, quantVal, scale, i, j;
  11.     uint32_t *tableAddress = (uint32_t *)JPEG->QMEM0;
  12.     i = 0;

  13.     while (i < JPEG_QUANT_TABLE_SIZE)
  14.     {
  15.         quantRow = *tableAddress;

  16.         for (j = 0; j < 4; j++)
  17.         {
  18.             quantVal = (quantRow >> (8 * j)) & 0xFF;

  19.             if (quantVal == 1)
  20.             {
  21.                 quality += 100;   /* 100% */
  22.             }
  23.             else
  24.             {
  25.                 scale = (quantVal * 100) /((uint32_t)JPEG_LUM_QuantTable
  26. [JPEG_ZIGZAG_ORDER[i + j]]);

  27.                 if (scale <= 100)
  28.                 {
  29.                     quality += (200 - scale) / 2;
  30.                 }
  31.                 else
  32.                 {
  33.                     quality += 5000 / scale;
  34.                 }
  35.             }
  36.         }

  37.         i += 4;
  38.         tableAddress++;
  39.     }

  40.     return (quality / ((uint32_t)64));
  41. }
复制代码
该函数用于获取当前JPEG图像的质量,返回值越大,说明图像质量越好,解码所要耗费的时间就越多。该函数我们一般用不到。
最后介绍的是将YUV数据转换成RGB数据函数,其定义如下:

  1. /**
  2. * @brief       将YUV数据转换成RGB数据
  3. * @note        利用DMA2D, 将JPEG解码的YUV数据转换成RGB数据, 全硬件完成, 速度非常快
  4. * @param       tjpeg   : JPEG编解码控制结构体
  5. * @param       pdst    : 输出数组首地址
  6. * @retval      0, 成功; 1, 超时,失败;
  7. */
  8. uint8_t jpeg_dma2d_yuv2rgb_conversion(jpeg_codec_typedef *tjpeg,uint32_t *pdst)
  9. {
  10.     uint32_t regval = 0;
  11.     uint32_t cm = 0;                    /* 采样方式 */
  12.     uint32_t timeout = 0;
  13.     uint32_t destination = 0;

  14.     if (tjpeg->Conf.ChromaSubsampling == JPEG_420_SUBSAMPLING)
  15.     {
  16.         cm = DMA2D_CSS_420;             /* YUV420转RGB */
  17.     }

  18.     else if (tjpeg->Conf.ChromaSubsampling == JPEG_422_SUBSAMPLING)
  19.     {
  20.         cm = DMA2D_CSS_422;             /* YUV422转RGB */
  21.     }
  22.     else if (tjpeg->Conf.ChromaSubsampling == JPEG_444_SUBSAMPLING)
  23.     {
  24.         cm = DMA2D_NO_CSS;              /* YUV444转RGB */
  25.     }

  26. destination = (uint32_t)pdst + (tjpeg->yuvblk_curheight *
  27. tjpeg->Conf.ImageWidth) * 2;        /* 计算目标地址的首地址 */

  28.     __HAL_RCC_DMA2D_CLK_ENABLE();       /* 使能DMA2D时钟 */
  29.     __HAL_RCC_DMA2D_FORCE_RESET();      /* 复位DMA2D */
  30.     __HAL_RCC_DMA2D_RELEASE_RESET();    /* 结束复位 */
  31.     DMA2D->CR &= ~(1 << 0);             /* 先停止DMA2D */
  32.     DMA2D->CR = 1 << 16;                /* 存储器到存储器,带PFC模式 */
  33.     DMA2D->OPFCCR = 2 << 0;             /* CM[2:0]=010,输出为RGB565格式 */
  34.     DMA2D->OOR = 0;                     /* 设置行偏移为0 */
  35.     DMA2D->IFCR |= 1 << 1;              /* 清除传输完成标志 */
  36.     regval = 11 << 0;                   /* CM[3:0]=1011,输入数据为YCbCr格式 */
  37. regval |= cm << 18;                 
  38. /* CSS[1:0]=cm,Chroma Sub-Sampling:0,4:4:4;1,4:2:2;2,4:2:0 */
  39.     DMA2D->FGPFCCR = regval;            /* 设置FGPCCR寄存器 */
  40.     DMA2D->FGOR = 0;                    /* 前景层行偏移为0 */
  41. DMA2D->NLR = tjpeg->yuvblk_height | (tjpeg->Conf.ImageWidth << 16);
  42. /* 设定行数寄存器 */

  43.     DMA2D->OMAR = destination;          /* 输出存储器地址 */

  44. DMA2D->FGMAR = (uint32_t)tjpeg->outbuf[tjpeg->outbuf_read_ptr].buf;
  45. /* 源地址 */
  46.     DMA2D->CR |= 1 << 0;                /* 启动DMA2D */

  47.     while ((DMA2D->ISR & (1 << 1)) == 0)/* 等待传输完成 */
  48.     {
  49.         timeout++;

  50.         if (timeout > 0X1FFFFFF)
  51.         {
  52.             break;                      /* 超时退出 */
  53.         }
  54.     }

  55.     tjpeg->yuvblk_curheight += tjpeg->yuvblk_height;    /* 偏移到下一个内存地址 */
  56.     /* YUV2RGB转码结束后,再复位一次DMA2D */
  57.     __HAL_RCC_DMA2D_FORCE_RESET();      /* 复位DMA2D */
  58.     __HAL_RCC_DMA2D_RELEASE_RESET();    /* 结束复位 */

  59.     if (timeout > 0X1FFFFFF)
  60.     {
  61.         return 1;
  62.     }

  63.     return 0;
  64. }
复制代码
该函数使用硬件DMA2D实现YCbCr(YUV)图像数据到RGB图像数据的格式转换,可以快速实现YUV→RGB数据的转换。具体的实现原理,我们在前面已经介绍过了。
JPEGCODEC驱动代码就介绍到这里。


2. PICTURE驱动代码
PICTURE驱动代码,本实验我们添加了hjpgd.c和hjpgd.h,用于实现JPG/JPEG图片的硬件JPEG解码。
hjpgd.h头文件中只是一些函数声明,下面直接介绍hjpgd.c文件的代码,首先是JPEG输入数据流回调函数,其定义如下:

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

  2. /**
  3. * @brief       JPEG输入数据流回调函数
  4. * @note        用于获取JPEG文件原始数据, 每当JPEG DMA IN BUF为空的时候,调用该函数
  5. * @param       无
  6. * @retval      无
  7. */
  8. void jpeg_dma_in_callback(void)
  9. {
  10.     hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta = 0;          /* 此buf已经处理完了 */
  11.     hjpgd.inbuf[hjpgd.inbuf_read_ptr].size = 0;         /* 此buf已经处理完了 */
  12.     hjpgd.inbuf_read_ptr++;                               /* 指向下一个buf */
  13. /* 归零 */
  14.     if (hjpgd.inbuf_read_ptr >= JPEG_DMA_INBUF_NB) hjpgd.inbuf_read_ptr = 0;

  15.     if (hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta == 0)   /* 无有效buf */
  16.     {
  17.         hjpgd.indma_pause = 1;  /* 标记暂停 */
  18.     }
  19.     else                        /* 有效的buf */
  20.     {
  21.         /* 继续下一次DMA传输 */
  22.         jpeg_in_dma_resume((uint32_t)hjpgd.inbuf[hjpgd.inbuf_read_ptr].buf,
  23. hjpgd.inbuf[hjpgd.inbuf_read_ptr].size);
  24.     }
  25. }
复制代码
该函数用于处理JPEG输入数据流,当JPEG输入DMA传输完成时,调用该函数。对已处理的buf标记清零,然后切换到下一个buf。当buf不够时,暂停JPEG输入FIFO获取数据,并标记暂停;当buf足够时,切换到下一个buf,继续传输。
下面介绍的是JPEG输出数据流(YCBCR)回调函数,其定义如下:

  1. /**
  2. * @brief       JPEG输出数据流(YCBCR)回调函数
  3. * @note        用于输出YCbCr数据流(YUV)
  4. * @param       无
  5. * @retval      无
  6. */
  7. void jpeg_dma_out_callback(void)
  8. {
  9.     uint32_t *pdata = 0;
  10.     hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta = 1;       /* 此buf已满 */
  11. hjpgd.outbuf[hjpgd.outbuf_write_ptr].size = JPEG_DMA_OUTBUF_LEN –
  12.      (DMA2_Stream1->NDTR << 2);             /* 此buf里面数据的长度 */

  13. if (hjpgd.state == JPEG_STATE_FINISHED)
  14. /* 如果文件已经解码完成,需要读取DOR最后的数据(<=32字节) */
  15.     {
  16.         pdata = (uint32_t *)(hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf +
  17.                                   hjpgd.outbuf[hjpgd.outbuf_write_ptr].size);

  18.         while (JPEG->SR & (1 << 4))
  19.         {
  20.             *pdata = JPEG->DOR;
  21.             pdata++;
  22.             hjpgd.outbuf[hjpgd.outbuf_write_ptr].size += 4;
  23.         }
  24.     }

  25.     hjpgd.outbuf_write_ptr++;   /* 指向下一个buf */

  26. if (hjpgd.outbuf_write_ptr >= JPEG_DMA_OUTBUF_NB)hjpgd.outbuf_write_ptr=0;
  27. /* 归零 */

  28.     if (hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta == 1)  /* 无有效buf */
  29.     {
  30.         jpeg_out_dma_pause();   /* 暂停输出数据 */
  31.         hjpgd.outdma_pause = 1; /* 标记暂停 */
  32.     }
  33.     else                        /* 有效的buf */
  34.     {
  35.         /* 继续下一次DMA传输 */
  36.         jpeg_out_dma_resume((uint32_t)hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf,
  37.                       JPEG_DMA_OUTBUF_LEN);

  38.     }
  39. }
复制代码

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

  1. /**
  2. * @brief       JPEG整个文件解码完成回调函数
  3. * @param       无
  4. * @retval      无
  5. */
  6. void jpeg_endofcovert_callback(void)
  7. {
  8.     hjpgd.state = JPEG_STATE_FINISHED;  /* 标记JPEG解码完成 */
  9. }
复制代码
该函数在JPG/JPEG文件解码结束时调用。该函数处理非常简单,直接将当前解码状态标记为:JPEG解码完成(JPEG_STATE_FINISHED)即可。
下面介绍的是JPEG header解析成功回调函数,其定义如下:

  1. /**
  2. * @brief       JPEG header解析成功回调函数
  3. * @param       无
  4. * @retval      无
  5. */
  6. void jpeg_hdrover_callback(void)
  7. {
  8.     uint8_t i = 0;
  9.     hjpgd.state = JPEG_STATE_HEADEROK;  /* HEADER获取成功 */
  10.     jpeg_get_info(&hjpgd);        /* 获取JPEG相关信息,包括大小,色彩空间,抽样等 */
  11.     picinfo.ImgWidth = hjpgd.Conf.ImageWidth;
  12.     picinfo.ImgHeight = hjpgd.Conf.ImageHeight;

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

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

  27.         case JPEG_444_SUBSAMPLING:
  28.             hjpgd.yuvblk_size = 24 * hjpgd.Conf.ImageWidth;
  29. /* YUV444,每个YUV像素占3个字节.每次输出8行.8*3=24 */
  30.             hjpgd.yuvblk_height = 8;    /* 每次输出8行 */
  31.             break;
  32.     }

  33.     hjpgd.yuvblk_curheight = 0; /* 当前行计数器清零 */

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

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

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

  49.     piclib_ai_draw_init();
  50. }
复制代码
该函数在JPEG头解码成功后调用。该函数先标记状态为JPEG头解码成功(JPEG_STATE_HEADEROK),然后调用jpeg_get_info函数获取JPEG相关信息。在得到JPEG文件的抽样方式、图库宽度等信息后,然后初始化画图。
下面介绍的是JPEG硬件解码图片函数,其定义如下:

  1. /**
  2. * @brief       JPEG硬件解码图片
  3. * @note        注意, 请保证:
  4. *              1, 待解码图片的分辨率,必须小于等于屏幕的分辨率!
  5. *              2, 请保证图片的宽度是16的倍数,否则解码出错!
  6. *
  7. * @param       filename : 包含路径的文件名(.jpeg/jpg)
  8. * @retval      操作结果
  9. *   @arg       0   , 成功
  10. *   @arg       其他, 错误码
  11. */
  12. uint8_t hjpgd_decode(char *filename)
  13. {
  14.     FIL *ftemp;
  15.     uint16_t *rgb565buf = 0;
  16.     volatile  uint32_t timecnt = 0;
  17.     uint32_t br = 0;
  18.     uint8_t fileover = 0;
  19.     uint8_t i = 0;
  20.     uint8_t res;
  21.     res = jpeg_core_init(&hjpgd);                   /* 初始化JPEG内核 */

  22.     if (res)return 1;

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

  24.     if (f_open(ftemp, filename, FA_READ) != FR_OK)  /* 打开图片失败 */
  25.     {
  26.         jpeg_core_destroy(&hjpgd);
  27.         myfree(SRAMITCM, ftemp);                    /* 释放内存 */
  28.         return 2;
  29.     }

  30. rgb565buf = mymalloc(SRAMEX, lcddev.width * lcddev.height * 2);
  31. /* 申请整帧内存 */
  32.     jpeg_decode_init(&hjpgd);       /* 初始化硬件JPEG解码器 */

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

  37.         if (res == FR_OK && br)
  38.         {
  39.             hjpgd.inbuf[i].size = br;   /* 读取 */
  40.             hjpgd.inbuf[i].sta = 1;     /* 标记buf满 */
  41.         }

  42.         if (br == 0)break;
  43.     }

  44. jpeg_in_dma_init((uint32_t)hjpgd.inbuf[0].buf, hjpgd.inbuf[0].size);
  45. /* 配置输入DMA */
  46.     jpeg_in_callback = jpeg_dma_in_callback;    /* JPEG DMA读取数据回调函数 */
  47.     jpeg_out_callback = jpeg_dma_out_callback;  /* JPEG DMA输出数据回调函数 */
  48.     jpeg_eoc_callback = jpeg_endofcovert_callback; /* JPEG 解码结束回调函数 */
  49.     jpeg_hdp_callback = jpeg_hdrover_callback; /* JPEG Header解码完成回调函数 */
  50.     jpeg_in_dma_start();    /* 启动DMA IN传输,开始解码JPEG图片 */

  51.     while (1)
  52.     {
  53.         if (hjpgd.inbuf[hjpgd.inbuf_write_ptr].sta == 0 && fileover == 0)   
  54. /* 有buf为空 */
  55.         {
  56.             res = f_read(ftemp, hjpgd.inbuf[hjpgd.inbuf_write_ptr].buf,
  57. JPEG_DMA_INBUF_LEN, &br);   /* 填满一个缓冲区 */

  58.             if (res == FR_OK && br)
  59.             {
  60.                 hjpgd.inbuf[hjpgd.inbuf_write_ptr].size = br;   /* 读取 */
  61.                 hjpgd.inbuf[hjpgd.inbuf_write_ptr].sta = 1;     /* buf满 */
  62.             }
  63.             else if (br == 0)
  64.             {
  65.                 timecnt = 0;    /* 清零计时器 */
  66.                 fileover = 1;   /* 文件结束了 */
  67.             }

  68.             if (hjpgd.indma_pause == 1 && hjpgd.inbuf[hjpgd.inbuf_read_ptr].sta
  69. == 1)   /* 之前是暂停的了,继续传输 */
  70.             {
  71.                jpeg_in_dma_resume((uint32_t)hjpgd.inbuf[hjpgd.inbuf_read_ptr].
  72. buf,hjpgd.inbuf[hjpgd.inbuf_read_ptr].size);
  73. /* 继续下一次DMA传输 */
  74.                 hjpgd.indma_pause = 0;
  75.             }

  76.             hjpgd.inbuf_write_ptr++;

  77.             if (hjpgd.inbuf_write_ptr >=JPEG_DMA_INBUF_NB)hjpgd.inbuf_write_ptr
  78. = 0;
  79.         }

  80.         if (hjpgd.outbuf[hjpgd.outbuf_read_ptr].sta == 1)
  81. /* buf里面有数据要处理 */
  82.         {
  83.             SCB_CleanInvalidateDCache();    /* 清空D catch */
  84.             jpeg_dma2d_yuv2rgb_conversion(&hjpgd, (uint32_t *)rgb565buf);   

  85. /* 利用DMA2D,将YUV图像转成RGB565图像 */

  86.             //SCB_CleanInvalidateDCache();                  /* 清空D catch */
  87.             hjpgd.outbuf[hjpgd.outbuf_read_ptr].sta = 0;    /* 标记buf为空 */
  88.             hjpgd.outbuf[hjpgd.outbuf_read_ptr].size = 0;   /* 数据量清空 */
  89.             hjpgd.outbuf_read_ptr++;
  90.             if (hjpgd.outbuf_read_ptr >=
  91. JPEG_DMA_OUTBUF_NB)hjpgd.outbuf_read_ptr = 0;    /* 限制范围 */

  92.             if (hjpgd.yuvblk_curheight >= hjpgd.Conf.ImageHeight)break;
  93. /* 当前高度等于或者超过图片分辨率的高度,则说明解码完成了,直接退出 */
  94.         }
  95.         else if (hjpgd.outdma_pause == 1 &&
  96. hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta == 0)  
  97. /* out暂停,且当前writebuf已经为空了,则恢复out输出 */
  98.         {
  99.                    jpeg_out_dma_resume((uint32_t)hjpgd.outbuf[hjpgd.outbuf_write_ptr].
  100. buf, hjpgd.yuvblk_size); /* 继续下一次DMA传输 */
  101.             hjpgd.outdma_pause = 0;
  102.         }

  103.         timecnt++;

  104.         if (hjpgd.state == JPEG_STATE_ERROR)    /* 解码出错,直接退出 */
  105.         {
  106.             res = 2;
  107.             break;
  108.         }

  109.         if (fileover)   /* 文件结束后,及时退出,防止死循环 */
  110.         {
  111.             if (hjpgd.state == JPEG_STATE_NOHEADER && hjpgd.indma_pause == 1)
  112.   /* 当前处于暂停状态,且没有解析到JPEG头 */
  113.             {
  114.                 break;  /* 解码JPEG头失败了 */
  115.             }

  116.             if (timecnt > 0X3FFFF)break;    /* 超时退出 */
  117.         }
  118.     }

  119.     if (hjpgd.state == JPEG_STATE_FINISHED) /* 解码完成了, 一次性输出到LCD */
  120.     {
  121.         pic_phy.fillcolor(picinfo.S_XOFF, picinfo.S_YOFF,
  122. hjpgd.Conf.ImageWidth, hjpgd.Conf.ImageHeight, rgb565buf);
  123.     }

  124.     f_close(ftemp);             /* 关闭文件 */
  125.     myfree(SRAMITCM, ftemp);    /* 释放申请的内存 */
  126.     myfree(SRAMEX, rgb565buf);  /* 释放内存 */
  127.     jpeg_core_destroy(&hjpgd);  /* 结束JPEG解码,释放内存 */
  128.     return res;
  129. }
复制代码
该函数用于解码一张JPG/JPEG图片。该函数采用的思路就是按照我们在57.3.1节介绍的步骤来解码JPG/JPEG图片。请大家参考前面的介绍和源码进行理解。
另外,我们需要将hjpgd_decode函数加入到图片解码库里面,修改piclib_ai_load_picfile函数代码,具体如下:

  1. /**
  2. * @brief       智能画图
  3. * @note        图片仅在x,y和width, height限定的区域内显示.
  4. *
  5. * @param       filename      : 包含路径的文件名(.bmp/.jpg/.jpeg/.gif等)
  6. * @param       x, y          : 起始坐标
  7. * @param       width, height : 显示区域
  8. * @param       fast          : 使能快速解码
  9. *   @arg                       0, 不使能
  10. *   @arg                       1, 使能
  11. *   @note                      图片尺寸小于等于液晶分辨率,才支持快速解码
  12. * @retval      无
  13. */
  14. uint8_t piclib_ai_load_picfile(char *filename, uint16_t x, uint16_t y, uint16_t
  15. width, uint16_t height, uint8_t fast)
  16. {
  17.     uint8_t res;                                             /* 返回值 */
  18.     uint8_t temp;

  19.     if ((x + width) > picinfo.lcdwidth)return PIC_WINDOW_ERR;/* x坐标超范围了 */

  20.     if ((y + height)>picinfo.lcdheight)return PIC_WINDOW_ERR;/* y坐标超范围了 */

  21.     /* 得到显示方框大小 */
  22.     if (width == 0 || height == 0)return PIC_WINDOW_ERR;    /* 窗口设定错误 */

  23.     picinfo.S_Height = height;
  24.     picinfo.S_Width = width;

  25.     /* 显示区域无效 */
  26.     if (picinfo.S_Height == 0 || picinfo.S_Width == 0)
  27.     {
  28.         picinfo.S_Height = lcddev.height;
  29.         picinfo.S_Width = lcddev.width;
  30.         return FALSE;
  31.     }

  32.     if (pic_phy.fillcolor == NULL)fast = 0; /* 颜色填充函数未实现,不能快速显示 */

  33.     /* 显示的开始坐标点 */
  34.     picinfo.S_YOFF = y;
  35.     picinfo.S_XOFF = x;

  36.     /* 文件名传递 */
  37.     temp = exfuns_file_type(filename);                /* 得到文件的类型 */

  38.     switch (temp)
  39.     {
  40.         case T_BMP:
  41.             res = stdbmp_decode(filename);            /* 解码bmp */
  42.             break;

  43.         case T_JPG:
  44.         case T_JPEG:
  45.             if (fast)   /* 可能需要硬件解码 */
  46.             {
  47.                 res = jpg_get_size((const uint8_t *)filename,
  48. &picinfo.ImgWidth, &picinfo.ImgHeight);

  49.                 if (res == 0)
  50.                 {
  51.                     if (picinfo.ImgWidth <= lcddev.width && picinfo.ImgHeight
  52.   <= lcddev.height &&picinfo.ImgWidth <= picinfo.S_Width &&
  53. picinfo.ImgHeight <= picinfo.S_Height &&  (picinfo.ImgWidth
  54. % 16) == 0)    /* 则可以硬件解码 */
  55.                     {
  56.                         res = hjpgd_decode(filename); /* 采用硬解码JPG/JPEG */
  57.                     }
  58.                     else
  59.                     {
  60.                         res=jpg_decode(filename, fast);/* 采用软件解码JPG/JPEG */
  61.                     }
  62.                 }
  63.             }
  64.             else
  65.             {
  66.                 res = jpg_decode(filename, fast);/* 统一采用软件解码JPG/JPEG */
  67.             }
  68.             break;

  69.         case T_GIF:
  70.             res = gif_decode(filename, x, y, width, height); /* 解码gif */
  71.             break;

  72.         default:
  73.             res = PIC_FORMAT_ERR;                         /* 非图片格式!!! */
  74.             break;
  75.     }

  76.     return res;
  77. }
复制代码
启用快速解码时,当JPG/JPEG图片尺寸满足小于等于屏幕分辨率,且图片宽度是16的倍数,则我们会通过调用hjpgd_decode函数实现硬件JPEG解码,从而大大提高速度。
PICTURE驱动代码就介绍到这里。

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

  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4.     uint8_t key;
  5.     uint8_t res;
  6.     DIR picdir;
  7.     uint16_t totpicnum;
  8.     FILINFO *picfileinfo;
  9.     char *pname;
  10.     uint32_t *picoffsettbl;
  11.     uint16_t curindex;
  12.     uint16_t temp;
  13.    
  14.     sys_mpu_config();                   /* 配置MPU */
  15.     sys_cache_enable();                 /* 使能Cache */
  16.     HAL_Init();                         /* 初始化HAL库 */
  17.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  18.     delay_init(600);                    /* 初始化延时 */
  19.     usart_init(115200);                 /* 初始化串口 */
  20.     led_init();                         /* 初始化LED */
  21.     key_init();                         /* 初始化按键 */
  22.     hyperram_init();                    /* 初始化HyperRAM */
  23.     lcd_init();                         /* 初始化LCD */
  24.     my_mem_init(SRAMIN);                /* 初始化AXI-SRAM1~4内存池 */
  25.     my_mem_init(SRAMEX);                /* 初始化XSPI2 HyperRAM内存池 */
  26.     my_mem_init(SRAM12);                /* 初始化AHB-SRAM1~2内存池 */
  27.     my_mem_init(SRAMDTCM);              /* 初始化DTCM内存池 */
  28.     my_mem_init(SRAMITCM);              /* 初始化ITCM内存池 */
  29.     exfuns_init();                      /* 为exfuns申请内存 */
  30.     f_mount(fs[0], "0:", 1);            /* 挂载SD卡 */
  31.     f_mount(fs[1], "1:", 1);            /* 挂载NOR Flash */
  32.     f_mount(fs[2], "2:", 1);            /* 挂载NAND Flash */
  33.    
  34.     /* 检查字库 */
  35.     while (fonts_init() != 0)
  36.     {
  37.         lcd_show_string(30, 30, 200, 16, 16, "STM32", RED);
  38.         
  39.         /* 初始化SD卡 */
  40.         while (sd_init() != 0)
  41.         {
  42.             lcd_show_string(30, 30, 200, 16, 16, "SD Card Error!", RED);
  43.             delay_ms(500);
  44.             lcd_show_string(30, 30, 200, 16, 16, "Please Check! ", RED);
  45.             delay_ms(500);
  46.             LED0_TOGGLE();
  47.         }
  48.         
  49.         lcd_show_string(30, 50, 200, 16, 16, "SD Card OK", RED);
  50.         lcd_show_string(30, 70, 200, 16, 16, "Font Updating...", RED);
  51.         
  52.         /* 更新字库 */
  53.         res = fonts_update_font(30, 90, 16, (uint8_t *)"0:", RED);
  54.         while (res != 0)
  55.         {
  56.             lcd_show_string(30, 90, 200, 16, 16, "Font Update Failed!", RED);
  57.             delay_ms(200);
  58.             lcd_show_string(30, 90, 200, 16, 16, "Please Check!      ", RED);
  59.             delay_ms(200);
  60.         }
  61.         
  62.         lcd_show_string(30, 90, 200, 16, 16, "Font Update Success!   ", RED);
  63.         delay_ms(1500);
  64.         lcd_clear(WHITE);
  65.     }
  66.    
  67.     text_show_string(30, 30, 200, 16, "正点原子STM32开发板", 16, 0, RED);
  68.     text_show_string(30, 50, 200, 16, "图片显示实验", 16, 0, RED);
  69.     text_show_string(30, 70, 200, 16, "ATOM@ALIENTEK", 16, 0, RED);
  70.     text_show_string(30, 90, 200, 16, "WKUP: PREV", 16, 0, RED);
  71.     text_show_string(30, 110, 200, 16, "KEY0: NEXT", 16, 0, RED);
  72.    
  73.     /* 打开图片文件夹 */
  74.     while (f_opendir(&picdir, "0:/PICTURE") != FR_OK)
  75.     {
  76.         text_show_string(30, 130, 200, 16, "PICTURE文件夹错误!", 16, 0, RED);
  77.         delay_ms(200);
  78.         lcd_fill(30, 130, 200, 16, WHITE);
  79.         delay_ms(200);
  80.     }
  81.    
  82.     /* 获取有效图片文件数量 */
  83.     totpicnum = pic_get_tnum("0:/PICTURE");
  84.     while (totpicnum == 0)
  85.     {
  86.         text_show_string(30, 130, 200, 16, "没有图片文件!", 16, 0, RED);
  87.         delay_ms(200);
  88.         lcd_fill(30, 130, 200, 16, WHITE);
  89.         delay_ms(200);
  90.     }
  91.    
  92.     /* 申请内存 */
  93.     picfileinfo = (FILINFO *)mymalloc(SRAMIN, sizeof(FILINFO));
  94.     pname = (char *)mymalloc(SRAMIN, FF_MAX_LFN * 2 + 1);
  95.     picoffsettbl = (uint32_t *)mymalloc(SRAMIN, 4 * totpicnum);
  96.     while ((picfileinfo == NULL) || (pname == NULL) || (picoffsettbl == NULL))
  97.     {
  98.         text_show_string(30, 130, 200, 16, "内存分配失败!", 16, 0, RED);
  99.         delay_ms(200);
  100.         lcd_fill(30, 130, 200, 16, WHITE);
  101.         delay_ms(200);
  102.     }
  103.    
  104.     /* 打开目录并记录图片索引 */
  105.     res = (uint8_t)f_opendir(&picdir, "0:/PICTURE");
  106.     if (res == 0)
  107.     {
  108.         curindex = 0;
  109.         while (1)
  110.         {
  111.             temp = picdir.dptr;
  112.             res = (uint8_t)f_readdir(&picdir, picfileinfo);
  113.             if ((res != 0) || (picfileinfo->fname[0] == 0))
  114.             {
  115.                 break;
  116.             }
  117.             
  118.             res = exfuns_file_type(picfileinfo->fname);
  119.             if ((res & 0xF0) == 0x50)
  120.             {
  121.                 picoffsettbl[curindex] = temp;
  122.                 curindex++;
  123.             }
  124.         }
  125.     }
  126.    
  127.     /* 开始显示图片 */
  128.     text_show_string(30, 130, 200, 16, "开始显示...", 16, 0, RED);
  129.     delay_ms(1500);
  130.     piclib_init();
  131.     curindex = 0;
  132.     res = (uint8_t)f_opendir(&picdir, (const TCHAR *)"0:/PICTURE");
  133.     while (res == 0)
  134.     {
  135.         /* 获取下一个图片文件信息 */
  136.         dir_sdi(&picdir, picoffsettbl[curindex]);
  137.         res = (uint8_t)f_readdir(&picdir, picfileinfo);
  138.         if ((res != 0) || (picfileinfo->fname[0] == 0))
  139.         {
  140.             break;
  141.         }
  142.         
  143.         /* 根据图片路径显示图片 */
  144.         strcpy((char *)pname, "0:/PICTURE/");
  145.         strcat((char *)pname, (const char *)picfileinfo->fname);
  146.         lcd_clear(BLACK);
  147.         piclib_ai_load_picfile(pname, 0, 0, lcddev.width, lcddev.height, 1);
  148.         text_show_string(2, 2, lcddev.width, 16, (char *)pname, 16, 1, RED);
  149.         
  150.         while (1)
  151.         {
  152.             key = key_scan(0);
  153.             if (key == KEY0_PRES)
  154.             {
  155.                 /* 切换上一张 */
  156.                 if (curindex != 0)
  157.                 {
  158.                     curindex--;
  159.                 }
  160.                 else
  161.                 {
  162.                     curindex = totpicnum - 1;
  163.                 }
  164.                 break;
  165.             }
  166.             else if (key == WKUP_PRES)
  167.             {
  168.                 /* 切换下一张 */
  169.                 curindex++;
  170.                 if (curindex >= totpicnum)
  171.                 {
  172.                     curindex = 0;
  173.                 }
  174.                 break;
  175.             }
  176.             
  177.             if (++t == 20)
  178.             {
  179.                 t = 0;
  180.                 LED0_TOGGLE();
  181.             }
  182.             
  183.             delay_ms(10);
  184.         }
  185.     }
  186.    
  187.     /* 释放内存 */
  188.     myfree(SRAMIN, picfileinfo);
  189.     myfree(SRAMIN, pname);
  190.     myfree(SRAMIN, picoffsettbl);
  191.    
  192.     while (1);
  193. }
复制代码
main函数里面我们通过读/写偏移量(图片文件在PICTURE文件夹下的读/写偏移位置,可以看做是一个索引),来查找上一个/下一个图片文件(使用dir_sdi函数)。通过piclib_ai_load_picfile函数,实现对JPG/JPEG图片的解码。这里将fast参数设置为1,当图片文件的分辨率小于等于液晶分辨率的时候,将使用硬件JPEG进行解码。

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

第五十七章 硬件JPEG解码实验47497.png
图57.4.1 硬件JPEG解码实验显示效果

按KEY0和WKUP可以快速切换到上一张或下一张。对比上一章实验,我们可以发现,对于小尺寸的JPG/JPEG图片(小于液晶分辨率),本例程解码速度明显提升。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-6-6 10:20

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表