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;變數聲明瞭一下,讓其可以在主程式中被呼叫。