1. 程式人生 > 其它 >esp32 lvgl播放視訊於oled,讀取SD卡,定時器中斷使用

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, "w");
       /* 寫入資料到檔案 
       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;變數聲明瞭一下,讓其可以在主程式中被呼叫。