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
-
Build the basic framework of LVGL
-
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.
- 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.