e-tipsmemo

ごった煮

NES Emulator in Rust : 4 Pad Input

e-tipsmemo.hatenablog.com

続き

スプライト実装をする前に、Nestestを通すために、PAD入力をサクッと実装する。
NES on FPGA PAD

キー入力

Piston windowでキー入力をとるのはEvent::Inputが来たとき。
本当はInputでパターンマッチした引数でうまくやれるかもしれんが、逆に長くなったので、やめた。

while let Some(event) = window.next() {
        match event {
            Event::Loop(Loop::Render(_)) => {
//略
            }
            Event::Input(_, _) => {
                if let Some(Button::Keyboard(key)) = event.press_args() {
                    match key {
                        Key::J => nes.pad.press(pad::PadButton::A),
                        Key::G => {
                            let tmp = !nes.ppu.grid_on.get();
                            nes.ppu.grid_on.set(tmp);
                        }
                        _ => {}
                    }
                }
                if let Some(Button::Keyboard(key)) = event.release_args() {
                    match key {
                        Key::J => nes.pad.release(pad::PadButton::A),
                        _ => {}
                    }
                }
            }
            _ => {}
        }

press/releaseで一旦NES内のPADステータス値を更新する。

PAD読み込み

NESプログラムがPADの状態を得るためには1Pなら0x4016、2Pなら0x4017に一定の操作を行う。

NES on FPGA PAD


0x01書き込み→0x00書き込みで読み込み可能状態になるのでPADか、NESの内部に状態(strobe)を持っておく。

pub fn write8(&self, addr: u16, data: u8) {
	match addr {
...
		0x4016 => {
			let strobe = (data & 0x01) != 0; //data == 1 -> true

			if strobe {
				self.pad.strobe_enable.set(true);
			} else {
				self.pad.shift_index.set(0);
				self.pad.strobe_enable.set(false);
			}
		}
....
	}
}


その後、0x4016(0x4017)をリードするたびに下から順にビットを判定し、1か0を返す。

bitflags! {
    #[derive(Default)]
    pub struct PadButton:u8{
        const A = 1 << 0;
        const B = 1 << 1;
        const START = 1 << 2;
        const SELECT = 1 << 3;
        const UP = 1 << 4;
        const DOWN = 1 << 5;
        const LEFT = 1 << 6;
        const RIGHT = 1 << 7;
    }
}
pub fn read8(&self, addr: u16) -> u8 {
	match addr {
....
		0x4016 => {
			let s = self.pad.shift_index.get();
			if s > 7 {
				return 1;
			}

			self.pad.shift_index.set(s + 1);

			let btn = if !self.pad.strobe_enable.get() {
				let scanbtn = pad::PadButton::from_bits_truncate(0x01 << s);
				self.pad.status.get().contains(scanbtn)
			} else {
				true
			};

			btn as u8
		}
		0x4017 => 0,
....
	}
}

2Pコントローラー(0x4017)を実装しないときは、しっかり0を返すべきで、ここで0以外の値を返していると、ずっと2P入力されていることになってスーパーマリオブラザーズでタイトル画面を操作できない。

nestest

前回の背景描写と合わせて、PAD操作ができれば、nestestを実行することができる。

f:id:katakanan:20210907224448p:plain

感想

操作ができるとちょっとうれしい。
まだスプライトを実装していないので、ギコ猫は動かせない。
事前調査で、piston_windowがController(Game pad?)も使えそうだったので、これで実装しているが、
Switch Pro ConをBluetooth接続したところ、なんの入力もされない!?
nestopiaでは使えるのに....
仕方ないので寄り道して、RustでSwitch Pro Controllerの入力をとる実装を入れるかもしれない。
(Switch持ってないのにPro conだけ買った。)