前言

  在日常的开发中,涉及到用户显示界面控制的时候,如何快速、优美的设计一款用户UI,是攻城狮应该具备的能力(择偶优先权),如何把嵌入式UI设计像高级语言图形化设计一样简便、快捷,今天重磅介绍一款GUI设计开源库,LittleVgl,俗称LVGL,来吧,直接上干货,淦!

硬件环境

  • STM32F407ZGT6(或者其他板子)
  • 2.4寸TFT电阻式触摸屏

软件环境

  • keil5
  • lvgl软件源码
  • 正点原子触摸屏例程
  • 注:以上只是小飞哥使用的环境,其他也可以的,重要的是这种方法~

LVGL开源库是什么?

  欢迎大家查看这篇文章,对LVGL有详细的介绍

  移植过程是相对不复杂的,主要是添加文件到STM32工程,将LVGL源码添加进来,源码部分就没有什么多说的啦,所有的基本绘图函数都在其中,用户UI代码主要是用户自己设计的显示代码,可以导入一些官方例程之类的,也不多做介绍

stm32移植HarmonyOS_sed

LVGL与嵌入式平台显示接口

  我们本次主要关注lv_port_disp.c和lv_port_indev.c,学习如何在LVGL开源库和嵌入式平台之间搭建一条桥梁,让两者联系起来,在lv_port_disp.c中调用打点函数或者画图形函数即可,小飞哥调用的是打点函数,显示部分只需要调用这一点即可。

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)

stm32移植HarmonyOS_sed_02

//快速画点
//x,y:坐标
//color:颜色
void LCD_Fast_DrawPoint(u16 x, u16 y, u32 color)
{
 if (lcddev.id == 0X9341 || lcddev.id == 0X5310)
 {
  LCD_WR_REG(lcddev.setxcmd);
  LCD_WR_DATA(x >> 8);
  LCD_WR_DATA(x & 0XFF);
  LCD_WR_REG(lcddev.setycmd);
  LCD_WR_DATA(y >> 8);
  LCD_WR_DATA(y & 0XFF);
 }
 else if (lcddev.id == 0X5510)
 {
  LCD_WR_REG(lcddev.setxcmd);
  LCD_WR_DATA(x >> 8);
  LCD_WR_REG(lcddev.setxcmd + 1);
  LCD_WR_DATA(x & 0XFF);
  LCD_WR_REG(lcddev.setycmd);
  LCD_WR_DATA(y >> 8);
  LCD_WR_REG(lcddev.setycmd + 1);
  LCD_WR_DATA(y & 0XFF);
 }
 else if (lcddev.id == 0X1963)
 {
  if (lcddev.dir == 0)
   x = lcddev.width - 1 - x;
  LCD_WR_REG(lcddev.setxcmd);
  LCD_WR_DATA(x >> 8);
  LCD_WR_DATA(x & 0XFF);
  LCD_WR_DATA(x >> 8);
  LCD_WR_DATA(x & 0XFF);
  LCD_WR_REG(lcddev.setycmd);
  LCD_WR_DATA(y >> 8);
  LCD_WR_DATA(y & 0XFF);
  LCD_WR_DATA(y >> 8);
  LCD_WR_DATA(y & 0XFF);
 }
 LCD->LCD_REG = lcddev.wramcmd;
 LCD->LCD_RAM = color;
}
void LCD_DrawFRONT_COLOR(u16 x, u16 y, u16 color)
{
 LCD_Fast_DrawPoint(x,y,color);
}
/* 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)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    int32_t x;
    int32_t y;
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            /* Put a pixel to the display. For example: */
            LCD_DrawFRONT_COLOR(x, y, color_p->full);
            color_p++;
        }
    }

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

