前言
最近看了 电子量产工具 这个项目,本专栏是对该项目的一个总结。
对于输入系统,这里只介绍 触摸屏线程 和 网络线程。
一、输入系统分析
在大纲的输入管理器下有 三种输入方式:触摸屏线程,网络线程,标准输入线程。
为什么要使用多线程呢?
在单线程程序中,如果某个任务需要花费很长时间,会导致整个程序进入阻塞状态,用户体验不佳。使用多线程可以将这类耗时任务放在后台线程中执行,保持前台线程的响应性,提升用户体验,提高并发性和效率。
底层的触摸屏线程,网络线程标,标准输入线程 与 板子直接交互,负责处理数据和逻辑。
输入管理器 向下负责管理各种输入设备,向上提供APP 所需的各种函数,起承上启下作用。
我们需要写出底层代码,由中间层 调用底层代码 供 上层APP直接使用。
二、封装输入结构体
- 用 InputDevice 结构体 模块化 输入设备。
后面会根据name
寻找目标输入设备。
typedef struct InputDevice {
char *name;
int (*GetInputEvent)(PInputEvent ptInputEvent); //获取输入事件
int (*DeviceInit)(void); //输入设备初始化
int (*DeviceExit)(void);
struct InputDevice *ptNext; //指针,用于连接链表
}InputDevice, *PInputDevice;
- InputEvent 结构体 模块化 输入事件。
typedef struct InputEvent {
struct timeval tTime; //触发的时间
int iType; //触发事件的类型
int iX; //对于触摸屏事件的触摸点x值
int iY; //触摸屏事件的触摸点y值
int iPressure; //触摸屏事件的触摸点压力值
char str[1024]; //网络输入事件的输入字符串
}InputEvent, *PInputEvent;
三、底层 touchscreen
- 实现 InputEvent 结构体。
- 触摸屏事件 ,需要使用 tslib 库。
tslib 是一个触摸屏的开源库,可以使用它来访问触摸屏设备。
ts_setup 函数用于在 tslib 中 初始化触摸屏设备 并 设置相关参数,包括 打开触摸屏设备、校准触摸屏、配置参数和注册触摸事件回调函数等。
- 获取 触摸屏 事件的 输入数据。
ts_read:用于从触摸屏设备中读取触摸事件的数据,并存储到 samp 结构体中。
struct ts_sample:用于存储获取到的触摸屏输入数据。它通常包含一些字段,表示触摸点的位置、时间戳和其他相关信息。
四、底层 netinput
netinput 和 touchscreen 一样 都需要实现 InputEvent 结构体,初始化输入设备,获取输入事件数据。
- 涉及网络通信,要了解一些基本知识:
- 网络传输中的2个对象:server 和 client。
- UDP 网络通信大概交互图:
- 网络输入初始化。
AF_INET
是针对 Internet 的通讯协族,可以允许远程通信使用。
SOCK_DGRAM
表明用的是 UDP 协议
static int NetinputDeviceInit(void)
{
struct sockaddr_in tSocketServerAddr;
int iRet;
/* socket 函数创建一个套接字,成功时返回文件描述符 */
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;
}
- 接收输入事件。
recvfrom
通常用于无连接套接字, 可以获得发送者的地址。
gettimeofday
函数,它是一个 C 标准库中的函数,主要用于获取当前的系统时间
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{
struct sockaddr_in tSocketClientAddr;
int iRecvLen;
char aRecvBuf[1000];
unsigned 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;
}
五、显示管理层
- 将 触摸屏输入 和 网络输入 注册进入链表。头添加的方式,添如链表。
- 对于环形缓冲区这里就不多说了,不懂的可以参考我之前的文章:环形缓冲区
如果我们频繁快速的持续向计算机输入数据,计算机可能执行某个进程不能及时的执行输入的数据,导致数据丢失。这时,我们可以将要输入的数据放入环形缓冲区内,计算机就不会造成数据丢失。
#define BUFFER_LEN 20 //缓冲区数组长度
static int g_iRead = 0; //读指针
static int g_iWrite = 0; //写指针
static InputEvent g_atInputEvents[BUFFER_LEN]; //缓冲区数组
/* 判断缓冲区是否满 */
static int isInputBufferFull(void)
{
return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN));
}
/* 判断缓冲区是否空 */
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;
}
}
- 初始化设备并创建线程。环形缓冲区有数据则唤醒线程。
这里使用了 互斥锁操作,在调用 pthread_cond_wait 前,必须先获取互斥锁,即使用 pthread_mutex_lock 函数。这确保了在等待条件期间的正确 同步 。
pthread_cond_wait 函数会在等待时自动解锁并将线程置于等待状态。当满足特定条件时,该线程会被唤醒并重新获得锁。
使用 pthread_cond_signal 函数来 唤醒 等待线程。
六、测试程序
-
触摸屏输入事件测试。
只需要在 main 函数里调用 输入管理层的封装好的函数即可。 -
网络输入事件测试。
还需要编写一个 client.c 文件,进行客户端 和 服务端的通信。
int main(int argc, char **argv)
{
int ret;
InputEvent event;
InputInit();
IntpuDeviceInit();
while (1)
{
printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = GetInputEvent(&event);
printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret);
if (ret) {
printf("GetInputEvent err!\n");
return -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;
}
测试效果
触摸屏事件:
网络输入事件:
总结
输入系统 的 触摸屏事件 设计 tslib 库,所以在编译时,一定要链接库。
网络输入事件的 通信 使用 UDP 会比较容易,那些函数 以及 通信流程 要了解。
网络输入事件 包括 客户端 和 服务端 间的通信,要编写 client 文件。