模板: RP2350-1.28-Touch-LVGL

来自Waveshare Wiki
跳转至: 导航搜索

LVGL 示例程序

C

示例效果

本例显示的效果为三个可以通过触摸屏滑动或六轴传感器抬动切换的界面。

  • 第一个界面
    • 显示内容:微雪电子的 Logo。
      RP2040-Touch-LCD-1.28-LVGL-title1.png
  • 第二个界面
    • 显示内容:六轴传感器的数据.
    • 刷新频率:每 500ms 更新一次。
    • 切换方式:通过在第一个界面触屏从下向上划动进行切换,也可以利用六轴传感器右抬触发输入设备确定事件选中界面,再向上抬触发切换事件实现界面切换。
      RP2040-Touch-LCD-1.28-LVGL-title2.png
  • 第三个界面
    • 显示内容:四个可以进行简单交互的控件。
    • 交互方式:可以通过触摸屏触屏交互,也可以使用六轴传感器抬动交互(上下抬切换选中的控件,初次右抬进入编辑模式,再次右抬进行交互)。
    • 切换方式:通过在第二个界面触屏从下向上划动进行切换,也可以利用六轴传感器右抬触发输入设备确定事件选中界面,再向上抬触发切换事件实现界面切换。
      RP2040-Touch-LCD-1.28-LVGL-title3.png

      注意:由于控件是界面的子对象,所以在使用六轴传感器进行交互时无法从选中界面切换到选中控件,可以通过触摸来重新选择控件。

示例简介

本例用于测试 LVGL 控件交互、样式美化等, LVGL 的具体开发请参考LVGL开发文档

源码结构
  • LVGL 库的源码位于工程文件夹的 ./lib/lvgl ,使用的版本号为8.1,二次开发请参考对应版本的开发文档。
  • LVGL 库的相关设置在工程文件夹的 ./examples/inc/lv_conf.h 中,可以设置显示刷新频率、系统占用数据等。
    Img1.png
  • LVGL 库的应用代码位于工程文件夹的 ./examples/src/LVGL_example.c
    RP2040-Touch-LCD-1.28-LVGL-src.png

实现功能

  1. 本例使用 DMA 传输颜色数据到 SPI 总线,降低 CPU 的利用率,在进行简单交互时可以控制 CPU 的占用率在 20% 以下,内存占用在 30% 以下。
  2. 本例系统时钟为 270MHz ,设置 SPI 的外围时钟频率与系统时钟一致,同时使用 LVGL 库的双缓冲区机制,在进行一个缓冲区的数据传输时另一个缓冲区进行渲染,保证了动画的流畅度。
  3. 本例使用触摸屏与六轴传感器两路输入设备,其中六轴模拟器利用陀螺仪数据模拟为编码器输入设备,可以应用于多个交互场景。

编译运行

  • 进入源码目录,如果 build 目录已经存在,则可以直接进入。如果没有,执行:
    mkdir build
  • 进入 build 目录,并添加SDK的地址:
    cd build
    export PICO_SDK_PATH=../../pico-sdk

注意:pico-sdk 的路径需要按照实际情况进行调整

  • 执行 cmake ,自动生成 Makefile 文件:
    cmake ..
  • 执行 make 生成可执行文件,然后在终端中输入:
    make

等待编译好后将生成的 .uf2 格式的文件复制到pico中即可。

源码解析

