292. Cpubus controller 4016
Route CpuBus $4016 reads/writes to controller port 1.
Lesson 292 of 356 · tests/chapter_07_controller_input/test_292_cpubus_controller_4016.py
File to update
emulator/bus/cpu_bus.pyWhy this step exists
The previous controller steps created a pure Controller object. Now the CPU bus must expose that controller through the NES memory-mapped controller port:
$4016 = controller port 1Normal NES polling sequence
write 1 to $4016
write 0 to $4016
read $4016 eight timesThose eight reads return
A, B, Select, Start, Up, Down, Left, RightReferences
https://www.nesdev.org/wiki/Standard_controller
https://www.nesdev.org/wiki/Controller_reading_codeSuggested implementation example
from emulator.input.controller import Controller
@dataclass
class CpuBus:
...
controller_1: Controller = field(default_factory=Controller)
def read(self, addr: int) -> int:
...
# Controller port 1
if addr == 0x4016:
return self.controller_1.read_bit()
# Controller port 2 / expansion input is out of scope for now.
if addr == 0x4017:
return 0
...
def write(self, addr: int, value: int) -> None:
...
# Controller port 1 strobe
if addr == 0x4016:
self.controller_1.write_strobe(value)
return
# $4017 writes are APU frame-counter writes, no-op for now.
if addr == 0x4017:
return
...Important distinction
$4016 read/write belongs to controller port 1.
$4017 read is controller port 2 / expansion input, out of scope for now.
$4017 write is APU frame counter, out of scope for now.Common misconception
"Controller input should be handled by pygame directly in CpuBus."No. CpuBus should only talk to the pure Controller object. Pygame keyboard mapping will later update Controller button booleans from a manual/frontend entry point.
Out of scope
- pygame keyboard mapping
- controller port 2 implementation
- Famicom expansion controllers
- DMC/controller read glitch behavior
- open bus upper-bit behavior
Run this lesson
uv run pytest tests/chapter_07_controller_input/test_292_cpubus_controller_4016.py -v