前言
最近看了 电子量产工具 这个项目,本专栏是对该项目的一个总结。
对于输入系统,这里只介绍 触摸屏线程 和 网络线程。
一、输入系统分析
在大纲的输入管理器下有 三种输入方式:触摸屏线程,网络线程,标准输入线程。
为什么要使用多线程呢?
在单线程程序中,如果某个任务需要花费很长时间,会导致整个程序进入阻塞状态,用户体验不佳。使用多线程可以将这类耗时任务放在后台线程中执行,保持前台线程的响应性,提升用户体验,提高并发性和效率。
底层的触摸屏线程,网络线程标,标准输入线程 与 板子直接交互,负责处理数据和逻辑。
输入管理器 向下负责管理各种输入设备,向上提供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 文件。