I am currently successfully running LVGL on a SPI TFT display and am attempting to integrate an RFID module as well. The RFID module works flawlessly by itself but does not function if I am using the TFT screen with LVGL.
I narrowed down the issue to the invocation of lv_tft_espi_create(). Removing this function restores all functionality to the RFID reader, but obviously I need the SPI settings to create the display object.
Is there anyway I can get both of these modules to work in tandem?
Any help would be greatly appreciated!!
What MCU/Processor/Board and compiler are you using?
I’m using VSCode with Platformio and uploading to an ESP32-VROOM-32D.
What LVGL version are you using?
9.1.0
What do you want to achieve?
I would like to be able to use the TFT display, running LVGL, in tandem with a RFID module.
What have you tried so far?
Someone recommended that I look into SPITransactions. My knowledge in this area isn’t very refined so I attempted to implement SPI transactions as such (code shortened for brevity):
#include <Arduino.h>
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <MFRC522.h>
#include <SPI.h>
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
void *draw_buf;
TFT_eSPI tft = TFT_eSPI();
// Define SPI settings for TFT and RFID
SPISettings tftSPISettings(20000000, MSBFIRST, SPI_MODE0); // Matched the SPI_FREQUENCY in my TFT config: 20MHz
SPISettings rfidSPISettings(10000000, MSBFIRST, SPI_MODE0);
void setup()
{
Serial.begin(115200);
SPI.begin();
SPI.beginTransaction(tftSPISettings); // BEGIN TFT SPI TRANSACTION
lv_init();
lv_tick_set_cb(lv_millis);
draw_buf = heap_caps_malloc(DRAW_BUF_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
// This is the problematic line:
lv_display_t *disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, DRAW_BUF_SIZE);
lv_display_set_rotation(disp, LVGL_SCREEN_ROTATION);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, my_touchpad_read);
calibrateTouch();
tft.fillScreen(TFT_BLACK);
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, LVGL_Arduino.c_str());
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
SPI.endTransaction(); // END TFT SPI TRANSACTION
SPI.beginTransaction(rfidSPISettings); // BEGIN RFID SPI TRANSACTION
mfrc522.PCD_Init();
SPI.endTransaction(); // END RFID SPI TRANSACTION
}
void loop()
{
SPI.beginTransaction(tftSPISettings); // BEGIN TFT SPI TRANSACTION
lv_task_handler();
SPI.endTransaction(); // END TFT SPI TRANSACTION
}
Adding these transactions resulted in nothing being displayed on the TFT screen anymore.
Your issu isnt lvgl ,but esp based. Check TFTespi driver code how init SPI , you need shared. And your next devices require use transaction , but not as in your code.
Some example
esp_err_t ret;
//Initialize the SPI bus
ret=spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); //VSPI
ESP_ERROR_CHECK(ret);
//Attach the LCD to the SPI bus
ret=spi_bus_add_device(SPI2_HOST, &devcfg, &spiDISP);
ESP_ERROR_CHECK(ret);
next devcfg2 and aad_device add multiple peripherals…
I’m sorry, I’m not sure I fully understand what your recommendation is.
There is a way to get the TFT_eSPI instance, as seen in this comment.
#include <SPI.h> // We only use SPI interface for this example
SPIClass& spix = SPI; // Create a class variable to hold the SPI class instance (default instance is SPI port 0 pins only)
#include <TFT_eSPI.h> // Hardware-specific library
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
void setup()
{
tft.init(); // Initialise library (will also get SPI class instance)
spix = tft.getSPIinstance(); // Set to instance used by TFT library (may be SPI port 0 or 1)
tft.setRotation(3);
// Show speed of fillScreen
tft.fillScreen(TFT_RED);
tft.fillScreen(TFT_GREEN);
tft.fillScreen(TFT_BLUE);
delay(1000);
tft.invertDisplay(1); /* Required for correct colours */
tft.startWrite(); // Fix TFT CS low for test purposes
// Now we use the spi instance used by the library (may not be default SPI instance)
spix.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
while (1)
{
// fillScreen is now slow so SPI rate changed to 1MHz
tft.fillScreen(random(0x10000));
for (uint16_t i = 0; i < (320*50*2); i++ ) spix.transfer(0xAA); // 50 line brown band written (colour 0xAAAA)
delay(1000);
}
}
void loop(){
}
Thanks for replying but unfortunately I’m having a lot of trouble understanding exactly what you’re trying to recommend.
Are you suggesting I only do the transaction in the flush callback? Would I still need to use the use the SPI instance returned from the TFT library or just use my own new instance?
I also think the best approach is to do the transferring inside of the flush callback. Yes, you will need to use the instance returned from the library.
Flush callback is called when LVGL is done “rendering” the buffer that needs to be flushed to your display, you should only transfer this buffer when it is ready to be transferred, so inside the flush callback.
Thanks for replying! I’m having a bit of trouble writing a display flush function. If I invoke lv_display_set_flush_cb(disp, my_disp_flush); with this function, all I get is a black screen:
Can you point me to a standard implementation of display flush that I can use? I see the LVGL documentation has this, and it works… But it slows things down quite a bit.
The flush function is used to transfer data from the LVGL buffer (area parameter in disp_flush()) to your display and to notify LVGL that the flushing is done. So just an empty function that immediately notifies LVGL that transfer is done will not work.
The standard implementation is exactly the one you just shared. To speed it up the most efficient method would be to use two frame buffers for LVGL, and transfer one to the display while the other is being rendered. See “two buffers”: Display interface — LVGL documentation.
void put_px(int16_t x, int16_t y, uint16_t color)
{
tft.drawPixel(x, y, color);
}
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
*`put_px` is just an example, it needs to be implemented by you.*/
uint16_t * buf16 = (uint16_t *)px_map; /*Let's say it's a 16 bit (RGB565) display*/
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
put_px(x, y, *buf16);
buf16++;
}
}
lv_display_flush_ready(disp);
}
it works fine - Obviously renders slower though, as I pulled this example directly from the LVGL docs.
I think that, according this this code, the display flush function isn’t working because I’m using the TFT_eSPI library rather than creating my own display from scratch.