LVGL学习(二)ESP32之LVGL输入设备
第二章 ESP32 LVGL for Encoder前言
LVGL有ESP32的相应支持,在github上下载相应的DEMO,此文章是关于ESP32的LVGL DEMO演示,并使用EC11编码器作为输入设备。一、EC11编码器
EC11编码器通常又被称为旋转编码器,一般主要是用于亮度,温度,频率,音量调节等参数控制。
三只脚中的C脚接地,AB脚接上拉电阻后,当左转或右转时,AB脚就有脉冲信号输出。1和2脚为按压开关,按下时导通。
根据以上正反转一定位一脉冲波形图可知:若将EC11旋转编码器的A端视为时钟,B端视为数据,整个EC11旋转编码器就可以视为根据时钟脉冲输出信号的同步元件。可以看做数据在时钟的边沿处输出(即时钟线检测边沿,数据线检测电平,这个思路编程最简单),当EC11旋转编码器正转时,在时钟线的下降沿处,数据线为高电平,或在时钟线的上升沿处,数据线为低电平;当EC11旋转编码器反转时,在时钟线的下降沿处,数据线为低电平,或在时钟线的上升沿处,数据线为高电平。可以不严谨的简单概括为:在时钟的下降沿处,A、B反相为正转,同相为反转。进一步总结为:EC11旋转编码器每转一格,A、B两线就会各自输出一个完整的脉冲,因此我们可以仅检测时钟线的时钟单边沿(上升沿或者下降沿任选一个做检测),根据时钟线的边沿处,信号线的电平来判断EC11旋转编码器是正转还是反转。为什么这样检测?由于一定位一脉冲的EC11旋转编码器在正常情况下A、B线初始状态都是高电平,所以直接检测时钟线的下降沿更方便。(另一种方法也可以看做数据与时钟的相位关系(即检测数据线和时钟线哪根线的边沿先出现,这个思路编程较为复杂,适合做硬件实现):正转时,A线相位超前于B线相位;反转时,B线相位超前于A线相位。使用简单的数字逻辑电路(异或门与D触发器)就可以识别到A、B的相位关系与转动次数。)
二、ESP32 LVGL编码器输入设计步骤
1.下载ESP32Encoder编码器库
代码如下(示例):
https://github.com/madhephaestus/ESP32Encoder.git
2.加入库文件
在VS Code工程中加入要用的库文件:
如上图并在SRC文件夹与include文件加入分别加入
lv_port_indev.h、main.h与lv_port_indev.cpp文件,其中lv_port_indev.h与lv_port_indev.cpp为模板,直接复制过来修改就可以。
3.相关代码
lv_port_indev.h代码如下(示例):/**
* @file lv_port_indev_templ.h
*
*/
/*Copy this file as "lv_port_indev.h" and set this value to "1" to enable content*/
#if 1
#ifndef LV_PORT_INDEV_TEMPL_H
#define LV_PORT_INDEV_TEMPL_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lvgl.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
void lv_port_indev_init(void); //加入初始化文件,并在主函数中调用
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_PORT_INDEV_TEMPL_H*/
#endif /*Disable/Enable content*/
main.h代码如下(示例):
#ifndef _MAIN_H
#define _MAIN_H
#include <ESP32Encoder.h>
ESP32Encoder encoder;
// GPIO配置
#define EC11_A_PIN 14
#define EC11_B_PIN 15
#define EC11_K_PIN 4
#endif
lv_port_indev.cpp代码如下(示例):
/**
* @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 "lvgl.h"
#include "main.h"
#include <ESP32Encoder.h>
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void encoder_init(void);
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static void encoder_handler(void);
/**********************
* STATIC VARIABLES
**********************/
// lv_indev_t * indev_touchpad;
// lv_indev_t * indev_mouse;
// lv_indev_t * indev_keypad;
lv_indev_t * indev_encoder; //多种输入设备,案例选择编码器
// lv_indev_t * indev_button;
static int32_t encoder_diff; //编码器输入值
static lv_indev_state_t encoder_state; //编码器按键状态
lv_indev_drv_t indev_drv;
/**********************
* 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
*/
/*------------------
* 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);`*/
}
/**********************
* STATIC FUNCTIONS
**********************/
/*------------------
* Encoder
* -----------------*/
/*Initialize your keypad*/
static void encoder_init(void)
{
/*Your code comes here*/
//ESP32Encoder::useInternalWeakPullResistors=DOWN;
// Enable the weak pull up resistors
ESP32Encoder::useInternalWeakPullResistors = UP;
// use pin 19 and 18 for the first encoder
encoder.attachHalfQuad(EC11_A_PIN, EC11_B_PIN);
// set starting count value after attaching
encoder.setCount(0);
//ESP32 编码器按键引脚设置
pinMode(EC11_K_PIN, INPUT );
pinMode(27, OUTPUT); //测试使用
}
/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
encoder_handler(); //获取编码器量处理函数
data->enc_diff = encoder_diff;
data->state = encoder_state;
}
/*Call this function in an interrupt to process encoder events (turn, press)*/
//状态处理函数
static void encoder_handler(void)
{
/*Your code comes here*/
static int last_count = 0;
encoder_state = LV_INDEV_STATE_REL;
encoder_diff = 0;
if (encoder.getCount() != last_count)
{
encoder_state = LV_INDEV_STATE_REL;
if (encoder.getCount() - last_count > 0)
{
encoder_diff = -1;
}
else if (encoder.getCount() - last_count < 0)
{
encoder_diff = 1;
}
last_count = encoder.getCount();
/* code */
}
if (digitalRead(EC11_K_PIN) == LOW)
{
/* code */
digitalWrite(27, HIGH); // turn the LED on (HIGH is the voltage level)
encoder_state = LV_INDEV_STATE_PR;
}
else if (digitalRead(EC11_K_PIN) == HIGH)
{
/* code */
encoder_state = LV_INDEV_STATE_REL;
digitalWrite(27, LOW); // turn the LED on (HIGH is the voltage level) 测试使用
}
}
#else /* Enable this file at the top */
/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif
main.cpp代码如下(示例):
#include <Arduino.h>
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <lv_demo.h>
#include <lv_port_indev.h>
/* 创建硬件定时器 */
hw_timer_t * timer = NULL;
#define LED_PIN 19
/*Change to your screen resolution*/
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 240;
/
static long last_time;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * 10 ];
TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */
void timlInterrupt() { lv_tick_inc(1);} //调用它编码器好用
/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
uint32_t w = ( area->x2 - area->x1 + 1 );
uint32_t h = ( area->y2 - area->y1 + 1 );
tft.startWrite();
tft.setAddrWindow( area->x1, area->y1, w, h );
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready( disp );
}
void setup()
{
Serial.begin( 115200 ); /* prepare for possible serial debug */
/
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // turn the LED on (HIGH is the voltage level)
String LVGL_Arduino = "Hello Arduino! ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.println( LVGL_Arduino );
Serial.println( "I am LVGL_Arduino" );
lv_init();
/* 1/(80MHZ/80) = 1us */
timer = timerBegin(0, 80, true);
/* 将onTimer函数附加到我们的计时器 */
timerAttachInterrupt(timer, &timlInterrupt, true);
/* *设置闹钟每秒调用onTimer函数1 tick为1us => 1秒为1000000us * /
/ *重复定时器(第三个参数)*/
timerAlarmWrite(timer, 1000, true);
/* 启动警报*/
timerAlarmEnable(timer);
tft.begin(); /* TFT init */
tft.setRotation( 3 ); /* Landscape orientation, flipped */
lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * 10 );
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init( &disp_drv );
/*Change the following line to your display resolution*/
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register( &disp_drv );
lv_port_indev_init();
/* Try an example from the lv_examples Arduino library
make sure to include it as written above.
lv_example_btn_1();
*/
// uncomment one of these demos
// lv_demo_widgets(); // OK
// lv_demo_benchmark(); // OK
lv_demo_keypad_encoder(); // works, but I haven't an encoder
// lv_demo_music(); // NOK
// lv_demo_printer();
// lv_demo_stress(); // seems to be OK
Serial.println( "Setup done" );
last_time = millis();
}
void loop()
{
//非阻塞写法
if (millis() - last_time > 5)
{
last_time = millis();
lv_timer_handler(); /* let the GUI do its work */
/* code */
}
}
其中还有一个最重要的步骤就是在配置文件中使能编码器为输入设备
/**
* @file lv_demo_conf.h
* Configuration file for v8.1.0-dev
*
*/
/*
* COPY THIS FILE AS lv_demo_conf.h
*/
#if 1 /*Set it to "1" to enable the content*/
#ifndef LV_DEMO_CONF_H
#define LV_DEMO_CONF_H
/*******************
* GENERAL SETTING
*******************/
#define LV_EX_PRINTF 0 /*Enable printf-ing data in demoes and examples*/
#define LV_EX_KEYBOARD 0 /*Add PC keyboard support to some examples (`lv_drivers` repository is required)*/
#define LV_EX_MOUSEWHEEL 1 /*Add 'encoder' (mouse wheel) support to some examples (`lv_drivers` repository is required)*/