Lvgl porting - Linux fbdev&evdev (based on LVGL v7)

Although lvgl officially provides library functions for linux framebuffer operation, I decided to try whether I could implement this operation myself

In the actual project, the official library function should be preferred, and the official implementation code is located in the folder lv_drivers/display fbdev c.

This article records the whole process.
If there are any improper remarks and operations in the article, please do not hesitate to give comments, criticism and correction.

Project address: https://gitee.com/JensenHua/lvgl_fbdev_evdev

Final effect

[video(video-l4Rijug5-1616507869486)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=332141380)(image-https://ss.csdn.net/p?http://i0.hdslb.com/bfs/archive/3f35710d418cad9d0c4384f10f9f29548153c56b.jpg )(Title lvgl porting to Linux Framebuffer)]

Video preview: bilibili video link

What to do, write it first

  1. Build the basic framework of LVGL

  2. Implement and register the display function my_disp_flush

    The function prototype:

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)

You need to realize the function of rendering any area on the screen

Function example

	int32_t x,y;
	for(y = area->y1; y<=area->y2;y++) {
		for (x=area->x1; x<=area->x2; x++) {
			memcpy(fb_base + x*pixel_width + y*line_width,
						&color_p->full, sizeof(lv_color_t));
			color_p++;
		}
	}

	lv_disp_flush_ready(disp);

Register driver

	lv_disp_drv_t disp_drv;
	lv_disp_drv_init(&disp_drv);
	disp_drv.flush_cb = my_disp_flush;
	disp_drv.buffer = &disp_buf;
	lv_disp_drv_register(&disp_drv);

Here you have realized the display function of LVGL. LVGL can be used even without porting the input system.

  1. Implement and register the input function my_touchpad_read
    The function prototype:
bool my_touchpad_read(lv_indev_drv_t * indev, lv_indev_data_t * data)

This function stores the screen click position and whether the contact is pressed or released

Function example

bool my_touchpad_read(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
	/* store the collected data */
	data->state = my_touchpad_touchdown ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
	if(data->state == LV_INDEV_STATE_PR) {
		data->point.x = last_x;
		data->point.y = last_y;
	}

	return false;
}

Register driver

	/* register input device driver */
	lv_indev_drv_t indev_drv;
	lv_indev_drv_init(&indev_drv);
	indev_drv.type = LV_INDEV_TYPE_POINTER;
	indev_drv.read_cb = my_touchpad_read;
	lv_indev_drv_register(&indev_drv); 

My implementation process

Display part

Let's start with the general idea

The Linux kernel provides a device called framebuffer. Users can open the device node and display customized content on the output device through a series of operations. Using this feature, we can complete the display part of LVGL.

The display requirements of LVGL have been mentioned at the beginning of the article

I implemented a function void my_fb_init(void)
What does this function do?

  • Open the device node / dev/fb0
  • Get FB through ioctl_ var_ screeninfo
  • Calculate row width, single pixel width and number of screen pixels
  • Map framebuffer to memory
  • Clear entire screen

After doing these operations, you can_ disp_ The memcpy function is used in the flush function to store data in the memory where the framebuffer is located

How to calculate the position of the corresponding pixel in the framebuffer through the X and Y coordinate data?
framebuffer start address + y * horizontal screen pixel width + x * pixel width

Finally, attach the code

/**
 * Get the screen info.
 * mmap the framebuffer to memory.
 * clear the screen.
 * @param
 * @return
 */
void my_fb_init(void)
{
	fd_fb = open(DEFAULT_LINUX_FB_PATH, O_RDWR);
	if(fd_fb < 0){
		handle_error("can not open /dev/fb0");
	}

	/* already get fd_fb */
	if(ioctl(fd_fb, FBIOGET_VSCREENINFO, &var) < 0){
		handle_error("can not ioctl");
	}

	/* already get the var screen info */
	line_width = var.xres * var.bits_per_pixel / 8;
	pixel_width = var.bits_per_pixel / 8;

	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;

	/* mmap the fb_base */

	fb_base = (unsigned char *)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if(fb_base == (unsigned char *)-1){
		handle_error("can not mmap frame buffer");
	}

	/* alreay get the start addr of framebuffer */
	memset(fb_base, 0xff, screen_size); /* clear the screen */
	
}

