Linux驱动开发(十四)---USB驱动开发学习(键盘+鼠标)
前文回顾
《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
继续宣传一下韦老师的视频
70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】
昨天学习了一下usb鼠标的简单识别,今天来完整的写一套键盘和鼠标的驱动,起码能够支持树莓派使用的。
先来写一下键盘的驱动。
键盘驱动框架
框架部分很简单,和昨天的鼠标基本一样,但是今天这里的table_id,要用一个通用定义,保证识别出所有的usb键盘。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <linux/hid.h>//通用ID
static const struct usb_device_id usb_kbd_id_table[] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_KEYBOARD) },{ } /* Terminating entry */
};/*
MODULE_DEVICE_TABLE 有两个功能。
一是:将设备加入到外设队列中,
二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。
该宏定义在<linux/module.h>下
这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table
局部变量,这个变量指向第二个参数
*/
MODULE_DEVICE_TABLE (usb,usb_kbd_id_table);//USB设备信息与驱动端匹配成功的时候调用。
static int myusbkbd_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);printk("USB驱动匹配成功! ID: 0x%X,0x%X\n",id->idVendor,id->idProduct);return 0;
}//USB断开的时候调用
static void myusbkbd_disconnect(struct usb_interface *intf)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);printk("USB 设备释放成功!\n");
}//定义USB驱动结构体
static struct usb_driver myusbkbd_driver =
{.name = "myusbkbd_drv",.probe = myusbkbd_probe,.disconnect = myusbkbd_disconnect,.id_table = usb_kbd_id_table,
};static int __init myusbkbd_init(void)
{//注册USB设备驱动printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);usb_register(&myusbkbd_driver);return 0;
}static void __exit myusbkbd_exit(void)
{//注销USB设备驱动printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);usb_deregister(&myusbkbd_driver);
}module_init(myusbkbd_init);
module_exit(myusbkbd_exit);
MODULE_AUTHOR("PGG");
MODULE_LICENSE("GPL");
还是来测试一下效果,能否顺利加载
[ 194.723576] drivers/char/myusbkbd.c myusbkbd_init 54
[ 194.723771] usbcore: registered new interface driver myusbkbd_drv
[ 200.588535] usb 1-1.3: new low-speed USB device number 6 using dwc_otg
[ 200.733864] usb 1-1.3: New USB device found, idVendor=0c45, idProduct=760b, bcdDevice= 1.05
[ 200.733901] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 200.733921] usb 1-1.3: Product: USB Keyboard
[ 200.733937] usb 1-1.3: Manufacturer: SONiX
[ 200.735207] drivers/char/myusbkbd.c myusbkbd_probe 29
[ 200.735228] USB驱动匹配成功! ID: 0x0,0x0
加载在成功!!不过不清楚为啥ID没有读出来。
尝试一下,修改为按照ID匹配
static const struct usb_device_id my_usbkbd_id_table[] =
{{USB_DEVICE(0x0c45,0x760b)},//键盘{}
};
结果又执行了两次probe函数
[ 124.011262] drivers/char/myusbkbd.c myusbkbd_init 58
[ 124.011457] usbcore: registered new interface driver myusbkbd_drv
[ 130.851294] usb 1-1.3: new low-speed USB device number 5 using dwc_otg
[ 130.996576] usb 1-1.3: New USB device found, idVendor=0c45, idProduct=760b, bcdDevice= 1.05
[ 130.996613] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 130.996633] usb 1-1.3: Product: USB Keyboard
[ 130.996650] usb 1-1.3: Manufacturer: SONiX
[ 130.997809] drivers/char/myusbkbd.c myusbkbd_probe 33
[ 130.997829] USB驱动匹配成功! ID: 0xC45,0x760B
[ 130.998194] drivers/char/myusbkbd.c myusbkbd_probe 33
[ 130.998211] USB驱动匹配成功! ID: 0xC45,0x760B
着实有点费解。不去想了。
丰富probe函数
切换回通用识别的方式,先修改probe函数。这里主要增加两部分内容:
- 消息获取
- 输入子系统的调用。
首先,我们先增加获取一些用得到的数据的内容
struct usb_device *dev = NULL;
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;struct input_dev *input_dev;
int i, pipe, maxp, maxps;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
printk("USB驱动匹配成功! ID: 0x%X,0x%X\n",id->idVendor,id->idProduct);//获取设备信息,主机接口信息,终端描述符
dev = interface_to_usbdev(intf);
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;//建立终端传输管道
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
/*从端点描述符中获取传输的数据大小 */
maxps = endpoint->wMaxPacketSize;printk("maxp[%d] maxps[%d]\n",maxp,maxps);
获取传输数据最大包数,发现了两种写法,标准驱动采用的上面的写法。其实都可用。
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
maxps = endpoint->wMaxPacketSize;
然后就开始结合输入子系统,创建input_dev,注册各个按键。
//创建输入子系统
input_dev = input_allocate_device();input_dev->name = "myusb_kbd";
input_dev->phys = "myusb_kbd";input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
for (i = 0; i < 255; i++)
{set_bit(usb_kbd_keycode[i], input_dev->keybit);
}
到这里的话,输入设备会出现,但是如何与usb终端进行数据交互,这里就用到了urb这个重要操作了
USB请求块(USB request block,URB)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,与网络设备驱动中的sk_buff结构体类似,是USB主机与设备之间传输数据的封装。
一个urb包含了执行usb传输所需要的所有信息。当要进行数据传输时,需要分配一个urb结构体,对其进行初始化,然后将其提交给usb核心。USB核心对urb进行解析,将控制信息提交给主机控制器,由主机控制器负责数据到设备的传输。这时,驱动程序只需等待,当数据回传到主机控制器后,会转发给USB核心,唤醒等待的驱动程序,由驱动程序完成剩下的工作。
更为具体地说,Linux中的设备驱动程序只要为每一次请求准备一个urb结构体,然后把它填充好,就可以调用函数usb_submit_urb()提交给USB核心。然后USB核心将urb传递给USB主机控制器,最终传递给USB设备。USB设备获得urb结构体后,会解析这个结构体,并以相反的路线将数据返回给Linux内核。
那么简单来说,这个urb就是负责穿梭于驱动和设备之间,将用户数据带回来的单元。
用法如下,注意:这里有个缓存大小的问题
USB支持4种基本的数据传输模式:控制传输、同步传输、中断传输、批量传输。控制传输方式支持双向传输,用来处理主端口到USB从端口的数据传输,包括设备控制指令、设备状态查询及确认命令。对于高速设备,允许数据包最大容量为8,16,32或64字节,对于低速设备只有8字节一种选择。
//urb申请使用
my_kbd_data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &my_kbd_data_dma);
my_kbd_urb = usb_alloc_urb(0, GFP_KERNEL);usb_fill_int_urb(my_kbd_urb, dev, pipe, my_kbd_data,(maxp > 8 ? 8 : maxp),usb_mouse_irq, NULL, endpoint->bInterval);
my_kbd_urb->transfer_dma = my_kbd_data_dma;
my_kbd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
最后,注册输入设备,并且开始传输urb
input_register_device(input_dev);
usb_submit_urb(my_kbd_urb, GFP_KERNEL);
中断函数先添加打印,看看前面的分析对不对。
static void my_usb_kbd_irq(struct urb *urb)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);usb_submit_urb(my_kbd_urb, GFP_ATOMIC);
}
上机调试
root@raspberrypi:/home/pgg/work/driver# insmod myusbkbd.ko
root@raspberrypi:/home/pgg/work/driver# dmesg
[ 5296.645084] drivers/char/myusbkbd.c myusbkbd_init 144
[ 5296.647678] usbcore: registered new interface driver myusbkbd_drv
root@raspberrypi:/home/pgg/work/driver# dmesg
[ 5296.645084] drivers/char/myusbkbd.c myusbkbd_init 144
[ 5296.647678] usbcore: registered new interface driver myusbkbd_drv
[ 5309.228696] usb 1-1.3: new low-speed USB device number 7 using dwc_otg
[ 5309.375217] usb 1-1.3: New USB device found, idVendor=0c45, idProduct=760b, bcdDevice= 1.05
[ 5309.375255] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 5309.375275] usb 1-1.3: Product: USB Keyboard
[ 5309.375292] usb 1-1.3: Manufacturer: SONiX
[ 5309.386724] drivers/char/myusbkbd.c myusbkbd_probe 73
[ 5309.386755] USB驱动匹配成功! ID: 0x0,0x0
[ 5309.386769] maxp[8] maxps[8]
[ 5309.387055] input: myusb_kbd as /devices/virtual/input/input4
[ 5309.891936] drivers/char/myusbkbd.c my_usb_kbd_irq 59
[ 5310.388930] drivers/char/myusbkbd.c my_usb_kbd_irq 59
[ 5310.893929] drivers/char/myusbkbd.c my_usb_kbd_irq 59
[ 5311.398930] drivers/char/myusbkbd.c my_usb_kbd_irq 59
看来流程是对的,不过这个中断,在没有按键的时候,还是会一直触发。我们来调试一下是不是有数据。
调试中断信息
通过打印一下status,来看一下有没有数据
printk("%s %s %d urb->status[%d]\n", __FILE__, __FUNCTION__, __LINE__,urb->status);
倒是一直有这个打印,然后status一直为0,说明一直有中断上来
间隔大约0.5秒。那这里暂时不分析为什么会一直上报。
解析按键并上报
增加一下按键分析,参考usbkbd.c的代码
static void my_usb_kbd_irq(struct urb *urb)
{int i=0;//printk("%s %s %d urb->status[%d]\n", __FILE__, __FUNCTION__, __LINE__,urb->status);for (i = 0; i < 8; i++){input_report_key(my_kbd_input_dev, usb_kbd_keycode[i + 224], (my_kbd_data[0] >> i) & 1);}for (i = 2; i < 8; i++) {if (my_kbd_data_old[i] > 3 && memscan(my_kbd_data + 2, my_kbd_data_old[i], 6) == my_kbd_data + 8) {if (usb_kbd_keycode[my_kbd_data_old[i]]){input_report_key(my_kbd_input_dev, usb_kbd_keycode[my_kbd_data_old[i]], 0);}else{hid_info(urb->dev, "Unknown key (scancode %#x) released.\n", my_kbd_data_old[i]);}}if (my_kbd_data[i] > 3 && memscan(my_kbd_data_old + 2, my_kbd_data[i], 6) == my_kbd_data_old + 8) {if (usb_kbd_keycode[my_kbd_data[i]]){input_report_key(my_kbd_input_dev, usb_kbd_keycode[my_kbd_data[i]], 1);}else{hid_info(urb->dev, "Unknown key (scancode %#x) pressed.\n", my_kbd_data[i]);}}}input_sync(my_kbd_input_dev);memcpy(my_kbd_data_old, my_kbd_data, 8);usb_submit_urb(urb, GFP_KERNEL);
}
更新驱动,然后监听一下生成的event1,按下按键A
只有按下的时候有数据,说明还是对的。
用户侧获取按键
那么怎么知道是A呢。那么我们用之前遥控器的用户侧程序,来解析一下事件
分别按下A和B
看来event中定义的数值是吻合的
那么我们就创建一个数组,在用户侧解析一下具体是什么按键。
成功获取按键的值。
完成鼠标驱动
在昨天的基础上,给鼠标也增加了输入系统,然后设备装载一下我自己写的两个驱动,看看能不能让鼠标动起来,键盘敲起来。
结果遇到一个有意思的问题。就是鼠标只能向右下方向移动,最终只能落到右下角。
原因就是键盘发送相对坐标的时候,应该是有符号的
所以数组定义的时候,应该也是有符号的。
最终成功让我的树莓派,用上了自己的驱动。
代码下载
《下载地址》
其实建议自己根绝usbkbd和usbmouse的源码自己理解和重写一遍,印象就会更加深刻。也更能理解usb对于hid设备的驱动流程。
结束语
今天的大事就是一个年近八旬的老太太,让中国的热血青年们夜不能寐。不过还是那首歌中唱的:
朋友来了,有好酒;若是那豺狼来了,迎接他的有,biubiubiu。
马上就七夕了,别忘了过节啊
Linux驱动开发(十四)---USB驱动开发学习(键盘+鼠标)相关推荐
- windows xp 驱动开发(十八) USB驱动程序开发用到的工具总结
转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎拍砖! 观察USB设备的工具. 通过这些工具可以方便学习USB协议 一. usbview 请参考我的 ...
- STC8H开发(十五): GPIO驱动Ci24R1无线模块
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- STC8H开发(十六): GPIO驱动XL2400无线模块
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- 十四. 四轮车驱动开发之五: 由浅至深理解6轴陀螺仪姿态解算算法<下>
十二.四轮车驱动开发之五: 由浅至深理解6轴陀螺仪姿态解算算法(上) 十三.四轮车驱动开发之五: 由浅至深理解6轴陀螺仪姿态解算算法(中) 十四.四轮车驱动开发之五: 由浅至深理解6轴陀螺仪姿态解算算 ...
- Linux开发十五_lcd驱动
Linux开发十五 lcd驱动 象棋小子 1048272975 lcd能够支持彩色图像的显示和视频的播放,是一种很重要的输出设备,在一些嵌入式人机交互系统中,也往往需要lcd进行显示.Linux ...
- 电商生鲜网站开发(四)——后台开发:商品模块-图片上传/多条件拼接sql
电商生鲜网站开发(四)--后台开发:商品模块-图片上传/多条件拼接sql 增加商品 上传图片 更新商品 删除商品 批量上下架 图片上传功能 文件名UUID 通用唯一识别码(Universally Un ...
- 高博SLAM十四讲书本程序学习——第3讲 三维空间刚体运动
小白高博SLAM十四讲书本程序学习_1 第3讲 三维空间刚体运动 在高博原始注释上,针对我自己不明白的部分,做额外注释 如果有错误的地方,请大家指点指点 博文目录 一.P.48 eigenMatrix ...
- i.MX 6ULL 驱动开发 十四:LED(paltform驱动框架)
一.驱动设计思想(机制.策略.分离.分层) 驱动设计思想(机制.策略.分离.分层)_正在起飞的蜗牛的博客-CSDN博客_机制与策略分离 二.驱动开发框架 三.platform 基本概念 Linux 驱 ...
- mtk一键usb驱动_微星b460主板装win7系统及bios设置教程(支持十代usb驱动)
[文章导读]我们知道自从b460主板发布后,有很多使用微星主板的网友问,微星b460主板其搭载intel 酷睿10代i5-10400处理器可以安装安装win7吗?微星b460主板预装的是win10系统 ...
- 十四种Java开发工具点评
图形界面的java开发工具 JDK Borland 的JBuilder ,JDeveloper,VisualAge for Java jcreater. 常见的十五种Java开发工具的特点 1.JDK ...
最新文章
- vs2008中js的语法提示及修正功能(downmoonn)
- 解决IDEA自动重置LanguageLevel和JavaCompiler版本的问题
- Linux定期监视某文件变化,监控Linux文件变化,防止服务器被黑
- Knative Serving 健康检查机制分析
- spring bean的初始化和销毁
- 深入学习SAP UI5框架代码系列之五:SAP UI5控件的实例数据修改和读取逻辑
- Linux TCP/IP中L4L3的实现框架:udp recv部分
- 南充一中计算机机房被盗,成都理工大学与南充市第一中学共建优质生源基地
- 21天Jenkins打卡Day16-清理工作空间
- 化学人学python有前途吗-课堂上老师不讲的有趣物理知识,才是孩子最感兴趣的!...
- Gambit 1.Gambit环境
- 最好的注册测绘师考试资料大全
- linux系统的wps办公软件,linux上安装wps办公软件
- 高清车牌识别系统无法连接服务器,智能停车场高清车牌识别系统,常见的故障及解决方法!...
- 触屏java版象棋下载 游戏下载安装,天天象棋下载手机版
- 【Musescore 】开源打谱软件 快速入门笔记
- jboot 增加llog4j日志
- js和jquery获取父级元素、子级元素、兄弟元素的方法
- AD domain 环境下VBS自动生成邮件签名
- Luogu 1315 【NOIP2011】观光公交 (贪心)
热门文章
- Android音视频开发之——音频非压缩编码和压缩编码,神级Android进阶笔记
- qc中的流程图怎么画_QC流程图
- iOS 开发全能工具箱
- 简单的C语言代码实现快速排序
- 独家首发成语类智力多玩法微信小程序源码下载好玩而又强大
- api64 gta5缺少steam_GTA5无法启动此程序,因为计算机中丢失steam_api64.dll 游戏打不来 现实这个,win7 64位....
- termux python教程_Termux 入门教程:架设手机 Server 下载文件
- Vue中的动态加载组件
- Codeforces-785-D(范德蒙恒等式)
- 省市区三级联动插件(v-distpicker)