NES 模拟器开发教程 12 - 输入设备

NES 支持许多设备,最常见的还是官方手柄,它有 8 个按键:

  • A
  • B
  • SELECT
  • START
  • UP
  • DOWN
  • LEFT
  • RIGHT

读取的时候比较奇怪,按道理来讲 8 个按键刚好可以用 1 个 byte 表示,读一次就可以了,但是 NES 读取的时候却是串行的,读 8 次,每次读一个按键,这样做应该是为了兼容性第三方控制器

1. 寄存器

手柄的寄存器位于 CPU 总线的 0x4016 和 0x4017,分别对应 1P 和 2P

7  bit  0
---- ----
xxxx xxxS
        |
        +- Controller shift register strobe

寄存器只有 bit 0 有效,做为选通标志。当写入选通为 1 时,则可以通过读取寄存器,每次返回一个按键状态,下一次读取返回下一个按钮状态。当写入 0 时,offset 被置位,再次选通读取时会重新从第一个按钮读取

需要注意的是,只能往 4016 写(写 4017 给 APU 用了),读可以往 4016 和 4017 读。写 4016 时,对两个手柄都有效,读时则 4016 为 P1,4017 为 P2

2. 按钮映射

按钮对应的比特位为:

bit 7 6 5 4 3 2 1 0
button A B Select Start Up Down Left Right

3. 实现

手柄实现非常简单,只需要读写和更新按钮状态的函数

export enum StandardControllerButton {
  A = 0x80,
  B = 0x40,
  SELECT = 0x20,
  START = 0x10,
  UP = 0x08,
  DOWN = 0x04,
  LEFT = 0x02,
  RIGHT = 0x01,
}

export class StandardController implements IStandardController {
  private data: number;
  private isStrobe = false;
  private offset = 0;

  public updateButton(button: StandardControllerButton, isPressDown: boolean) {
    if (isPressDown) {
      this.data |= button;
    } else {
      this.data &= ~button & 0xFF;
    }
  }

  public write(data: uint8) {
    if (data & 0x01) {
      this.isStrobe = true;
    } else {
      this.offset = 0;
      this.isStrobe = false;
    }
  }

  public read(): uint8 {
    const data = this.isStrobe ? this.data & StandardControllerButton.A : this.data & (0x80 >> this.offset++);

    return data ? 1 : 0;
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容