抑郁症健康,内容丰富有趣,生活中的好帮手!
抑郁症健康 > 动手做一个键鼠套装(含linux驱动)

动手做一个键鼠套装(含linux驱动)

时间:2023-07-02 16:18:28

相关推荐

在学习了USB理论知识以及linux USB子系统后,结合《圈圈教你玩USB》中制作USB鼠标、键盘的理念,决定自己用STM32开发板做一个键鼠套装,并实现对应的linux驱动。

设备端采用STM32开发板,模拟为一个包含鼠标左右键和键盘W键共三个按键的键鼠套装(因自己的开发板总共只有3个按键)。主机端采用nanopi m3开发板,linux内核版本为4.4.49,可至nanopc t3维基百科中进行下载(两者CPU相同)。实现目标为:两个按键模拟为一个鼠标,一个按键模拟为键盘的W键,并在按下后由驱动控制STM32板上LED的点亮(类似多数机械键盘的RGB灯光)。

设备端:

STM32MCU内部自带USB从控制器,可实现为16个单向端点或8个双向端点,至于USB的基础知识(设备、配置、接口、端点以及描述符)不在本文章讨论内,大家可查阅相关知识了解或是以后我再补上。

STM32标准库中并不包含USB的固件库,需到官网下载,下载后定位到如下位置:

原版固件库以头文件和源文件进行分类,我们对其中的文件进行重新分类,拷贝到我们的项目中,在自己的STM32项目中新建CORE和CONFIG两个文件夹,CORE中文件算是USB中的核心文件,提取了USB协议的共同点,一般很少改动其中内容,CORE中存放文件为:

CONFIG中为需要修改的文件,因设备而异,其中的文件为:

接下来参考官方原版固件Project中的JoyStickMouse例程,改动比较大的为usb_desc.c文件,其中主要是设备的各种描述符的填写,我们将设备描述符Joystick_DeviceDescriptor中的bDeviceClass改为0xFF,表明这是一个厂商自定义设备,idProduct的两字节可改成自己喜欢的数字,代表这是自己的第几个设备。配置描述符Joystick_ConfigDescriptor中bNumEndpoints改为2表明我们要使用两个端口,bInterfaceClass改为0xEF,表明是一个混杂设备,子类bInterfaceSubClass和协议nInterfaceProtocol都改为0,HID的描述符可以删除,端点描述符将wMaxPacketSize原来的0x04改为0x0A,因为第一个端点既作为鼠标也作为键盘的数据发送到主机端,为了减少报告描述符的修改,我们这里依然发送8个字节的键盘数据,再加上因为有两个端口,所以会多出一个字节用来代表是鼠标还是键盘的发送,共9字节,显然原来的4个字节是不够用的。增加一组端点描述符为:

表明此端点为一个最多10个字节的中断输出(主机到设备)端点,两次发送间隔至少为32ms。同时记得修改JOYSTICK_SIZ_CONFIG_DESC配置描述符的长度,这里其实推荐圈圈的sizeof做法,数组改成数字相加的形式,不然每次数都很麻烦。

报告描述符可将鼠标和键盘的报告描述符合二为一,这样就能实现一个USB设备既是鼠标又是键盘的特性,当然还需要稍作修改。添加鼠标的报告ID,0x85,0x01,以后设备发送到主机若的数据若第一个字节为0x01则代表鼠标有按键按下了:

同理键盘,键盘添加为0x85,0x02。而字符串描述符可依个人喜好修改,之后在linux中输入cat /proc/bus/input/devices后便可查看到设备名称。同样,别忘了修改JOYSTICK_SIZ_REPORT_DESC报告描述符的长度。

修改hw_config.c文件中Joystick_Send函数,使其将传入的字符通过端点1发送出去。修改usb_prop.c中Joystick_Reset函数,添加对端点1数据输入到设备的使能:

SetEPRxAddr(ENDP1,ENDP1_RXADDR);SetEPRxStatus(ENDP1, EP_RX_DIS);SetEPRxCount(ENDP1,10);SetEPRxValid(ENDP1);

