完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第五十章 硬件 JPEG 解码实验 上一章,我们学习了图片解码,学会了使用软件解码显示 bmp/jpg/jpeg/gif 等格式的图片, 但是软件解码速度都比较慢,本章我们将学习如何使用STM32F767自带的硬件JPEG编解码器, 实现对 JPG/JPEG 图片的硬解码,从而大大提高解码速度。本章分为如下几个部分: 50.1 硬件 JPEG 编解码器简介 50.2 硬件设计 50.3 软件设计 50.4 下载验证 50.1 硬件 JPEG 编解码器简介 STM32F767 自带了硬件 JPEG 编解码器,可以实现快速 JPG/JPEG 编解码,本章我们仅使 用 JPG/JPEG 解码器。STM32F7 的 JPEG 编解码器具有如下特点: 支持 JPEG 编码/解码 支持 24 位颜色深度(即 RGB888) 单周期解码/编码一个像素 支持 JPEG 头数据编解码 多达 4 个可编程量化表 完全可编程的哈弗曼表(AC 和 DC 各 2 个) 完全可编程的最小编码单元(MCU) 单周期哈弗曼编码/解码 STM32F7 的 JPEG 编解码器框图如图 50.1.1 所示: 图 50.1.1 STM32F7 硬件 JPEG 编解码器框图 图 50.1.1 为 STM32F7 的硬件 JPEG 编解码器框图,我们只需要对相关寄存器进行设置,然 后读写输入/输出 FIFO,即可完成 JPEG 的编解码。本章,我们只介绍如何利用 STM32F7 的硬 件 JPEG 解码器实现对 JPG/JPEG 图片的解码。 硬件 JPEG 解码器,支持解码符合 ISO/IEC10918-1 协议规范的 JPEG 数据流,并且支持解 码 JPEG 头(可配置),通过输入 FIFO 读取需要解码的 JPEG 数据,通过输出 FIFO 将解码完成 的 YUV 数据传输给外部。 注意:硬件 JPEG 解码器解码完成后是 YUV 格式的数据,并不是 RGB 格式的数据,所以 不能直接显示到 LCD 上面,必须经过 YUVRGB 的转换,才可以显示在 LCD 上面。 硬件 JPEG 解码时 FIFO 数据的处理(读取/写入)有两种方式:1,中断方式;2,DMA 方 式。为了达到最快的解码速度,我们一般使用 DMA 来处理 FIFO 数据。接下来,我们介绍一下 硬件 JPEG 解码的数据处理过程。 输入 FIFO DMA 通过设置 JPEG_CR 寄存器的 IDMAEN 位为 1,可以使能 JPEG 输入 FIFO 的 DMA,当输 入 FIFO(总容量为 32 字节)至少半空的时候,将产生一个 DMA 请求,读取 16 字节数据到输 入 FIFO。通过设置 IDMAEN 位为 0,可以暂停 FIFO 获取数据,这个操作在 DMA 传输完成, 读取下一批 JPEG 数据的时候经常用到。 注意:在当前图片解码完成后,开启下一张图片解码之前,需要对输入 FIFO 进行一次清 空(设置 JPEG_CR 寄存器的 IFF 位),否则上一张图片的数据会影响到下一张图片的解码。 输出 FIFO DMA 通过设置 JPEG_CR 寄存器的 ODMAEN 位为 1,可以使能 JPEG 输出 FIFO 的 DMA,当输 出 FIFO(总容量为 32 字节)至少半满的时候,将产生一个 DMA 请求,可以从输出 FIFO 读取 16 字节数据。通过设置 ODMAEN 位为 0,可以暂停 FIFO 输出数据,这个操作在 DMA 传输完 成,执行 YUVRGB 转换的时候经常用到。 注意:当图片解码结束以后,输出 FIFO 里面可能还有数据,此时我们需要手动读取 FIFO 里面的数据,直到 JPEG_SR 寄存器的 OFNEF 位为 0。 JPEG 头解码 通过设置JPEG_CONFR1 寄存器的HDR 位为 1,可以使能JPEG头解码,通过设置 JPEG_CR 寄存器 HPDIE 位为 1,可以使能 JPEG 头解码完成中断。在完成 JPEG 头解码之后,我们可以 获取当前 JPEG 图片的很多参数,包括:颜色空间、色度抽样、高度、宽度和 MCU 总数等信 息。这些参数对我们后面的解码和颜色转换(YUVRGB)非常重要。 硬件 JPEG 使用 DMA 实现 JPG/JPEG 图片解码的数据处理流程如图 50.1.2 所示: 图 50.1.2 硬件 JPEG 解码数据处理流程(DMA 方式) 由图可知,数据处理主要由 2 个 DMA 完成:输入 DMA 和输出 DMA,分别处理硬件 JPEG 的输入 FIFO 和输出 FIFO 的数据。通过适当控制输入 FIFO/输出 FIFO 的暂停和重启,从而控 制整个数据处理的进程,暂停 FIFO 的时间越少,解码速度就越快。 图中我们还用到了 2 个 JPEG 中断:JPEG 头解析完成中断和 JPEG 解码完成中断,他们共 用一个中断服务函数。JPEG 头解析完成中断,在 JPEG 头解码完成后进入,此时我们可以获取 JPG/JPEG 图片的很多重要信息,方便后续解码。JPEG 解码完成中断,在 JPG/JPEG 图片解码 完成后进入,标志着整张图片解码完成。 接下来,我们介绍本章需要用到的一些寄存器。 首先是 JPEG 内核控制寄存器:JPEG_CONFR0,该寄存器仅最低位(START 位)有效, 设置该位为 1,可以启动 JPEG 解码流程。通过设置该位为 0,可以退出当前 JPEG 解码。 接下来,我们看 JPEG 配置寄存器 1:JPEG_CONFR1,该寄存器各位描述如图 50.1.3 所示: 图 50.1.3 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 配置寄存器 3:JPEG_CONFR3,该寄存器各位描述如图 50.1.4 所示: 图 50.1.4 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,该寄存器各位描述如图 50.1.5 所示: 图 50.1.5 JPEG_CR 寄存器各位描述 OFF 位,用于清空输出 FIFO,在启动新图片解码之前,需要对输出 FIFO 进行清空。 IFF 位,用于清空输入 FIFO,在启动新图片解码之前,需要对输入 FIFO 进行清空。 ODMAEN 位,用于使能输出 FIFO 的 DMA,我们设置此位为 1。 IDMAEN 位,用于使能输入 FIFO 的 DMA,我们设置此位为 1。 HPDIE 位,用于使能 JPEG 头解码完成中断,我们设置为 1,使能 JPEG 头解码完成中断, 在中断服务函数里面读取 JPEG 的相关信息(长宽、颜色空间、色度抽样等),并根据色度抽样 方式,获取对应的 YUVRGB 转换函数。 EOCIE 位,用于使能 JPEG 解码完成中断,我们设置为 1,使能 JPEG 解码完成中断,在中 断服务函数里面标记 JPEG 解码完成,以便结束 JPEG 解码流程。 JCEN 位,用于使能硬件 JPEG 内核,我们必须设置此位为 1,以启动硬件 JPEG 内核。 接下来,我们看 JPEG 状态寄存器:JPEG_SR,该寄存器各位描述如图 50.1.6 所示: 图 50.1.6 JPEG_SR 寄存器各位描述 HPDF 位,表示 JPEG 头解码完成的标志,当该位为 1 时,表示 JPEG 头解析成功,我们可 以读取相关寄存器,获取 JPEG 图片的长宽、颜色空间和色度抽样等重要信息。向 JPEG_FCR 寄存器的 CHPDF 位写 1,可以清零此位。 EOCF 位,表示 JPEG 解码完成的标志,当该位为 1 时,表示一张 JPEG 图像解码完成。此 时我们可以从输出 FIFO 读取最后的数据。向 JPEG_FCR 寄存器的 CEOCF 位写 1,可以清零此 位。 接下来,我们看 JPEG 标志清零寄存器:JPEG_FCR,该寄存器各位描述如图 50.1.7 所示: 图 50.1.7 JPEG_FCR 寄存器各位描述 该寄存器,仅两位有效:CHPDF 位和 CEOCF 位,向这两个位写入 1,可以分别清除 JPEG_SR 寄存器的 HPDF 和 EOCF 位。 最后,还有 JPEG 数据输入寄存器(JPEG_DIR)和 JPEG 数据输出寄存器(JPEG_DOR), 这两个寄存器都是 32 位有效,前者用于往输入 FIFO 写入数据。后者用于读取输出 FIFO 的数 据。 至此,本实验所需要用到的相关寄存器,就全部介绍完了,更详细的介绍,请参考 《STM32F7xx 参考手册》 21.5 节。 接下来,我们看看在 DMA 模式下,使用 STM32F7 的硬件 JPEG 解码 JPG/JPEG 的简要步 骤,HAL 库中硬件 JPEG 解码函数分布在 stm32f7xx_hal_jpeg.c 和头文件 stm32f7xx_hal_jpeg.h 中。 1)初始化硬件 JPEG 内核。 首先,我们通过设置 AHB2ENR 的 bit1 位为 1,使能硬件 JPEG 内核时钟,然后通过 JPEG_CR 寄存器的 JCEN 位,使能硬件 JPEG。通过清零 JPEG_CONFR0 寄存器的 START 位,停止 JPEG 编解码进程。通过设置 JPEG_CONFR1 寄存器的 HDR 位,使能 JPEG 头解码。最后设置 JPEG 中断服务函数的中断优先级,完成初始化硬件 JPEG 内核过程。 在 HAL 库中,初始化 JPEG 是通过函数 HAL_JPEG_Init 来实现的,该函数声明如下: HAL_StatusTypeDef HAL_JPEG_Init(JPEG_HandleTypeDef *hjpeg); 该函数的使用方法大家可以参考我们实验源码即可。 JPEG 时钟使能方法: __HAL_RCC_JPEG_CLK_ENABLE(); //使能 JPEG 时钟 和其他外设一样,HAL 库也提供了硬件 JPEG 初始化回调函数,声明如下: void HAL_JPEG_MspInit(JPEG_HandleTypeDef *hjpeg); 一般情况下,时钟使能,中断优先级设置都放在回调函数中。 2)初始化硬件 JPEG 解码。 在初始化硬件 JPEG 内核以后,我们配置 JPEG 内核工作在 JPEG 解码模式。通过设置 JPEG_CONFR1 寄存器的 DE 位,使能 JPEG 解码模式。然后设置 JPEG_CR 寄存器的 OFF、IFF、 HPDIE、EOCIE 等位,清空输出/输入 FIFO,并开启 JPEG 头解码完成和 JPEG 解码完成中断。 最后,设置 JPEG_CONFR0 寄存器的 START 位,启动 JPEG 解码进程。操作过程如下: JPEG->CONFR1|=JPEG_CONFR1_DE; //使能硬件 JPEG 解码模式 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_HPD);//使能 Header 解码完中断 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_EOC);//使能解码完成中断 JPEG->CONFR0|=JPEG_CONFR0_START; //使能 JPEG 编解码进程 注意:此时我并未开启 JPEG 的输入和输出 DMA,只要我们不往输入 FIFO 写入数据,JPEG 内核就一直处于等待数据输入状态。 3)配置硬件 JPEG 输入输出 DMA。 这一步,我们将配置 JPEG 的输入 DMA 和输出 DMA,分别负责 JPEG 输入 FIFO 和输出 FIFO 的数据传输。对于输入 DMA,目标地址为 JPEG_DIR 寄存器地址,源地址为一片内存区 域,利用输入 DMA 实现 JPEG 输入 FIFO 数据的自动填充。对于输出 DMA,目标地址为一片 内存区域,源地址为 JPEG_DOR 寄存器地址,利用输出 DMA 实现 JPEG 输出 FIFO 数据自动 搬运到对应内存区域。对于输入 DMA 和输出 DMA,我们都需要开启传输完成中断,并设置相 关中断服务函数。在传输完成中断里面,实现对输入输出数据的处理。 对于 JPEG 输入输出 DMA 配置,我们主要调用 HAL 库函数 HAL_DMA_Init 处理即可,具 体的配置方法请参考 50.3 小节实验源码讲解。 HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma); 4)编写相关中断服务函数,启动 DMA。 我们总共开启了 4 个中断:JPEG 头解码完成中断、JPEG 解码完成中断、输入 DMA 传输 完成中断和输出 DMA 传输完成中断。前两个中断共用一个中断服务函数,所以我们总共需要 编写 3 个中断服务函数。另外,我们采用回调函数的方式,对数据进行处理,总共需要编写 4 个回调函数,分别对应 4 个中断产生时的数据处理。在配置完这些以后,启动 DMA,并通过 设置 JPEG_CR 寄存器的 IDMAEN 和 ODMAEN 位,开启 JPEG 的输入和输出 FIFO DMA 请求, 开始执行 JPEG 解码。 5)处理 JPEG 数据输出数据,执行 YUVRGB 转换,并送 LCD 显示。 最后,在主循环里面,根据输入 DMA 和输出 DMA 的数据处理情况,持续从源文件读取 JPEG 数据流,并将硬件 JPEG 解码完成的 YUV 数据流转换成 RGB 格式。最后,在完成一张 JPEG 解码之后,将 RGB 数据直接一次性显示到 LCD 屏幕上,实现图片显示。 50.2 硬件设计 本章实验功能简介:本实验开机的时候先检测字库,然后检测 SD 卡是否存在,如果 SD 卡存在,则开始查找 SD 卡根目录下的 PICTURE 文件夹,如果找到则显示该文件夹下面的图 片文件(支持 bmp、jpg、jpeg 或 gif 格式),循环显示,通过按 KEY0 和 KEY2 可以快速浏览 下一张和上一张,KEY_UP 按键用于暂停/继续播放,DS1 用于指示当前是否处于暂停状态。 如果未找到 PICTURE 文件夹/任何图片文件,则提示错误。同样我们也是用 DS0 来指示程序 正在运行。 本实验也可以通过 USMART 调用 ai_load_picfile 和 minibmp_decode 解码任意指定路径 的图片。 注意:本例程的实验现象,同上一章(图片显示实验)完全一模一样,唯一的区别就是 JPEG 解码速度(要求图片分辨率小于等于 LCD 分辨率)变快了很多。STM32F7 的硬件 JPEG 解码 性能可以在最快40ms内完成一张800*480的JPEG图片解码(读数据+解码+YUVRGB转换, 但是不包括显示)。 所要用到的硬件资源如下: 1) 指示灯 DS0 和 DS1 2) KEY0、KEY2 和 KEY_UP 三个按键 3) 串口 4) LCD 模块 5) SD 卡 6) SPI FLASH 7) 硬件 JPEG 解码器 前面 6 个部分,在之前的实例中都介绍过了,我们在此就不介绍了,最后的硬件 JPEG 解 码器,完全是 STM32F7 的内部资源,不需要在开发板上做任何操作,只需要软件配置即可。 需要注意的是,我们在 SD 卡根目录下要建一个 PICTURE 的文件夹,用来存放 JPEG、JPG、 BMP 或 GIF 等图片。 50.3 软件设计 打开本章实验工程目录可以看到,首先在 HARDWARE 文件夹所在的文件夹下新建一个 JPEGCODEC 文件夹,并新建 jpeg_utils.c、jpeg_utils.h、jpeg_utils_tbl.h、jpegcodec.c 和 jpegcodec.h 等 5 个文件。并将 JPEGCODEC 文件夹加入头文件包含路径。其中 jpeg_utils.c、jpeg_utils.h 和 jpeg_utils_tbl.h 等三个文件,实现了 YUVRGB 的转换,支持 YUV420、YUV422、YUV444、 灰度和 CMYK 到 RGB565、RGB888 和 ARGB8888 的转换。这几个文件是我们移植 ST 官方 JPEG 解码例程相关代码而来,并作出适当修改,以获得最快的转换速度。jpegcodec.c 和 jpegcodec.h 是硬件 JPEG 解码的底层驱动代码。然后在 PICTURE 文件夹下新建 hjpgd.c 和 hjpgd.h,用于实 现 JPG/JPEG 图片的硬件 JPEG 解码。 从工程界面可以看到,我们将jpeg_utils.c和jpegcodec.c加入HARDWARE组下,并将hjpgd.c 加入 PICTURE 组下。由于篇幅所限,我们就不把所有代码都贴出来了,仅列出一些重要的函 数给大家讲解。 首先,看 jpegcodec.c 文件里面,比较重要的函数代码如下: void (*jpeg_in_callback)(void); //JPEG DMA 输入回调函数 void (*jpeg_out_callback)(void); //JPEG DMA 输出 回调函数 void (*jpeg_eoc_callback)(void); //JPEG 解码完成 回调函数 void (*jpeg_hdp_callback)(void); //JPEG Header 解码完成 回调函数 //DMA2_Stream0 中断服务函数 //处理硬件 JPEG 解码时输入的数据流 void DMA2_Stream0_IRQHandler(void) { if(__HAL_DMA_GET_FLAG(&JPEGDMAIN_Handler,DMA_FLAG_TCIF0_4) !=RESET) //DMA 传输完成 { __HAL_DMA_CLEAR_FLAG(&JPEGDMAIN_Handler,DMA_FLAG_TCIF0_4); //清除 DMA 传输完成中断标志位 JPEG->CR&=~(1<<11); //关闭 JPEG 的 DMA IN __HAL_JPEG_DISABLE_IT(&JPEG_Handler,JPEG_IT_IFT|JPEG_IT_IFNF| JPEG_IT_OFT|JPEG_IT_OFNE|JPEG_IT_EOC|JPEG_IT_HPD); //关闭 JPEG 中断,防止被打断. if(jpeg_in_callback!=NULL)jpeg_in_callback(); //执行回调函数 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_EOC| JPEG_IT_HPD); //使能 EOC 和 HPD 中断. } } //DMA2_Stream1 中断服务函数 //处理硬件 JPEG 解码后输出的数据流 void DMA2_Stream1_IRQHandler(void) { if(__HAL_DMA_GET_FLAG(&JPEGDMAOUT_Handler,DMA_FLAG_TCIF1_5) !=RESET) //DMA 传输完成 { __HAL_DMA_CLEAR_FLAG(&JPEGDMAOUT_Handler,DMA_FLAG_TCIF1_5); //清除 DMA 传输完成中断标志位 JPEG->CR&=~(1<<12); //关闭 JPEG 的 DMA OUT __HAL_JPEG_DISABLE_IT(&JPEG_Handler,JPEG_IT_IFT|JPEG_IT_IFNF| JPEG_IT_OFT|JPEG_IT_OFNE|JPEG_IT_EOC|JPEG_IT_HPD);//关闭 JPEG 中断, if(jpeg_out_callback!=NULL)jpeg_out_callback();//执行回调函数 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_EOC|JPEG_IT_HPD); //使能 EOC 和 HPD 中断. } } //JPEG 解码中断服务函数 void JPEG_IRQHandler(void) { if(__HAL_JPEG_GET_FLAG(&JPEG_Handler,JPEG_FLAG_HPDF)!=RESET) //JPEG Header 解码完成 { jpeg_hdp_callback(); __HAL_JPEG_DISABLE_IT(&JPEG_Handler,JPEG_IT_HPD); //禁止 Jpeg Header 解码完成中断 __HAL_JPEG_CLEAR_FLAG(&JPEG_Handler,JPEG_FLAG_HPDF); //清除 HPDF 位(header 解码完成位) } if(__HAL_JPEG_GET_FLAG(&JPEG_Handler,JPEG_FLAG_EOCF)!=RESET) //JPEG 解码完成 { JPEG_DMA_Stop(); jpeg_eoc_callback(); __HAL_JPEG_CLEAR_FLAG(&JPEG_Handler,JPEG_FLAG_EOCF); //清除 EOC 位(解码完成位) __HAL_DMA_DISABLE(&JPEGDMAIN_Handler); //关闭 JPEG 数据输入 DMA __HAL_DMA_DISABLE(&JPEGDMAOUT_Handler);//关闭 JPEG 数据输出 DMA } } //初始化硬件 JPEG 内核 //tjpeg:jpeg 编解码控制结构体 //返回值:0,成功; // 其他,失败 u8 JPEG_Core_Init(jpeg_codec_typedef *tjpeg) { u8 i; JPEG_Handler.Instance=JPEG; HAL_JPEG_Init(&JPEG_Handler); //初始化 JPEG for(i=0;i tjpeg->inbuf.buf=mymalloc(SRAMIN,JPEG_DMA_INBUF_LEN); if(tjpeg->inbuf.buf==NULL) { JPEG_Core_Destroy(tjpeg); return 1; } } for(i=0;i tjpeg->outbuf.buf=mymalloc(SRAMIN,JPEG_DMA_OUTBUF_LEN+32); //有可能会多需要 32 字节内存 if(tjpeg->outbuf.buf==NULL) { JPEG_Core_Destroy(tjpeg); return 1; } } return 0; } //关闭硬件 JPEG 内核,并释放内存 //tjpeg:jpeg 编解码控制结构体 void JPEG_Core_Destroy(jpeg_codec_typedef *tjpeg) { u8 i; JPEG_DMA_Stop();//停止 DMA 传输 for(i=0;i for(i=0;i } //初始化硬件 JPEG 解码器 //tjpeg:jpeg 编解码控制结构体 void JPEG_Decode_Init(jpeg_codec_typedef *tjpeg) { u8 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; //图片解码结束标志 tjpeg->blkindex=0; //当前 MCU 编号 tjpeg->total_blks=0; //总 MCU 数目 for(i=0;i tjpeg->inbuf.sta=0; tjpeg->inbuf.size=0; } for(i=0;i tjpeg->outbuf.sta=0; tjpeg->outbuf.size=0; } JPEG->CONFR1|=JPEG_CONFR1_DE; //使能硬件 JPEG 解码模式 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_HPD);//使能 Header 解码完中断 __HAL_JPEG_ENABLE_IT(&JPEG_Handler,JPEG_IT_EOC);//使能解码完成中断 JPEG->CONFR0|=JPEG_CONFR0_START;//使能 JPEG 编解码进程 } //启动 JPEG DMA 解码过程 void JPEG_DMA_Start(void) { __HAL_DMA_ENABLE(&JPEGDMAIN_Handler); //打开 JPEG 数据输入 DMA __HAL_DMA_ENABLE(&JPEGDMAOUT_Handler); //打开 JPEG 数据输出 DMA JPEG->CR|=3<<11; } //停止 JPEG DMA 解码过程 void JPEG_DMA_Stop(void) { JPEG->CR&=~(3<<11); //JPEG IN&OUT DMA 禁止 JPEG->CONFR0&=~(1<<0); //停止 JPEG 编解码进程 __HAL_JPEG_DISABLE_IT(&JPEG_Handler,JPEG_IT_IFT|JPEG_IT_IFNF| JPEG_IT_OFT|JPEG_IT_OFNE|JPEG_IT_EOC|JPEG_IT_HPD); //关闭所有中断 JPEG->CFR=3<<5; //清空标志 } //暂停 DMA IN 过程 void JPEG_IN_DMA_Pause(void) { JPEG->CR&=~(1<<11); //暂停 JPEG 的 DMA IN } //恢复 DMA IN 过程 //memaddr:存储区首地址 //memlen:要传输数据长度(以字节为单位) void JPEG_IN_DMA_Resume(u32 memaddr,u32 memlen) { if(memlen%4)memlen+=4-memlen%4;//扩展到 4 的倍数 memlen/=4; //除以 4 DMA2->LIFCR|=0X3D<<6*0; //清空通道 0 上所有中断标志 DMA2_Stream0->M0AR=memaddr; //设置存储器地址 DMA2_Stream0->NDTR=memlen; //传输长度为 memlen DMA2_Stream0->CR|=1<<0; //开启 DMA2,Stream0 JPEG->CR|=1<<11; //恢复 JPEG DMA IN } //暂停 DMA OUT 过程 void JPEG_OUT_DMA_Pause(void) { JPEG->CR&=~(1<<12); //暂停 JPEG 的 DMA OUT } //恢复 DMA OUT 过程 //memaddr:存储区首地址 //memlen:要传输数据长度(以字节为单位) void JPEG_OUT_DMA_Resume(u32 memaddr,u32 memlen) { if(memlen%4)memlen+=4-memlen%4;//扩展到 4 的倍数 memlen/=4; //除以 4 DMA2->LIFCR|=0X3D<<6*1; //清空通道 1 上所有中断标志 DMA2_Stream1->M0AR=memaddr; //设置存储器地址 DMA2_Stream1->NDTR=memlen; //传输长度为 memlen DMA2_Stream1->CR|=1<<0; //开启 DMA2,Stream1 JPEG->CR|=1<<12; //恢复 JPEG DMA OUT } //获取图像信息 //tjpeg:jpeg 解码结构体 void JPEG_Get_Info(jpeg_codec_typedef *tjpeg) { u32 yblockNb,cBblockNb,cRblockNb; switch(JPEG->CONFR1&0X03) { case 0:tjpeg->Conf.ColorSpace=JPEG_GRAYSCALE_COLORSPACE; break; case 2:tjpeg->Conf.ColorSpace=JPEG_YCBCR_COLORSPACE;break; case 3: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 tjpeg->Conf.ImageQuality=0;//图像质量参数在最后才可获取,先设置为 0 }这里,我们总共列出了 13 个函数。接下来,我们简单介绍一下这些函数。 DMA2_Stream0_IRQHandler 中断服务函数,用于处理 JPEG 解码时输入 FIFO 的数据,当 输入 DMA 传输完成时,会进入该函数,我们通过 jpeg_in_callback 回调函数(该函数在后面再 做介绍),处理输入 DMA 传输完成事务。 DMA2_Stream1_IRQHandler 中断服务函数,用于处理 JPEG 解码时输出 FIFO 的数据,当 输出 DMA 传输完成时,会进入该函数,我们通过 jpeg_out_callback 回调函数(该函数在后面 再做介绍),处理输出 DMA 传输完成事务。 JPEG_IRQHandler 中断服务函数,根据 JPEG_SR 的状态标志位,分别处理 JPEG 头解码完 成中断和 JPEG 文件解码完成中断。当 JPEG 头解码完成时,调用 jpeg_hdp_callback 回调函数 处理相关事务。当 JPEG 文件解码完成时,调用 jpeg_eoc_callback 回调函数处理相关事务,同 时停止 DMA 传输。 JPEG_Core_Init 函数,初始化硬件 JPEG 内核。在该函数里面,有对 tjpeg->inbuf.buf 和 tjpeg->outbuf.buf 两个数组申请内存。tjpeg 是我们在 jpegcodec.h 里面定义的一个结构体,用 于控制整个 JPEG 解码,该结构体定义如下: //JPEG 数据缓冲结构体 typedef struct { u8 sta; //状态:0,无数据;1,有数据. u8 *buf; //JPEG 数据缓冲区 u16 size; //JPEG 数据长度 }jpeg_databuf_type; //jpeg 编解码控制结构体 typedef struct { JPEG_ConfTypeDef Conf; //当前 JPEG 文件相关参数 jpeg_databuf_type inbuf[JPEG_DMA_INBUF_NB]; //DMA IN buf jpeg_databuf_type outbuf[JPEG_DMA_OUTBUF_NB]; //DMA OUT buf vu8 inbuf_read_ptr; //DMA IN buf 当前读取位置 vu8 inbuf_write_ptr; //DMA IN buf 当前写入位置 vu8 indma_pause; //输入 DMA 暂停状态标识 vu8 outbuf_read_ptr; //DMA OUT buf 当前读取位置 vu8 outbuf_write_ptr; //DMA OUT buf 当前写入位置 vu8 outdma_pause; //输入 DMA 暂停状态标识 vu8 state; //解码状态:0,未识别 Header; //1,识别到 Header; 2,解码完成; u32 blkindex; //当前 block 编号 u32 total_blks; //jpeg 文件总 block 数 u32 (*ycbcr2rgb)(u8 *,u8 *,u32 ,u32); //颜色转换函数指针,原型请参考: //JPEG_YCbCrToRGB_Convert_Function }jpeg_codec_typedef;其中 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 的读写位置、暂停状态、解码状态、当前 MCU block 编号、 总 MCU block 数和颜色转换函数指针等参数。该结构体里面的 JPEG_ConfTypeDef 结构体定义, 是在 jpeg_utils.h 里面定义的,该结构体定义如下: //JPEG 文件信息结构体 typedef struct { u8 ColorSpace; //图像的颜色空间: gray-scale/YCBCR/RGB/CMYK u8 ChromaSubsampling; //YCBCR/CMYK 颜色空间的色度抽样情况: //0:4:4:4; 1:4:2:2; 2:4:1:1; 3:4:2:0 u32 ImageHeight; //图像高度 u32 ImageWidth; //图像宽度 u8 ImageQuality; //图像编码质量:1~100 }JPEG_ConfTypeDef;JPEG_Core_Destroy 函数,用于关闭 JPEG 处理(停止 DMA 传输),并释放内存。 JPEG_Decode_Init 函数,用于初始化硬件JPEG解码器,同时对输入 DMA FIFO 和输出DMA FIFO 的相关标记进行清理处理,以便开始 JPEG 解码。 JPEG_DMA_Start 和 JPEG_DMA_Stop 函数,分别用于启动和关闭 JPEG DMA 解码。 JPEG_IN_DMA_Pause 和 JPEG_IN_DMA_Resume 函数,分别用于暂停和重启输入 DMA。 JPEG_OUT_DMA_Pause 和 JPEG_OUT_DMA_Resume 函数,分别用于暂停和重启输出 DMA。 JPEG_Get_Info 函数,用于获取 JPEG 图像信息,在 JPEG 头解码完成后,被调用。该函数 可以获取 JPEG 图片的宽度、高度、颜色空间和色度抽样等重要信息。 接下来,我们看 jpeg_utils.c 文件,该文件移植自 ST 官方的硬件 JPEG 解码代码,该文件 我们仅介绍 JPEG_GetDecodeColorConvertFunc 函数,该函数代码如下: //获取 YCbCr 到 RGB 颜色转换函数和总的 MCU Block 数目. //pJpegInfo:JPEG 文件信息结构体 //pFunction:JPEG_YCbCrToRGB_Convert_Function 的函数指针,根据 jpeg 图像参数,指向 //不同的颜色转换函数. //ImageNbMCUs:总的 MCU 块数目 //返回值:0,正常; // 1,失败; u8 JPEG_GetDecodeColorConvertFunc(JPEG_ConfTypeDef *pJpegInfo, JPEG_YCbCrToRGB_Convert_Function *pFunction, u32 *ImageNbMCUs) { u32 hMCU, vMCU; JPEG_ConvertorParams.ColorSpace=pJpegInfo->ColorSpace; //色彩空间 JPEG_ConvertorParams.ImageWidth=pJpegInfo->ImageWidth; //图像宽度 JPEG_ConvertorParams.ImageHeight=pJpegInfo->ImageHeight; //图像高度 JPEG_ConvertorParams.ImageSize_Bytes=pJpegInfo->ImageWidth*pJpegInfo-> ImageHeight*JPEG_BYTES_PER_PIXEL; //转换后的图像总字节数 JPEG_ConvertorParams.ChromaSubsampling=pJpegInfo->ChromaSubsampling;//抽样 if(JPEG_ConvertorParams.ColorSpace==JPEG_YCBCR_COLORSPACE)//YCbCr420 { if(JPEG_ConvertorParams.ChromaSubsampling==JPEG_420_SUBSAMPLING) { *pFunction=JPEG_MCU_YCbCr420_ARGB_ConvertBlocks;//YCbCr420 JPEG_ConvertorParams.LineOffset=JPEG_ConvertorParams.ImageWidth%16; if(JPEG_ConvertorParams.LineOffset!=0) { JPEG_ConvertorParams.LineOffset=16-JPEG_ConvertorParams.LineOffset; } JPEG_ConvertorParams.H_factor=16; JPEG_ConvertorParams.V_factor=16; }else if(JPEG_ConvertorParams.ChromaSubsampling== JPEG_422_SUBSAMPLING)//YCbCr422 { *pFunction=JPEG_MCU_YCbCr422_ARGB_ConvertBlocks;//YCbCr422 JPEG_ConvertorParams.LineOffset=JPEG_ConvertorParams.ImageWidth%16; if(JPEG_ConvertorParams.LineOffset!=0) { JPEG_ConvertorParams.LineOffset=16-JPEG_ConvertorParams.LineOffset; } JPEG_ConvertorParams.H_factor=16; JPEG_ConvertorParams.V_factor=8; }else // YCbCr444 { *pFunction=JPEG_MCU_YCbCr444_ARGB_ConvertBlocks;//YCbCr444 JPEG_ConvertorParams.LineOffset=JPEG_ConvertorParams.ImageWidth%8; if(JPEG_ConvertorParams.LineOffset!=0) { JPEG_ConvertorParams.LineOffset=8-JPEG_ConvertorParams.LineOffset; } JPEG_ConvertorParams.H_factor=8; JPEG_ConvertorParams.V_factor=8; } }else if(JPEG_ConvertorParams.ColorSpace== JPEG_GRAYSCALE_COLORSPACE)//GrayScale 颜色空间 { *pFunction=JPEG_MCU_Gray_ARGB_ConvertBlocks;//使用 Y Gray 转换 JPEG_ConvertorParams.LineOffset=JPEG_ConvertorParams.ImageWidth%8; if(JPEG_ConvertorParams.LineOffset!=0) { JPEG_ConvertorParams.LineOffset=8-JPEG_ConvertorParams.LineOffset; } JPEG_ConvertorParams.H_factor=8; JPEG_ConvertorParams.V_factor=8; }else if(JPEG_ConvertorParams.ColorSpace==JPEG_CMYK_COLORSPACE) { *pFunction=JPEG_MCU_YCCK_ARGB_ConvertBlocks;//使用 CMYK 颜色转换 JPEG_ConvertorParams.LineOffset=JPEG_ConvertorParams.ImageWidth%8; if(JPEG_ConvertorParams.LineOffset!=0) { JPEG_ConvertorParams.LineOffset=8-JPEG_ConvertorParams.LineOffset; } JPEG_ConvertorParams.H_factor=8; JPEG_ConvertorParams.V_factor=8; }else return 0X01; //不支持的颜色空间 JPEG_ConvertorParams.WidthExtend=JPEG_ConvertorParams.ImageWidth+ JPEG_ConvertorParams.LineOffset; JPEG_ConvertorParams.ScaledWidth=JPEG_BYTES_PER_PIXEL* JPEG_ConvertorParams.ImageWidth; hMCU=(JPEG_ConvertorParams.ImageWidth/JPEG_ConvertorParams.H_factor); if((JPEG_ConvertorParams.ImageWidth%JPEG_ConvertorParams.H_factor)!=0) hMCU++; //+1 for horizenatl incomplete MCU vMCU=(JPEG_ConvertorParams.ImageHeight/JPEG_ConvertorParams.V_factor); if((JPEG_ConvertorParams.ImageHeight%JPEG_ConvertorParams.V_factor)!=0) vMCU++; //+1 for vertical incomplete MCU JPEG_ConvertorParams.MCU_Total_Nb=(hMCU*vMCU); *ImageNbMCUs=JPEG_ConvertorParams.MCU_Total_Nb; return 0X00; }该函数参数有 3 个:pJpegInfo 是一个 JPEG_ConfTypeDef 结构体指针,用于传递当前 JPEG 的相关参数;pFunction 是函数指针,类型为:JPEG_YCbCrToRGB_Convert_Function,定义如 下: typedef u32 (* JPEG_YCbCrToRGB_Convert_Function)(u8 *pInBuffer,u8 *pOutBuffer,u32 BlockIndex,u32 DataCount); 相关参数说明: pInBuffer:指向输入的 YCbCr blocks 缓冲区 pOutBuffer:指向输出的 RGB888/ARGB8888 帧缓冲区 BlockIndex:输入 buf 里面的第一个 MCU 块编号 DataCount:输入缓冲区的大小 该函数指针可以指向在 jpeg_utils.c 里面定义的其他 5 个函数: 1,JPEG_MCU_YCbCr420_ARGB_ConvertBlocks 函数,实现 YUV420RGB 的转换。 2,JPEG_MCU_YCbCr422_ARGB_ConvertBlocks 函数,实现 YUV422RGB 的转换。 3,JPEG_MCU_YCbCr444_ARGB_ConvertBlocks 函数,实现 YUV444RGB 的转换。 4,JPEG_MCU_Gray_ARGB_ConvertBlocks 函数,实现灰度图像RGB 的转换。 5,JPEG_MCU_YCCK_ARGB_ConvertBlocks 函数,实现 CMYKRGB 的转换。 这五个函数都是用于色彩转换,支持将 YUV、灰度和 CMYK 格式转换为 RGB565、RGB888 和 ARGB8888 等格式,由于篇幅所限,就不贴出来了,请大家参考 jpeg_utils.c 的源代码。 ImageNbMCUs,用于表示当前 JPG/JPEG 文件总 MCU 数。该函数其他代码,我们就不多 说了,请大家参考源码注释理解。 接下来,我们看 hjpgd.c 里面的代码,该文件代码如下: jpeg_codec_typedef hjpgd; //JPEG 硬件解码结构体 //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 { JPEG_IN_DMA_Pause(); //暂停读取数据 hjpgd.indma_pause=1; //暂停读取数据 }else //有效的 buf { JPEG_IN_DMA_Resume((u32)hjpgd.inbuf[hjpgd.inbuf_read_ptr].buf, hjpgd.inbuf[hjpgd.inbuf_read_ptr].size); //继续下一次 DMA 传输 } } //JPEG 输出数据流(YUV)回调函数,用于输出 YUV 数据流 void jpeg_dma_out_callback(void) { u32 *pdata=0; hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta=1; //此 buf 已满 hjpgd.outbuf[hjpgd.outbuf_write_ptr].size=JPEG_DMA_OUTBUF_LEN- (DMA2_Stream1->NDTR<<2);//此 buf 里面数据的长度 if(hjpgd.state==JPEG_STATE_FINISHED)//解码完成,需读 DOR 最后的数据 { pdata=(u32*)(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 { JPEG_OUT_DMA_Pause(); //暂停输出数据 hjpgd.outdma_pause=1; //暂停输出数据 }else //有效的 buf { JPEG_OUT_DMA_Resume((u32)hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf, JPEG_DMA_OUTBUF_LEN); //继续下一次 DMA 传输 } } //JPEG 整个文件解码完成回调函数 void jpeg_endofcovert_callback(void) { hjpgd.state=JPEG_STATE_FINISHED; //标记 JPEG 解码完成 } //JPEG header 解析成功回调函数 void jpeg_hdrover_callback(void) { hjpgd.state=JPEG_STATE_HEADEROK; //HEADER 获取成功 JPEG_Get_Info(&hjpgd); //获取 JPEG 相关信息,包括大小,色彩空间,抽样等 JPEG_GetDecodeColorConvertFunc(&hjpgd.Conf,&hjpgd.ycbcr2rgb, &hjpgd.total_blks);//获取 JPEG 色彩转换函数,以及总 MCU 数 picinfo.ImgWidth=hjpgd.Conf.ImageWidth; picinfo.ImgHeight=hjpgd.Conf.ImageHeight; ai_draw_init(); } //JPEG 硬件解码图片 //注意: //1,待解吗图片的分辨率,必须小于等于屏幕的分辨率! //2,请保证图片的宽度是 16 的倍数,以免左侧出现花纹. //pname:图片名字(带路径) //返回值:0,成功 // 其他,失败 u8 hjpgd_decode(u8* pname) { FIL* ftemp; u16* rgb565buf; vu32 timecnt=0; u32 mcublkindex=0; u8 fileover=0;u8 i=0;u8 res; res=JPEG_Core_Init(&hjpgd); //初始化 JPEG 内核 if(res)return 1; ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //申请内存 if(f_open(ftemp,(char*)pname,FA_READ)!=FR_OK) //打开图片失败 { JPEG_Core_Destroy(&hjpgd); myfree(SRAMIN,ftemp); //释放内存 return 2; } rgb565buf=mymalloc(SRAMEX,lcddev.width*lcddev.height*2);//申请整帧内存 JPEG_Decode_Init(&hjpgd); //初始化硬件 JPEG 解码器 for(i=0;i res=f_read(ftemp,hjpgd.inbuf.buf,JPEG_DMA_INBUF_LEN,&br);//填满 FIFO if(res==FR_OK&&br){ hjpgd.inbuf.size=br; hjpgd.inbuf.sta=1;} //标记 buf 满 if(br==0)break; } JPEG_IN_OUT_DMA_Init((u32)hjpgd.inbuf[0].buf,(u32)hjpgd.outbuf[0].buf, hjpgd.inbuf[0].size,JPEG_DMA_OUTBUF_LEN);//配置 DMA 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_DMA_Start(); //启动 DMA 传输 while(1) { SCB_CleanInvalidateDCache(); //清空 D catch if(hjpgd.inbuf[hjpgd.inbuf_write_ptr].sta==0&&fileover==0)//有 buf 为空 { 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((u32)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 里面有数据要处理 { mcublkindex+=hjpgd.ycbcr2rgb(hjpgd.outbuf[hjpgd.outbuf_read_ptr].buf, (u8*)rgb565buf,mcublkindex,hjpgd.outbuf[hjpgd.outbuf_read_ptr].size); 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(mcublkindex==hjpgd.total_blks) break; }else if(hjpgd.outdma_pause==1&&hjpgd.outbuf[hjpgd.outbuf_write_ptr].sta==0) //out 暂停,且当前 writebuf 已经为空了,则恢复 out 输出 { JPEG_OUT_DMA_Resume((u32)hjpgd.outbuf[hjpgd.outbuf_write_ptr].buf, JPEG_DMA_OUTBUF_LEN); //继续下一次 DMA 传输 hjpgd.outdma_pause=0; } timecnt++; if(fileover) //文件结束后,及时退出,防止死循环 { if(hjpgd.state==JPEG_STATE_NOHEADER)break; //解码失败了 if(timecnt>0X3FFF)break; //超时退出 } } if(hjpgd.state==JPEG_STATE_FINISHED) //解码完成了 { piclib_fill_color(picinfo.S_XOFF,picinfo.S_YOFF,hjpgd.Conf.ImageWidth, hjpgd.Conf.ImageHeight,rgb565buf); } myfree(SRAMIN,ftemp); myfree(SRAMEX,rgb565buf); JPEG_Core_Destroy(&hjpgd); return 0; }该文件里面,总共有 5 个函数,接下来分别介绍: jpeg_dma_in_callback 函数,用于处理 JPEG 输入数据流,当 JPEG 输入 DMA 传输完成时, 调用该函数。对已处理的 buf 标记清零,然后切换到下一个 buf。当 buf 不够时,暂停 JPEG 输 入 FIFO 获取数据,并标记暂停;当 buf 足够时,切换到下一个 buf,继续传输。 jpeg_dma_out_callback 函数,用于处理 JPEG 输出数据流,当 JPEG 输入 DMA 传输完成时, 调用该函数。对已满的 buf 标记满,并标记容量,然后切换到下一个 buf。当 buf 不够时,暂停 获取 JPEG 输出 FIFO 的数据,并标记暂停;当 buf 足够时,切换到下一个 buf,继续传输。当 解码状态结束时,需要手动读取 JPEG_DOR 寄存器的数据。 jpeg_endofcovert_callback 函数,在 JPG/JPEG 文件解码结束时调用。该函数处理非常简单, 直接将当前解码状态标记为:JPEG 解码完成(JPEG_STATE_FINISHED)即可。 jpeg_hdrover_callback 函数,在 JPEG 头解码成功后调用。该函数先标记状态为 JPEG 头解 码成功(JPEG_STATE_HEADEROK),然后调用 JPEG_Get_Info 函数获取 JPEG 相关信息,通 过 JPEG_GetDecodeColorConvertFunc 函数取得颜色转换函数和总 MCU 数。最后初始化画图, 准备解码显示。 hjpgd_decode 函数,用于解码一张 JPG/JPEG 图片。该函数采用我们在 50.1 节最后介绍的 步骤来解码 JPG/JPEG 图片。请大家参考前面的介绍和源码进行理解。 另外,我们需要将 hjpgd_decode 函数加入到图片解码库里面,修改 ai_load_picfile 函数代 码如下: //智能画图 //FileName:要显示的图片文件 BMP/JPG/JPEG/GIF //x,y,width,height:坐标及显示区域尺寸 //fast:使能 jpg 小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能. // 当有硬件 JPEG 解码的时候,快速解码使用硬件 jpeg 解码,以提高速度 //图片在开始和结束的坐标点范围内显示 u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast) { u8 res; u8 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=f_typetell((u8*)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) { if(picinfo.ImgWidth<=lcddev.width&&picinfo.ImgHeight<= lcddev.height&&picinfo.ImgWidth<=picinfo.S_Width&& picinfo.ImgHeight<=picinfo.S_Height) //则可以硬件解码 { res=hjpgd_decode((u8*)filename);//采用硬解码 JPG/JPEG }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 图片尺寸满足小于等于屏幕分辨率,且启用快速解码时,我们会通过调用 hjpgd_decode 函数实现硬件 JPEG 解码,从而大大提高速度。 最后,我们看看 main.c 文件,代码如下: //得到 path 路径下,目标文件的总个数 //path:路径 //返回值:总有效文件数 u16 pic_get_tnum(u8 *path) { ……//请参考上一章例程源码/本例程源码 } int main(void) { u8 led0sta=1;u8 res; DIR picdir; //图片目录 FILINFO *picfileinfo; //文件信息 u8 *pname; //带路径的文件名 u16 totpicnum; //图片文件总数 u16 curindex; //图片当前索引 u8 key; u8 t;u16 temp; u8 pause=0; //暂停标记 u32 *picoffsettbl; //图片文件 offset 索引表 Cache_Enable(); //打开 L1-Cache MPU_Memory_Protection(); //保护相关存储区域 HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz ……//省略部分代码 totpicnum=pic_get_tnum("0:/PICTURE"); //得到总有效文件数 while(totpicnum==NULL)//图片文件为 0 { Show_Str(30,170,240,16,"没有图片文件!",16,0); delay_ms(200); LCD_Fill(30,170,240,186,WHITE); delay_ms(200); } picfileinfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO)); //申请内存 pname=mymalloc(SRAMIN,_MAX_LFN*2+1); //为带路径的文件名分配内存 picoffsettbl=mymalloc(SRAMIN,4*totpicnum);//申请 4*totpicnum 内存,存放图片索引 while(!picfileinfo||!pname||!picoffsettbl) //内存分配出错 { Show_Str(30,170,240,16,"内存分配失败!",16,0); 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=f_typetell((u8*)picfileinfo->fname); if((res&0XF0)==0X50)//取高四位,看看是不是图片文件 { picoffsettbl[curindex]=temp;//记录索引 curindex++; } } } Show_Str(30,170,240,16,"开始显示...",16,0); 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); ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片 Show_Str(2,2,lcddev.width,16,pname,16,1); //显示图片名字 t=0; while(1) { key=KEY_Scan(0); //扫描按键 if(t>250)key=1; //模拟一次按下 KEY0 if((t%20)==0) LED0_Toggle;//LED0 闪烁,提示程序正在运行. if(key==KEY2_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; } ……//省略部分代码 }这部分代码比较长,我们省略了一些内容。详细的代码,请大家参考光盘本例程源码。 这里除了 main 函数,还有一个 pic_get_tnum 的函数,用来得到 path 路径下,所有有效文 件(图片文件)的个数。在 main 函数里面我们通过读/写偏移量(图片文件在 PICTURE 文件夹 下的读/写偏移位置,可以看做是一个索引),来查找上一个/下一个图片文件(使用 dir_sdi 函数)。 通过 ai_load_picfile 函数,实现对 JPG/JPEG 图片的解码。这里将 fast 参数设置为 1,当图片文 件的分辨率小于等于液晶分辨率的时候,将使用硬件 JPEG 进行解码。 至此本例程代码编写完成。最后,本实验可以通过 USMART 来调用相关函数,以对比性 能。将 mf_scan_files、ai_load_picfile 和 hjpgd_decode 等函数添加到 USMART 管理,即可以通 过串口调用这几个函数,测试对比软件 JPEG 解码和硬件 JPEG 解码的速度差别。 50.4 下载验证 在代码编译成功之后,我们下载代码到 ALIENTEK 阿波罗 STM32 开发板上,可以看到 LCD 开始显示图片(假设 SD 卡及文件都准备好了,即:在 SD 卡根目录新建:PICTURE 文件夹, 并存放一些图片文件(.bmp/.jpg/.gif)在该文件夹内),如图 50.4.1 所示: 图 50.4.1 硬件 JPEG 解码实验显示效果 按 KEY0 和 KEY2 可以快速切换到下一张或上一张,KEY_UP 按键可以暂停自动播放,同 时 DS1 亮,指示处于暂停状态,再按一次 KEY_UP 则继续播放。对比上一章实验,我们可以 发现,对于小尺寸的 JPG/JPEG 图片(小于液晶分辨率),本例程解码速度明显提升。 我们通过 USMART 调用 ai_load_picfile 函数,对比测试同一张图片,使用硬件 JPEG 解码 和不使用硬件 JPEG 解码,速度差别明显,如图 50.4.2 所示: 图 50.4.2 硬件 JPEG 与软件 JPEG 解码速度对比 上图,是我们使用 4.3 寸 800*480 分辨率的 MCU 屏做的测试,可以看出,对于同一张图 片(图片分辨率:800*480),硬件 JPEG 解码,只需要 96.2ms,软件 JPEG 解码,则需要 887.8ms!硬件 JPEG 解码速度是软件 JPEG 解码的 9.2 倍!!可见,硬件 JPEG 解码大大提高了对 JPG/JPEG图片的解码能力。 |
|
相关推荐
|
|
1980 浏览 1 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1836 浏览 3 评论
4416 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1985 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
2489 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-19 14:14 , Processed in 0.683386 second(s), Total 63, Slave 46 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号