官方的支持还是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
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 6void 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 320void 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); // RGB565lv_display_flush_ready(disp);
}
4 输入接口
https://docs.lvgl.io/master/details/main-modules/indev/index.html
按照介绍,输入接口只有两部分。
a type: pointer, keypad, etc.
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 14void 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。

530

被折叠的 条评论
为什么被折叠?



