291. Captura del mando y lectura serie

Capturar los botones del mando y leerlos en serie.

Lección 291 de 356 · tests/chapter_07_controller_input/test_291_controller_capture_and_serial_read.py

Archivos creados en este paso

emulator/input/controller.py

Por qué existe este paso

La CPU de la NES no recibe el estado del mando como un byte completo desde $4016. Recibe un bit de botón cada vez. Antes de conectar ese protocolo a CpuBus, el objeto puro Controller debe saber cómo:

Referencias

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

Término clave: strobe Un strobe es una señal de control escrita por la CPU. Para el mando de la NES, el strobe controla si el mando sigue capturando el estado en vivo de los botones o avanza por los bits serie ya capturados.

Ejemplo mínimo

controller.a = True
controller.write_strobe(1)
controller.write_strobe(0)
controller.read_bit()  # returns A bit

Concepto erróneo habitual

"The controller should return all buttons as one byte."

El emulador puede almacenar internamente los botones capturados como un solo byte, pero el protocolo orientado a la CPU lee un bit cada vez.

Ejemplo de implementación sugerido

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

Fuera de alcance

  • Enrutamiento de $4016 en CpuBus
  • Mapeo de teclado de pygame
  • Puerto de mando 2
  • Mandos de expansión de Famicom
  • Comportamiento de glitch de lectura de DMC/mando

Ejecutar esta lección

uv run pytest tests/chapter_07_controller_input/test_291_controller_capture_and_serial_read.py -v