• 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。

  • 相关阅读:
    Win7。56个进程让我头疼
    bzoj2843极地旅行社
    bzoj2751[HAOI2012]容易题(easy)
    bzoj3442学习小组
    bzoj4423[AMPPZ2013]Bytehattan
    bzoj4591[Shoi2015]超能粒子炮·改
    bzoj2299[HAOI2011]向量
    bzoj3223Tyvj 1729 文艺平衡树
    bzoj2563阿狸和桃子的游戏
    bzoj3673可持久化并查集 by zky&&bzoj3674可持久化并查集加强版
  • 原文地址:https://www.cnblogs.com/caiya/p/16001412.html
Copyright © 2020-2023  润新知