天天看點

輸入系統應用程式設計--APP通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式

一、輸入系統架構

輸入系統應用程式設計--APP通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式

假設使用者程式直接通路/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

  • 檢視裝置節點對應硬體資訊:

    cat /proc/bus/input/devices

    資訊對應核心中表示的輸入裝置資訊
    輸入系統應用程式設計--APP通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式
    上圖中“B: EV=b”用來表示該裝置支援哪類輸入事件。b的二進制是1011,bit0、1、3為1,表示該裝置支援0、1、3這三類事件,即EV_SYN、EV_KEY、EV_ABS。
  • 使用指令讀取資料:

    hexdump /dev/input/event1

    (我這裡event1表示觸摸屏)

    在開發闆上執行讀取螢幕資料指令後,先會阻塞,點選或滑動觸摸屏後,會列印資訊。

三、擷取輸入裝置資訊

輸入系統應用程式設計--APP通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式

要想列印出struct input_id 中的資料即硬體本身資訊,根據linux源碼中evdev.c檔案中相關函數,可知ioctl的request要寫為EVIOCGID。

輸入系統應用程式設計--APP通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式

要想擷取硬體支援哪類事件(如相對位移事件,絕對位移事件),就要讀取輸入裝置的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通路硬體程式設計一、輸入系統架構二、常用指令三、擷取輸入裝置資訊四、APP通路硬體的四種方式

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

           

繼續閱讀