LVGL 初始化

  • LVGL 库的初始化函数
    • 实现功能:主要用于初始化 LVGL 所需的硬件和结构体变量。
    void LVGL_Init(void);
  • LVGL 库核心结构体变量定义
    • 定义功能:*LVGL 库的初始化流程主要就是对几个 LVGL 核心结构体变量的初始化,LVGL库的运行依靠核心结构体变量。
    • 定义方式: buf0 和 buf1 大小设置为屏幕显示面积的一半是为了实现 LVGL 双缓冲机制,能够在降低大面积刷屏锯齿的同时有效提高屏幕刷新率;在使用单缓冲区时最好设置为屏幕显示面积的10%,可以有效降低系统占用但在大面积图像刷新时会出现较明显的锯齿。
    // LVGL
    static lv_disp_draw_buf_t disp_buf;                   //LVGL显示缓冲区
    static lv_color_t buf0[DISP_HOR_RES * DISP_VER_RES/2];//LVGL颜色数据缓冲区0
    static lv_color_t buf1[DISP_HOR_RES * DISP_VER_RES/2];//LVGL颜色数据缓冲区1
    static lv_disp_drv_t disp_drv;                        //LVGL显示驱动
    
    static lv_indev_drv_t indev_ts;                       //LVGL触摸屏输入设备驱动
    static lv_indev_drv_t indev_en;                       //LVGL六轴传感器模拟编码器输入设备驱动
    static lv_group_t *group;                             //编码器控件组                  
    
  • LVGL 库的初始化函数实现
    • 实现功能:根据设计需求完善 LVGL 库核心结构体变量。
    • 实现方式: LVGL 库的编码器输入设备拥有编辑模式和浏览模式,在浏览模式下触发切换事件就会在控件组内切换选中的控件,触发确认事件进入选中控件的编辑模式。所以让六轴传感器模拟编码器时必须要引入 lv_group_t 结构体变量作为六轴传感器的控件组。
    // 初始化LVGL核心
    lv_init();
    
    // 初始化LVGL显示缓冲区结构体变量disp_buf
    lv_disp_draw_buf_init(&disp_buf, buf0, buf1, DISP_HOR_RES * DISP_VER_RES / 2); 
    lv_disp_drv_init(&disp_drv);    
    disp_drv.flush_cb = disp_flush_cb;
    disp_drv.draw_buf = &disp_buf;        
    disp_drv.hor_res = DISP_HOR_RES;
    disp_drv.ver_res = DISP_VER_RES;
    lv_disp_t *disp= lv_disp_drv_register(&disp_drv);   
    
    // 初始化触摸屏输入设备
    lv_indev_drv_init(&indev_ts); 
    indev_ts.type = LV_INDEV_TYPE_POINTER;    
    indev_ts.read_cb = ts_read_cb;            
    lv_indev_t * ts_indev = lv_indev_drv_register(&indev_ts);
    
    // 初始化编码器输入设备
    lv_indev_drv_init(&indev_en);   
    indev_en.type = LV_INDEV_TYPE_ENCODER;  
    indev_en.read_cb = encoder_read_cb;         
    lv_indev_t * encoder_indev = lv_indev_drv_register(&indev_en);
    group = lv_group_create();
    lv_indev_set_group(encoder_indev, group);//将编码器控制设备添加控件组
    
  • LVGL 库运行实现
    • 实现功能:LVGL 库定时调用函数 lv_tick_inc 来通知LVGL过去的时间,以便 LVGL 能够更新其内部的时间状态,处理与时间相关的任务,例如动画、定时器等。在主函数的循环中还需要调用 lv_task_handler 函数来处理一些设定好的任务。
    • 实现方式:需要确保 lv_task_handler 的优先级低于 lv_tick_inc 的优先级,所以在本例中 lv_tick_inc 在定时器回调函数中调用。
    add_repeating_timer_ms(5, repeating_lvgl_timer_callback, NULL, &lvgl_timer);//每5ms调用一次定时器回调函数
    
    static bool repeating_lvgl_timer_callback(struct repeating_timer *t) 
    {
        lv_tick_inc(5);
        return true;
    }
    
    int main()
    {
        ...
        while(1)
        {
          lv_task_handler();
          DEV_Delay_ms(5); 
        }
    
    }
    

