canooee 发表于 2024-12-2 13:17:12

【Avnet | NXP FRDM-MCXN947试用活动】RT-Thread移植LVGL

感谢参与本次活动,今天我们就在分享一下RTT移植LVGL

【前言】
RTT是国产的开源操作系统,LVGL作为一款开源的GUI框架,RTT提供LVGL的配置,可以减少手工添加海量包含文件工程量。虽然MCUXPresso提供了LVGL的工程示例,但是目前在RTT上移植LVGL的文章比较少。作者经过好几天的摸索,完成了整个工程的移植工程,在此分享如下:
【开发硬件】
1、FRDM-MCXN947开发板。
2、LCD屏:BOARD\_LCD\_S035【开发环境】

MDK 5.38

ENV工具

【基础工程生成】

1、源码下载。RTT与NXP已经提供了基于RTT的BSP,下载地址为 https://github.com/RT-Thread/rt-thread.git rt-thread

在文件下打开命令行输入: git clone https://github.com/RT-Thread/rt-thread.git rt-thread,就可以把RTT的源码下载到本地。

2、进入\rt-thread\bsp\nxp\mcx\mcxn\frdm-mcxn947,打开合命令行pkgs --update,同步工程。

3、执行scons --target=mdk5,生成mdk5的工程。

到此基础工程就生成了,下面将在此基础上进行外设的驱动、LVGL的配置。

【外设的驱动】
LCD屏的驱动,BOARD\_LCD\_S035的主控芯片为st7796,8080接口。为兼容官方LCD接口,开发板上的FlexIO LCD --J8为屏的接口,原理图如下:

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131316u6uivdmuemssim6z.png)

在NXP的官方示例中有现成的LCD屏动。因此只需要把LCD的驱动添加进工程,便可以驱动成功。
驱动用到的文件有lcd_impl_flexio.c/、st7796_lcd.c/h、
新建一个vide.c进行lcd屏的初始化

```
static lcd_impl_flexio_t s_lcd_impl;
st7796_lcd_t s_lcd = {
    .config =
      {
                        .direction = ST7796_DIR_0,
                        .pix_fmt   = ST7796_RGB565,
                        .bgr_mode= 1,
                        .inversion = 0,
                        .mirrored= 0,
                        .vflip = 1,
      },
    .cb =
      {
            .reset_cb      = lcd_impl_reset,
            .write_cmd_cb= lcd_impl_write_cmd,
            .write_data_cb = lcd_impl_write_data,
      },
    .user_data = &s_lcd_impl,
};


void display_init()
{
        BOARD_InitLCDFXIOPins();
        lcd_impl_init(&s_lcd_impl);
        st7796_lcd_init(&s_lcd);
}



void display_window_fill_buf(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye, uint16_t* color)
{

                st7796_lcd_load(&s_lcd, (uint8_t*)color, xs, xe, ys,ye);

}
```

此文件实现的功能为,将st7796实例化,同时封装了供lvgl写入一个buff的功能函数display_window_fill_buf
在MDK工程中,添加一个分组mylvgl,把这两个文件添加进去:

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131420v34drhyr14diielp.png)

到此LCD的初始化就完成了。
【LVGL的配置】
1、在env的配置工具中,打开LVGL选项,

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131507kt1fz9zh6wdu2t6l.png)

保存后,在线升级工程与重新生成MDK5工程。
2、在mdk工程中的mylvgl中添加lv_port_disp.c,以及lvgl_conf.h

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131524mc28euece8m8bmmv.png)

在lv_port_disp.c中添加lvgl的显示驱动代码如下:

```
#include <lvgl.h>
#include "video.h"

#define MY_DISP_HOR_RES   320
#define MY_DISP_VER_RES    480

#define DISP_BUFFER_LINES   (480/5)



/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;

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

/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1;

/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    display_window_fill_buf((uint16_t)(area->x1), (uint16_t)(area->y1), (uint16_t)(area->x2), (uint16_t)(area->y2),(uint16_t *)(color_p));
    /*IMPORTANT!!!
   *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

void lv_port_disp_init(void)
{
    /*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
    lv_disp_draw_buf_init(&disp_buf, buf_1, RT_NULL, MY_DISP_HOR_RES * DISP_BUFFER_LINES);

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

    /*Set the resolution of the display*/
    disp_drv.hor_res = 320;
    disp_drv.ver_res = 480;

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

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

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

在代码中主要是定义好屏的像素点,以及添加向屏写入一个区域的接口函数display_window_fill_buf。
到此,显示驱动就移植完成。

【触摸驱动】
1、屏的触摸驱动为gt911,在RTT中有现成的gt911的驱动,打开env配置工具,配置如下:
!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131542c7t96iy4mzadvnt7.png)

2、GT911为i2c接口,通过查看屏的原理图,接入的IO为P4_0、P4_1上,同时INT接到了P4_6上面。这样需要把屏的驱动配置到i2c2之上。
3、重新生成代码后,我们打开rtconfig.h添加配置如下:

```
#define BSP_USING_I2C
#define BSP_USING_I2C1
#define BSP_USING_I2C2
#define BSP_USING_TOUCH
#define RT_TOUCH_PIN_IRQ
```

4、添加一个分组,把gt911.c/touch.c添加进来,同时把对应的头文件路径也添加进工程。
5、添加lv_port_indev.c,进行触摸的初始化:

```
/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date         Author       Notes
* 2023-03-09   Rbb666       The first version
*/
#include <lvgl.h>
#include <rtdevice.h>
#define BSP_USING_TOUCH
#define DBG_TAG "lv_port_indev"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "touch.h"

#ifdef BSP_USING_TOUCH
#include "gt911.h"

#include "drv_pin.h"


