09.轮询、select、多线程--电子书输入模块+滑动切屏

本文介绍了tslib库基础用法,包括初始化代码和触摸屏函数。重点阐述三种输入监听方式:轮询、select和多线程,分析其优缺点及知识点,如轮询的非阻塞设置、select函数参数、多线程的创建与唤醒。还提及添加input模块、注册设备及滑动翻页处理代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.tslib库基础用法

1.初始化代码

#include <stdlib.h> //不确定,如果只加下面的不行,就加上这个
#include <tslib.h>

char *pcTSName = NULL;
static struct tsdev *g_tTSDev;				//触摸屏设备的结构体,类似于文件句柄fd
if ((pcTSName = getenv("TSLIB_TSDEVICE")) != NULL )  //获取设备名,获取成功了就用设备名打开
{
	g_tTSDev = ts_open(pcTSName, 0); 		//0是以阻塞方式打开,1是非阻塞
}
else
{
	g_tTSDev = ts_open("/dev/event0", 1);  //获取不成功就直接打开这个设备,这个设备一般就是触摸屏的
}

if (ts_config(g_tTSDev)) {					//配置触摸屏
	DBG_PRINTF("ts_config error!\n");
	return -1;
}

2.使用触摸屏的函数
触摸屏按下,会发生一次事件,得到一次数据;不松开,会按照一定频率得到数据;松开时,发生一次事件,得到一次数据。

struct ts_sample tSamp;					//放触摸屏数据的结构体
static struct timeval tPreTime;			//时间的结构体,触摸屏会返回一个时间值
iRet = ts_read(g_tTSDev, &tSamp, 1);	//上面设置的是阻塞,所以这里如果未获取到数据,就休眠

if (iRet == 1)							//获取到数据
{
	tSamp.pressure == 1					//触摸屏按下
	tSamp.pressure == 0 				//触摸屏松开
	tSamp.x , tSamp.y					//X,Y坐标
}

1.三种方式实现方法(*重要,中心思想

1.轮询方式
轮询方式是循环检查各个输入模块,看哪个输入模块有数据写入,然后反馈到上层函数。
这种方法优点是思想简单,缺点是占用CPU资源,需要一直在while中循环。
2.select方式
将需要监听的文件句柄(管道、文件、SOCKET等)放到一个类似数组里,然后通过select函数进行监听,当有文件可读或者可写或者发生事件时,返回。
这种方法的优点是不占用特别多的CPU资源,将监听的文件交予内核去监听,当有可读可写的文件时,唤醒程序。
3.多线程
多线程是把需要监听的文件分发到不同的线程,主线程进入睡眠,当子线程发现有文件可读(可写)时,唤醒主进程进行处理。
总结:
轮询方式就是大家都不阻塞,一个一个查询有没有数据输入。
select方式是大家阻塞不阻塞都行,我把所有设备都放到select里面,selecte阻塞,由系统去帮我监听有无数据输入,不占用系统资源。
多线程方式是,主线程进入睡眠,子线程去做监听的事情,大家的输入都阻塞等待,因为线程是单独调度的,所以大家都阻塞也不互相影响。

2.添加input模块,以及注册设备

这个模块类似与font_manager、encoding_manager等
input_manager.h:

/*定义一些宏,表示啥设备,以及具体的操作(上下翻页等)*/
#define INPUT_TYPE_STDIN        0		
#define INPUT_TYPE_TOUCHSCREEN  1

#define INPUT_VALUE_UP          0   
#define INPUT_VALUE_DOWN        1
#define INPUT_VALUE_EXIT        2
#define INPUT_VALUE_UNKNOWN     -1
/*事件结构体*/
typedef struct InputEvent {
	struct timeval tTime;	//事件发生时间
	int iType; 				//谁发生的 stdin,tochscreen
	int iVal;  				//发生的什么事件,上下翻页、退出等
}T_InputEvent, *PT_InputEvent;
/*输入设备结构体*/
typedef struct InputOpr {
	char *name;				//名字
	pthread_t tTreadID;		//线程ID号(线程那里用得到)
	int (*DeviceInit)(void);	//设备初始化函数
	int (*DeviceExit)(void);	//设备退出函数
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);	//设备获取事件的函数
	struct InputOpr *ptNext;	//指向下一个设备结构体
}T_InputOpr, *PT_InputOpr;

