@TOC

前言

最近看了 电子量产工具 这个项目,本专栏是对该项目的一个总结。

对于输入系统,这里只介绍 触摸屏线程 和 网络线程。


一、输入系统分析

【电子量产工具】2.输入系统_触摸屏

在大纲的输入管理器下有 三种输入方式:触摸屏线程,网络线程,标准输入线程

为什么要使用多线程呢? 在单线程程序中,如果某个任务需要花费很长时间,会导致整个程序进入阻塞状态,用户体验不佳。使用多线程可以将这类耗时任务放在后台线程中执行,保持前台线程的响应性,提升用户体验,提高并发性和效率。

底层的触摸屏线程,网络线程标,标准输入线程 与 板子直接交互,负责处理数据和逻辑。输入管理器 向下负责管理各种输入设备,向上提供APP 所需的各种函数,起承上启下作用。

我们需要写出底层代码,由中间层 调用底层代码 供 上层APP直接使用。

二、封装输入结构体

  1. InputDevice 结构体 模块化 输入设备。后面会根据 name 寻找目标输入设备。
typedef struct InputDevice {
	char *name;
	int (*GetInputEvent)(PInputEvent ptInputEvent);						//获取输入事件
	int (*DeviceInit)(void);											//输入设备初始化
	int (*DeviceExit)(void);
	struct InputDevice *ptNext;										//指针,用于连接链表
}InputDevice, *PInputDevice;
  1. InputEvent 结构体 模块化 输入事件。
typedef struct InputEvent {
	struct timeval	tTime;											//触发的时间
	int iType;														//触发事件的类型
	int iX;													//对于触摸屏事件的触摸点x值
	int iY;													//触摸屏事件的触摸点y值
	int iPressure;											//触摸屏事件的触摸点压力值
	char str[1024];											//网络输入事件的输入字符串
}InputEvent, *PInputEvent;

三、底层 touchscreen

  1. 实现 InputEvent 结构体。
  2. 触摸屏事件 ,需要使用 tslib 库tslib 是一个触摸屏的开源库,可以使用它来访问触摸屏设备。 ts_setup 函数用于在 tslib 中 初始化触摸屏设备 并 设置相关参数,包括 打开触摸屏设备、校准触摸屏、配置参数和注册触摸事件回调函数等。

  1. 获取 触摸屏 事件的 输入数据。 ts_read:用于从触摸屏设备中读取触摸事件的数据,并存储到 samp 结构体中。 struct ts_sample:用于存储获取到的触摸屏输入数据。它通常包含一些字段,表示触摸点的位置、时间戳和其他相关信息。

四、底层 netinput

netinputtouchscreen 一样 都需要实现 InputEvent 结构体,初始化输入设备,获取输入事件数据。

  1. 涉及网络通信,要了解一些基本知识:
  • 网络传输中的2个对象:serverclient
  • UDP 网络通信大概交互图:
  1. 网络输入初始化。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;
}
  1. 接收输入事件。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;
}

五、显示管理层

  1. 将 触摸屏输入 和 网络输入 注册进入链表。头添加的方式,添如链表。
  2. 对于环形缓冲区这里就不多说了,不懂的可以参考我之前的文章:环形缓冲区

如果我们频繁快速的持续向计算机输入数据,计算机可能执行某个进程不能及时的执行输入的数据,导致数据丢失。这时,我们可以将要输入的数据放入环形缓冲区内,计算机就不会造成数据丢失。

#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;
	}
}

  1. 初始化设备并创建线程。环形缓冲区有数据则唤醒线程。

这里使用了 互斥锁操作,在调用 pthread_cond_wait 前,必须先获取互斥锁,即使用 pthread_mutex_lock 函数。这确保了在等待条件期间的正确 同步 。

pthread_cond_wait 函数会在等待时自动解锁并将线程置于等待状态。当满足特定条件时,该线程会被唤醒并重新获得锁。使用 pthread_cond_signal 函数来 唤醒 等待线程。

【电子量产工具】2.输入系统_数据_02

六、测试程序

  1. 触摸屏输入事件测试。只需要在 main 函数里调用 输入管理层的封装好的函数即可。
  2. 网络输入事件测试。还需要编写一个 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;	
}

测试效果

触摸屏事件:

【电子量产工具】2.输入系统_触摸屏_03

网络输入事件:

【电子量产工具】2.输入系统_初始化_04


总结

输入系统 的 触摸屏事件 设计 tslib 库,所以在编译时,一定要链接库。网络输入事件的 通信 使用 UDP 会比较容易,那些函数 以及 通信流程 要了解。网络输入事件 包括 客户端服务端 间的通信,要编写 client 文件。