LittleVGL (LVGL)乾貨入門教程二之LVGL的輸入裝置(indev)API對接。

Trisuborn發表於2020-12-03

LittleVGL (LVGL)乾貨入門教程二之LVGL的輸入裝置(indev)API對接


前言:

閱讀前,請確保你擁有以下條件:

  • 你已經完成“顯示API”的移植。
  • 你已經實現了一個螢幕的觸控驅動 (如果你使用外部物理按鍵進行操作,那麼請確保你實現了讀按鍵狀態的驅動)。

LVGL有三大種需要對接的API

  1. 顯示API(教程一已實現, 連結:LittleVGL (LVGL)入門教程一之移植到stm32晶片
  2. 輸入裝置API(比如觸控式螢幕、按鍵 等, 此篇教程實現)
  3. 檔案系統API(如FatFs等)

這篇文章講“輸入裝置API”的移植,預設你已經移植好了“顯示API”。

重要) 編譯LVGL至少需要c99標準



一、indev輸入裝置的種類介紹

(一)輸入裝置的種類

LVGL有5種輸入裝置介面:

  1. Touchpad      (觸控板,例如電容屏、電阻屏等)
  2. Mouse             (滑鼠)
  3. Keypad            (鍵盤)
  4. Encoder           (編碼器)
  5. Button              (外部按鍵)

我們常用的是“Touchpad、Keypad”,這篇文章基於這兩種,其他類似,可以根據目錄第三章看你想看的介面API對接。


二、lv_port_indev更改

(一)使能indev的port檔案
  1. 方法很簡單,在lv_port_indev檔案中把“#if 0”改為“#if 1”,c檔案和h檔案都要改。
  2. 在lv_port_indev.h中新增宣告void lv_port_indev_init(void);
(二)優化indev的port檔案(重要

提醒,這裡的修改很冗長和枯燥,主要原因,原port檔案非常混亂,各種indev的API混在一起,如果你不實現就會報錯或警告,而實際上我們的專案往往只會用上一兩個indev,比如我同時使用鍵盤和滑鼠或同時使用touchpad和button。為了程式碼的精簡和易於管理,這個部分以新增預處理語句為主,以後你要用什麼型別的indev,修改預處理語句和巨集定義即可。

/************************************************
 * 一、我們在檔案"lv_port_indev.c"頂部定義巨集定義
 ************************************************/
#define LV_USE_INDEV_TOUCHPAD 	0x0u
#define LV_USE_INDEV_MOUSE	 	0x1u
#define LV_USE_INDEV_KEYPAD 	0x2u
#define LV_USE_INDEV_ENCODER 	0x4u
#define LV_USE_INDEV_BUTTON 	0x8u
#define LV_USE_INDEV  	LV_USE_INDEV_TOUCHPAD	| \
						LV_USE_INDEV_KEYPAD			// 使用Touchpad 和 keypad

/************************************************
 * 二、根據註釋 我們找到 STATIC PROTOTYPES,按型別
 * 新增預處理語句
 ************************************************/
#if ( LV_USE_INDEV & LV_USE_INDEV_TOUCHPAD ) == LV_USE_INDEV_TOUCHPAD
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);
#endif

#if ( LV_USE_INDEV & LV_USE_INDEV_MOUSE ) == LV_USE_INDEV_MOUSE
static void mouse_init(void);
static bool mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool mouse_is_pressed(void);
static void mouse_get_xy(lv_coord_t * x, lv_coord_t * y);
#endif

#if ( LV_USE_INDEV & LV_USE_INDEV_KEYPAD ) == LV_USE_INDEV_KEYPAD
static void keypad_init(void);
static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static uint32_t keypad_get_key(void);
#endif

#if ( LV_USE_INDEV & LV_USE_INDEV_ENCODER ) == LV_USE_INDEV_ENCODER
static void encoder_init(void);
static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static void encoder_handler(void);
#endif

#if ( LV_USE_INDEV & LV_USE_INDEV_BUTTON ) == LV_USE_INDEV_BUTTON
static void button_init(void);
static bool button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static int8_t button_get_pressed_id(void);
static bool button_is_pressed(uint8_t id);
#endif

/************************************************
 * 我們繼續往下看是indev的初始化函式實現,官方註釋
 * 很貼心,大家看著新增預處理語句就行
 ************************************************/
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;

#if ( LV_USE_INDEV & LV_USE_INDEV_TOUCHPAD ) == LV_USE_INDEV_TOUCHPAD
    /*------------------
     * 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);
#endif
#if ( LV_USE_INDEV & LV_USE_INDEV_MOUSE ) == LV_USE_INDEV_MOUSE
    /*------------------
     * Mouse
     * -----------------*/

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

    /*Register a mouse input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = mouse_read;
    indev_mouse = lv_indev_drv_register(&indev_drv);

    /*Set cursor. For simplicity set a HOME symbol now.*/
    lv_obj_t * mouse_cursor = lv_img_create(lv_disp_get_scr_act(NULL), NULL);
    lv_img_set_src(mouse_cursor, LV_SYMBOL_HOME);
    lv_indev_set_cursor(indev_mouse, mouse_cursor);
