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