因为采取了多种输入控制方式,因此称之为多输入。而获取输入事件的方式是轮询。
首先明确最终的目的是显示在添加多一种输入方式->触摸屏
然后,为了程序的拓展性与可读性,需要将之前的代码进行重构,将所有的输入事件封装成为一个统一的形式。具体表现在类似显示和解码,用文件 input_manage.c/h 来对底层的输入进行管理,然后底层在进行相应的数据上报/初始化等工作。
和前面搭建显示架构与编码架构类似,通过 input_manager.c/h 来描述输入的行为,然后具体的输入设备再按照 input_manager.c/h 的要求对自身的硬件行为做封装。
代码的框架如下所示。
input_manager.h 中定义了一个结构体类型,描述了每一个具体的输入设备需要提供的参数以及操作函数。而 input_manager.c 则对具体的输入设备提供的函数进行相对应的封装以提供给上层的应用进行调用。
因此,分析的内容主要如下收拾
首先,对于每一个具体的输入设备而言,在使用前必须要进行相关的初始化操作,在退出后可能也需要进行一些退出的操作。
其次,因为描述的是输入设备,因此,最核心的需求就是,获取设备的输入信息。
接下来,就是一些很常规的操作,例如设备的名字,以及用于链表操作的结构体指针。
综上所述,抽象出的结构体如下所示
// 输入设备结构体
typedef struct InputOpr {
char* name;
int (*InputDeviceInit)(void); // 初始化
int (*InputDeviceExit)(void); // 退出函数
int (*GetInputEvent)(PT_InputEventOpr);// 获取输入信息
struct InputOpr* ptNext;
}T_InputOpr, *PT_InputOpr;
x
// 输入设备结构体
typedef struct InputOpr {
char* name;
int (*InputDeviceInit)(void); // 初始化
int (*InputDeviceExit)(void); // 退出函数
int (*GetInputEvent)(PT_InputEventOpr);// 获取输入信息
struct InputOpr* ptNext;
}T_InputOpr, *PT_InputOpr;
在获取输入信息的时候,出现一个问题,具体的输入信息如何和抽象的电子书操作相联系。例如说,当串口作为输入设备时,想要设备收到 ‘u’ 时给顶层提交上一页的信号,‘n’ 时则为下一页的信号。这些具体的内容不应该由顶层的应用进行判断,因此,还需要提供一个结构体给设备用于描述提交的信息。
抽象出的信息结构体如下所示
#define INPUT_TYPE_STDIN 0
#define INPUT_TYPE_TOUCHSCREENT1
#define INPUT_VALUE_PAGEUP0
#define INPUT_VALUE_PAGEDOWN 1
#define INPUT_VALUE_UNKNOW 2
#define INPUT_VALUE_EXIT -1
// 输入事件结构体
typedef struct InputEventOpr{
struct timeval tTime;
int iVal; // 键值, 格式为 INPUT_VALUE_XXX
int iType; // 哪一个输入设备, 格式为 INPUT_TYPE_XXX
}T_InputEventOpr, *PT_InputEventOpr;
// 输入事件结构体
typedef struct InputEventOpr{
struct timeval tTime;
int iVal; // 键值, 格式为 INPUT_VALUE_XXX
int iType; // 哪一个输入设备, 格式为 INPUT_TYPE_XXX
}T_InputEventOpr, *PT_InputEventOpr;
至此,对于每一个具体的设备而言,都可以用上述的两个结构体很好的描述。剩下的内容就是,具体的设备按照上述的结构体抽象自身的硬件操作了。
上层应用需要调用 input_manager.c 提供的函数用于获取底层输入设备的输入信息,代码如下。本质上就是调用链表中的每一个输入设备的获取输入信息函数,当获取到第一个输入信息后就直接返回。
int GetAllsInputEvent(PT_InputEventOpr ptInputEventOpr)
{
int iResult = 0;
PT_InputOpr ptCurrent = g_ptInputOprHead;
while (ptCurrent)
{
iResult = ptCurrent->GetInputEvent(ptInputEventOpr);
// 只要成功获取到一个输入值就直接返回
if(0 == iResult)
return 0;
ptCurrent = ptCurrent->ptNext;
}
return -1;
}
int GetAllsInputEvent(PT_InputEventOpr ptInputEventOpr)
{
int iResult = 0;
PT_InputOpr ptCurrent = g_ptInputOprHead;
while (ptCurrent)
{
iResult = ptCurrent->GetInputEvent(ptInputEventOpr);
// 只要成功获取到一个输入值就直接返回
if(0 == iResult)
return 0;
ptCurrent = ptCurrent->ptNext;
}
return -1;
}
在例程中,输入设备有两个:串口以及触摸屏。接下来对这两个设备进行分析。
想要实现的效果是,无诸塞的获取串口收到的数据,当收到 ‘u’ 时,电子书上翻一页,收到 ‘n’ 时,电子书下翻一页。此处无诸塞的意思是,当串口收到数据的话就立即返回,不等待用户按下回车,没有收到用户的输入信息的话就直接返回错误。
首先按照 input_manager.h 定义的结构体定义一个串口输入设备结构体类型,如下所示
static struct InputOpr g_tStdinOpr = {
.name = "stdin",
.InputDeviceInit = StdinDeviceInit,
.InputDeviceExit = StdinDeviceExit,
.GetInputEvent = StdinGetInputEvent,
};
static struct InputOpr g_tStdinOpr = {
.name = "stdin",
.InputDeviceInit = StdinDeviceInit,
.InputDeviceExit = StdinDeviceExit,
.GetInputEvent = StdinGetInputEvent,
};
此处的难点在于,如何无诸塞的获取到串口的数据,这部分的内容是参考 https://blog.csdn.net/fanwenjieok/article/details/38331633 的。经过测试,可以使用的代码如下所示
首先是初始化函数 StdinDeviceInit ,代码如下所示
static int StdinDeviceInit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn off canonical mode
tTTYState.c_lflag &= ~ICANON;
//minimum of number input read.
tTTYState.c_cc[VMIN] = 1; /* 有一个数据时就立刻返回 */
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
static int StdinDeviceInit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn off canonical mode
tTTYState.c_lflag &= ~ICANON;
//minimum of number input read.
tTTYState.c_cc[VMIN] = 1; /* 有一个数据时就立刻返回 */
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
退出函数如下所示
static int StdinDeviceExit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn on canonical mode
tTTYState.c_lflag |= ICANON;
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
static int StdinDeviceExit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn on canonical mode
tTTYState.c_lflag |= ICANON;
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
然后就是最关键的获取数据函数,如题目所示,此处使用的是轮询的方式,也就是一直查询有没有收到数据
static int StdinGetInputEvent(PT_InputEventOpr ptInputEventOpr)
{
int iCh = 0;
struct timeval tTV;
fd_set tFDs;
tTV.tv_sec = 0;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);
FD_SET(STDIN_FILENO, &tFDs);
select(STDIN_FILENO+1, &tFDs, NULL, NULL, &tTV);
if(FD_ISSET(STDIN_FILENO, &tFDs)){
ptInputEventOpr->iType = INPUT_TYPE_STDIN;
iCh = fgetc(stdin);
switch (iCh){
case ‘n‘:
ptInputEventOpr->iVal = INPUT_VALUE_PAGEDOWN;
break;
case ‘u‘:
ptInputEventOpr->iVal = INPUT_VALUE_PAGEUP;
break;
case ‘q‘:
ptInputEventOpr->iVal = INPUT_VALUE_EXIT;
break;
default:
ptInputEventOpr->iVal = INPUT_VALUE_EXIT;
break;
}
return 0;
}
return -1;
}
static int StdinGetInputEvent(PT_InputEventOpr ptInputEventOpr)
{
int iCh = 0;
struct timeval tTV;
fd_set tFDs;
tTV.tv_sec = 0;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);
FD_SET(STDIN_FILENO, &tFDs);
select(STDIN_FILENO+1, &tFDs, NULL, NULL, &tTV);
if(FD_ISSET(STDIN_FILENO, &tFDs)){
ptInputEventOpr->iType = INPUT_TYPE_STDIN;
iCh = fgetc(stdin);
switch (iCh){
case ‘n‘:
ptInputEventOpr->iVal = INPUT_VALUE_PAGEDOWN;
break;
case ‘u‘:
ptInputEventOpr->iVal = INPUT_VALUE_PAGEUP;
break;
case ‘q‘:
ptInputEventOpr->iVal = INPUT_VALUE_EXIT;
break;
default:
ptInputEventOpr->iVal = INPUT_VALUE_EXIT;
break;
}
return 0;
}
return -1;
}
还是一样的套路,首先初始化一个结构体
struct InputOpr g_tTouchScreenInputOpr = {
.name = "TouchScreenInput",
.InputDeviceInit = TouchScreenInputDeviceInit,
.InputDeviceExit = TouchScreenInputDeviceExit,
.GetInputEvent = TouchScreenGetInputEvent,
};
struct InputOpr g_tTouchScreenInputOpr = {
.name = "TouchScreenInput",
.InputDeviceInit = TouchScreenInputDeviceInit,
.InputDeviceExit = TouchScreenInputDeviceExit,
.GetInputEvent = TouchScreenGetInputEvent,
};
触摸屏数据的来源是通过 tslib 库来获取输入的内容,参考 ts_print.c 的内容
首先是初始化函数,主要方式打开触摸屏设备的操作(参考自 ts_print.c)
static int TouchScreenInputDeviceInit(void)
{
char *tsdevice = NULL;
if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
g_tTs = ts_open(tsdevice, 1);
}
else {
g_tTs = ts_open("/dev/event0", 1);
}
if (!g_tTs) {
DBG_PRINTF("ts_open error!\n");
return -1;
}
if (ts_config(g_tTs)) {
DBG_PRINTF("ts_config error!\n");
return -1;
}
if(GetDispResolution(&g_iTouchScreenXres, &g_iTouchScreenYres)){
DBG_PRINTF("GetDispResolution error\r\n");
return -1;
}
return 0;
}
static int TouchScreenInputDeviceInit(void)
{
char *tsdevice = NULL;
if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
g_tTs = ts_open(tsdevice, 1);
}
else {
g_tTs = ts_open("/dev/event0", 1);
}
if (!g_tTs) {
DBG_PRINTF("ts_open error!\n");
return -1;
}
if (ts_config(g_tTs)) {
DBG_PRINTF("ts_config error!\n");
return -1;
}
if(GetDispResolution(&g_iTouchScreenXres, &g_iTouchScreenYres)){
DBG_PRINTF("GetDispResolution error\r\n");
return -1;
}
return 0;
}
接下来的退出函数,什么事情都不用干
static int TouchScreenInputDeviceExit(void)
{
return 0;
}
static int TouchScreenInputDeviceExit(void)
{
return 0;
}
获取触摸屏数据。这里有两个需要注意的地方
// 必须在显示设备初始化后才可以调用,否则会返回不可预知的数据 int GetDispResolution(int *iXres, int *iYres) { if(g_ptDispOprHead){ *iXres = g_ptDispOprHead->iXres; *iYres = g_ptDispOprHead->iYres; return 0; } else return -1; }
x1// 必须在显示设备初始化后才可以调用,否则会返回不可预知的数据
2int GetDispResolution(int *iXres, int *iYres)
3{
4if(g_ptDispOprHead){
5*iXres = g_ptDispOprHead->iXres;
6*iYres = g_ptDispOprHead->iYres;
78return 0;
9}
10else
11return -1;
12}
// 判断是否为同一次触摸 // 依据是两次触摸间隔 // 小于500ms认为是同一次触摸 static int isEffectiveTouch(const struct timeval* ptCurrentTime) { int iNowMs; int iPreMs; iNowMs = ptCurrentTime->tv_sec * 1000 + ptCurrentTime->tv_usec / 1000; iPreMs = g_tLastTouchTime.tv_sec * 1000 + g_tLastTouchTime.tv_usec / 1000; return (iNowMs > iPreMs+500); }
131// 判断是否为同一次触摸
2// 依据是两次触摸间隔
3// 小于500ms认为是同一次触摸
4static int isEffectiveTouch(const struct timeval* ptCurrentTime)
5{
6int iNowMs;
7int iPreMs;
8
9iNowMs = ptCurrentTime->tv_sec * 1000 + ptCurrentTime->tv_usec / 1000;
10iPreMs = g_tLastTouchTime.tv_sec * 1000 + g_tLastTouchTime.tv_usec / 1000;
11
12return (iNowMs > iPreMs+500);
13}
获取数据的代码如下所示,可以看到,依旧是采用了轮询的方式,当获取到数据的话就直接返回,没有获取到数据的话就返回错误。
static int TouchScreenGetInputEvent(PT_InputEventOpr ptInputEventOpr)
{
struct ts_sample tSamp;
int ret;
ret = ts_read(g_tTs, &tSamp, 1);
if (ret < 0) {
return -1;
}
//gettimeofday(ptInputEventOpr->tTime, NULL);
if(isEffectiveTouch(&(tSamp.tv))){
DBG_PRINTF("effective touch \r\n");
ptInputEventOpr->iType = INPUT_TYPE_TOUCHSCREENT;
ptInputEventOpr->tTime = tSamp.tv;
g_tLastTouchTime = tSamp.tv;
if(tSamp.x < g_iTouchScreenXres/3)
ptInputEventOpr->iVal = INPUT_VALUE_PAGEUP;
else if(tSamp.x > 2*g_iTouchScreenXres/3)
ptInputEventOpr->iVal = INPUT_VALUE_PAGEDOWN;
else
ptInputEventOpr->iVal = INPUT_VALUE_UNKNOW;
return 0;
}
else
return -1;
return 0;
}
static int TouchScreenGetInputEvent(PT_InputEventOpr ptInputEventOpr)
{
struct ts_sample tSamp;
int ret;
ret = ts_read(g_tTs, &tSamp, 1);
if (ret < 0) {
return -1;
}
//gettimeofday(ptInputEventOpr->tTime, NULL);
if(isEffectiveTouch(&(tSamp.tv))){
DBG_PRINTF("effective touch \r\n");
ptInputEventOpr->iType = INPUT_TYPE_TOUCHSCREENT;
ptInputEventOpr->tTime = tSamp.tv;
g_tLastTouchTime = tSamp.tv;
if(tSamp.x < g_iTouchScreenXres/3)
ptInputEventOpr->iVal = INPUT_VALUE_PAGEUP;
else if(tSamp.x > 2*g_iTouchScreenXres/3)
ptInputEventOpr->iVal = INPUT_VALUE_PAGEDOWN;
else
ptInputEventOpr->iVal = INPUT_VALUE_UNKNOW;
return 0;
}
else
return -1;
return 0;
}
完整的工程可以参考附件内的压缩包。
以上
原文:https://www.cnblogs.com/jh442755/p/10799722.html