前面我已经介绍过了如何在Arduino环境下用LCD显示文本、图案和图片,这一讲主要介绍一下GIF动图的显示。
本文的硬件配置如下:
注:我这里以ESP8266和ESP32为例讲解,实际上根据自己的MCU选择一种即可,方法和原理都是一样的。
ESP8266接线如下:
特别说明:不同厂家做的LCD对这几个引脚的命名可能会有差异,但意思是一样的。
ESP32接线如下:
关于ESP8266 Arduino的环境搭建我之前出过教程了,这里就不多说了,不懂的同学可以先看下我之前的博客。 esp8266开发入门教程(基于Arduino)——环境安装
ESP32 Arduino的环境搭建你们可以自行查阅资料。
打开Arduino IDE,依次打开 工具 -> 管理库… 在搜索框输入需要安装的库名称,找到对应的库,点击安装即可。
本文需要使用的Arduino库如下:
关于TFT_eSPI 库的使用我前面已经介绍过几次了,这里就不再讲解了,不懂的同学可以先看下我之前的博客。 esp8266应用教程——TFT LCD Arduino应用开发——LCD显示图片
我这里用的转换工具是在GitHub上面找到的一个代码,你们想要的话可以在下面的链接下载。这个工具使用起来稍微有点麻烦,如果你有更好的工具也可以推荐给博主。
工具链接:https://pan.baidu.com/s/1f2rgD1a9PY-_hboPdbfaow 提取码:4h6h
运行方法如下: 提示:下面演示的示例是我之前用来转换图片数据的,转换gif也是一样的。
第一步:把要转换的图片放到这个工具的目录下。
第二步:打开电脑的运行窗口(win10可以使用win+R快捷键),输入“cmd”打开命令窗口。 第三步:在命令窗口中输入跳转命令,跳转到转换工具所在的目录下。 例如:
cd C:\Users\z\Desktop\图片处理工具\image_to_c\dist\Windows
第四步:运行应用程序。 格式:应用程序名+空格+图片名+空格+>+空格+转换后的文件名。 例如:
image_to_c64.exe demo-image1.jpg > demo-image1.h
运行成功的话就会生成相应的头文件。
示例代码如下: 提示:示例里面同时使用了5个GIF demo,内存占用了2兆多,这些数据都是直接存到代码里面的,因此在下载程序时要注意FLASH的配置,保证APP分区的内存足够,否则会编译出错。也可以注释掉一部分素材,单独播放某个GIF,这样内存占用较少。 程序源码和素材可以在文章底部下载。
#include <SPI.h> #include <TFT_eSPI.h> #include <AnimatedGIF.h> // #include "Arduino.h" // #include <esp_heap_caps.h> #include "gif_demo1.h" #include "gif_demo2.h" #include "gif_demo3.h" #include "gif_demo4.h" #include "gif_demo5.h" #define GIF_DEMO1 gif_demo1 #define GIF_DEMO2 gif_demo2 #define GIF_DEMO3 gif_demo3 #define GIF_DEMO4 gif_demo4 #define GIF_DEMO5 gif_demo5 #ifdef ESP8266 #include <avr/pgmspace.h> #else #include <pgmspace.h> #endif AnimatedGIF gif; TFT_eSPI tft = TFT_eSPI(); #define GIF_ENABLE #define NORMAL_SPEED // Comment out for rame rate for render speed test #ifdef GIF_ENABLE // GIFDraw is called by AnimatedGIF library frame to screen #define DISPLAY_WIDTH tft.width() #define DISPLAY_HEIGHT tft.height() #define BUFFER_SIZE 256 // Optimum is >= GIF width or integral division of width #ifdef USE_DMA uint16_t usTemp[2][BUFFER_SIZE]; // Global to support DMA use #else uint16_t usTemp[1][BUFFER_SIZE]; // Global to support DMA use #endif bool dmaBuf = 0; // Draw a line of image directly on the LCD void GIFDraw(GIFDRAW *pDraw) { uint8_t *s; uint16_t *d, *usPalette; int x, y, iWidth, iCount; // pDraw->iX = 50; // pDraw->iY = 50; // Displ;ay bounds chech and cropping iWidth = pDraw->iWidth; if (iWidth + pDraw->iX > DISPLAY_WIDTH) iWidth = DISPLAY_WIDTH - pDraw->iX; usPalette = pDraw->pPalette; y = pDraw->iY + pDraw->y; // current line if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1) return; // Old image disposal s = pDraw->pPixels; if (pDraw->ucDisposalMethod == 2) // restore to background color { for (x = 0; x < iWidth; x++) { if (s[x] == pDraw->ucTransparent) s[x] = pDraw->ucBackground; } pDraw->ucHasTransparency = 0; } // Apply the new pixels to the main image if (pDraw->ucHasTransparency) // if transparency used { uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; pEnd = s + iWidth; x = 0; iCount = 0; // count non-transparent pixels while (x < iWidth) { c = ucTransparent - 1; d = &usTemp[0][0]; while (c != ucTransparent && s < pEnd && iCount < BUFFER_SIZE ) { c = *s++; if (c == ucTransparent) // done, stop { s--; // back up to treat it like transparent } else // opaque { *d++ = usPalette[c]; iCount++; } } // while looking for opaque pixels if (iCount) // any opaque pixels? { // DMA would degrtade performance here due to short line segments tft.setAddrWindow(pDraw->iX + x, y, iCount, 1); tft.pushPixels(usTemp, iCount); x += iCount; iCount = 0; } // no, look for a run of transparent pixels c = ucTransparent; while (c == ucTransparent && s < pEnd) { c = *s++; if (c == ucTransparent) x++; else s--; } } } else { s = pDraw->pPixels; // Unroll the first pass to boost DMA performance // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) if (iWidth <= BUFFER_SIZE) for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++]; else for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++]; #ifdef USE_DMA // 71.6 fps (ST7796 84.5 fps) tft.dmaWait(); tft.setAddrWindow(pDraw->iX, y, iWidth, 1); tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount); dmaBuf = !dmaBuf; #else // 57.0 fps tft.setAddrWindow(pDraw->iX, y, iWidth, 1); tft.pushPixels(&usTemp[0][0], iCount); #endif iWidth -= iCount; // Loop if pixel buffer smaller than width while (iWidth > 0) { // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) if (iWidth <= BUFFER_SIZE) for (iCount = 0; iCount < iWidth; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++]; else for (iCount = 0; iCount < BUFFER_SIZE; iCount++) usTemp[dmaBuf][iCount] = usPalette[*s++]; #ifdef USE_DMA tft.dmaWait(); tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount); dmaBuf = !dmaBuf; #else tft.pushPixels(&usTemp[0][0], iCount); #endif iWidth -= iCount; } } } /* GIFDraw() */ #endif void setup() { Serial.begin(115200); tft.begin(); tft.setRotation(2); tft.fillScreen(TFT_BLACK); gif.begin(BIG_ENDIAN_PIXELS); } #ifdef NORMAL_SPEED // Render at rate that is GIF controlled void loop() { #ifdef GIF_DEMO1 if (gif.open((uint8_t *)GIF_DEMO1, sizeof(GIF_DEMO1), GIFDraw)) { Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low while (gif.playFrame(true, NULL)) { yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } #endif #ifdef GIF_DEMO2 if (gif.open((uint8_t *)GIF_DEMO2, sizeof(GIF_DEMO2), GIFDraw)) { Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low while (gif.playFrame(true, NULL)) { yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } #endif #ifdef GIF_DEMO3 if (gif.open((uint8_t *)GIF_DEMO3, sizeof(GIF_DEMO3), GIFDraw)) { Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low while (gif.playFrame(true, NULL)) { yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } #endif #ifdef GIF_DEMO4 if (gif.open((uint8_t *)GIF_DEMO4, sizeof(GIF_DEMO4), GIFDraw)) { tft.fillScreen(TFT_BLACK); Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low while (gif.playFrame(true, NULL)) { yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } #endif #ifdef GIF_DEMO5 if (gif.open((uint8_t *)GIF_DEMO5, sizeof(GIF_DEMO5), GIFDraw)) { Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); tft.startWrite(); // The TFT chip slect is locked low while (gif.playFrame(true, NULL)) { yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices } #endif } #else // Test maximum rendering speed void loop() { long lTime = micros(); int iFrames = 0; if (gif.open((uint8_t *)GIF_DEMO4, sizeof(GIF_DEMO4), GIFDraw)) { tft.startWrite(); // For DMA the TFT chip slect is locked low while (gif.playFrame(false, NULL)) { // Each loop renders one frame iFrames++; yield(); } gif.close(); tft.endWrite(); // Release TFT chip select for other SPI devices lTime = micros() - lTime; Serial.print(iFrames / (lTime / 1000000.0)); Serial.println(" fps"); } } #endif
素材原图: 前面代码示例中前三个素材是冰墩墩,结果上传之后提示我图片违规。 就贼离谱,连带着我后面拍的LCD实际运行的视频也上传不了。试了几次都不行,累了,毁灭吧。 想要看效果的同学可以在文章底部的的链接里面下载。
运行结果: 上面也说了,原本想展示一下运行效果的,结果冰墩墩的这几个图上传之后老是说我违规,算了,不想搞了,就这样吧。
上面的示例源码是按GIF本身正常的速度运行的,其实也可以全速运行。全速运行的刷新率主要取决于GIF里面每张图片的像素和颜色复杂度,我测试过几个GIF,一些简单的素材刷新率能达到60fps,一般普通240*240像素的GIF刷新率在20fps左右。 但是要注意的是我现在测试的结果是基于动图数据存到spi flash的情况,CPU和flash的频率都调到最大了,LCD这边spi的通讯速率则是27MHz,如果数据存到RAM,SPI通讯速率再快一些,刷新率应该还能再高一些。 全速下运行的结果如下: 手机拍摄LCD屏幕会有很明显的色差和纹波,这个没法消除,只有这个能发出来,将就着看吧。
这一讲简单介绍了在Arduino环境下使用LCD显示GIF动图,整个流程总的来说还是不难的,把驱动调好之后直接凋库显示就完了。如果还有什么问题,欢迎在评论区留言或者私信给我。
想要源代码、素材或图片处理工具的自行下载: 链接:https://pan.baidu.com/s/1Ptc2F9yYrjCQJkycG129wg 提取码:4a1i
Arduino开发教程汇总: https://blog.csdn.net/ShenZhen_zixian/article/details/121659482
原文链接:https://blog.csdn.net/ShenZhen_zixian/article/details/122729045?utm_medium=distribute.pc_feed_blog.none-task-blog-hot_rank_bottoming-4.nonecasedepth_1-utm_source=distribute.pc_feed_blog.none-task-blog-hot_rank_bottoming-4.nonecase