3 Star 29 Fork 18

马志良/easyio-lib-for-esp32

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

35_JPG_LCD_DMA

例程简介

之前Demo中的图片使用位图取模的存储方式,使用起来有诸多不便:需要单独取模、占用Flash巨大、没有文件格式不利于大批量图片的存储。生活中,图片最常用的格式是.jpg,jpg图片是一种有损压缩的格式,虽然会损失一些局部细节,但所需存储空间大为较少。.jpg的显示需要jpg解码器,MCU使用较多的jpg解码器有的TJpgDeclibjpeg

其中TJpgDec体积小巧,虽然解码速度没有libjpeg快,但RAM仅占用3KB大小,且方便移植。之前使用的FATFS也是出自同一作者。TJpgDec官网

使用 FreeRTOSTask,创建两个任务:

  1. led_task ,控制LED闪烁

  2. jpg_lcd_spi_dma_task ,初始化 SPI总线、LCD、jpg解码器,并以SPI-DMA 双缓冲环形队列的方式去刷液晶屏,让lcd来回显示两张图片。

tjpgd.c.h: jpg解码器TJpgDec的源码。

jpg_decode.c.h: 为jpg解码的流程。参照了lcd例程的decode_image.c.h,并在此上进行的优化,可进行重复解码(内存一直被占用,而decode_image.c.h在解码后马上就释放了内存)

lcd_spi_dma.c.h: 为SPI-DMA 双缓冲环形队列刷液晶屏。easyio已将其封装好,只需要调用lcd_spi_dma_display_init();lcd_spi_dma_display_img(spi_device_handle_t spi, uint16_t ***pixels);即可使用完整功能。

硬件连接

LCDSD_CARD 使用同一SPI总线,VSPI_HOST(ESP32的SPI3)。

ESP32 pin SPI pin SD card pin LCD pin
GPIO18 SCK CLK SCL
GPIO23 MOSI CMD SDA
GPIO19 MISO D0
N/C D1
N/C D2
GPIO27 CS_SD D3
GPIO5 CS_LCD CS
GPIO25 RESET
GPIO22 D/C
GPIO21 BLK

运行现象

  • LED闪烁。

  • 上电后,液晶屏会先显示Hello! TJpgDec持续2s。之后会以2s为间隔,在两幅图片间来回切换。

经测试:

  • ESP32运行于160MHz,解码Demo中的两个图片分别用时136ms和131ms。【如不对SDK的配置进行修改,默认运行频率即为160MHz】

  • ESP32运行于240MHz,解码Demo中的两个图片分别用时91ms和88ms。耗时基本为160MHz的2/3,合理。

ESP-IDF的默认运行频率为CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ,可在menuconfig中修改。详细说明可见ESP-IDF 电源管理

SPI-DMA 双缓冲环形队列的运行机制

SPI-DMA 双缓冲环形队列刷屏涉及到的内容较多,完整理解起来需要些时间。所幸easyio已将其封装好,只需要调用lcd_spi_dma_display_init();lcd_spi_dma_display_img(spi_device_handle_t spi, uint16_t ***pixels);即可使用完整功能。

前几节的SPI刷液晶屏,虽然也用到了DMA,但都是通过画点的方式来完成绘图。传输效率低下。本Demo参照了IDF中的lcdDemo,使用SPI-DMA 双缓冲环形队列的方式去刷屏,极大提高了刷屏帧率。SPI通信部分完全交由DMA控制,DMA发送时会占用其中一块DMA内存,在MCU等待DMA发送完成前,会进行下一帧显示图片的像素格式转换,并将转换好的像素放入DMA的另一块内存。待DMA发送完成后立马将刚刚处理好的内存交给下一帧SPI-DMA通信,如此往复。最大程度上提高了MCU资源的利用率。(在spi_lcd.h中设置,默认会以16行屏幕像素为一次分割,来进行一次SPI-DMA的传输队列。每个队列只发送一行的像素,包括设置坐标、写GRAM,每个队列都是一次SPI-DMA通信。以此来达到减小DMA内存占用的目的,并不是一次DMA刷完整个屏幕。这种方式组成的环形DMA,DMA内存只需要2块PARALLEL_LINES*lcddev.width*sizeof(uint16_t)大小)

如何将.jpg图片导入工程并调用

本Demo的jpg图片,直接以二进制文件的方式编译在ESP32Flash中。没有使用文件系统。

导入.jpg图片步骤

  1. 获取jpg图片:如有下载好的jpg图片可直接使用。但如果是使用QQ截屏,就需要先粘贴到画图,再另存为-JPEG文件。(直接使用QQ截屏的jpg会解码失败,不明原因)

  2. 之后将.jpg图片复制到工程\components\easyio_lib\pic下的文件夹。

  3. 再修改\components\easyio_lib\CMakeLists.txt文件,插入如EMBED_FILES "pic/image_esp32mcu.jpg" "pic/image_esp32wifi.jpg"的jpg图片路径。

  4. 代码中输入图片时,需要用下面的方式去引用jpg文件的起始地址:

