LittleVGL (LVGL)干货入门教程二之LVGL的输入设备(indev)API对接
前言:
阅读前,请确保你拥有以下条件:
- 你已经完成“显示API”的移植。
- 你已经实现了一个屏幕的触摸驱动 (如果你使用外部物理按键进行操作,那么请确保你实现了读按键状态的驱动)。
LVGL有三大种需要对接的API
- 显示API(教程一已实现, 链接:LittleVGL (LVGL)入门教程一之移植到stm32芯片)
- 输入设备API(比如触摸屏、按键 等, 此篇教程实现)
- 文件系统API(如FatFs等)
这篇文章讲“输入设备API”的移植,默认你已经移植好了“显示API”。
(重要) 编译LVGL至少需要c99标准
目录:
一、indev输入设备的种类介绍
(一)输入设备的种类
LVGL有5种输入设备接口:
- Touchpad (触摸板,例如电容屏、电阻屏等)
- Mouse (鼠标)
- Keypad (键盘)
- Encoder (编码器)
- Button (外部按键)
我们常用的是“Touchpad、Keypad”,这篇文章基于这两种,其他类似,可以根据目录第三章看你想看的接口API对接。
二、lv_port_indev更改
(一)使能indev的port文件
- 方法很简单,在lv_port_indev文件中把“#if 0”改为“#if 1”,c文件和h文件都要改。
- 在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芯片
本篇完
其他:
LittleVGL (LVGL)干货入门教程一之移植到stm32芯片
LittleVGL (LVGL)干货入门教程二之LVGL的输入设备(indev)API对接。
LittleVGL (LVGL)干货入门教程三之LVGL的文件系统(fs)API对接。
LittleVGL (LVGL)干货入门教程四之制作和使用中文汉字字库