LVGL5(PICO2移植3--LVGL)(TODO)

官方的支持还是TODO,看来这次不会太顺利。。。

1 添加LVGL到环境

在源文件中拉LVGL源代码。

 git clone https://github.com/lvgl/lvgl.git

修改CMakelist,增加

add_subdirectory(lvgl)

target_link_libraries(blink pico_stdlib freertos hardware_spi lvgl)

将lv_conf.h拷贝到外面的根目录。

cp lvgl/lv_conf_template.h ./lv_conf.h

做一些必要的修改和配置。。。

之后就可以make了,此时Make的时间暴增,在我的WSL虚拟机上要差不多20分钟了。可以看到,91%的文件都是LVGL的。这部分应该可以裁剪一些。。。

为了保证编译时间,要把example和demo去掉。在CMakeList中添加:

set(CONFIG_LV_BUILD_EXAMPLES 0)

set(CONFIG_LV_BUILD_DEMOS 0)

此外,在lv_conf.h中有freertos的宏。

#define LV_USE_OS   LV_OS_FREERTOS

最早以为要打开这个,结果打开了还要配置环境,实际可以不用也能出效果。

2 实现回调接口

参考LVGL的移植手册和Github的一些参考代码。

2.1 显示接口

官方的说明:https://docs.lvgl.io/9.2/porting/display.html

基本的就是下面两个接口,高级的后面再看吧。。。

void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)

绘制的接口,在某个区域绘制某个图形。

void lv_display_set_buffers(lv_display_t *disp, void *buf1, void *buf2, uint32_t buf_size, lv_display_render_mode_t render_mode)

双缓冲相关的。

实现如下:

flash接口。其实就是更新窗口的操作。调用之前的set_addr_window。

void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
	//LCD_SetWindow(area->x1, area->y1, area->x2 + 1, area->y2 + 1);
    set_addr_window(area->x1, area->y1, area->x2 + 1, area->y2 + 1);

	// ideally use by hardware
	// lv_draw_sw_rgb565_swap(px_map, TFT_WIDTH * TFT_HEIGHT / 10);
	uint32_t DataLen = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2;
    gpio_put(PIN_LCD_DC,1);
    gpio_put(PIN_LCD_CS,0);
	spi_write_blocking(SPI_PORT, px_map, DataLen);
	gpio_put(PIN_LCD_CS,1);

    /* IMPORTANT!!!
     * Inform LVGL that flushing is complete so buffer can be modified again. */
    lv_display_flush_ready(display);
}

Set_buffers接口。就是分配一片内存空间。这里使用pvPortMalloc分配而不是普通的Malloc。主要是考虑实时性和线程之间的安全。

uint32_t draw_buf_size = LCD_WIDTH * LCD_HEIGHT / 10 * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565);
    uint8_t *draw_buf = pvPortMalloc(draw_buf_size);
	lv_display_set_buffers(display, draw_buf, NULL, draw_buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);

2.2 TP接口

官方文档:https://docs.lvgl.io/9.2/porting/indev.html

总共的回调接口大概是两个。一个设置输入类型,一个读取输入。

lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);

bool indev_touch_read(lv_indev_t *drv, lv_indev_data_t *data)

实现如下:

