1 unstable release

0.0.1 May 16, 2022

#51 in #gameboy

Custom license

765KB
5K SLoC

Padme core

Pixel As Dot-Matrix Emulator

padme-core is a Gameboy emulator engine. It itself doesn't rely on libstd or on dynamic memory, which makes it easier to use in any embedded platforms or web assembly.

Tests

For fast unit / integration / doc tests:

cargo test

For more expensive tests, you can use:

cargo test -- --ignored

or run all tests with:

cargo test -- --include-ignored

Examples

  1. Create your hardware components: a screen, a speaker and a serial output

These components should be specific to your platform.

use padme_core::{AudioSpeaker, Button, Pixel, Rom, Screen, SerialOutput, System};

struct MyScreen {
    // ... your framebuffer implementation
}

impl Screen for MyScreen {
    fn set_pixel(&mut self, pixel: &Pixel, x: u8, y: u8) {
        // add pixel to your framebuffer
    }
}

struct MySpeaker {
    // ... your audio buffer implementation
}

impl AudioSpeaker for MySpeaker {
    fn set_samples(&mut self, left: f32, right: f32) {
        // add samples for left and right channels
    }
}

struct MySerialConsole {
}

impl SerialOutput for MySerialConsole {
    fn putchar(&mut self, ch: u8) {
        // add char to 
    }
}

Alternatively, if you don't need / want some of these components, it's possible to use empty versions:

use padme::default::{NoScreen, NoSerial, NoSpeaker};
  1. Load a rom
use padme_core::Rom;

let bin: Vec<u8> = std::fs::read("some_game.gb").expect("could not find game");
let mut rom = Rom::load(bin).unwrap();
  1. Create your emulator and run it
use std::time::Instant;
use std::thread::sleep;

let mut emulator = System::new(rom, MyScreen, MySerialConsole, MySpeaker);
// Set the number of frame per seconds
// This also sets the number of cycles needed per frame given the fixed CPU clock frequency
emulator.set_frame_rate(60);

while running {
    // We need to know how much time it took to display a frame
    let t0 = Instant::now();
    // This executes all the cycles needed to display one frame
    emulator.update_frame();
    // Deal with button inputs
    emulator.set_button(Button::A, a_pressed);
    emulator.set_button(Button::B, b_pressed);
    emulator.set_button(Button::Start, start_pressed);
    emulator.set_button(Button::Select, select_pressed);
    emulator.set_button(Button::Up, up_pressed);
    emulator.set_button(Button::Down, down_pressed);
    emulator.set_button(Button::Left, left_pressed);
    emulator.set_button(Button::Right, right_pressed);
    // Now we just need to wait the remaining time before the next frame
    // This is because we need to keep ~60 frames / second
    let frame_time = t0.elapsed();
    let min_frame_time = emulator.min_frame_time();
    if frame_time < min_frame_time {
        sleep(min_frame_time - frame_time);
    }
}

Alternatively, you may want to execute the steps yourself:

use padme_core::{CLOCK_SPEED};

let cycles_per_frame = CLOCK_SPEED / 60;
let mut cycles = 0u32;
while cycles < cycles_per_frame {
    cycles += emulator.step() as u32;
}
emulator.screen().update();

To see some implementations, check out padme-demo, a desktop demo or padme-browser, a web assembly version.

Features

  • no_std
  • Timer
  • DMA
  • CPU Dissassembler
  • Pixel Processor Unit with fifo
  • External Screen
  • External Serial port
  • Joypad
  • Rom, MBC1, MBC3
  • Integration tests
  • Audio processor unit

Todo

  • Add support for MBC2, MBC4, MBC5, MBC6, MBC7
  • Add unit tests for each module

Dependencies