input_manager.c

int RegisterInputOpr(PT_InputOpr ptInputOpr)	//注册设备函数
int AllInputDevicesInit(void)					//给注册过的设备一个一个初始化
												//线程方法里,要给每一个设备添加一个线程
												//select方法里,要在这里把每一个设备描述符加到fd_set集里
int GetInputEvent(PT_InputEvent ptInputEvent)	//最重要的函数,是main函数的接口
												//main函数调用时给一个地址,这个函数获得T_InputEvent结构体通过指针传回给main
int InputInit(void)								//初始化

3.三种方式的知识点

1.轮询方式
轮询方式需要每一种输入设备都不能阻塞,所以主要涉及到的是Stdin(键盘输入)和touchscreen(触摸屏)的非阻塞设置.
标准输入非阻塞设置代码:

#include <termios.h>
#include <unistd.h>
#include <stdio.h>
/*非阻塞方式初始化*/
void Init()
{
	struct termios tTTYState;
	//get the terminal state
    tcgetattr(STDIN_FILENO, &tTTYState);
    //turn off canonical mode		
    tTTYState.c_lflag &= ~ICANON;
    //minimum of number input read. 最少几个数据时开始读
    tTTYState.c_cc[VMIN] = 1;    // 有一个数据时就立刻返回
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
}
/*退出阻塞模式*/
void Exit()
{
	struct termios tTTYState;
    //get the terminal state
    tcgetattr(STDIN_FILENO, &tTTYState);	//STDIN_FILENO是标准输入的fd
    //turn on canonical mode
    tTTYState.c_lflag |= ICANON;
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);	
}

如上,再执行 fgetchar(stdin)时,没有数据写入就直接跳过,不进入阻塞
2.select函数详解

 #include <sys/select.h>
 //原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

struct fd_set set;	//一个监听文件句柄集合

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

void FD_CLR(int fd, fd_set *set);		//把fd从集合中去掉
int  FD_ISSET(int fd, fd_set *set);		//判断句柄集中的fd是否发生设置的事件(读、写、事件)
void FD_SET(int fd, fd_set *set);		//把fd加入集合中
void FD_ZERO(fd_set *set);				//对一个集合清零

nfds: 是检测的文件句柄最大值加1,如监听fd=0,1,5,8,那么nfds=9
readfds:需要监听写事件的句柄集,传入传出参数
writefds:需要监听读事件的句柄集,传入传出参数
exceptfds:需要监听发生事件的句柄集,传入传出参数
timeout:设置的等待时间,可通过一个struct timeval结构体设置每次等待的时间。也可设置成NULL,意为阻塞等待

/*这两句在设备初始化的时候来设置*/
FD_ZERO(&g_tRFds);
FD_SET(ptTmp->iFd, &g_tRFds);
/*以下一句在GetInputEvent函数中*/
iRet = select(g_iMaxFd, &tRFds, NULL, NULL, NULL);	//阻塞等待事件
if (iRet > 0)
{
	while (ptTmp)
	{
		if (FD_ISSET(ptTmp->iFd, &tRFds))	//发生事件后挨个查看是哪个发生了
		{
			if(0 == ptTmp->GetInputEvent(ptInputEvent))
			{
				return 0;
			}
		}
		ptTmp = ptTmp->ptNext;
	}
}

3.多线程编程
创建子进程

int pthread_create(pthread_t *restrict thread,			//返回一个指针,指向pthread_t结构体,pthread_t类似与线程号
              const pthread_attr_t *restrict attr,		//设置一些属性,NULL表示默认属性
              void *(*start_routine)(void*), 			//设置一个函数,就是子线程执行的函数
              void *restrict arg);						//给子线程的函数传入一个指针
	
