入力サブシステムは、Linux でのタッチ スクリーン座標を報告します。
1. 入力サブシステムの概要
Linux では、入力サブシステムは、入力サブシステムのデバイス ドライバー層、入力サブシステムのコア層 (Input Core)、および入力サブシステムのイベント処理層 (Event Handler)で構成されます。
- デバイス ドライバ層
デバイス ドライバ層は、ハードウェア デバイスの各レジスタへのアクセスを実装し、基盤となるハードウェアからユーザー層への応答データを標準入力イベントに変換し、コア層を介してイベント処理層に送信します。 - コア層 コア
層は、デバイス ドライバー層とイベント処理層の間の接続ブリッジであり、デバイス ドライバー層とイベント処理層にプログラミング インターフェイスを提供します。 - イベント処理層 イベント
処理層は、ユーザー空間に統合されたアクセス インターフェイスを提供し、ドライバー層によって送信されたデータを処理します。これにより、入力デバイスのドライバー部分はデバイス ファイルの操作を気にする必要がなく、デバイス ファイルの操作のみを考慮するようになります。各ハードウェア レジスタの動作と送信された入力イベントを考慮する必要があります。
2. 入力サブシステムの利点
- 物理的形状が異なる同様の入力デバイスの処理機能を統合します。たとえば、PS/2、USB、Bluetooth など、すべての種類のマウスは同じように扱われます。入力サブシステムの一般的なイベント タイプは、キー イベント (キーボードなど)、相対座標イベント (マウスなど)、および絶対座標イベント (タッチ スクリーンなど) です。
- 入力レポートをユーザー アプリケーションにディスパッチするためのシンプルなイベント インターフェイスを提供します。ドライバーは、/dev ノードおよび関連するアクセス メソッドを作成および管理する必要はありません。そのため、入力 API を簡単に呼び出して、マウスの動き、キーボードのキーの押下、またはタッチ イベントをユーザー空間に送信できます。
- 入力ドライバーの共通部分を抽出し、ドライバーを簡素化し、一貫性を提供します。たとえば、入力サブシステムは、シリアル ポートやキーボード コントローラーなどのハードウェア入力へのアクセスをサポートする低レベル ドライバー (シリアルと呼ばれる) のコレクションを提供します。
3. 入力サブシステム関連インタフェース機能
- struct input_dev 構造体
input_dev 構造体は、基礎となるハードウェア デバイスを表し、すべての入力デバイスを抽象化したものです。ドライバー層は、input_dev 構造体を埋める必要があります。
struct input_dev {
const char *name; //设备名字--比如:键盘的名字
const char *phys; //设备在系统中的路径。比如:input/key0
const char *uniq; //唯一ID号
struct input_id id; //用于匹配事件处理层 handler
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)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt_slot *mt;
int mtsize;
int slot;
int trkid;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
//文件操作函数 ,可以自行实现
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
bool sync;//最后一次同步后没有新的事件置 1
struct device dev;
struct list_head h_list;
struct list_head node;
};
- struct input_event 構造体
この構造体は通常アプリケーション層で呼び出され、ユーザーはイベント層から報告されたデータ内容を受け取ります。
struct input_event {
struct timeval time; //时间戳
__u16 type;//事件类型EV_KEY、EV_REL、EV_ABS
__u16 code;//事件数据值,若按键事件,则保证按键键值;若坐标信息,则表明为x,y
__s32 value;//标志值,若按键,则表示按下还是松开;若坐标,则表示位具体的坐标值
};
- inptu_dev 構造体関数の動的割り当てと解放
//input_dev 構造体の動的割り当て
struct input_dev *input_allocate_device(void)
//input_dev 構造体の解放
void input_free_device(struct input_dev *dev)
- 入力サブシステムの登録と登録解除
//入力サブシステムを登録
int input_register_device(struct input_dev *dev)
//入力サブシステムをキャンセル
void input_free_device(struct input_dev *dev)
パラメーター: input_dev --入力デバイス構造体の
戻り値:登録が成功すると 0 が返され、失敗すると他の値が返されます。
- 報告されるデータの内容を設定する input_set_capability
input_set_capability関数は、input_dev 構造体に値を入力し、報告されるデータ タイプとデータ情報を設定するために使用されます。
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
パラメータ: dev --input_dev 構造体
タイプ --イベントタイプ EV_KEY、EV_REL、EV_ABS
コード -- 報告される特定の値
例: input_set_capability(dev,EV_KEY ,KEY_A) );//キーイベントを報告します。報告されたキー値は「A」です
- 報告されるデータの内容を設定します __set_bit
inptu_dev 構造体はビットを設定する関数によって埋められ、input_set_capability 関数の内部は __set_bit 関数を呼び出すことで実現されます。
inline void __set_bit(int nr, volatile unsigned long *addr)
仮パラメータ: nr – 報告される特定の値addr --ボタンイベントを報告するために
設定するアドレス例: __set_bit(EV_KEY,dev->evbit);//イベント属性をキーイベントとして設定します __set_bit(KEY_A,dev->keybit);//報告されたキー値を設定し、繰り返し報告を設定します 例: __set_bit(EV_REP,dev->evbit);
- 報告される値の範囲を設定する input_set_abs_params
input_set_abs_params 関数は、報告される値の値の範囲を設定するために使用されます。
void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
仮パラメータ: dev --input_dev 構造体
axis --報告値
min --minimum value
max --maximum
fuzz --Data偏差値
フラット -- スムーズな位置
タッチ スクリーンの x 座標範囲を設定します:
input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//x 座標範囲を設定します
タッチ スクリーンの x 座標範囲を設定します:
input_set_abs_params(touch_dev,ABS_PRESSURE,0,1 ,0,0);//圧力値の範囲を設定します
- データをイベント処理層にレポートする
//キーボードなどのキーイベントのキー値をレポート
inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
//マウスなどの相対イベント座標値をレポート
inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
//タッチスクリーンなどの絶対イベント座標値をレポート
inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);
仮パラメータ: dev --input_dev 構造体
code --event data値、キーイベントの場合、ボタンのキー値を保証します; 座標情報の場合、x,y 値として示されます
-- フラグ値、ボタンが押されている場合、ボタンが押されたか離されたかを示します。それは座標であり、特定の座標値を示します
これらの関数は、input_even 関数によって内部でデータのレポートを完了します。
- イベント同期 input_mt_sync
void input_mt_sync(struct input_dev *dev)
パラメータ: dev --input_dev 構造体
データレポート完了後は必ずイベント同期関数を呼び出してください。
4. タッチスクリーン座標を報告する入力サブシステムの例
ハードウェア プラットフォーム: tiny4412
開発プラットフォーム: ubuntu18.04
クロス コンパイラー: arm-linux-gcc
カーネル: linux3.5
タッチ スクリーン ドライバー IC: ft5X06
ft5x06 ドライバーのサンプル リファレンス: Linux での IIC サブシステムとタッチ スクリーン ドライバー
- 入力サブシステム登録レポートデータ例
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
static struct work_struct touch_work;
static struct i2c_client *touch_client;
static struct input_dev *touch_dev=NULL;
/*工作处理函数*/
static void touch_work_func(struct work_struct *work)
{
u8 touch_buff[7];
int x,y;
int num;
i2c_smbus_read_i2c_block_data(touch_client,0, 7,touch_buff);
num=touch_buff[2]&0xf;//触控点个数
x=((touch_buff[3]&0xf)<<8)|touch_buff[4];
y=((touch_buff[5]&0xf)<<8)|touch_buff[6];
//printk("(x,y)=%d,%d\tnum=%d\n",x,y,num);
if(num)
{
input_report_abs(touch_dev,ABS_X,x);//上报x坐标
input_report_abs(touch_dev,ABS_Y,y);//上报x坐标
input_report_abs(touch_dev,ABS_PRESSURE,1);//压力值,1表示按下
input_report_key(touch_dev,BTN_TOUCH,1);//按下
}
else
{
input_report_abs(touch_dev,ABS_PRESSURE,0);//压力值,0表示松开
input_report_key(touch_dev,BTN_TOUCH,0);//释放
}
input_sync(touch_dev);//同步
}
/*中断处理函数*/
static irqreturn_t touch_irq_work(int irq, void *dev)
{
schedule_work(&touch_work);//调度工作
return IRQ_HANDLED;
}
static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)//资源匹配函数
{
int ret;
printk("资源匹配成功\n");
printk("name=%s\taddr=%#x\tirq=%d\n",client->name,client->addr,client->irq);
touch_client=client;
/*动态分配input_dev结构体*/
touch_dev=input_allocate_device();
if(!touch_dev)return -1;//动态分配失败
/*设置要上报的数据内容*/
input_set_capability(touch_dev,EV_ABS,ABS_X);//上报x坐标
input_set_capability(touch_dev,EV_ABS,ABS_Y);//上报x坐标
input_set_capability(touch_dev,EV_ABS,ABS_PRESSURE);//压力值
input_set_capability(touch_dev,EV_KEY,BTN_TOUCH);//触摸屏点击事件
/*设置xy取值范围*/
input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//设置x坐标范围
input_set_abs_params(touch_dev,ABS_Y,0,480,0,0);//设置y坐标范围
input_set_abs_params(touch_dev,ABS_PRESSURE,0,1,0,0);//设置压力值
/*注册输入子系统*/
ret=input_register_device(touch_dev);
if(ret)return ret;//注册输入子系统设备失败
/*1.初始化工作*/
INIT_WORK(&touch_work, touch_work_func);
/*注册中断*/
ret=request_irq(client->irq,touch_irq_work,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"ft5x06",NULL);
if(ret)
{
printk("中断注册失败\n");
return -1;
}
return 0;
}
static int ft5x06_remove(struct i2c_client *client)//资源释放函数
{
printk("IIC驱动程资源释放成功\n");
free_irq(client->irq,NULL);//注销中断
/*注销输入子系统设备*/
input_unregister_device(touch_dev);
/*释放input_dev结构体*/
input_free_device(touch_dev);
return 0;
}
//资源匹配结构体
static struct i2c_device_id id_table[]=
{
{
"touch_ft5x06",0},
{
},
};
static struct i2c_driver ft5x06_drv=
{
.probe=ft5x06_probe,
.remove=ft5x06_remove,
.driver=
{
.name="touch_drv",
},
.id_table=id_table,//资源匹配结构体
};
static int __init wbyq_ft5x06_drv_init(void)
{
i2c_add_driver(&ft5x06_drv);
return 0;
}
/*驱动释放*/
static void __exit wbyq_ft5x06_drv_cleanup(void)
{
i2c_del_driver(&ft5x06_drv);
printk("IIC驱动层注销成功\n");
}
module_init(wbyq_ft5x06_drv_init);//驱动入口函数
module_exit(wbyq_ft5x06_drv_cleanup);//驱动出口函数
MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 ft5x06_drv Driver");
- アプリケーション層でタッチスクリーン座標を読み取る例
#include <stdio.h>
#include <linux/fb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <linux/input.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
static unsigned char *lcd_p=NULL;//屏幕缓存地址
static unsigned char *gbk_addr=NULL;//屏幕缓存地址
static struct fb_fix_screeninfo fb_fix;//固定参数结构体
static struct fb_var_screeninfo fb_var;//可变参数结构体
extern const unsigned char ascii_32_16[][32*16/8];//逐列式,高位在前
/*LCD画点函数*/
static inline void LCD_DrawPoint(int x,int y,int c)
{
//获取要绘制的点的地址
unsigned int *p= (unsigned int *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
*p=c;//写入颜色值
}
/*
显示汉字
x,y --要显示的位置
size --字体大小
font --要显示的汉字
c -- 颜色值
*/
static void LCD_DisplayFont(int x,int y,int size,char *font,int c)
{
u8 *p=NULL;
u8 H,L;
u32 addr=0;//汉字偏移地址
u16 font_size=size*size/8;//汉字点阵大小(宽度保证为8的倍数)
H=*font;//汉字高字节
L=*(font+1);//汉字的低字节
if(L<0x7F)L-=0x40;
else L-=0x41;
H-=0x81;
addr=(190*H+L)*font_size;//汉字所在点阵中的偏移地址
p=malloc(font_size);
if(p==NULL)
{
printf("申请空间失败\r\n");
return ;
}
memcpy(p,gbk_addr+addr,font_size);//读取点阵码数据
int i,j;
int x0=x;
unsigned char tmep;
for(i=0;i<size*size/8;i++)
{
tmep=p[i];//取出每个字节数据
for(j=0;j<8;j++)
{
if(tmep&0x80)
{
LCD_DrawPoint(x0,y,c);
}
x0++;
c+=60;//修改颜色值
tmep<<=1;//继续下一个像素点
}
//换行
if(x0-x>=size)
{
x0=x;
y++;
}
}
}
/*
显示字符
x,y --要显示的位置
h,w -- 字符高和宽
cha --要显示的字符
c -- 颜色值
取模走向:逐列式,高位在前
*/
static void LCD_DisplayCha(int x,int y,int h,int w,char cha,int c)
{
int i,j;
int y0=y;
u8 temp;
for(i=0;i<w*h/8;i++)
{
temp=ascii_32_16[cha-' '][i];
for(j=0;j<8;j++)
{
if(temp&0x80)LCD_DrawPoint(x,y0,c);
y0++;
c+=100;//修改颜色值
temp<<=1;
}
if(y0-y==h)//换到下一列
{
y0=y;
x++;
}
}
}
/*
显示字符串
x,y --要显示的位置
size -- 字符高度
str --要显示的字符串
c -- 颜色值
*/
static void LCD_DisplayStr(int x,int y,int size,char *str,int c)
{
int x0=x;
while(*str)
{
if(*str>=0x80)//汉字
{
LCD_DisplayFont(x0,y,size,str,c);
str+=2;
x0+=size;
}
else if(*str>=' ' && *str<='~')//字符显示
{
LCD_DisplayCha(x0,y,size,size/2,*str,c);
str++;
x0+=size/2;
}
else str++;
}
}
int main()
{
/*1.打开设备*/
int fd=open("/dev/fb0", 2);
if(fd<0)
{
printf("打开设备失败\n");
}
/*2.获取固定参数*/
memset(&fb_fix,0, sizeof(fb_fix));
ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
printf("屏幕缓存大小:%d\n",fb_fix.smem_len);
printf("一行的字节数:%d\n",fb_fix.line_length);
/*3.获取屏幕可变参数*/
memset(&fb_var,0, sizeof(fb_var));
ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
printf("屏幕尺寸:%d*%d\n",fb_var.xres,fb_var.yres);
printf("颜色位数:%d\n",fb_var.bits_per_pixel);
/*4.将屏幕缓冲区映射到进程空间*/
lcd_p=mmap(NULL,fb_fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
if(lcd_p==(void *)-1)
{
printf("内存映射失败\n");
return 0;
}
/*打开字库文件*/
int fontfd=open("GBK_32.DZK",2);
if(fontfd<0)
{
printf("字库文件打开失败\n");
return 0;
}
struct stat statbuf;
fstat(fontfd,&statbuf);
if(statbuf.st_size<=0)goto AA;
gbk_addr=mmap(NULL,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fontfd,0);
close(fontfd);
if(gbk_addr==(void *)-1)goto AA;
memset(lcd_p,0xff,fb_fix.smem_len);//将屏幕清空为白色
LCD_DisplayStr(300,50,32,"触摸屏驱动测试",0x45ff);
/*打开触摸屏设备*/
fd=open("/dev/input/event1",2);
if(fd<0)
{
printf("触摸屏驱动打开失败\n");
return 0;
}
struct pollfd fds=
{
.fd=fd,
.events=POLLIN,
};
/*
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
*/
struct input_event touchxy;
while(1)
{
poll(&fds,1,-1);
read(fd,&touchxy,sizeof(touchxy));
switch(touchxy.type)
{
case EV_KEY://按键值
printf("key=%d\tstat=%d\n",touchxy.code,touchxy.value);
break;
case EV_ABS://绝对坐标
if(touchxy.code == ABS_X)//x坐标
{
printf("x=%d\n",touchxy.value);
}
else if(touchxy.code == ABS_Y)//Y坐标
{
printf("y=%d\n",touchxy.value);
}
else if(touchxy.code == ABS_PRESSURE)//压力值
{
printf("press=%d\n",touchxy.value);
}
break;
}
}
munmap(gbk_addr,statbuf.st_size);
AA:
//取消映射
munmap(lcd_p,fb_fix.smem_len);
return 0;
}