博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
21.Linux-写USB键盘驱动(详解)
阅读量:7041 次
发布时间:2019-06-28

本文共 8282 字,大约阅读时间需要 27 分钟。

本节目的:

    根据上节写的,来依葫芦画瓢写出键盘驱动

 


 

1.首先我们通过上节的代码中修改,来打印下键盘驱动的数据到底是怎样的

先来回忆下,我们之前写的鼠标驱动的id_table是这样:

 

所以我们要修改id_table,使这个驱动为键盘的驱动,如下图所示:

 

然后修改中断函数,通过printk()打印数据:

我们先按下按键A为例,打印出0x04,如下图:

 

 

我们再同时按下按键A和S,打印出0x04,0X16, 如下图:

 

显然这些普通按键都是从buf[2]开始的,那第一个数组到底又存什么值?

我们按完所有键盘按键,发现只有8个按键会打印在buf[0]里,如下图所示:

 

所以buf[0]是用来保存键盘的特定功能的键,而buf[1]可能是个保留键,没有用到的,buf[2]~buf[7]是普通按键,比如ABCD,1234,F1,F2等等,能支持最多6个按键同时按下。

2.那么每个按键的数据又是怎么定义的?

2.1比如我们按下按键A,为什么打印0X04?

我们找到输入子系统(input.h)中按键A定义的值,它对应的却是30,看来不是直接调用的,如下图:

 

 

我们再来参考内核自带的USB键盘驱动 (/drivers/hid/usbhid/usbkbd.c)

发现它的中断函数中有个键盘描述码表(其中0表示保留的意思):

 

 

 

发现该数组的0X04就是0X30,看来要写个键盘驱动,还需要上面的数组才行.

那么问题又来了,如果我们按下左alt键,buf[0]中会出现0x04,如果也代入到键盘描述码表中,显然就会当作键盘按键A来使用。

2.2我们来分析内核的键盘中断函数是如何处理的:

发现有这么一句:

for (i = 0; i < 8; i++)       input_report_key(kbd->dev, usb_kbd_keycode[i+ 224], (kbd->new[0] >> i) & 1);

 

其中kbd->new表示的就是键盘数据数组,它将buf[0]的每一位通通以usb_kbd_keycode[i+ 224]的形式上传到按键事件中

显然我们的buf[0]的0X04就是上传的usb_kbd_keycode[4+ 224]

2.3我们来看看usb_kbd_keycode[226]里的数据对应的到底是不是左ALT键

找到usb_kbd_keycode[226]=56:

 

然后再进入input.h,找到56的定义,刚好就是KEY_LEFTALT(左边的alt键)

 

 

3.接下来再来仔细分析下内核自带的USB键盘驱动usbkbd.c里的中断函数:

代码如下:

