Linux基础项目开发day2:量产工具——输入系统
文章目录
- 前言
- 一、数据结构抽象
- 1、数据本身
- 2、设备本身
- 3、input_manager.h
- 二、触摸屏编程
- 1、touchscreen.c
- 三、触摸屏单元测试
- 1、touchscreen.c
- 2、上机测试
- 四、网络编程
- netiput.c
- 五、网络单元测试
- 1、netiput.c
- 2、client.c
- 3、上机测试
- 六、输入系统的框架
- 1、框架思路
- 2、input_,manager.c
- 3、环形缓冲区
- 4、input_,manager.c加入环形Buffer操作函数
- 七、输入管理单元测试
- 1、input_test.c
- 2、input下的Makefile
- 3、uinttest下的Makefile
- 4、上板测试
前言
前面我们实现了显示系统,现在我们来学习输入系统,类似的,都需要我们来进行学习搭建!具体分为三层:
一、数据结构抽象
对于每一个设备,每一个模块,都用结构体来比表示他,以后就可以很方便的去替换这些模块,所以我们需要抽象出两个结构体,分别是:1、数据本身,2、设备本身。
输入来源:1、网络数据输入 2、点击时间输入(触摸屏)
1、数据本身
iType——判断是什么数据
INPUT_TYPE_TOUCH——触摸屏事件
INPUT_TYPE_NET——网络输入事件
触摸屏数据:
int iX;、int iY: 判断具体坐标
int iPressure: 压力值
网络数据:
char str[1024]: 网络数据
2、设备本身
*name: 设备名字
(*GetInputEvent)(PInputEvent ptInputEvent):上层代码通过这个函数获得数据,返回值判断是否成功,如果成功结果保存至PInputEvent ptInputEvent中
(*DeviceInit)(void):提供初始化函数,比如打开设备节点等
(*DeviceExit)(void):提供退出函数
InputDevice *ptNext;链表所需要
3、input_manager.h
#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H
#include <sys/time.h>
#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;
#endif
二、触摸屏编程
1、touchscreen.c
#include <input_manager.h>
#include <tslib.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;
//读取成功,返回0
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,
};
三、触摸屏单元测试
直接在touchscreen.c添加一个主函数进行测试!
1、touchscreen.c
#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
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、上机测试
上机效果:
四、网络编程
对网络输入构造出同触摸屏编程中一样的结构体!
netiput.c
关于网络编程API的学习也可以看看我写的一个文章:网络编程(TCP&UDP)
#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;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iAddrLen = sizeof(struct sockaddr);
/* 获取数据 */
iRecvLen = recvfrom(g_iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';//在接受缓冲区尾端添加字符串结束符
//填充输入事件结构体
ptInputEvent->iType = INPUT_TYPE_NET;
gettimeofday(&ptInputEvent->tTime, NULL);
strncpy(ptInputEvent->str, ucRecvBuf, 1000);
ptInputEvent->str[999] = '\0';
return 0;
}
else
return -1;
}
/* 用于初始网络输入设备 */
static int NetinputDeviceInit(void)
{
struct sockaddr_in tSocketServerAddr;//服务地址结构体
int iRet;//返回值
int iClientNum = -1;
/* 创建一个UDP套接字 */
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)
{
RegisterInputDevice(&g_tNetinputDev);
/*void RegisterInputDevice(PInputDevice ptInputDev)
*{
* ptInputDev->ptNext = g_InputDevs;
* g_InputDevs = ptInputDev;
*}
*/
}
五、网络单元测试
1、netiput.c
这里直接在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;
}
3、上机测试
分别编译好后复制到板子上
六、输入系统的框架
1、框架思路
1、 输入管理器inputmanager实现函数:
①InputInit
②InputDeviceInit
③GetInputEvent
2、输入管理器支持多个设备,那怎么将他们连起来呢?
InputInit函数创建一个链表,链表指向网络输入设备本身和触摸屏设备本身,通过链表可以找到所有的输入设备!
3.对于每一个设备都可以调用里面的 GetInputEvent 来获得数据,如果想同时获得多个设备的输入数据的话,那么该怎么获得?
调用InputDeviceInit从链表里面把每一个设备取出来,调用里面的DeviceInit 初始化,并且为每一个输入设备创造一个线程thread,线程不断调用设备里面的GetInputEvent 等待数据,一旦得到数据,就可以将获得的数据放到某个buffer
4.上层的应用程序调用GetInputEvent 时候就会去某个buffer里面查看是否有数据,有数据则返回,没数据则休眠
2、input_,manager.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <input_manager.h>
/* 定义输入设备链表头指针 */
static PInputDevice g_InputDevs = NULL;
/* 关于线程定义的变量 */
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
/* 注册输入设备,将其加入链表中 */
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 ptInputDev = (PInputDevice)data;
InputEvent tEvent;
int ret;
while (1)
{
/* 读数据 */
ret = ptInputDev->GetInputEvent(&tEvent);
if (!ret)
{
/* 保存数据 */
pthread_mutex_lock(&g_tMutex);
PutInputEventToBuffer(&tEvent);
/* 唤醒等待数据的线程 */
pthread_cond_signal(&g_tConVar); /* 通知接收线程 */
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)
{
/* 初始化设备 */
ret = ptTmp->DeviceInit();
/* 创建线程 */
if (!ret)
{
ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
}
ptTmp= ptTmp->ptNext;
}
}
/* 获取输入事件 */
int GetInputEvent(PInputEvent ptInputEvent)
{
InputEvent tEvent;
int ret;
/* 无数据则休眠 */
pthread_mutex_lock(&g_tMutex);
if (GetInputEventFromBuffer(&tEvent))//读取数据成功(有数据可读)
{
*ptInputEvent = tEvent;//保存数据
pthread_mutex_unlock(&g_tMutex);
return 0;
}
else//读取数据失败(没数据可读)
{
/* 休眠等待 */
pthread_cond_wait(&g_tConVar, &g_tMutex);
if (GetInputEventFromBuffer(&tEvent))//有数据杯唤醒之后,读取数据
{
*ptInputEvent = tEvent;//保存数据
ret = 0;
}
else
{
ret = -1;
}
pthread_mutex_unlock(&g_tMutex);
}
return ret;
}
怎么避免数据丢失?
比如触摸屏,它一下子会上报很多数据,对于网络输入,也有可能会一次性有很多客户端发来数据,所以不能使用变量来保存数据,要使用一个“唤醒缓冲区”
3、环形缓冲区
环形缓冲去其实也是一个数组,只不过是一个“高级”数据罢了!
例:char buf[5];
这里的满是指还有一个位置可以填!
4、input_,manager.c加入环形Buffer操作函数
#include <pthread.h> /* 包含线程相关函数和数据结构头文件 */
#include <stdio.h> /* 包含标准输入输出函数头文件 */
#include <unistd.h> /* 包含UNIX标准函数头文件 */
#include <semaphore.h> /* 包含信号量相关函数和数据结构头文件 */
#include <string.h> /* 包含字符串处理函数头文件 */
#include <input_manager.h> /* 包含输入管理器相关函数和数据结构头文件 */
/* 全局输入设备链表头指针 */
static PInputDevice g_InputDevs = NULL;
/* 互斥锁和条件变量,用于线程同步 */
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
/* 环形缓冲区相关定义 */
#define BUFFER_LEN 20
static int g_iRead = 0;
static int g_iWrite = 0;
static InputEvent g_atInputEvents[BUFFER_LEN];
/* 环形缓冲区操作函数 */
/* 判断唤醒缓冲区是否为满 */
//满了返回1,没满返回0
static int isInputBufferFull(void)
{
return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN));
}
/* 判断唤醒缓冲区是否为空 */
//空了返回1,没空返回0
static int isInputBufferEmpty(void)
{
return (g_iRead == g_iWrite);
}
//储存输入事件到环形缓冲区里面
static void PutInputEventToBuffer(PInputEvent ptInputEvent)
{
if (!isInputBufferFull())//数组不满,就储存
{
g_atInputEvents[g_iWrite] = *ptInputEvent;
g_iWrite = (g_iWrite + 1) % BUFFER_LEN;
}
}
//在环形缓冲区里面读取输入事件
static int GetInputEventFromBuffer(PInputEvent ptInputEvent)
{
if (!isInputBufferEmpty())//数组不空就读取
{
*ptInputEvent = g_atInputEvents[g_iRead];
g_iRead = (g_iRead + 1) % BUFFER_LEN;
return 1;
}
else
{
return 0;
}
}
/* 注册输入设备函数 */
void RegisterInputDevice(PInputDevice ptInputDev)
{
ptInputDev->ptNext = g_InputDevs;
g_InputDevs = ptInputDev;
}
/* 初始化输入系统 */
void InputInit(void)
{
/* 注册触摸屏设备 */
extern void TouchscreenRegister(void);
TouchscreenRegister();
/* 注册网络输入设备 */
extern void NetInputRegister(void);
NetInputRegister();
}
/* 输入接收线程函数 */
static void *input_recv_thread_func(void *data)
{
PInputDevice ptInputDev = (PInputDevice)data;
InputEvent tEvent;
int ret;
while (1)
{
/* 从输入设备读取数据 */
ret = ptInputDev->GetInputEvent(&tEvent);
if (!ret)
{
/* 保存数据到环形缓冲区 */
pthread_mutex_lock(&g_tMutex);
PutInputEventToBuffer(&tEvent);
/* 唤醒等待数据的线程 */
pthread_cond_signal(&g_tConVar);
pthread_mutex_unlock(&g_tMutex);
}
}
return NULL;
}
/* 初始化输入设备 */
void IntpuDeviceInit(void)
{
int ret;
pthread_t tid;
/* 遍历所有输入设备,进行初始化和创建接收线程 */
PInputDevice ptTmp = g_InputDevs;
while (ptTmp)
{
/* 初始化设备 */
ret = ptTmp->DeviceInit();
/* 创建接收线程 */
if (!ret)
{
ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);
}
ptTmp = ptTmp->ptNext;
}
}
/* 获取输入事件 */
int GetInputEvent(PInputEvent ptInputEvent)
{
InputEvent tEvent;
int ret;
/* 无数据则休眠 */
pthread_mutex_lock(&g_tMutex);
if (GetInputEventFromBuffer(&tEvent))
{
*ptInputEvent = tEvent;
pthread_mutex_unlock(&g_tMutex);
return 0;
}
else
{
/* 休眠等待 */
pthread_cond_wait(&g_tConVar, &g_tMutex);
if (GetInputEventFromBuffer(&tEvent))
{
*ptInputEvent = tEvent;
ret = 0;
}
else
{
ret = -1;
}
pthread_mutex_unlock(&g_tMutex);
}
return ret;
}
七、输入管理单元测试
1、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> // IO控制设备的函数定义
#include <input_manager.h> // 自定义输入管理的头文件
int main(int argc, char **argv)
{
int ret; // 用于函数返回值
InputEvent event; // 定义一个输入事件的结构体变量
InputInit(); // 初始化输入系统
IntpuDeviceInit(); // 初始化输入设备,注意这里应该是 InputDeviceInit
while (1) // 主循环
{
// 打印当前文件名、函数名和行号
printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = GetInputEvent(&event); // 从输入设备获取一个事件
// 再次打印,并显示GetInputEvent函数的返回值
printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret);
if (ret) { // 如果返回值不是0,表示获取事件时出错
printf("GetInputEvent err!\n");
return -1; // 出错返回-1
}
else // 如果成功获取事件
{
// 打印事件类型
printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType );
if (event.iType == INPUT_TYPE_TOUCH) // 如果是触摸屏事件
{
// 打印触摸屏事件的详细信息
printf("Type : %d\n", event.iType);
printf("iX : %d\n", event.iX);
printf("iY : %d\n", event.iY);
printf("iPressure : %d\n", event.iPressure);
}
else if (event.iType == INPUT_TYPE_NET) // 如果是网络事件
{
// 打印网络事件的详细信息
printf("Type : %d\n", event.iType);
printf("str : %s\n", event.str);
}
}
}
return 0; // 正常退出程序
}
2、input下的Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += touchscreen.o
obj-y += netinput.o
obj-y += input_manager.o
3、uinttest下的Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o :=
#obj-y += disp_test.o
obj-y += input_test.o
4、上板测试