Description
I had an Arduino project in operation for a couple of years that uses an ESP32 devkit v1 board and DFRobot ST7735S display with microSD slot.
Now I have come back to this again to add some new features to sections of the code unrelated to the display or SD card using a newer version of the Arduino IDE (and board packages/libraries). I cannot utilise the display and SD card at the same time if TFT_eSPI has been initialised, where previously this would work.
Something in the period since I originally wrote this project has changed, as rolling back to my previous (working) code has the same issue.
I’ve no idea which direction to go to try and resolve this and my lack of C++ experience limits my deeper diagnostic ability.
What MCU/Processor/Board and compiler are you using?
- ESP32 Devkit v1 connected to DFRobot ST7735S display
- Arduino IDE 2.3.7 on Windows 10. Board profile set to DOIT ESP32 DEVKIT V1 from esp32 board package
- esp32 board package 3.3.5
Libraries: - lvgl 9.4.0
- TFT_eSPI 2.5.43
- SPI 3.3.5 (from ESP32 board package)
- SDFat 2.3.0 (from Arduino libraries)
- SD 3.3.5 (from ESP32 board package)
- FS 3.3.5 (from ESP32 board package)
What do you want to achieve?
Use of SD card to display images with LVGL, while also having the SD card available to read text files for other functions.
What have you tried so far?
- Running LVGL_Arduino example to test the display (works)
- Running SDFat SdInfo and ReadCSVFile examples to test the SD card (works)
- Reverting lv_conf.h to lv_conf_template.h content and enabling tft_espi support in that config
- Rewriting SD access using Arduino SD library
- Rewriting SD access using FATfs library
- Changing SPI clock speed in sd.begin() and in TFT_eSPI User_Setup.h
- Filesystem check of SD card (confirmed FAT32 no errors)
- Swapping SD card
Using the LVGL_Arduino example as a starting point and adding SD card interfacing:
- Moving call to sd.begin() to occur before lv_init() - sd.begin() successfully initialises
- Moving call to sd.begin() to occur after lv_init() but before lv_tft_espi_create() - sd.begin() successfully initialises
- Moving call to sd.begin() to occur after lv_init() and lv_tft_espi_create() - sd.begin() fails
Code to reproduce
User_setup.h from TFT_eSPI:
#define USER_SETUP_ID 2
#define ST7735_DRIVER
#define TFT_WIDTH 128
#define TFT_HEIGHT 160
#define ST7735_GREENTAB2
#define TFT_CS 5 // Chip select control pin D8
#define TFT_DC 21 // Data Command control pin
#define TFT_RST 22 // Reset pin (could connect to NodeMCU RST, see next line)
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
#define SMOOTH_FONT
#define SPI_FREQUENCY 16000000
#define SPI_TOUCH_FREQUENCY 2500000
Modified LVGL Arduino example
/*Using LVGL with Arduino requires some extra steps:
*Be sure to read the docs here: https://docs.lvgl.io/master/integration/framework/arduino.html */
#include <lvgl.h>
#include <TFT_eSPI.h>
#include "SdFat.h"
/*To use the built-in examples and demos of LVGL uncomment the includes below respectively.
*You also need to copy `lvgl/examples` to `lvgl/src/examples`. Similarly for the demos `lvgl/demos` to `lvgl/src/demos`.
*Note that the `lv_examples` library is for LVGL v7 and you shouldn't install it for this version (since LVGL v8)
*as the examples and demos are now part of the main LVGL library. */
//#include <examples/lv_examples.h>
//#include <demos/lv_demos.h>
/*Set to your screen resolution and rotation*/
#define TFT_HOR_RES 128
#define TFT_VER_RES 160
#define TFT_ROTATION LV_DISPLAY_ROTATION_90
// SD card pins
#define PIN_SD_CS 17
/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
struct network {
char ssid[32] = "";
char password[32] = "";
};
SdFs sd;
SdFile file;
static lv_fs_drv_t drv;
network read_config_network(bool backup = false) {
bool open_ok = false;
if (backup == true) {
Serial.println(F("READ_NETWORK_CONFIG: Reading backup network config"));
open_ok = file.open("/backup.cfg");
} else {
Serial.println(F("READ_NETWORK_CONFIG: Reading main network config"));
open_ok = file.open("/main.cfg");
}
network netconf;
if (open_ok) {
String ssid = "";
String password = "";
bool got_ssid = false;
char line[32];
while (file.available()) {
int n = file.fgets(line, sizeof(line));
if (n <= 0) {
Serial.println(F("READ_NETWORK_CONFIG: fgets failed"));
}
if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) {
Serial.println(F("READ_NETWORK_CONFIG: line too long"));
}
// remove CR and LF
if (line[n - 1] == '\n') {
line[n -1] = 0;
}
if (line[n - 2] == '\r') {
line[n -2] = 0;
}
if (got_ssid == false) {
ssid = line;
got_ssid = true;
} else {
password = line;
break;
}
}
file.close();
strcpy(netconf.ssid, ssid.c_str());
strcpy(netconf.password, password.c_str());
Serial.println(F("READ_NETWORK_CONFIG: Config read ok!"));
return netconf;
} else {
Serial.println(F("READ_NETWORK_CONFIG: Unable to open file"));
return netconf;
}
}
// Define LVGL filesystem driver
static void *sd_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode) {
(void)drv;
uint8_t oflag = 0;
if (mode == LV_FS_MODE_WR) oflag = O_WRONLY | O_CREAT | O_TRUNC;
else if (mode == LV_FS_MODE_RD) oflag = O_RDONLY;
else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) oflag = O_RDWR | O_CREAT;
SdFile *fp = new SdFile();
if (!fp->open(path, oflag)) {
delete fp;
return NULL;
}
return fp;
}
static lv_fs_res_t sd_close(lv_fs_drv_t *drv, void *file_p) {
(void)drv;
SdFile *fp = (SdFile *)file_p;
fp->close();
delete fp;
return LV_FS_RES_OK;
}
static lv_fs_res_t sd_read(lv_fs_drv_t *drv, void *file_p, void *buff, uint32_t btr, uint32_t *br) {
(void)drv;
SdFile *fp = (SdFile *)file_p;
*br = fp->read(buff, btr);
return LV_FS_RES_OK;
}
static lv_fs_res_t sd_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence) {
(void)drv;
SdFile *fp = (SdFile *)file_p;
if (whence == LV_FS_SEEK_SET) fp->seekSet(pos);
else if (whence == LV_FS_SEEK_CUR) fp->seekCur(pos);
else if (whence == LV_FS_SEEK_END) fp->seekEnd(pos);
return LV_FS_RES_OK;
}
static lv_fs_res_t sd_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p) {
(void)drv;
SdFile *fp = (SdFile *)file_p;
*pos_p = fp->curPosition();
return LV_FS_RES_OK;
}
void init_sd_driver(void) {
Serial.println(F("Initialising LVGL->SD filesystem driver"));
lv_fs_drv_init(&drv);
drv.letter = 'S';
drv.open_cb = sd_open;
drv.close_cb = sd_close;
drv.read_cb = sd_read;
drv.seek_cb = sd_seek;
drv.tell_cb = sd_tell;
lv_fs_drv_register(&drv);
}
bool init_sd(void) {
Serial.println(F("Initialising SD card"));
bool ok = false;
if (!(sd.begin(SdSpiConfig(PIN_SD_CS, SHARED_SPI, SD_SCK_MHZ(16))))) {
Serial.println("SD card failed!");
} else {
Serial.println("SD card OK");
ok = true;
init_sd_driver();
}
return ok;
}
#if LV_USE_LOG != 0
void my_print( lv_log_level_t level, const char * buf )
{
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
#endif
/* LVGL calls it when a rendered image needs to copied to the display*/
void my_disp_flush( lv_display_t *disp, const lv_area_t *area, uint8_t * px_map)
{
/*Call it to tell LVGL you are ready*/
lv_display_flush_ready(disp);
}
/*use Arduinos millis() as tick source*/
static uint32_t my_tick(void)
{
return millis();
}
void setup()
{
String LVGL_Arduino = "Hello Arduino! ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin( 115200 );
Serial.println( LVGL_Arduino );
lv_init();
init_sd(); // <-- this init works fine
/*Set a tick source so that LVGL will know how much time elapsed. */
lv_tick_set_cb(my_tick);
/* register print function for debugging */
#if LV_USE_LOG != 0
lv_log_register_print_cb( my_print );
#endif
network test_a = read_config_network(false); // <-- this read works because it is before lv_tft_espi_create()
lv_display_t * disp;
Serial.println("Example is using TFT_ESPI");
/*TFT_eSPI can be enabled lv_conf.h to initialize the display in a simple way*/
disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, TFT_ROTATION);
network test_b = read_config_network(false); // <-- this read fails because it is after lv_tft_espi_create()
lv_obj_t *label = lv_label_create( lv_screen_active() );
lv_label_set_text( label, "Hello Arduino, I'm LVGL!" );
lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );
lv_obj_t * logo = lv_image_create(lv_screen_active());
lv_image_set_src(logo, "S:images/logo.bin"); // <-- this also fails, even with the filesystem driver
lv_obj_align(logo, LV_ALIGN_TOP_MID, 0, 10);
Serial.println( "Setup done" );
}
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
delay(5); /* let this time pass */
}
Screenshot and/or video
Serial output when running the above example:
02:32:49.297 -> ets Jun 8 2016 00:22:57
02:32:49.297 ->
02:32:49.297 -> rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
02:32:49.297 -> configsip: 0, SPIWP:0xee
02:32:49.297 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
02:32:49.297 -> mode:DIO, clock div:1
02:32:49.297 -> load:0x3fff0030,len:4744
02:32:49.341 -> load:0x40078000,len:15672
02:32:49.341 -> load:0x40080400,len:3164
02:32:49.341 -> entry 0x4008059c
02:32:49.589 -> Hello Arduino! V9.4.0
02:32:49.589 -> Initialising SD card
02:32:49.589 -> SD card OK
02:32:49.589 -> Initialising LVGL->SD filesystem driver
02:32:49.589 -> READ_NETWORK_CONFIG: Reading main network config
02:32:49.589 -> READ_NETWORK_CONFIG: Config read ok!
02:32:49.589 -> Example is using TFT_ESPI
02:32:50.496 -> READ_NETWORK_CONFIG: Reading main network config
02:32:50.800 -> READ_NETWORK_CONFIG: Unable to open file
02:32:51.138 -> Setup done