前言
Littlevgl是近两年刚刚兴起的嵌入式GUI系统,网上资源特别少,自己在使用的过程中也遇到比较多的问题,仅仅只能通过github论坛来查找遇到的问题。这篇帖子写的关于编码器的配置和使用,本人小白,刚刚接触Littlevgl,如果有写错的地方,请各位大牛指导。
Littlevgl的官方网站
Littlevgl官方网站
Littlevgl官方github源代码
很全面的官方半中文文档
用于按键接口的类型
键盘 LV_INDEV_TYPE_KEYPAD
编码器 LV_INDEV_TYPE_ENCODER
littlevgl本身是应用于触屏屏,但也提供了按键控制界面的接口方式。
接口方式有三种:
1.键盘 LV_INDEV_TYPE_KEYPAD
2.编码器 LV_INDEV_TYPE_ENCODER
3.按钮 LV_INDEV_TYPE_BUTTON
第三种方式一般不常使用,因为是将硬件按键固定于屏幕上指定的区域,非常不便利。
键盘和编码器的介绍
键盘的键
LV_KEY_NEXT 专注于下一个对象
LV_KEY_PREV 专注于上一个对象
LV_KEY_ENTER 触发器LV_EVENT_PRESSED/CLICKED/LONG_PRESSED等事件
LV_KEY_UP 增加值或向上移动
LV_KEY_DOWN 减小值或向下移动
LV_KEY_RIGHT 增加值或向右移动
LV_KEY_LEFT 减小值或向左移动
LV_KEY_ESC 关闭或退出(例如,关闭下拉列表)
LV_KEY_DEL 删除(例如,“ 文本”区域中右侧的字符)
LV_KEY_BACKSPACE 删除左侧的字符(例如,文本区域中的字符)
LV_KEY_HOME 转到开头/顶部(例如,在“ 文本”区域中)
LV_KEY_END 转到末尾(例如,在“ 文本”区域中)
编码器的键
LV_KEY_NEXT 专注于下一个对象
LV_KEY_PREV 专注于上一个对象
LV_KEY_ENTER 触发器LV_EVENT_PRESSED/CLICKED/LONG_PRESSED等事件
LV_KEY_RIGHT 增加值或向右移动
LV_KEY_LEFT 减小值或向左移动
键盘和编码器的区别
键盘接口类型
可用于按键比较多的产品制作,每一个按键都可以对应一个按键功能,通过不同的按键来操作gui界面的跳转,返回,上移,下移,选取。
编码器接口类型
可用于按键比较少的产品,三个按键便可以实现跟键盘接口一样的多功能控制界面,两个键用于界面控件的选取,一个键用于按键的触发。
再通过更改组的模式来达到控制多个控件和单个控件的效果
编辑模式
void lv_group_set_editing(lv_group_t *group, bool edit) edit:true
控制单个控件的上下跳转,列表下拉等
导航模式
void lv_group_set_editing(lv_group_t *group, bool edit) edit:false
在多个控件上移动,移动选取控件的焦点
官方例程配置
键盘
#include "lv_port_indev_templ.h"
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);
lv_indev_t * indev_keypad;
void lv_port_indev_init(void)
{
lv_indev_drv_t indev_drv;
/*------------------
* 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);
}
/*------------------
* Keypad
* -----------------*/
/* Initialize your keypad */
static void keypad_init(void)
{
/*Your code comes here*/
}
/* 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*/
uint32_t act_key = keypad_get_key();
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
/*Translate the keys to LittlevGL 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*/
return 0;
}
编码器
#include "lv_port_indev_templ.h"
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);
lv_indev_t * indev_encoder;
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
void lv_port_indev_init(void)
{
lv_indev_drv_t indev_drv;
/*------------------
* 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_KEYPAD;
indev_drv.read_cb = encoder_read;
indev_encoder = lv_indev_drv_register(&indev_drv);
}
/*------------------
* Encoder
* -----------------*/
/* Initialize your keypad */
static void encoder_init(void)
{
/*Your code comes here*/
}
/* Will be called by the library to read the encoder */
static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
data->enc_diff = encoder_diff;
data->state = encoder_state;
/*Return `false` because we are not buffering and no more data to read*/
return false;
}
/*Call this function in an interrupt to process encoder events (turn, press)*/
static void encoder_handler(void)
{
/*Your code comes here*/
encoder_diff += 0;
encoder_state = LV_INDEV_STATE_REL;
}
自定义配置
官方源代码键盘接口例程还好,编码器接口例程看起来不清不楚,这也是我会写这篇文章的原因。
官方代码大概框架已经有所了解,那么应该怎么去配置呢?
首先配置输入设备接口,需要有硬件按键扫描函数,按键驱动在网上到处都有,像正点原子、野火还有安富莱都有提供按键驱动大家可以自行查找,但我下面也会贴出我自己用的按键扫描函数,是搬运于正点原子的。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY_OK GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)//读取按键ok
#define KEY_LEFT GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)//读取按键left
#define KEY_RIGHT GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_3)//读取按键right
#define KEY_OK_PRES 1 //KEY_OK
#define KEY_LEFT_PRES 2 //KEY_LEFT
#define KEY_RIGHT_LEFT_PRES 3 //KEY_RIGHT
void KEY_Init(void);//按键初始化
u8 KEY_Scan(u8 mode); //按键扫描函数
#endif
key.c
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
//按键处理函数
//返回按键值
//mode:0,不支持长按;1,支持长按;
//返回值:
//0,没有按键按下
//KEY_OK_PRES,KEY_OK 按下
//KEY_LEFT_PRES,KEY_LEFT 按下
//KEY_RIGHT_PRES,KEY_RIGHT 按下
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键松开标志
if(mode)key_up=1; //支持长按
if(key_up&&(KEY_OK==0||KEY_LEFT==0||KEY_RIGHT==0))
{
delay_ms(20);//去抖动
key_up=0;
if(KEY_OK==0)return KEY_OK_PRES;
else if(KEY_LEFT==0)return KEY_LEFT_PRES;
else if(KEY_RIGHT==0)return KEY_RIGHT_LEFT_PRES;
}else if(KEY_OK==1&&KEY_LEFT==1&&KEY_RIGHT==1)key_up=1;
return 0;// 没有按键按下
}
这样扫描按键驱动就编写完成了,然后将此驱动函数放入设备读取
以下是按键接口驱动
#include "lv_port_indev.h"
static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static lv_indev_t * indev_keypad;
static lv_group_t *g;
void lv_port_indev_init(void)
{
lv_indev_drv_t indev_drv;
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);
g = lv_group_create(); //创建组
lv_indev_set_group(indev_keypad, g); //将组绑定到输入设备
}
static bool keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
/*Get whether the a key is pressed and save the pressed key*/
uint32_t act_key = KEY_Scan(1);
if(act_key != 0) {
data->state = LV_INDEV_STATE_PR;
/*Translate the keys to LittlevGL control characters according to your key definitions*/
switch(act_key) {
case 1:
act_key = LV_KEY_ENTER;
break;
case 2:
act_key = LV_KEY_PREV;
break;
case 3:
act_key = LV_KEY_NEXT;
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;
}
/**
** 为了方便获得组和输入设备可以在这个编写返回函数
**/
lv_indev_t * Get_indev(void)
{
return indev_keypad;
}
lv_group_t *Get_group(void)
{
return g;
}
上面是键盘接口的编写,由于我只有三个键我就不演示其他按键,这个比较简单,根据官方例程可以轻松写出来,要使用的时候,只需要创建对象,加入组,就可以通过按键控制界面控件,缺点就是需要有两个键来移动控件之间的焦点,另外两个键来编辑控件的滑动和列表下拉。
编码器接口驱动
#include "lv_port_indev.h"
static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
static lv_indev_t * indev_encoder;
static lv_group_t *g;
void lv_port_indev_init(void)
{
lv_indev_drv_t indev_drv;
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);
g = lv_group_create();
lv_indev_set_group(indev_encoder, g);
}
static bool encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static uint32_t last_key = 0;
uint32_t act_key = KEY_Scan(0);
if(act_key != 0) {
switch(act_key) {
case 1:
act_key = LV_KEY_ENTER;
encoder_state = LV_INDEV_STATE_PR;
break;
case 2:
act_key = LV_KEY_LEFT;
encoder_diff = -1;
encoder_state = LV_INDEV_STATE_REL;
break;
case 3:
act_key = LV_KEY_RIGHT;
encoder_state = LV_INDEV_STATE_REL;
encoder_diff = 1;
break;
}
last_key = act_key;
}
else {
encoder_diff = 0;
encoder_state = LV_INDEV_STATE_REL;
}
data->key = last_key;
data->enc_diff = encoder_diff;
data->state = encoder_state;
/*Return `false` because we are not buffering and no more data to read*/
return false;
}
/**
** 为了方便获得组和输入设备可以在这个编写返回函数
**/
lv_indev_t * Get_indev(void)
{
return indev_encoder;
}
lv_group_t *Get_group(void)
{
return g;
}
默认编辑模式,可通过调用void lv_group_set_editing(lv_group_t *group, bool edit)
来更改模式 edit–> true:编辑模式 false:导航模式
调用
void test()
{
lv_theme_t *th = lv_theme_night_init(184, NULL);
lv_theme_set_current(th); //设置夜间的主题模式
lv_obj_t * scr = lv_scr_act(); //获取活动屏幕
lv_obj_set_style(scr, &lv_style_scr); //设置默认的SCR主题
//调用此函数记得在输入设备驱动的头文件申明函数
lv_obj_t *group = Get_group(); //获取设备关联的组
lv_obj_t *cont = lv_cont_create(scr,NULL);//创建一个容器
lv_obj_set_size(cont,320,240);
lv_obj_t *btn1 = lv_btn_create(cont,NULL);//创建按钮1
lv_obj_set_size(btn1,50,30);
lv_obj_set_pos(btn1,0,0);
lv_obj_t *btn2 = lv_btn_create(cont,btn1);//创建按钮2
lv_obj_set_pos(btn2,0,60);
lv_obj_t *btn3 = lv_btn_create(cont,btn1);//创建按钮3
lv_obj_set_pos(btn3,0,120);
lv_obj_t *btn4 = lv_btn_create(cont,btn1);//创建按钮4
lv_obj_set_pos(btn4,0,180);
lv_obj_t *roller = lv_roller_create(cont,NULL);//创建滚轮控件
lv_roller_set_options(roller,"1\n2\n3\n4\n5\n6",LV_ROLLER_MODE_INIFINITE);
lv_obj_set_pos(roller,100,100);
lv_group_add_obj(group ,btn1);
lv_group_add_obj(group ,btn2);
lv_group_add_obj(group ,btn3);
lv_group_add_obj(group ,btn4);
lv_group_add_obj(group ,roller);
lv_group_set_editing(group,false);//导航模式
}
int main()
{
//时钟初始化
//LCD初始化
//延时初始化
/*****省略一些初始化*****/
KEY_Init(); //按键初始化
lv_init(); //lvgl系统初始化
lv_port_disp_init(); //lvgl显示接口初始化,放在lv_init()的后面
lv_port_encoder_init(); //输入设备接口初始化
test();
while(1)
{
lv_task_handler();
delay_ms(5);
}
}
总结
很久没写过博客,这是第二篇博客,可能描述起来没什么经验有点乱,但目前还没有人对lvgl的编码器编写过博客,基于这次在这个地方卡的比较久,所以写这篇博客,给刚接触的小白参考一下。
后续会继续更新littlevgl这方面的知识,也是博主自己在制作产品的过程中慢慢摸索出来的,虽然经验不是很够,但希望能对其他人有帮助。也希望其他在使用lvgl的大牛,能多发点关于这方面的知识。