模板: RP2040-1.28-LVGL

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

LVGL 示例程序

示例效果

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

  • 第一个界面
    • 显示内容:微雪电子的 Logo。
      RP2040-Touch-LCD-1.28-LVGL-title1.png
  • 第二个界面
    • 显示内容:六轴传感器的数据。
    • 刷新频率:每 500ms 更新一次。
    • 切换方式:利用六轴传感器右抬触发输入设备确定事件选中界面,再向上抬触发切换事件实现界面切换。
      RP2040-Touch-LCD-1.28-LVGL-title2.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_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_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 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 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中