#endif
#if ( LV_USE_INDEV & LV_USE_INDEV_KEYPAD ) == LV_USE_INDEV_KEYPAD
    /*------------------
     * Keypad
     * -----------------*/

    /*Initialize your keypad or keyboard if you have*/
    keypad_init();

    /*Register a keypad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;
    indev_drv.read_cb = keypad_read;
    indev_keypad = lv_indev_drv_register(&indev_drv);

    /* Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     * add objects to the group with `lv_group_add_obj(group, obj)`
     * and assign this input device to group to navigate in it:
     * `lv_indev_set_group(indev_keypad, group);` */
#endif
#if ( LV_USE_INDEV & LV_USE_INDEV_ENCODER ) == LV_USE_INDEV_ENCODER
    /*------------------
     * Encoder
     * -----------------*/

    /*Initialize your encoder if you have*/
    encoder_init();

    /*Register a encoder input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb = encoder_read;
    indev_encoder = lv_indev_drv_register(&indev_drv);

    /* Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     * add objects to the group with `lv_group_add_obj(group, obj)`
     * and assign this input device to group to navigate in it:
     * `lv_indev_set_group(indev_encoder, group);` */
#endif
#if ( LV_USE_INDEV & LV_USE_INDEV_BUTTON ) == LV_USE_INDEV_BUTTON
    /*------------------
     * Button
     * -----------------*/

    /*Initialize your button if you have*/
    button_init();

    /*Register a button input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_BUTTON;
    indev_drv.read_cb = button_read;
    indev_button = lv_indev_drv_register(&indev_drv);

    /*Assign buttons to points on the screen*/
    static const lv_point_t btn_points[2] = {
            {10, 10},   /*Button 0 -> x:10; y:10*/
            {40, 100},  /*Button 1 -> x:40; y:100*/
    };
    lv_indev_set_button_points(indev_button, btn_points);
#endif

}

/************************************************
 * 三、根據註釋找到 STATIC FUNCTIONS
 * 下面都是很長的函式實現,我不一一列出來了,
 * 按照不同的indev根據上面的模板進行預處理語句新增就行
 * 值得注意的是,我們可能會使用多種indev,所以不宜
 * 使用“#if”和“#elif”“#endif”,宜使用“#if”和“#endif”
 ************************************************/


三、移植輸入裝置API

相信大家在上文進行預處理語句的新增的過程中應該有注意各個函式的名字,很直白,函式什麼功能基本都能從名字看出來,那我們分別以“touchpad”和“keypad”的移植為例,進行API對接。

(一)以touchpad為例:

開啟檔案"lv_port_indev.c",找到並參考如下改法:

/*------------------
 * Touchpad
 * -----------------*/
 * /*Initialize your touchpad*/
static void touchpad_init(void)
{
    /*Your code comes here*/
    /* 你的touch初始化 */
    touch->init();
}

/* 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;

    /*Save the pressed coordinates and the state*/
    /* 這裡呼叫了 touchpad_is_pressed,這個實現在下面 */
    /* 從名字可以知道,這個函式對“是否有點被觸控”進行判斷 */
    if(touchpad_is_pressed()) {
    	/* touchpad_get_xy 在下面實現 */
        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*/
	/* 在這裡我們對觸控式螢幕是否被觸控進行判斷 */
	/* 例如觸控式螢幕一般有INT引腳,就是中斷引腳 */
	/* 我們根據中斷引腳是否有訊號判斷是否被觸控(不過STM32貌似沒有電平觸發中斷,只有邊沿觸發) */
	/* 邊沿觸發只能觸發一次,容易判斷錯誤,所以我們一般採用輪詢讀點 */
	/* 我們分別以電容和電阻觸控式螢幕為例 */

	bool res = false;

	/* 電阻屏 */
	// res = res_is_touched();
	// return res;
	
	/* 電容屏 */
	/* 電容屏一般可以讀status暫存器是否有值來判斷是否有點 */
	uint8_t status = cap_is_touched();
	if ( status )
		return true;
	
    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) = my_read_x();
    (*y) = my_read_y();
}

那麼到這裡,你的觸控式螢幕API就移植結束了,在外面呼叫函式lv_port_indev_init();進行初始化就行。

(二)以keypad為例:
1.移植keypad API

開啟檔案"lv_port_indev.c",找到並參考如下改法:

/*------------------
 * Keypad
 * -----------------*/

/* Initialize your keypad */
static void keypad_init(void)
{
    /*Your code comes here*/
    /* 你的初始化程式碼 */
    my_key_init();
}