LVGL 显示

  • LVGL 显示回调函数
    • 实现功能:主要完成图像在刷新区的绘制。
    void disp_flush( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p )
    参数:
        lv_disp_drv_t *disp_drv: 显示驱动结构体指针,包含了与显示相关的信息和函数指针。该参数常用于通知刷新完成
        const lv_area_t *area  : 区域结构体指针,包含待刷新区域的位置信息。在本例中,用于创建 TFT 显示的窗口
        lv_color_t *color_p    : 颜色结构体指针,表示要在刷新区域内显示的颜色数据。在本例中,作为DMA输入读取地址将数据传输到SPI总线,完成图像的绘制
    
  • LVGL 显示颜色设置
    • 设置目的:由于 lv_color_t 结构体在默认状态下所构建的像素颜色储存方式与本例需要传输的数据不一致,直接进行传输会导致显示的图像出现色差。
    • 设置方式:在 lv_conf.h 文件夹中修改设置改变颜色的储存方式。
    #define LV_COLOR_16_SWAP 1
    
  • LVGL 显示刷新率设置
    • 设置方式:在 lv_conf.h 中还能设置显示缓冲区刷新频率的时间,可以修改这个定义来改变屏幕的刷新时间。
    #define LV_DISP_DEF_REFR_PERIOD  10     /*[ms]*/
  • LVGL 显示回调函数实现
    • 实现方式:在本例中为了最大化降低处理器的利用率,使用 DMA 来进行颜色数据的传输,设置 color_p 作为读地址,SPI 总线的输出数据寄存器为写地址。
    static void disp_flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
    {
    
        LCD_1IN28_SetWindows(area->x1, area->y1, area->x2 , area->y2);//设置图像的显示区域
        dma_channel_configure(dma_tx,
                              &c,
                              &spi_get_hw(LCD_SPI_PORT)->dr,          //SPI总线的输出数据寄存器地址
                              color_p,                                //待刷新的颜色数据数组地址
                              ((area->x2 + 1 - area-> x1)*(area->y2 + 1 - area -> y1))*2,
                              true                                    //设置完毕后立刻进行传输
                              );
    }
    
  • LVGL 刷新完成通知实现
    • 实现功能:每一次图像刷新完成后都需要通知 LVGL 核心,以便 LVGL 准备下个刷新图像的渲染。
    • 实现方式:本例在 DMA 传输完成中断服务函数中通知 LVGL 图像刷新完成,如果使用阻塞通知的机制就无法利用双缓冲机制来提高刷新速度。
    static void dma_handler(void)
    {
        if (dma_channel_get_irq0_status(dma_tx)) {
            dma_channel_acknowledge_irq0(dma_tx);
            lv_disp_flush_ready(&disp_drv);         
        }
    }
    

LVGL 输入

  • LVGL 的输入设备回调函数
    • 实现功能:主要用于更新输入事件。
    static void ts_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data);
    参数:
      lv_indev_drv_t *indev_drv: LVGL 中的输入设备驱动结构体指针。在本例中,该结构体为触摸屏输入设备驱动
      lv_indev_data_t *data    : LVGL 中的输入设备数据结构体指针。在本例中,该结构体用于存储输入设备的状态和数据,包括当前的触摸状态(按下或释放)以及触摸点的坐标
    static void encoder_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data);
    参数:
      lv_indev_drv_t *indev_drv: LVGL 中的输入设备驱动结构体指针。在本例中,该结构体为六轴传感器模拟的编码器输入设备驱动
      lv_indev_data_t *data    : LVGL 中的输入设备数据结构体指针。在本例中,该结构体用于存储输入设备的状态和数据,包括当前的切换事件(上抬或下抬)以及确定事件(右抬)
    
  • LVGL 调用输入设备回调函数的频率设置
    • 设置方式: LVGL 默认每 30ms 调用一次输入设备回调函数来更新输入设备所触发的事件,可以在 lv_conf.h 中进行设置。
    #define LV_INDEV_DEF_READ_PERIOD 30     /*[ms]*/
    
  • 触摸屏输入设备的回调函数实现
    • 实现方式:主要是通过触摸中断更新触摸屏的触摸状态和触摸点坐标。
    static void touch_callback(uint gpio, uint32_t events)
    {
      if (gpio == Touch_INT_PIN)
      {
          CST816S_Get_Point();//更新Touch_CTS816全局变量
          ts_x = Touch_CTS816.x_point;
          ts_y = Touch_CTS816.y_point;
          ts_act = LV_INDEV_STATE_PRESSED;
      }
    }
    
    static void ts_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data)
    {
        data->point.x = ts_x;
        data->point.y = ts_y; 
        data->state = ts_act;
        ts_act = LV_INDEV_STATE_RELEASED;
    }
    
    
  • 六轴传感器输入设备回调函数实现
    • 操作方式:模拟编码器作为输入设备,上下抬触发切换事件,右抬触发确定事件。
    • 实现方式:由于六轴传感器的数据变换较为频繁,为了减少误操作的情况采用定时多次采样的方式来更新设备的切换事件和确定事件。
    static bool repeating_imu_diff_timer_callback(struct repeating_timer *t);//每隔50ms更新一次输入事件
    {
        get_diff_data();//多次采样识别输入事件更新全局变量encoder_diff和encoder_act
        return true;
    }
    
    static void encoder_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data)
    {
      data->enc_diff = encoder_diff;
      data->state    = encoder_act; 
    }
    

