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