Arduino应用开发——LCD显示GIF动图

18 篇文章 68 订阅
15 篇文章 23 订阅

Arduino应用开发——LCD显示GIF动图

前言

前面我已经介绍过了如何在Arduino环境下用LCD显示文本、图案和图片,这一讲主要介绍一下GIF动图的显示。

1 硬件介绍

1.1 硬件配置

本文的硬件配置如下:

模块型号说明
LCDST7789LCD常用的驱动IC有很多,如:ILI9341、ST7735等,不同的驱动IC,驱动代码也是有区别的
注:驱动IC型号和屏幕型号是不同的,不管屏幕的厂家是哪个,屏幕尺寸是多大,只有驱动IC型号一样,驱动代码都是一样的
ESP8266ESP-12F这是安信可的一款模组,内部主要是用乐鑫的ESP8266EX再加上一个片外FLASH组成
ESP32ESP-WROOM-32MCU是乐鑫的一款芯片,开发板型号ESP32 DEVKITV1

注:我这里以ESP8266和ESP32为例讲解,实际上根据自己的MCU选择一种即可,方法和原理都是一样的。

1.2 硬件连接

ESP8266接线如下:

esp8266lcd说明
VCCVCC电源正
GNDGND电源负
GPIO14\HSPI_CLKCLKSPI时钟线
GPIO13\HSPI_MOSISDISPI数据线,esp8266输出,lcd输入
GPIO12\HSPI_MISOSDOSPI数据线,esp8266输入,lcd输出,注:该引脚一般可以不用,除非你要读取LCD的信息
GPIO4CSSPI片选
GPIO5WR(D/C)并口时作为写入信号/SPI时作为命令或参数的选择
RSTRSTLCD复位引脚,可以用普通IO控制,引脚不足的情况下也可以和esp8266的复位引脚接到一起

特别说明:不同厂家做的LCD对这几个引脚的命名可能会有差异,但意思是一样的。

ESP32接线如下:

esp32lcd说明
VCCVCC电源正
GNDGND电源负
GPIO18\SPI_CLKCLKSPI时钟线
GPIO23\SPI_MOSISDISPI数据线,esp32输出,lcd输入
GPIO19\SPI_MISOSDOSPI数据线,esp32输入,lcd输出,注:该引脚一般可以不用,除非你要读取LCD的信息
GPIO15CSSPI片选
GPIO5WR(D/C)并口时作为写入信号/SPI时作为命令或参数的选择
GPIO4RSTLCD复位引脚,可以用普通IO控制,引脚不足的情况下也可以和esp32的复位引脚接到一起

特别说明:不同厂家做的LCD对这几个引脚的命名可能会有差异,但意思是一样的。

2 开发环境搭建

2.1 安装开发板

关于ESP8266和ESP32的Arduino环境搭建我之前出过教程了,这里就不多说了,不懂的同学可以先看下我之前的博客。
esp8266开发入门教程(基于Arduino)——环境安装
ESP32-S2 Arduino开发环境搭建

2.2 安装库

打开Arduino IDE,依次打开 工具 -> 管理库…
在搜索框输入需要安装的库名称,找到对应的库,点击安装即可。

本文需要使用的Arduino库如下:

Arduino库版本说明
TFT_eSPI2.4.2该库通过SPI方式驱动LCD,支持多种LCD常用驱动IC
AnimatedGIF1.4.6GIF的解码库,用来显示GIF动图

3 LCD驱动的使用和测试

关于TFT_eSPI 库的使用我前面已经介绍过几次了,这里就不再讲解了,不懂的同学可以先看下我之前的博客。
esp8266应用教程——TFT LCD
Arduino应用开发——LCD显示图片

4 将GIF动图转成数据

我这里用的转换工具是在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 编写应用程序

示例代码如下:
提示:示例里面同时使用了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

6 测试验证

素材原图:
前面代码示例中前三个素材是冰墩墩,结果上传之后提示我图片违规。
请添加图片描述
就贼离谱,连带着我后面拍的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

STM32F103C8T6是一款常用的单片机,它具有丰富的外设资源和强大的处理能力,可以用于各种应用场景,包括TFT LCD动图播放。下面是一个简单的示例代码,用于在STM32F103C8T6上播放动图: ```c #include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "stm32f10x_spi.h" // 定义TFT LCD的引脚连接 #define LCD_RST_PIN GPIO_Pin_0 #define LCD_RST_PORT GPIOA #define LCD_CS_PIN GPIO_Pin_1 #define LCD_CS_PORT GPIOA #define LCD_DC_PIN GPIO_Pin_2 #define LCD_DC_PORT GPIOA #define LCD_SDA_PIN GPIO_Pin_7 #define LCD_SDA_PORT GPIOA #define LCD_SCL_PIN GPIO_Pin_5 #define LCD_SCL_PORT GPIOA // 初始化TFT LCD void LCD_Init(void) { // 初始化引脚 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = LCD_RST_PIN | LCD_CS_PIN | LCD_DC_PIN | LCD_SDA_PIN | LCD_SCL_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化SPI接口 SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); } // 播放动图 void PlayAnimation(void) { // TODO: 在这里编写动图播放的代码 } int main(void) { // 初始化TFT LCD LCD_Init(); // 播放动图 PlayAnimation(); while (1) { // 主循环 } } ``` 请注意,上述代码只是一个简单的示例,具体的TFT LCD播放动图的实现需要根据具体的LCD型号和驱动芯片进行调整。你需要根据你使用的TFT LCD的规格书和驱动芯片的手册来编写相应的代码。
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值