手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它。业余打发时间就玩起来吧,总比刷某音强。从某多多上8块儿大洋买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着在上面玩童年经典游戏啦。
我使用的是正点原子的I.MX6U-ALPHA 开发板,板子资源很丰富。计划搞一个系列在上面玩各种有意思的事情。包含linux驱动开发和应用开发,最终学以致用,在玩中学,兴趣是最好的老师。
展示下我买的FC游戏手柄长这样,普普通通,但便宜啊,还是经典的味道。
驱动移植过程
确定设备类型
要让板子支持这一USB接口的FC游戏手柄,首先得知道这个手柄是使用的什么接口协议。插到win10电脑上看了下,是一个USB协议接口的HID类型的设备。USB-HID是Universal Serial Bus-Human Interface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等。
USB的硬件端口是统一的,但是USB设备却是多种多样的,USB主机根据USB设备的描述符来区分不同的USB设备。每一个USB设备都有自己的描述符,当插入USB设备之后,主机会向从机发送命令,从机收到命令之后,会返回特定的描述符信息。主机通过解析收到的描述符,来识别从机设备的相关信息,这个过程,就是设备枚举(enumeration)过程。
获取USB的VID和PID信息
我的这个FC手柄插到电脑上后识别出了usb-hid设备。查看到它的vid和pid信息,直接在电脑的设备管理器里能够查看到,这个信息很有用,后面驱动移植需要用到。
已启动设备 HID\VID_0810&PID_0001\6&1eff4ed2&0&0000。
驱动程序名称: input.inf
查找linux内核源码,锁定相关驱动
在linux内核源码的linux/drivers/hid/路径下,有跟HID相关的驱动源码。打开hid-core.c文件(HID support for Linux),查看下该源文件中是否包含该usb设备的VID和PID信息。如果没有,则在hid_have_special_driver添加上VID和PID信息。这个里面的一些宏定义在文件hid-ids.h中可以查看。
torvalds大神linux源码的github地址:
GitHub - torvalds/linux: Linux kernel source tree
/*
* A list of devices for which there is a specialized driver on HID bus.
*
* Please note that for multitouch devices (driven by hid-multitouch driver),
* there is a proper autodetection and autoloading in place (based on presence
* of HID_DG_CONTACTID), so those devices don't need to be added to this list,
* as we are doing the right thing in hid_scan_usage().
*
* Autodetection for (USB) HID sensor hubs exists too. If a collection of type
* physical is found inside a usage page of type sensor, hid-sensor-hub will be
* used as a driver. See hid_scan_report().
*/
static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_RP_649) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705) },
//......
}
查找近似hid的游戏手柄驱动
通过make menuconfig打开内核配置选项查看。
找到有个DragonRise Inc, game controller。虽然不确定它跟我的这款FC手柄完美匹配,但至少从名字上看,这就是个游戏手柄的hid设备。
如果有默认的内核配置选项文件,也可以直接添加选项开关:
CONFIG_HID_DRAGONRISE=y
跟这个相关的驱动源文件是linux/drivers/hid/hid-dr.c。打开这个文件,添加上我的usb设备的VID和PID信息。
static const struct hid_device_id dr_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
{ HID_USB_DEVICE(0x0810, 0x0001), },
{ }
};
编译内核驱动
#使用Yocto SDK里的GCC 5.3.0交叉编译器编译出厂Linux源码,可不用指定ARCH等,直接执行Make
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
#编译前先清除
make distclean
#配置defconfig文件
make imx_v7_defconfig -j 16
#开始编译zImage
make zImage -j 16
这之后,更新板子上的内核并重启设备。把该USB游戏手柄插上去,输入dmesg查看内核日志信息,看是否识别到该设备节点。
dmesg
查看输入设备、获取输入事件信息
/dev/input/目录
/dev/input/目录下的事件都是在驱动中调用input_register_device(struct input_dev *dev)产生的。我的/dev/input/目录中的文件如下:
$ ls /dev/input/
by-id by-path event0 event1 event2 event3
每个event代表一个事件。那么如何知道每个事件分别与哪个设备对应?可以借助于/proc/bus查看。
/proc/bus/input/devices
/proc/bus/input/devices存放了与event对应的相关设备信息。我的板子上查看到的内容如下:
$ cat /proc/bus/input/devices
可以看到,每一项的“H:”一行后边的内容中就是对应的event。
直接读取/dev/input/eventx
使用cat查看输入事件的内容,操作相应输入设备,事件会上报内容。以字符串方式解读会呈现乱码。所以可以使用hexdump读取十六进制的数据。
测试读取demo
linux内核使用 input_event结构体描述所有的输入事件。
/*
* The event structure itself
*/
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
为了验证该usb的游戏手柄是否工作,以及获取它对应的键值,写一个小的demo测试读取下。
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#define _EV_KEY 0x01 /* button pressed/released */
#define _EV_ABS 0x03
#define _EV_MSC 0x04
int main() {
printf("hello,usb hid joystick key test\n");
int fd = open("/dev/input/event3", O_RDONLY);
struct input_event e;
while(1) {
read(fd, &e, sizeof(e));
switch(e.type) {
case _EV_KEY:
printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
break;
case _EV_ABS:
printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
break;
case _EV_MSC:
printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
break;
default:
if(e.type != 0){
printf("type:%d, code: %d,value: %d, time: %d\n",e.type, e.code,e.value, e.time);
}
}
}
close(fd);
return 0;
}
evtest测试工具
在开发input子系统驱动时,常常会使用evtest工具进行测试。evtest是打印evdev内核事件的工具,它直接从内核设备读取并打印设备描述的带有值和符号名的事件,可以用来调试鼠标、键盘、触摸板等输入设备。通常用于调试输入设备的问题。
输出数据中,“type”是input类型,可以是“EV KEY”、“EV SW”、“EV SND”、“EV LED”或数值;“value”可以是十进制也可以是十六进制,或者是查询的kev/开关/声音/LED的常量名。
evtest工具下载安装
下载地址:Index of /debian/pool/main/e/evtest/ | 南阳理工学院开源镜像站 | Nanyang Institute of Technology Open Source Mirror
交叉编译安装
#解压缩
$ tar -xjvf evtest_1.33.orig.tar.bz2
$ cd evtest-1.33/
#加载环境
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
#生成makefile,指定交叉编译
.confiqure --host=arm-linux
#编译
make
evtest工具使用
运行示例
time
:事件产生的时间。
type:事件类型,常见的有:EV_KEY(键盘)、EV_REL(相对坐标)、EV_ABS(绝对坐标)、,定义在[input-event-codes.h] (https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h#LC35) 或 input.h 中。
code
:事件的代码,对事件进一步的描述,如:键盘事件的键值(KEY_NUMLOCK、KEY_ESC、KEY_1、KEY_A
)。
value
:事件的值,对事件更具体地描述,如:按键的按下/抬起。
以下是使用devtest工具,(各按一次上,下,左,右,选择,开始等按键), 抓取的各个按键的反馈信息:
手柄上的键值确定
FC手柄上一般包含以下键。左,右,上,下,start,select,A,B,X,Y。
/**
* FC手柄 bit 键位对应关系 真实手柄中有一个定时器,处理 连A 连B
* 0 1 2 3 4 5 6 7
* A B Select Start Up Down Left Right
*/
如果你买的usb接口的FC游戏手柄是DragonRise Inc. game这家的,估计就不用以上这么的测试键值了,直接启用就能用。但是我随便买的这款需要测试下对应起来才能用。
经过以上测试,最终确定键值的对应关系如下:
游戏手柄按键 | 读出的键值 |
---|---|
左侧方向键上 | type: 3, code:1,value: 0 type: 3, code:1,value: 127 |
左侧方向键下 | type: 3, code:1,value: 255 type: 3, code:1,value: 127 |
左侧方向键左 | type: 3, code:0,value: 0 type: 3, code:0,value: 127 |
左侧方向键右 | type: 3, code:0,value: 255 type: 3, code:0,value: 127 |
SELECT键 | type: 1, code:296,value: 1 type: 1, code:296,value: 0 |
START键 | type: 1, code:297,value: 1 type: 1, code:297,value: 0 |
右边数字键1 | type: 1, code:288,value: 1 type: 1, code:288,value: 0 |
右边数字键2 | type: 1, code:289,value: 1 type: 1, code:289,value: 0 |
右边数字键3 | type: 1, code:290,value: 1 type: 1, code:290,value: 0 |
右边数字键4 | type: 1, code:291,value: 1 type: 1, code:291,value: 0 |
其他资源
USB HID_Soc点灯大师的博客-CSDN博客
为了V3S不吃灰,移植NES游戏 / 全志 SOC / WhyCan Forum(哇酷开发者社区)
V3S移植nes游戏模拟器(附带游戏合集)_v3s编译游戏模拟器_qq_46604211的博客-CSDN博客
Linux下查看输入设备、获取输入事件的详细方法_evtest命令_蓝天居士的博客-CSDN博客
linux驱动开发学习笔记九:menuconfig过程详解
开发者搜索-Beta-让技术搜索更简单高效
Linux系统struct input_event结构体分类型(鼠标、键盘、触屏)详解与例子_wkd_007的博客-CSDN博客开发者搜索-Beta-让技术搜索更简单高效
USB_HID基础_usbhid_jansert的博客-CSDN博客
玩转USB HID系列:Linux下使用C语言和libusb开发USB HID_whstudio123的博客-CSDN博客
i.MX6ULL驱动开发 | 20 - Linux input 子系统_imx6ull input驱动框架_Mculover666的博客-CSDN博客
GitHub - torvalds/linux: Linux kernel source tree
嵌入式Linux:V3s移植NES游戏,声音,游戏手柄_全志v3s 移植nes_liefyuan的博客-CSDN博客