LVGL 控件布局

  • LVGL 控件初始化函数
    • 实现功能:主要用于风格化控件和布局控件。
    void Widgets_Init(void);
  • LVGL 控件创建函数
    • 实现功能:创建控件,不同控件需要使用不同的函数接口,可以选择父对象进行创建。
    lv_obj_t *btn = lv_btn_create(lv_scr_act()); //创建一个控件,其中lv_scr_act层为该按键的父对象,可以替换为list、title等可以有子对象的控件
  • LVGL 控件的对齐定位
    • 实现功能:使控件能够基于参考点进行偏移定位。控件对齐偏移的参考点控件的中心。
    • 对齐标准: LVGL 库具备内部对齐和外部对齐两种方式。默认以左上角作为原点,向左为水平方向的正方向,向下为垂直方向的正方向。
    lv_obj_align(btn, LV_ALIGN_CENTER, -50 , 50);//将btn控件定位在中心点向左偏移50个像素,向下偏移50个像素的位置
    RP2040-Touch-LCD-1.28-LVGL-align.png
  • LVGL 控件的风格化
    • 实现功能:使控件能够按照设置的风格进行渲染。利用 lv_obj_add_style 函数可以实现对控件各个部位在不同状态下的渲染。
    static lv_style_t style_base;
    lv_style_init(&style_base);                                                          //初始化风格
    lv_style_set_bg_color(&style_base, lv_palette_main(LV_PALETTE_LIGHT_GREEN));         //设置背景颜色
    lv_style_set_border_color(&style_base, lv_palette_darken(LV_PALETTE_LIGHT_GREEN, 3));//设置边界的颜色
    lv_style_set_border_width(&style_base, 2);                                           //设置编辑的宽度
    lv_style_set_radius(&style_base, 10);                                                //设置倒角的尺寸
    lv_style_set_shadow_width(&style_base, 10);                                          //设置阴影的宽度
    
    lv_obj_add_style(btn,&style_base,0);                                                 //设置btn主题的风格,0可以替换为位置和状态
    RP2040-Touch-LCD-1.28-LVGL-style.png
  • LVGL 控件组添加函数
    • 实现功能:将 LVGL 控件并入控件组,使编码器输入设备可以进入编辑模式对控件的状态进行修改。
    lv_group_add_obj(group, btn);//将btn控件添加到控件组group中

Python

示例效果

本例显示的效果为四个可以通过触摸屏滑动切换的界面。

  • 第一个界面
    • 显示内容:微雪电子的 Logo。
      RP2040-Touch-LCD-1.28-LVGL-title1.png
  • 第二、三个界面
    • 显示内容:六轴传感器的数据。
    • 刷新频率:每 300ms 更新一次。
      RP2040-Touch-LCD-1.28-PY-LVGL-title2.png
  • 第四个界面
    • 显示内容:两个用于查看电池电压、控制LCD背光的界面。
    • 刷新频率:电池电压数据每 200ms 更新一次。
    • 交互方式:界面顶部有两个选项卡:BAT、BL,您可以通过触摸顶部选项切换到对应的界面。BAT 为电池电压监测界面,在这个界面您可以实时监测电池的电压大小。BL 为 LCD 背光控制界面,在这个界面,您可以通过滚动滚轮控制屏幕亮度。
      RP2040-Touch-LCD-1.28-PY-LVGL-title3.png