Input device section

In the input part, I only realize single touch temporarily, and may realize multi touch in the future. The use of single touch will limit some interactive functions, such as double finger click events, double finger zoom in and out, etc.

Let's start with the general idea

The platform I selected has a 7-inch capacitive touch screen with a resolution of 1024 * 600. The device driver in the Linux operating system provides a series of input events (this part is limited to space and will not be discussed). My capacitive touch screen probably has the following events

  • Synchronization event EV_SYN

Used to interval events

  • Key event EV_KEY

Pressure value BTN_TOUCH

  • Absolute displacement event EV_ABS
  • Contact ID ABS_ MT_ TRACKING_ ID
  • X coordinate ABS_MT_POSITION_X ABS_X
  • Y coordinate ABS_MT_POSITION_Y ABS_Y

These events are enough for us to complete the migration of the input system

Input event handling

Asynchronous notification (preferred)

When reading input events using asynchronous notification, you need to provide a signal processing function called my in this project_ touchpad_ sig_ handler. In the main function, it is not allowed to create a separate thread to read input events. All operations are completed by the signal processing function.
First, make the following settings in the input device initialization function

    signal(SIGIO, my_touchpad_sig_handler);
    fcntl(indev_info.tp_fd, F_SETOWN, getpid());
    flags = fcntl(indev_info.tp_fd, F_GETFL);
    fcntl(indev_info.tp_fd, F_SETFL, flags | FASYNC | O_NONBLOCK);
    printf("Successfully run in async mode.\n");

Signal processing functions I use

/**
 * async signal handler
 * @param
 * @return
 */
void my_touchpad_sig_handler(int signal)
{
    while(read(indev_info.tp_fd, &indev_info.indev_event,
            sizeof(struct input_event)) > 0)
        my_touchpad_probe_event();
}

I extracted the event filtering function into a function_ touchpad_ probe_ Event, the code is as follows

void my_touchpad_probe_event(void)
{

    switch(indev_info.indev_event.type)
    {
        case EV_KEY:    /* Key event. Provide the pressure data of touchscreen*/
            if(indev_info.indev_event.code == BTN_TOUCH)          /* Screen touch event */
            {
                if(1 == indev_info.indev_event.value)         /* Touch down */
                {
                    indev_info.touchdown = true;
                }
                else if(0 == indev_info.indev_event.value)     /* Touch up */
                {
                    indev_info.touchdown = false;
                }
                else                            /* Unexcepted data */
                {
                    goto touchdown_err;
                }
            }
            break;
        case EV_ABS:    /* Abs event. Provide the position data of touchscreen*/
            if(indev_info.indev_event.code == ABS_MT_POSITION_X)
            {
                indev_info.last_x = indev_info.indev_event.value;
            }
            if(indev_info.indev_event.code == ABS_MT_POSITION_Y)
            {
                indev_info.last_y = indev_info.indev_event.value;
            }
            break;
        default:
            break;
    }
touchdown_err:      /* Do nothing. Just return and ready for next event come. */
    return;

}

Please pull the specific code to view the git warehouse

POLL mechanism (not recommended, affecting animation refresh rate)

It should be noted that the timing time of poll will directly affect the refresh speed of the screen, so I especially recommend that you set the time to < = 5ms, which is less than or equal to the corresponding time of the system recommended by the official. The smaller the value, the smoother the animation refresh.

The poll timing (INPUT_SAMEPLING_TIME) I use is 1ms

The transplantation of this part is more complicated than I thought. I implemented two functions: my and my_touchpad_init and my_touchpad_thread.

Let's look at the first function my_touchpad_init
This function only opens / dev/input/event1 and sets fd, events and events of pollfd structure array
function code