LVGL与嵌入式平台控制接口

  然后是触摸屏控制部分,本文只介绍如何实现触摸屏结合LVGL控制,完成代替物理按键控制,关于触摸屏的驱动,可以查看原子哥的详细介绍,就不做赘述了。

  lv_port_indev.c源文件中,我们可以看到定义了很多的接口,鼠标的,按键的,触摸的,等等,此次我们只用到触摸部分,我只保留了触摸的代码,其他的都删除了(主要是懒,删除最快捷),一下是修改后的代码,触摸部分的代码,只需要把原子哥的电阻屏驱动代码copy进来即可。

  其实,lvgl的框架是不错的,这里为了省事,是直接改掉了tuoch_read函数,其他的获取坐标、按下确认等都没有使用,建议小伙伴们可以使用下完整的框架试试。

/* Will be called by the library to read the touchpad */
static bool touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    tp_dev.scan(0);

    if (tp_dev.sta & TP_PRES_DOWN) //
    {
        printf("x坐标:%d,Y坐标:%d\r\n", tp_dev.x[0], tp_dev.y[0]);
        last_x = tp_dev.x[0];
        last_y = tp_dev.y[0];

        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_PR;
    }
    else
    {
        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_REL;
    }

    //    /*Save the pressed coordinates and the state*/
    //    if(touchpad_is_pressed()) {
    //        touchpad_get_xy(&last_x, &last_y);
    //        data->state = LV_INDEV_STATE_PR;
    //    } else {
    //        data->state = LV_INDEV_STATE_REL;
    //    }

    //    /*Set the last pressed coordinates*/
    //    data->point.x = last_x;
    //    data->point.y = last_y;

    /*Return `false` because we are not buffering and no more data to read*/
    return false;
}
/**
 * @file lv_port_indev_templ.c
 *
 */

/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev.h"
#include "lv_hal_indev.h"
#include "touch.h"

/*********************
 *      DEFINES
 *********************/
extern _m_tp_dev tp_dev; //锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷touch.c锟斤拷锟芥定锟斤拷
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

static void touchpad_init(void);
static bool touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y);

/**********************
 *  STATIC VARIABLES
 **********************/
lv_indev_t *indev_touchpad;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_indev_init(void)
{
    /* Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */

    lv_indev_drv_t indev_drv;

    /*------------------
     * Touchpad
     * -----------------*/

    /*Initialize your touchpad if you have*/
    touchpad_init();

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    indev_touchpad = lv_indev_drv_register(&indev_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*------------------
 * Touchpad
 * -----------------*/

/*Initialize your touchpad*/
static void touchpad_init(void)
{
    /*Your code comes here*/
}

/* Will be called by the library to read the touchpad */
static bool touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    tp_dev.scan(0);

    if (tp_dev.sta & TP_PRES_DOWN) //
    {
        printf("x坐标:%d,Y坐标:%d\r\n", tp_dev.x[0], tp_dev.y[0]);
        last_x = tp_dev.x[0];
        last_y = tp_dev.y[0];

        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_PR;
    }
    else
    {
        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_REL;
    }

    //    /*Save the pressed coordinates and the state*/
    //    if(touchpad_is_pressed()) {
    //        touchpad_get_xy(&last_x, &last_y);
    //        data->state = LV_INDEV_STATE_PR;
    //    } else {
    //        data->state = LV_INDEV_STATE_REL;
    //    }

    //    /*Set the last pressed coordinates*/
    //    data->point.x = last_x;
    //    data->point.y = last_y;

    /*Return `false` because we are not buffering and no more data to read*/
    return false;
}

/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/

    return false;
}

/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y)
{
    /*Your code comes here*/

    (*x) = 0;
    (*y) = 0;
}

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif

LVGL配置文件

   开发过程中,屏幕尺寸一般是根据外壳结构,产品形态等决定的,但是一般不会更换驱动芯片,所以对开发者来讲,底层驱动是不变的,但是随着屏幕尺寸的改变,苦逼的是还要一一修改尺寸,我们可以通过配置文件,对不同的屏幕做适配,可以参考LVGL官方配置文件,这里可以更改屏幕尺寸,配置文件里面还有很多配置项,很像我们做上位机界面时候的属性,通过修改不同的值,得到我们想要的效果,小伙伴们可以探索探索哈,实践出真知~

stm32移植HarmonyOS_sed_03

 

 

stm32移植HarmonyOS_ide_04