示例简介

本例用于测试 LVGL 控件交互、样式美化等,使用的版本号为9.1,LVGL V9 的开发文档中没有提供 Python 示例,具体开发请参考LVGL开发文档

实现功能

  1. 本例系统时钟为 230MHz ,设置 SPI 的外围时钟频率与系统时钟一致,同时使用 LVGL 库的双缓冲区机制,在进行一个缓冲区的数据传输时另一个缓冲区进行渲染,保证了动画的流畅度。
  2. 本例使用触摸屏切换界面,实现实时展示六轴传感器、电池电压数据,通过控件实时控制屏幕亮度,展示了 LVGL 控件的简单运用。

编译运行

  • 操作步骤
    • 开始使用前,需要设置集成开发环境 Thonny Python IDE (Windows版本 V3.3.3)
    • 下载例程,按住板上的 boot 按键,通过 Micro USB 线将板子接到电脑的 USB 接口,然后松开按键,电脑会将 pico 识别为一个可移动盘,将 Python 目录下的 .uf2 格式的文件复制到 pico 中
    • 打开 Thonny,将例程中的 exampleslib 目录上传至开发板
      RP2040-Touch-LCD-1.69-LVGL-PY-1.jpg
    • 打开 examples 目录下的文件 LCD_1in28_LVGL_test.py,参考下图步骤运行程序
      RP2040-Touch-LCD-1.69-LVGL-PY-2.jpg
  • 构建固件

源码解析

源码结构

  • LVGL 库的源码请参考 lv_micropython
  • LVGL 库的相关设置在 lv_micropython仓库/lib/lv_bindings/lv_conf.h 文件中,可以设置显示刷新频率、系统占用数据等。
  • LVGL 库的应用代码位于工程文件夹的 examples/LVGL_example.pylib/LVGL.py
    RP2350-Touch-LCD-1.69-LVGL-PY-3.jpg
    RP2350-Touch-LCD-1.69-LVGL-PY-4.jpg

LVGL 初始化

在使用 LVGL 图像库之前,您需要导入 LVGL 库并初始化 LVGL 对象

  • 导入 LVGL 库
    • 代码位置:lib/LVGL.py
    • 实现功能:导入 LVGL 库,并使用别名 "lv" 以方便调用其功能
    import lvgl as lv
    
  • 创建 LVGL 对象
    • 代码位置:examples/LCD_1in69_LVGL_test.py
    • 实现功能:创建 LVGL 对象,并传入 LCD 和 TSC 对象作为参数
    # Init LVGL
    LVGL(LCD=LCD,TSC=TSC)
    
  • LVGL 初始化
    • 代码位置:lib/LVGL.py
    • 代码说明:LVGL 核心初始化位于 LVGL 类的初始化函数,在创建 LVGL 对象时自动调用
    if not lv.is_initialized(): lv.init()
    

LVGL 运行

