This commit is contained in:
2025-11-02 19:29:17 -07:00
parent 957189cd0b
commit e966763fed
12 changed files with 228 additions and 149 deletions

6
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "user-apps/nes/Peanut-GB"]
path = user-apps/nes/Peanut-GB
url = https://github.com/deltabeard/Peanut-GB
[submodule "picolibc"] [submodule "picolibc"]
path = picolibc path = picolibc
url = https://github.com/picolibc/picolibc url = https://github.com/picolibc/picolibc
[submodule "user-apps/gboy/Peanut-GB"]
path = user-apps/gboy/Peanut-GB
url = https://github.com/deltabeard/Peanut-GB

20
Cargo.lock generated
View File

@@ -1306,6 +1306,16 @@ dependencies = [
"tinybmp", "tinybmp",
] ]
[[package]]
name = "gboy"
version = "0.1.0"
dependencies = [
"abi",
"bindgen",
"cc",
"embedded-graphics",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -1753,16 +1763,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "nes"
version = "0.1.0"
dependencies = [
"abi",
"bindgen",
"cc",
"embedded-graphics",
]
[[package]] [[package]]
name = "new_debug_unreachable" name = "new_debug_unreachable"
version = "1.0.6" version = "1.0.6"

View File

@@ -8,7 +8,7 @@ members = [
"user-apps/snake", "user-apps/snake",
"user-apps/gallery", "user-apps/gallery",
"user-apps/gif", "user-apps/gif",
"user-apps/nes", "user-apps/gboy",
] ]
[profile.release] [profile.release]

View File

@@ -1,5 +1,7 @@
kernel-dev board: kernel-dev board:
cargo run --bin kernel --features {{board}} cargo run --bin kernel --features {{board}}
kernel-release-probe board:
cargo run --bin kernel --profile release --features {{board}}
kernel-release board: kernel-release board:
cargo build --bin kernel --release --no-default-features --features {{board}} cargo build --bin kernel --release --no-default-features --features {{board}}
elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel
@@ -37,4 +39,4 @@ userapps: cbindgen
just userapp snake just userapp snake
just userapp gallery just userapp gallery
just userapp gif just userapp gif
just userapp nes just userapp gboy

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "nes" name = "gboy"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View File

@@ -47,6 +47,15 @@ fn bindgen() {
cc::Build::new() cc::Build::new()
.file("peanut_gb_stub.c") .file("peanut_gb_stub.c")
.include("Peanut-GB") .include("Peanut-GB")
// optimization flags
.flag("-O3") // max optimization
.flag("-ffast-math") // faster floating point, if any
.flag("-funroll-loops") // unroll small loops
// CPU/architecture flags (example for ARM Cortex-M33)
.flag("-mcpu=cortex-m33")
.flag("-mthumb")
// optional: strip debug info
.flag("-g0")
.compile("peanut_gb"); .compile("peanut_gb");
println!("cargo:rustc-link-search=Peanut-GB"); println!("cargo:rustc-link-search=Peanut-GB");

116
user-apps/gboy/src/main.rs Normal file
View File

@@ -0,0 +1,116 @@
#![no_std]
#![no_main]
#![allow(static_mut_refs)]
extern crate alloc;
use abi::{
Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{file_len, read_file},
get_key,
keyboard::{KeyCode, KeyState},
print, sleep,
};
use alloc::{vec, vec::Vec};
use core::{cell::LazyCell, ffi::c_void, mem::MaybeUninit, panic::PanicInfo};
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
mod peanut;
use peanut::gb_run_frame;
use crate::peanut::{
gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_init, gb_init_lcd, gb_rom_read, gb_s,
lcd_draw_line,
};
static mut DISPLAY: Display = Display;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
const PEANUT_A: u8 = 0x01;
const PEANUT_B: u8 = 0x02;
const PEANUT_SELECT: u8 = 0x04;
const PEANUT_START: u8 = 0x08;
const PEANUT_RIGHT: u8 = 0x10;
const PEANUT_LEFT: u8 = 0x20;
const PEANUT_UP: u8 = 0x40;
const PEANUT_DOWN: u8 = 0x80;
const GAME: &'static str = "/games/gameboy/zelda.gb";
static mut GAME_ROM: Option<Vec<u8>> = None;
#[repr(C)]
struct Priv {}
pub fn main() {
print!("Starting Gameboy app");
let size = file_len(GAME);
unsafe { GAME_ROM = Some(vec![0_u8; size]) };
let read = read_file(GAME, 0, unsafe { GAME_ROM.as_mut().unwrap() });
assert!(size == read);
print!("Rom size: {}", read);
let mut priv_ = MaybeUninit::<Priv>::uninit();
let mut gb = MaybeUninit::<gb_s>::uninit();
let init_status = unsafe {
gb_init(
gb.as_mut_ptr(),
Some(gb_rom_read),
Some(gb_cart_ram_read),
Some(gb_cart_ram_write),
Some(gb_error),
priv_.as_mut_ptr() as *mut c_void,
)
};
print!("gb init status: {}", init_status);
unsafe { gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line)) };
loop {
let event = get_key();
if event.state != KeyState::Idle {
match event.key {
KeyCode::Esc => return,
KeyCode::Tab => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_START;
},
KeyCode::Del => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_SELECT;
},
KeyCode::Enter => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_A;
},
KeyCode::Backspace => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_B;
},
KeyCode::JoyUp => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_UP;
},
KeyCode::JoyDown => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_DOWN;
},
KeyCode::JoyLeft => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_LEFT;
},
KeyCode::JoyRight => unsafe {
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !PEANUT_RIGHT;
},
_ => (),
};
};
unsafe { gb_run_frame(gb.as_mut_ptr()) };
}
}