/**
 * Just initialize the touchpad
 * @param
 * @return
 */
void my_touchpad_init(void)
{
	
	tp_fd = open(DEFAULT_LINUX_TOUCHPAD_PATH, O_RDWR);
	if(tp_fd < 0){
		handle_error("can not open /dev/input/event1");
	}

	mpollfd[0].fd = tp_fd;
	mpollfd[0].events = POLLIN;
	mpollfd[0].revents = 0;

}

Let's look at the second function my_touchpad_thread
This function is used to process the input event and store the event value. First, call poll to implement the poll mechanism

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

Call read to read the data in the input device
Input through structure_ type in event to distinguish events, so as to store code values

It should be noted that we should process these data through an independent task. Fortunately, LVGL provides a function to create a task

lv_task_t * lv_task_create(lv_task_cb_t task_xcb, uint32_t period, lv_task_prio_t prio, void * user_data)

We use this function to create a thread to receive input events

/* create a thread to collect screen input data */
   lv_task_create(my_touchpad_thread, SYSTEM_RESPONSE_TIME, LV_TASK_PRIO_MID, NULL);

function code

/**
* A thread to collect input data of screen.
* @param
* @return
*/
void my_touchpad_thread(lv_task_t *task)
{
   (void)task;

   int len;
   
   len = poll(mpollfd, nfds, INPUT_SAMEPLING_TIME);
   
   	if(len > 0){		/* There is data to read */
   		
   		len = read(tp_fd, &my_event, sizeof(my_event));
   		if(len == sizeof(my_event)){ /* On success */
   		
   			//printf("get event: type = 0x%x,code = 0x%x,value = 0x%x\n",my_event.type,my_event.code,my_event.value);
   			switch(my_event.type)
   			{
   				case EV_SYN:	/* Sync event. Do nonthing */
   					break;
   				case EV_KEY:	/* Key event. Provide the pressure data of touchscreen*/
   					if(my_event.code == BTN_TOUCH)		/* Screen touch event */
   					{
   						if(my_event.value == 0x1)		/* Touch down */
   						{
   							//printf("screen touchdown\n");
   							my_touchpad_touchdown = true;
   						}
   							
   						else if(my_event.value == 0x0)	/* Touch up */
   						{
   							my_touchpad_touchdown = false;
   							//printf("screen touchdown\n");
   						}
   
   						else							/* Unexcepted data */
   							//printf("Unexcepted data\n");
   							goto touchdown_err;
   					}
   						
   					break;
   				case EV_ABS:	/* Abs event. Provide the position data of touchscreen*/
   					if(my_event.code == ABS_MT_POSITION_X)
   						last_x = my_event.value;
   					if(my_event.code == ABS_MT_POSITION_Y)
   						last_y = my_event.value;
   					break;
   					
   				default:
   					break;
   			}
   		}
   		else{			  /* On error */
   		
   			handle_error("read error\n");
   		}
   	}
   	else if(len == 0){	/* Time out */
   	
   		/* Do nothing */
   	}
   	else{	/* Error */
   		handle_error("poll error!");
   	}
   

   
touchdown_err:		/* Do nothing. Just return and ready for next event come. */
   return;

}

Apply the official introduction

LVGL is an open source graphics library. It provides everything needed to create an embedded GUI. It has easy-to-use graphics elements, beautiful visual effects and low memory consumption.

My last article Use of LVGL: PC simulator routines running LVGL , briefly introduces how to run lvgl program on PC

My platform NXP I.MX6ULL Cortex A7

My article may not be enough for you to complete your preliminary understanding of LVGL, but the richness of the official documents is surprising. If there are places in this article that you can't understand, you will LVGL development documentation Find the answer in

I highly recommend that you read the links in item 1.2.3 of the development document first. If you really don't understand it, you can make a general understanding with the translation tools.

Project preparation (you can skip this part if you understand the creation of LVGL project)

Keywords: LVGL

Added by iamyespee on Mon, 10 Jan 2022 09:04:12 +0200