手柄驱动

引言

       最近时间稍微空闲了一点,打开抽屉时发现了里面已经积灰了的手柄,心中似乎又燃起了一团火焰,这是4年前买的北通卡洛智游者安卓手柄型号2171s,当年一百块买的,北通吹的可升级芯片,结果发售后就再也没管过。里面的两个震动马达和线性扳机yellowko也从来没有体验过,从拿到手的那一天起,yellowko就一直期待着这两个功能的开放,但随着时间流逝,安卓手柄热度也没有增加,官方似乎放弃了这款手柄。在一次意外发现它能在Windows上正常工作后,yellowko开始了在Windows上探究让其发光发热的旅途。

一、最初的探索——Xbox 360手柄模拟

       最基本的就是通用手柄对于Xbox 360手柄的模拟,通常使用的时x360ce,官网地址在此。如果只要让手柄能被游戏识别为xbox手柄,以及键位的映射,这个软件可以解决绝大多数问题。

除此以外,steam在2018年也推出了控制器相关的设置,就在“设置“-“控制器”-“常规控制器设置”里,同样可以解决游戏中手柄的识别问题。

但是这些方案都不是完美的,缺点在于都不能独立实现手柄的震动和线性扳机功能,也就是说如果电脑中没有安装对应手柄的震动和线性的驱动程序,就不能靠这些软件把手柄的电机调动起来,里面震动相关的选项也没有什么作用。

二、无意的发现——震动反馈的实现

       由于之前也做过不少的单片机开发,对于串口调试也有一些经验,无疑就是按照协议收发一个个的数据包,而手柄在Windows中作为一个HID设备,那么应该是能使用HID调试助手之类的东西收发数据包的,果不其然,yellowko在网上随便找了一个HID调试工具,看到了手柄所发给电脑的数据。一共21字节,但由于原始的HID数据会在前面添加一个0x00字节,实际接收到的是22字节,大部分位上都包含了手柄上反馈的数据。通过按下按键并记录对应改变的位,记录下了手柄上各个键所对应的键值。

       由于官方并没有提供任何资料,所以,对于电机控制的数据格式,yellowko只能慢慢猜,使用了一个最简单暴力的办法,直接给手柄发了21字节的1,即21个0xFF。令人激动的是,它居然震起来了,通过一个一个字节的排除,定位了控制大小两个电机的两个字节所在的位置。当然这个是手柄对数据要求不高才能达到,部分手柄可能会对某些位进行校验,导致这种随便发送的数据会被丢弃。

       需要特别注意一点是,这种方法是存在风险的,小米手柄驱动的开发者在给手柄发全1时似乎是把手柄弄坏了。其次,在hid调试软件中发送的并不是原始的HID数据包,如果要调用系统函数直接发送原生HID数据包,需要在前面增加一个字节,应该是作为ID来识别,yellowko对HID的协议并没有进行深入了解,所以这里不是很清楚,在前面添加的是0x00。

三、最后的实现——手柄驱动的完成

       在搞清楚手柄的各键值和震动反馈后,yellowko在网上开始寻找Windows驱动的构建方法,但是发现从零开始构建驱动不是一件简单的事,然后就开始转向寻找同类型的开源项目,这还真找到了几个。

第一个是vJoy,这是一个开源的虚拟手柄驱动,其中提供了一系列的手柄相关接口,可以以此为基础简化开发。第二个是ViGEm Bus Driver,这是在找vJoy相关代码时找到的,是一个模拟Xbox 360或者DS4手柄的驱动,可以和vJoy结合开发。最后找到的是小米手柄的驱动,这个驱动是利用了ScpServer这样一套模拟Xbox 360手柄的驱动来开发的。而yellowko最终参考的就是小米手柄的驱动来实现的。那么接下来yellowko会介绍一下对这个工程的简单分析和改动,使其适配yellowko的手柄的过程。

首先需要安装ScpSever的驱动程序,然后下载小米手柄驱动的工程,用Visual Studio打开这个工程后,接下来开始分析Program.cs这个文件。入口在Main(string[] args)这个方法,往下看

var compatibleDevices = HidDevices.Enumerate(0x2717, 0x3144).ToList();

这句里Enumerate方法的两个参数分别是VID和PID,这个手柄插入被识别为HID设备后就可以在设备管理器里找到这两个参数。修改为对应值后继续往下看。foreach这一段是打开HID设备的,前面ID输对了应该不会出现什么问题。

再往下在这里定义了一个Vibration []数组,这个为了存放原始的震动数据的,长度足够就行,小米的是[0x20,0x00,0x00],北通2171s我实际测出来是[-,-,-,0x00,0x00],前三位没有影响。接着就是这个

if (Device.WriteFeatureData(Vibration) == false)

这句的意思是向HID设备写一个特征报告,而这个特征报告的格式是在设备接入时就已经告诉主机了的,小米的手柄在向Windows注册时提供了这项参数,所以在运行这个方法时不会报错,而北通2171s并没有,所以导致了这一步会返回false,这种情况下就不能使用WriteFeatureData()这个方法,而应该直接使用Write()。这个方法是直接向HID设备写数据的,如果HID正常运行,是会返回true的。