static rt_device_t touch_dev;
static lv_indev_t *touch_indev;
struct rt_touch_data *read_data;

volatile static rt_uint8_t touch_detect_flag = 0;

static void touchpad_read(lv_indev_drv_t *indev, lv_indev_data_t *data)
{
    if (touch_detect_flag != 1)
      return;

    rt_device_read(touch_dev, 0, read_data, 1);

    if (read_data->event == RT_TOUCH_EVENT_NONE)
      return;

    data->point.x = read_data->x_coordinate;
    data->point.y = read_data->y_coordinate;

    if (read_data->event == RT_TOUCH_EVENT_DOWN)
      data->state = LV_INDEV_STATE_PR;
    if (read_data->event == RT_TOUCH_EVENT_MOVE)
      data->state = LV_INDEV_STATE_PR;
    if (read_data->event == RT_TOUCH_EVENT_UP)
      data->state = LV_INDEV_STATE_REL;

    touch_detect_flag = 0;
    rt_device_control(touch_dev, RT_TOUCH_CTRL_ENABLE_INT, RT_NULL);
}

static rt_err_t rx_callback(rt_device_t dev, rt_size_t size)
{
    touch_detect_flag = 1;
    rt_device_control(dev, RT_TOUCH_CTRL_DISABLE_INT, RT_NULL);
    return 0;
}

rt_err_t gt911_probe(rt_uint16_t x, rt_uint16_t y)
{
    void *id;

    touch_dev = rt_device_find("gt911");
    if (touch_dev == RT_NULL)
    {
      rt_kprintf("can't find device gt911\n");
      return -1;
    }

    if (rt_device_open(touch_dev, RT_DEVICE_FLAG_INT_RX) != RT_EOK)
    {
      rt_kprintf("open device failed!");
      return -1;
    }

    id = rt_malloc(sizeof(rt_uint8_t) * 8);
    rt_device_control(touch_dev, RT_TOUCH_CTRL_GET_ID, id);
    rt_uint8_t *read_id = (rt_uint8_t *)id;
    rt_kprintf("id = GT%d%d%d \n", read_id - '0', read_id - '0', read_id - '0');

    rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_X_RANGE, &x);/* if possible you can set your x y coordinate*/
    rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_Y_RANGE, &y);
    rt_device_control(touch_dev, RT_TOUCH_CTRL_GET_INFO, id);
    rt_kprintf("range_x = %d \n", (*(struct rt_touch_info *)id).range_x);
    rt_kprintf("range_y = %d \n", (*(struct rt_touch_info *)id).range_y);
    rt_kprintf("point_num = %d \n", (*(struct rt_touch_info *)id).point_num);
    rt_free(id);

    rt_device_set_rx_indicate(touch_dev, rx_callback);

    read_data = (struct rt_touch_data *)rt_calloc(1, sizeof(struct rt_touch_data));
    if (!read_data)
    {
      return -RT_ENOMEM;
    }

    return RT_EOK;
}

#define RST_PIN   ((4*32)+7)
#define INT_PIN   ((4*32)+6)

rt_err_t rt_hw_gt911_register(void)
{
    struct rt_touch_config cfg;
    rt_base_t int_pin = INT_PIN;
    rt_base_t rst_pin = NULL;

    cfg.dev_name = "i2c2";
    cfg.irq_pin.pin = int_pin;
    cfg.irq_pin.mode = PIN_MODE_INPUT_PULLDOWN;
    cfg.user_data = &rst_pin;

    rt_hw_gt911_init("gt911", &cfg);

    gt911_probe(320, 480);

    return RT_EOK;
}
#endif

void lv_port_indev_init(void)
{
#ifdef BSP_USING_TOUCH
    static lv_indev_drv_t indev_drv;         /* Descriptor of a input device driver */
    lv_indev_drv_init(&indev_drv);         /* Basic initialization */
    indev_drv.type = LV_INDEV_TYPE_POINTER;/* Touch pad is a pointer-like device */
    indev_drv.read_cb = touchpad_read;       /* Set your driver function */

    /* Register the driver in LVGL and save the created input device object */
    touch_indev = lv_indev_drv_register(&indev_drv);

    /* Register touch device */
    rt_err_t res = rt_hw_gt911_register();
    RT_ASSERT(res == RT_EOK);
#endif
}
```

代码中,需要指定GT9110的i2c为i2c2,同时添加RST、INIT_PIN为P4_7、P4_6。但是由于可能RST跟LCD是共用的,如果把RST也传进去,就会影响LCD,所以传进去时,我修改成null。

到此,触摸的驱动就完成。

【界面的设计】
NXP的LVGL设计工具gui_guider是一优秀的设计工具。
打开工具后生成一个320*480的界面,然后导入rtthead工程到
frdm-mcxn947目录下面:

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131605nmifnfefdov3g89z.png)

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131623qlhctss1ggxjhc4q.png)

新建gui_guider分配,把custom、generated文件夹中所有的.c文件添加进工程分组,同时把头文件添加进工程路径中。

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131637qzfekd1fc04ge222.png)

在lv_user_gui.c中添加代码如下,定位lvgl初始到gui-guider的初始化码之中:

```
#include "rtconfig.h"
#include "lvgl.h"
#include "events_init.h"
#include "gui_guider.h"
lv_ui guider_ui;
void lv_user_gui_init(void)
{
    /* display demo; you may replace with your LVGL application at here */
    setup_ui(&guider_ui);
    events_init(&guider_ui);
}
```

到此工程全部完成。下载代码到开发板,即可实现如下效果:

!(https://www.eefocus.com/forum/data/attachment/forum/202412/02/131657k1tmmslftmoolmol.png)
页: [1]
查看完整版本: 【Avnet | NXP FRDM-MCXN947试用活动】RT-Thread移植LVGL