// 引用二进制化的jpeg文件的首尾地址
extern const uint8_t image_esp32mcu_jpg_start[] asm("_binary_image_esp32mcu_jpg_start");
extern const uint8_t image_esp32mcu_jpg_end[] asm("_binary_image_esp32mcu_jpg_end");

extern const uint8_t image_esp32wifi_jpg_start[] asm("_binary_image_esp32wifi_jpg_start");
extern const uint8_t image_esp32wifi_jpg_end[] asm("_binary_image_esp32wifi_jpg_end");

其中asm()中的内容,会根据引入工程的jpg文件名称而变化,需要手动修改。其详细地址可在编译后全局搜索到。

SPI-DMA 双缓冲环形队列的刷屏速度

刷屏帧率测试会在下一个Demo演示。并会演示IDFlcd例程自带的一个波纹动效。

(剧透:320x240的屏幕,以80MHz的SPI速率驱动lcd,平均在53.5FPS。【40MHz为30.2FPS】)

关键函数

// 初始化lcd使用`SPI-DMA 双缓冲环形队列`刷屏(申请DMA双缓冲内存)
void lcd_spi_dma_display_init(void);

// 刷一整个LCD屏幕(传输使用DMA环形队列加速)
void lcd_spi_dma_display_img(spi_device_handle_t spi, uint16_t ***pixels);

// 申请jpg解码器、和输出到LCD的RGB565像素 需要的内存空间(此为TJpgDec解码器的预处理,仅需调用一次。重复调用可能会因为申请的空间超出ESP32可用范围,导致错误。)
esp_err_t jpg_decode_request_ram(uint16_t ***pixels, uint16_t buf_height, uint16_t buf_width);

// 解码jpg图片,输出为RGB565的像素格式
esp_err_t jpg_decode(const unsigned char *jpg_img, uint16_t ***pixels, uint8_t scale);

乐鑫ESP32-SPI帮助文档:ESP32-SPI Master

函数功能及更多介绍详见官网在线文档。 TJpgDec官网:TJpgDec在线文档

注意事项

  • 虽然将jpg解码过程进行了优化,使得可在只占用3KB+一屏幕像素内存的情况下,能进行重复解码。但如果想将多张解码后的图片同时放入RAM,考虑到ESP32的RAM大小,当显示图片的分辨率大于等于320x240时,就不要重复调用jpg_decode_request_ram了,会导致申请内存失败(320x240-RGB565图片占用RAM:320x240x2 = 153600Bytes = 150KB。虽然ESP32有512KB的SRAM,但运行IDF框架后,可用内存仅为200KB左右)。

  • ESP32 如何查看芯片内存(例如:DRAM、IRAM、rodata)使用情况?:ESP-FAQ

  • ESP32内存模型:ESP32内存模型

  • ESP32应用程序的内存布局:ESP32内存布局

  • ESP32堆内存分配

  • ESP32应用程序启动流程


  • SPI-DMA 双缓冲环形队列暂不支持ili94xx型号的显示。因 ili94xxSPI模式下,只能使用 3Bytes-RGB666 的像素格式,显示效果一般,且3字节的方式对SPI带宽及内存浪费严重。所以本Demo的SPI-DMA 双缓冲环形队列刷屏,目前只对2Bytes-RGB565做了适配。

  • HX8357C因为像素偏移导致图片错位,SPI-DMA加速目前仅对 ili9341st7789 完美支持。

  • 如果用QQ截图取图,不能直接使用QQ截图保存的.jpg,会导致解码失败。需要先粘贴到画图,然后另存为.jpg。

  • 为匹配液晶屏的RGB565像素格式,tjpgd.h 中的JD_FORMAT应设置为1,设置为1:RGB565 (1 WORD/pix)。为0:RGB888 (3 BYTE/pix)会出现乱码。

  • 更改LCD的SPI-SCK时钟速率,10MHz、20MHz刷屏时会感觉到轻微拖影,而设置为40MHz、80MHz,基本感觉不到,瞬切。(80MHz对线路要求严格,有可能因线路问题导致花屏。)

  • TjpgDec相似的jpg解码库还有libjpeg,解码速度略快、功能更多,但所需的RAM和Flash空间也更大些。

  • 刷屏帧率测试会在下一个Demo演示。并会演示IDFlcd例程自带的一个波纹动效。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/ZhiliangMa/easyio-lib-for-esp32.git
git@gitee.com:ZhiliangMa/easyio-lib-for-esp32.git
ZhiliangMa
easyio-lib-for-esp32
easyio-lib-for-esp32
master

搜索帮助