usb_conf.h中增加#define ENDP1_RXADDR (0x140)宏定义,注释#define EP1_OUT_Callback NOP_Process,并在usb_istr.c中增加void EP1_OUT_Callback(void)函数,用于获取从主机到来的数据,这里主机只会发送两字节简单数据,第一个还是代表发送到的设备是鼠标还是键盘,分别为1和2。第二个字节为数据内容,可据此做需要文章,实现诸如键盘流水灯等效果。这里先做试验,判断收到的数据是否为1,为1则翻转开发板上LED0,代码如下:

在main函数中添加按键扫描程序或者在按键中断中判断按键,在按键按下后,通过Joystick_Send函数,鼠标发送5字节数据,分别为:报告ID(鼠标为1),按键信息,X轴,Y轴,滚轮,键盘同理,为9字节数据,第一个为报告ID(键盘为2),第二个为特殊按键是否按下(例如shift,alt等),第三字节未使用,后面的每个字节代表一个按键,因为可以同时按下几个键。我们可以在代表键盘w的按键按下后,发送一个含有0x1A(w键盘码)的9字节数据。至此,设备端开发完毕。

PS:可以修改bDeviceClass为0,bInterfaceClass为3,nInterfaceProtocol为2,即原HID设备。windows电脑将会识别,并自动安装驱动,分别按下三个键将会实现鼠标左右键以及键盘w的功能。

主机端:

主机端主要编写对应于自己特殊设备的驱动,参考内核 drivers/hid/usbhid中的usbmouse.c驱动代码。首先修改此目录下Kconfig文件,增加配置项,例如:

修改此目录下Makefile文件,增加obj-$(CONFIG_CXN_USB_MOUSE) += cxnmouse.o,代表当对应的配置选择后编译对应的文件。

拷贝一份usbmouse.c并进行重命名,接下来就是对驱动文件的修改了,我们会以最少的修改量来完成这块STM32键鼠套装的驱动。

首先修改自定义数据结构体usb_mouse,增加一个urb请求、存放数据的指针和dma地址:

修改掉usb_mouse_id_table使其在插入我们的设备后能够匹配到我们的驱动:

USB_DEVICE_AND_INTERFACE_INFO为一个宏,意识是通过设备的信息和接口信息让驱动和设备进行匹配,这里我们的匹配规则为设备厂商为0x0483,第4个设备。0xEF为混杂设备,子类和协议都分别是0,这些都对应于我们之前对设备的描述符配置,在设备插入电脑后,linux系统会去进行获取以及寻找对应的驱动。

