跳到主要内容位置

输入系统

1.数据结构抽象#

对于量产工具测试,输入两个来源:

  • 网络输入
  • 屏幕点击

将输入数据抽象成一个输入事件,Input_Event。

typedef struct InputEvent
{
struct timeval tTime;
int iType;
int iX;
int iY;
int iPressure;
char str[1024];
} InputEvent, *PInputEvent;

再定义一个输入设备结构体,用来描述不同设备操作:

typedef struct InputDevice
{
char *name;
int (*GetInputEvent)(PInputEvent ptInputEvent);
int (*DeviceInit)(void);
int (*DeviceExit)(void);
struct InputDevice *ptNext;
} InputDevice, *PInputDevice;

2.触摸屏输入#

2.1 编程#

编写input/touchscreen.c

先定义一个输入设备结构体-触摸屏设备:

static InputDevice g_tTouchscreenDev = {
.name = "touchscreen",
.GetInputEvent = tsGetInputEvent,
.DeviceInit = tsDeviceInit,
.DeviceExit = tsDeviceExit,
};

参考"01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\11_input\02_tslib\mt_cal_distance.c"

实现结构体中的三个函数,使用tslib中的函数:

  • 初始化函数:ts_setup
  • 结束函数:ts_close
#include <stdio.h>
#include <tslib.h>
#include "input_manager.h"
static struct tsdev *g_ts;
static int tsDeviceInit(void)
{
g_ts = ts_setup(NULL, 0);
if (!g_ts)
{
printf("ts_setup err\n");
return -1;
}
return 0;
}
static int tsDeviceExit(void)
{
ts_close(g_ts);
return 0;
}
  • 获取输入事件:ts_read。读取输入事件到samp,给传入的ptInputEvent赋值。
static int tsGetInputEvent(PInputEvent ptInputEvent)
{
struct ts_sample samp;
int ret;
ret = ts_read(g_ts, &samp, 1);
if (ret != 1)
return -1;
ptInputEvent->iX = samp.x;
ptInputEvent->iY = samp.y;
ptInputEvent->iPressure = samp.pressure;
ptInputEvent->iType = INPUT_TYPE_TOUCH;
ptInputEvent->tTime = samp.tv;
return 0;
}

2.2 测试#

main#

直接在input/touchscreen.c文件下写一个主函数用于测试:

#if 1
int main(int argc, char **argv)
{
int ret;
InputEvent event;
g_tTouchscreenDev.DeviceInit();
while (1)
{
ret = g_tTouchscreenDev.GetInputEvent(&event);
if (ret)
{
printf("GetInputEvent err!\n");
}
else
{
printf("Type : %d\n", event.iType);
printf("iX : %d\n", event.iX);
printf("iY : %d\n", event.iY);
printf("iPressure: %d\n", event.iPressure);
}
}
g_tTouchscreenDev.DeviceExit();
return 0;
}
#endif

修改makefile#

(1)在input目录下添加一个Makefile

EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += touchscreen.o

(2)修改顶层目录makefile:

  • 链接tslib库
LDFLAGS := -lts

前提是交叉编译过tslib,并安装:参考《01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板》7.5.2 交叉编译、测试 tslib

  • 添加input目录
obj-y += display/
obj-y += input/

问题:

注意在input_manager.h中包含

#include <sys/time.h>

但我发现在#include <tslib.h>的情况下,不包含time.h,也通过了编译。说明tslib.h中也包含了time.h,但是这样不规范。

3.网络输入#

3.1 编程#

3.2 测试#

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
source ~/.bashrc

编译成功后,仅剩下touchscreen的警告,因为关闭了其测试程序:

拷贝01all_series_quickstart\04嵌入式Linux应用开发基础知识\source\12_socket\udp2\client.c到

6th_project_practice_source\09_input_netinput_unittest\unittest,然后编译一下

book@100ask:~/nfs_rootfs/09_input_netinput_unittest$ arm-buildroot-linux-gnueabihf-gcc -o client unittest/client.c

4.输入管理#

为了支持同时从多个输入设备得到数据,不丢失数据,引入输入管理架构。

4.1 总体结构#

要想支持多个输入设备,只能使用线程: 为每个InputDevice都创建一个“读取线程”

4.2 如何避免数据丢失?#

比如触摸屏,它会一下子上报很多数据

对于网络输入,也有可能同时又多个client发来数据、

所以,不能使用单一的变量来保存数据,而是使用一个数组来保存数据 — 使用“环形缓冲区”