//在初始化每个input设备时,顺便给每个input的设备创建一个子线程去阻塞等待数据,传入的指针就是自己这个input设备等待数据的函数指针
pthread_create(&ptTmp->tTreadID, NULL, InputEventTreadFunction, ptTmp->GetInputEvent);

进程间的锁

static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER; //创建一个锁
pthread_mutex_lock(&g_tMutex);		//对全局数据操作前上锁
pthread_mutex_unlock(&g_tMutex);	//操作完毕后解锁

唤醒机制

static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;  //创建一个用于识别的东西,大概是这么个玩意儿吧
pthread_cond_signal(&g_tConVar);			//唤醒主线程
pthread_cond_wait(&g_tConVar, &g_tMutex);	//进入睡眠

看一下子线程的主程序

static void *InputEventTreadFunction(void *pVoid)
{
	T_InputEvent tInputEvent;		//这个是 表示事件的结构体,里面包含了时间、什么事件(上、下翻页等)、啥设备触发 等信息
	/* 定义函数指针 */
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);	//定义一个函数指针,这个指针是返回int型,入参是PT_InputEvent 
	GetInputEvent = (int (*)(PT_InputEvent))pVoid;		//函数指针指向传入的参数,传入的就是各设备等待数据的函数,此时应该都设置成阻塞方式,因为是子线程在获得数据,所以就可以阻塞了,也必须阻塞
	while (1)
	{
		if(0 == GetInputEvent(&tInputEvent))			//等待获取数据
		{
			/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
			/* 访问临界资源前,先获得互斥量 */
			pthread_mutex_lock(&g_tMutex);		//先加锁,阻塞等待获得锁
			g_tInputEvent = tInputEvent;		//把这边获得的 T_InputEvent 事件结构体给全局变量

			/*  唤醒主线程 */
			pthread_cond_signal(&g_tConVar);	//唤醒主线程,主线程睡眠在GetInputEvent函数里
												//这个函数就做了一件事,就是上锁拿到这个全局变量,然后返回给main函数处理
			/* 释放互斥量 */
			pthread_mutex_unlock(&g_tMutex);
		}
	}
	return NULL;
}

4.滑动翻页处理代码

while (1)
{
	struct ts_sample tSamp;
	struct ts_sample tSampPressed;
	struct ts_sample tSampReleased;
	
	int iRet;
	int bStart = 0;			//按下的标志位 0:未按下
	int iDelta;
		iRet = ts_read(g_tTSDev, &tSamp, 1); /* 阻塞 */
		if (iRet == 1)
		{
			if ((tSamp.pressure > 0) && (bStart == 0))	//如果还未按下,并且本次事件是按下,那么记下此时的点
			{
				/* 刚按下 */
				/* 记录刚开始压下的点 */
				tSampPressed = tSamp;
				bStart = 1;
			}
			
			if (tSamp.pressure <= 0)	//表示松开了
			{
				/* 松开 */
				tSampReleased = tSamp;
				/* 处理数据 */
				if (!bStart)			//如果还未按下,那么就说明本次数据出错了,返回
				{
					return -1;
				}
				else
				{
					iDelta = tSampReleased.x - tSampPressed.x;
					ptInputEvent->tTime = tSampReleased.tv;
					ptInputEvent->iType = INPUT_TYPE_TOUCHSCREEN;
					
					if (iDelta > giXres/5)
					{
						/* 翻到上一页 */
						ptInputEvent->iVal = INPUT_VALUE_UP;
					}
					else if (iDelta < 0 - giXres/5)
					{
						/* 翻到下一页 */
						ptInputEvent->iVal = INPUT_VALUE_DOWN;
					}
					else
					{
						ptInputEvent->iVal = INPUT_VALUE_UNKNOWN;
					}
					return 0;
				}
			}
		}
		else
		{
			return -1;
		}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值