此贴基本为2021/1发布的《自制控gen2记录》的存档,加上近期的side device补全。此外同时融合了几篇关于原生串口协议的分析。
首次阅读可以先忽略概览和main/side device加工指南以外的分析讨论内容。
概览
基础规格
- 32-key电容触摸
- 无按键灯光
- 红外对射AIR
- 原生串口触摸板,键盘AIR
- 此项目理论兼容超级星和初三:串口协议均未更改,需要自备键盘映射USBIO的AM方案。
设计目标
- 自用: 只满足必须的输入功能。
- 自制: 选取适合手工加工极少个数的材料和工艺
- 尽量避免工厂定制类加工服务和稀缺元件。留学难民没有某宝也能搓
工艺要求
- PCB开片: 唯一使用的定制服务,JLC基础配置即可(无SMT)
- 过孔焊: 网上买个烙铁,有手就行
- 物理支持和连接工艺: 难以定制亚克力的,在本地五金店准备强力胶或者螺丝固定均可
资源链接
请结合详细说明使用,并留意文末改进项!
Main Device 触摸板
加工指南
- 控制器: Teensy LC
- 触摸芯片: MPR121, sparkfun layout (不是adafruit的一般都是sparkfun layout)。注意如果有ADD到GND的短接jumper要划开,否则会短路
- 按钮(选配): 12mm或以下的触觉按钮即可
- AIR排线插口: 10针脚2.54mm间距的IDC公头插口(其实只用了8个,但我没买到8pin的就开了10pin)
全部焊上去即可。
触摸感应
- 内部干扰:开PCB初衷是除掉手工误差的寄生电容干扰,效果很好,比之前胶带贴的稳定多了。同时既然所有走线全都打印不需要任何人力了,不如试下32key。
- 实现前的顾虑:
32key最大的问题就是下排16个电极的线怎么连。因为我没有亚克力外壳,所以pcb整个下半手都会碰到且不能安放芯片,而手碰到线和手碰到电极一样会触发电容感应,故手能碰到的地方都是不可以走线的不然一坨电极会被乱触发。这样几乎唯一的地方就是立体从反面过去了,但从反面的话和上排电极重叠的部分会有和上排金属的寄生电容干扰和手经过时的误判问题。实际结果是这两个顾虑都是多余的。 根据AN3747,立体重叠走线只要重叠的走线面积相对触摸感应面积足够小,就完全没有问题。
- 设计方案:(见PCB layout)在上层以16x2的规划铺设32个触摸区金属,上排在m1层直接连接到MPR121。下排在每个电极下边缘中央开一个via(为了好看但via孔其实很小几乎注意不到),从下层走触摸区中心竖直向上连接到MPR121。下层导线为DRC最小线宽。因为下层金属面积远小于和上层的距离(PCB厚度),所以其电容几乎可以忽略。同时上层金属应该屏蔽了手指的静电,所以上排触摸不会触发下排。
- MPR121配置:设置了3个MPR121,分别启用并负责12/8/12个电极。理论上8/8/8/8可能能获得更高的触摸刷新率,但我觉得不差那几百微秒。另外gsk提到过如果电极间距太近且电极是不同mpr驱动的话,因为MPR是轮询可能会有当前电极测量时隔壁电极强接地带来干扰的问题。我的电极间距全部是3mm,目前没有发现这种现象,我自己玩也没有漏键。
控制器和固件
这次改用了Teensy LC。Arduino又大又贵又慢还送不到,再见。Teensyduino使用了Arduino的API格式,所以很方便的把上次的代码移植了。但同样的接口背后的实现都是Teensy自己的,有的库实现比Arduino舒服多了。但同时我也用了Teensy特有的代码,移植时注意。(追记:使用Pro Micro抄板可以获得更优价格,如有需要请自行修改PCB和软件。尤其注意Pro Micro Vdd=5V时会烧MPR121,需要额外添加变压和逻辑电压转换器)
目前只实现了游戏串口直通的软件。AIR和JVS/USBIO按钮(test/service/coin)依然采用键盘。不实现全键盘映射是因为Teensy的USB终点的配置管理不太一样,之前的Nicohood HID库用不了了,自带的USB键盘用的是boot键盘的HID识别,包格式的限制下同时只能按6个键,懒得去找怎么给Teensy换NKRO键盘了,等有需求了再说吧。
Teensy是3.3V逻辑,一般可以驱动5V逻辑,但LC不能接受5V输入。MPR121也是3.3V不能接受5V,上次得用adafruit带变压器的MPR,这次随便找个照抄sparkfun layout的国产MPR就行了(搞笑的是这次买到的还tm抄错了,本来要切开ADD-GND的jumper结果按他的layout什么都不用做了)。还有一个IR供电电压问题后面讲。WS2812B是5V逻辑但估计3.3V数据可以驱动,虽然我没试。
PC端的代码没变,我用的我自己改过的segatools,主要受影响的是AIR的键盘映射。主面板输入协议改ini即可。NFC整合和主控制器无关。之前提到过的针对Arduino的串口DTR/RTS的修正在Teensy上不需要了。
Side Device 红外对射AIR
加工指南
Side device的PCB设计考虑了更多的可修改性,同一个PCB同时可以用作发射端和接收端。加工相较main device就没那么无脑了。使用的元件有:
- IR发光二极管或其他5V可以驱动的发射方案
- IR光敏二极管、光敏三极管、调制解码接收器(如果你给发射端改出了调制)或者其他3.3V可以驱动的接收方案
- Micro USB breakout: 详细类型见PCB
- SN74HC04N 经典7400系列的非门
- 电阻
- 和main device一样的排线接口
发射端组装例(请酌情修改):
- MicroUSB、7400非门和排线接口直接焊
- 依照反面丝印在3->2和2->1间各焊一个LED(正向电压1.1V,总共将输入5V降到约3V)
- 依照正面丝印在左侧TX端电阻格焊上限流电阻(200Ohm,电流约15mA,总功耗约75mW)留的间距不够可以借用右侧RX的下方接地焊盘斜着装电阻
接受端组装例(请酌情修改):
- 排线接口直接焊,其余标TX ONLY的元件留空
- 依照反面丝印在+和Y间焊光敏二极管(+接短脚,Y接长脚)
- 依照正面丝印在右侧RX端电阻格焊上下拉电阻(47kOhm)留的间距不够可以借用左侧TX的下方接地焊盘斜着装电阻
支持结构例:
- 螺丝孔位置算好的,相邻两组螺丝孔的对角两个孔连线竖直向下时,板子的倾斜程度正好为街机。详见组装例和经典老图
- 螺丝孔适合#6螺丝和螺母(电脑主板同直径)。注意板子反面因为有过孔焊点所以需要架空一段空间,在反面拧两个螺母上去足够。
AIR轮询控制
之前的AIR方案有发射端干扰的问题,相信很多人都有体会。只要用的不是激光LED,同一个接收端能感应到多个发射端的照射。(用带调制的感觉频率响应也没窄到能区分六个,而且元件还很难买)但是,如果只有一个发射端和一个接收端判定就很精确。轮询方案就是通过软件控制发射端的开关来排除不相干的发射端的干扰,每次读取时控制只有其对应的发射端点亮。实现可以用一段伪代码解释:
for (int air = 0; air < 6; air++) {
digitalWrite(AIR_TX_PIN[air], HIGH);
delay(AIR_READ_DELAY);
air_broken[air] = analogRead(AIR_RX_PIN[air]) < AIR_THRESHOLD;
digitalWrite(AIR_TX_PIN[air], LOW);
}
用了轮询后效果非常好!激光一般的感觉!以至于IR对齐的难度显著提升了,手办盒子支架放不稳了, 用PCB加上稳定的支架即可获得相当理想的效果
计时:LED是半导体设备,响应速度极快,这个方案才得以实现(如果换成灯泡那就没戏了)。在默认所有发射端关闭下,每次读取的流程为:打开发射i,延迟t,读取接收i,关闭发射i。而延迟t用i2c读取mpr121的时间即可满足。具体见代码。实际运行起来看起来像6个发射端常亮(有的IR能看到一点微弱的红色),但比真常亮要暗,但其实是高速闪烁(pwm亮度就是这个原理)。
供电:这个是我用KB874特有的问题。这个光电开关组整合好了LED的限流电阻和光敏二极管的采样和判定。额定工作电压为5V,发射端电流为20mA。首先,逻辑元件接3.3V的Vdd一般输出就也是3V了,所以接收端直接走控制器供电,输出信号也不会烧爆控制器。接下来发射端的问题就比较大了,一是控制器逻辑针脚只能供5mA,二是3.3V时接收端的信号强度不够。 基本上来说发射端的功率远大于控制器的针脚额定,虽然不一定会烧,但是还是尽量避免功率不足对于控制器和发射亮度的影响。我的解法是加装一个micro usb口用作5V供电,加装一个非门作为继电器/3.3V转5V(具体SN74HC04N,7400系列逻辑,一块芯片正好6个inverter,单口25mA总共50mA):usb和控制器的GND连在一起,inverter的Vdd接5V,六个输入接控制器,六个输出接LED。混用不同的逻辑电压时注意Vth,一般的5V逻辑的Vth最高也有3V,只要高于Vth(min)其实就可以当做低电压转5V用。而很多60V的mosfet三极管的Vth(min)是4V,这种就不行。
串口通信
协议
实现细节文本实在不好解释,看我的控制器代码吧,实现串口协议的函数在serial.cpp里,这些函数的用法在ino里。大致有以下几点:
- 丢包判定: 对于需要响应的指令(AUTO_SCAN_STOP, RESET, GET_BOARD_INFO),如果在一定的短暂时间内没有收到应答,游戏执行重发,重发三次后判定设备未连接放弃重发。
- 对于不需要响应的指令,AUTO_SCAN_START丢了那就gg了重启吧。SET_LED丢了也没事因为SET_LED很频繁,就算颜色没变他还是会不停的发。
- 游戏过程中,pc不断发送SET_LED,控制器不断发送AUTO_SCAN,两者均无丢包检查
- 包由定位字节,头部和主体构成。定位字节为FF,头部和主体中不得出现FF,即见到FF就一定是新包,之前的东西都是错误发送。主体数据中若出现FF需要转义
- FD用于转义,其之后的字节加一。FF -> FD FE;FD -> FD FC,其他字节不得转义。
- checksum错误等价于丢包,反复出现checksum甚至会被判定为控制器坏了,游戏会建议你换个新的(草)。checksum计算的是转义之前的全部包内容(包括FF定位字节)
限速
I2C全速运行时的触摸更新发包速度会将游戏的串口卡死:初次的初始化序列正常执行,但随后通信就中断了——游戏认定控制器响应超时,控制器的IO缓冲溢出。通过修改capnhook(segatools依赖的hook串口的库),记录了下游戏调用串口IO的轨迹。具体数据可见 这张表
- 游戏逻辑似乎是同步卡死的,固定每写入1次光效读取6次触摸
- 读取间隔平均为8000. us,刷新率为125 Hz(fps)
- segatools记录的每次读取的大小为208字节,上行传输带宽208.0Kbps(注意虽然这超过了大部分COM的波特率,但是对于非UART的USB Serial设备完全可及,如Teensy或者32u4,它们的理论速度均为USB带宽,至少1.5Mbps)
- 按照这个估算,每个触摸包无转义大小为35字节(touched采样结果必定无转义),理论最高发送间隔是1346us,考虑到overhead差不多和实际吻合了
如果在采样循环中手动添加delay(1)
问题就完美消失且判定无异,但对于代码逻辑洁癖这种设计简直是灾难。。。最终我对设计做出了以下更改:
- 只有触摸变化时发送更新——若触摸状态和上次采样一致则跳过串口发送
- 但是,如果距离上次发送已经经过了15ms,则强制发送更新
原因应该比较明显:
- 就算你是高桥名人,我也不相信你能在8ms内敲3下板子来手动日爆串口。(除非你电容触摸的干扰飞天了,那你应该先把触摸修好)
- 如果不定期发一个更新,游戏会认定连接断了。在test中可以明显看到,输入测试正常,但如果初始化自检期间不不停敲打触摸板就会报错timeout
- 如此可以实现游戏每次读取,要么什么都读不到因为没有变化,要么一定读到最新的触摸状态
Arduino串口激活握手
注意:不适用于Teensy
如果控制器的串口输出在Serial Monitor下正常运行,但独立写的读取串口的程序却能发不能收,很可能是中了Arduino的串口重启的握手坑:查了Arduino Serial Monitor的源码才发现,RS232有这么个DTR和RTS的机制可能需要在初始化的时候设置,Arduino的大部分板子都要(辣鸡msdn样例,找了我好久。。)。在电脑端的初始化流程中添加以下内容即可正常读写:
if (!EscapeCommFunction(hComPort, SETDTR)) {
fprintf(stderr, "Cannot set COM DTR%ld\n", GetLastError());
goto fail_cleanup;
}
if (!EscapeCommFunction(hComPort, SETRTS)) {
fprintf(stderr, "Cannot set COM RTS%ld\n", GetLastError());
goto fail_cleanup;
}
具体作用搜索RS232 DTR和RS232 RTS CTS。虽然理论上RTS/CTS的机制不是这么用的,但反正会丢包要有补发机制,RTS/CTS也没用了(个人见解,懒得验证)。
对于基于Arduino的控制器,可以直接在运行调用串口的程序前调用这个小程序(含预编译exe)单独进行握手,之后的程序即可正常读写。
运行配置
- 控制器的串口必须为COM1,可以通过windows设备管理器(COM#属性->端口设置->高级)指定端口号。
- 修改segatools.ini,在
[slider]
下添加一行enable=0
,segatools就不会hook触摸板了。
- 仅适用于Arduino:下载或者编译comopen.exe,修改segatools的start.bat,在inject之前运行comopen.exe。
改进项
以下为我在实际使用中见到的问题,但我直到有切实重做前不会修改和验证这些方案,建议新制作时根据实际情况做出修改而不是盲目复用(不欢迎不理解设计直接无脑抄方案)。
- 控制器可以改用Pro Micro/各种32u4抄板以降低成本
- Main device触摸区高度不足,换手滑条容易手打到上方的芯片
- Main device上部非触摸区占用高度过大,可以考虑压缩走线或者完全反面走触摸线把芯片移动到边上,同时降低开片成本
- Side device未做特殊抗干扰处理。已知会被HTC Vive的lighthouse干扰翻。以及无法在阳光直射环境下使用(可以稳定对抗至少3颗2600lm的日光LED,但日照充足的房间白天可能用不了。。。)