本文接着上一篇 LVGL linux arm平台上的详细移植过程 开始讲解驱动部分和main.c demo部分的移植和编写。
驱动部分我们需要将 src\lvgl\examples\porting 底下的这几个文件
src
├── lvgl
├── examples
└── porting
├── lv_port_disp_template.c
├── lv_port_disp_template.h
├── lv_port_indev_template.c
└── lv_port_indev_template.h
重命名后放置到 src\lv_drivers 目录下
└── src
├── lv_drivers
├── lv_port_disp.c #9
├── lv_port_disp.h #10
├── lv_port_indev.c #11
└── lv_port_indev.h #12
#9 lv_port_disp.c 显示相关实现 lv_port_disp_init 里的disp_init实现及修改,修改见下面中文注释部分;可以参考lv_drivers\display\fbdev.c 中通用的linux fb初始化及绘图过程。我们以rgb565 为例。 lv_port_disp.h 中 extern void lv_port_disp_init(void);导出初始化函数接口。
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init(); //初始化你的屏幕framebuffer
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
//lvgl有三种buffer模式用来draw,看下面注释
/* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed your display drivers `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are three buffering configurations:
* 1. Create ONE buffer with some rows:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer with some rows:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Create TWO screen-sized buffer:
* Similar to 2) but the buffer have to be screen sized. When LVGL is ready it will give the
* whole frame to display. This way you only need to change the frame buffer's address instead of
* copying the pixels.
* */
//arm的内存比较大,我们选择第三种buffer大小
/* Example for 3) */
static lv_disp_buf_t draw_buf_dsc_3;
static lv_color_t draw_buf_3_1[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*A screen sized buffer*/
static lv_color_t draw_buf_3_2[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*An other screen sized buffer*/
lv_disp_buf_init(&draw_buf_dsc_3, draw_buf_3_1, draw_buf_3_2, LV_HOR_RES_MAX * LV_VER_RES_MAX); /*Initialize the display buffer*/
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
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 = 1024; //根据屏幕分辩率参数设置
disp_drv.ver_res = 600;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush; //绘图的回调函数,待会要重点实现
/*Set a display buffer*/
disp_drv.buffer = &draw_buf_dsc_3; //这里改成第三个buffer
#if LV_USE_GPU
/*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/
/*Blend two color array using opacity*/
disp_drv.gpu_blend_cb = gpu_blend;
/*Fill a memory array with a color*/
disp_drv.gpu_fill_cb = gpu_fill;
#endif
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
打开及初始化framebuffer
static struct fb_var_screeninfo vinfo;
static struct fb_fix_screeninfo finfo;
static char *fbp = 0;
static long int screensize = 0;
static int fbfd = 0;
static void disp_init(void)
{
// Open the file for reading and writing
fbfd = open(FBDEV_PATH, O_RDWR); //FBDEV_PATH 在lv_conf.h中定义一般是/dev/fb0
if(fbfd == -1) {
perror("Error: cannot open framebuffer device");
return;
}
printf("The framebuffer device was opened successfully.\n");
// Get fixed screen information
if(ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
perror("Error reading fixed information");
return;
}
// Get variable screen information
if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
perror("Error reading variable information");
return;
}
printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
// Figure out the size of the screen in bytes
screensize = finfo.smem_len; //finfo.line_length * vinfo.yres;
// Map the device to memory 映射frambuffer到虚拟内存空间提供给画图操作
fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if((intptr_t)fbp == -1) {
perror("Error: failed to map framebuffer device to memory");
return;
}
memset(fbp, 0, screensize);
printf("The framebuffer device was mapped to memory successfully.\n");
}
实现画图函数:
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
if(fbp == NULL ||
area->x2 < 0 ||
area->y2 < 0 ||
area->x1 > (int32_t)vinfo.xres - 1 ||
area->y1 > (int32_t)vinfo.yres - 1) {
lv_disp_flush_ready(disp_drv);
return;
}
/*Truncate the area to the screen*/
int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
int32_t act_x2 = area->x2 > (int32_t)vinfo.xres - 1 ? (int32_t)vinfo.xres - 1 : area->x2;
int32_t act_y2 = area->y2 > (int32_t)vinfo.yres - 1 ? (int32_t)vinfo.yres - 1 : area->y2;
lv_coord_t w = (act_x2 - act_x1 + 1);
long int location = 0;
long int byte_location = 0;
unsigned char bit_location = 0;
/*32 or 24 bit per pixel*/
if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) {
uint32_t * fbp32 = (uint32_t *)fbp;
int32_t y;
for(y = act_y1; y <= act_y2; y++) {
location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 4;
memcpy(&fbp32[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 4);
color_p += w;
}
}
/*16 bit per pixel 显然我们会调用以下函数进行绘图*/
else if(vinfo.bits_per_pixel == 16) {
uint16_t * fbp16 = (uint16_t *)fbp;
int32_t y;
for(y = act_y1; y <= act_y2; y++) {
location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 2;
memcpy(&fbp16[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 2);
color_p += w;
}
}
/*8 bit per pixel*/
else if(vinfo.bits_per_pixel == 8) {
uint8_t * fbp8 = (uint8_t *)fbp;
int32_t y;
for(y = act_y1; y <= act_y2; y++) {
location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length;
memcpy(&fbp8[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1));
color_p += w;
}
}
/*1 bit per pixel*/
else if(vinfo.bits_per_pixel == 1) {
uint8_t * fbp8 = (uint8_t *)fbp;
int32_t x;
int32_t y;
for(y = act_y1; y <= act_y2; y++) {
for(x = act_x1; x <= act_x2; x++) {
location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * vinfo.xres;
byte_location = location / 8; /* find the byte we need to change */
bit_location = location % 8; /* inside the byte found, find the bit we need to change */
fbp8[byte_location] &= ~(((uint8_t)(1)) << bit_location);
fbp8[byte_location] |= ((uint8_t)(color_p->full)) << bit_location;
color_p++;
}
color_p += area->x2 - act_x2;
}
} else {
/*Not supported bit per pixel*/
}
//May be some direct update command is required
//ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect));
lv_disp_flush_ready(disp_drv);
}
#10 lv_port_disp.h 导出初始化函数main.c中需要先初始化
/**********************
* MACROS
**********************/
extern void lv_port_disp_init(void);
在讲解触摸屏驱动前请先移植好tslib 触摸屏的坐标事件读取将采用tslib的接口 ,根据上篇将so和头文件放到相应的位置。
#11 lv_port_indev.c
#include "lv_port_indev.h"
#include "../lv_drv_conf.h" //添加依赖的头文件
#include "../tslib.h" //添加tslib库头文件
#include
#include
#include
#include
#include
#include
#include //如果你用了epoll 或者select可添加此头文件
#include
#include
#include
#include //如果你是自己处理去抖动那直接读取触摸屏事件可以添加此头文件
#include
static struct tsdev *ts = NULL;
void lv_port_indev_init(void)
{
/* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
lv_indev_drv_t indev_drv;
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad if you have*/
touchpad_init();//触摸屏驱动初始化
/*Register a touchpad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read; //读取触摸事件的回调函数,tasklist中会调用
indev_touchpad = lv_indev_drv_register(&indev_drv); //将input drv注册到tasklist
//下面的mouse key等的都注释掉不实现。
}
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
ts = ts_setup(NULL, 1); //初始化触摸屏,注意ts_setup的第二个参数要设置为1非阻塞,如果设置成0阻塞的话那tasklist就无法处理了会卡在这里。
if (!ts) {
perror("ts_open");
exit(1);
}
}
实现触摸屏事件的读取
/* Will be called by the library to read the touchpad */
static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
struct ts_sample samp;
int ret;
ret = ts_read(ts, &samp, 1);
if (ret < 0) {
perror("ts_read");
ts_close(ts);
exit(1);
}
if (ret != 1)
return true;
if (samp.pressure > 0) {
data->state = LV_INDEV_STATE_PR;
}
else{
data->state = LV_INDEV_STATE_REL;
}
/*Set the last pressed coordinates*/
data->point.x = samp.x;
data->point.y = samp.y;
/*Return `false` because we are not buffering and no more data to read*/
return false;
}
#12 lv_port_indev.h 导出初始化函数
/**********************
* MACROS
**********************/
extern void lv_port_indev_init(void);
#include "lvgl.h"
#include
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_demo_widgets.h"
void lvgl_first_demo_start(void);
int main(void)
{
lv_init(); //lvgl 系统初始化
lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面
lv_port_indev_init(); //lvgl 输入接口初始化,放在 lv_init()的后面
//lvgl_first_demo_start(); //打开这个注释就是一个画button的简单demo 注意注释掉下面的lv_demo_widgets
lv_demo_widgets();
printf("start demo \n");
while(1)
{
lv_task_handler();
}
}
void tim_lv_tick()
{
lv_tick_inc(1);//lvgl 的 1ms 心跳
}
static void btn_event_cb(lv_obj_t * btn, lv_event_t event)
{
if(event == LV_EVENT_CLICKED) {
static uint8_t cnt = 0;
cnt++;
/*Get the first child of the button which is the label and change its text*/
lv_obj_t * label = lv_obj_get_child(btn, NULL);
lv_label_set_text_fmt(label, "Button: %d", cnt);
}
}
void lvgl_first_demo_start(void)
{
lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL); /*Add a button the current screen*/
lv_obj_set_pos(btn, 10, 10); /*Set its position*/
lv_obj_set_size(btn, 120, 50); /*Set its size*/
lv_obj_set_event_cb(btn, btn_event_cb); /*Assign a callback to the button*/
lv_obj_t * label = lv_label_create(btn, NULL); /*Add a label to the button*/
lv_label_set_text(label, "Button"); /*Set the labels text*/
lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
lv_label_set_text(label1, "Hello world!");
lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_align(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);
}
main.c函数的作用就是编译出来看效果的,在这里要包含驱动的#include “lv_port_disp.h” #include “lv_port_indev.h” 两个头文件并初始化驱动。lv_demos如何使用呢?
1、打开lv_ex_conf.h
/*Show some widget*/
#define LV_USE_DEMO_WIDGETS 1 //打开对于的demo定义
2、包含你要运行的lv_demos的相应demo的头文件#include “lv_demo_widgets.h”
3、在完成初始化后调用demo入口函数lv_demo_widgets(); 由于前面我们已经用cmake将demo代码编译成为库,故这里直接引用即可。
4、最后记得在src 的CMakeLists.txt下添加include_directories(./lv_demos/src/lv_demo_widgets/) #设置引用demo头文件路径不然会报找不到头文件的错误。
然后还有比较重要的systick的实现
/* 1: use a custom tick source.
* It removes the need to manually update the tick with `lv_tick_inc`) */
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM == 1
#define LV_TICK_CUSTOM_INCLUDE "../../../sys_tick.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
#13 sys_tick.c
#include "sys_tick.h"
#include
uint32_t custom_tick_get(void)
{
static uint64_t start_ms = 0;
if(start_ms == 0) {
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
}
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t now_ms;
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
uint32_t time_ms = now_ms - start_ms;
return time_ms;
}
#14 sys_tick.h
#ifndef __SYS_TICK__H
#define __SYS_TICK__H
#include
#include
#include
extern uint32_t custom_tick_get(void);
#endif
分析tick的使用:
在main中while 1 里面lv_task_handler 这里是处理tasklist的循环
uint32_t handler_start = lv_tick_get(); //如果你用的是custom的
return LV_TICK_CUSTOM_SYS_TIME_EXPR;
LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) //调用的是我们定义的接口
好了现在工程已经移植完成,接下来去到工程根目录下
cd lvgl_prj
cmake . //这里有个 . 哈,表示当前文件这步之后会生成Makefile
make
编译。关于错误,一般都是头文件包含路径的错误,你可以加 …/lvgl/lvgl.h 以定位到src目录再索引到lvgl的头文件,或者你可以在cmakelists.txt中去导出这个头文件路径。
关于字体宏的一些错误,请根据错误相应的打开lv_conf.h 中的
#define LV_FONT_MONTSERRAT_10 1
#define LV_FONT_MONTSERRAT_12 1
以消除错误。
至此,lvgl的移植已完成。
Enjoy The LVGL For Arm Linux!