/* Will be called by the library to read the mouse */
static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static uint32_t last_key = 0;

    /*Get the current x and y coordinates*/
    /* 這裡官方預設你有滑鼠裝置,但實際上可能沒有,我們註釋掉 */
    // mouse_get_xy(&data->point.x, &data->point.y);

    /*Get whether the a key is pressed and save the pressed key*/
    /* 這裡告訴你,你讀到的點要根據你的需求進行轉換 */
    /* 當我通過keypad_get_key()函式獲取act_key值後 */
    /* 轉換成LVGL的操作符,如LV_KEY_NEXT等 */
    uint32_t act_key = keypad_get_key();
    if(act_key != 0) {
        data->state = LV_INDEV_STATE_PR;
        /*Translate the keys to LVGL control characters according to your key definitions*/
        switch(act_key) {
        case 1:
            act_key = LV_KEY_NEXT;
            break;
        case 2:
            act_key = LV_KEY_PREV;
            break;
        case 3:
            act_key = LV_KEY_LEFT;
            break;
        case 4:
            act_key = LV_KEY_RIGHT;
            break;
        case 5:
            act_key = LV_KEY_ENTER;
            break;
        /* 這裡可以新增更多操作符 */
        }
        last_key = act_key;
    } else {
        data->state = LV_INDEV_STATE_REL;
    }

    data->key = last_key;

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

/*Get the currently being pressed key.  0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
    /*Your code comes here*/
	/* LVGL有較多操作符,如果你的按鍵不夠用,可以採用組合鍵的方案 */
	/* 當然,使用外部按鍵的話你應該考慮“按鍵抖動”的問題,使用軟消抖或硬消抖 */
	/* 假設我有3個按鍵:“UP”、“SURE”、“DOWN” */

	switch ( my_read_key() ) {
	case UP:
		return 1;
	case SURE:
		return 5;
	case DOWN:
		return 2;
	/* 組合鍵 */
	case (UP | SURE):
		return 3;
	case (DOWN | SURE):
		return 4;
	}

    return 0;
}

LVGL 操作符參考:
在這裡插入圖片描述

keypad比較特殊,光移植完還不行,要使用的話,需要indev group。

2.使用keypad的方法

根據LVGL註釋的原話:

	/* 
	 * Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     * add objects to the group with `lv_group_add_obj(group, obj)`
     * and assign this input device to group to navigate in it:
     * `lv_indev_set_group(indev_keypad, group);` 
     * */

我們可以知道,我們要使用下面程式碼建立一個group,以後我們要把UI obj放入group,才可進行操作(觸控式螢幕不用)。

/* 建立group */
lv_group_t * group = lv_group_create();
// 這裡的變數“indev_keypad”在檔案頂部“STATIC VARIABLES”區有定義
// 這句話要加入void lv_port_indev_init(void);函式對應的keypad的程式碼裡
lv_indev_set_group(indev_keypad, group);

/* 例如我們建立了一個button物件 */
lv_obj_t * btn = lv_btn_create( lv_scr_act(), NULL );
/* 我們要通過keypad控制btn,那麼我們需要新增進group */
lv_group_add_obj( group, btn );

那麼到這裡,你的keypad API就移植結束了,在外面呼叫函式lv_port_indev_init();進行初始化就行。


四、使用示例

#include <lvgl.h>

#define LVGL_TICK 	5

/* 引入之前建立的group */
extern lv_group_t * group;	// 如果用觸控式螢幕就不用

/************************************************
 * @brief 事件控制程式碼
 * 
 * @param obj       
 * @param event 
 ************************************************/
static void event_handler(lv_obj_t * obj, lv_event_t event)
{
    switch (event) {
    case LV_EVENT_CLICKED:
        printf( "btn Clicked\n" );
        // lv_obj_del( obj ); // 點選就消失
        break;
    default:
        break;
    }
}

static void my_lvgl_test(void)
{
	lv_obj_t * btn = lv_btn_create( lv_scr_act(), NULL );
	lv_obj_set_size( btn, 80, 50 );
	lv_obj_set_event_cb( btn, event_handler );	// 設定事件控制程式碼
	lv_group_add_obj( group, btn );	// 新增進group 如果用觸控式螢幕就不用
	lv_obj_align( btn, NULL, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_recolor(label1, true);
    lv_label_set_text(label1, "#ff0000 btn#");
    lv_obj_align(label1, btn, LV_ALIGN_CENTER, 0, 0);
}

static void lvgl_init( void ) 
{
    lv_init();
    lv_port_disp_init();        // 顯示器初始化
    lv_port_indev_init();       // 輸入裝置初始化
    // lv_port_fs_init();          // 檔案系統裝置初始化
}

int main()
{
	lvgl_init();

	my_lvgl_test();

	while(1) {
		// 先呼叫 lv_tick_inc 再呼叫 lv_task_handler
		lv_tick_inc(LVGL_TICK);
		lv_task_handler();
		delay_ms(LVGL_TICK);
	}
}

五、啟動LVGL

參考我的上一篇文章即可:LittleVGL (LVGL)乾貨入門教程一之移植到stm32晶片


本篇完


下一篇

待續。。。

相關文章