291. 控制器捕获与串行读取
捕获控制器按钮并串行读取。
第 291 / 356 · tests/chapter_07_controller_input/test_291_controller_capture_and_serial_read.py
本步骤中创建的文件
emulator/input/controller.py为什么需要这一步
NES CPU 不会从 $4016 一次性获取完整的控制器状态字节。它每次只接收一个按钮位。在将该协议接入 CpuBus 之前,纯 Controller 对象应当能够:
参考资料
https://www.nesdev.org/wiki/Standard_controller
https://www.nesdev.org/wiki/Controller_reading_code
1. capture the current button booleans into a stable snapshot
2. expose the captured bits in NES serial order
3. handle strobe high vs strobe low behavior关键术语:选通信号 选通信号(strobe)是由 CPU 写入的控制信号。对于 NES 控制器,选通信号控制控制器是继续捕获实时按钮状态,还是逐步读取已捕获的串行位。
最小示例
controller.a = True
controller.write_strobe(1)
controller.write_strobe(0)
controller.read_bit() # returns A bit常见误解
"The controller should return all buttons as one byte."模拟器内部可能将捕获的按钮状态存储为一个字节,但面向 CPU 的协议每次只读取一位。
建议的实现示例
def capture_buttons(self) -> None:
value = 0
if self.a:
value |= BUTTON_A
if self.b:
value |= BUTTON_B
if self.select:
value |= BUTTON_SELECT
if self.start:
value |= BUTTON_START
if self.up:
value |= BUTTON_UP
if self.down:
value |= BUTTON_DOWN
if self.left:
value |= BUTTON_LEFT
if self.right:
value |= BUTTON_RIGHT
self.captured_buttons = value
self.read_index = 0
def write_strobe(self, value: int) -> None:
self.strobe = (value & 1) == 1
if self.strobe:
self.capture_buttons()
def read_bit(self) -> int:
if self.strobe:
self.capture_buttons()
if self.read_index >= 8:
return 1
bit = (self.captured_buttons >> self.read_index) & 1
self.read_index += 1
return bit超出本步骤范围
- CpuBus $4016 路由
- pygame 键盘映射
- 控制器端口 2
- Famicom 扩展控制器
- DMC/控制器读取毛刺行为
运行本课
uv run pytest tests/chapter_07_controller_input/test_291_controller_capture_and_serial_read.py -v