思路:我就用的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。