View File

@@ -0,0 +1,85 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::{DISPLAY, GAME_ROM};
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
use abi::{display::Pixel565, fs::read_file};
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
pub const GBOY_WIDTH: usize = 160;
pub const GBOY_HEIGHT: usize = 144;
pub unsafe extern "C" fn gb_rom_read(gb: *mut gb_s, addr: u32) -> u8 {
unsafe { GAME_ROM.as_ref().unwrap()[addr as usize] }
}
pub unsafe extern "C" fn gb_cart_ram_read(gb: *mut gb_s, addr: u32) -> u8 {
0
}
pub unsafe extern "C" fn gb_cart_ram_write(gb: *mut gb_s, addr: u32, val: u8) {}
pub unsafe extern "C" fn gb_error(gb: *mut gb_s, err: gb_error_e, addr: u16) {}
const NUM_PALETTES: usize = 3;
const SHADES_PER_PALETTE: usize = 4;
const PALETTES: [[Rgb565; SHADES_PER_PALETTE]; NUM_PALETTES] = [
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // BG
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // OBJ0
[
Rgb565::new(8, 24, 32),
Rgb565::new(52, 104, 86),
Rgb565::new(136, 192, 112),
Rgb565::new(224, 248, 208),
], // OBJ1
];
pub unsafe extern "C" fn lcd_draw_line(_gb: *mut gb_s, pixels: *const u8, line: u8) {
if line < GBOY_HEIGHT as u8 {
let pixels = unsafe { core::slice::from_raw_parts(pixels, GBOY_WIDTH) };
let y = line as u16;
for (x, &p) in pixels.iter().enumerate() {
let palette_idx = ((p & 0xF0) >> 4) as usize;
let shade_idx = (p & 0x03) as usize;
let color = PALETTES
.get(palette_idx)
.and_then(|pal| pal.get(shade_idx))
.copied()
.unwrap_or(Rgb565::new(0, 0, 0));
let sx = (x as u16) * 2;
let sy = y * 2;
draw_color(color, sx, sy);
draw_color(color, sx + 1, sy);
draw_color(color, sx, sy + 1);
draw_color(color, sx + 1, sy + 1);
}
}
}
fn draw_color(color: Rgb565, x: u16, y: u16) {
let mut pixel = Pixel565::default();
pixel.0 = Point::new(x.into(), y.into());
pixel.1 = color;
unsafe {
pixel.draw(&mut DISPLAY).unwrap();
}
}

View File

@@ -1,88 +0,0 @@
#![no_std]
#![no_main]
#![allow(static_mut_refs)]
extern crate alloc;
use abi::{
Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{file_len, read_file},
get_key,
keyboard::{KeyCode, KeyState},
print, sleep,
};
use alloc::{vec, vec::Vec};
use core::{ffi::c_void, mem::MaybeUninit, panic::PanicInfo};
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
mod peanut;
use peanut::gb_run_frame;
use crate::peanut::{
gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_init, gb_init_lcd, gb_rom_read, gb_s,
lcd_draw_line,
};
static mut DISPLAY: Display = Display;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
const GAME: &'static str = "/games/nes/game.nes";
#[repr(C)]
struct Priv {
rom: Vec<u8>,
}
pub fn main() {
print!("Starting Nes app");
let size = file_len(GAME);
let mut priv_ = MaybeUninit::<Priv>::uninit();
// let read = unsafe {
// let priv_ptr = priv_.as_mut_ptr();
// (*priv_ptr).rom = Vec::with_capacity(size);
// read_file(GAME, 0, &mut (*priv_ptr).rom)
// };
// print!("read: {}, file size: {}", read, size);
// assert!(read == size);
let mut gb = MaybeUninit::<gb_s>::uninit();
let init_status = unsafe {
gb_init(
gb.as_mut_ptr(),
Some(gb_rom_read),
Some(gb_cart_ram_read),
Some(gb_cart_ram_write),
Some(gb_error),
priv_.as_mut_ptr() as *mut c_void,
)
};
print!("gb init status: {}", init_status);
unsafe { gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line)) };
loop {
let event = get_key();
if event.state != KeyState::Idle {
let key = match event.key {
KeyCode::Esc => return,
_ => (),
};
};
unsafe { gb_run_frame(gb.as_mut_ptr()) };
}
}

View File

@@ -1,45 +0,0 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::DISPLAY;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
use abi::{display::Pixel565, fs::read_file};
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
pub const NES_WIDTH: usize = 256;
pub const NES_HEIGHT: usize = 240;
pub unsafe extern "C" fn gb_rom_read(gb: *mut gb_s, addr: u32) -> u8 {
let mut buf = [0_u8; 1];
read_file("/games/nes/rom.nes", addr as usize, &mut buf);
buf[0]
}
pub unsafe extern "C" fn gb_cart_ram_read(gb: *mut gb_s, addr: u32) -> u8 {
0
}
pub unsafe extern "C" fn gb_cart_ram_write(gb: *mut gb_s, addr: u32, val: u8) {}
pub unsafe extern "C" fn gb_error(gb: *mut gb_s, err: gb_error_e, addr: u16) {}
pub unsafe extern "C" fn lcd_draw_line(gb: *mut gb_s, pixels: *const u8, line: u8) {
unsafe {
if line < NES_HEIGHT as u8 {
let pixels = core::slice::from_raw_parts(pixels, NES_WIDTH);
let mut x = 0;
for p in pixels {
let mut pixel = Pixel565::default();
pixel.0 = Point::new(x, line.into());
pixel.1 = Rgb565::new(*p, 10, 10);
pixel.draw(&mut DISPLAY).unwrap();
x += 1;
}
}
}
}