LVGL linux arm平台上的详细移植过程(下)

本文接着上一篇 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

1.1 显示驱动

#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);

1.2 触摸屏驱动

在讲解触摸屏驱动前请先移植好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);

二、演示demo导入main.c的编写

#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头文件路径不然会报找不到头文件的错误。

三、自定义tick

然后还有比较重要的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!

你可能感兴趣的:(LVGL,笔记,linux,arm,lvgl)