static void indev_touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    uint16_t x, y;
    bool touched = xpt2046_read(&x, &y);

    if(touched) {
        data->state = LV_INDEV_STATE_PRESSED;
        data->point.x = x;
        data->point.y = y;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

void lv_port_indev_init(void)
{
    static lv_indev_drv_t indev_drv;

    lv_indev_drv_init(&indev_drv);

    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = indev_touch_read;

    lv_indev_t *mouse_indev = lv_indev_drv_register(&indev_drv);
}

3 main的修改

新的main

主要是增加lv_init,创建display,创建缓冲区,初始化界面,之后就是最重要的flash接口。

然后task要改成lvgl_task。之前LCD和TP单独的task都可以去掉了。

int main()
{
    stdio_init_all();
    hardware_init();

    lv_init();

    // Create a display
	lv_display_t * display = lv_display_create(LCD_WIDTH, LCD_HEIGHT);
	lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
	lv_disp_set_default(display);

    uint32_t draw_buf_size = LCD_WIDTH * LCD_HEIGHT / 10 * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565);
    uint8_t *draw_buf = pvPortMalloc(draw_buf_size);
	lv_display_set_buffers(display, draw_buf, NULL, draw_buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lv_display_set_flush_cb(display, my_flush_cb);

    init_gui();

    xTaskCreate(led_task, "LED_Task", 256, NULL, 1, NULL);
    //xTaskCreate(vTaskLCD, "LCD", 4096, NULL, 1, NULL);
    //xTaskCreate(vTaskTP, "TP", 1024, NULL, 3, NULL);
    xTaskCreate(lvgl_task, "lvgl_task", 1024, NULL, 1, NULL);   
    vTaskStartScheduler();

    //lv_demo_widgets();        // 或其他 demo

    while (1) {
        lv_timer_handler();   // LVGL 主循环
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

lvgl_task实现如下:

void lvgl_task(void *pvParameters)
{
    lcd_init();      // 初始化 LCD(ILI9486)
    //lv_port_indev_init();     // 初始化触摸(XPT2046)

    //lv_demo_widgets();        // 或其他 demo

    while (1) {
        uint32_t time_till_next = lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(time_till_next));
    }
}

init_gui()就是具体画图,效果就是如下所示。

4 效果

4.1 第一版

只实现了LCD接口的效果。

从效果来看,明显颜色发白。初步判断是Gamma校正没做。

5 遇到的坑

5.1 调试

之前写过,调试用的是uart,也就是在cmakelist中开一下。pico_enable_stdio_usb(blink 1),但是真的遇到问题,这个不行。原因是很多异常发生在初始化的时候,uart启动的时候,可能程序就已经异常了。或者说等串口工具连上,MCU早就卡死了。

也就是说printf几乎只能用在有限的场合。

其实有两种方式感觉好一点,一个是led,有点类似心跳,通过这样来观察MCU的状态。通过改变间隔时间,还可以扩展。。。

还有一个就是SWD,这个专门会研究。

5.2 FreeRTOSConfig.h的修改

最开始我没有改这个,一直很多报错。后面看了下参考的实现,发现改动很多。

好吧,直接用大神的吧。。。

参考

https://github.com/GiulianoTo/Pico-ResTouch-LCD-3.5-examples

https://github.com/rprouse/pico-displayDrivs/tree/e5b90cb32beb70cb331b47d96d4040eb01aaddcb

https://docs.lvgl.io/9.2/porting/index.html

微雪的官方驱动:https://github.com/GiulianoTo/Pico-ResTouch-LCD-3.5-examples/tree/main/waveshare-c-demo/Pico-ResTouch-LCD-X_X_Code

3 显示接口

https://docs.lvgl.io/master/details/main-modules/display/index.html

从官方文档看,有4个接口要实现。

Creating a Display
Draw Buffer(s)
Flush Callback
Flush-Wait Callback

其中,Creating a Display是屏幕的初始化,Draw Buffer是绘图,Flush Callback是刷新回调。Flush-Wait Callback刷新等待回调(可选,在低功耗或高性能场景时可能会用到)

看起来就是三个接口要实现。

3.1 初始化

#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "ili9488_driver.h"

#define ILI9488_SPI spi1
#define PIN_MOSI 11
#define PIN_SCK  10
#define PIN_CS   9
#define PIN_DC   8
#define PIN_RST  7
#define PIN_BL   6

void ili9488_send_cmd(uint8_t cmd) {
    gpio_put(PIN_DC, 0);
    spi_write_blocking(ILI9488_SPI, &cmd, 1);
}

void ili9488_send_data(const uint8_t *data, size_t len) {
    gpio_put(PIN_DC, 1);
    spi_write_blocking(ILI9488_SPI, data, len);
}

void ili9488_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    uint8_t buf[4];
    ili9488_send_cmd(0x2A);
    buf[0] = x0 >> 8; buf[1] = x0;
    buf[2] = x1 >> 8; buf[3] = x1;
    ili9488_send_data(buf, 4);
    ili9488_send_cmd(0x2B);
    buf[0] = y0 >> 8; buf[1] = y0;
    buf[2] = y1 >> 8; buf[3] = y1;
    ili9488_send_data(buf, 4);
    ili9488_send_cmd(0x2C);
}

void ili9488_init(void) {
    spi_init(ILI9488_SPI, 40 * 1000 * 1000);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_init(PIN_CS); gpio_set_dir(PIN_CS, GPIO_OUT);
    gpio_init(PIN_DC); gpio_set_dir(PIN_DC, GPIO_OUT);
    gpio_init(PIN_RST); gpio_set_dir(PIN_RST, GPIO_OUT);
    gpio_init(PIN_BL); gpio_set_dir(PIN_BL, GPIO_OUT);

    gpio_put(PIN_CS, 1);
    gpio_put(PIN_RST, 0); sleep_ms(100);
    gpio_put(PIN_RST, 1); sleep_ms(100);

    // ILI9488 初始化序列,可用 Adafruit ILI9488 的寄存器表
    // 比如:ILI9488_MADCTL、COLMOD、SLPOUT、DISPON
    // 颜色模式16bit:
    ili9488_send_cmd(0x3A);
    uint8_t colmod = 0x55;
    ili9488_send_data(&colmod, 1);
}
 

注册设备

void lv_port_disp_init(void)
{
    static lv_color_t buf1[SCREEN_HOR_RES * 20];
    static lv_color_t buf2[SCREEN_HOR_RES * 20];

    lv_display_t * disp = lv_display_create(SCREEN_HOR_RES, SCREEN_VER_RES);
    lv_display_set_flush_cb(disp, my_flush_cb);
    lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISP_RENDER_MODE_PARTIAL);
}