LVGL 库定时调用心跳接口函数 lv.tick_inc 来通知 LVGL 过去的时间,以便 LVGL 能够更新其内部的时间状态,处理与时间相关的任务,例如动画、定时器等。此外还需要调用 lv.task_handler 函数,以便 LVGL 及时处理事件和任务,保证用户界面的响应和刷新。

  • LVGL 运行接口
    • 代码位置:lib/LVGL.py
    • 实现方式:创建一个 event_loop 对象。在该类的初始化函数中,我们创建一个定时器。该定时器会在设定的时间间隔内自动调用心跳接口函数和事件处理函数。调用的时间间隔可以通过传递 freq 参数调节,例如 lv_utils.event_loop(freq=200),默认为 40 ms。
    # Create event loop if not yet present
    if not lv_utils.event_loop.is_running(): self.event_loop=lv_utils.event_loop()
    
  • LVGL 心跳接口
    • 代码位置:lib/lv_utils.py
    • 实现方式:需要确保 lv.task_handler 的优先级低于 lv.tick_inc 的优先级,所以在本例中,lv.tick_inc 在定时器回调函数中调用。
    #每 5ms 调用一次定时器回调函数
    self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb) // 本例中 self.delay = 5
    
    def timer_cb(self, t):
        lv.tick_inc(self.delay)
        if self.scheduled < self.max_scheduled:
            try:
                micropython.schedule(self.task_handler_ref, 0)
                self.scheduled += 1 # 正在处理的任务数增加
            except:
                pass
    
  • LVGL 任务处理器
    • 代码位置:lib/lv_utils.py
    • 实现方式:要处理 LVGL 的任务,需要定期调用 lv.task_handler,本例在定时器回调函数中调用 lv.tick_inc 之后再进行调用。
    def task_handler(self, _):
        try:
            if lv._nesting.value == 0:
                lv.task_handler()
                if self.refresh_cb: self.refresh_cb()
            self.scheduled -= 1 # 正在处理的任务数减少
        except Exception as e:
            if self.exception_sink:
                self.exception_sink(e)
    

LVGL 显示

要实现 LVGL 显示,必须初始化一个显示驱动,并设置显示驱动的各个属性,例如,颜色格式、绘制缓冲区、渲染模式以及显示回调函数。在每个 LV_DEF_REFR_PERIOD(在 lv_conf.h 中设置),LVGL 会检测 UI 上是否发生了一些需要重绘的事情。例如,按下按钮、更改图表、发生动画等。需要重新绘制时,LVGL 调用显示回调函数完成图像在刷新区的绘制。

  • LVGL 显示刷新率设置
    • 代码位置:lv_micropython仓库/lib/lv_bindings/lv_conf.h
    • 设置方式:在 lv_conf.h 中可以修改 LV_DEF_REFR_PERIOD 来改变屏幕的刷新时间。
    #define LV_DEF_REFR_PERIOD 10 // 单位:ms,这里为10ms
    
  • LVGL 显示相关变量定义
    • 代码位置:lib/LVGL.py
    • 实现功能:buf0 和 buf1 大小设置为屏幕显示面积的 33% 是为了实现 LVGL 双缓冲机制,能够在降低大面积刷屏锯齿的同时有效提高屏幕刷新率;在使用单缓冲区时最好设置为屏幕显示面积的 10%,可以有效降低系统占用但在大面积图像刷新时会出现较明显的锯齿。
    # Init LVGL display
    buf1 = lv.draw_buf_create(self.LCD.width, self.LCD.height // 3 , color_format, 0)
    buf2 = lv.draw_buf_create(self.LCD.width, self.LCD.height // 3, color_format, 0)   
    
  • LVGL 显示设备注册
    • 代码位置:lib/LVGL.py
    • 实现功能:根据设计需求完善 LVGL 库核心结构体变量,初始化显示驱动 disp_drv,并设置绘制缓冲区,该缓冲区是 LVGL 用来渲染屏幕内容的简单数组。一旦渲染准备就绪,绘制缓冲区的内容将使用显示驱动程序中设置的 disp_drv_flush_cb 函数发送到显示器。
    self.disp_drv = lv.display_create(self.LCD.width, self.LCD.height) # 创建显示驱动器对象,并设置宽度和高度
    self.disp_drv.set_color_format(color_format) # 设置颜色格式为 RGB565
    self.disp_drv.set_draw_buffers(buf1, buf2)   # 设置绘制缓冲区
    self.disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) # 设置渲染模式为部分刷新模式
    self.disp_drv.set_flush_cb(self.disp_drv_flush_cb) # 设置显示回调函数
    
  • LVGL 显示回调函数
    • 代码位置:lib/LVGL.py
    • 实现功能:主要完成图像在刷新区的绘制。
    def disp_drv_flush_cb(self,disp_drv,area,color_p)
    参数:
        disp_drv : 显示驱动结构体指针,包含了与显示相关的信息和函数指针。该参数常用于通知刷新完成
        area     : 区域结构体指针,包含待刷新区域的位置信息。在本例中,用于创建 TFT 显示的窗口
        color_p  : 颜色结构体指针,表示要在刷新区域内显示的颜色数据。在本例中,作为DMA输入读取地址将数据传输到SPI总线,完成图像的绘制
    
  • LVGL 显示回调函数实现
    • 代码位置:lib/LVGL.py
    • 实现方式:在本例中为了最大化降低处理器的利用率,使用 DMA 来进行颜色数据的传输,设置 color_p 作为读地址,SPI 总线的输出数据寄存器为写地址。代码较长仅展示部分,完整代码请下载示例查看。
    def disp_drv_flush_cb(self,disp_drv,area,color_p):
        self.rp2_wait_dma() # 等待 DMA 空闲
        
        ......
        self.rgb565_swap_func(data_view, size)  # 转换颜色格式
        
        self.LCD.setWindows(area.x1, area.y1, area.x2+1, area.y2+1) # 设置 LVGL 界面显示位置
        
        ...... # DMA 配置
    
        self.rp2_dma.enable() # 使能 DMA
    
        self.rp2_wait_dma() # 等待 DMA 空闲
               
        self.disp_drv.flush_ready() # 通知 LVGL 数据传输完成
    }
    

