esp32 lvgl播放视频于oled,读取SD卡,定时器中断使用

思路:我就用的lvgl传lv_img_dsc_t结构体数据的方式,将图片转换为c矩阵数据保存为.bin文件与SD卡中,然后esp32读取sd卡图片数据,保存于定义的

lv_img_dsc_t变量中,然后将定义的lv_img_dsc_t结构体变量传给lvgl的lv_img控件,以显示图片,定时刷新每一帧图片就完成视频播放的效果。

备注:

1,不知道为什么,再定时器中断函数中读取sd卡,esp32一直重启,原因未知,所以读取sd代码要放在loop()循环里。

2,不知道为什么,将lv_task_handler()放入定时器中断函数中,定时调用,esp32也一直重启,原因未知,所以lv_task_handler();也要放在loop()循环中。

步骤:

1,将视频变为一帧一帧的图片,这个百度很多方法,我就直接网上下载的图片。

2,将图片的分辨率改为自己显示屏的分辨率,我就python自动处理的,因为图片数量有点多,python还可以看一看图片一帧一帧的放出来的视频效果,源码如下:

import time
import cv2



if __name__ == '__main__':
    #测试图片视频效果
    # for i in range(5355):
    #     str = 'E:/DC BREAKER/python/Project/ESP32/bad_apple_320_240_25fps/'+'%04d'%(i)+'.jpg'
    #     print("当前显示图片="+str)
    #     img = cv2.imread(str)
    #     cv2.imshow('image', img)
    #     cv2.waitKey(20)

    #图片分辨率修改
    for i in range(5355):
        str = 'E:/DC BREAKER/python/Project/ESP32/bad_apple_320_240_25fps/'+'%04d'%(i)+'.jpg'
        print("当前处理图片="+str)
        img = cv2.imread(str)
        img_200x200 = cv2.resize(img, (128, 64))
        strsave = 'E:/DC BREAKER/python/Project/ESP32/bad_apple_128_64_25fps/' + '%04d' % (i) + '.jpg'
        cv2.imwrite(strsave, img_200x200)

    # # 显示一张照片
    # img = cv2.imread('E:/DC BREAKER/python/Project/ESP32/bad_apple_320_240_25fps/0000.jpg')
    # cv2.imshow('image', img)
    # cv2.waitKey(0)

3,将改好的图片放入官网的图片转c数组在线小工具里,将图片转换为.c文件保存下来,网站如下:

https://lvgl.io/tools/imageconverter

这个工具里面,可以将图片全部选中一起导入进去,它就会自己一个一个图片的将图片数据转换为c数组保存在本地.c文件。

4,将图片的.c文件里的图片数据读出来,然后保存到.bin文件里,这些.bin文件就是保存每一帧图片转换的数据的二进制文件,将他们导入SD卡,然后esp32读取SD卡中的这些.bin文件的图片数据,就可以在lvgl显示图片啦。这里我用的c语言IDE将.c文件自动转化为.bin文件,源码如下:

#include"stdio.h" 
#include"string.h"



//16进制字符串转10进制数 
unsigned char strtohex(char str[4]){
    char mystrhex[2];
    unsigned char a,b;
    mystrhex[0]=str[2];//10位 
    mystrhex[1]=str[3];//个位 
    if(mystrhex[0]>='0'&&mystrhex[0]<='9'){
        a=mystrhex[0]-48;
    }
    else if(mystrhex[0]>='a'&&mystrhex[0]<='f'){
        a=mystrhex[0]-87;
    }
    if(mystrhex[1]>='0'&&mystrhex[1]<='9'){
        b=mystrhex[1]-48;
    }
    else if(mystrhex[1]>='a'&&mystrhex[1]<='f'){
        b=mystrhex[1]-87;
    }
    return a*16+b;    
}