3.2 flush_callback

#include "lvgl.h"
#include "ili9488_driver.h"

#define SCREEN_HOR_RES 480
#define SCREEN_VER_RES 320

void my_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p)
{
    uint16_t x1 = area->x1;
    uint16_t y1 = area->y1;
    uint16_t w = area->x2 - area->x1 + 1;
    uint16_t h = area->y2 - area->y1 + 1;

    ili9488_set_window(x1, y1, x1 + w - 1, y1 + h - 1);
    ili9488_send_data((uint8_t*)color_p, w * h * 2); // RGB565

    lv_display_flush_ready(disp);
}
 

4 输入接口

https://docs.lvgl.io/master/details/main-modules/indev/index.html

按照介绍,输入接口只有两部分。

  1. a type: pointer, keypad, etc.

  2. a read callback: to read the current touch point or pressed key

选择设备类型,写回调。。分别是一下两个函数。

lv_indev_t * indev = lv_indev_create();        /* Create input device */
lv_indev_set_type(indev, LV_INDEV_TYPE_...);   /* Set the device type */
lv_indev_set_read_cb(indev, my_input_read);    /* Set the read callback */

4.1 初始化

#include "hardware/spi.h"
#include "xpt2046_driver.h"

#define TP_SPI  spi1
#define TP_CS   15
#define TP_IRQ  14

void xpt2046_init(void) {
    gpio_init(TP_CS);
    gpio_set_dir(TP_CS, GPIO_OUT);
    gpio_put(TP_CS, 1);
}

static uint16_t xpt2046_read(uint8_t cmd) {
    uint8_t buf[3] = {cmd, 0, 0};
    uint8_t rx[3];
    gpio_put(TP_CS, 0);
    spi_write_read_blocking(TP_SPI, buf, rx, 3);
    gpio_put(TP_CS, 1);
    return ((rx[1] << 8) | rx[2]) >> 4;
}

bool xpt2046_get_xy(uint16_t *x, uint16_t *y) {
    *x = xpt2046_read(0xD0); // X+
    *y = xpt2046_read(0x90); // Y+
    return true;
}
 

注册到LVGL

void lv_port_indev_init(void)
{
    lv_indev_t * indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev, my_touch_read);
}

4.2 输入回调

#include "lvgl.h"
#include "xpt2046_driver.h"

void my_touch_read(lv_indev_t * indev, lv_indev_data_t * data)
{
    uint16_t x, y;
    bool touched = xpt2046_get_xy(&x, &y);
    if (touched) {
        data->point.x = (x * 480) / 4095; // 映射
        data->point.y = (y * 320) / 4095;
        data->state = LV_INDEV_STATE_PRESSED;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}
 

5 集成

main

#include "pico/stdlib.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "ili9488_driver.h"
#include "xpt2046_driver.h"

int main() {
    stdio_init_all();
    ili9488_init();
    xpt2046_init();
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello LVGL 9 on Pico2!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

    while (1) {
        lv_timer_handler();
        sleep_ms(5);
    }
}
 

cmake

cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(pico_lvgl_xpt2046)

pico_sdk_init()

add_executable(pico_lvgl_xpt2046
    main.c
    ili9488_driver.c
    xpt2046_driver.c
    lv_port_disp.c
    lv_port_indev.c
)

target_include_directories(pico_lvgl_xpt2046 PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/lvgl
)

add_subdirectory(lvgl)
target_link_libraries(pico_lvgl_xpt2046 pico_stdlib hardware_spi)
pico_add_extra_outputs(pico_lvgl_xpt2046)
 

问题一览

现象原因对策
屏幕白没复位或SPI线反检查RST/DC连接
颜色错误16bit模式没设置对确保 0x3A=0x55
触摸偏移坐标未校准加简单线性映射或校准算法
SPI太慢提升SPI频率(40MHz以内)

6 性能提升

使用双缓冲 (buf1 + buf2);

开启 LVGL 的 partial render 模式;

SPI 频率设为 40MHz(尝试继续提升);

如果屏幕太慢,可以 DMA 驱动 SPI。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值