How to setup my_disp_flush function within a library

Description

I’m trying to create a library containing everything to run my TFT. This way my main code becomes easy to manage and I can swap displays by pointing to another library (I’m now using a simple lcd and would like to convert it to a tft).

If I run the full example in my “main.cpp” file it runs great. Now I’m creating a library (see code below). The only thing that doesn’t work as runnin the whole code in one cpp file is that I can’t link these: disp_drv.flush_cb = my_disp_flush;

I get this error:

lib\FabnetTFT\FabnetTFT.cpp: In member function ‘void FabnetTFT::start()’:
lib\FabnetTFT\FabnetTFT.cpp:36:23: error: cannot convert ‘FabnetTFT::my_disp_flush’ from type ‘void (FabnetTFT::)(lv_disp_drv_t*, const lv_area_t*, lv_color_t*) {aka void (FabnetTFT::)(_disp_drv_t*, const lv_area_t*, lv_color16_t*)}’ to type ‘void ()(_disp_drv_t, const lv_area_t*, lv_color_t*) {aka void ()(_disp_drv_t, const lv_area_t*, lv_color16_t*)}’
disp_drv.flush_cb = my_disp_flush;

What MCU/Processor/Board and compiler are you using?

I’m using an ESP32 with Platformio

What do you want to achieve?

My own library where all my TFT functions are handled

What have you tried so far?

Get it running without creating my own library, that works fine.

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

myTFT.h:


#ifndef _MYTFT_h
#define _MYTFT_h

#include <Adafruit_HX8357.h>
#include <Adafruit_STMPE610.h>
#include <lvgl.h>
#include <Ticker.h>

#ifdef ESP32
#define STMPE_IRQ 27
#define STMPE_CS 32
#define TFT_CS 15
#define TFT_DC 33
#define SD_CS 14
#endif

#define TFT_RST -1

class MyLCD
{
public:
    MyLCD();
    void start();
    void loop();
  
private:

    Ticker tick; /* timer for interrupt handler */
    Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST);
    Adafruit_STMPE610 touch = Adafruit_STMPE610(STMPE_CS);

    lv_disp_buf_t disp_buf;
    lv_color_t buf[LV_HOR_RES_MAX * 10];
    
    uint8_t SCR_rotation = 1;
    static void lv_tick_handler(void);
    void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
    
    lv_obj_t *scr;
  
    void lv_test_theme_1(lv_theme_t *th);
};

#endif

myTFT.cpp

#include "myTFT.h"
#include <Arduino.h>

#define LVGL_TICK_PERIOD 20
#define LV_THEME_MATERIAL_H 1
#define USE_LV_LOG 0

myTFT::myTFT()
{
    ;
}

/*
myTFT::~myTFT()
{
}
*/

void myTFT::start()
{
    Serial.println(F("Starting LCD"));
    lv_init();

    tft.begin();                   /* TFT init */
    tft.setRotation(SCR_rotation); /* Landscape orientation */

    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 = 480;
    disp_drv.ver_res = 320;

    //getting lost?? Here's the issue!!!!!
    disp_drv.flush_cb = my_disp_flush;
}

void myTFT::loop()
{
    lv_task_handler(); /* let the GUI do its work */
    delay(5);
}

//******** Private Functions

/* Display flushing */

void myTFT::my_disp_flush(_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    uint16_t c;

    tft.startWrite();                                                                            /* Start new TFT transaction */
    tft.setAddrWindow(area->x1, area->y1, (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1)); /* set the working window */
    for (int y = area->y1; y <= area->y2; y++)
    {
        for (int x = area->x1; x <= area->x2; x++)
        {
            c = color_p->full;
            tft.writeColor(c, 1);
            color_p++;
        }
    }
    tft.endWrite();            /* terminate TFT transaction */
    lv_disp_flush_ready(disp); /* tell lvgl that flushing is done */
}

The disp func should be a static method.
For example, here is .ino code for a Adafruit_ILI9341:

int my_disp_flush_width, my_disp_flush_height;

static void my_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p) {
  my_disp_flush_width = area->x2 - area->x1 + 1;
  my_disp_flush_height = area->y2 - area->y1 + 1;
  display.startWrite();
  display.setAddrWindow(area->x1, area->y1, my_disp_flush_width, my_disp_flush_height);
  while ((my_disp_flush_height--) > 0) {
    display.writePixels((uint16_t*)color_p, my_disp_flush_width, false);
    color_p += my_disp_flush_width;
  }
  display.endWrite();
  lv_disp_flush_ready(disp);
}

You could always thunk/proxy your class method from the static method.

Pv

Hi Paul,

Thanks for your reaction.

Still didn’t manage to get any further. My way around for now is like this:

void DisplayStart(){
    Display.start1();
    Display.disp_drv.flush_cb = my_disp_flush;
    Display.start2();
}

So in start1 I’ve added all functions like these:

void MyTFT::start1(){
    Serial.println(F("Starting LCD"));
    lv_init();

    tft.begin();                   /* TFT init */
    tft.setRotation(SCR_rotation); /* Landscape orientation */

    lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);

    /*Initialize the display*/
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = 480;
    disp_drv.ver_res = 320;
}

Then I link the flush function and start the second part:

void MyTFT::start2()
{
    disp_drv.buffer = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    /*Initialize the graphics library's tick*/
    tick.attach_ms(LVGL_TICK_PERIOD, lv_tick_handler);
    
    lv_theme_t *th = lv_theme_night_init(30, LV_FONT_ROBOTO_12);
    lv_test_theme_1(th);
    
}

Works fine now, but I would be even better if the flush becomes part of the library…

This is the missing part I guess:

You could always thunk/proxy your class method from the static method.

But I don’t know how I should do that…

Thanks and regards
David

The main issue that @paulpv is referring to is that C++ class methods cannot be passed to LittlevGL directly. This is because of an inherent compatibiliy issue between class methods and “normal” C functions. Here’s an illustration of one way C++ might be implemented compared to C.

void my_class_func(ClassInstance *this, int first_param, int second_param);
void normal_c_func(int first_param, int second_param);

Keep in mind that that is only one of the many ways it could be done. Don’t rely on that kind of implementation-specific behavior in actual code. :slightly_smiling_face:

To solve that problem, you have to create a regular C “thunk” (declared using extern C in global scope), and call your class function from there. Here’s a simple example.

class LvDispFuncs {
public:
    virtual void flush(lv_disp_drv_t *drv, const lv_area_t * area, lv_color_t * color_p);
}
class MySpecificDisp: public LvDispFuncs {
}

void MySpecificDisp::flush(lv_disp_drv_t *drv, const lv_area_t * area, lv_color_t * color_p)
{
    /* TODO: flush to display */
}

LvDispFuncs *classInstance;

extern "C" void my_disp_flush_thunk(lv_disp_drv_t *drv, const lv_area_t * area, lv_color_t * color_p)
{
    /* TODO: check if classInstance is NULL */
    classInstance->flush(drv, area, color_p);
}

Hopefully that example is easy enough to understand.