int main(){
    //*********************
    int t;
    int i=0;
    int j=0;
    int cnt=0;
    FILE *fp;
    char mys[4];//用于保存16进制数据字符串
    /**************参数修改区**********************************/
    unsigned char data[1032];//读取图片数据保存buffer,我的有1032个数据    
    char filebuffdef[]="E:/DC BREAKER/DEV-CPP/test1/data/";    //读取文件目录 
    char savefilebuffdef[]="E:/DC BREAKER/DEV-CPP/test1/databin/p"; //保存文件目录 
    /******************************************************/
    char buff[4];
    char filebuff[sizeof(filebuffdef)];
    char savefilebuff[sizeof(filebuffdef)];    
    for(j=0;j<374;j++){
        printf("开始第%d个文件",j);
        sprintf(filebuff,filebuffdef);
        sprintf(buff,"%04d",j);
        strcat(filebuff,buff);
        strcat(filebuff,".c");
        printf("%s\n",filebuff);
        fp = fopen(filebuff, "r");
        fseek(fp,0L,2);
        t=ftell(fp);
        printf("%d\n",t);
        rewind(fp);
        char readstr[t];
        i=0;
        while(!feof(fp)){
            readstr[i]=fgetc(fp);
            i++;
        }
        fclose(fp);    
        cnt=0;
        for(i=0;i<sizeof(readstr);i++){
            if(readstr[i]=='0'&&readstr[i+1]=='x'){
                mys[0]=readstr[i];
                mys[1]=readstr[i+1];
                mys[2]=readstr[i+2];
                mys[3]=readstr[i+3];
                data[cnt]=strtohex(mys);
                cnt++;
                i=i+3;
            }        
        }
        /* 打开文件用于读写 */
        sprintf(savefilebuff,savefilebuffdef);
        sprintf(buff,"%d",j);
        strcat(savefilebuff,buff);
        strcat(savefilebuff,".bin");
        printf("%s\n",savefilebuff);
       fp = fopen(savefilebuff, "wb");
       /* 写入数据到文件 
       fread(buffer,size,count,fp);buffer:存放读取到的数据块的数据缓冲区起始地址,size:为函数一次读取的一个数据块的字节长度,
       count:是所要读取的数据块个数,fp:表示文件指针 
       */
       fwrite(data, sizeof(data), 1, fp);
       fclose(fp);    
    }

    //*****************
    return(0);
}

5,esp32读取sd卡图片数据,然后一帧一帧的传入lvgl的lv_img控件,即可实现视频显示,esp32源码如下:

主函数:

#include <lvgl.h>
#include "SSD1306Wire.h" // alias for `#include "SSD1306Wire.h"`
#include "caiya_gui.h"
#include "SD.h"

unsigned char time0flag=0;//定时器使用标记寄存器
unsigned char time1flag=0;
unsigned char time2flag=0;
hw_timer_t *timer = NULL;//定义hw_timer_t 结构类型的指针
hw_timer_t *timer1 = NULL;
hw_timer_t *timer2 = NULL;

SSD1306Wire  display(0x3c, 4, 15);
static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];


unsigned char buffer[1032];
lv_img_dsc_t myimage= {{LV_IMG_CF_INDEXED_1BIT,0,0,128, 64},1033,buffer};
String filename= "/p";//读取sd卡的文件名存储寄存器

USER_DATA user_data = {{"xixi"},0};//初始化一下
/* Display flushing */
void my_disp_flush(lv_disp_drv_t* disp, const lv_area_t* area, lv_color_t* color_p)
{
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);

    for (uint16_t i = 0; i < h; i++){
      for (uint16_t j = 0; j < w; j++){
        if(color_p->full != 0){
          display.setPixel(j+area->x1, i+area->y1);
        }
        else{
          display.clearPixel(j+area->x1, i+area->y1);
        }
        color_p++;
      }
    }
   display.display();
  lv_disp_flush_ready(disp);
}

/*读取sd卡文件,lvgl显示函数*/
void mysd() {
static int pcnt=0;
  if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
//  Serial.println("SD card Ready!");
//  Serial.printf("SD.cardSize = %lld \r\n", SD.cardSize());
//  Serial.printf("SD.cardType = %d \r\n", SD.cardType());
  filename= "/badapple/p";
  filename.concat(pcnt);
  filename.concat(".bin");
  File file = SD.open(filename, FILE_READ);
//  Serial.printf("is there /badapple/p1.bin? :%d \r\n", SD.exists("/badapple/p1.bin"));
  file.read(buffer,1032);
  file.close();
  SD.end();
  myimage.data=buffer;
  lv_img_set_src(img1, &myimage);
  if(pcnt==374)pcnt=0;
  else pcnt++;
}


//  函数名称:onTimer()
//  函数功能:中断服务的功能,它必须是一个返回void(空)且没有输入参数的函数
//  为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性
void IRAM_ATTR TimerEvent()
{
  if(time0flag==0)time0flag=1;
  else time0flag=0;
}
//定时器1中断服务函数
void IRAM_ATTR Timer1Event(){
   if(time1flag==0)time1flag=1;
   else time1flag=0;
}
//定时器2中断服务函数
void IRAM_ATTR Timer2Event(){
  if(time2flag==0){
    digitalWrite(2,0);
    time2flag=1;
  }
  else{
    digitalWrite(2,1);
    time2flag=0;
  }
}

