Getting Started with LvGL Part 2: Display interface

In the previous guide (here), we took a look at LvGL and its feature and initialized the environment to make it work. In part two, we shall interface the TFT and display the demo.

6. Including LvGL into the project:

First, head to LvGL github page from here.

Select V8.3 from here:

Next, download as zip from here:

Extract the files and rename the folder to be LvGL and copy it to the driver folder of the project.

Also, create new header within the driver folder with name of lv_conf.h

Hence, the drive folder should be like this:

Now, open lv_conf.h and copy the content lv_conf_template.h from here.

Now, we start modifying:

At line 15 set if 0 to if 1 as following:

#if 1

At line 27 set the color depth to 16 since the screen is RGB565.

#define LV_COLOR_DEPTH 16

Line 52 is for the memory of the LvGL:

#define LV_MEM_SIZE (48U * 1024U) 

For now, keep it to default of 48KB (depending on your application).

At line 752, enable the widget demo:

#define LV_USE_DEMO_WIDGETS 1

This will allow us to run the demo on the screen.

Save the changes to the file.

Now right click on the project and select properties as following:

Then go to C/C++ build, settings, then MCU GCC Compiler include paths:

In include path:

"${workspace_loc:/${ProjName}/Drivers/lvgl}"

In include files:

"${workspace_loc:/${ProjName}/Drivers/lvgl/lvgl.h}"

Click on apply and close.

Next open the base_time.c source file:

Include the lvgl.h header file as following:

#include "lvgl.h"

Within the systick interrupt handler, call lv_tick_inc(1) as following:

	/*Call LvGL tick function*/
	lv_tick_inc(1);

Hence, the updated systick handler is as following:

/*Interrupt handler of SysTick*/
void SysTick_Handler(void)
{
	/*Increment the counter with every interrupt*/
	mil++;

	/*Call LvGL tick function*/
	lv_tick_inc(1);
}

Now, create new source and header file with name of LCDController.c and LCDController.h

Within LCDController.h:

Get the content from here.

Modify it as following:

#ifndef INC_LCDCONTROLLER_H_
#define INC_LCDCONTROLLER_H_


#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/

#include "lvgl.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/
/* Initialize low level display driver */
void lv_port_disp_init(void);

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void);

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void);

/**********************
 *      MACROS
 **********************/

#ifdef __cplusplus
} /*extern "C"*/
#endif



#endif /* INC_LCDCONTROLLER_H_ */

Now, open the source file:

Include the following header file:

#include "LCDController.h"
#include <stdbool.h>
#include "ILI_9341.h"
#include "stdint.h"
#include "lvgl.h"
#include "LCD_Pins.h"

Define the dimensions of the display:

#define MY_DISP_HOR_RES 240


#define MY_DISP_VER_RES 320

Define the buffer size:

#define buffer_width 100

Declare these two functions:

static void disp_init(void);

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

Since we are using DMA, it is good idea to use double buffer with LvGL to improve the performance of the TFT.

Hence, we can declare the the two buffers as following:

static lv_color_t buf_1[MY_DISP_HOR_RES *buffer_width];
static lv_color_t buf_2[MY_DISP_HOR_RES *buffer_width];

Width here is how many lines to be updated at with single DMA transfer. 100 means update 100 lines of the LCD with each DMA transfer. (From testing, more 100 will get some graphics glitches (due to breadboard nature)).

Declare the display driver:

static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/

Declare display draw driver:

static lv_disp_draw_buf_t draw_buf_dsc_1;

The initialization function:

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/


    

    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, buf_2, MY_DISP_HOR_RES*buffer_width);   /*Initialize the display buffer*/


    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/


    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

Since the initailization calls disp_init funtion, this function will initialize the LCD.

static void disp_init(void)
{
	ILI9341_Init();

}

Enable display flush as following:

volatile bool disp_flush_enabled = true;

volatile bool disp_flush_enabled = true;

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void)
{
    disp_flush_enabled = true;
}

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void)
{
    disp_flush_enabled = false;
}

For the display flush function:

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{

	setAddrWindow(area->x1, area->y1, area->x2, area->y2);
	int height = area->y2 - area->y1 + 1;
	int width = area->x2 - area->x1 + 1;

	ILI9341_DrawBitmap(width, height, (uint8_t *)color_p);

}

SetAddrWindow will tell the embedded RAM within the TFT which area to be flushed.

After the area is set in the RAM, flush the content of LvGL buffet to the TFT.

Now, the LvGL is waiting for the next flush.

Hence, in SPI_TX_Finished function:

void SPI_TX_Finished(void)
{
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(&disp_drv);

    /*Set CS line to High*/
    CS_HIGH();

}

Inform the LvGL that TFT is ready for the next TFT flush.

Set CS pin to high.

Thats all for the TFT Integration.

7. Main Code:

In main.c:

Include the following:

#include "time_base.h"
#include "lvgl.h"
#include "demos/lv_demos.h"

In main function:

  • Initialize the timer base.
  • Initialize the LvGL.
  • Initialize the display.
  • Launch the demo.
	Time_Base_Init(216000000);
	lv_init();
	lv_port_disp_init();
	lv_demo_widgets();

In while 1 loop:

  • Call lv_timer_handler.
  • delay by 5 millisecond.

	while (1)
	{
		lv_timer_handler();
		delay(5);


	}

Hence, the entire code as following:

#include "time_base.h"
#include "lvgl.h"
#include "demos/lv_demos.h"

int main (void)
{
	Time_Base_Init(216000000);
	lv_init();
	lv_port_disp_init();
	lv_demo_widgets();


	while (1)
	{
		lv_timer_handler();
		delay(5);


	}


}

8. Code.

You may download the project from here:

9. Results:

Next step, we shall include the touch driver.

Stay tune.

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *