《Mario Paint》
乍一听起来可能很奇怪,任天堂咋还出过鼠标呢?这款鼠标其实是针对 SFC 游戏《Mario Paint》的外设,咱就先来说说《Mario Paint》这款神奇的游戏。
《Mario Paint》是任天堂在 1992 年发售的一款创作型游戏,你可以在其中绘图、制作简单的动画,甚至还能创作音乐。这款游戏最初研发的目的是为了改善 “游戏机在父母眼中的形象”,所以总体方向上偏向于激发儿童创造力,最终销量也表明它确实得到了父母们的认可,而且绝对的领先于它的后续模仿者们。虽然《Mario Paint》没有再出过续作,但是它的很多元素都在任天堂的另一款创作向游戏上得到了延续。没错,就是《超级马力欧创作家》系列,比如:
- “马造”中的重来狗、声响青蛙、关卡机器人等角色都是来自《Mario Paint》的;
- “马造”中不同物体撞击音符砖块可以发出不同音色的声音,其中很多音色的具体设定与《Mario Paint》音乐模式中保持对应;
- 初代“马造”种的加载动画来自于《Mario Paint》;
- “马造”中比较模型的“猪叫”和“娃娃脸”音效都是来自《Mario Paint》的音乐模式。
SFC/SNES 作为家用主机,没法触屏操作,只靠手柄操作这类游戏显然是十分困难的。所以,任天堂推出了一款专门针对 SFC/SNES 的鼠标外设,最初作为《Mario Paint》套装内的物品出售,后来伴随着支持的游戏越来越多,也会推出单独出售的版本。
在 PC 上使用
我在闲鱼上捡到了垃圾成色的《Mario Paint》套装内容,包含游戏本体、鼠标外设、鼠标垫。拿到后用游戏本提测试了下,鼠标工作正常。但是作为一个“鼠标”,却只能在 SFC/SNES 上使用,这也太缺少鼠标的灵魂了。所以咱今天就尝试把这款鼠标转接到现代 PC 上。
通信协议
开始之前,我们要先搞明白鼠标本体跟 SFC/SNES 方便起见,下文统一使用 SFC
主机通信的过程。这款鼠标的接口和主机手柄的接口一样,是一个 7 Pin 接口,从左到右编号:
对应的功能如下:
Pin | Description | Wire Color |
---|---|---|
1 | +5v | 棕色 |
2 | Data Clock | 红色 |
3 | Data Latch | 橙色 |
4 | Serial Data | 黄色 |
5 | N/C | - |
6 | N/C | - |
7 | Ground | 绿色 |
在之际运行时,SFC 主机每 16.67ms (大概 60Hz)会向手柄/鼠标请求一次状态信息,具体流程如下:
- SFC 主机通过 Pin 3 发送 12us 的高电平;
- Pin 3 高电平恢复 6us 后, SFC 主机向 Pin 2 输入震荡时钟信号,周期为 12us。普通手柄持续 16 周期,鼠标持续 32 周期;
- SFC 主机发送时钟信号的同时从 Pin 4 读取手柄/鼠标发来的对应的数据位。
通过规定数据位与时钟周期的对应关系,SFC 主机就可以读到手柄不同按键的状态,或者是鼠标的位置信息。对于鼠标来说,32 位通过 Pin 4 读取到的信息含义如下表:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | B | Y | Select | Start | Up | Down | Left | Right | A | X | L | R | 1 | 1 | 1 | 0 |
10 | Dir Y | Y6 | Y5 | Y4 | Y3 | Y2 | Y1 | Y0 | Dir X | X6 | X5 | X4 | X3 | X2 | X1 | X0 |
其中前 15 位与普通手柄的语义一致,但是第 16 位为0,以此告知 SFC 主机插入的是鼠标,而不是普通手柄。第17 位开始位鼠标的位置变化信息,鼠标的两个按键则被映射到了 A 和 X 键。
转接到 PC
弄清楚通信协议后,接下来的思路就清晰了:我们需要一个转接的硬件,模拟 SFC 主机读取鼠标数据,同时模拟 PC 的鼠标执行鼠标动作。
硬件上,我选择的是 Arduino Leonardo,与 UNO 最大的区别是它可以很方便的模拟 USB 设备,正好符合我们的场景。接下来需要把鼠标连接到 Leonardo 的引脚上,比较推荐的做法是再买一个 SFC手柄延长线,这样我们只需要把延长线抛开接上引脚,然后鼠标接上延长线就行,不会破坏鼠标原来的接口。
代码上思路就比较简单了,循环读取鼠标信息,并向 USB 发送模拟的指令,借助于 Leonardo 封装的 Mouse 库,实现起来很简单:
#include "Mouse.h"
int DATA_CLOCK = 6;
int DATA_LATCH = 7;
int DATA_SERIAL = 12;
int range = 5; // output range of X or Y movement; affects movement speed
int buttons[32];
int x = 0;
int y = 0;
void setup(){
setupPins();
Mouse.begin();
}
void loop(){
getControllerData();
updateMouse();
delay(1);
}
void setupPins(void){
// Set DATA_CLOCK normally HIGH
pinMode(DATA_CLOCK, OUTPUT);
digitalWrite(DATA_CLOCK, HIGH);
// Set DATA_LATCH normally LOW
pinMode(DATA_LATCH, OUTPUT);
digitalWrite(DATA_LATCH, LOW);
// Set DATA_SERIAL normally HIGH
pinMode(DATA_SERIAL, OUTPUT);
digitalWrite(DATA_SERIAL, HIGH);
pinMode(DATA_SERIAL, INPUT);
}
void getControllerData(void){
// Latch for 12us
digitalWrite(DATA_LATCH, HIGH);
delayMicroseconds(12);
digitalWrite(DATA_LATCH, LOW);
delayMicroseconds(6);
for(int i = 0; i < 32; i++){
digitalWrite(DATA_CLOCK, LOW);
delayMicroseconds(6);
buttons[i] = 1 - digitalRead(DATA_SERIAL);
digitalWrite(DATA_CLOCK, HIGH);
delayMicroseconds(6);
}
bool output = false;
}
void updateMouse(){
if (buttons[9] == 1) {
// if the mouse is not pressed, press it:
if (!Mouse.isPressed(MOUSE_LEFT)) {
Mouse.press(MOUSE_LEFT);
}
}
// else the mouse button is not pressed:
else {
// if the mouse is pressed, release it:
if (Mouse.isPressed(MOUSE_LEFT)) {
Mouse.release(MOUSE_LEFT);
}
}
if (buttons[8] == 1) {
// if the mouse is not pressed, press it:
if (!Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.press(MOUSE_RIGHT);
}
}
// else the mouse button is not pressed:
else {
// if the mouse is pressed, release it:
if (Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.release(MOUSE_RIGHT);
}
}
//get x\y offset
x = 0;
y = 0;
for (int i = 0; i < 7; i++)
{
x *= 2;
y *= 2;
x += buttons[17+i];
y += buttons[25+i];
}
if (buttons[16]==1){
x = x *-1;
}
if (buttons[24]==1){
y = y *-1;
}
if ((x != 0) || (y != 0)) {
Mouse.move(y*range, x*range, 0);
}
}
实际体验
先说结论:在现代的高分辨率的 PC 上基本不能用,因为 DPI 实在是太低太低了,稍微小点的区域根本移动不进去。不过老硬件就要配上老电脑嘛,在 Windows 3.1 上用起来还是不错的,视频效果如下(y2b须扶墙):
参考链接
Super Nintendo Entertainment System: pinouts & protocol
Super Mario Maker 2 - Super Mario Wiki, the Mario encyclopedia