void setup()
{
  Serial.begin(115200); /* prepare for possible serial debug */
  pinMode(2,OUTPUT);
  digitalWrite(2,1);
  pinMode(0,INPUT_PULLUP);
  lv_init();
  display.init();
  lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);
  /*Initialize the display*/
  lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = 128;
  disp_drv.ver_res = 64;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.buffer = &disp_buf;
  lv_disp_drv_register(&disp_drv);
  
  set_caiya_gui();
  lv_scr_load_anim(scr1, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 500, 5000, false); // 加载屏幕TWO,动画效果为LV_SCR_LOAD_ANIM_FADE_ON,切换时间为500ms,延迟5000ms后从第一屏开始切换,切换完成后删除屏幕一

/*函数名称:timerBegin()
  函数功能:Timer初始化,分别有三个参数
  函数输入:1. 定时器编号(0到3,对应全部4个硬件定时器)
           2. 预分频器数值(ESP32计数器基频为80M,80分频单位是微秒)
           3. 计数器向上(true)或向下(false)计数的标志
  函数返回:一个指向 hw_timer_t 结构类型的指针*/
  timer = timerBegin(0, 80, true);
/*函数名称:timerAttachInterrupt()
  函数功能:绑定定时器的中断处理函数,分别有三个参数
  函数输入:1. 指向已初始化定时器的指针(本例子:timer)
           2. 中断服务函数的函数指针
           3. 表示中断触发类型是边沿(true)还是电平(false)的标志
  函数返回:无*/
  timerAttachInterrupt(timer, &TimerEvent, true);
/*函数名称:timerAlarmWrite()
  函数功能:指定触发定时器中断的计数器值,分别有三个参数
  函数输入:1. 指向已初始化定时器的指针(本例子:timer)
           2. 第二个参数是触发中断的计数器值(1000000 us -> 1s)
           3. 定时器在产生中断时是否重新加载的标志
  函数返回:无*/ 
  timerAlarmWrite(timer, 20000, true);
  timerAlarmEnable(timer); //  使能定时器

  timer1 = timerBegin(1, 80, true);
  timerAttachInterrupt(timer1, &Timer1Event, true);
  timerAlarmWrite(timer1, 5000, true);
  timerAlarmEnable(timer1); //  使能定时器

  timer2 = timerBegin(2, 80, true);
  timerAttachInterrupt(timer2, &Timer2Event, true);
  timerAlarmWrite(timer2, 500000, true);
  timerAlarmEnable(timer2); //  使能定时器
}
int flag = 0;
unsigned char playflag=0;//播放视频标记位
void loop()
{
  if(time1flag==1)lv_task_handler(); /* let the GUI do its work */
  if((time0flag==1)&&(playflag==1))mysd();
  if(digitalRead(0)==0)
  {
    while(digitalRead(0)==0);
    if(flag==1)flag=0;
    else flag++;
    Serial.println(flag);
    if(flag==1){
      playflag=0;
      lv_scr_load_anim(scr2, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 500, 0, false);
    }
    else if(flag==0){
      playflag=1;
      lv_scr_load_anim(scr1, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 500, 0, false);
    }
    //手动发送事件
    //方式 1:发送用户自定义事件,同时携带用户自定义数据
    user_data.age=(unsigned char)flag;
    Serial.println(user_data.age);
    lv_event_send(label2,USER_EVENT_1,&user_data);
     
  }

  
}

其他源文件参考上一篇博客,都差不多的,只是将定义的lv_obj_t* img1;变量声明了一下,让其可以在主程序中被调用。

***********************************************************************************************

SD的库中默认SD卡用vspi与esp32通信,这个在SD.h中源码一看便知:

 

 SS表示IO5,在Pins_Arduino.h中可以查到:

 

 SPI在SPI.cpp中有赋值:

 

 可以看到默认给的是VSPI参数,然后在SPI.cpp中的这个函数即可看到默认值是怎么赋值的,修改管脚也是在这儿弄。

 

 所以如果要是用HSPI,需要如下代码即可:

 HSPI的默认MISO引脚是12,而12在ESP32中是用于上电时设置flash电平的,上电之前上拉会导致芯片无法启动,因此我们需要将默认的引脚换一个,比如替换为26。

 

posted @ 2022-03-13 19:07  菜芽caiya  阅读(3519)  评论(0编辑  收藏  举报