05_01 多输入 轮询方式

前面的电子书采用的控制方式是用串口发送相关的命令,当程序收到命令后就会执行对应的操作。现在在原来的基础上添加多一种输入方式,触摸屏控制方式;当按下对应的位置时就可以完成对应的操作。

因为采取了多种输入控制方式,因此称之为多输入。而获取输入事件的方式是轮询。


编写代码的思路是什么?

首先明确最终的目的是显示在添加多一种输入方式->触摸屏

  1. 触摸屏幕左半部分实现向前翻页
  2. 触摸屏幕右半部分实现向后翻页
  3. 触摸屏幕中间不进行任何操作

然后,为了程序的拓展性与可读性,需要将之前的代码进行重构,将所有的输入事件封装成为一个统一的形式。具体表现在类似显示和解码,用文件 input_manage.c/h 来对底层的输入进行管理,然后底层在进行相应的数据上报/初始化等工作。

  1. 将输入系统抽象出一个结构体,将输入行为抽象成为统一的控制信号
  2. 按照结构体的定义,对各个输入系统进行封装

框架搭建

和前面搭建显示架构与编码架构类似,通过 input_manager.c/h 来描述输入的行为,然后具体的输入设备再按照 input_manager.c/h 的要求对自身的硬件行为做封装。

代码的框架如下所示。

input_manager.h 中定义了一个结构体类型,描述了每一个具体的输入设备需要提供的参数以及操作函数。而 input_manager.c 则对具体的输入设备提供的函数进行相对应的封装以提供给上层的应用进行调用。

因此,分析的内容主要如下收拾

  1. 输入设备结构体的抽象
  2. 具体设备的代码编写

Q:如何用一个结构体类型描述输入设备?

首先,对于每一个具体的输入设备而言,在使用前必须要进行相关的初始化操作,在退出后可能也需要进行一些退出的操作。

其次,因为描述的是输入设备,因此,最核心的需求就是,获取设备的输入信息。

接下来,就是一些很常规的操作,例如设备的名字,以及用于链表操作的结构体指针。

综上所述,抽象出的结构体如下所示

  
  
// 输入设备结构体
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;

至此,对于每一个具体的设备而言,都可以用上述的两个结构体很好的描述。剩下的内容就是,具体的设备按照上述的结构体抽象自身的硬件操作了。

上层应用需要调用 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;
}

在例程中,输入设备有两个:串口以及触摸屏。接下来对这两个设备进行分析。


串口输入

想要实现的效果是,无诸塞的获取串口收到的数据,当收到 ‘u’ 时,电子书上翻一页,收到 ‘n’ 时,电子书下翻一页。此处无诸塞的意思是,当串口收到数据的话就立即返回,不等待用户按下回车,没有收到用户的输入信息的话就直接返回错误。

首先按照 input_manager.h 定义的结构体定义一个串口输入设备结构体类型,如下所示

  
  
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 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;
}

触摸屏输入

还是一样的套路,首先初始化一个结构体

  
  
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 TouchScreenInputDeviceExit(void)
{
    return 0;
}

获取触摸屏数据。这里有两个需要注意的地方

  1. 通过 tslib 获取的触点数据是对应 LCD 的坐标,因此,在对获取的触摸屏数据进行判断前,必须在获取到LCD的前提下。因此在 disp_manager.c 中添加一个获取LCD分辨率的接口给外部调用,代码如下所示
        
        
    // 必须在显示设备初始化后才可以调用,否则会返回不可预知的数据
    int GetDispResolution(int *iXres, int *iYres)
    {
        if(g_ptDispOprHead){
        *iXres = g_ptDispOprHead->iXres;
        *iYres = g_ptDispOprHead->iYres;
    
        return 0;
        }
        else
            return -1;
    }
  2. 当一次按下触摸屏时,对应的会获取到多个数据。因此,需要加入某些机制用于区分触摸是否有效(也就是确定是否是同一次触摸)。实现方式是通过判断事件来确定
        
        
    // 判断是否为同一次触摸
    // 依据是两次触摸间隔
    // 小于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);
    }

获取数据的代码如下所示,可以看到,依旧是采用了轮询的方式,当获取到数据的话就直接返回,没有获取到数据的话就返回错误。

  
  
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;
}

完整的工程可以参考附件内的压缩包。


以上



附件列表

    猜你喜欢

    转载自www.cnblogs.com/jh442755/p/10799722.html