esp32(tftlcd) lvgl外部 SD 卡图片显示,lvgl动画演示,esp32wifi使用(获取时间,天气)
lv_img 就是一个图片控件,它就是根据你传入的图片源来显示你想要的图片,littleVGL 为
了提供最大的灵活性,它支持如下三种图片源方式:
1) 内部 C 数组,用 lv_img_dsc_t 结构体来进行描述
2) 外部存储文件,比如 SD 卡或者 U 盘上的图片文件
3) LV_SYMBOL_XXX 形式的图标字体或者文本,此时 lv_img 图片就相当于一个 lv_label 标签控件
如果你确定好图片源之后,就可以通过 lv_img_set_src(img, src)接口来显示此图片,此接口内部会自动判断出 src 是属于哪一种图片源方式,然后选择
相应的解析程序把图片给显示出来.
前面的博客使用了第一种方式,然后也将C 数组存储在sd卡,然后读取sd数据,构建 lv_img_dsc_t 结构体来进行描述。但是可能是构建的数据不对,或者
lvgl不太兼容这种方式,显示效果不好,图片有很多噪点,所以想了想,当图片数据在sd卡上时,还是的用第二种方式。
第 2 种外部存储文件图片源方式,它是把图片数据文件放到了外部的储存介质上,比如 SD 卡或者 U 盘上,所以这里你必须得另外用到 littleVGL 的文件系统
模块,然后对于这个外部存储文件的格式这里又可以分为俩大类,一类是图片的最原始格式,如.png 文件,它不需要经过任何转换,你只需要把这张.png 图片直
接放到外部的存储介质上就可以了,但是对于这种方式,littleVGL是不能直接支持的,你必须得外加实现 png图片的解析库,
可以参考官方的 https://blog.littlevgl.com/2018-10-05/png_converter 资料,对于另外一类是.bin文件格式,它是需要经过在线图片转换工具的转换,拿到转换
后的.bin 文件后,你就可以调用 lv_img_set_src(img, "S:folder1/my_img.bin")接口把图片给显示出来了,不过前提就是你得先实现 littleVGL 的文件系统驱动哦!
我这里使用第二种方式。
当图片作为bin文件读取时,需要esp32运行文件系统,具体可以参考ESP-IDF demo中的spiffs。此外,还需要实现 lvgl 文件系统中的文件操作函数。具体
文件参考下图。
Spiffs是一个用于嵌入式目标上的SPI NOR flash设备的文件系统。
Spiffs有以下几个特点:
1、小(嵌入式)目标,没有堆的少量RAM
2、只有大范围的数据(块)才能被删除
3、擦除将把所有块中的位重置为1
4、写操作把1变成0
5、0只能被擦除成1
6、磨损均衡
SPIFFS能做什么:
1、专门为低ram使用而设计
2、使用静态大小的ram缓冲区,与文件的数量无关
3、类可移植操作系统接口:打开、关闭、读、写、查找、统计等
4、它可以在任何NOR闪存上运行,不仅是SPI闪存——理论上也可以在微处理器的嵌入式闪存上运行
5、多个spiffs配置可以在相同的目标上运行—甚至可以在相同的SPI闪存设备上运行
6、实现静态磨损调平(也就是flash的寿命维护)
7、内置文件系统一致性检查
8、高度可配置的
用法:
1,/*Copy this file as "lv_port_fs.h" and set this value to "1" to enable content*/,.c文件类似
2,然后在.h文件申明初始化函数,这个文件类容就这么多啦。
3,.c文件,设置sd卡的名字,应该算是
4,.c文件,这个结构体定义的话,直接使用ff.h里面的变量,如下一段代码所示:
/* Create a type to store the required data about your file. * If you are using a File System library * it already should have a File type. * For example FatFS has `FIL`. In this case use `typedef FIL file_t`*/ typedef struct { /*Add the data you need to store about a file*/ uint32_t dummy1; uint32_t dummy2; }file_t; /*Similarly to `file_t` create a type for directory reading too */ typedef struct { /*Add the data you need to store about directory reading*/ uint32_t dummy1; uint32_t dummy2; }dir_t;
/* Create a type to store the required data about your file.*/ typedef FIL file_t; /*Similarly to `file_t` create a type for directory reading too */ typedef FF_DIR dir_t;
5,.c文件,初始化函数,也就是.h申明的函数的内容,这个函数参考下面这个修改,这里就实现 littleVGL 的文件系统驱动 。然后fs_init()函数的话,就是
初始化sd卡,所以可以不用在这个文件初始化,另外的文件去初始化即可,参考下一段代码,其实就是把sd卡挂载起来就行,怎么搞参考前面的博客。
void lv_fs_if_init(void) { /*---------------------------------------------------- * Initialize your storage device and File System * -------------------------------------------------*/ fs_init(); /*--------------------------------------------------- * Register the file system interface in LittlevGL *--------------------------------------------------*/ /* Add a simple drive to open images */ lv_fs_drv_t fs_drv; /*A driver descriptor*/ lv_fs_drv_init(&fs_drv); /*Set up fields...*/ fs_drv.file_size = sizeof(file_t); fs_drv.letter = DRIVE_LETTER; fs_drv.open_cb = fs_open; fs_drv.close_cb = fs_close; fs_drv.read_cb = fs_read; fs_drv.write_cb = fs_write; fs_drv.seek_cb = fs_seek; fs_drv.tell_cb = fs_tell; fs_drv.free_space_cb = fs_free; fs_drv.size_cb = fs_size; fs_drv.remove_cb = fs_remove; fs_drv.rename_cb = fs_rename; fs_drv.trunc_cb = fs_trunc; fs_drv.rddir_size = sizeof(dir_t); fs_drv.dir_close_cb = fs_dir_close; fs_drv.dir_open_cb = fs_dir_open; fs_drv.dir_read_cb = fs_dir_read; lv_fs_drv_register(&fs_drv); }
bool SdCard::init() { SPIClass* sd_spi = new SPIClass(HSPI); // another SPI if (!SD.begin(15, *sd_spi)) // SD-Card SS pin is 15 { Serial.println("Card Mount Failed"); return false; } uint8_t cardType = SD.cardType(); if (cardType == CARD_NONE) { Serial.println("No SD card attached"); return false; } Serial.print("SD Card Type: "); if (cardType == CARD_MMC) { Serial.println("MMC"); } else if (cardType == CARD_SD) { Serial.println("SDSC"); } else if (cardType == CARD_SDHC) { Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf("SD Card Size: %lluMB\n", cardSize); return true; }
然后就可以了,在主函数里include这个头文件,littleVGL 的文件系统驱动 算是搞好了,再在lvgl上移值好TFTLCD,主程序初始化好,sd卡,屏幕,文件系统即可使用,将
图片在https://lvgl.io/tools/imageconverter里面转换为.bin文件,然后存储到sd卡里面,即可使用,这个官方在线小工具是可以好多图片自动处理的。
显示图片参考下面代码:
int len = sprintf(buf, "S:/hold/%04d.bin", frame_id++); buf[len] = 0; lv_img_set_src(img1,buf); if (frame_id == 311) frame_id = 0;
**********************
所有代码:
1,display.h,display.c
这里就是实现了TFTLCD移植到LVGL里面,然后TFTLCD的使用以及移植到LVGL参考前面的博客。
#include "display.h" #include <TFT_eSPI.h> /* TFT pins should be set in path/to/Arduino/libraries/TFT_eSPI/User_Setups/Setup24_ST7789.h */ TFT_eSPI tft = TFT_eSPI(); static lv_disp_buf_t disp_buf; static lv_color_t buf[LV_HOR_RES_MAX * 10]; 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); tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); tft.pushColors(&color_p->full, w * h, true); tft.endWrite(); lv_disp_flush_ready(disp); } void Display::init() { lv_init(); tft.begin(); /* TFT init */ //tft.setRotation(4); /* mirror */ 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 = 240; disp_drv.ver_res = 240; disp_drv.flush_cb = my_disp_flush; disp_drv.buffer = &disp_buf; lv_disp_drv_register(&disp_drv); } void Display::routine() { lv_task_handler(); }
#ifndef DISPLAY_H #define DISPLAY_H #include <lvgl.h> class Display { private: public: void init(); void routine(); }; #endif
2,sd_card.h,sd_card.c
这里就是实现sd卡的初始化,具体怎么弄参考前面的博客,然后里面还有一些esp32操作sd卡的函数,跟前面博客中读取sd卡差不多,应该很好理解,我也是复制的别人的。
#include "sd_card.h" bool SdCard::init() { SPIClass* sd_spi = new SPIClass(HSPI); // another SPI if (!SD.begin(15, *sd_spi)) // SD-Card SS pin is 15 { Serial.println("Card Mount Failed"); return false; } uint8_t cardType = SD.cardType(); if (cardType == CARD_NONE) { Serial.println("No SD card attached"); return false; } Serial.print("SD Card Type: "); if (cardType == CARD_MMC) { Serial.println("MMC"); } else if (cardType == CARD_SD) { Serial.println("SDSC"); } else if (cardType == CARD_SDHC) { Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf("SD Card Size: %lluMB\n", cardSize); return true; } void SdCard::listDir(const char* dirname, uint8_t levels) { Serial.printf("Listing directory: %s\n", dirname); File root = SD.open(dirname); if (!root) { Serial.println("Failed to open directory"); return; } if (!root.isDirectory()) { Serial.println("Not a directory"); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { Serial.print(" DIR : "); Serial.println(file.name()); if (levels) { listDir(file.name(), levels - 1); } } else { Serial.print(" FILE: "); Serial.print(file.name()); Serial.print(" SIZE: "); Serial.println(file.size()); } file = root.openNextFile(); } } void SdCard::createDir(const char* path) { Serial.printf("Creating Dir: %s\n", path); if (SD.mkdir(path)) { Serial.println("Dir created"); } else { Serial.println("mkdir failed"); } } void SdCard::removeDir(const char* path) { Serial.printf("Removing Dir: %s\n", path); if (SD.rmdir(path)) { Serial.println("Dir removed"); } else { Serial.println("rmdir failed"); } } void SdCard::readFile(const char* path) { Serial.printf("Reading file: %s\n", path); File file = SD.open(path); if (!file) { Serial.println("Failed to open file for reading"); return; } Serial.print("Read from file: "); while (file.available()) { Serial.write(file.read()); } file.close(); } String SdCard::readFileLine(const char* path, int num = 1) { Serial.printf("Reading file: %s line: %d\n", path, num); File file = SD.open(path); if (!file) { return ("Failed to open file for reading"); } char* p = buf; while (file.available()) { char c = file.read(); if (c == '\n') { num--; if (num == 0) { *(p++) = '\0'; String s(buf); s.trim(); return s; } } else if (num == 1) { *(p++) = c; } } file.close(); return String("error parameter!"); } void SdCard::writeFile(const char* path, const char* message) { Serial.printf("Writing file: %s\n", path); File file = SD.open(path, FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing"); return; } if (file.print(message)) { Serial.println("File written"); } else { Serial.println("Write failed"); } file.close(); } void SdCard::appendFile(const char* path, const char* message) { Serial.printf("Appending to file: %s\n", path); File file = SD.open(path, FILE_APPEND); if (!file) { Serial.println("Failed to open file for appending"); return; } if (file.print(message)) { Serial.println("Message appended"); } else { Serial.println("Append failed"); } file.close(); } void SdCard::renameFile(const char* path1, const char* path2) { Serial.printf("Renaming file %s to %s\n", path1, path2); if (SD.rename(path1, path2)) { Serial.println("File renamed"); } else { Serial.println("Rename failed"); } } void SdCard::deleteFile(const char* path) { Serial.printf("Deleting file: %s\n", path); if (SD.remove(path)) { Serial.println("File deleted"); } else { Serial.println("Delete failed"); } } void SdCard::readBinFromSd(const char* path, uint8_t* buf) { File file = SD.open(path); size_t len = 0; if (file) { len = file.size(); size_t flen = len; while (len) { size_t toRead = len; if (toRead > 512) { toRead = 512; } file.read(buf, toRead); len -= toRead; } file.close(); } else { Serial.println("Failed to open file for reading"); } } void SdCard::writeBinToSd(const char* path, uint8_t* buf) { File file = SD.open(path, FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing"); return; } size_t i; for (i = 0; i < 2048; i++) { file.write(buf, 512); } file.close(); } void SdCard::fileIO(const char* path) { File file = SD.open(path); static uint8_t buf[512]; size_t len = 0; uint32_t start = millis(); uint32_t end = start; if (file) { len = file.size(); size_t flen = len; start = millis(); while (len) { size_t toRead = len; if (toRead > 512) { toRead = 512; } file.read(buf, toRead); len -= toRead; } end = millis() - start; Serial.printf("%u bytes read for %u ms\n", flen, end); file.close(); } else { Serial.println("Failed to open file for reading"); } file = SD.open(path, FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing"); return; } size_t i; start = millis(); for (i = 0; i < 2048; i++) { file.write(buf, 512); } end = millis() - start; Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); file.close(); }
#ifndef SD_CARD_H #define SD_CARD_H #include "FS.h" #include "SD.h" #include "SPI.h" class SdCard { private: char buf[128]; public: bool init(); void listDir( const char* dirname, uint8_t levels); void createDir( const char* path); void removeDir( const char* path); void readFile( const char* path); String readFileLine( const char* path, int num); void writeFile( const char* path, const char* message); void appendFile( const char* path, const char* message); void renameFile( const char* path1, const char* path2); void deleteFile( const char* path); void readBinFromSd(const char* path, uint8_t* buf); void writeBinToSd(const char* path, uint8_t* buf); void fileIO( const char* path); }; extern SdCard tf; #endif
3,lv_port_fatfs.h,lv_port_fatfs.c
这个就是实现LVGL文件系统,按照上面介绍更改好就可以。
/** * @file lv_fs_fatfs.c * For ESP32 */ /********************* * INCLUDES *********************/ #include "lv_port_fatfs.h" /********************* * DEFINES *********************/ #define DRIVE_LETTER 'S' /********************** * TYPEDEFS **********************/ /* Create a type to store the required data about your file.*/ typedef FIL file_t; /*Similarly to `file_t` create a type for directory reading too */ typedef FF_DIR dir_t; /********************** * STATIC PROTOTYPES **********************/ static void fs_init(void); static lv_fs_res_t fs_open(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode); static lv_fs_res_t fs_close(lv_fs_drv_t* drv, void* file_p); static lv_fs_res_t fs_read(lv_fs_drv_t* drv, void* file_p, void* buf, uint32_t btr, uint32_t* br); static lv_fs_res_t fs_write(lv_fs_drv_t* drv, void* file_p, const void* buf, uint32_t btw, uint32_t* bw); static lv_fs_res_t fs_seek(lv_fs_drv_t* drv, void* file_p, uint32_t pos); static lv_fs_res_t fs_size(lv_fs_drv_t* drv, void* file_p, uint32_t* size_p); static lv_fs_res_t fs_tell(lv_fs_drv_t* drv, void* file_p, uint32_t* pos_p); static lv_fs_res_t fs_remove(lv_fs_drv_t* drv, const char* path); static lv_fs_res_t fs_trunc(lv_fs_drv_t* drv, void* file_p); static lv_fs_res_t fs_rename(lv_fs_drv_t* drv, const char* oldname, const char* newname); static lv_fs_res_t fs_free(lv_fs_drv_t* drv, uint32_t* total_p, uint32_t* free_p); static lv_fs_res_t fs_dir_open(lv_fs_drv_t* drv, void* dir_p, const char* path); static lv_fs_res_t fs_dir_read(lv_fs_drv_t* drv, void* dir_p, char* fn); static lv_fs_res_t fs_dir_close(lv_fs_drv_t* drv, void* dir_p); /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_fs_if_init(void) { /*---------------------------------------------------- * Initialize your storage device and File System * -------------------------------------------------*/ fs_init(); /*--------------------------------------------------- * Register the file system interface in LittlevGL *--------------------------------------------------*/ /* Add a simple drive to open images */ lv_fs_drv_t fs_drv; /*A driver descriptor*/ lv_fs_drv_init(&fs_drv); /*Set up fields...*/ fs_drv.file_size = sizeof(file_t); fs_drv.letter = DRIVE_LETTER; fs_drv.open_cb = fs_open; fs_drv.close_cb = fs_close; fs_drv.read_cb = fs_read; fs_drv.write_cb = fs_write; fs_drv.seek_cb = fs_seek; fs_drv.tell_cb = fs_tell; fs_drv.free_space_cb = fs_free; fs_drv.size_cb = fs_size; fs_drv.remove_cb = fs_remove; fs_drv.rename_cb = fs_rename; fs_drv.trunc_cb = fs_trunc; fs_drv.rddir_size = sizeof(dir_t); fs_drv.dir_close_cb = fs_dir_close; fs_drv.dir_open_cb = fs_dir_open; fs_drv.dir_read_cb = fs_dir_read; lv_fs_drv_register(&fs_drv); } /********************** * STATIC FUNCTIONS **********************/ /* Initialize your Storage device and File system. */ static void fs_init(void) { ///* Initialisation de la carte SD */ //Serial.print(F("Init SD card... ")); //SPIClass* sd_spi = new SPIClass(HSPI); // another SPI //if (!SD.begin(15, *sd_spi)) // SD-Card SS pin is 15 //{ // Serial.println("Card Mount Failed"); // return; //} } /** * Open a file * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_open(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) { uint8_t flags = 0; if (mode == LV_FS_MODE_WR) flags = FA_WRITE | FA_OPEN_ALWAYS; else if (mode == LV_FS_MODE_RD) flags = FA_READ; else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = FA_READ | FA_WRITE | FA_OPEN_ALWAYS; FRESULT res = f_open((file_t*)file_p, path, flags); if (res == FR_OK) { f_lseek((file_t*)file_p, 0); return LV_FS_RES_OK; } else { return LV_FS_RES_UNKNOWN; } } /** * Close an opened file * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable. (opened with lv_ufs_open) * @return LV_FS_RES_OK: no error, the file is read * any error from lv_fs_res_t enum */ static lv_fs_res_t fs_close(lv_fs_drv_t* drv, void* file_p) { f_close((file_t*)file_p); return LV_FS_RES_OK; } /** * Read data from an opened file * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable. * @param buf pointer to a memory block where to store the read data * @param btr number of Bytes To Read * @param br the real number of read bytes (Byte Read) * @return LV_FS_RES_OK: no error, the file is read * any error from lv_fs_res_t enum */ static lv_fs_res_t fs_read(lv_fs_drv_t* drv, void* file_p, void* buf, uint32_t btr, uint32_t* br) { FRESULT res = f_read((file_t*)file_p, buf, btr, (UINT*)br); if (res == FR_OK) return LV_FS_RES_OK; else return LV_FS_RES_UNKNOWN; } /** * Write into a file * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable * @param buf pointer to a buffer with the bytes to write * @param btr Bytes To Write * @param br the number of real written bytes (Bytes Written). NULL if unused. * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_write(lv_fs_drv_t* drv, void* file_p, const void* buf, uint32_t btw, uint32_t* bw) { FRESULT res = f_write((file_t*)file_p, buf, btw, (UINT*)bw); if (res == FR_OK) return LV_FS_RES_OK; else return LV_FS_RES_UNKNOWN; } /** * Set the read write pointer. Also expand the file size if necessary. * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable. (opened with lv_ufs_open ) * @param pos the new position of read write pointer * @return LV_FS_RES_OK: no error, the file is read * any error from lv_fs_res_t enum */ static lv_fs_res_t fs_seek(lv_fs_drv_t* drv, void* file_p, uint32_t pos) { f_lseek((file_t*)file_p, pos); return LV_FS_RES_OK; } /** * Give the size of a file bytes * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable * @param size pointer to a variable to store the size * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_size(lv_fs_drv_t* drv, void* file_p, uint32_t* size_p) { (*size_p) = f_size(((file_t*)file_p)); return LV_FS_RES_OK; } /** * Give the position of the read write pointer * @param drv pointer to a driver where this function belongs * @param file_p pointer to a file_t variable. * @param pos_p pointer to to store the result * @return LV_FS_RES_OK: no error, the file is read * any error from lv_fs_res_t enum */ static lv_fs_res_t fs_tell(lv_fs_drv_t* drv, void* file_p, uint32_t* pos_p) { *pos_p = f_tell(((file_t*)file_p)); return LV_FS_RES_OK; } /** * Delete a file * @param drv pointer to a driver where this function belongs * @param path path of the file to delete * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_remove(lv_fs_drv_t* drv, const char* path) { lv_fs_res_t res = LV_FS_RES_NOT_IMP; /* Add your code here*/ return res; } /** * Truncate the file size to the current position of the read write pointer * @param drv pointer to a driver where this function belongs * @param file_p pointer to an 'ufs_file_t' variable. (opened with lv_fs_open ) * @return LV_FS_RES_OK: no error, the file is read * any error from lv_fs_res_t enum */ static lv_fs_res_t fs_trunc(lv_fs_drv_t* drv, void* file_p) { f_sync((file_t*)file_p); /*If not syncronized fclose can write the truncated part*/ f_truncate((file_t*)file_p); return LV_FS_RES_OK; } /** * Rename a file * @param drv pointer to a driver where this function belongs * @param oldname path to the file * @param newname path with the new name * @return LV_FS_RES_OK or any error from 'fs_res_t' */ static lv_fs_res_t fs_rename(lv_fs_drv_t* drv, const char* oldname, const char* newname) { FRESULT res = f_rename(oldname, newname); if (res == FR_OK) return LV_FS_RES_OK; else return LV_FS_RES_UNKNOWN; } /** * Get the free and total size of a driver in kB * @param drv pointer to a driver where this function belongs * @param letter the driver letter * @param total_p pointer to store the total size [kB] * @param free_p pointer to store the free size [kB] * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_free(lv_fs_drv_t* drv, uint32_t* total_p, uint32_t* free_p) { lv_fs_res_t res = LV_FS_RES_NOT_IMP; /* Add your code here*/ return res; } /** * Initialize a 'fs_read_dir_t' variable for directory reading * @param drv pointer to a driver where this function belongs * @param dir_p pointer to a 'fs_read_dir_t' variable * @param path path to a directory * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_dir_open(lv_fs_drv_t* drv, void* dir_p, const char* path) { FRESULT res = f_opendir((dir_t*)dir_p, path); if (res == FR_OK) return LV_FS_RES_OK; else return LV_FS_RES_UNKNOWN; } /** * Read the next filename form a directory. * The name of the directories will begin with '/' * @param drv pointer to a driver where this function belongs * @param dir_p pointer to an initialized 'fs_read_dir_t' variable * @param fn pointer to a buffer to store the filename * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_dir_read(lv_fs_drv_t* drv, void* dir_p, char* fn) { FRESULT res; FILINFO fno; fn[0] = '\0'; do { res = f_readdir((dir_t*)dir_p, &fno); if (res != FR_OK) return LV_FS_RES_UNKNOWN; if (fno.fattrib & AM_DIR) { fn[0] = '/'; strcpy(&fn[1], fno.fname); } else strcpy(fn, fno.fname); } while (strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0); return LV_FS_RES_OK; } /** * Close the directory reading * @param drv pointer to a driver where this function belongs * @param dir_p pointer to an initialized 'fs_read_dir_t' variable * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */ static lv_fs_res_t fs_dir_close(lv_fs_drv_t* drv, void* dir_p) { f_closedir((dir_t*)dir_p); return LV_FS_RES_OK; }
/** * @file lv_port_fs_templ.h * */ /*Copy this file as "lv_port_fs.h" and set this value to "1" to enable content*/ #if 1 #ifndef LV_PORT_FS_TEMPL_H #define LV_PORT_FS_TEMPL_H #ifdef __cplusplus extern "C" { #endif /********************* * INCLUDES *********************/ #include "lvgl.h" #include "ff.h" //FatFs(通用FAT文件系统模块) /********************* * DEFINES *********************/ void lv_fs_if_init(void); /********************** * TYPEDEFS **********************/ /********************** * GLOBAL PROTOTYPES **********************/ /********************** * MACROS **********************/ #ifdef __cplusplus } /* extern "C" */ #endif #endif /*LV_PORT_FS_TEMPL_H*/ #endif /*Disable/Enable content*/
4,caiya_gui.h,caiya_gui.c,文件就是LVGL gui的相关设置了,跟前面博客使用的差不多,这里面耶加入了动画,注释应该可以看懂动画怎么用。LV_IMG_DECLARE(myimage1); LV_IMG_DECLARE(myimage2); LV_IMG_DECLARE(myimage3);是声明一些图片的c数组数据,也就是使用LVGL显示图片的第一种方式,数据在另外一个.c文件,就不贴了,数据太多,也都是在线转换的,参考前面的博客。
/********************* * INCLUDES *********************/ #include "caiya_gui.h" #include "stdio.h" lv_obj_t* scr1; lv_obj_t* scr2; lv_obj_t* label2; lv_obj_t* label3; lv_obj_t* label4; lv_obj_t* label5; lv_obj_t* label6; lv_obj_t* label7; lv_obj_t* img1; LV_IMG_DECLARE(myimage1); LV_IMG_DECLARE(myimage2); LV_IMG_DECLARE(myimage3); static void ofs_y_anim(void * img, int32_t v) { lv_obj_align(img, NULL, LV_ALIGN_IN_TOP_LEFT, 200,v); } void set_init_gui(void){ /*屏幕1*/ static lv_style_t style; lv_style_init(&style); lv_style_set_border_width(&style, LV_STATE_DEFAULT, 4); lv_style_set_bg_color(&style, LV_STATE_DEFAULT, LV_COLOR_WHITE); lv_style_set_border_color(&style, LV_STATE_DEFAULT, LV_COLOR_BLUE);//设置控件边框颜色 lv_obj_t* bar = lv_bar_create(lv_scr_act(), NULL);//在默认屏上创建obj对象 lv_obj_add_style(bar, LV_LABEL_PART_MAIN, &style); lv_obj_set_size(bar, 200, 30); lv_obj_align(bar, NULL, LV_ALIGN_CENTER, 0, 0); lv_bar_set_anim_time(bar, 2000); lv_bar_set_value(bar, 100, LV_ANIM_ON); static lv_style_t style1; lv_style_init(&style1); lv_style_set_border_width(&style1, LV_STATE_DEFAULT, 10); lv_style_set_border_color(&style1, LV_STATE_DEFAULT, LV_COLOR_BLUE);//设置控件边框颜色 lv_style_set_pad_top(&style1, LV_STATE_DEFAULT, 20);//填充(Padding)可在边缘的内侧设置空间。 lv_style_set_pad_bottom(&style1, LV_STATE_DEFAULT, 20); lv_style_set_pad_left(&style1, LV_STATE_DEFAULT, 20); lv_style_set_pad_right(&style1, LV_STATE_DEFAULT, 20); lv_style_set_text_font(&style1,LV_STATE_DEFAULT,&lv_font_montserrat_24);//12-14-16-18-20-22-24 lv_style_set_text_color(&style1, LV_STATE_DEFAULT, LV_COLOR_RED);//设置字体颜色 lv_obj_t * label = lv_label_create(lv_scr_act(), NULL); lv_obj_add_style(label, LV_LABEL_PART_MAIN, &style1); lv_label_set_long_mode(label, LV_LABEL_LONG_SROLL); lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); lv_obj_set_pos(label, 20, 10); lv_obj_set_size(label, 200, 70); lv_label_set_text(label, "let us begining......"); lv_obj_t * imglogo = lv_img_create(lv_scr_act(), NULL); lv_img_set_src(imglogo, &myimage2); /*功能:对象对齐 参数: obj 操作对象 obj_ref 参考对象,为 NULL 时,将对齐父对象。 LV_ALIGN_... 对齐类型 x_shift 对齐之后x轴偏移像素点 y_shift 对齐之后y轴偏移像素点*/ lv_obj_align(imglogo, NULL, LV_ALIGN_IN_TOP_LEFT, 45, 180); } void set_p_gui(void){ /*屏幕2*/ scr1 = lv_obj_create(NULL, NULL); // 创建新屏幕但未加载到显示 img1 = lv_img_create(scr1, NULL); lv_img_set_src(img1, &myimage1); lv_obj_set_pos(scr1, 0, 0); lv_obj_set_size(scr1, 240, 240); /*****************************************/ lv_obj_t *act_obj = lv_scr_act(); // 获取当前活动页 if (act_obj == scr1) return; lv_obj_clean(act_obj); // 清空此前页面 lv_scr_load_anim(scr1, LV_SCR_LOAD_ANIM_FADE_ON,500,0, false); } void set_mt_gui(void){ /*屏幕3*/ static lv_style_t style11; lv_style_init(&style11); lv_style_set_text_font(&style11,LV_STATE_DEFAULT,&lv_font_montserrat_20);//12-14-16-18-20-22-24 lv_style_set_text_color(&style11, LV_STATE_DEFAULT, LV_COLOR_GREEN);//设置字体颜色 scr2 = lv_obj_create(NULL, NULL); // 创建新屏幕但未加载到显示 label2 = lv_label_create(scr2, NULL); // 创建label lv_obj_add_style(label2, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label2, LV_LABEL_LONG_SROLL); lv_label_set_align(label2, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label2, 4, 4); lv_obj_set_size(label2, 150, 35); lv_label_set_text(label2, "city:Chengdu"); label3 = lv_label_create(scr2, NULL); lv_obj_add_style(label3, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label3, LV_LABEL_LONG_SROLL); lv_label_set_align(label3, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label3, 4, 36); lv_obj_set_size(label3, 150, 35); lv_label_set_text(label3, "textDay:Sunny"); label4 = lv_label_create(scr2, NULL); lv_obj_add_style(label4, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label4, LV_LABEL_LONG_SROLL); lv_label_set_align(label4, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label4, 4, 71); lv_obj_set_size(label4, 150, 35); lv_label_set_text(label4, "temp high:27"); label5 = lv_label_create(scr2, NULL); lv_obj_add_style(label5, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label5, LV_LABEL_LONG_SROLL); lv_label_set_align(label5, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label5, 4, 106); lv_obj_set_size(label5, 150, 35); lv_label_set_text(label5, "temp low:15"); label6 = lv_label_create(scr2, NULL); lv_obj_add_style(label6, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label6, LV_LABEL_LONG_SROLL); lv_label_set_align(label6, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label6, 4, 141); lv_obj_set_size(label6, 150, 35); lv_label_set_text(label6, "humi:95"); label7 = lv_label_create(scr2, NULL); lv_obj_add_style(label7, LV_LABEL_PART_MAIN, &style11); lv_label_set_long_mode(label7, LV_LABEL_LONG_SROLL); lv_label_set_align(label7, LV_LABEL_ALIGN_LEFT); lv_obj_set_pos(label7, 4, 176); lv_obj_set_size(label7, 150, 35); lv_label_set_text(label7, "time: 11:40:49"); /* Now create the actual image */ lv_obj_t * img = lv_img_create(scr2, NULL); lv_img_set_src(img, &myimage3); lv_obj_align(img, NULL, LV_ALIGN_IN_TOP_LEFT, 200,20); /*You can automatically change the value of a variable between a start and an end value using animations. The animation will happen by periodically calling an ”animator” function with the corresponding value parameter.*/ lv_anim_t ma; lv_anim_init(&ma); /*Set the "animator" function*/ lv_anim_set_var(&ma, img); /*Set the "animator" function*/ lv_anim_set_exec_cb(&ma, (lv_anim_exec_xcb_t)ofs_y_anim); /*Set start and end values. E.g. 0, 150*/ lv_anim_set_values(&ma, 0, 100); /*Length of the animation [ms]*/ lv_anim_set_time(&ma, 1000); /*Number of repetitions. Default is 1. LV_ANIM_REPEAT_INFINIT for infinite repetition*/ lv_anim_set_repeat_count(&ma, LV_ANIM_REPEAT_INFINITE); /*Play the animation backward too with this duration. Default is 0 (disabled) [ms]*/ lv_anim_set_playback_time(&ma, 1000); /*Start the animation*/ lv_anim_start(&ma); /****************************************************/ lv_obj_t *act_obj = lv_scr_act(); // 获取当前活动页 if (act_obj == scr2) return; lv_obj_clean(act_obj); // 清空此前页面 lv_scr_load_anim(scr2, LV_SCR_LOAD_ANIM_FADE_ON,500,3000, false); } int frame_id = 0; char buf[100]; void display_photo() { int len = sprintf(buf, "S:/hold/%04d.bin", frame_id++); buf[len] = 0; lv_img_set_src(img1, buf); lv_obj_align(img1, NULL, LV_ALIGN_CENTER, 0, 0); if (frame_id == 6) frame_id = 0; } void photo_gui_del(void) { if (NULL != scr1) { lv_obj_clean(scr1); // 清空此前页面 scr1 = NULL; } } void mt_gui_del(void) { if (NULL != scr2) { lv_obj_clean(scr2); // 清空此前页面 scr2 = NULL; } } void mt_process(USER_DATA mtdata){ if(mtdata.age==1) { lv_label_set_text_fmt(label2, "city:%s", mtdata.getcity); lv_label_set_text_fmt(label3, "textDay:%s", mtdata.getweather); lv_label_set_text_fmt(label4, "temp high:%s", mtdata.gethigh); lv_label_set_text_fmt(label5, "temp low:%s", mtdata.getlow); lv_label_set_text_fmt(label6, "humi:%s", mtdata.gethumi); lv_label_set_text_fmt(label7, "time: %s", mtdata.gettime); } }
#ifndef CAIYA_GUI_H #define CAIYA_GUI_H #ifdef __cplusplus extern "C" { //extern "C"表示编译生成的内部符号名使用C约定。 #endif #include "lvgl.h" #define USER_EVENT_START 20 #define USER_EVENT_1 (USER_EVENT_START+1) //用户自定义事件 1 //构建一个用户自定义数据结构体,当然了,如果你的用户数据简单,可以不用结构体 typedef struct{ char getcity[32]; char getweather[64]; char gethigh[32]; char getlow[32]; char gethumi[32]; char gettime[32]; unsigned char age; }USER_DATA; //extern lv_img_dsc_t screen_buffer; void set_init_gui(void); void set_p_gui(void); void set_mt_gui(void); void display_photo(); void photo_gui_del(void); void mt_gui_del(void); void mt_process(USER_DATA mtdata); //static void btn_event_cb(lv_obj_t * obj,lv_event_t event); #ifdef __cplusplus } /* extern "C" */ #endif #endif
5,主程序,实现功能。
#include "display.h" #include "sd_card.h" #include "lv_port_fatfs.h" #include "caiya_gui.h" #include <NTPClient.h> #include <WiFi.h> #include <WiFiUdp.h> #include <HTTPClient.h> #include <ArduinoJson.h> /*** Component objects ***/ Display screen; SdCard tf; unsigned char time1flag=0;//定时器使用标记寄存器 hw_timer_t *timer = NULL;//定义hw_timer_t 结构类型的指针 int flag = 1; unsigned char playflag=0;//播放视频标记位 USER_DATA user_data = {{"nodata"},{"nodata"},{"nodata"},{"nodata"},{"nodata"},{"nodata"},0};//初始化一下 // 函数名称:onTimer() // 函数功能:中断服务的功能,它必须是一个返回void(空)且没有输入参数的函数 // 为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性 void IRAM_ATTR TimerEvent() { static int timercnt=0;//用于标记到点刷新图片 if((timercnt==1000)&&(playflag==1)){ time1flag=1; timercnt=0; } else if(playflag==1)timercnt++; else timercnt=0; } /*wifi*/ char mytime[32]; WiFiUDP ntpUDP; void getTime(){ NTPClient timeClient(ntpUDP, "ntp1.aliyun.com", 60 * 60 * 8, 30 * 60 * 1000); timeClient.begin(); timeClient.update(); String time_str = timeClient.getFormattedTime(); strcpy(mytime,time_str.c_str()); } const char *host = "api.seniverse.com"; const char *privateKey = "Scxilbk8z2ntaRPtN"; const char *city = "chengdu"; const char *language = "en"; struct WetherData { char city[32]; char weather[64]; char high[32]; char low[32]; char humi[32]; }; struct WetherData weatherdata = {0}; void getWeather(){ WiFiClient client; if (!client.connect(host, 80)) { Serial.println("Connect host failed!"); return; } Serial.println("host Conected!"); String getUrl = "/v3/weather/daily.json?key="; getUrl += privateKey; getUrl += "&location="; getUrl += city; getUrl += "&language="; getUrl += language; client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); // Serial.println("Get send"); char endOfHeaders[] = "\r\n\r\n"; bool ok = client.find(endOfHeaders); // if (!ok) // { // Serial.println("No response or invalid response!"); // } // Serial.println("Skip headers"); String line=""; line += client.readStringUntil('\n'); DynamicJsonDocument doc(1400); DeserializationError error = deserializeJson(doc, line); if (error) { Serial.println("deserialize json failed"); return; } Serial.println("deserialize json success"); if(line.length()<300){ Serial.println("limit"); } else{ strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as<const char*>()); strcpy(weatherdata.weather, doc["results"][0]["daily"][0]["text_day"].as<const char*>()); strcpy(weatherdata.high, doc["results"][0]["daily"][0]["high"].as<const char*>()); strcpy(weatherdata.low, doc["results"][0]["daily"][0]["low"].as<const char*>()); strcpy(weatherdata.humi, doc["results"][0]["daily"][0]["humidity"].as<const char*>()); } client.stop(); strcpy(user_data.getcity,weatherdata.city); strcpy(user_data.getweather,weatherdata.weather); strcpy(user_data.gethigh,weatherdata.high); strcpy(user_data.getlow,weatherdata.low); strcpy(user_data.gethumi,weatherdata.humi); strcpy(user_data.gettime,mytime); } bool sdokflag=true;//sd卡挂载成功标记 void setup() { Serial.begin(115200); pinMode(2,OUTPUT); digitalWrite(2,1); pinMode(0,INPUT_PULLUP); /*** Init screen ***/ screen.init(); /*** Init micro SD-Card ***/ sdokflag=tf.init(); if(sdokflag)Serial.println("sd ok"); lv_fs_if_init(); /*** Inflate GUI objects ***/ set_init_gui(); timer = timerBegin(0, 80, true); timerAttachInterrupt(timer, &TimerEvent, true); timerAlarmWrite(timer, 5000, true); timerAlarmEnable(timer); // 使能定时器 WiFi.begin("漂亮丫头是大笨猪","lovexx1997andcaiya"); } unsigned char wififlag=0;//wifi连接成功标记位 int aa; void loop() { if(((WiFi.status()!=WL_CONNECTED)==true)&&(wififlag==0)){ wififlag=1; Serial.println("wifi ok"); } screen.routine();/* let the GUI do its work */ if((time1flag==1)&&(playflag==1)&&(sdokflag==true)){ aa=millis(); display_photo(); delay(300); time1flag=0; Serial.println(millis()-aa); } else if(sdokflag==false)Serial.println("sd not ok"); /*按键处理*/ if(digitalRead(0)==0) { while(digitalRead(0)==0); digitalWrite(2,!digitalRead(2)); if(flag==1)flag=0; else flag++; if(flag==1){ playflag=0; photo_gui_del(); set_mt_gui(); delay(300); if(wififlag==1){ getTime(); getWeather(); } else Serial.println("wifi not ok"); user_data.age=(unsigned char)flag; mt_process(user_data); } else if(flag==0){ mt_gui_del(); set_p_gui(); playflag=1; } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」