嵌入式Linux—輸入子系統

来源:https://www.cnblogs.com/Tayoou/archive/2023/02/26/17157871.html
-Advertisement-
Play Games

輸入系統 常見的輸入設備有鍵盤、滑鼠、遙控桿、書寫板、觸摸屏等等,用戶通過這些輸入設備與Linux系統進行數據交換。 內核中怎樣表示一個輸入設備 // include/linux/input.h struct input_dev { const char *name; //設備名稱 const ch ...


輸入系統

常見的輸入設備有鍵盤、滑鼠、遙控桿、書寫板、觸摸屏等等,用戶通過這些輸入設備與Linux系統進行數據交換。

內核中怎樣表示一個輸入設備

// include/linux/input.h
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

得到如下信息:

[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0018 Vendor=dead Product=beef Version=28bb  //設備ID(定義在input.h的struct input_id結構體)
N: Name="goodix-ts"      //名稱
P: Phys=input/ts         //物理地址
S: Sysfs=/devices/virtual/input/input1  //sys系統地址
U: Uniq=          //標識號(無)
H: Handlers=event1 evbug
B: PROP=2        //設備屬性
B: EV=b          //支持何種輸入事件 
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0   //設備具有的鍵
B: ABS=6e18000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c

APP可以獲得什麼數據

// include/linux/input.h
struct input_value {
	__u16 type;   //當前數據的事件類型
	__u16 code;   //當前事件類型下的哪一個事件
	__s32 value;  //
};

Type的內容:

// include/uapi/linux/input-event-codes.h
/*
 * 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舉例)

// include/uapi/linux/input-event-codes.h
#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
#define KEY_8			9
#define KEY_9			10
#define KEY_0			11

獲取輸入設備信息實例

兩個ioctl的request參數說明(input.h)
request 說明
EVIOCGID 返回輸入設備ID
EVIOCGBIT(ev,len) 獲取輸入設備支持的事件類型列表

ev值的說明:ev參數表示要獲取的事件類型,它是一個整數值

  • 當ev=0,表示要獲取輸入設備支持的所有事件類型列表,包括鍵盤事件、滑鼠事件、相對事件、絕對事件、事件同步、雜項事件等。
  • 當ev=1,表示要獲取輸入設備支持的鍵盤事件類型列表。
  • 當ev=2,表示要獲取輸入設備支持的相對事件類型列表。

EVIOCGBIT的iotcl調用說明:必須使用

len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);    
//len是evbit的實際讀取大小,如果單獨使用sizeof(evbit)得到len,將發生段錯誤
源碼:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>

/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
	int fd;
	struct input_id id;
	int err;
	unsigned char byte;
	unsigned int evbit[2];
	int i;
	int bit;
	unsigned int len;
	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 ",
		"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);
	if(fd == -1) 
	{
		printf("can not open %s\n", argv[1]);
		return -1;
	}
	err = ioctl(fd, EVIOCGID, &id);      //返回輸入設備ID
	if(err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}
	len = ioctl(fd, EVIOCGBIT(0,sizeof(evbit)), evbit);   //返回輸入事件類型
	printf("support ev type:\n");
	for(i = 0;i < len;i++)
	{
		byte = ((unsigned char *)evbit)[i];
		for(bit = 0;bit < 8;bit++)
		{
			if(byte & (1<<bit))
			{
				printf("%s \n", ev_names[i*8 + bit]);
			}
		}

	}
	return 0;

}

實驗結果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event0
bustype = 0x19
vendor  = 0x0
product = 0x0
version = 0x0
support ev type:
EV_SYN
EV_KEY
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
bustype = 0x18
vendor  = 0xdead
product = 0xbeef
version = 0x28bb
support ev type:
EV_SYN
EV_KEY
EV_ABS
[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: PROP=2
B: EV=b
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0
B: ABS=6e18000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c

結論:EV值與程式輸出的type結果一致

查詢和休眠喚醒方式讀輸入事件

所謂的阻塞與非阻塞,是在open處聲明。當設置為阻塞方式,如果沒有輸入事件,整個進程都在阻塞態

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>


/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
	int fd;
	unsigned int len;
	struct input_event event;   //read讀到的是input_event類型的結構體

	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("can not open %s\n", argv[1]);
		return -1;
	}

	while(1)
	{
		len = read(fd, &event, sizeof(event));     //阻塞方式下,進程阻塞在此
		if(len == sizeof(event))
		{
			printf("type = 0x%x, code = 0x%x, value = 0x%x", event.type, event.code, event.value);
		}
		else
		{
			printf("read err %d", len);
		}
	}
	return 0;

}

實驗現象:
  • 查詢方式(非阻塞):反覆查詢,輸出"read err",直到操作輸入設備時,輸出內容更改為輸入事件內容
  • 休眠-喚醒方式(阻塞):只有操作屏幕,才會輸出事件內容

POLL方式讀輸入事件

poll會在設定的時間內進行監聽,當改時間內有輸入事件返回或超過設定時間沒有事件返回,poll都將喚醒。poll/select函數可以監測多個文件,可以監測多種事件。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>


/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
	int fd;
	struct input_event event;   //read讀到的是input_event類型的結構體
	struct pollfd pollfd;
	nfds_t nfds = 1;      //同時打開一個文件

	if(argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}
	fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查詢)
	if(fd == -1) 
	{
		printf("can not open %s\n", argv[1]);
		return -1;
	}
	while(1)
	{
		pollfd.fd = fd;
		pollfd.events = POLLIN;
		pollfd.revents = 0;     //revents初始化為0,當有輸入事件傳入,內核改寫revents
		poll(&pollfd, nfds, 3000);    //poll等待時間為3s
		if(pollfd.revents == POLLIN)      //只有poll函數返回了數據,才調用read
		{
			while(read(fd, &event, sizeof(event)) == sizeof(event))    //把一次獲取到的數據讀完再退出
			{
				printf("type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
			}
		}
		else if(pollfd.revents == 0)
		{
			printf("time out\n");
		}
		else
		{
			printf("read err\n");
		}
	}
	return 0;

}
關於POLL實現多路復用IO
struct pollfd pollfd[n];    //n為文件個數
nfds_t nfds = n;      //同時打開n個文件

.......

if(pollfd[0].revents == POLLIN){}     //依次訪問revents
if(pollfd[1].revents == POLLIN){}

.......

非同步通知方式讀輸入事件

[補充]fcntl的五個功能:

  • 複製一個現有的描述符(cmd=F_DUPFD).
  • 獲得/設置文件描述符標記(cmd=F_GETFD或F_SETFD).
  • 獲得/設置文件狀態標記(cmd=F_GETFL或F_SETFL).
  • 獲得/設置非同步I/O所有權(cmd=F_GETOWN或F_SETOWN).
  • 獲得/設置記錄鎖(cmd=F_GETLK , F_SETLK或F_SETLKW).
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int fd;

void sig_func(int sig)
{
	struct input_event event;
	while(read(fd, &event, sizeof(event)) == sizeof(event))
	{
		printf("type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
	}
}


/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{	
	int count = 0;
	unsigned short flag;
	if(argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}
	signal(SIGIO, sig_func);   //1.註冊信號處理函數(信號類型為IO類型)
	fd = open(argv[1], O_RDWR | O_NONBLOCK);  //2.打開驅動(一定要用非阻塞方式,否則無輸入事件進程一直被阻塞)
	if(fd == -1) 
	{
		printf("can not open %s\n", argv[1]);
		return -1;
	}
	fcntl(fd ,F_SETOWN, getpid());   //3.告知驅動程式app進程ID
	flag = fcntl(fd, F_GETFL);       //4.獲得文件狀態標記
	fcntl(fd, F_SETFL, flag | FASYNC); //5.設置文件狀態標記(將進程添加到驅動fasync事件等待隊列)
	
	while(1)
	{
		printf("count = %d\n", count++);
		sleep(2);
	}
	return 0;

}

實驗結果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
count = 0
count = 1
count = 2               //無輸入事件時正常計數
type = 0x3, code = 0x39, value = 0x6
type = 0x3, code = 0x35, value = 0x1a6
type = 0x3, code = 0x36, value = 0x131
type = 0x3, code = 0x30, value = 0x1f
type = 0x3, code = 0x3a, value = 0x1f
type = 0x1, code = 0x14a, value = 0x1
type = 0x0, code = 0x0, value = 0x0
count = 3
type = 0x3, code = 0x35, value = 0x1a7
type = 0x0, code = 0x0, value = 0x0
count = 4
type = 0x3, code = 0x35, value = 0x1a9
type = 0x0, code = 0x0, value = 0x0
count = 5
type = 0x3, code = 0x35, value = 0x1a8
type = 0x0, code = 0x0, value = 0x0
count = 6

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原生的API&註解方式 1.MyBatis原生的API調用 1.1原生API快速入門 需求:在前面的項目基礎上,使用MyBatis原生的API完成,即直接通過SqlSession介面的方法來完成操作 1.2代碼實現 MyBatisNativeTest.java,演示原生的API操作 其實底層還是使用 ...
  • 一、什麼是模塊? 模塊是一系列功能的集合體,而函數是某一個功能的集合體,因此模塊可以看成是一堆函數的集合體。一個py文件內部就可以放一堆函數,因此一個py文件就可以看成一個模塊。如果這個py文件的文件名為module.py,模塊名則是module。 二、模塊的四種形式 在Python中,總共有以下四 ...
  • 一、單元測試框架簡介 1. 什麼是單元測試 單元測試是指在軟體開發過程中,針對軟體的最小單位(函數,方法)進行正確性的檢查測試。 2. 常用單元測試框架 2.1 Java 類別 junit testng 2.2 Python 類別 unittest pytest 3. 單元測試框架主要作用 測試發現 ...
  • 【深進1.例1】求區間和 題目描述 給定 $n$ 個正整數組成的數列 $a_1, a_2, \cdots, a_n$ 和 $m$ 個區間 $[l_i,r_i]$,分別求這 $m$ 個區間的區間和。 輸入格式 共 $n+m+2$ 行。 第一行,為一個正整數 $n$ 。 第二行,為 $n$ 個正整數 $ ...
  • 原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此聲明。 簡介 日常編程工作中,Java集合會經常被使用到,且經常需要對集合做一些類似過濾、排序、對象轉換之類的操作。 為了簡化這類操作,Java8添加了一套新的Stream API,使用方式就像寫SQL一樣,大大簡化了這 ...
  • 這篇文章主要描述分散式數據存儲系統中的數據分片方法,包括哈希方法、一致性哈希方法、帶有限負載的一致性哈希方法以及帶虛擬節點的一致性哈希方法。 ...
  • Lambda 表達式以及方法引用 Java 8 的新特性筆記,重點講的是: Lambda 函數式介面 方法引用 Steam 流 Lambda 表達式 Lambda 的基礎使用不記錄,記錄 JDK 8 實戰 書上的一些底層和核心筆記。 行為參數化 一個貫徹 Lambda 表達式的一個模式、編程規範。 ...
  • 服務端 工作需要又需要用到socketTCP通訊,這麼多年了,終於稍微能寫點了。讓我說其實也說不出個啥來,看了很多的非同步後稍微對非同步socket的導流 endreceive後 再beginreceive 形成一個內迴圈有了個認識,加上我自己的封包拆包機制,然後再仿那些其它的大多數代碼結構弄點onRe ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...