4.3 编程#

输入管理的框架代码#

  • void RegisterInputDevice(PInputDevice ptInputDev):下层调用,注册输入设备到输入管理的设备链表
  • void IntputRegisterInit(void):上层调用,注册所有输入设备
  • static void *input_recv_tread_func(void *data):内部调用,输入设备事件的接收线程函数
  • void IntputDeviceInit(void):上层调用,输入设备初始化,调用下层函数进行初始化。具体地,从输入管理的设备链表中找出设备,调用其初始化函数ptInputDevtmp->DeviceInit(),然后为其创建一个线程。 为何需要使用线程?因为有两个输入设备,如果在同一个程序里面轮询,读取触摸屏时,可能会休眠,那么网络输入就会丢失;读取网络数据时,也可能会休眠,那么触摸屏数据就会丢失。所有需要为每个输入设备都创建一个线程,并且要使用锁,实现互斥地访问环形缓冲区。
  • int GetInputEvent(PInputEvent PT_InputEvent):上层调用,获取输入事件
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "input_manager.h"
static PInputDevice g_InputDevs = NULL;
/* 下层注册输入设备 */
void RegisterInputDevice(PInputDevice ptInputDev)
{
ptInputDev->ptNext = g_InputDevs;
g_InputDevs = ptInputDev;
}
void IntputRegisterInit(void)
{
extern void TouchscreenRegister(void);
extern void NetinputDevRegister(void);
/* 注册 touchscreen */
TouchscreenRegister();
/* 注册 netinput */
NetinputDevRegister();
}
static void *input_recv_tread_func(void *data)
{
PInputDevice ptInputDev = (PInputDevice)data; //得到输入设备
InputEvent tEvent;
int ret;
while (1)
{
//读数据
ptInputDev->GetInputEvent(&tEvent);
if (!ret)
{
//保存数据
}
}
return NULL;
}
void IntputDeviceInit(void)
{
int ret;
pthread_t tid;
/* 初始化所有输入设备,创建pthread */
PInputDevice ptInputDevtmp = g_InputDevs;
while (ptInputDevtmp)
{
/* 初始化设备 */
ret = ptInputDevtmp->DeviceInit();
if (!ret)
{
/* 初始化成功,就创建线程 */
ret = pthread_create(&tid, NULL, input_recv_tread_func, ptInputDevtmp);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
}
ptInputDevtmp = ptInputDevtmp->ptNext;
}
}
int GetInputEvent(PInputEvent PT_InputEvent)
{
/* 无数据则休眠 */
/* 有数据就放回 */
}

实现环形缓冲区#

  • 注意NEXT_POS(x)需要取余数计算
  • 注意空条件与满条件的区别,以放弃一个存储位置来做区分:读写位置相同时为空,下一个写的位置等于读的位置时为满。
/* start---------实现环形buffer */
#define BUFFER_LEN 20
#define NEXT_POS(x) ((x + 1) % BUFFER_LEN)
static int g_iRead = 0;
static int g_iWrite = 0;
static InputEvent g_atInputEvents[BUFFER_LEN];
static int isInputEmpty(void)
{
return (g_iRead == g_iWrite);
}
static int isInputFull(void)
{
return (NEXT_POS(g_iWrite) == g_iRead);
}
static void PutInputEventsToBuffer(PInputEvent ptInputEvent)
{
if (!isInputFull())
{
g_atInputEvents[g_iWrite] = *ptInputEvent;
g_iWrite = NEXT_POS(g_iWrite);
}
}
static int GetInputEventsFromBuffer(PInputEvent ptInputEvent)
{
if (!isInputEmpty())
{
*ptInputEvent = g_atInputEvents[g_iRead];
g_iRead = NEXT_POS(g_iRead);
return 1;
}
else
{
return 0;
}
}
/* end---------实现环形buffer */

线程函数与获取输入事件函数#