这时就会进入下一阶段,通过Xiaomi_gamepad()方法创建rumble_thread()和input_thread()两个线程,分别控制震动的发送和手柄键位的接收。

rumble_thread()就是把Vibration[]中负责大电机和小电机的两个字节的数据刷新到局部变量local_vibration[]中然后写入到HID设备。input_thread()中首先把从HID接收到的数据复制到currentState中,接下来判断设备是否在线以及数据是否正确

if (data.Status == HidDeviceData.ReadStatus.Success && currentState.Length >= 21 && currentState[0] == 4)

小米手柄发送的HID数据首部为0x04,所以判断语句里是4,这个需要根据不同的手柄数据来修改,北通2171s的首部就是0x00。接下来的一长串就是检测数据中是否有代表对应按键按下的位发送变化,如果变化,就把按下的键值传给Buttons。要适配不同的手柄,只要按照前面获取到的键值表对应修改即可。再往后又有一个涉及到vibration[]的地方,修改为对应值即可,需要判断自己手柄大小电机分别是哪个字节控制的,然后把bigMotor和smallMotor的值赋予对应的位即可。

       改到这里,其实就已经完成了,编译完成后就可以使用了。但是程序其实还有一些小细节是可以优化的,第一点,可以给这个程序增加一个识别手柄后让手柄震动一下的功能,这样方便判断程序开始正常运行。第二点,程序input_thread()中设置的timout=30,这个间隔其实过长,会导致手柄震动反馈有不跟手的感觉,电机不能及时制动,把时延改短可以有效解决这个问题,但是过段时延会导致读取失败,需要自己调整。Yellowko在自己改动后的驱动里以及做了相应更改,有兴趣了解的可以看一看。

四、之后的展望——通用手柄驱动

       经过这次对小米手柄驱动的修改,yellowko看见了制作通用手柄驱动的可能性,在ScpServer、HidLibrary这两套优秀的库以及小米手柄驱动的支持下,只需要改变键值、发送的数据,即可实现手柄的适配。在未来有时间的话,yellowko可能会制作一个GUI界面,将这些数据交由用户配置,实现普通用户也能根据自己的手柄参数设置属于自己手柄的驱动。

五、相关链接

yellowko所修改的北通卡洛驱动 https://github.com/yellowko/Betop-2171s-BFM-driver


知识共享许可协议

本作品由yellowko采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

关于 “手柄驱动” 的 18 个意见

  1. 大神,有编译好的驱动能给我一份吗?小白不会弄,seira@126.com,感谢!

    1. 我之前发布过在这里了的https://github.com/yellowko/Betop-2171s-BFM-driver/releases/tag/Release1.0
      邮箱也给你发了

    2. 那个邮件因为有可执行文件发过去被服务器拒绝了,你看看自己到github下吧,地址前一条给了

  2. 大神太厉害了,这个破手柄终于完美支持震动了,以前用xoutput模拟360,就是不支持震动

  3. 请问楼主,报错:System.IO.IOException:“SCP Virtual Bus Device not found”,不太理解,可以怎么解决?

  4. 大佬,你好,我是一个战斧F1的用户,就是那个臭名昭著的国产主机,这几天我突发奇想,能不能给把主机送的这个手柄利用起来,发现连接PC只是会识别为普通的蓝牙控制器,并且键位是错乱的,我在查找了一些资料之后发现,Github上也是大佬通SCP这个驱动制作了一个有线驱动,而蓝牙驱动这位大佬虽然给出了按键值,但是因为自己不懂编程所以也看不懂应该修改哪里,所以非常不要脸的想请问下是否能帮忙修改这个按键值。
    蓝牙按键值地址:https://github.com/mumumusuc/FuzeController
    有线驱动地址:https://github.com/mumumusuc/FuzeGamepad

    1. 我看了一下这个两个项目,蓝牙驱动只有linux上的,有线驱动也不完整,并且都没有解决震动的问题。按照你的说法,手柄是能在PC上被识别的,只是键位错乱,那么我推荐你使用x360ce重新映射手柄的键位,链接是https://www.x360ce.com/。如果你使用steam,也可以使用steam自带的手柄映射功能对键位进行校准。

      1. 有线的模式下可以正常使用线性扳机这些,确实没震动,非常感谢大佬你抽空看了下,感谢

        1. 帮你编译了你给的驱动,邮件发你了,我没改过,你试试能不能用,后缀自己改成exe。

          1. 惊了,GMAIL没收到,如果是有线的话,我用VS打包过了,是可以正常是用的,再次感谢

          2. 附件被google查出可执行文件拦截了,编译的是有线的驱动,你自己有的话那就没事了。

  5. 我这2天在改写别人送我的一个京东京造的蓝牙手柄, 用的也是github的那个小米驱动, 因为震动问题解决不了. 搜到了博主的这篇博客.

    我也在GITHUB上提交了这个修改 jd-jzv3n-gamepad , 我这个手柄收到的报文只有11位, 而且WRITE也是无效的. 不是他开机时震一下. 我甚至怀疑他没有震动. 这个手柄是北通代工的, 他在X Y 轴上的线性值变化也很奇怪 , 是 127 – 0 , 255 – 129 . 这样的排列方法.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据