一、輸入系統架構
假設使用者程式直接通路/dev/input/event0裝置節點,或者使用tslib通路裝置節點,資料的流程如下:
- APP發起讀操作,若無資料則休眠;
- 使用者操作裝置,硬體上産生中斷;
-
輸入系統驅動層對應的驅動程式進行中斷:
讀取到資料,轉換為标準的輸入事件,向核心層彙報。
所謂輸入事件就是一個“struct input_event”結構體。
-
核心層可以決定把輸入事件轉發給上面哪個handler來處理:
從handler的名字來看,它就是用來處輸入操作的。有多種handler,比如:evdev_handler、kbd_handler、joydev_handler等等。
最常用的是evdev_handler:它隻是把input_event結構體儲存在核心buffer等,APP來讀取時就原原本本地傳回。它支援多個APP同時通路輸入裝置,每個APP都可以獲得同一份輸入事件。
當APP正在等待資料時,evdev_handler會把它喚醒,這樣APP就可以傳回資料。
-
APP對輸入事件的處理:
APP獲得資料的方法有2種:直接通路裝置節點(比如/dev/input/event0,1,2,…),或者通過tslib、libinput這類庫來間接通路裝置節點。這些庫簡化了對資料的處理。
APP得到的輸入事件
輸入事件結構體
struct input_event {
struct timeval time; // 發生的時間
__u16 type; // 哪類事件
__u16 code; // 哪個事件
__s32 value; // 事件值
};
事件類型:type
EV_KEY表示按鍵類、EV_REL表示相對位移(比如滑鼠),EV_ABS表示絕對位置(比如觸摸屏)
/*
* Event types
*/
#define EV_SYN 0x00 // 同步事件
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
哪個具體事件:code
對于EV_KEY(按鍵)類事件,它表示鍵盤。鍵盤上有很多按鍵,比如數字鍵1、2、3,字母鍵A、B、C裡等。
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
對于觸摸屏,它提供的是絕對位置資訊,有X方向、Y方向,還有壓力值。是以code值有這些:
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
...........
事件值:value
對于按鍵,它的value可以是0(表示按鍵被松開)、1(表示按鍵被按下)、2(表示長按);
對于觸摸屏,它的value就是坐标值、壓力值。
若驅動程式上報完所有資料後,會上報一個同步事件表示資料上報完畢。
同步事件的type、code、value都是0
核心中表示的輸入裝置
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
// 支援哪類事件,支援哪些
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
..............................
}
二、常用指令
- 檢視裝置節點:
ls /dev/input/* -l
- 檢視裝置節點對應硬體資訊:
資訊對應核心中表示的輸入裝置資訊 上圖中“B: EV=b”用來表示該裝置支援哪類輸入事件。b的二進制是1011,bit0、1、3為1,表示該裝置支援0、1、3這三類事件,即EV_SYN、EV_KEY、EV_ABS。cat /proc/bus/input/devices
- 使用指令讀取資料:
hexdump /dev/input/event1
(我這裡event1表示觸摸屏)
在開發闆上執行讀取螢幕資料指令後,先會阻塞,點選或滑動觸摸屏後,會列印資訊。
三、擷取輸入裝置資訊
要想列印出struct input_id 中的資料即硬體本身資訊,根據linux源碼中evdev.c檔案中相關函數,可知ioctl的request要寫為EVIOCGID。
要想擷取硬體支援哪類事件(如相對位移事件,絕對位移事件),就要讀取輸入裝置的evbit,ioctl的request要寫為“EVIOCGBIT(0, size)”,size的大小可以由你決定:你想讀多少位元組就設定為多少。
#include <stdio.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*擷取輸入裝置資訊*/
/* ./get_input_info /dev/input/event0 */
int main(int argc,char **argv)
{
struct input_id id;
int fd;
int err;
int len;
int i;
int bit;
unsigned int evbit[2];
unsigned char byte;
// 支援事件的類型
char *ev_names[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV_PWR"
};
if(argc!=2){
printf("Usage:%s <dev>\n",argv[1]);
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd==-1){
printf("open error\n");
return -1;
}
err = ioctl(fd,EVIOCGID,&id);
if(err==0){
printf("bustype is 0x%x\n",id.bustype);
printf("vendor is 0x%x\n" ,id.vendor);
printf("product is 0x%x\n",id.product);
printf("version is 0x%x\n",id.version);
}
len = ioctl(fd,EVIOCGBIT(0, sizeof(evbit)),&evbit);
if(len>0 && len<=sizeof(evbit)){
printf("support ev type:");
for(i=0;i<len;i++){
byte = ((unsigned char*)evbit)[i];
for(bit=0;bit<8;bit++){
if(byte & (1<<bit)){
printf("%s ",ev_names[i*8+bit]);
}
}
}
printf("\n");
}
return 0;
}
四、APP通路硬體的四種方式
- 查詢方式
- 休眠-喚醒
- POLL和select方式
- 異步通知
在上面擷取輸入裝置的代碼基礎上實作完成APP通路硬體的程式設計
1.查詢方式和休眠-喚醒方式
1.1介紹
查詢方式:應用程式不停地去讀取硬體資訊,即使沒有硬體資訊發生
反映到程式上就是用open函數以不阻塞的方式打開裝置,觸摸屏沒有發生事件,列印err,觸摸屏發生滑動或點選等事件就列印相應資訊。
休眠-喚醒方式:當發生硬體資訊,應用程式才會通路硬體
反映到程式上就是用open函數以阻塞的方式(預設方式)打開裝置。
1.2代碼
#include <stdio.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
/*擷取輸入裝置資訊*/
/*查詢方式 休眠-喚醒方式*/
/* ./get_input_info /dev/input/event1 noblock*/
int main(int argc,char **argv)
{
struct input_id id;
int fd;
int err;
int len;
int i;
int bit;
unsigned int evbit[2];
unsigned char byte;
struct input_event event;
char *ev_names[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV_PWR"
};
if(argc<2){
printf("Usage:%s <dev> [noblock]\n",argv[0]);
return -1;
}
if(argc == 3 && !strcmp(argv[2],"noblock")){
fd = open(argv[1],O_RDWR|O_NONBLOCK);
}else{
fd = open(argv[1],O_RDWR);
}
if(fd==-1){
printf("open error\n");
return -1;
}
err = ioctl(fd,EVIOCGID,&id);
if(err==0){
printf("bustype is 0x%x\n",id.bustype);
printf("vendor is 0x%x\n" ,id.vendor);
printf("product is 0x%x\n",id.product);
printf("version is 0x%x\n",id.version);
}
len = ioctl(fd,EVIOCGBIT(0, sizeof(evbit)),&evbit);
if(len>0 && len<=sizeof(evbit)){
printf("support ev type:");
for(i=0;i<len;i++){
byte = ((unsigned char*)evbit)[i];
for(bit=0;bit<8;bit++){
if(byte & (1<<bit)){
printf("%s ",ev_names[i*8+bit]);
}
}
}
printf("\n");
}
while(1)
{
len = read(fd,&event,sizeof(event));
if(len == sizeof(event)){
printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}else{
sleep(3);
printf("err\n");
}
}
return 0;
}
// 查詢方式
./get_input_info /dev/input/event1 noblock
// 休眠-喚醒方式
./get_input_info /dev/input/event1
2.POLL方式及select方式
2.1 說明
APP不是直接調用read函數,而是先調用poll或select函數,這2個函數中可以傳入“逾時時間”。它們的作用是:如果驅動程式中有資料,則立刻傳回;否則就休眠。在休眠期間,如果有人操作了硬體,驅動程式獲得資料後就會把APP喚醒,導緻poll或select立刻傳回;如果在“逾時時間”内無人操作硬體,則時間到後poll或select函數也會傳回
poll/select函數可以監測多個檔案,可以監測多種事件:
事件類型 | 說明 |
---|---|
POLLIN | 有資料可讀 |
POLLRDNORM | 等同于POLLIN |
POLLRDBAND | Priority band data can be read,有優先級較較高的“band data”可讀 |
POLLPRI | 高優先級資料可讀 |
POLLOUT | 可以寫資料 |
POLLWRNORM | 等同于POLLOUT |
POLLWRBAND | Priority data may be written |
POLLERR | 發生了錯誤 |
POLLHUP | 挂起 |
POLLNVAL | 無效的請求,一般是fd未open |
2.2 poll函數
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
nfds:nfds_t類型的參數,用于标記數組fds中的結構體元素的總數量;
timeout:是poll函數調用阻塞的時間,機關:毫秒;
傳回值為0表示逾時
struct pollfd {
int fd; /檔案描述符/
short events;
};
2.3 poll方式代碼
#include <stdio.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
/*poll方式*/
/* ./input_read_poll /dev/input/event0 */
int main(int argc,char **argv)
{
struct input_id id;
int fd;
int err;
int len;
int i;
int bit;
int ret;
nfds_t nfds = 1;
unsigned int evbit[2];
unsigned char byte;
struct input_event event;
struct pollfd fds[1];
char *ev_names[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV_PWR"
};
if(argc!=2){
printf("Usage:%s <dev>\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR|O_NONBLOCK);
if(fd==-1){
printf("open error\n");
return -1;
}
err = ioctl(fd,EVIOCGID,&id);
if(err==0){
printf("bustype is 0x%x\n",id.bustype);
printf("vendor is 0x%x\n" ,id.vendor);
printf("product is 0x%x\n",id.product);
printf("version is 0x%x\n",id.version);
}
len = ioctl(fd,EVIOCGBIT(0, sizeof(evbit)),&evbit);
if(len>0 && len<=sizeof(evbit)){
printf("support ev type:");
for(i=0;i<len;i++){
byte = ((unsigned char*)evbit)[i];
for(bit=0;bit<8;bit++){
if(byte & (1<<bit)){
printf("%s ",ev_names[i*8+bit]);
}
}
}
printf("\n");
}
while(1)
{
fds[0].fd = fd;
// 監測的檔案事件是資料可讀
fds[0].events = POLLIN;
fds[0].revents = 0;
ret = poll(fds,nfds,5000);
if(ret>0)
{
if(fds[0].revents == POLLIN)
{
while(read(fd,&event,sizeof(event))==sizeof(event))
{
printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
}
}
else if(ret==0)
{
printf("time out\n");
}
else
{
printf("poll err\n");
}
}
return 0;
}
2.4 select說明
select 方式
POLL機制、SELECT機制是完全一樣的,隻是APP接口函數不一樣
int select(nfds, readfds, writefds, exceptfds, timeout)
nfds:select監視的檔案句柄數,視程序中打開的檔案數而定,一般設為你要監視各檔案中的最大檔案号加一。
readfds:select監視的可讀檔案句柄集合。
writefds: select監視的可寫檔案句柄集合。
exceptfds:select監視的異常檔案句柄集合。
timeout:本次select()的逾時結束時間
FD_ZERO(fd_set *fdset):清空fdset與所有檔案句柄的聯系。
FD_SET(int fd, fd_set *fdset):建立檔案句柄fd與fdset的聯系。
FD_CLR(int fd, fd_set *fdset):清除檔案句柄fd與fdset的聯系。
FD_ISSET(int fd, fd_set *fdset):檢查fdset聯系的檔案句柄fd是否
可讀寫,當>0表示可讀寫。
2.4 select方式代碼
#include <stdio.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
/*select方式*/
/* ./input_read_select /dev/input/event0 */
int main(int argc,char **argv)
{
struct input_id id;
int fd;
int err;
int len;
int i;
int bit;
int ret;
// 最大的檔案句柄數+1
int nfds = fd+1;
fd_set readfds[1] = {fd};
unsigned int evbit[2];
unsigned char byte;
struct input_event event;
struct timeval time;
char *ev_names[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV_PWR"
};
if(argc!=2){
printf("Usage:%s <dev>\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR|O_NONBLOCK);
if(fd==-1){
printf("open error\n");
return -1;
}
err = ioctl(fd,EVIOCGID,&id);
if(err==0){
printf("bustype is 0x%x\n",id.bustype);
printf("vendor is 0x%x\n" ,id.vendor);
printf("product is 0x%x\n",id.product);
printf("version is 0x%x\n",id.version);
}
len = ioctl(fd,EVIOCGBIT(0, sizeof(evbit)),&evbit);
if(len>0 && len<=sizeof(evbit)){
printf("support ev type:");
for(i=0;i<len;i++){
byte = ((unsigned char*)evbit)[i];
for(bit=0;bit<8;bit++){
if(byte & (1<<bit)){
printf("%s ",ev_names[i*8+bit]);
}
}
}
printf("\n");
}
while(1)
{
// 清零
FD_ZERO(readfds);
// 建立檔案句柄fd與readfds的聯系
FD_SET(fd, readfds);
// 設定逾時時間
time.tv_sec = 5;
time.tv_usec = 0;
ret = select(nfds,readfds,NULL,NULL,&time);
if(ret>0)
{
//再次确認fd有資料
if(FD_ISSET(fd, readfds))
{
memset(&event,0,sizeof(event));
while(read(fd,&event,sizeof(event))==sizeof(event))
{
printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
}
}
else if(ret==0)
{
printf("time out\n");
}
else
{
printf("select err\n");
}
}
return 0;
}
3.異步通信方式
3.1 說明
異步通知,就是APP可以忙自己的事,當驅動程式用資料時它會主動給APP發信号,這會導緻APP執行信号處理函數。
驅動程式通知APP時,它會發出“SIGIO”這個信号,表示有“IO事件”要處理。
就APP而言,你想處理SIGIO資訊,那麼需要提供信号處理函數,并且要跟SIGIO挂鈎。這可以通過一個signal函數來“給某個信号注冊處理函數”
linux信号具體介紹可參照這裡
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
在程式設計時需要注意以下幾點:
-
核心裡有那麼多驅動,你想讓哪一個驅動給你發SIGIO信号?
APP要打開驅動程式的裝置節點。
-
驅動程式怎麼知道要發信号給你而不是别人?
APP要把自己的程序ID告訴驅動程式。
-
APP有時候想收到信号,有時候又不想收到信号:
應該可以把APP的意願告訴驅動:設定Flag裡面的FASYNC位為1,使能“異步通知”。
linux fcntl函數詳解可參照這裡
3.2 代碼
#include <stdio.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int fd;
void sig_hanler()
{
struct input_event event;
memset(&event,0,sizeof(event));
while(read(fd,&event,sizeof(event))==sizeof(event))
{
printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
}
/*異步通信*/
/* ./input_read_fasync /dev/input/event1 */
int main(int argc,char **argv)
{
struct input_id id;
int err;
int len;
int i;
int bit;
int count = 1;
int flags;
unsigned int evbit[2];
unsigned char byte;
char *ev_names[] = {
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV_ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV_PWR"
};
if(argc!=2)
{
printf("Usage:%s <dev>\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR|O_NONBLOCK);
if(fd==-1)
{
printf("open error\n");
return -1;
}
err = ioctl(fd,EVIOCGID,&id);
if(err==0)
{
printf("bustype is 0x%x\n",id.bustype);
printf("vendor is 0x%x\n" ,id.vendor);
printf("product is 0x%x\n",id.product);
printf("version is 0x%x\n",id.version);
}
len = ioctl(fd,EVIOCGBIT(0, sizeof(evbit)),&evbit);
if(len>0 && len<=sizeof(evbit))
{
printf("support ev type:");
for(i=0;i<len;i++)
{
byte = ((unsigned char*)evbit)[i];
for(bit=0;bit<8;bit++)
{
if(byte & (1<<bit))
{
printf("%s ",ev_names[i*8+bit]);
}
}
}
printf("\n");
}
signal(SIGIO,sig_hanler);
// 把程序ID告訴驅動
fcntl(fd,F_SETOWN,getpid());
// 使能驅動FASYNC
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
while(1)
{
sleep(3);
printf("main loop:%d\n",count);
count++;
}
return 0;
}