完善线程函数input_recv_tread_func与获取输入事件函数GetInputEvent:

  • GetInputEvent:上层调用此函数获取输入数据,为实现多线程对环形缓冲区资源的临界访问,需要使用互斥锁。参考"01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\13_thread\02_视频配套源码\pthread5.c"代码。
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
int GetInputEvent(PInputEvent ptInputEvent)
{
InputEvent tEvent;
int ret;
/* 无数据则休眠 */
pthread_mutex_lock(&g_tMutex); //获取互斥锁
if (GetInputEventsFromBuffer(&tEvent)) //从缓冲区获取输入事件
{
/* 成功获取 */
ret = 0;
pthread_mutex_unlock(&g_tMutex); //释放互斥锁
}
else
{
/* 未成功获取,休眠等待 */
pthread_cond_wait(&g_tConVar, &g_tMutex);
if (GetInputEventsFromBuffer(&tEvent)) //唤醒后,再次获取并判断,防止数据已经被拿走
{
ret = 0;
}
else
{
ret = -1;
}
pthread_mutex_unlock(&g_tMutex);
}
/* 有数据就返回 */
if (ret == 0)
*ptInputEvent = tEvent;
return ret;
}
  • input_recv_tread_func:多个输入设备共用这个函数来创建不同的线程,通过传入的data转换为PInputDevice来区分;调用下层的设备提供的函数获取输入事件,成功就保存数据 - 保存数据前需要获取互斥锁,保存数据后唤醒等待数据的线程(在我们的任务中就是调用上面函数GetInputEvent的线程),然后再释放互斥锁。
static void *input_recv_tread_func(void *data)
{
PInputDevice ptInputDev = (PInputDevice)data; //得到输入设备
InputEvent tEvent;
int ret;
while (1)
{
//读数据,没有数据它会在内部进入休眠
ret = ptInputDev->GetInputEvent(&tEvent);
if (!ret)
{
//保存数据
pthread_mutex_lock(&g_tMutex);
PutInputEventsToBuffer(&tEvent);
//唤醒等待数据的线程
pthread_cond_signal(&g_tConVar); //唤醒
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}

补充#

input_manager.h

声明函数

// 下层注册输入设备
void RegisterInputDevice(PInputDevice ptInputDev);
// app
void IntputRegisterInit(void);
void IntputDeviceInit(void);
int GetInputEvent(PInputEvent PT_InputEvent);

struct timeval需要包含time.h

#include <sys/time.h>
typedef struct InputEvent
{
struct timeval tTime;
int iType;
int iX;
int iY;
int iPressure;
char str[1024];
} InputEvent, *PInputEvent;

总结#


4.4 测试#

main#

unittest目录下创建input_test.c

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include "input_manager.h"
int main(int argc, char **argv)
{
InputEvent tEvent;
int ret;
IntputRegisterInit();
IntputDeviceInit();
while (1)
{
ret = GetInputEvent(&tEvent);
if (ret)
{
printf("GetInputEvent err!\n");
}
else
{
if (tEvent.iType == INPUT_TYPE_NET)
{
printf("Type : %d\n", tEvent.iType);
printf("str : %s\n", tEvent.str);
}
else if (tEvent.iType == INPUT_TYPE_TOUCH)
{
printf("Type : %d\n", tEvent.iType);
printf("iX : %d\n", tEvent.iX);
printf("iY : %d\n", tEvent.iY);
printf("iPressure: %d\n", tEvent.iPressure);
}
}
}
return 0;
}

上机#

(1)修改Makefile

unittest/Makefile

EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += input_test.o

input/Makefile

EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += touchscreen.o
obj-y += netinput.o
obj-y += input_manager.o

顶层目录的Makefile

...
obj-y += unittest/
obj-y += input/
...

(2)错误:

链接错误:/home/book/nfs_rootfs/12_intput_manager_unittest/input/input_manager.c:108: undefined reference to pthread_create

需要在makefile中加入pthread库

不能把临时变量的地址赋给返回值,应该进行值拷贝

int GetInputEvent(PInputEvent ptInputEvent)
{
InputEvent tEvent;
int ret;
/* 无数据则休眠 */
pthread_mutex_lock(&g_tMutex); //获取互斥锁
if (GetInputEventsFromBuffer(&tEvent))
{
ret = 0;
//释放互斥锁
pthread_mutex_unlock(&g_tMutex);
}
else
{
/* 休眠等待 */
pthread_cond_wait(&g_tConVar, &g_tMutex);
if (GetInputEventsFromBuffer(&tEvent))
{
ret = 0;
}
else
{
ret = -1;
}
pthread_mutex_unlock(&g_tMutex);
}
if (ret == 0)
*ptInputEvent = tEvent; /* 错误:ptInputEvent = &tEvent; 不能把临时变量的地址赋给返回值,应该进行值拷贝 */
return ret;
/* 有数据就放回 */
}

测试通过:


请点击左侧菜单(移动端为右下角)选择要查看的所有笔记吧。