实例演示

   群里有小伙伴最近有些疑惑,想使用slider做控制,不知道如何是用触摸屏,对如何如何获取触摸值,通过slider传输值目标器件,小飞哥一听,必须安排,粉丝的事无大小,干!!!

  我们直接使用官方库,slider例程,例程是有两种slider,我使用的是第二种,添加到我们的工程中即可,需要再配置文件中开启方框中的选项。

stm32移植HarmonyOS_ide_05

 

stm32移植HarmonyOS_sed_06

void lv_ex_slider_2(void)
{
    /* Create a slider in the center of the display */
    lv_obj_t * slider = lv_slider_create(lv_scr_act(), NULL);
    lv_obj_set_width(slider, LV_DPI * 2);
    lv_obj_align(slider, NULL, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_event_cb(slider, slider_event_cb);
    lv_slider_set_range(slider, 0, 100);     //修改此处,可以修改slider数值范围,目前是0-100
    
    /* Create a label below the slider */
    slider_label = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(slider_label, "0");
    lv_obj_set_auto_realign(slider_label, true);
    lv_obj_align(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
    
    /* Create an informative label */
    lv_obj_t * info = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(info, "Welcome to join in Embeded-Party\n");
    lv_obj_align(info, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);
}

  上面顺利的话,此时我们的触摸屏驱动是OK的,无论按在哪一处,都是有坐标值的,来测试下,触摸任意位置时候,获取到的坐标值,可以发现,我们无论按在哪一位置,总是有坐标的,但是,显然是有很多我们不需要的值,那么如何处理呢?

stm32移植HarmonyOS_stm32移植HarmonyOS_07

stm32移植HarmonyOS_触摸屏_08

 

  对于slider来说,我们只需要进度条区域内的值作为我们输出的对象,这里就可以体会到LVGL的乐趣了,如何把坐标值转换为slider对应的数值,我们不需要关心如何实现的,直接按就完事了,黑盒子会帮我们实现,我们只需要在slider回调函数里面处理即可:

static void slider_event_cb(lv_obj_t * slider, lv_event_t event)
{ 
    int16_t barvalue = 0;

    if(event == LV_EVENT_VALUE_CHANGED) {
        static char buf[4]; /* max 3 bytes for number plus 1 null terminating byte */
       
       barvalue =  lv_slider_get_value(slider);
       printf("barvlue is %d\r\n",barvalue);
        snprintf(buf, 4, "%u", lv_slider_get_value(slider));
        lv_label_set_text(slider_label, buf);
    }
}

  main函数部分

int main(void)
{
 HAL_Init();      //初始化HAL库
 Stm32_Clock_Init(336, 8, 2, 7); //设置时钟,168Mhz
 delay_init(168);    //初始化延时函数
 uart_init(115200);    //初始化USART
 LED_Init();      //初始化LED
 KEY_Init();      //初始化KEY
 LCD_Init();      //初始化LCD
 tp_dev.init();     //触摸屏初始化

 lv_init();    // 初始化lvgl
 lv_port_disp_init(); // 显示初始化
 lv_port_indev_init();
 //lv_demo_widgets();  // 例子演示
 lv_ex_slider_2();
  //lv_ex_calendar_1();
 //lv_demo_keypad_encoder();
// lv_demo_printer();
 KEY_Init();        //初始化按键
 TIM3_Init(1000 - 1, 84 - 1); //定时器3初始化,定时器时钟为84M,分频系数为8400-1,
           //所以定时器3的频率为84M/8400=10K,自动重装载为100-1,那么定时器周期就是10ms

 while (1)
 {
  lv_task_handler(); // 循环调用处理lvgl tasks
 }
}

  对比上面,小伙伴们是不是发现什么不同了呢,当我们触摸位置在slider有效区域内的时候,坐标值,坐标值是 已经转换为0-100内的数值了的,是不是很方便呢。

stm32移植HarmonyOS_sed_09


      本次的介绍就到这里啦,后面有更精彩的内容,拥有自己的GUI,欢迎大家持续关注嵌入式实验基地,来这里还可以学习HAL库+cubemx的更多精彩内容哦!