usb_mouse_probe中增加一项urb的注册,因为我们有两个端口,而原先的usb只有一个输入端口,直接上代码:

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id){struct usb_device *dev = interface_to_usbdev(intf);struct usb_host_interface *interface;struct usb_endpoint_descriptor *endpointin, *endpointout;//增加一个输出端点struct usb_mouse *mouse;struct input_dev *input_dev;int pipein, pipeout, maxpin, maxpout;//增加一个输出管道和输出管道的最大字节数int error = -ENOMEM;interface = intf->cur_altsetting;/*两个端口,第一个为输入,第二个为输出*/if (interface->desc.bNumEndpoints != 2)return -ENODEV;endpointin = &interface->endpoint[0].desc;if (!usb_endpoint_is_int_in(endpointin))return -ENODEV;endpointout = &interface->endpoint[1].desc;if(!usb_endpoint_is_int_out(endpointout))return -ENODEV;/*设置管道和大小*/pipein = usb_rcvintpipe(dev, endpointin->bEndpointAddress);maxpin = usb_maxpacket(dev, pipein, usb_pipeout(pipein));pipeout = usb_sndintpipe(dev, endpointout->bEndpointAddress);maxpout = usb_maxpacket(dev, pipeout, usb_pipeout(pipeout));mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);input_dev = input_allocate_device();if (!mouse || !input_dev)goto fail1;/*分配用于输入或输出数据的内存*/mouse->data = usb_alloc_coherent(dev, 12, GFP_ATOMIC, &mouse->data_dma);if (!mouse->data)goto fail2;mouse->leds = usb_alloc_coherent(dev, 2, GFP_ATOMIC, &mouse->leds_dma);if (!mouse->leds)goto fail2;/*分配两个urb,发送USB请求时用*/mouse->irq = usb_alloc_urb(0, GFP_KERNEL);if (!mouse->irq)goto fail2;mouse->led = usb_alloc_urb(0, GFP_KERNEL);if (!mouse->led)goto fail2;mouse->usbdev = dev;mouse->dev = input_dev;/*下面一段用于为设备填充各种名字*/if (dev->manufacturer)strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));if (dev->product) {if (dev->manufacturer)strlcat(mouse->name, " ", sizeof(mouse->name));strlcat(mouse->name, dev->product, sizeof(mouse->name));}if (!strlen(mouse->name))snprintf(mouse->name, sizeof(mouse->name),"USB HIDBP Mouse %04x:%04x",le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));usb_make_path(dev, mouse->phys, sizeof(mouse->phys));strlcat(mouse->phys, "/input0", sizeof(mouse->phys));/*键盘鼠标归根结底为input设备,只是走了USB总线传输数据*/input_dev->name = mouse->name;input_dev->phys = mouse->phys;usb_to_input_id(dev, &input_dev->id);input_dev->dev.parent = &intf->dev;/*支持左右键和w键*/input_dev->evbit[0] = BIT_MASK(EV_KEY);input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT);input_dev->keybit[BIT_WORD(KEY_RESERVED)] |= BIT_MASK(KEY_W);input_set_drvdata(input_dev, mouse);input_dev->open = usb_mouse_open;input_dev->close = usb_mouse_close;/*填充urb,定义回调函数,使用的管道等*/usb_fill_int_urb(mouse->irq, dev, pipein, mouse->data,(maxpin > 12 ? 12 : maxpin),usb_mouse_irq, mouse, endpointin->bInterval);mouse->irq->transfer_dma = mouse->data_dma;mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;usb_fill_int_urb(mouse->led, dev, pipeout, mouse->leds,(maxpout > 2 ? 2 : maxpout),usb_mouse_led, mouse, endpointout->bInterval);mouse->led->transfer_dma = mouse->leds_dma;mouse->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;error = input_register_device(mouse->dev);if (error)goto fail2;usb_set_intfdata(intf, mouse);return 0;fail2:usb_free_urb(mouse->irq);usb_free_urb(mouse->led);usb_free_coherent(dev, 12, mouse->data, mouse->data_dma);usb_free_coherent(dev, 2, mouse->leds, mouse->leds_dma);fail1:input_free_device(input_dev);kfree(mouse);return error;}

usb_mouse_disconnect中增加对应的设备卸载时需要释放的内存:

static void usb_mouse_disconnect(struct usb_interface *intf){struct usb_mouse *mouse = usb_get_intfdata (intf);usb_set_intfdata(intf, NULL);if (mouse) {usb_kill_urb(mouse->irq);usb_kill_urb(mouse->led);input_unregister_device(mouse->dev);usb_free_urb(mouse->led);usb_free_urb(mouse->irq);usb_free_coherent(interface_to_usbdev(intf), 12, mouse->data, mouse->data_dma);usb_free_coherent(interface_to_usbdev(intf), 2, mouse->leds, mouse->leds_dma);kfree(mouse);}}

usb_mouse_open函数中增加mouse->led->dev = mouse->usbdev;

usb_mouse_close函数中增加usb_kill_urb(mouse->led);

修改usb_mouse_irq函数,此函数为设备发送消息给主机,经过USB核心处理之后,最后到达的回调函数,修改其中对于数据的处理,修改后如下:

/*鼠标按键信息*/if (data[0] == 1){input_report_key(dev, BTN_LEFT, data[1] & 0x01);input_report_key(dev, BTN_RIGHT, data[1] & 0x02);input_sync(dev);}else if (data[0] == 2)//键盘信息{if (data[3] == 0x1A){input_report_key(dev, KEY_W, 1);input_sync(dev);mouse->leds[0] = 0x02;mouse->leds[1] = 0x01;usb_submit_urb(mouse->led, GFP_ATOMIC);//通过端点2将点灯的命令发送给设备}}

增加usb_mouse_led函数,此函数为发送完点灯信息到设备之后的回调函数,可以判断其中的urb->status查看是否正确发送到设备。

最后,通过make menuconfig将其中新增的驱动加入到内核中,烧写内核,将STM32的USB线连接到nano板的USB口中,按动按键,可以发现w的输出以及灯的变化。另此处灯未做过多处理,会有闪烁的情况,因为按键按下后会持续发送点灯信号,大家可以对灯再进行下处理,实现流水或呼吸效果。

如果觉得《动手做一个键鼠套装(含linux驱动)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。