LVGL 输入

在 LVGL 中,允许用户注册输入设备,如触摸板、鼠标、键盘或编码器等设备。用户可以通过这些输入设备控制用户界面,实现更好的交互。

  • LVGL 调用输入设备回调函数的频率设置
    • 代码位置:lv_micropython仓库/lib/lv_bindings/lv_conf.h
    • 设置方式:与屏幕的刷新时间使用的是相同的宏,每 10ms 调用一次输入设备回调函数来更新输入设备所触发的事件,可通过修改 LV_DEF_REFR_PERIOD 定义进行设置。
    #define LV_DEF_REFR_PERIOD 10 // 单位:ms,这里为10ms
    
  • LVGL 输入设备注册
    • 代码位置:examples/LVGL_example.py
    • 设置方式:注册触摸屏设备 indev_drv 并初始化
    # Init touch screen as input device
    self.indev_drv = lv.indev_create()                 # 创建对象
    self.indev_drv.set_type(lv.INDEV_TYPE.POINTER)     # 注册触摸屏设备
    self.indev_drv.set_read_cb(self.indev_drv_read_cb) # 设置回调函数
    
  • LVGL 的输入设备回调函数
    • 代码位置:lib/LVGL.py
    • 实现功能:主要用于更新输入事件。
    def indev_drv_read_cb(indev_drv, data)
    参数:
      indev_drv : LVGL 中的输入设备驱动结构体指针。在本例中,该结构体为触摸屏输入设备驱动
      data      : LVGL 中的输入设备数据结构体指针。在本例中,该结构体用于存储输入设备的状态和数据,包括当前的触摸状态(按下或释放)以及触摸点的坐标
    
  • 触摸屏输入设备的回调函数实现
    • 代码位置:lib/CST816T.pylib/LVGL.py
    • 实现方式:主要是通过触摸中断更新触摸屏的触摸状态和触摸点坐标。
    def Int_Callback(self,pin):
        if self.Mode == 0 :
            self.Gestures = self._read_byte(0x01)
    
        elif self.Mode == 1 :           
            self.Flag = 1
            self.get_point()
        
        elif self.Mode == 2 :           
            self.Flag = 1
            self.get_point()
            self.Gestures = self._read_byte(0x01)
    
    def indev_drv_read_cb( self, indev_drv, data):
        self.rp2_wait_dma()  
    
        data.point.x = self.TSC.X_point
        data.point.y = self.TSC.Y_point
        
        data.state = 1 if self.TSC.Flag == 1 else 0 
        self.TSC.Flag = 0
    

LVGL 控件布局

