前言:
前面我们已经实现了显示系统,现在我们来实现输入系统,与显示系统类似,下面让我们一起来对输入系统进行学习搭建吧
目录
一、数据结构抽象
1. 数据本身
2. 设备本身:
3. input_manager.h
二、触摸屏编程
touchscreen.c
三、触摸屏单元测试
1.touchscreen.c
2.上机测试
四、网络编程
netiput.c
五、网络单元测试
1.netiput.c
2.client.c
3.上机测试
一、数据结构抽象
对于每一个设备,每一个模块,都用一个结构体来表示它,以后就会很方便的替换这些模块,所以对于设备本身我们需要抽象出一个结构体,所以对于输入系统我们需要抽象出两个结构体,这两个结构分别是:1.数据本身、2.设备本身。
输入来源:1.网络数据输入 2.点击事件输入
1. 数据本身
通过这个数据抽象,就可以既表示出触摸屏本身的数据也能表示出网络本身的数据
触摸屏数据:
int iType : 判断是什么数据,是触摸屏数据还是网络数据
int iX、int iY: 判断具体坐标
int iPressure: 压力值
网络数据:
char str[1024]: 网络数据
2. 设备本身:
*name: 设备名字
(*GetInputEvent)(PInputEvent ptInputEvent): 上层代码通过这个函数得到数据,返回值判断数据是否成功,如果成功数据保存至PInputEvent ptInputEvent中
(*DeviceInit): 提供初始化函数,比如打开设备节点等
(*DeviceExit): 提供退出函数
InputDevice *ptNext : 如果想支持多个设备,就应该将这些设备列到一起,所以需要一个链表指针
3. input_manager.h
#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H
#include <sys/time.h>
#ifndef NULL
#define NULL (void *)8#endif
#define INPUT_TYPE_TOUCH 1
#define INPUT_TYPE_NET 2
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;
void RegisterInputDevice(PInputDevice ptInputDev);
void InputInit(void);
#endif
第9行:触摸屏事件
第10行:网络事件
第13~20行:抽象出数据本身结构体InputEvent
第14行:定义出一个时间
第23~29行:抽象出设备本身结构体InputDevice
第32行:用于输入系统管理,提供一个注册函数,下面的各个设备进行引用它
第33行:用注册函数进行引用
二、触摸屏编程
设备结构体的实现如下结构体:使用tslib
touchscreen.c
#include <input_manager.h>
#include <tslib.h>
#include <stdio.h>
static struct tsdev *g_ts;
static int TouchscreenGetInputEvent(PInputEvent ptInputEvent)
{
struct ts_sample samp;
int ret;
ret = ts_read(g_ts, &samp, 1);
if (ret != 1)
return -1;
ptInputEvent->iType = INPUT_TYPE_TOUCH;
ptInputEvent->iX = samp.x;
ptInputEvent->iY = samp.y;
ptInputEvent->iPressure = samp.pressure;
ptInputEvent->tTime = samp.tv;
return 0;
}
static int TouchscreenDeviceInit(void)
{
g_ts = ts_setup(NULL, 0);
if (!g_ts)
{
printf("ts_setup err\n");
return -1;
}
return 0;
}
static int TouchscreenDeviceExit(void)
{
ts_close(g_ts);
return 0;
}
static InputDevice g_tTouchscreenDev ={
.name = "touchscreen",
.GetInputEvent = TouchscreenGetInputEvent,
.DeviceInit = TouchscreenDeviceInit,
.DeviceExit = TouchscreenDeviceExit,
};
void TouchscreenRegister(void)
{
TouchscreenRegister(&g_tTouchscreenDev);
}
第4行:初始化结果放到全局变量g_ts中
第8行:定义一个ts_sample 的结构体samp
static int TouchscreenGetInputEvent(PInputEvent ptInputEvent)
第6~23行:获得输入事件
static int TouchscreenDeviceInit(void)
第25~35行:对设备的初始化
static int TouchscreenDeviceExit(void)
第37~41行:关闭设备
void TouchscreenRegister(void)
第52~55行:将结构体g_tTouchscreenDev注册到上一层,上一层就是输入管理器,用注册函数进行引用
三、触摸屏单元测试
1.touchscreen.c
在touchscreen.c 代码下面加一个main函数进行触摸屏的单元测试
#if 1
int main(int argc, char **argv)
{
InputEvent event;
int ret;
g_tTouchscreenDev.DeviceInit();
while (1)
{
ret = g_tTouchscreenDev.GetInputEvent(&event);
if (ret) {
printf("GetInputEvent err!\n");
return -1;
}
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);
}
}
return 0;
}
#endif
注意:#if 1则执行,# if 0则不执行
第5行:定义一个InputEvent 结构体 event 获取数据本身传给GetInputEvent()
第8行:调用g_tTouchscreenDev结构体中的DeviceInit()进行初始化
第12行:调用g_tTouchscreenDev结构体中的GetInputEvent(&event)进行获取输入事件
第17~23行:如果返回值ret=0的话,则将数据信息打印出来
EXTRA_CFLAGS :=
CFLAGS_file.o :=
#obj-y += disp_test.o
将unittest文件夹中的Makefile中的main函数部分注释掉,因为一个程序只能允许有一个main
EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += touchscreen.o
input目录下的Makefile
CROSS_COMPILE ?=
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
LDFLAGS := -lts
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := test
obj-y += display/
obj-y += input/
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built!
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
需要修改顶层目录下的Makefile
第20行:LDFLAGS := -lts 设置链接
第31行:打开input目录下的文件
2.上机测试
进行make编译,如果出现以下情况,则重新译一下tslib
上板效果如下:点击开发板会将点击的信息显示出来
四、网络编程
对网络输入构造出同触摸屏编程中一样的结构体
netiput.c
#include <input_manager.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/* socket
* bind
* sendto/recvfrom
*/
#define SERVER_PORT 8888
static int g_iSocketServer;
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{
struct sockaddr_in tSocketClientAddr;
struct int iRecvLen;
char aRecvBuf[1000];
int iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
aRecvBuf[iRecvLen] = '\0';
//printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
ptInputEvent->iType = INPUT_TYPE_NET;
gettimeofday(&ptInputEvent->tTime, NULL);
strncpy(ptInputEvent->str, aRecvBuf, 1000);
ptInputEvent->str[999] = '\0';
return 0;
}
else
return -1;
}
static int NetinputDeviceInit(void)
{
struct sockaddr_in tSocketServerAddr;
int iRet;
g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == g_iSocketServer)
{
printf("socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
return 0;
}
static int NetinputDeviceExit(void)
{
close(g_iSocketServer);
return 0;
}
static InputDevice g_tNetinputDev ={
.name = "touchscreen",
.GetInputEvent = NetinputGetInputEvent,
.DeviceInit = NetinputDeviceInit,
.DeviceExit = NetinputDeviceExit,
};
void NetInputRegister(void)
{
NetInputRegister(&g_tNetinputDev);
}
第29行:端口
第22行:获得的数据
第28行:获得的数据保存到ucRecvBuf里面
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
第24~45行:获得输入事件
static int NetinputDeviceInit(void)
第47~74行:初始化socket
static int NetinputDeviceExit(void)
第76~80行:关闭设备
void NetInputRegister(void)
第88~91行:将结构体g_tNetinputDev注册到上一层,上一层就是输入管理器,用注册函数进行引用
五、网络单元测试
1.netiput.c
这里充当服务器端,用来接收数据
#if 1
int main(int argc, char **argv)
{
InputEvent event;
int ret;
g_tNetinputDev.DeviceInit();
while (1)
{
ret = g_tNetinputDev.GetInputEvent(&event);
if (ret) {
printf("GetInputEvent err!\n");
return -1;
}
else
{
printf("Type : %d\n", event.iType);
printf("str : %s\n", event.str);
}
}
return 0;
}
#endif
注意:需要把触摸屏里面的main函数注释掉
Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += touchscreen.o
obj-y += netinput.o
2.client.c
这里充当客户端,用来发送数据
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
int iSendLen;
int iAddrLen;
if (argc != 3)
{
printf("Usage:\n");
printf("%s <server_ip> <str>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
#if 0
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
#endif
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
close(iSocketClient);
return 0;
}
arm-buildroot-linux-gnueabihf-gcc -o client unittest/client.c
make
cp test ~/nfs_rootfs/
[root@100ask:~]# cd /mnt/
3.上机测试
六、 输入系统的框架
如果只有一个输入设备,那么我们只需要写一个main函数即可,但是我们需要好几个设备输入,那么要想统一管理,那么就需要设计一个输入管理系统进行管理选择到底哪个设备进行输入。
在输入管理器中我们需要初始化触摸屏,并且为触摸屏提供一个线程,初始化网络设备,为网络设备提供一个线程,上层应用程序提供一些函数就可以方便的得到各类的输入事件,线程的创建线程的管理都由输入管理器进行设置。
1.input_manager.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
static PInputDevice g_InputDevs = NULL;
void RegisterInputDevice(PInputDevice ptInputDev)
{
ptInputDev->ptNext = g_InputDevs;
g_InputDevs = ptInputDev;
}
/* */
void InputInit(void)
{
/* regiseter touchscreen */
extern void TouchscreenRegister(void);
TouchscreenRegister();
/* regiseter netinput */
extern void NetInputRegister(void);
NetInputRegister();
}
static void *input_recv_thread_func (void *data)
{
PInputDevice tInputDev = (PInputDevice)data;
InputEvent tEvent;
int ret;
while (1)
{
/* 读数据 */
ret = tInputDev->GetInputEvent(&tEvent);
if (!ret)
{
/* 保存数据 */
/* 唤醒等待数据的线程 */
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}
void IntpuDeviceInit(void)
{
int ret;
pthread_t tid;
/* for each inputdevice, init, pthread_create */
PInputDevice ptTmp = g_InputDevs;
while (ptTmp)
{
/* init device */
ret = ptTmp->DeviceInit();
/* pthread create */
if (!ret)
{
ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
}
ptTmp= ptTmp->ptNext;
}
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
/* 无数据则休眠 */
/* 返回数据 */
}
第7行:这一层要接受下一层传输上来的注册信息 ,所以要定义一个 g_InputDevs的链表头
void RegisterInputDevice(PInputDevice ptInputDev)
第9~13行:提供一个注册函数
void InputInit(void)
第16~25行:向上提供一个InputInit(void) 函数
第19、20行:注册触摸屏设备
第23、24行:注册网络设备
注意:其中extern是外部函数的意思
void IntpuDeviceInit(void)
第54~74行:对于每个设备初始化它,并且创建线程
int GetInputEvent(PT_InputEvent ptInputEvent)
第76行:最重要的一个函数 ,最上层的代码只要调用这个函数就可以得到这些设备的数据
怎么避免数据丢失 ?
比如触摸屏,它会一下子上报很多数据
对于网络输入,也有可能同时又多个client发来数据
所以,不能使用单一的变量来保存数据,而是使用一个数组来保存数据
使用“环形缓冲区”