static void usb_kbd_irq(struct urb *urb){       struct usb_kbd *kbd = urb->context;       int i;       switch (urb->status) {                        // 只有urb->status==0时,说明数据传输成功       case 0:                  /* success */              break;       case -ECONNRESET:     /* unlink */       case -ENOENT:       case -ESHUTDOWN:              return;       /* -EPIPE:  should clear the halt */       default:          /* error */              goto resubmit;       }       for (i = 0; i < 8; i++)                           //上传crtl、shift、atl、windows 等按键              input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);       for (i = 2; i < 8; i++) {                  //上传普通按键              /*通过上个状态的按键数据kbd->old[i]的非0值,来查找当前状态的按键数据,若没有找到,说明已经松开了该按键 */          if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {              if (usb_kbd_keycode[kbd->old[i]])              //再次判断键盘描述码表的值是否非0                input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); //上传松开事件               else                info("Unknown key (scancode %#x) released.", kbd->old[i]);              }      /*通过当前状态的按键数据kbd->new[i]的非0值,来查找上个状态的按键数据,若没有找到,说明已经按下了该按键 */         if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {             if (usb_kbd_keycode[kbd->new[i]]) //再次判断键盘描述码表的值是否非0                input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);      //上传按下事件             else                info("Unknown key (scancode %#x) pressed.", kbd->new[i]);              }       }       input_sync(kbd->dev);        memcpy(kbd->old, kbd->new, 8);                     //更新上个状态值 resubmit:       i = usb_submit_urb (urb, GFP_ATOMIC);       if (i)         err ("can't resubmit intr, %s-%s/input0, status %d",         kbd->usbdev->bus->bus_name,         kbd->usbdev->devpath, i);}

3.1上面获取普通按键时,为什么不直接判断非0,要判断按键数据> 3?

之前我们就分析了,当按键数据=0X01、0X02时,代表的是特定功能的键(crtl、shift),是属于buf[0]的数据

其中memscan()是用来匹配上次按键和当前按键的数据,它这么做的原因是怕上个buf[]和当前buf[]的数据错位,这里就不做详细分析了

一切迎刃而解,我们只需要将自己的代码也通过这个码表添加所有按键按键事件,然后再在键盘中断函数中根据数据来上传事件即可

4.本节键盘代码如下:

#include 
#include
#include
#include
#include
#include
static struct input_dev *myusb_kbd_dev; //input_devstatic unsigned char *myusb_kbd_buf; //虚拟地址缓存区static dma_addr_t myusb_kbd_phyc; //DMA缓存区;static __le16 myusb_kbd_size; //数据包长度static struct urb *myusb_kbd_urb; //urbstatic const unsigned char usb_kbd_keycode[252] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 150,158,159,128,136,177,178,176,142,152,173,140}; //键盘码表共有252个数据 void my_memcpy(unsigned char *dest,unsigned char *src,int len) //复制缓存{ while(len--) { *dest++= *src++; }}static void myusb_kbd_irq(struct urb *urb) //键盘中断函数{ static unsigned char buf1[8]={ 0,0,0,0,0,0,0,0}; int i; /*上传crtl、shift、atl、windows 等按键*/ for (i = 0; i < 8; i++) if(((myusb_kbd_buf[0]>>i)&1)!=((buf1[0]>>i)&1)) { input_report_key(myusb_kbd_dev, usb_kbd_keycode[i + 224], (myusb_kbd_buf[0]>> i) & 1); input_sync(myusb_kbd_dev); //上传同步事件 } /*上传普通按键*/ for(i=2;i<8;i++) if(myusb_kbd_buf[i]!=buf1[i]) { if(myusb_kbd_buf[i] ) //按下事件 input_report_key(myusb_kbd_dev,usb_kbd_keycode[myusb_kbd_buf[i]], 1); else if(buf1[i]) //松开事件 input_report_key(myusb_kbd_dev,usb_kbd_keycode[buf1[i]], 0); input_sync(myusb_kbd_dev); //上传同步事件 } my_memcpy(buf1, myusb_kbd_buf, 8); //更新数据 usb_submit_urb(myusb_kbd_urb, GFP_KERNEL);}static int myusb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id){ volatile unsigned char i; struct usb_device *dev = interface_to_usbdev(intf); //设备 struct usb_endpoint_descriptor *endpoint; struct usb_host_interface *interface; //当前接口 int pipe; //端点管道 interface=intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; //当前接口下的端点描述符 printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); /* 1)分配一个input_dev结构体 */ myusb_kbd_dev=input_allocate_device(); /* 2)设置input_dev支持 按键事件*/ set_bit(EV_KEY, myusb_kbd_dev->evbit); set_bit(EV_REP, myusb_kbd_dev->evbit); //支持重复按功能 for (i = 0; i < 252; i++) set_bit(usb_kbd_keycode[i], myusb_kbd_dev->keybit); //添加所有键 clear_bit(0, myusb_kbd_dev->keybit); /* 3)注册input_dev结构体*/ input_register_device(myusb_kbd_dev); /* 4)设置USB键盘数据传输 */ /*->4.1)通过usb_rcvintpipe()创建一个端点管道*/ pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); /*->4.2)通过usb_buffer_alloc()申请USB缓冲区*/ myusb_kbd_size=endpoint->wMaxPacketSize; myusb_kbd_buf=usb_buffer_alloc(dev,myusb_kbd_size,GFP_ATOMIC,&myusb_kbd_phyc); /*->4.3)通过usb_alloc_urb()和usb_fill_int_urb()申请并初始化urb结构体 */ myusb_kbd_urb=usb_alloc_urb(0,GFP_KERNEL); usb_fill_int_urb (myusb_kbd_urb, //urb结构体 dev, //usb设备 pipe, //端点管道 myusb_kbd_buf, //缓存区地址 myusb_kbd_size, //数据长度 myusb_kbd_irq, //中断函数 0, endpoint->bInterval); //中断间隔时间 /*->4.4) 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址*/ myusb_kbd_urb->transfer_dma =myusb_kbd_phyc; //设置DMA地址 myusb_kbd_urb->transfer_flags =URB_NO_TRANSFER_DMA_MAP; //设置使用DMA地址 /*->4.5)使用usb_submit_urb()提交urb*/ usb_submit_urb(myusb_kbd_urb, GFP_KERNEL); return 0;}static void myusb_kbd_disconnect(struct usb_interface *intf){ struct usb_device *dev = interface_to_usbdev(intf); //设备 usb_kill_urb(myusb_kbd_urb); usb_free_urb(myusb_kbd_urb); usb_buffer_free(dev, myusb_kbd_size, myusb_kbd_buf,myusb_kbd_phyc); input_unregister_device(myusb_kbd_dev); //注销内核中的input_dev input_free_device(myusb_kbd_dev); //释放input_dev}static struct usb_device_id myusb_kbd_id_table [] = { { USB_INTERFACE_INFO( USB_INTERFACE_CLASS_HID, //接口类:hid类 USB_INTERFACE_SUBCLASS_BOOT, //子类:启动设备类 USB_INTERFACE_PROTOCOL_KEYBOARD) }, //USB协议:键盘协议};static struct usb_driver myusb_kbd_drv = { .name = "myusb_kbd", .probe = myusb_kbd_probe, .disconnect = myusb_kbd_disconnect, .id_table = myusb_kbd_id_table,};/*入口函数*/static int myusb_kbd_init(void){ usb_register(&myusb_kbd_drv); return 0;} /*出口函数*/static void myusb_kbd_exit(void){ usb_deregister(&myusb_kbd_drv);}module_init(myusb_kbd_init);module_exit(myusb_kbd_exit);MODULE_LICENSE("GPL");

 

5.测试运行

5.1 重新设置编译内核(去掉默认的hid_USB驱动)

make menuconfig ,进入menu菜单重新设置内核参数:

进入-> Device Drivers -> HID Devices 

<> USB Human Interface Device (full HID) support     //hid:人机交互的USB驱动,比如鼠标,键盘等

然后make uImage 编译内核

将新的键盘驱动模块放入nfs文件系统目录中

5.2然后烧写内核,装载触摸屏驱动模块

如下图,当我们插上USB键盘时,可以看到该VID和PID,和电脑上的键盘的参数一样

 

 

5.3使用cat  tty1进程测试

 

 

5.4 使用exec 0</dev/tty1测试

(exec命令详解入口地址: )

如下图,就能通过板子上的键盘来操作了

 

 

转载地址:http://yzxal.baihongyu.com/

你可能感兴趣的文章
JavaScript 作用域
查看>>
Linux Ubuntu 16.04 主机名设置
查看>>
CCNP 静态路由
查看>>
单链表二[不带头节点链表]
查看>>
Spring mvc 拦截器
查看>>
MySQL GROUP BY 和GROUP_CONCAT的一些用法
查看>>
## mysqldump 导出数据库各参数详细说明
查看>>
java中URL编码和中文相互转换
查看>>
影评:《云图》:生命并非微不足道
查看>>
hibernate4之一对一关系映射(二)
查看>>
我的友情链接
查看>>
Android第五课 编译错误分析
查看>>
VS_远程调试
查看>>
博为峰Java技术题 ——JavaSE Java实现在不同编码之间进行文件转换
查看>>
Throws与Throw
查看>>
php趣味编程 - php求黑色星期五
查看>>
zabbix安装
查看>>
ELK之权限管理
查看>>
×_7_12_2013 I: Light on or off
查看>>
JIT
查看>>