在 LVGL 中,我们能够建立各种不同的用户界面。界面的基本组成部分是对象,也称为控件(Widgets),比如按钮(Button)、标签(Label)、图像(Image)、列表(List)、图表或文本区域。在一个界面中,可以同时创建多个控件,并且我们可以设置它们的位置、尺寸、父对象、样式以及事件处理程序等基本属性。

  • 创建 LVGL 界面对象
    • 代码位置:examples/LVGL_example.py
    • 实现功能:创建 LVGL 界面对象,并传入 LCD 、 TSC 、 IMU 对象作为参数
    # Init WIDGETS
    WIDGETS(LCD=LCD,TSC=TSC,IMU=IMU)
    
  • LVGL 创建图块
    • 代码位置:examples/LVGL_example.py
    • 实现功能:Tile 视图是一个容器对象,其元素(称为图块)可以按网格形式排列。用户可以通过滑动来在各个图块之间导航。使用 Tile 视图对象调用 add_tile(tileview, row_id, col_id, dir) 在第 row_id 行和第 col_id 列上创建一个新图块。dir可以是 lv.DIR.LEFT/RIGHT/TOP/BOTTOM/HOR/VER/ALL 或值,以便通过滑动移动到给定方向的相邻图块。
    //在 (0,0) 创建一个图块,支持向下滑动到 (0,1)
    self.tv = lv.tileview(self.scr)
    self.tile1 = self.tv.add_tile(0, 0, lv.DIR.BOTTOM)
    
  • LVGL 创建控件
    • 代码位置:examples/LVGL_example.py
    • 实现功能:创建控件,不同控件需要使用不同的函数接口,可以选择父对象进行创建。
    //创建一个 table 控件,其中 tile2 为该控件的父对象,可以替换为list、title等可以有子对象的控件
    self.table_imu_data = lv.table(self.tile2)
    
  • LVGL 控件的对齐定位
    • 代码位置:examples/LVGL_example.py
    • 实现功能:使控件能够基于参考点进行偏移定位。控件对齐偏移的参考点控件的中心。
    • 对齐标准: LVGL 库具备内部对齐和外部对齐两种方式。默认以左上角作为原点,向左为水平方向的正方向,向下为垂直方向的正方向。
    //将控件定位在中心点右偏移15个像素
    self.table_imu_data.align(lv.ALIGN.CENTER, 15 ,0)
    
    RP2040-Touch-LCD-1.28-LVGL-align.png
  • LVGL 控件切换字体大小
    • 代码位置:lv_micropython仓库/lib/lv_bindings/lv_conf.hexamples/LVGL_example.py
    • 实现功能:在实际运用时一个界面可能需要运用多种字体大小,可以在 lv_conf.h 中使能多种字体大小,且可以设置默认字体大小。设置字体大小时需要对控件风格化,使控件能够按照设置的风格进行渲染。利用 add_style 函数可以实现对控件各个部位在不同状态下的渲染。
    #define LV_FONT_MONTSERRAT_16 1                        // 使能16号字体
    #define LV_FONT_MONTSERRAT_18 1                        // 使能18号字体
    #define LV_FONT_DEFAULT &lv_font_montserrat_18         // 设置默认字体大小为18号
    
    table_imu_data= lv.style_t()                           // 创建对象
    table_imu_data.init()                                  // 初始化
    table_imu_data.set_text_font(lv.font_montserrat_16)    // 设置字体大小为16
    
    self.table_imu_data.add_style(style_imu_table, 0)      // 设置风格
    
  • LVGL 控件事件处理
    • 代码位置:examples/LVGL_example.py
    • 实现功能:在 LVGL 中,可以给控件添加事件处理回调函数,使控件发生被点击、滚动、重绘等事件时,触发事件从而进入事件处理回调函数。在程序中调用 obj.add_event_cb(event_cb, filter, None) 函数,为控件 obj 添加事件 filter 的处理函数 event_cb,当控件 obj 触发 filter 事件时,系统会自动调用 event_cb 函数。最后一个参数是指向事件中可用的任何自定义数据的指针。
    //为控件 sw 添加事件 LV_EVENT_VALUE_CHANGED 的处理函数 sw_event_cb
    self.sw.add_event_cb(self.sw_event_cb, lv.EVENT.VALUE_CHANGED, None)