mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-10 02:51:31 +11:00
remove proc macro usage and finish out the mmio (#125)
* . * more updates, much more to do soon. * hello world works again. * fix key interrupts. * remove the old book * don't forget timers. * we can just use search, duh. * cleanup * more bios * finished most mmio, finished bios. * dump some old macros that shouldn't have been pub to begin with. * Update README.md * for now, just make this a u16 * timers * fix hello_world * timer fix * docs on timers * block resetting work ram for now. * put preserves_flags on bios calls as appropriate * add a code page 437 tile sheet. * docs. * sound :( it's all bad but we'll fix it in 0.5 * move most backup files back into the lib. * CI might work now? * fix up non-serial examples. * oops, gotta check out the repo. * readme.
This commit is contained in:
parent
8efef6ebc5
commit
fd3a308e8a
|
@ -1,3 +1,9 @@
|
|||
[build]
|
||||
target = "thumbv4t-none-eabi"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
|
||||
[target.thumbv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tlinker.ld"]
|
||||
runner = "mgba"
|
||||
|
|
50
.github/workflows/ci.yaml
vendored
50
.github/workflows/ci.yaml
vendored
|
@ -12,54 +12,30 @@ jobs:
|
|||
rust:
|
||||
- { toolchain: nightly }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Apt Dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential libssl-dev binutils-arm-none-eabi
|
||||
run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust.toolchain }}
|
||||
default: true
|
||||
|
||||
- name: Install Rust Source
|
||||
run: rustup component add rust-src
|
||||
- name: Install cargo-make crate
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-make
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
- name: Install gbafix crate
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: gbafix
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
- uses: actions/checkout@v2
|
||||
- name: Make Test
|
||||
|
||||
- name: Build The Examples
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust.toolchain }}
|
||||
command: make
|
||||
args: test
|
||||
- name: Make Release
|
||||
command: build
|
||||
args: --examples
|
||||
|
||||
- name: Check That Docs Compile
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust.toolchain }}
|
||||
command: make
|
||||
args: justrelease
|
||||
#build-book:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: stable
|
||||
# default: true
|
||||
# - name: Install mdbook crate
|
||||
# uses: actions-rs/install@v0.1
|
||||
# with:
|
||||
# crate: mdbook
|
||||
# version: latest
|
||||
# use-tool-cache: true
|
||||
# - uses: actions/checkout@v2
|
||||
# - name: Build the book
|
||||
# run: cd book && mdbook build
|
||||
command: test
|
||||
args: --doc
|
||||
|
|
21
Cargo.toml
21
Cargo.toml
|
@ -12,27 +12,24 @@ license = "Zlib OR Apache-2.0 OR MIT"
|
|||
publish = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["serial"]
|
||||
serial = ["embedded-hal", "nb"]
|
||||
|
||||
[dependencies]
|
||||
voladdress = { version = "0.4" }
|
||||
gba-proc-macro = "0.5"
|
||||
embedded-hal = { version = "0.2.4", optional = true }
|
||||
nb = { version = "1.0.0", optional = true }
|
||||
nb = { version = "1", optional = true }
|
||||
|
||||
[profile.dev]
|
||||
lto = false
|
||||
panic = "abort"
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
|
||||
[[example]]
|
||||
name = "uart_echo"
|
||||
required-features = ["serial"]
|
||||
#[[example]]
|
||||
#name = "uart_echo"
|
||||
#required-features = ["serial"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv6m-none-eabi"
|
||||
targets = []
|
||||
|
|
33
README.md
33
README.md
|
@ -16,7 +16,9 @@ A crate to make GBA programming easy.
|
|||
Currently we don't have as much documentation as we'd like.
|
||||
If you check out the [awesome-gbadev](https://github.com/gbdev/awesome-gbadev) repository they have many resources, though most are oriented towards C.
|
||||
|
||||
## First Time Setup
|
||||
## System Setup
|
||||
|
||||
There's a few extra things to install that you just need to do once per system.
|
||||
|
||||
Building for the GBA requires Nightly rust, and also uses the `build-std` feature, so you'll need the rust source available.
|
||||
|
||||
|
@ -49,21 +51,28 @@ cargo install cargo-make
|
|||
cargo install gbafix
|
||||
```
|
||||
|
||||
<!--
|
||||
## First Time Setup
|
||||
## Project Setup
|
||||
|
||||
Writing a Rust program for the GBA requires a fair amount of special setup. All
|
||||
of the steps are detailed for you in the [Development
|
||||
Setup](https://rust-console.github.io/gba/development-setup.html) part at the
|
||||
start of the book.
|
||||
To build a GBA project, you'll want to copy the `.cargo/config.toml` file from this repo into your own project.
|
||||
|
||||
If you've done the described global setup once before and just want to get a new
|
||||
project started quickly we got you covered:
|
||||
Then use one of the examples as a guide to get started.
|
||||
|
||||
```sh
|
||||
curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME
|
||||
When you build your project, cargo will put outputs in the `target/thumbv4t-none-eabi/` directory.
|
||||
This includes the `debug/` and `release/` sub-directories.
|
||||
Your binary will be in there, but it'll be in ELF format.
|
||||
You can run this directly in an emulator such as [mGBA](https://mgba.io/) if you'd like.
|
||||
|
||||
When you're ready to convert your program into a "proper" GBA rom you'll need to run an `objcopy`
|
||||
to extract just the raw binary data:
|
||||
```
|
||||
-->
|
||||
arm-none-eabi-objcopy -O binary [RUST_BINARY_NAME] [ROM_NAME].gba
|
||||
```
|
||||
|
||||
Then you'll need to patch the header data with `gbafix`
|
||||
```
|
||||
gbafix [ROM_NAME].gba
|
||||
```
|
||||
And you'll be all done!
|
||||
|
||||
# Contribution
|
||||
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
use gba::{
|
||||
io::{
|
||||
background::{BackgroundControlSetting, BG0CNT},
|
||||
display::{DisplayControlSetting, DISPCNT},
|
||||
},
|
||||
palram::index_palram_bg_4bpp,
|
||||
vram::{text::TextScreenblockEntry, Tile4bpp, CHAR_BASE_BLOCKS, SCREEN_BASE_BLOCKS},
|
||||
Color,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
pub const WHITE: Color = Color::from_rgb(31, 31, 31);
|
||||
pub const LIGHT_GRAY: Color = Color::from_rgb(25, 25, 25);
|
||||
pub const DARK_GRAY: Color = Color::from_rgb(15, 15, 15);
|
||||
// bg palette
|
||||
index_palram_bg_4bpp(0, 1).write(WHITE);
|
||||
index_palram_bg_4bpp(0, 2).write(LIGHT_GRAY);
|
||||
index_palram_bg_4bpp(0, 3).write(DARK_GRAY);
|
||||
// bg tiles
|
||||
set_bg_tile_4bpp(0, 0, ALL_TWOS);
|
||||
set_bg_tile_4bpp(0, 1, ALL_THREES);
|
||||
// screenblock
|
||||
let light_entry = TextScreenblockEntry::from_tile_id(0);
|
||||
let dark_entry = TextScreenblockEntry::from_tile_id(1);
|
||||
checker_screenblock(8, light_entry, dark_entry);
|
||||
// bg0 control
|
||||
BG0CNT.write(BackgroundControlSetting::new().with_screen_base_block(8));
|
||||
// Display Control
|
||||
DISPCNT.write(DisplayControlSetting::new().with_bg0(true));
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub const ALL_TWOS: Tile4bpp = Tile4bpp([
|
||||
0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222,
|
||||
]);
|
||||
|
||||
pub const ALL_THREES: Tile4bpp = Tile4bpp([
|
||||
0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333, 0x33333333,
|
||||
]);
|
||||
|
||||
pub fn set_bg_tile_4bpp(charblock: usize, index: usize, tile: Tile4bpp) {
|
||||
assert!(charblock < 4);
|
||||
assert!(index < 512);
|
||||
unsafe { CHAR_BASE_BLOCKS.index(charblock).cast::<Tile4bpp>().offset(index as isize).write(tile) }
|
||||
}
|
||||
|
||||
pub fn checker_screenblock(slot: usize, a_entry: TextScreenblockEntry, b_entry: TextScreenblockEntry) {
|
||||
let mut p = unsafe { SCREEN_BASE_BLOCKS.index(slot).cast::<TextScreenblockEntry>() };
|
||||
let mut checker = true;
|
||||
for _row in 0..32 {
|
||||
for _col in 0..32 {
|
||||
unsafe {
|
||||
p.write(if checker { a_entry } else { b_entry });
|
||||
p = p.offset(1);
|
||||
}
|
||||
checker = !checker;
|
||||
}
|
||||
checker = !checker;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use gba::{
|
||||
io::display::{DisplayControlSetting, DisplayMode, DISPCNT},
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::fmt::Write;
|
||||
use gba::mgba::{MGBADebug, MGBADebugLevel};
|
||||
|
||||
if let Some(mut mgba) = MGBADebug::new() {
|
||||
let _ = write!(mgba, "{}", info);
|
||||
mgba.send(MGBADebugLevel::Fatal);
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
Mode3::write_pixel(120, 80, Color::from_rgb(31, 0, 0));
|
||||
Mode3::write_pixel(136, 80, Color::from_rgb(0, 31, 0));
|
||||
Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31));
|
||||
panic!("fumoffu!");
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
unsafe {
|
||||
(0x400_0000 as *mut u16).write_volatile(0x0403);
|
||||
(0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
||||
(0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
||||
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
static __IRQ_HANDLER: extern "C" fn() = irq_handler;
|
||||
|
||||
extern "C" fn irq_handler() {}
|
|
@ -1,23 +1,19 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
use gba::{
|
||||
fatal,
|
||||
io::{
|
||||
display::{DisplayControlSetting, DisplayMode, DISPCNT, VBLANK_SCANLINE, VCOUNT},
|
||||
keypad::read_key_input,
|
||||
},
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused)]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
// This kills the emulation with a message if we're running inside an
|
||||
// emulator we support (mGBA or NO$GBA), or just crashes the game if we
|
||||
// aren't.
|
||||
fatal!("{}", info);
|
||||
//fatal!("{}", info);
|
||||
|
||||
loop {
|
||||
DISPCNT.read();
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
|
@ -25,7 +21,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
|
|||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vblank() {
|
||||
while VCOUNT.read() < VBLANK_SCANLINE {}
|
||||
while VCOUNT.read() < 160 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
|
@ -33,30 +29,29 @@ pub fn spin_until_vblank() {
|
|||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vdraw() {
|
||||
while VCOUNT.read() >= VBLANK_SCANLINE {}
|
||||
while VCOUNT.read() >= 160 {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting =
|
||||
DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
#[no_mangle]
|
||||
pub fn main() -> ! {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
|
||||
let mut px = Mode3::WIDTH / 2;
|
||||
let mut py = Mode3::HEIGHT / 2;
|
||||
let mut color = Color::from_rgb(31, 0, 0);
|
||||
let mut px = mode3::WIDTH / 2;
|
||||
let mut py = mode3::HEIGHT / 2;
|
||||
let mut color = Color::from_rgb(31, 3, 1);
|
||||
|
||||
loop {
|
||||
// read our keys for this frame
|
||||
let this_frame_keys = read_key_input();
|
||||
let keys: Keys = KEYINPUT.read().into();
|
||||
|
||||
// adjust game state and wait for vblank
|
||||
px = px.wrapping_add((2 * this_frame_keys.x_tribool() as i32) as usize);
|
||||
py = py.wrapping_add((2 * this_frame_keys.y_tribool() as i32) as usize);
|
||||
if this_frame_keys.l() {
|
||||
px = px.wrapping_add((2 * keys.x_signum()) as usize);
|
||||
py = py.wrapping_add((2 * keys.y_signum()) as usize);
|
||||
if keys.l() {
|
||||
color = Color(color.0.rotate_left(5));
|
||||
}
|
||||
if this_frame_keys.r() {
|
||||
if keys.r() {
|
||||
color = Color(color.0.rotate_right(5));
|
||||
}
|
||||
|
||||
|
@ -64,17 +59,18 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|||
spin_until_vblank();
|
||||
|
||||
// draw the new game and wait until the next frame starts.
|
||||
if px >= Mode3::WIDTH || py >= Mode3::HEIGHT {
|
||||
if (px + 1) >= mode3::WIDTH || (py + 1) >= mode3::HEIGHT {
|
||||
// out of bounds, reset the screen and position.
|
||||
Mode3::dma_clear_to(Color::from_rgb(0, 0, 0));
|
||||
px = Mode3::WIDTH / 2;
|
||||
py = Mode3::HEIGHT / 2;
|
||||
mode3::dma3_clear_to(Color::from_rgb(0, 0, 0));
|
||||
px = mode3::WIDTH / 2;
|
||||
py = mode3::HEIGHT / 2;
|
||||
color = Color(color.0.rotate_left(7));
|
||||
} else {
|
||||
// draw the new part of the line
|
||||
Mode3::write(px, py, color);
|
||||
Mode3::write(px, py + 1, color);
|
||||
Mode3::write(px + 1, py, color);
|
||||
Mode3::write(px + 1, py + 1, color);
|
||||
mode3::bitmap_xy(px, py).write(color);
|
||||
mode3::bitmap_xy(px, py + 1).write(color);
|
||||
mode3::bitmap_xy(px + 1, py).write(color);
|
||||
mode3::bitmap_xy(px + 1, py + 1).write(color);
|
||||
}
|
||||
|
||||
// now we wait again
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
#![no_main]
|
||||
#![feature(isa_attribute)]
|
||||
|
||||
use gba::{
|
||||
io::{
|
||||
display::{DisplayControlSetting, DisplayMode, DisplayStatusSetting, DISPCNT, DISPSTAT},
|
||||
irq::{self, IrqEnableSetting, IrqFlags, BIOS_IF, IE, IME},
|
||||
keypad::read_key_input,
|
||||
timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L},
|
||||
},
|
||||
vram::bitmap::Mode3,
|
||||
Color,
|
||||
};
|
||||
use gba::prelude::*;
|
||||
|
||||
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||
const RED: Color = Color::from_rgb(31, 0, 0);
|
||||
|
@ -26,50 +18,50 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||
|
||||
fn start_timers() {
|
||||
let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16;
|
||||
const TIMER_SETTINGS: TimerControlSetting =
|
||||
TimerControlSetting::new().with_overflow_irq(true).with_enabled(true);
|
||||
const TIMER_SETTINGS: TimerControl =
|
||||
TimerControl::new().with_irq_on_overflow(true).with_enabled(true);
|
||||
|
||||
TM0CNT_L.write(init_val);
|
||||
TM0CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU1024));
|
||||
TM1CNT_L.write(init_val);
|
||||
TM1CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU64));
|
||||
TIMER0_RELOAD.write(init_val);
|
||||
TIMER0_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(3));
|
||||
TIMER1_RELOAD.write(init_val);
|
||||
TIMER1_CONTROL.write(TIMER_SETTINGS.with_prescaler_selection(1));
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
DISPCNT.write(DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true));
|
||||
Mode3::clear_to(BLACK);
|
||||
#[no_mangle]
|
||||
fn main() -> ! {
|
||||
DISPCNT.write(DisplayControl::new().with_display_mode(3).with_display_bg2(true));
|
||||
mode3::dma3_clear_to(BLACK);
|
||||
|
||||
// Set the IRQ handler to use.
|
||||
irq::set_irq_handler(irq_handler);
|
||||
unsafe { USER_IRQ_HANDLER.write(Some(irq_handler_a32)) };
|
||||
|
||||
// Enable all interrupts that are set in the IE register.
|
||||
unsafe { IME.write(IrqEnableSetting::IRQ_YES) };
|
||||
unsafe { IME.write(true) };
|
||||
|
||||
// Request that VBlank, HBlank and VCount will generate IRQs.
|
||||
const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||
.with_vblank_irq_enable(true)
|
||||
.with_hblank_irq_enable(true)
|
||||
.with_vcounter_irq_enable(true);
|
||||
const DISPLAY_SETTINGS: DisplayStatus = DisplayStatus::new()
|
||||
.with_vblank_irq_enabled(true)
|
||||
.with_hblank_irq_enabled(true)
|
||||
.with_vcount_irq_enabled(true);
|
||||
DISPSTAT.write(DISPLAY_SETTINGS);
|
||||
|
||||
// Start two timers with overflow IRQ generation.
|
||||
start_timers();
|
||||
|
||||
loop {
|
||||
let this_frame_keys = read_key_input();
|
||||
let this_frame_keys: Keys = KEYINPUT.read().into();
|
||||
|
||||
// The VBlank IRQ must be enabled at minimum, or else the CPU will halt
|
||||
// at the call to vblank_interrupt_wait() as the VBlank IRQ will never
|
||||
// be triggered.
|
||||
let mut flags = IrqFlags::new().with_vblank(true);
|
||||
let mut flags = InterruptFlags::new().with_vblank(true);
|
||||
|
||||
// Enable interrupts based on key input.
|
||||
if this_frame_keys.a() {
|
||||
flags = flags.with_hblank(true);
|
||||
}
|
||||
if this_frame_keys.b() {
|
||||
flags = flags.with_vcounter(true);
|
||||
flags = flags.with_vcount(true);
|
||||
}
|
||||
if this_frame_keys.l() {
|
||||
flags = flags.with_timer0(true);
|
||||
|
@ -83,7 +75,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|||
// Puts the CPU into low power mode until a VBlank IRQ is received. This
|
||||
// will yield considerably better power efficiency as opposed to spin
|
||||
// waiting.
|
||||
gba::bios::vblank_interrupt_wait();
|
||||
unsafe { VBlankIntrWait() };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,20 +83,31 @@ static mut PIXEL: usize = 0;
|
|||
|
||||
fn write_pixel(color: Color) {
|
||||
unsafe {
|
||||
Mode3::write(PIXEL, 0, color);
|
||||
PIXEL = (PIXEL + 1) % (Mode3::WIDTH * Mode3::HEIGHT);
|
||||
(0x0600_0000 as *mut Color).wrapping_offset(PIXEL as isize).write_volatile(color);
|
||||
PIXEL += 1;
|
||||
if PIXEL == (mode3::WIDTH * mode3::HEIGHT) {
|
||||
PIXEL = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn irq_handler(flags: IrqFlags) {
|
||||
#[instruction_set(arm::a32)]
|
||||
extern "C" fn irq_handler_a32() {
|
||||
// we just use this a32 function to jump over back to t32 code.
|
||||
irq_handler_t32()
|
||||
}
|
||||
|
||||
fn irq_handler_t32() {
|
||||
let flags = IRQ_PENDING.read();
|
||||
|
||||
if flags.vblank() {
|
||||
vblank_handler();
|
||||
}
|
||||
if flags.hblank() {
|
||||
hblank_handler();
|
||||
}
|
||||
if flags.vcounter() {
|
||||
vcounter_handler();
|
||||
if flags.vcount() {
|
||||
vcount_handler();
|
||||
}
|
||||
if flags.timer0() {
|
||||
timer0_handler();
|
||||
|
@ -119,29 +122,29 @@ fn vblank_handler() {
|
|||
|
||||
// When using `interrupt_wait()` or `vblank_interrupt_wait()`, IRQ handlers must acknowledge
|
||||
// the IRQ on the BIOS Interrupt Flags register.
|
||||
unsafe { BIOS_IF.write(BIOS_IF.read().with_vblank(true)) };
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_vblank(true)) };
|
||||
}
|
||||
|
||||
fn hblank_handler() {
|
||||
write_pixel(GREEN);
|
||||
|
||||
unsafe { BIOS_IF.write(BIOS_IF.read().with_hblank(true)) };
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_hblank(true)) };
|
||||
}
|
||||
|
||||
fn vcounter_handler() {
|
||||
fn vcount_handler() {
|
||||
write_pixel(RED);
|
||||
|
||||
unsafe { BIOS_IF.write(BIOS_IF.read().with_vcounter(true)) };
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_vcount(true)) };
|
||||
}
|
||||
|
||||
fn timer0_handler() {
|
||||
write_pixel(YELLOW);
|
||||
|
||||
unsafe { BIOS_IF.write(BIOS_IF.read().with_timer0(true)) };
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_timer0(true)) };
|
||||
}
|
||||
|
||||
fn timer1_handler() {
|
||||
write_pixel(PINK);
|
||||
|
||||
unsafe { BIOS_IF.write(BIOS_IF.read().with_timer1(true)) };
|
||||
unsafe { INTR_WAIT_ACKNOWLEDGE.write(INTR_WAIT_ACKNOWLEDGE.read().with_timer1(true)) };
|
||||
}
|
||||
|
|
|
@ -1,31 +1,20 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
use core::cmp;
|
||||
use gba::{
|
||||
fatal,
|
||||
io::{
|
||||
display::{DisplayControlSetting, DisplayMode, DISPCNT},
|
||||
timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L},
|
||||
},
|
||||
save::*,
|
||||
vram::bitmap::Mode3,
|
||||
warn, Color,
|
||||
};
|
||||
use gba::{fatal, prelude::*, save::*, warn};
|
||||
|
||||
fn set_screen_color(r: u16, g: u16, b: u16) {
|
||||
const SETTING: DisplayControlSetting =
|
||||
DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||
fn set_screen_color(r: u8, g: u8, b: u8) {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
Mode3::dma_clear_to(Color::from_rgb(r, g, b));
|
||||
mode3::dma3_clear_to(Color::from_rgb(r, g, b));
|
||||
}
|
||||
fn set_screen_progress(cur: usize, max: usize) {
|
||||
let lines = cur * (Mode3::WIDTH / max);
|
||||
let lines = cur * (mode3::WIDTH / max);
|
||||
let color = Color::from_rgb(0, 31, 0);
|
||||
for x in 0..lines {
|
||||
for y in 0..Mode3::HEIGHT {
|
||||
Mode3::write(x, y, color);
|
||||
for y in 0..mode3::HEIGHT {
|
||||
mode3::bitmap_xy(x, y).write(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +22,7 @@ fn set_screen_progress(cur: usize, max: usize) {
|
|||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
set_screen_color(31, 0, 0);
|
||||
fatal!("{}", info);
|
||||
fatal!("{}", info)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -63,18 +52,18 @@ fn check_status<T>(r: Result<T, Error>) -> T {
|
|||
}
|
||||
|
||||
fn setup_timers() {
|
||||
TM0CNT_L.write(0);
|
||||
TM1CNT_L.write(0);
|
||||
TIMER0_RELOAD.write(0);
|
||||
TIMER1_RELOAD.write(0);
|
||||
|
||||
let ctl = TimerControlSetting::new().with_tick_rate(TimerTickRate::CPU1024).with_enabled(true);
|
||||
TM0CNT_H.write(ctl);
|
||||
let ctl = TimerControlSetting::new().with_tick_rate(TimerTickRate::Cascade).with_enabled(true);
|
||||
TM1CNT_H.write(ctl);
|
||||
let ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true);
|
||||
TIMER0_CONTROL.write(ctl);
|
||||
let ctl = TimerControl::new().with_chained_counting(true).with_enabled(true);
|
||||
TIMER1_CONTROL.write(ctl);
|
||||
}
|
||||
|
||||
// I'm fully aware how slow this is. But this is just example code, so, eh.
|
||||
fn get_timer_secs() -> f32 {
|
||||
let raw_timer = (TM1CNT_L.read() as u32) << 16 | TM0CNT_L.read() as u32;
|
||||
let raw_timer = (TIMER1_COUNTER.read() as u32) << 16 | TIMER0_COUNTER.read() as u32;
|
||||
(raw_timer as f32 * 1024.0) / ((1 << 24) as f32)
|
||||
}
|
||||
macro_rules! output {
|
||||
|
@ -128,8 +117,8 @@ fn do_test(seed: Rng, offset: usize, len: usize, block_size: usize) -> Result<()
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
#[no_mangle]
|
||||
fn main() -> ! {
|
||||
// set a pattern to show that the ROM is working at all.
|
||||
set_screen_color(31, 31, 0);
|
||||
|
||||
|
|
7
src/art.rs
Normal file
7
src/art.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
//! This module has constants for various tile sheets in compressed form.
|
||||
//!
|
||||
//! Depending on the tile sheet, the optimal compression varies. The docs of
|
||||
//! each constant explain how to decompress the data correctly.
|
||||
|
||||
mod cga_8x8_thick;
|
||||
pub use cga_8x8_thick::*;
|
98
src/art/cga_8x8_thick.rs
Normal file
98
src/art/cga_8x8_thick.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
/// The CGA [Code Page 437][cp437] type face, with thick lines.
|
||||
///
|
||||
/// There's 256 tiles, packed down to 1bpp. To decompress this easily you can
|
||||
/// call [`BitUnPack`] as follows:
|
||||
/// ```no_run
|
||||
/// # use gba::prelude::*;
|
||||
/// # use gba::art::CGA_8X8_THICK;
|
||||
/// # use core::convert::TryFrom;
|
||||
/// # use core::mem::size_of_val;
|
||||
/// let info = UnpackInfo {
|
||||
/// source_data_len_bytes: size_of_val(&CGA_8X8_THICK) as usize,
|
||||
/// source_unit_bit_width: 1,
|
||||
/// // this assumes we want to unpack to 4bpp tiles
|
||||
/// destination_unit_bit_width: 4,
|
||||
/// data_offset: 0,
|
||||
/// };
|
||||
/// // our example unpacks directly to the start of VRAM.
|
||||
/// unsafe { BitUnPack(CGA_8X8_THICK.as_ptr(), 0x0600_0000 as *mut u32, &info) };
|
||||
/// ```
|
||||
///
|
||||
/// I am not a lawyer, but type faces are not protected by copyright in the USA.
|
||||
/// The copyright status of font faces [varies by country][wp]. If it matters,
|
||||
/// CGA was first released in 1981.
|
||||
///
|
||||
/// [cp437]: https://en.wikipedia.org/wiki/Code_page_437
|
||||
///
|
||||
/// [wp]:
|
||||
/// https://en.wikipedia.org/wiki/Intellectual_property_protection_of_typefaces
|
||||
pub const CGA_8X8_THICK: [u32; 512] = [
|
||||
// Note(Lokathor): I generated this by (1) converting the type face file from
|
||||
// an RGB PNG to Indexed Color PNG using GIMP, (2) running `grit
|
||||
// CGA8x8thick-indexed.png -gB1` to output an assembly file full of the
|
||||
// compressed data `.word` entries, (3) copying all those words into Rust.
|
||||
0x00000000, 0x00000000, 0x81A5817E, 0x7E8199BD, 0xFFDBFF7E, 0x7EFFE7C3, 0x7F7F7F36, 0x00081C3E,
|
||||
0x7F3E1C08, 0x00081C3E, 0x7F1C3E1C, 0x1C086B7F, 0x3E1C0808, 0x1C083E7F, 0x3C180000, 0x0000183C,
|
||||
0xC3E7FFFF, 0xFFFFE7C3, 0x42663C00, 0x003C6642, 0xBD99C3FF, 0xFFC399BD, 0xBEF0E0F0, 0x1E333333,
|
||||
0x6666663C, 0x187E183C, 0x0CFCCCFC, 0x070F0E0C, 0xC6FEC6FE, 0x0367E6C6, 0xE73CDB18, 0x18DB3CE7,
|
||||
0x7F1F0701, 0x0001071F, 0x7F7C7040, 0x0040707C, 0x187E3C18, 0x183C7E18, 0x66666666, 0x00660066,
|
||||
0xDEDBDBFE, 0x00D8D8D8, 0x361CC67C, 0x1E331C36, 0x00000000, 0x007E7E7E, 0x187E3C18, 0xFF183C7E,
|
||||
0x187E3C18, 0x00181818, 0x18181818, 0x00183C7E, 0x7F301800, 0x00001830, 0x7F060C00, 0x00000C06,
|
||||
0x03030000, 0x00007F03, 0xFF662400, 0x00002466, 0x7E3C1800, 0x0000FFFF, 0x7EFFFF00, 0x0000183C,
|
||||
0x00000000, 0x00000000, 0x0C1E1E0C, 0x000C000C, 0x00363636, 0x00000000, 0x367F3636, 0x0036367F,
|
||||
0x1E033E0C, 0x000C1F30, 0x18336300, 0x0063660C, 0x6E1C361C, 0x006E333B, 0x00030606, 0x00000000,
|
||||
0x06060C18, 0x00180C06, 0x18180C06, 0x00060C18, 0xFF3C6600, 0x0000663C, 0x3F0C0C00, 0x00000C0C,
|
||||
0x00000000, 0x060C0C00, 0x3F000000, 0x00000000, 0x00000000, 0x000C0C00, 0x0C183060, 0x00010306,
|
||||
0x7B73633E, 0x003E676F, 0x0C0C0E0C, 0x003F0C0C, 0x1C30331E, 0x003F3306, 0x1C30331E, 0x001E3330,
|
||||
0x33363C38, 0x0078307F, 0x301F033F, 0x001E3330, 0x1F03061C, 0x001E3333, 0x1830333F, 0x000C0C0C,
|
||||
0x1E33331E, 0x001E3333, 0x3E33331E, 0x000E1830, 0x000C0C00, 0x000C0C00, 0x000C0C00, 0x060C0C00,
|
||||
0x03060C18, 0x00180C06, 0x003F0000, 0x00003F00, 0x30180C06, 0x00060C18, 0x1830331E, 0x000C000C,
|
||||
0x7B7B633E, 0x001E037B, 0x33331E0C, 0x0033333F, 0x3E66663F, 0x003F6666, 0x0303663C, 0x003C6603,
|
||||
0x6666361F, 0x001F3666, 0x1E16467F, 0x007F4616, 0x1E16467F, 0x000F0616, 0x0303663C, 0x007C6673,
|
||||
0x3F333333, 0x00333333, 0x0C0C0C1E, 0x001E0C0C, 0x30303078, 0x001E3333, 0x1E366667, 0x00676636,
|
||||
0x0606060F, 0x007F6646, 0x7F7F7763, 0x0063636B, 0x7B6F6763, 0x00636373, 0x6363361C, 0x001C3663,
|
||||
0x3E66663F, 0x000F0606, 0x3333331E, 0x00381E3B, 0x3E66663F, 0x00676636, 0x0C06331E, 0x001E3318,
|
||||
0x0C0C2D3F, 0x001E0C0C, 0x33333333, 0x003F3333, 0x33333333, 0x000C1E33, 0x6B636363, 0x0063777F,
|
||||
0x1C366363, 0x0063361C, 0x1E333333, 0x001E0C0C, 0x1831637F, 0x007F664C, 0x0606061E, 0x001E0606,
|
||||
0x180C0603, 0x00406030, 0x1818181E, 0x001E1818, 0x63361C08, 0x00000000, 0x00000000, 0xFF000000,
|
||||
0x00180C0C, 0x00000000, 0x301E0000, 0x006E333E, 0x3E060607, 0x003B6666, 0x331E0000, 0x001E3303,
|
||||
0x3E303038, 0x006E3333, 0x331E0000, 0x001E033F, 0x0F06361C, 0x000F0606, 0x336E0000, 0x1F303E33,
|
||||
0x6E360607, 0x00676666, 0x0C0E000C, 0x001E0C0C, 0x30300030, 0x1E333330, 0x36660607, 0x0067361E,
|
||||
0x0C0C0C0E, 0x001E0C0C, 0x7F330000, 0x00636B7F, 0x331F0000, 0x00333333, 0x331E0000, 0x001E3333,
|
||||
0x663B0000, 0x0F063E66, 0x336E0000, 0x78303E33, 0x6E3B0000, 0x000F0666, 0x033E0000, 0x001F301E,
|
||||
0x0C3E0C08, 0x00182C0C, 0x33330000, 0x006E3333, 0x33330000, 0x000C1E33, 0x6B630000, 0x00367F7F,
|
||||
0x36630000, 0x0063361C, 0x33330000, 0x1F303E33, 0x193F0000, 0x003F260C, 0x070C0C38, 0x00380C0C,
|
||||
0x00181818, 0x00181818, 0x380C0C07, 0x00070C0C, 0x00003B6E, 0x00000000, 0x361C0800, 0x007F6363,
|
||||
0x3303331E, 0x1E30181E, 0x33003300, 0x007E3333, 0x331E0038, 0x001E033F, 0x603CC37E, 0x00FC667C,
|
||||
0x301E0033, 0x007E333E, 0x301E0007, 0x007E333E, 0x301E0C0C, 0x007E333E, 0x031E0000, 0x1C301E03,
|
||||
0x663CC37E, 0x003C067E, 0x331E0033, 0x001E033F, 0x331E0007, 0x001E033F, 0x0C0E0033, 0x001E0C0C,
|
||||
0x181C633E, 0x003C1818, 0x0C0E0007, 0x001E0C0C, 0x63361C63, 0x0063637F, 0x1E000C0C, 0x00333F33,
|
||||
0x063F0038, 0x003F061E, 0x30FE0000, 0x00FE33FE, 0x7F33367C, 0x00733333, 0x1E00331E, 0x001E3333,
|
||||
0x1E003300, 0x001E3333, 0x1E000700, 0x001E3333, 0x3300331E, 0x007E3333, 0x33000700, 0x007E3333,
|
||||
0x33003300, 0x1F303E33, 0x663C18C3, 0x00183C66, 0x33330033, 0x001E3333, 0x037E1818, 0x18187E03,
|
||||
0x0F26361C, 0x003F6706, 0x3F1E3333, 0x0C0C3F0C, 0x5F33331F, 0xE363F363, 0x3C18D870, 0x0E1B1818,
|
||||
0x301E0038, 0x007E333E, 0x0C0E001C, 0x001E0C0C, 0x1E003800, 0x001E3333, 0x33003800, 0x007E3333,
|
||||
0x1F001F00, 0x00333333, 0x3733003F, 0x00333B3F, 0x7C36363C, 0x00007E00, 0x1C36361C, 0x00003E00,
|
||||
0x060C000C, 0x001E3303, 0x3F000000, 0x00000303, 0x3F000000, 0x00003030, 0x7B3363C3, 0xF03366CC,
|
||||
0xDB3363C3, 0xC0F3F6EC, 0x18001818, 0x00181818, 0x3366CC00, 0x0000CC66, 0xCC663300, 0x00003366,
|
||||
0x11441144, 0x11441144, 0x55AA55AA, 0x55AA55AA, 0x77DBEEDB, 0x77DBEEDB, 0x18181818, 0x18181818,
|
||||
0x18181818, 0x1818181F, 0x181F1818, 0x1818181F, 0x6C6C6C6C, 0x6C6C6C6F, 0x00000000, 0x6C6C6C7F,
|
||||
0x181F0000, 0x1818181F, 0x606F6C6C, 0x6C6C6C6F, 0x6C6C6C6C, 0x6C6C6C6C, 0x607F0000, 0x6C6C6C6F,
|
||||
0x606F6C6C, 0x0000007F, 0x6C6C6C6C, 0x0000007F, 0x181F1818, 0x0000001F, 0x00000000, 0x1818181F,
|
||||
0x18181818, 0x000000F8, 0x18181818, 0x000000FF, 0x00000000, 0x181818FF, 0x18181818, 0x181818F8,
|
||||
0x00000000, 0x000000FF, 0x18181818, 0x181818FF, 0x18F81818, 0x181818F8, 0x6C6C6C6C, 0x6C6C6CEC,
|
||||
0x0CEC6C6C, 0x000000FC, 0x0CFC0000, 0x6C6C6CEC, 0x00EF6C6C, 0x000000FF, 0x00FF0000, 0x6C6C6CEF,
|
||||
0x0CEC6C6C, 0x6C6C6CEC, 0x00FF0000, 0x000000FF, 0x00EF6C6C, 0x6C6C6CEF, 0x00FF1818, 0x000000FF,
|
||||
0x6C6C6C6C, 0x000000FF, 0x00FF0000, 0x181818FF, 0x00000000, 0x6C6C6CFF, 0x6C6C6C6C, 0x000000FC,
|
||||
0x18F81818, 0x000000F8, 0x18F80000, 0x181818F8, 0x00000000, 0x6C6C6CFC, 0x6C6C6C6C, 0x6C6C6CFF,
|
||||
0x18FF1818, 0x181818FF, 0x18181818, 0x0000001F, 0x00000000, 0x181818F8, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x00000000, 0xFFFFFFFF, 0x0F0F0F0F, 0x0F0F0F0F, 0xF0F0F0F0, 0xF0F0F0F0, 0xFFFFFFFF, 0x00000000,
|
||||
0x3B6E0000, 0x006E3B13, 0x1F331E00, 0x03031F33, 0x03333F00, 0x00030303, 0x36367F00, 0x00363636,
|
||||
0x0C06333F, 0x003F3306, 0x1B7E0000, 0x000E1B1B, 0x66666600, 0x03063E66, 0x183B6E00, 0x00181818,
|
||||
0x331E0C3F, 0x3F0C1E33, 0x7F63361C, 0x001C3663, 0x6363361C, 0x00773636, 0x3E180C38, 0x001E3333,
|
||||
0xDB7E0000, 0x00007EDB, 0xDB7E3060, 0x03067EDB, 0x1F03061C, 0x001C0603, 0x3333331E, 0x00333333,
|
||||
0x3F003F00, 0x00003F00, 0x0C3F0C0C, 0x003F000C, 0x0C180C06, 0x003F0006, 0x0C060C18, 0x003F0018,
|
||||
0x18D8D870, 0x18181818, 0x18181818, 0x0E1B1B18, 0x3F000C0C, 0x000C0C00, 0x003B6E00, 0x00003B6E,
|
||||
0x1C36361C, 0x00000000, 0x18000000, 0x00000018, 0x00000000, 0x00000018, 0x303030F0, 0x383C3637,
|
||||
0x3636361E, 0x00000036, 0x060C180E, 0x0000001E, 0x3C3C0000, 0x00003C3C, 0x00000000, 0x00000000,
|
||||
];
|
|
@ -95,6 +95,17 @@ newtype!(
|
|||
u16
|
||||
);
|
||||
|
||||
newtype_enum! {
|
||||
/// General IO modes.
|
||||
IoMode = u16,
|
||||
/// * IO disabled
|
||||
Disabled = 0,
|
||||
/// * General Purpose IO
|
||||
GPIO = 2,
|
||||
/// * JoyBus mode
|
||||
JoyBus = 3,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl IoControlSetting {
|
||||
phantom_fields! {
|
||||
|
@ -112,17 +123,6 @@ impl IoControlSetting {
|
|||
}
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// General IO modes.
|
||||
IoMode = u16,
|
||||
/// * IO disabled
|
||||
Disabled = 0,
|
||||
/// * General Purpose IO
|
||||
GPIO = 2,
|
||||
/// * JoyBus mode
|
||||
JoyBus = 3,
|
||||
}
|
||||
|
||||
/// Empty struct that implements embedded_hal traits.
|
||||
#[cfg(feature = "serial")]
|
||||
#[derive(Clone)]
|
|
@ -1,7 +0,0 @@
|
|||
//! Holds fundamental types/ops which the rest of the crate is built on.
|
||||
//!
|
||||
//! These things should probably, in time, be extracted out into their own
|
||||
//! crate (or convert to using a robust existing crate).
|
||||
|
||||
pub mod fixed_point;
|
||||
//pub(crate) use self::fixed_point::*;
|
1080
src/bios.rs
1080
src/bios.rs
File diff suppressed because it is too large
Load diff
108
src/debug.rs
108
src/debug.rs
|
@ -3,12 +3,98 @@
|
|||
//! This is the underlying implementation behind the various print macros in
|
||||
//! the gba crate. It currently supports the latest versions of mGBA and NO$GBA.
|
||||
|
||||
use crate::sync::{InitOnce, RawMutex, Static};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
sync::{InitOnce, RawMutex, Static},
|
||||
};
|
||||
use core::fmt::{Arguments, Error};
|
||||
use voladdress::*;
|
||||
|
||||
pub mod mgba;
|
||||
pub mod nocash;
|
||||
|
||||
/// Delivers a fatal message to the emulator debug output, and crashes
|
||||
/// the the game.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! fatal {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Fatal, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
debug::crash()
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a error message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Error, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a warning message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Warning, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers an info message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Info, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a debug message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Debug, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// A cross-emulator debug level.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
|
@ -99,26 +185,20 @@ pub fn is_debugging_disabled() -> bool {
|
|||
/// This is used to implement fatal errors outside of mGBA.
|
||||
#[inline(never)]
|
||||
pub fn crash() -> ! {
|
||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||
{
|
||||
IME.write(IrqEnableSetting::IRQ_NO);
|
||||
unsafe {
|
||||
IME.write(false);
|
||||
// Stop all ongoing DMAs just in case.
|
||||
DMA0::set_control(DMAControlSetting::new());
|
||||
DMA1::set_control(DMAControlSetting::new());
|
||||
DMA2::set_control(DMAControlSetting::new());
|
||||
DMA3::set_control(DMAControlSetting::new());
|
||||
DMA0CNT_H.write(DmaControl::new());
|
||||
DMA1CNT_H.write(DmaControl::new());
|
||||
DMA2CNT_H.write(DmaControl::new());
|
||||
DMA3CNT_H.write(DmaControl::new());
|
||||
|
||||
// Writes the halt call back to memory
|
||||
//
|
||||
// we use an infinite loop in RAM just to make sure removing the
|
||||
// Game Pak doesn't break this crash loop.
|
||||
let target = VolAddress::<u16>::new(0x03000000);
|
||||
target.write(0xe7fe); // assembly instruction: `loop: b loop`
|
||||
let target = VolAddress::<u16, Unsafe, Unsafe>::new(0x03000000);
|
||||
target.write(0xE7FE); // thumb assembly instruction: `loop: b loop`
|
||||
core::mem::transmute::<_, extern "C" fn() -> !>(0x03000001)()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
||||
loop {}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
//! Module for External Work RAM (`EWRAM`).
|
|
@ -1,256 +0,0 @@
|
|||
#![allow(non_camel_case_types)]
|
||||
// Note(Lokathor): NOT CURRENTLY USED.
|
||||
//
|
||||
// If we want this in the future we can convert it to const generics.
|
||||
|
||||
//! Module for fixed point math types and operations.
|
||||
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
ops::{Add, Div, Mul, Neg, Shl, Shr, Sub},
|
||||
};
|
||||
use typenum::{consts::False, marker_traits::Unsigned, type_operators::IsEqual, U8};
|
||||
|
||||
/// Fixed point `T` value with `F` fractional bits.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct Fx<T, F: Unsigned> {
|
||||
num: T,
|
||||
phantom: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<T, F: Unsigned> Fx<T, F> {
|
||||
/// Uses the provided value directly.
|
||||
pub fn from_raw(r: T) -> Self {
|
||||
Fx { num: r, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Unwraps the inner value.
|
||||
pub fn into_raw(self) -> T {
|
||||
self.num
|
||||
}
|
||||
|
||||
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||
Fx { num: op(self.num), phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Fx<T, F>) -> Self::Output {
|
||||
Fx { num: self.num + rhs.num, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: Fx<T, F>) -> Self::Output {
|
||||
Fx { num: self.num - rhs.num, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
|
||||
type Output = Self;
|
||||
fn shl(self, rhs: u32) -> Self::Output {
|
||||
Fx { num: self.num << rhs, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
|
||||
type Output = Self;
|
||||
fn shr(self, rhs: u32) -> Self::Output {
|
||||
Fx { num: self.num >> rhs, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
|
||||
type Output = Self;
|
||||
fn neg(self) -> Self::Output {
|
||||
Fx { num: -self.num, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! fixed_point_methods {
|
||||
($t:ident) => {
|
||||
impl<F: Unsigned> Fx<$t, F> {
|
||||
/// Gives the smallest positive non-zero value.
|
||||
pub fn precision() -> Self {
|
||||
Fx { num: 1, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Makes a value with the integer part shifted into place.
|
||||
pub fn from_int_part(i: $t) -> Self {
|
||||
Fx { num: i << F::U8, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Changes the fractional bit quantity, keeping the base type the same.
|
||||
pub fn adjust_fractional_bits<Y: Unsigned + IsEqual<F, Output = False>>(self) -> Fx<$t, Y> {
|
||||
let leftward_movement: i32 = Y::to_i32() - F::to_i32();
|
||||
Fx {
|
||||
num: if leftward_movement > 0 {
|
||||
self.num << leftward_movement
|
||||
} else {
|
||||
self.num >> (-leftward_movement)
|
||||
},
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_point_methods! {u8}
|
||||
fixed_point_methods! {i8}
|
||||
fixed_point_methods! {i16}
|
||||
fixed_point_methods! {u16}
|
||||
fixed_point_methods! {i32}
|
||||
fixed_point_methods! {u32}
|
||||
|
||||
macro_rules! fixed_point_signed_multiply {
|
||||
($t:ident) => {
|
||||
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||
type Output = Self;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||
let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
|
||||
if pre_shift < 0 {
|
||||
if pre_shift == core::i32::MIN {
|
||||
Fx { num: core::$t::MIN, phantom: PhantomData }
|
||||
} else {
|
||||
Fx { num: (-((-pre_shift) >> F::U8)) as $t, phantom: PhantomData }
|
||||
}
|
||||
} else {
|
||||
Fx { num: (pre_shift >> F::U8) as $t, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_point_signed_multiply! {i8}
|
||||
fixed_point_signed_multiply! {i16}
|
||||
fixed_point_signed_multiply! {i32}
|
||||
|
||||
macro_rules! fixed_point_unsigned_multiply {
|
||||
($t:ident) => {
|
||||
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||
type Output = Self;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||
Fx {
|
||||
num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_point_unsigned_multiply! {u8}
|
||||
fixed_point_unsigned_multiply! {u16}
|
||||
fixed_point_unsigned_multiply! {u32}
|
||||
|
||||
macro_rules! fixed_point_signed_division {
|
||||
($t:ident) => {
|
||||
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||
type Output = Self;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||
Fx { num: divide_result as $t, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_point_signed_division! {i8}
|
||||
fixed_point_signed_division! {i16}
|
||||
fixed_point_signed_division! {i32}
|
||||
|
||||
macro_rules! fixed_point_unsigned_division {
|
||||
($t:ident) => {
|
||||
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||
type Output = Self;
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||
Fx { num: divide_result as $t, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fixed_point_unsigned_division! {u8}
|
||||
fixed_point_unsigned_division! {u16}
|
||||
fixed_point_unsigned_division! {u32}
|
||||
|
||||
/// Alias for an `i16` fixed point value with 8 fractional bits.
|
||||
pub type fx8_8 = Fx<i16, U8>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
use typenum::U4;
|
||||
let one = Fx::<u16, U4>::from_int_part(1);
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
assert!(one + one == two)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub() {
|
||||
use typenum::U4;
|
||||
let one = Fx::<u16, U4>::from_int_part(1);
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
assert!(two - one == one)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shl() {
|
||||
use typenum::U4;
|
||||
let one = Fx::<u16, U4>::from_int_part(1);
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
assert!(one << 1 == two)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shr() {
|
||||
use typenum::U4;
|
||||
let one = Fx::<u16, U4>::from_int_part(1);
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
assert!(two >> 1 == one)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg() {
|
||||
use typenum::U4;
|
||||
let one = Fx::<i16, U4>::from_int_part(1);
|
||||
let neg_one = Fx::<i16, U4>::from_int_part(-1);
|
||||
assert!(-one == neg_one);
|
||||
assert!(-(-one) == one);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
use typenum::U4;
|
||||
let half = Fx::<u16, U4>::from_int_part(1) >> 1;
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
let three = Fx::<u16, U4>::from_int_part(3);
|
||||
let twelve = Fx::<u16, U4>::from_int_part(12);
|
||||
assert!(two * three == twelve * half);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div() {
|
||||
use typenum::U4;
|
||||
let two = Fx::<u16, U4>::from_int_part(2);
|
||||
let six = Fx::<u16, U4>::from_int_part(6);
|
||||
let twelve = Fx::<u16, U4>::from_int_part(12);
|
||||
assert!(twelve / two == six);
|
||||
}
|
||||
}
|
20
src/io.rs
20
src/io.rs
|
@ -1,20 +0,0 @@
|
|||
//! This module contains definitions and types for the IO Registers.
|
||||
//!
|
||||
//! ## Naming
|
||||
//!
|
||||
//! In the interest of making things easy to search for, all io register
|
||||
//! constants are given the names used in the
|
||||
//! [GBATEK](https://problemkaputt.de/gbatek.htm) technical description.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub mod background;
|
||||
pub mod color_blend;
|
||||
pub mod display;
|
||||
pub mod dma;
|
||||
pub mod irq;
|
||||
pub mod keypad;
|
||||
pub mod sio;
|
||||
pub mod sound;
|
||||
pub mod timers;
|
||||
pub mod window;
|
|
@ -1,117 +0,0 @@
|
|||
//! Module for Background controls
|
||||
|
||||
use super::*;
|
||||
|
||||
/// BG0 Control. Read/Write. Display Mode 0/1 only.
|
||||
pub const BG0CNT: VolAddress<BackgroundControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0008) };
|
||||
/// BG1 Control. Read/Write. Display Mode 0/1 only.
|
||||
pub const BG1CNT: VolAddress<BackgroundControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_000A) };
|
||||
/// BG2 Control. Read/Write. Display Mode 0/1/2 only.
|
||||
pub const BG2CNT: VolAddress<BackgroundControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_000C) };
|
||||
/// BG3 Control. Read/Write. Display Mode 0/2 only.
|
||||
pub const BG3CNT: VolAddress<BackgroundControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_000E) };
|
||||
|
||||
newtype! {
|
||||
/// Allows configuration of a background layer.
|
||||
///
|
||||
/// Bits 0-1: BG Priority (lower number is higher priority, like an index)
|
||||
/// Bits 2-3: Character Base Block (0 through 3, 16k each)
|
||||
/// Bit 6: Mosaic mode
|
||||
/// Bit 7: is 8bpp
|
||||
/// Bit 8-12: Screen Base Block (0 through 31, 2k each)
|
||||
/// Bit 13: Display area overflow wraps (otherwise transparent, affine BG only)
|
||||
/// Bit 14-15: Screen Size
|
||||
BackgroundControlSetting, u16
|
||||
}
|
||||
impl BackgroundControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
bg_priority: 0-1,
|
||||
char_base_block: 2-3,
|
||||
mosaic: 6,
|
||||
is_8bpp: 7,
|
||||
screen_base_block: 8-12,
|
||||
affine_display_overflow_wrapping: 13,
|
||||
size: 14-15=BGSize<Zero, One, Two, Three>,
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of a background.
|
||||
///
|
||||
/// The meaning changes depending on if the background is Text or Affine mode.
|
||||
///
|
||||
/// * In text mode, the screen base block determines where to start reading the
|
||||
/// tile arrangement data (2k). Size Zero gives one screen block of use. Size
|
||||
/// One and Two cause two of them to be used (horizontally or vertically,
|
||||
/// respectively). Size Three is four blocks used, [0,1] above and then [2,3]
|
||||
/// below. Each screen base block used is always a 32x32 tile grid.
|
||||
/// * In affine mode, the screen base block determines where to start reading
|
||||
/// data followed by the size of data as shown. The number of tiles varies
|
||||
/// according to the size used.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum BGSize {
|
||||
/// * Text: 256x256px (2k)
|
||||
/// * Affine: 128x128px (256b)
|
||||
Zero = 0,
|
||||
/// * Text: 512x256px (4k)
|
||||
/// * Affine: 256x256px (1k)
|
||||
One = 1,
|
||||
/// * Text: 256x512px (4k)
|
||||
/// * Affine: 512x512px (4k)
|
||||
Two = 2,
|
||||
/// * Text: 512x512px (8k)
|
||||
/// * Affine: 1024x1024px (16k)
|
||||
Three = 3,
|
||||
}
|
||||
|
||||
/// BG0 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG0HOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0010) };
|
||||
/// BG0 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG0VOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0012) };
|
||||
|
||||
/// BG1 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG1HOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0014) };
|
||||
/// BG1 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG1VOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0016) };
|
||||
|
||||
/// BG2 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG2HOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0018) };
|
||||
/// BG2 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG2VOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_001A) };
|
||||
|
||||
/// BG3 X-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG3HOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_001C) };
|
||||
/// BG3 Y-Offset. Write only. Text mode only. 9 bits.
|
||||
pub const BG3VOFS: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_001E) };
|
||||
|
||||
// TODO: affine backgrounds
|
||||
// BG2X_L
|
||||
// BG2X_H
|
||||
// BG2Y_L
|
||||
// BG2Y_H
|
||||
// BG2PA
|
||||
// BG2PB
|
||||
// BG2PC
|
||||
// BG2PD
|
||||
// BG3PA
|
||||
// BG3PB
|
||||
// BG3PC
|
||||
// BG3PD
|
||||
|
||||
// TODO: windowing
|
||||
// pub const WIN0H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0040) };
|
||||
// pub const WIN1H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0042) };
|
||||
// pub const WIN0V: VolAddress<u16> = unsafe { VolAddress::new(0x400_0044) };
|
||||
// pub const WIN1V: VolAddress<u16> = unsafe { VolAddress::new(0x400_0046) };
|
||||
// pub const WININ: VolAddress<u16> = unsafe { VolAddress::new(0x400_0048) };
|
||||
// pub const WINOUT: VolAddress<u16> = unsafe { VolAddress::new(0x400_004A) };
|
||||
|
||||
// TODO: blending
|
||||
// pub const BLDCNT: VolAddress<u16> = unsafe { VolAddress::new(0x400_0050) };
|
||||
// pub const BLDALPHA: VolAddress<u16> = unsafe { VolAddress::new(0x400_0052) };
|
||||
// pub const BLDY: VolAddress<u16> = unsafe { VolAddress::new(0x400_0054) };
|
|
@ -1,76 +0,0 @@
|
|||
//! Module that holds stuff for the color blending ability.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Color Special Effects Selection (R/W)
|
||||
pub const BLDCNT: VolAddress<ColorEffectSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0050) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
ColorEffectSetting, u16
|
||||
}
|
||||
|
||||
impl ColorEffectSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
bg0_1st_target_pixel: 0,
|
||||
bg1_1st_target_pixel: 1,
|
||||
bg2_1st_target_pixel: 2,
|
||||
bg3_1st_target_pixel: 3,
|
||||
obj_1st_target_pixel: 4,
|
||||
backdrop_1st_target_pixel: 5,
|
||||
color_special_effect: 6-7=ColorSpecialEffect<None, AlphaBlending, BrightnessIncrease, BrightnessDecrease>,
|
||||
bg0_2nd_target_pixel: 8,
|
||||
bg1_2nd_target_pixel: 9,
|
||||
bg2_2nd_target_pixel: 10,
|
||||
bg3_2nd_target_pixel: 11,
|
||||
obj_2nd_target_pixel: 12,
|
||||
backdrop_2nd_target_pixel: 13,
|
||||
}
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// TODO: docs
|
||||
ColorSpecialEffect = u16,
|
||||
/// TODO: docs
|
||||
None = 0,
|
||||
/// TODO: docs
|
||||
AlphaBlending = 1,
|
||||
/// TODO: docs
|
||||
BrightnessIncrease = 2,
|
||||
/// TODO: docs
|
||||
BrightnessDecrease = 3,
|
||||
}
|
||||
|
||||
/// Alpha Blending Coefficients (R/W) (not W)
|
||||
pub const BLDALPHA: VolAddress<AlphaBlendingSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0052) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
AlphaBlendingSetting, u16
|
||||
}
|
||||
|
||||
impl AlphaBlendingSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
eva_coefficient: 0-4,
|
||||
evb_coefficient: 8-12,
|
||||
}
|
||||
}
|
||||
|
||||
/// Brightness (Fade-In/Out) Coefficient (W) (not R/W)
|
||||
pub const BLDY: VolAddress<BrightnessSetting, Safe, Safe> = unsafe { VolAddress::new(0x400_0054) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
BrightnessSetting, u32
|
||||
}
|
||||
|
||||
impl BrightnessSetting {
|
||||
phantom_fields! {
|
||||
self.0: u32,
|
||||
evy_coefficient: 0-4,
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
//! Contains types and definitions for display related IO registers.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// LCD Control. Read/Write.
|
||||
///
|
||||
/// The "force vblank" bit is always set when your Rust code first executes.
|
||||
pub const DISPCNT: VolAddress<DisplayControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0000) };
|
||||
|
||||
newtype!(
|
||||
/// Setting for the display control register.
|
||||
///
|
||||
/// * 0-2: `DisplayMode`
|
||||
/// * 3: CGB mode flag
|
||||
/// * 4: Display frame 1 (Modes 4/5 only)
|
||||
/// * 5: "hblank interval free", allows full access to OAM during hblank
|
||||
/// * 6: Object tile memory 1-dimensional
|
||||
/// * 7: Force vblank
|
||||
/// * 8: Display bg0 layer
|
||||
/// * 9: Display bg1 layer
|
||||
/// * 10: Display bg2 layer
|
||||
/// * 11: Display bg3 layer
|
||||
/// * 12: Display objects layer
|
||||
/// * 13: Window 0 display
|
||||
/// * 14: Window 1 display
|
||||
/// * 15: Object window
|
||||
DisplayControlSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl DisplayControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
mode: 0-2=DisplayMode<Mode0, Mode1, Mode2, Mode3, Mode4, Mode5>,
|
||||
frame1: 4,
|
||||
hblank_interval_free: 5,
|
||||
oam_memory_1d: 6,
|
||||
force_vblank: 7,
|
||||
bg0: 8,
|
||||
bg1: 9,
|
||||
bg2: 10,
|
||||
bg3: 11,
|
||||
obj: 12,
|
||||
win0: 13,
|
||||
win1: 14,
|
||||
obj_window: 15,
|
||||
}
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// The six display modes available on the GBA.
|
||||
DisplayMode = u16,
|
||||
/// * Affine: No
|
||||
/// * Layers: 0/1/2/3
|
||||
/// * Size(px): 256x256 to 512x512
|
||||
/// * Tiles: 1024
|
||||
/// * Palette Modes: 4bpp or 8bpp
|
||||
Mode0 = 0,
|
||||
/// * BG0 / BG1: As Mode0
|
||||
/// * BG2: As Mode2
|
||||
Mode1 = 1,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2/3
|
||||
/// * Size(px): 128x128 to 1024x1024
|
||||
/// * Tiles: 256
|
||||
/// * Palette Modes: 8bpp
|
||||
Mode2 = 2,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 240x160 (1 page)
|
||||
/// * Bitmap
|
||||
/// * Full Color
|
||||
Mode3 = 3,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 240x160 (2 pages)
|
||||
/// * Bitmap
|
||||
/// * Palette Modes: 8bpp
|
||||
Mode4 = 4,
|
||||
/// * Affine: Yes
|
||||
/// * Layers: 2
|
||||
/// * Size(px): 160x128 (2 pages)
|
||||
/// * Bitmap
|
||||
/// * Full Color
|
||||
Mode5 = 5,
|
||||
}
|
||||
|
||||
/// Assigns the given display control setting.
|
||||
pub fn set_display_control(setting: DisplayControlSetting) {
|
||||
DISPCNT.write(setting);
|
||||
}
|
||||
/// Obtains the current display control setting.
|
||||
pub fn display_control() -> DisplayControlSetting {
|
||||
DISPCNT.read()
|
||||
}
|
||||
|
||||
/// Display Status and IRQ Control. Read/Write.
|
||||
pub const DISPSTAT: VolAddress<DisplayStatusSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0004) };
|
||||
|
||||
newtype!(
|
||||
/// A newtype over display status and interrupt control values.
|
||||
DisplayStatusSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
impl DisplayStatusSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
vblank_flag: 0,
|
||||
hblank_flag: 1,
|
||||
vcounter_flag: 2,
|
||||
vblank_irq_enable: 3,
|
||||
hblank_irq_enable: 4,
|
||||
vcounter_irq_enable: 5,
|
||||
vcount_setting: 8-15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical Counter (LY). Read only.
|
||||
///
|
||||
/// Gives the current scanline that the display controller is working on. If
|
||||
/// this is at or above the `VBLANK_SCANLINE` value then the display controller
|
||||
/// is in a "vertical blank" period.
|
||||
pub const VCOUNT: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x400_0006) };
|
||||
|
||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||
pub const VBLANK_SCANLINE: u16 = 160;
|
||||
|
||||
/// Global mosaic effect control. Write-only.
|
||||
pub const MOSAIC: VolAddress<MosaicSetting, Safe, Safe> = unsafe { VolAddress::new(0x400_004C) };
|
||||
|
||||
newtype! {
|
||||
/// Allows control of the Mosaic effect.
|
||||
///
|
||||
/// Values are the _increase_ for each top-left pixel to be duplicated in the
|
||||
/// final result. If you want to duplicate some other pixel than the top-left,
|
||||
/// you can offset the background or object by an appropriate amount.
|
||||
///
|
||||
/// 0) No effect (1+0)
|
||||
/// 1) Each pixel becomes 2 pixels (1+1)
|
||||
/// 2) Each pixel becomes 3 pixels (1+2)
|
||||
/// 3) Each pixel becomes 4 pixels (1+3)
|
||||
///
|
||||
/// * Bits 0-3: BG mosaic horizontal increase
|
||||
/// * Bits 4-7: BG mosaic vertical increase
|
||||
/// * Bits 8-11: Object mosaic horizontal increase
|
||||
/// * Bits 12-15: Object mosaic vertical increase
|
||||
MosaicSetting, u16
|
||||
}
|
||||
impl MosaicSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
bg_horizontal_inc: 0-3,
|
||||
bg_vertical_inc: 4-7,
|
||||
obj_horizontal_inc: 8-11,
|
||||
obj_vertical_inc: 12-15,
|
||||
}
|
||||
}
|
415
src/io/dma.rs
415
src/io/dma.rs
|
@ -1,415 +0,0 @@
|
|||
//! Module for using the four Direct Memory Access (DMA) units.
|
||||
//!
|
||||
//! The GBA has four DMA units, numbered 0 through 3. If you ever try to have
|
||||
//! more than one active at once the lowest numbered DMA will take priority and
|
||||
//! complete first. Any use of DMA halts the CPU's operation. DMA can also be
|
||||
//! configured to activate automatically at certain times, and when configured
|
||||
//! like that the CPU runs in between the automatic DMA activations. (This is
|
||||
//! actually the intended method for doing sound.) Each DMA unit has an intended
|
||||
//! use:
|
||||
//!
|
||||
//! * DMA0: highest priority, but can only read from internal memory.
|
||||
//! * DMA1/DMA2: Intended for sound transfers.
|
||||
//! * DMA3: Can be used to write into Game Pak ROM / FlashROM (not SRAM).
|
||||
//!
|
||||
//! ## DMA Anatomy
|
||||
//!
|
||||
//! Each DMA is utilized via a combination four IO registers:
|
||||
//!
|
||||
//! * **Source Address:** (`*const u32`) Where to read from. DMA0 can only read
|
||||
//! from internal memory, the other units can read from any non-SRAM memory.
|
||||
//! * **Destination Address:** (`*mut u32`) Where to write to. DMA0/1/2 can only
|
||||
//! write to internal memory, DMA3 can write to any non-SRAM memory.
|
||||
//! * **Word Count:** (`u16`) How many units to transfer. Despite being called
|
||||
//! "word count" you can also use DMA to transfer half-words. DMA0/1/2 are
|
||||
//! limited to a 14-bit counter value, DMA3 allowed the full 16-bit range to
|
||||
//! be used for the counter. Note that even when transferring half-words you
|
||||
//! MUST have both Source and Destination be 32-bit aligned.
|
||||
//! * **Control:** (`DMAControlSetting`) This is one of those fiddly bit-flag
|
||||
//! registers with all sorts of settings. See the type for more info.
|
||||
//!
|
||||
//! Note that Source, Destination, and Count are all read-only, while the
|
||||
//! Control is read/write. When a DMA unit is _Enabled_ it copies the relevent
|
||||
//! Source, Destination, and Count values into its own internal registers (so a
|
||||
//! second Enable will reuse the old values). If the DMA _Repeats_ it re-copies
|
||||
//! the Count, and also the Destination if
|
||||
//! `DMADestAddressControl::IncrementReload` is configured in the Control, but
|
||||
//! not the Source.
|
||||
//!
|
||||
//! When the DMA completes the Enable bit will be cleared from the Control,
|
||||
//! unless the Repeat bit is set in the Control, in which case the Enable bit is
|
||||
//! left active and the DMA will automatically activate again at the right time
|
||||
//! (depending on the Start Timing setting). You have to manually turn off the
|
||||
//! correct bit to stop the DMA unit.
|
||||
//!
|
||||
//! ## Safety
|
||||
//!
|
||||
//! As you might have noticed by now, utilizing DMA can be very fiddly. It moves
|
||||
//! around bytes with no concern for the type system, including the `Clone` and
|
||||
//! `Copy` traits that Rust relies on. Use of DMA can be made _somewhat safe_
|
||||
//! via wrapper methods (such as those we've provided), but it's fundamentally
|
||||
//! an unsafe thing to use.
|
||||
//!
|
||||
//! ## DMA Can Cause Subtle Bugs
|
||||
//!
|
||||
//! Since the CPU is halted while DMA is active you can miss out on interrupts
|
||||
//! that should have fired. This can cause any number of unintended effects. DMA
|
||||
//! is primarily intended for loading large amounts of graphical data from ROM,
|
||||
//! or loading sound data at regulated intervals to avoid pops and crackles. It
|
||||
//! _can_ be used for general purpose bulk transfers but you are advised to use
|
||||
//! restraint.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// Allows you to configure a DMA unit.
|
||||
DMAControlSetting, u16
|
||||
}
|
||||
#[allow(missing_docs)]
|
||||
impl DMAControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
dest_address_control: 5-6=DMADestAddressControl<Increment, Decrement, Fixed, IncrementReload>,
|
||||
source_address_control: 7-8=DMASrcAddressControl<Increment, Decrement, Fixed>,
|
||||
dma_repeat: 9,
|
||||
use_32bit: 10,
|
||||
start_time: 12-13=DMAStartTiming<Immediate, VBlank, HBlank, Special>,
|
||||
irq_when_done: 14,
|
||||
enabled: 15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets how the destination address should be adjusted per data transfer.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DMADestAddressControl {
|
||||
/// Offset +1
|
||||
Increment = 0,
|
||||
/// Offset -1
|
||||
Decrement = 1,
|
||||
/// No change
|
||||
Fixed = 2,
|
||||
/// Offset +1 per transfer and auto-reset to base when the DMA repeats.
|
||||
IncrementReload = 3,
|
||||
}
|
||||
|
||||
/// Sets how the source address should be adjusted per data transfer.
|
||||
///
|
||||
/// Note that only 0,1,2 are allowed, 3 is prohibited.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DMASrcAddressControl {
|
||||
/// Offset +1
|
||||
Increment = 0,
|
||||
/// Offset -1
|
||||
Decrement = 1,
|
||||
/// No change
|
||||
Fixed = 2,
|
||||
}
|
||||
|
||||
/// Sets when the DMA should activate.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DMAStartTiming {
|
||||
/// Causes the DMA to start as soon as possible (2 wait cycles after enabled)
|
||||
Immediate = 0,
|
||||
/// Start at VBlank
|
||||
VBlank = 1,
|
||||
/// Start at HBlank
|
||||
HBlank = 2,
|
||||
/// The special timing depends on the DMA it's used with:
|
||||
/// * 0: Prohibited
|
||||
/// * 1/2: Sound FIFO,
|
||||
/// * 3: Video Capture, for transferring from memory/camera into VRAM
|
||||
Special = 3,
|
||||
}
|
||||
|
||||
pub struct DMA0;
|
||||
impl DMA0 {
|
||||
/// DMA 0 Source Address, read only.
|
||||
const DMA0SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00B0) };
|
||||
/// DMA 0 Destination Address, read only.
|
||||
const DMA0DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00B4) };
|
||||
/// DMA 0 Word Count, read only.
|
||||
const DMA0CNT_L: VolAddress<u16, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00B8) };
|
||||
/// DMA 0 Control, read/write.
|
||||
const DMA0CNT_H: VolAddress<DMAControlSetting, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x400_00BA) };
|
||||
|
||||
/// Assigns the source register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to read from.
|
||||
pub unsafe fn set_source(src: *const u32) {
|
||||
crate::sync::memory_read_hint(src);
|
||||
Self::DMA0SAD.write(src);
|
||||
}
|
||||
|
||||
/// Assigns the destination register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to write to.
|
||||
pub unsafe fn set_dest(dest: *mut u32) {
|
||||
Self::DMA0DAD.write(dest);
|
||||
crate::sync::memory_write_hint(dest);
|
||||
}
|
||||
|
||||
/// Assigns the count register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The count given must specify a valid number of units to write, starting at
|
||||
/// the assigned destination address.
|
||||
pub unsafe fn set_count(count: u16) {
|
||||
Self::DMA0CNT_L.write(count)
|
||||
}
|
||||
|
||||
/// Reads the current control setting.
|
||||
pub fn control() -> DMAControlSetting {
|
||||
Self::DMA0CNT_H.read()
|
||||
}
|
||||
|
||||
/// Writes the control setting given.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that the Source, Destination, and Count values are set
|
||||
/// correctly **before** you activate the Enable bit.
|
||||
pub unsafe fn set_control(setting: DMAControlSetting) {
|
||||
Self::DMA0CNT_H.write(setting)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DMA1;
|
||||
impl DMA1 {
|
||||
/// DMA 1 Source Address, read only.
|
||||
const DMA1SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00BC) };
|
||||
/// DMA 1 Destination Address, read only.
|
||||
const DMA1DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00C0) };
|
||||
/// DMA 1 Word Count, read only.
|
||||
const DMA1CNT_L: VolAddress<u16, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00C4) };
|
||||
/// DMA 1 Control, read/write.
|
||||
const DMA1CNT_H: VolAddress<DMAControlSetting, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x400_00C6) };
|
||||
|
||||
/// Assigns the source register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to read from.
|
||||
pub unsafe fn set_source(src: *const u32) {
|
||||
crate::sync::memory_read_hint(src);
|
||||
Self::DMA1SAD.write(src);
|
||||
}
|
||||
|
||||
/// Assigns the destination register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to write to.
|
||||
pub unsafe fn set_dest(dest: *mut u32) {
|
||||
Self::DMA1DAD.write(dest);
|
||||
crate::sync::memory_write_hint(dest);
|
||||
}
|
||||
|
||||
/// Assigns the count register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The count given must specify a valid number of units to write, starting at
|
||||
/// the assigned destination address.
|
||||
pub unsafe fn set_count(count: u16) {
|
||||
Self::DMA1CNT_L.write(count)
|
||||
}
|
||||
|
||||
/// Reads the current control setting.
|
||||
pub fn control() -> DMAControlSetting {
|
||||
Self::DMA1CNT_H.read()
|
||||
}
|
||||
|
||||
/// Writes the control setting given.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that the Source, Destination, and Count values are set
|
||||
/// correctly **before** you activate the Enable bit.
|
||||
pub unsafe fn set_control(setting: DMAControlSetting) {
|
||||
Self::DMA1CNT_H.write(setting)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DMA2;
|
||||
impl DMA2 {
|
||||
/// DMA 2 Source Address, read only.
|
||||
const DMA2SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00C8) };
|
||||
/// DMA 2 Destination Address, read only.
|
||||
const DMA2DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00CC) };
|
||||
/// DMA 2 Word Count, read only.
|
||||
const DMA2CNT_L: VolAddress<u16, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00D0) };
|
||||
/// DMA 2 Control, read/write.
|
||||
const DMA2CNT_H: VolAddress<DMAControlSetting, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x400_00D2) };
|
||||
|
||||
/// Assigns the source register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to read from.
|
||||
pub unsafe fn set_source(src: *const u32) {
|
||||
crate::sync::memory_read_hint(src);
|
||||
Self::DMA2SAD.write(src);
|
||||
}
|
||||
|
||||
/// Assigns the destination register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to write to.
|
||||
pub unsafe fn set_dest(dest: *mut u32) {
|
||||
Self::DMA2DAD.write(dest);
|
||||
crate::sync::memory_write_hint(dest);
|
||||
}
|
||||
|
||||
/// Assigns the count register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The count given must specify a valid number of units to write, starting at
|
||||
/// the assigned destination address.
|
||||
pub unsafe fn set_count(count: u16) {
|
||||
Self::DMA2CNT_L.write(count)
|
||||
}
|
||||
|
||||
/// Reads the current control setting.
|
||||
pub fn control() -> DMAControlSetting {
|
||||
Self::DMA2CNT_H.read()
|
||||
}
|
||||
|
||||
/// Writes the control setting given.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that the Source, Destination, and Count values are set
|
||||
/// correctly **before** you activate the Enable bit.
|
||||
pub unsafe fn set_control(setting: DMAControlSetting) {
|
||||
Self::DMA2CNT_H.write(setting)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the "general purpose" DMA unit, with the fewest limits.
|
||||
pub struct DMA3;
|
||||
impl DMA3 {
|
||||
/// DMA 3 Source Address, read only.
|
||||
const DMA3SAD: VolAddress<*const u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00D4) };
|
||||
/// DMA 3 Destination Address, read only.
|
||||
const DMA3DAD: VolAddress<*mut u32, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00D8) };
|
||||
/// DMA 3 Word Count, read only.
|
||||
const DMA3CNT_L: VolAddress<u16, Safe, Unsafe> = unsafe { VolAddress::new(0x400_00DC) };
|
||||
/// DMA 3 Control, read/write.
|
||||
const DMA3CNT_H: VolAddress<DMAControlSetting, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x400_00DE) };
|
||||
|
||||
/// Assigns the source register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to read from.
|
||||
pub unsafe fn set_source(src: *const u32) {
|
||||
crate::sync::memory_read_hint(src);
|
||||
Self::DMA3SAD.write(src);
|
||||
}
|
||||
|
||||
/// Assigns the destination register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The source pointer must be aligned and valid to write to.
|
||||
pub unsafe fn set_dest(dest: *mut u32) {
|
||||
Self::DMA3DAD.write(dest);
|
||||
crate::sync::memory_write_hint(dest);
|
||||
}
|
||||
|
||||
/// Assigns the count register.
|
||||
///
|
||||
/// This register is read only, so it is not exposed directly.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The count given must specify a valid number of units to write, starting at
|
||||
/// the assigned destination address.
|
||||
pub unsafe fn set_count(count: u16) {
|
||||
Self::DMA3CNT_L.write(count)
|
||||
}
|
||||
|
||||
/// Reads the current control setting.
|
||||
pub fn control() -> DMAControlSetting {
|
||||
Self::DMA3CNT_H.read()
|
||||
}
|
||||
|
||||
/// Writes the control setting given.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must ensure that the Source, Destination, and Count values are set
|
||||
/// correctly **before** you activate the Enable bit.
|
||||
pub unsafe fn set_control(setting: DMAControlSetting) {
|
||||
Self::DMA3CNT_H.write(setting)
|
||||
}
|
||||
|
||||
/// Fills `count` slots (starting at `dest`) with the value at `src`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Both pointers must be aligned, and all positions specified for writing
|
||||
/// must be valid for writing.
|
||||
pub unsafe fn fill32(src: *const u32, dest: *mut u32, count: u16) {
|
||||
const FILL_CONTROL: DMAControlSetting = DMAControlSetting::new()
|
||||
.with_source_address_control(DMASrcAddressControl::Fixed)
|
||||
.with_use_32bit(true)
|
||||
.with_enabled(true);
|
||||
// TODO: destination checking against SRAM
|
||||
crate::sync::memory_read_hint(src);
|
||||
Self::DMA3SAD.write(src);
|
||||
Self::DMA3DAD.write(dest);
|
||||
Self::DMA3CNT_L.write(count);
|
||||
Self::DMA3CNT_H.write(FILL_CONTROL);
|
||||
crate::sync::memory_write_hint(dest);
|
||||
|
||||
// Note(Lokathor): Once DMA is set to activate it takes 2 cycles for it to
|
||||
// kick in. You can do any non-DMA thing you like before that, but since
|
||||
// it's only two cycles we just insert two NOP instructions to ensure that
|
||||
// successive calls to `fill32` or other DMA methods don't interfere with
|
||||
// each other.
|
||||
asm!(
|
||||
"
|
||||
NOP
|
||||
NOP
|
||||
",
|
||||
options(nomem, nostack)
|
||||
);
|
||||
}
|
||||
}
|
191
src/io/irq.rs
191
src/io/irq.rs
|
@ -1,191 +0,0 @@
|
|||
//! Module containing a wrapper for interrupt request (IRQ) handling.
|
||||
//!
|
||||
//! When an interrupt is executed, the CPU will be set to IRQ mode and code
|
||||
//! execution will jump to the physical interrupt vector, located in BIOS. The
|
||||
//! BIOS interrupt handler will then save several registers to the IRQ stack
|
||||
//! pointer and execution will jump to the user interrupt handler starting at
|
||||
//! `0x0300_7FFC`, in ARM mode.
|
||||
//!
|
||||
//! Currently, the user interrupt handler is defined in `rsrt0.S`. It is set up
|
||||
//! to execute a user-specified interrupt handler after saving some registers.
|
||||
//! This handler is declared as a static function pointer on the Rust side, and
|
||||
//! can be set by using [`set_irq_handler`](irq::set_irq_handler).
|
||||
//!
|
||||
//! ## Notes
|
||||
//! * The interrupt will only be triggered if [`IME`](irq::IME) is enabled, the
|
||||
//! flag corresponding to the interrupt is enabled on the [`IE`](irq::IE)
|
||||
//! register, and the "IRQ Enable" flag is set on the register related to the
|
||||
//! interrupt, which varies. For example, to enable interrupts on VBlank you
|
||||
//! would set the
|
||||
//! [`vblank_irq_enable`](io::display::DisplayStatusSetting::vblank_irq_enable)
|
||||
//! flag on the [`DISPSTAT`](io::display::DISPCNT) register.
|
||||
//! * If you intend to use [`interrupt_wait`](bios::interrupt_wait) or
|
||||
//! [`vblank_interrupt_wait`](bios::vblank_interrupt_wait) to wait for an
|
||||
//! interrupt, your interrupt handler MUST update the BIOS Interrupt Flags at
|
||||
//! [`BIOS_IF`](irq::BIOS_IF) in addition to the usual interrupt
|
||||
//! acknowledgement (which is handled for you by the user interrupt handler).
|
||||
//! This is done by setting the corresponding IRQ flag on
|
||||
//! [`BIOS_IF`](irq::BIOS_IF) at the end of the interrupt handler.
|
||||
//! * You can change the low-level details of the interrupt handler by editing
|
||||
//! the `MainIrqHandler` routine in `rsrt0.S`. For example, you could declare
|
||||
//! an external static variable in Rust holding a table of interrupt function
|
||||
//! pointers and jump directly into one of them in assembly, without the need
|
||||
//! to write the branching logic in Rust. However, note that the main
|
||||
//! interrupt handler MUST acknowledge all interrupts received by setting
|
||||
//! their corresponding bits to `1` in the [`IF`](irq::IF) register.
|
||||
//! * If you wait on one or more interrupts, be sure at least one of them is
|
||||
//! able to be triggered or the call to wait will never return.
|
||||
//! * If you wait on multiple interrupts and those interrupts fire too quickly,
|
||||
//! it is possible that the call to wait will never return as interrupts will
|
||||
//! be constantly received before control is returned to the caller. This
|
||||
//! usually only happens when waiting on multiple timer interrupts with very
|
||||
//! fast overflow rates.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! extern "C" fn irq_handler(flags: IrqFlags) {
|
||||
//! if flags.vblank() {
|
||||
//! // Run drawing logic here.
|
||||
//!
|
||||
//! // Acknowledge the IRQ on the BIOS Interrupt Flags register.
|
||||
//! BIOS_IF.write(BIOS_IF.read().with_vblank(true));
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main_loop() {
|
||||
//! // Set the IRQ handler to use.
|
||||
//! irq::set_irq_handler(irq_handler);
|
||||
//!
|
||||
//! // Handle only the VBlank interrupt.
|
||||
//! const FLAGS: IrqFlags = IrqFlags::new().with_vblank(true);
|
||||
//! IE.write(flags);
|
||||
//!
|
||||
//! // Enable all interrupts that are set in the IE register.
|
||||
//! IME.write(IrqEnableSetting::UseIE);
|
||||
//!
|
||||
//! // Enable IRQ generation during VBlank.
|
||||
//! const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||
//! .with_vblank_irq_enable(true);
|
||||
//! DISPSTAT.write(DISPLAY_SETTINGS);
|
||||
//!
|
||||
//! loop {
|
||||
//! // Sleep the CPU until a VBlank IRQ is generated.
|
||||
//! bios::vblank_interrupt_wait();
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Implementation Details
|
||||
//!
|
||||
//! This is the setup the provided user interrupt handler in `rsrt0.S` will do
|
||||
//! when an interrupt is received, in order. It is based on the _Recommended
|
||||
//! User Interrupt Handling_ portion of the GBATEK reference.
|
||||
//!
|
||||
//! 1. Save the status of [`IME`](irq::IME).
|
||||
//! 2. Save the IRQ stack pointer and change to system mode to use the user
|
||||
//! stack instead of the IRQ stack (to prevent stack overflow).
|
||||
//! 3. Disable interrupts by setting [`IME`](irq::IME) to 0, so other interrupts
|
||||
//! will not preempt the main interrupt handler.
|
||||
//! 4. Acknowledge all IRQs that occurred and were enabled in the
|
||||
//! [`IE`](irq::IE) register by writing the bits to the [`IF`](irq::IF)
|
||||
//! register.
|
||||
//! 5. Save the user stack pointer, switch to Thumb mode and jump to the
|
||||
//! user-specified interrupt handler. The IRQ flags that were set are passed
|
||||
//! as an argument in `r0`.
|
||||
//! 6. When the handler returns, restore the user stack pointer and switch back
|
||||
//! to IRQ mode.
|
||||
//! 7. Restore the IRQ stack pointer and the status of [`IME`](irq::IME).
|
||||
//! 8. Return to the BIOS interrupt handler.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype!(
|
||||
/// A newtype over all interrupt flags.
|
||||
IrqFlags,
|
||||
pub(crate) u16
|
||||
);
|
||||
|
||||
impl IrqFlags {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
vblank: 0,
|
||||
hblank: 1,
|
||||
vcounter: 2,
|
||||
timer0: 3,
|
||||
timer1: 4,
|
||||
timer2: 5,
|
||||
timer3: 6,
|
||||
serial: 7,
|
||||
dma0: 8,
|
||||
dma1: 9,
|
||||
dma2: 10,
|
||||
dma3: 11,
|
||||
keypad: 12,
|
||||
game_pak: 13,
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupt Enable Register. Read/Write.
|
||||
///
|
||||
/// After setting up interrupt handlers, set the flags on this register type corresponding to the
|
||||
/// IRQs you want to handle.
|
||||
pub const IE: VolAddress<IrqFlags, Safe, Unsafe> = unsafe { VolAddress::new(0x400_0200) };
|
||||
|
||||
/// Interrupt Request Flags / IRQ Acknowledge. Read/Write.
|
||||
///
|
||||
/// The main user interrupt handler will acknowledge the interrupt that was set
|
||||
/// by writing to this register, so there is usually no need to modify it.
|
||||
/// However, if the main interrupt handler in `rsrt0.S` is changed, then the
|
||||
/// handler must write a `1` bit to all bits that are enabled on this register
|
||||
/// when it is called.
|
||||
pub const IF: VolAddress<IrqFlags, Safe, Unsafe> = unsafe { VolAddress::new(0x400_0202) };
|
||||
|
||||
newtype! {
|
||||
/// Setting to control whether interrupts are enabled.
|
||||
IrqEnableSetting, u16
|
||||
}
|
||||
|
||||
impl IrqEnableSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
/// System-wide control for if interrupts of all kinds are enabled or not.
|
||||
interrupts_enabled: 0,
|
||||
}
|
||||
|
||||
/// Yes, you want to have interrupts.
|
||||
pub const IRQ_YES: Self = Self::new().with_interrupts_enabled(true);
|
||||
|
||||
/// No, you do not want to have interrupts.
|
||||
pub const IRQ_NO: Self = Self::new();
|
||||
}
|
||||
|
||||
/// Interrupt Master Enable Register. Read/Write.
|
||||
pub const IME: VolAddress<IrqEnableSetting, Safe, Unsafe> = unsafe { VolAddress::new(0x400_0208) };
|
||||
|
||||
/// BIOS Interrupt Flags. Read/Write.
|
||||
///
|
||||
/// When using either [`interrupt_wait`](bios::interrupt_wait) or
|
||||
/// [`vblank_interrupt_wait`](bios::vblank_interrupt_wait), the corresponding
|
||||
/// interrupt handler MUST set the flag of the interrupt it has handled on this
|
||||
/// register in addition to the usual interrupt acknowledgement.
|
||||
pub const BIOS_IF: VolAddress<IrqFlags, Safe, Unsafe> = unsafe { VolAddress::new(0x0300_7FF8) };
|
||||
|
||||
/// A function pointer for use as an interrupt handler.
|
||||
pub type IrqHandler = extern "C" fn(IrqFlags);
|
||||
|
||||
/// Sets the function to run when an interrupt is executed. The function will
|
||||
/// receive the interrupts that were acknowledged by the main interrupt handler
|
||||
/// as an argument.
|
||||
pub fn set_irq_handler(handler: IrqHandler) {
|
||||
unsafe {
|
||||
__IRQ_HANDLER = handler;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn default_handler(_flags: IrqFlags) {}
|
||||
|
||||
// Inner definition of the interrupt handler. It is referenced in `rsrt0.S`.
|
||||
#[doc(hidden)]
|
||||
#[no_mangle]
|
||||
static mut __IRQ_HANDLER: IrqHandler = default_handler;
|
129
src/io/keypad.rs
129
src/io/keypad.rs
|
@ -1,129 +0,0 @@
|
|||
//! Allows access to the keypad.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The Key Input Register.
|
||||
///
|
||||
/// This register follows the "low-active" convention. If you want your code to
|
||||
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
||||
/// to work with) then call `read_key_input()` rather than reading this register
|
||||
/// directly. It will perform the necessary bit flip operation for you.
|
||||
pub const KEYINPUT: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x400_0130) };
|
||||
|
||||
/// A "tribool" value helps us interpret the arrow pad.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum TriBool {
|
||||
/// -1
|
||||
Minus = -1,
|
||||
/// +0
|
||||
Neutral = 0,
|
||||
/// +1
|
||||
Plus = 1,
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// Records a particular key press combination.
|
||||
///
|
||||
/// Methods here follow the "high-active" convention, where a bit is enabled
|
||||
/// when it's part of the set.
|
||||
KeyInput, u16
|
||||
}
|
||||
|
||||
impl KeyInput {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
a: 0,
|
||||
b: 1,
|
||||
select: 2,
|
||||
start: 3,
|
||||
right: 4,
|
||||
left: 5,
|
||||
up: 6,
|
||||
down: 7,
|
||||
r: 8,
|
||||
l: 9,
|
||||
}
|
||||
|
||||
/// Takes the set difference between these keys and another set of keys.
|
||||
pub fn difference(self, other: Self) -> Self {
|
||||
KeyInput(self.0 ^ other.0)
|
||||
}
|
||||
|
||||
/// Right/left tribool.
|
||||
///
|
||||
/// Right is Plus and Left is Minus
|
||||
pub fn x_tribool(self) -> TriBool {
|
||||
if self.right() {
|
||||
TriBool::Plus
|
||||
} else if self.left() {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
|
||||
/// Up/down tribool.
|
||||
///
|
||||
/// Down is Plus and Up is Minus
|
||||
pub fn y_tribool(self) -> TriBool {
|
||||
if self.down() {
|
||||
TriBool::Plus
|
||||
} else if self.up() {
|
||||
TriBool::Minus
|
||||
} else {
|
||||
TriBool::Neutral
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current state of the keys
|
||||
pub fn read_key_input() -> KeyInput {
|
||||
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
||||
// unused bits are always low, so we XOR with this mask to get a result where
|
||||
// the only active bits are currently pressed keys.
|
||||
KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111)
|
||||
}
|
||||
|
||||
/// Use this to configure when a keypad interrupt happens.
|
||||
///
|
||||
/// See the `KeyInterruptSetting` type for more.
|
||||
pub const KEYCNT: VolAddress<KeyInterruptSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0132) };
|
||||
|
||||
newtype! {
|
||||
/// Allows configuration of when a keypad interrupt fires.
|
||||
///
|
||||
/// * The most important bit here is the `irq_enabled` bit, which determines
|
||||
/// if an interrupt happens at all.
|
||||
/// * The second most important bit is the `irq_logical_and` bit. If this bit
|
||||
/// is set, _all_ the selected buttons are required to be set for the
|
||||
/// interrupt to be fired (logical AND). If it's not set then _any_ of the
|
||||
/// buttons selected can be pressed to fire the interrupt (logical OR).
|
||||
/// * All other bits select a particular button to be required or not as part
|
||||
/// of the interrupt firing.
|
||||
///
|
||||
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
||||
/// fire. You must still set the [`IME`](irq::IME) to have interrupts at all,
|
||||
/// and you must further set [`IE`](irq::IE) for keypad interrupts to be
|
||||
/// possible.
|
||||
KeyInterruptSetting, u16
|
||||
}
|
||||
#[allow(missing_docs)]
|
||||
impl KeyInterruptSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
a: 0,
|
||||
b: 1,
|
||||
select: 2,
|
||||
start: 3,
|
||||
right: 4,
|
||||
left: 5,
|
||||
up: 6,
|
||||
down: 7,
|
||||
r: 8,
|
||||
l: 9,
|
||||
irq_enabled: 14,
|
||||
irq_logical_and: 15,
|
||||
}
|
||||
}
|
269
src/io/sound.rs
269
src/io/sound.rs
|
@ -1,269 +0,0 @@
|
|||
//! Module for sound registers.
|
||||
|
||||
use super::*;
|
||||
|
||||
//TODO within these "read/write" registers only some bits are actually read/write!
|
||||
|
||||
/// Sound Channel 1 Sweep Register (`NR10`). Read/Write.
|
||||
pub const SOUND1CNT_L: VolAddress<SweepRegisterSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0060) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
SweepRegisterSetting, u16
|
||||
}
|
||||
|
||||
impl SweepRegisterSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sweep_shift: 0-2,
|
||||
sweep_decreasing: 3,
|
||||
sweep_time: 4-6,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 1 Duty/Length/Envelope (`NR11`, `NR12`). Read/Write.
|
||||
pub const SOUND1CNT_H: VolAddress<DutyLenEnvelopeSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0062) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
DutyLenEnvelopeSetting, u16
|
||||
}
|
||||
|
||||
impl DutyLenEnvelopeSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sound_length: 0-5,
|
||||
wave_pattern_duty: 6-7, //TODO: enum this
|
||||
envelope_step_time: 8-10,
|
||||
envelope_increasing: 11,
|
||||
initial_envelope_volume: 12-15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 1 Frequency/Control (`NR13`, `NR14`). Read/Write.
|
||||
pub const SOUND1CNT_X: VolAddress<FrequencyControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0064) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
FrequencyControlSetting, u32 // TODO: u16 or u32?
|
||||
}
|
||||
|
||||
impl FrequencyControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u32,
|
||||
frequency: 0-10,
|
||||
length_flag: 14,
|
||||
is_initial: 15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 2 Channel 2 Duty/Length/Envelope (`NR21`, `NR22`). Read/Write.
|
||||
pub const SOUND2CNT_L: VolAddress<DutyLenEnvelopeSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0068) };
|
||||
|
||||
/// Sound Channel 2 Frequency/Control (`NR23`, `NR24`). Read/Write.
|
||||
pub const SOUND2CNT_H: VolAddress<FrequencyControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_006C) };
|
||||
|
||||
/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write.
|
||||
pub const SOUND3CNT_L: VolAddress<StopWaveRAMSelectSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0070) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
StopWaveRAMSelectSetting, u16
|
||||
}
|
||||
|
||||
impl StopWaveRAMSelectSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
wave_ram_dimension_2d: 5,
|
||||
wave_ram_bank_number: 6,
|
||||
sound_channel_3_playing: 7,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 3 Length/Volume (`NR23`, `NR24`). Read/Write.
|
||||
pub const SOUND3CNT_H: VolAddress<LengthVolumeSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0072) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
LengthVolumeSetting, u16
|
||||
}
|
||||
|
||||
impl LengthVolumeSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sound_length: 0-7,
|
||||
sound_volume: 13-14,
|
||||
force_75percent: 15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 3 Frequency/Control (`NR33`, `NR34`). Read/Write.
|
||||
pub const SOUND3CNT_X: VolAddress<FrequencyControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0074) };
|
||||
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM0_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0090) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM0_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0092) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM1_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0094) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM1_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0096) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM2_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0098) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM2_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_009A) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM3_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_009C) };
|
||||
/// Channel 3 Wave Pattern RAM (W/R)
|
||||
pub const WAVE_RAM3_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_009E) };
|
||||
|
||||
/// Sound Channel 4 Length/Envelope (`NR41`, `NR42`). Read/Write.
|
||||
pub const SOUND4CNT_L: VolAddress<LengthEnvelopeSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0078) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
LengthEnvelopeSetting, u32 // TODO: is this u32?
|
||||
}
|
||||
|
||||
impl LengthEnvelopeSetting {
|
||||
phantom_fields! {
|
||||
self.0: u32,
|
||||
sound_length: 0-5,
|
||||
envelope_step_time: 8-10,
|
||||
envelope_increasing: 11,
|
||||
initial_envelope_volume: 12-15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound Channel 4 Frequency/Control (`NR43`, `NR44`). Read/Write.
|
||||
pub const SOUND4CNT_H: VolAddress<NoiseFrequencySetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_007C) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
NoiseFrequencySetting, u32 // TODO: is this u32?
|
||||
}
|
||||
|
||||
impl NoiseFrequencySetting {
|
||||
phantom_fields! {
|
||||
self.0: u32,
|
||||
frequency_divide_ratio: 0-2,
|
||||
counter_step_width_7bit: 3,
|
||||
shift_clock_frequency: 4-7,
|
||||
length_flag_stop: 14,
|
||||
initial_restart: 15,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: unify FIFO as
|
||||
|
||||
/// Sound A FIFO, Data 0 and Data 1 (W)
|
||||
pub const FIFO_A_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_00A0) };
|
||||
/// Sound A FIFO, Data 2 and Data 3 (W)
|
||||
pub const FIFO_A_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_00A2) };
|
||||
/// Sound B FIFO, Data 0 and Data 1 (W)
|
||||
pub const FIFO_B_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_00A4) };
|
||||
/// Sound B FIFO, Data 2 and Data 3 (W)
|
||||
pub const FIFO_B_H: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_00A6) };
|
||||
|
||||
/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write.
|
||||
pub const SOUNDCNT_L: VolAddress<NonWaveVolumeEnableSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0080) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
NonWaveVolumeEnableSetting, u16
|
||||
}
|
||||
|
||||
impl NonWaveVolumeEnableSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
right_master_volume: 0-2,
|
||||
left_master_volume: 4-6,
|
||||
right_enable_flags: 8-11, // TODO: this is junk
|
||||
left_enable_flags: 12-15, // TODO: junk
|
||||
}
|
||||
}
|
||||
|
||||
/// DMA Sound Control/Mixing. Read/Write.
|
||||
pub const SOUNDCNT_H: VolAddress<WaveVolumeEnableSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0082) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
WaveVolumeEnableSetting, u16
|
||||
}
|
||||
|
||||
impl WaveVolumeEnableSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sound_number_volume: 0-1=NumberSoundVolume<Quarter, Half, Full>,
|
||||
dma_sound_a_full_volume: 2,
|
||||
dma_sound_b_full_volume: 3,
|
||||
dma_sound_a_enable_right: 8,
|
||||
dma_sound_a_enable_left: 9,
|
||||
dma_sound_a_timer_select: 10,
|
||||
dma_sound_a_reset_fifo: 11,
|
||||
dma_sound_b_enable_right: 12,
|
||||
dma_sound_b_enable_left: 13,
|
||||
dma_sound_b_timer_select: 14,
|
||||
dma_sound_b_reset_fifo: 15,
|
||||
}
|
||||
}
|
||||
|
||||
newtype_enum! {
|
||||
/// TODO: docs
|
||||
NumberSoundVolume = u16,
|
||||
/// TODO: docs
|
||||
Quarter = 0,
|
||||
/// TODO: docs
|
||||
Half = 1,
|
||||
/// TODO: docs
|
||||
Full = 2,
|
||||
}
|
||||
|
||||
/// Sound on/off (`NR52`). Read/Write.
|
||||
pub const SOUNDCNT_X: VolAddress<SoundMasterSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0084) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
SoundMasterSetting, u16
|
||||
}
|
||||
|
||||
impl SoundMasterSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
sound1_on: 0,
|
||||
sound2_on: 1,
|
||||
sound3_on: 2,
|
||||
sound4_on: 3,
|
||||
psg_fifo_master_enabled: 7,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sound on/off (`NR52`). Read/Write.
|
||||
pub const SOUNDBIAS: VolAddress<SoundPWMSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0088) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
SoundPWMSetting, u16
|
||||
}
|
||||
|
||||
impl SoundMasterSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
bias_level: 1-9,
|
||||
amplitude_resolution: 14-15, // TODO: enum this
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
//! Module for timers.
|
||||
//!
|
||||
//! The timers are slightly funny in that reading and writing from them works
|
||||
//! somewhat differently than with basically any other part of memory.
|
||||
//!
|
||||
//! When you read a timer's counter you read the current value.
|
||||
//!
|
||||
//! When you write a timer's counter you write _the counter's reload value_.
|
||||
//! This is used whenever you enable the timer or any time the timer overflows.
|
||||
//! You cannot set a timer to a given counter value, but you can set a timer to
|
||||
//! start at some particular value every time it reloads.
|
||||
//!
|
||||
//! The timer counters are `u16`, so if you want to set them to run for a
|
||||
//! certain number of ticks before overflow you would write something like
|
||||
//!
|
||||
//! ```rust
|
||||
//! let init_val: u16 = u32::wrapping_sub(0x1_0000, ticks) as u16;
|
||||
//! ```
|
||||
//!
|
||||
//! A timer reloads any time it overflows _or_ goes from disabled to enabled. If
|
||||
//! you want to "pause" a timer _without_ making it reload when resumed then you
|
||||
//! should not disable it. Instead, you should set its `TimerTickRate` to
|
||||
//! `Cascade` and disable _the next lower timer_ so that it won't overflow into
|
||||
//! the timer you have on hold.
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: striding blocks?
|
||||
|
||||
/// Timer 0 Counter/Reload. Special (see module).
|
||||
pub const TM0CNT_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0100) };
|
||||
|
||||
/// Timer 1 Counter/Reload. Special (see module).
|
||||
pub const TM1CNT_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0104) };
|
||||
|
||||
/// Timer 2 Counter/Reload. Special (see module).
|
||||
pub const TM2CNT_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0108) };
|
||||
|
||||
/// Timer 3 Counter/Reload. Special (see module).
|
||||
pub const TM3CNT_L: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_010C) };
|
||||
|
||||
/// Timer 0 Control. Read/Write.
|
||||
pub const TM0CNT_H: VolAddress<TimerControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0102) };
|
||||
|
||||
/// Timer 1 Control. Read/Write.
|
||||
pub const TM1CNT_H: VolAddress<TimerControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0106) };
|
||||
|
||||
/// Timer 2 Control. Read/Write.
|
||||
pub const TM2CNT_H: VolAddress<TimerControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_010A) };
|
||||
|
||||
/// Timer 3 Control. Read/Write.
|
||||
pub const TM3CNT_H: VolAddress<TimerControlSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_010E) };
|
||||
|
||||
newtype! {
|
||||
/// Allows control of a timer unit.
|
||||
///
|
||||
/// * Bits 0-2: How often the timer should tick up one unit. You can either
|
||||
/// specify a number of CPU cycles or "cascade" mode, where there's a single
|
||||
/// tick per overflow of the next lower timer. For example, Timer 1 would
|
||||
/// tick up once per overflow of Timer 0 if it were in cascade mode. Cascade
|
||||
/// mode naturally does nothing when used with Timer 0.
|
||||
/// * Bit 6: Raise a timer interrupt upon overflow.
|
||||
/// * Bit 7: Enable the timer.
|
||||
TimerControlSetting, u16
|
||||
}
|
||||
impl TimerControlSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
tick_rate: 0-2=TimerTickRate<CPU1, CPU64, CPU256, CPU1024, Cascade>,
|
||||
overflow_irq: 6,
|
||||
enabled: 7,
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls how often an enabled timer ticks upward.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum TimerTickRate {
|
||||
/// Once every CPU cycle
|
||||
CPU1 = 0,
|
||||
/// Once per 64 CPU cycles
|
||||
CPU64 = 1,
|
||||
/// Once per 256 CPU cycles
|
||||
CPU256 = 2,
|
||||
/// Once per 1,024 CPU cycles
|
||||
CPU1024 = 3,
|
||||
/// Once per overflow of the next lower timer. (Useless with Timer 0)
|
||||
Cascade = 4,
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
//! Module that holds stuff for the Window ability.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Window 0 Horizontal Dimensions (W)
|
||||
pub const WIN0H: VolAddress<HorizontalWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0040) };
|
||||
|
||||
/// Window 1 Horizontal Dimensions (W)
|
||||
pub const WIN1H: VolAddress<HorizontalWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0042) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
HorizontalWindowSetting, u16
|
||||
}
|
||||
|
||||
impl HorizontalWindowSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
col_end: 0-7,
|
||||
col_start: 8-15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Window 0 Vertical Dimensions (W)
|
||||
pub const WIN0V: VolAddress<VerticalWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0044) };
|
||||
|
||||
/// Window 1 Vertical Dimensions (W)
|
||||
pub const WIN1V: VolAddress<VerticalWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0046) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
VerticalWindowSetting, u16
|
||||
}
|
||||
|
||||
impl VerticalWindowSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
row_end: 0-7,
|
||||
row_start: 8-15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Control of Inside of Window(s) (R/W)
|
||||
pub const WININ: VolAddress<InsideWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_0048) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
InsideWindowSetting, u16
|
||||
}
|
||||
|
||||
impl InsideWindowSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
win0_bg0: 0,
|
||||
win0_bg1: 1,
|
||||
win0_bg2: 2,
|
||||
win0_bg3: 3,
|
||||
win0_obj: 4,
|
||||
win0_color_special: 5,
|
||||
win1_bg0: 8,
|
||||
win1_bg1: 9,
|
||||
win1_bg2: 10,
|
||||
win1_bg3: 11,
|
||||
win1_obj: 12,
|
||||
win1_color_special: 13,
|
||||
}
|
||||
}
|
||||
|
||||
/// Control of Outside of Windows & Inside of OBJ Window (R/W)
|
||||
pub const WINOUT: VolAddress<OutsideWindowSetting, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x400_004A) };
|
||||
|
||||
newtype! {
|
||||
/// TODO: docs
|
||||
OutsideWindowSetting, u16
|
||||
}
|
||||
|
||||
impl OutsideWindowSetting {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
outside_bg0: 0,
|
||||
outside_bg1: 1,
|
||||
outside_bg2: 2,
|
||||
outside_bg3: 3,
|
||||
outside_obj: 4,
|
||||
outside_color_special: 5,
|
||||
obj_win_bg0: 8,
|
||||
obj_win_bg1: 9,
|
||||
obj_win_bg2: 10,
|
||||
obj_win_bg3: 11,
|
||||
obj_win_obj: 12,
|
||||
obj_win_color_special: 13,
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
//! Module for Internal Work RAM (`IWRAM`).
|
134
src/lib.rs
134
src/lib.rs
|
@ -1,49 +1,56 @@
|
|||
#![cfg_attr(not(test), no_std)]
|
||||
#![no_std]
|
||||
#![feature(asm, global_asm, isa_attribute)]
|
||||
#![allow(unused_imports)]
|
||||
//#![warn(missing_docs)]
|
||||
|
||||
//! This crate helps you write GBA ROMs.
|
||||
//!
|
||||
//! ## SAFETY POLICY
|
||||
//! ## Safety
|
||||
//!
|
||||
//! Some parts of this crate are safe wrappers around unsafe operations. This is
|
||||
//! good, and what you'd expect from a Rust crate.
|
||||
//! This crate takes *minimal* precautions to avoid GBA specific code from being
|
||||
//! run on a standard desktop by accident by using `#[cfg(target_arch = "arm")]`
|
||||
//! in appropriate places. However, there are obviously many other ARM devices
|
||||
//! in the world. If you actually run the GBA specific code on something that
|
||||
//! isn't a GBA, then that's your fault.
|
||||
//!
|
||||
//! However, the safe wrappers all assume that you will _only_ attempt to
|
||||
//! execute this crate on a GBA or in a GBA Emulator.
|
||||
//! ## Docs.rs
|
||||
//!
|
||||
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
||||
//! do, it's a giant bag of Undefined Behavior.
|
||||
//! The docs on docs.rs are generated for the `thumbv6m-none-eabi` target
|
||||
//! because the docs.rs docker image isn't currently able to use the
|
||||
//! `-Zbuild-std=core` ability of cargo. Instead, we have it just build using a
|
||||
//! "close enough" Tier 2 target.
|
||||
//!
|
||||
//! When building your actual GBA games you should of course use the
|
||||
//! `thumbv4t-none-eabi` target.
|
||||
|
||||
pub(crate) use gba_proc_macro::phantom_fields;
|
||||
pub mod prelude {
|
||||
pub use crate::mmio_types::*;
|
||||
|
||||
use voladdress::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::mmio_addresses::*;
|
||||
|
||||
pub mod macros;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::bios::*;
|
||||
}
|
||||
|
||||
pub mod mmio_types;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod mmio_addresses;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod bios;
|
||||
|
||||
pub mod iwram;
|
||||
|
||||
pub mod ewram;
|
||||
|
||||
pub mod io;
|
||||
|
||||
pub mod palram;
|
||||
|
||||
pub mod vram;
|
||||
|
||||
pub mod oam;
|
||||
|
||||
pub mod rom;
|
||||
|
||||
pub mod save;
|
||||
pub mod art;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod sync;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod save;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod debug;
|
||||
|
||||
/*
|
||||
extern "C" {
|
||||
/// This marks the end of the `.data` and `.bss` sections in IWRAM.
|
||||
///
|
||||
|
@ -56,25 +63,7 @@ extern "C" {
|
|||
pub static __bss_end: u8;
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
||||
#[derive(PartialOrd, Ord, Hash)]
|
||||
Color, pub u16
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Constructs a color from the channel values provided (should be 0..=31).
|
||||
///
|
||||
/// No actual checks are performed, so illegal channel values can overflow
|
||||
/// into each other and produce an unintended color.
|
||||
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
||||
Color(b << 10 | g << 5 | r)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// After here is totally unsorted nonsense
|
||||
//
|
||||
TODO: math module for math functions you probably want on the GBA
|
||||
|
||||
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||
|
@ -180,55 +169,4 @@ pub unsafe fn divrem_i32_unchecked(numer: i32, denom: i32) -> (i32, i32) {
|
|||
(false, false) => (udiv as i32, urem as i32),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quickcheck::quickcheck;
|
||||
|
||||
// We have an explicit property on the non_restoring division
|
||||
quickcheck! {
|
||||
fn divrem_u32_non_restoring_prop(num: u32, denom: u32) -> bool {
|
||||
if denom > 0 {
|
||||
divrem_u32_non_restoring(num, denom) == (num / denom, num % denom)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have an explicit property on the simple division
|
||||
quickcheck! {
|
||||
fn divrem_u32_simple_prop(num: u32, denom: u32) -> bool {
|
||||
if denom > 0 {
|
||||
divrem_u32_simple(num, denom) == (num / denom, num % denom)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test the u32 wrapper
|
||||
quickcheck! {
|
||||
fn divrem_u32_prop(num: u32, denom: u32) -> bool {
|
||||
if denom > 0 {
|
||||
divrem_u32(num, denom).unwrap() == (num / denom, num % denom)
|
||||
} else {
|
||||
divrem_u32(num, denom).is_none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test the i32 wrapper
|
||||
quickcheck! {
|
||||
fn divrem_i32_prop(num: i32, denom: i32) -> bool {
|
||||
if denom == 0 || num == core::i32::MIN && denom == -1 {
|
||||
divrem_i32(num, denom).is_none()
|
||||
} else {
|
||||
divrem_i32(num, denom).unwrap() == (num / denom, num % denom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
190
src/macros.rs
190
src/macros.rs
|
@ -1,190 +0,0 @@
|
|||
//! Contains the macros for the crate.
|
||||
//!
|
||||
//! Because (unlike everything else in Rust) a macro has to be declared before
|
||||
//! use, we place them in their own module and then declare that module at the
|
||||
//! start of the crate.
|
||||
|
||||
/// Assists in defining a newtype wrapper over some base type.
|
||||
///
|
||||
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
||||
/// of your docs and derives in front of your newtype in the same way you would
|
||||
/// for a normal struct. Then the inner type to be wrapped it name.
|
||||
///
|
||||
/// The macro _assumes_ that you'll be using it to wrap numeric types and that
|
||||
/// it's safe to have a `0` value, so it automatically provides a `const fn`
|
||||
/// method for `new` that just wraps `0`. Also, it derives Debug, Clone, Copy,
|
||||
/// Default, PartialEq, and Eq. If all this is not desired you can add `, no
|
||||
/// frills` to the invocation.
|
||||
///
|
||||
/// ```no_run
|
||||
/// newtype! {
|
||||
/// /// Records a particular key press combination.
|
||||
/// KeyInput, u16
|
||||
/// }
|
||||
/// newtype! {
|
||||
/// /// You can't derive most stuff above array size 32, so we add
|
||||
/// /// the `, no frills` modifier to this one.
|
||||
/// BigArray, [u8; 200], no frills
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! newtype {
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
|
||||
$(#[$attr])*
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($v $old_name);
|
||||
impl $new_name {
|
||||
/// A `const` "zero value" constructor
|
||||
pub const fn new() -> Self {
|
||||
$new_name(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
|
||||
$(#[$attr])*
|
||||
#[repr(transparent)]
|
||||
pub struct $new_name($v $old_name);
|
||||
};
|
||||
}
|
||||
|
||||
/// Assists in defining a newtype that's an enum.
|
||||
///
|
||||
/// First give `NewType = OldType,`, then define the tags and their explicit
|
||||
/// values with zero or more entries of `TagName = base_value,`. In both cases
|
||||
/// you can place doc comments or other attributes directly on to the type
|
||||
/// declaration or the tag declaration.
|
||||
///
|
||||
/// The generated enum will get an appropriate `repr` attribute as well as
|
||||
/// Debug, Clone, Copy, PartialEq, and Eq
|
||||
///
|
||||
/// ```no_run
|
||||
/// newtype_enum! {
|
||||
/// /// The Foo
|
||||
/// Foo = u16,
|
||||
/// /// The Bar
|
||||
/// Bar = 0,
|
||||
/// /// The Zap
|
||||
/// Zap = 1,
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! newtype_enum {
|
||||
(
|
||||
$(#[$struct_attr:meta])*
|
||||
$new_name:ident = $old_name:ident,
|
||||
$($(#[$tag_attr:meta])* $tag_name:ident = $base_value:expr,)*
|
||||
) => {
|
||||
$(#[$struct_attr])*
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr($old_name)]
|
||||
pub enum $new_name {
|
||||
$(
|
||||
$(#[$tag_attr])*
|
||||
$tag_name = $base_value,
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Delivers a fatal message to the emulator debug output, and crashes
|
||||
/// the the game.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! fatal {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Fatal, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
debug::crash()
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a error message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Error, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a warning message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Warning, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers an info message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Info, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Delivers a debug message to the emulator debug output.
|
||||
///
|
||||
/// This works basically like `println`. You should avoid null ASCII values.
|
||||
/// Furthermore on mGBA, there is a maximum length of 255 bytes per message.
|
||||
///
|
||||
/// This has no effect outside of a supported emulator.
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::debug;
|
||||
if !debug::is_debugging_disabled() {
|
||||
debug::debug_print(debug::DebugLevel::Debug, &format_args!($($arg)*)).ok();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Using timers 0 and 1, performs a crude timing of the expression given.
|
||||
#[macro_export]
|
||||
macro_rules! time_this01 {
|
||||
($x:expr) => {{
|
||||
use $crate::io::timers::*;
|
||||
const NORMAL_ON: TimerControlSetting = TimerControlSetting::new().with_enabled(true);
|
||||
const CASCADE_ON: TimerControlSetting =
|
||||
TimerControlSetting::new().with_enabled(true).with_tick_rate(TimerTickRate::Cascade);
|
||||
const OFF: TimerControlSetting = TimerControlSetting::new();
|
||||
TM1CNT_H.write(CASCADE_ON);
|
||||
TM0CNT_H.write(NORMAL_ON);
|
||||
$x;
|
||||
TM0CNT_H.write(OFF);
|
||||
TM1CNT_H.write(OFF);
|
||||
let end_low = TM0CNT_L.read() as u32;
|
||||
let end_high = TM1CNT_L.read() as u32;
|
||||
end_high << 16 | end_low
|
||||
}};
|
||||
}
|
289
src/mmio_addresses.rs
Normal file
289
src/mmio_addresses.rs
Normal file
|
@ -0,0 +1,289 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use voladdress::*;
|
||||
|
||||
pub mod mode3;
|
||||
|
||||
// TODO: modules for the other video modes
|
||||
|
||||
/// [DISPCNT](https://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||
pub const DISPCNT: VolAddress<DisplayControl, Safe, Safe> = unsafe { VolAddress::new(0x0400_0000) };
|
||||
|
||||
/// [DISPSTAT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||
pub const DISPSTAT: VolAddress<DisplayStatus, Safe, Safe> = unsafe { VolAddress::new(0x0400_0004) };
|
||||
|
||||
/// [VCOUNT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||
pub const VCOUNT: VolAddress<u8, Safe, ()> = unsafe { VolAddress::new(0x0400_0006) };
|
||||
|
||||
/// [BG0CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG0CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0008) };
|
||||
|
||||
/// [BG1CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG1CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000A) };
|
||||
|
||||
/// [BG2CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG2CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000C) };
|
||||
|
||||
/// [BG3CNT](https://problemkaputt.de/gbatek.htm#lcdiobgcontrol)
|
||||
pub const BG3CNT: VolAddress<BackgroundControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_000E) };
|
||||
|
||||
/// [BG0HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG0HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0010) };
|
||||
/// [BG0VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG0VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0012) };
|
||||
|
||||
/// [BG1HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG1HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0014) };
|
||||
/// [BG1VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG1VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0016) };
|
||||
|
||||
/// [BG2HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG2HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0018) };
|
||||
/// [BG2VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG2VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001A) };
|
||||
|
||||
/// [BG3HOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG3HOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001C) };
|
||||
/// [BG3VOFS](https://problemkaputt.de/gbatek.htm#lcdiobgscrolling)
|
||||
pub const BG3VOFS: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_001E) };
|
||||
|
||||
/// [BG2PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PA: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0020) };
|
||||
/// [BG2PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PB: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0022) };
|
||||
/// [BG2PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PC: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0024) };
|
||||
/// [BG2PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2PD: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0026) };
|
||||
|
||||
/// [BG2X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2X: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_0028) };
|
||||
/// [BG2Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG2Y: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_002C) };
|
||||
|
||||
/// [BG3PA](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PA: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0030) };
|
||||
/// [BG3PB](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PB: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0032) };
|
||||
/// [BG3PC](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PC: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0034) };
|
||||
/// [BG3PD](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3PD: VolAddress<i16, (), Safe> = unsafe { VolAddress::new(0x0400_0036) };
|
||||
|
||||
/// [BG3X](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3X: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_0038) };
|
||||
/// [BG3Y](https://problemkaputt.de/gbatek.htm#lcdiobgrotationscaling)
|
||||
pub const BG3Y: VolAddress<i32, (), Safe> = unsafe { VolAddress::new(0x0400_003C) };
|
||||
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0H_RIGHT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0040) };
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0H_LEFT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0041) };
|
||||
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1H_RIGHT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0042) };
|
||||
/// [WIN0H](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1H_LEFT: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0043) };
|
||||
|
||||
/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0V_BOTTOM: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0044) };
|
||||
/// [WIN0V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN0V_TOP: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0045) };
|
||||
|
||||
/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1V_BOTTOM: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0046) };
|
||||
/// [WIN1V](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN1V_TOP: VolAddress<u8, (), Safe> = unsafe { VolAddress::new(0x0400_0047) };
|
||||
|
||||
/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_0: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_0048) };
|
||||
/// [WININ](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_1: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_0049) };
|
||||
/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_OUT: VolAddress<WindowEnable, Safe, Safe> = unsafe { VolAddress::new(0x0400_004A) };
|
||||
/// [WINOUT](https://problemkaputt.de/gbatek.htm#lcdiowindowfeature)
|
||||
pub const WIN_IN_OBJ: VolAddress<WindowEnable, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_004B) };
|
||||
|
||||
/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction)
|
||||
pub const MOSAIC_BG: VolAddress<MosaicSize, (), Safe> = unsafe { VolAddress::new(0x0400_004C) };
|
||||
/// [MOSAIC](https://problemkaputt.de/gbatek.htm#lcdiomosaicfunction)
|
||||
pub const MOSAIC_OBJ: VolAddress<MosaicSize, (), Safe> = unsafe { VolAddress::new(0x0400_004D) };
|
||||
|
||||
/// [BLDCNT](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDCNT: VolAddress<BlendControl, Safe, Safe> = unsafe { VolAddress::new(0x0400_0050) };
|
||||
|
||||
/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDALPHA_A: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0052) };
|
||||
|
||||
/// [BLDALPHA](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDALPHA_B: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0053) };
|
||||
|
||||
/// [BLDY](https://problemkaputt.de/gbatek.htm#lcdiocolorspecialeffects)
|
||||
pub const BLDY: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x0400_0054) };
|
||||
|
||||
/// [SOUND1CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_SWEEP: VolAddress<ToneSweep, Safe, Safe> = unsafe { VolAddress::new(0x0400_0060) };
|
||||
/// [SOUND1CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_DUTY_LEN_ENV: VolAddress<ToneDutyLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0062) };
|
||||
/// [SOUND1CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel1tonesweep)
|
||||
pub const TONE1_FREQ_CNT: VolAddress<ToneFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0064) };
|
||||
|
||||
/// [SOUND2CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone)
|
||||
pub const TONE2_DUTY_LEN_ENV: VolAddress<ToneDutyLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0068) };
|
||||
/// [SOUND2CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel2tone)
|
||||
pub const TONE2_FREQ_CNT: VolAddress<ToneFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_006C) };
|
||||
|
||||
/// [SOUND3CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_CONTROL: VolAddress<WaveControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0070) };
|
||||
/// [SOUND3CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_LEN_VOLUME: VolAddress<WaveLenVolume, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0072) };
|
||||
/// [SOUND3CNT_X](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_FREQ_CNT: VolAddress<WaveFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0074) };
|
||||
/// [WAVE_RAM](https://problemkaputt.de/gbatek.htm#gbasoundchannel3waveoutput)
|
||||
pub const WAVE_RAM: VolBlock<u32, Safe, Safe, 4> = unsafe { VolBlock::new(0x0400_0090) };
|
||||
|
||||
/// [SOUND4CNT_L](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise)
|
||||
pub const NOISE_LEN_ENV: VolAddress<NoiseLenEnv, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0078) };
|
||||
/// [SOUND4CNT_H](https://problemkaputt.de/gbatek.htm#gbasoundchannel4noise)
|
||||
pub const NOISE_FREQ_CNT: VolAddress<NoiseFrequencyControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_007C) };
|
||||
|
||||
/// [FIFO_A](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound)
|
||||
pub const FIFO_A: VolAddress<u32, (), Safe> = unsafe { VolAddress::new(0x0400_00A0) };
|
||||
/// [FIFO_B](https://problemkaputt.de/gbatek.htm#gbasoundchannelaandbdmasound)
|
||||
pub const FIFO_B: VolAddress<u32, (), Safe> = unsafe { VolAddress::new(0x0400_00A4) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Source Address (W) (internal memory)
|
||||
pub const DMA0SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Destination Address (W) (internal memory)
|
||||
pub const DMA0DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA0CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00B8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 0 Control (R/W)
|
||||
pub const DMA0CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00BA) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Source Address (W) (any memory)
|
||||
pub const DMA1SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00BC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Destination Address (W) (internal memory)
|
||||
pub const DMA1DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA1CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 1 Control (R/W)
|
||||
pub const DMA1CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00C6) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Source Address (W) (any memory)
|
||||
pub const DMA2SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00C8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Destination Address (W) (internal memory)
|
||||
pub const DMA2DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00CC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Word Count (W) (14 bit, 1..4000h)
|
||||
pub const DMA2CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D0) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 2 Control (R/W)
|
||||
pub const DMA2CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00D2) };
|
||||
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Source Address (W) (any memory)
|
||||
pub const DMA3SAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D4) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Destination Address (W) (any memory)
|
||||
pub const DMA3DAD: VolAddress<usize, (), Unsafe> = unsafe { VolAddress::new(0x0400_00D8) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Word Count (W) (16 bit, 1..10000h)
|
||||
pub const DMA3CNT_L: VolAddress<u16, (), Unsafe> = unsafe { VolAddress::new(0x0400_00DC) };
|
||||
/// [DMA](https://problemkaputt.de/gbatek.htm#gbadmatransfers) 3 Control (R/W)
|
||||
pub const DMA3CNT_H: VolAddress<DmaControl, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_00DE) };
|
||||
|
||||
/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0100) };
|
||||
/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0104) };
|
||||
/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_0108) };
|
||||
/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_COUNTER: VolAddress<u16, Safe, ()> = unsafe { VolAddress::new(0x0400_010C) };
|
||||
|
||||
/// [TM0CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0100) };
|
||||
/// [TM1CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0104) };
|
||||
/// [TM2CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_0108) };
|
||||
/// [TM3CNT_L](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_RELOAD: VolAddress<u16, (), Safe> = unsafe { VolAddress::new(0x0400_010C) };
|
||||
|
||||
/// [TM0CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER0_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0102) };
|
||||
/// [TM1CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER1_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0106) };
|
||||
/// [TM2CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER2_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_010A) };
|
||||
/// [TM3CNT_H](https://problemkaputt.de/gbatek.htm#gbatimers)
|
||||
pub const TIMER3_CONTROL: VolAddress<TimerControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_010E) };
|
||||
|
||||
/// [SIOCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const SIOCNT: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x400_0128) };
|
||||
/// [SIODATA8](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const SIODATA8: VolAddress<u8, Safe, Safe> = unsafe { VolAddress::new(0x400_012A) };
|
||||
/// [RCNT](https://problemkaputt.de/gbatek.htm#gbacommunicationports)
|
||||
pub const RCNT: VolAddress<u16, Safe, Safe> = unsafe { VolAddress::new(0x0400_0134) };
|
||||
|
||||
/// [KEYINPUT](https://problemkaputt.de/gbatek.htm#gbakeypadinput)
|
||||
pub const KEYINPUT: VolAddress<KeysLowActive, Safe, ()> = unsafe { VolAddress::new(0x0400_0130) };
|
||||
/// [KEYCNT](https://problemkaputt.de/gbatek.htm#gbakeypadinput)
|
||||
pub const KEYCNT: VolAddress<KeyInterruptControl, Safe, Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0130) };
|
||||
|
||||
/// Points to the (A32) user interrupt handler function.
|
||||
pub const USER_IRQ_HANDLER: VolAddress<Option<unsafe extern "C" fn()>, Safe, Unsafe> =
|
||||
unsafe { VolAddress::new(0x0300_7FFC) };
|
||||
/// "Interrupt Master Enable", [IME](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol)
|
||||
pub const IME: VolAddress<bool, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_0208) };
|
||||
/// "Interrupts Enabled", [IE](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol)
|
||||
pub const IE: VolAddress<InterruptFlags, Safe, Unsafe> = unsafe { VolAddress::new(0x0400_0200) };
|
||||
/// Shows which interrupts are pending.
|
||||
///
|
||||
/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (reading)
|
||||
pub const IRQ_PENDING: VolAddress<InterruptFlags, Safe, ()> =
|
||||
unsafe { VolAddress::new(0x0400_0202) };
|
||||
/// Acknowledges an interrupt as having been handled.
|
||||
///
|
||||
/// [IF](https://problemkaputt.de/gbatek.htm#gbainterruptcontrol) (writing)
|
||||
pub const IRQ_ACKNOWLEDGE: VolAddress<InterruptFlags, (), Safe> =
|
||||
unsafe { VolAddress::new(0x0400_0202) };
|
||||
/// Use this during [`IntrWait`] and [`VBlankIntrWait`] interrupt handling.
|
||||
///
|
||||
/// You should:
|
||||
/// * read the current value
|
||||
/// * set any additional interrupt bits that you wish to mark as handled (do not
|
||||
/// clear any currently set bits!)
|
||||
/// * write the new value back to this register
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use crate::prelude::*;
|
||||
/// // to acknowledge a vblank interrupt
|
||||
/// let current = INTR_WAIT_ACKNOWLEDGE.read();
|
||||
/// unsafe { INTR_WAIT_ACKNOWLEDGE.write(current.with_vblank(true)) };
|
||||
/// ```
|
||||
///
|
||||
/// [GBATEK: IntrWait](https://problemkaputt.de/gbatek.htm#bioshaltfunctions)
|
||||
pub const INTR_WAIT_ACKNOWLEDGE: VolAddress<InterruptFlags, Safe, Unsafe> = unsafe {
|
||||
// Note(Lokathor): This uses a mirrored location that's closer to the main IO
|
||||
// Control memory region so that LLVM has a better chance of being able to
|
||||
// just do an offset read/write from an address that's already in a register.
|
||||
// The "base" address of this location is 0x0300_7FF8, so some documents may
|
||||
// refer to that value instead.
|
||||
VolAddress::new(0x0300_FFF8)
|
||||
};
|
42
src/mmio_addresses/mode3.rs
Normal file
42
src/mmio_addresses/mode3.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use crate::prelude::Color;
|
||||
|
||||
use voladdress::*;
|
||||
|
||||
pub const WIDTH: usize = 240;
|
||||
|
||||
pub const HEIGHT: usize = 160;
|
||||
|
||||
pub const BITMAP: VolBlock<Color, Safe, Safe, { WIDTH * HEIGHT }> =
|
||||
unsafe { VolBlock::new(0x0600_0000) };
|
||||
|
||||
pub const fn bitmap_xy(x: usize, y: usize) -> VolAddress<Color, Safe, Safe> {
|
||||
BITMAP.index(y * WIDTH + x)
|
||||
}
|
||||
|
||||
pub fn dma3_clear_to(color: Color) {
|
||||
use crate::prelude::{
|
||||
DestAddrControl, DmaControl, DmaStartTiming, SrcAddrControl, DMA3CNT_H, DMA3CNT_L, DMA3DAD,
|
||||
DMA3SAD,
|
||||
};
|
||||
let wide_color: u32 = color.0 as u32 | ((color.0 as u32) << 16);
|
||||
unsafe {
|
||||
DMA3SAD.write(&wide_color as *const _ as usize);
|
||||
DMA3DAD.write(0x0600_0000);
|
||||
const MODE3_WORD_COUNT: u16 = (WIDTH * HEIGHT * 2 / 4_usize) as u16;
|
||||
DMA3CNT_L.write(MODE3_WORD_COUNT);
|
||||
const CTRL: DmaControl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Fixed)
|
||||
.with_transfer_u32(true)
|
||||
.with_start_time(DmaStartTiming::Immediately)
|
||||
.with_enabled(true);
|
||||
DMA3CNT_H.write(CTRL);
|
||||
asm!(
|
||||
"
|
||||
nop
|
||||
nop
|
||||
",
|
||||
options(nostack),
|
||||
);
|
||||
}
|
||||
}
|
145
src/mmio_types.rs
Normal file
145
src/mmio_types.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
#![allow(unused)]
|
||||
|
||||
/// Sets up a constant new constructor for a zeroed value.
|
||||
macro_rules! const_new {
|
||||
() => {
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use const_new;
|
||||
|
||||
/// Sets up a bitfield integer
|
||||
macro_rules! bitfield_int {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
pub const fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
((self.0 & MASK) >> $low) as $nt
|
||||
}
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ ($get as $inner)) & MASK))
|
||||
}
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_int;
|
||||
|
||||
/// Sets up a bitfield int wrapped newtype
|
||||
macro_rules! bitfield_newtype {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
pub const fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
$nt(self.0 & MASK)
|
||||
}
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ $get.0) & MASK))
|
||||
}
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_newtype;
|
||||
|
||||
/// Sets up a bitfield enum (CAUTION: misuse of this can cause UB!)
|
||||
macro_rules! bitfield_enum {
|
||||
($inner:ty; $low:literal ..= $high:literal : $nt:ident, $get:ident, $with:ident, $set:ident) => {
|
||||
// TODO: make this const when we have const transmute
|
||||
pub fn $get(self) -> $nt {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
unsafe { core::mem::transmute(self.0 & MASK) }
|
||||
}
|
||||
pub const fn $with(self, $get: $nt) -> Self {
|
||||
const MASK: $inner = ((1 << $high) - 1) << $low;
|
||||
Self(self.0 ^ ((self.0 ^ $get as $inner) & MASK))
|
||||
}
|
||||
pub fn $set(&mut self, $get: $nt) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_enum;
|
||||
|
||||
/// Sets up a bitfield bool
|
||||
macro_rules! bitfield_bool {
|
||||
($inner:ty; $bit:literal, $get:ident, $with:ident, $set:ident) => {
|
||||
pub const fn $get(self) -> bool {
|
||||
(self.0 & (1 << $bit)) != 0
|
||||
}
|
||||
pub const fn $with(self, $get: bool) -> Self {
|
||||
Self(self.0 ^ ((($get as $inner).wrapping_neg() ^ self.0) & (1 << $bit)))
|
||||
}
|
||||
pub fn $set(&mut self, $get: bool) {
|
||||
*self = self.$with($get);
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use bitfield_bool;
|
||||
|
||||
mod display_control;
|
||||
pub use display_control::*;
|
||||
|
||||
mod display_status;
|
||||
pub use display_status::*;
|
||||
|
||||
mod background_control;
|
||||
pub use background_control::*;
|
||||
|
||||
mod window_enable;
|
||||
pub use window_enable::*;
|
||||
|
||||
mod mosaic_size;
|
||||
pub use mosaic_size::*;
|
||||
|
||||
mod blend_control;
|
||||
pub use blend_control::*;
|
||||
|
||||
mod color;
|
||||
pub use color::*;
|
||||
|
||||
mod keys;
|
||||
pub use keys::*;
|
||||
|
||||
mod dma_control;
|
||||
pub use dma_control::*;
|
||||
|
||||
mod key_interrupt_control;
|
||||
pub use key_interrupt_control::*;
|
||||
|
||||
mod register_ram_reset_control;
|
||||
pub use register_ram_reset_control::*;
|
||||
|
||||
mod interrupt_flags;
|
||||
pub use interrupt_flags::*;
|
||||
|
||||
mod timer_control;
|
||||
pub use timer_control::*;
|
||||
|
||||
mod tone_duty_len_env;
|
||||
pub use tone_duty_len_env::*;
|
||||
|
||||
mod tone_frequency_control;
|
||||
pub use tone_frequency_control::*;
|
||||
|
||||
mod tone_sweep;
|
||||
pub use tone_sweep::*;
|
||||
|
||||
mod wave_control;
|
||||
pub use wave_control::*;
|
||||
|
||||
mod wave_len_volume;
|
||||
pub use wave_len_volume::*;
|
||||
|
||||
mod wave_frequency_control;
|
||||
pub use wave_frequency_control::*;
|
||||
|
||||
mod noise_len_env;
|
||||
pub use noise_len_env::*;
|
||||
|
||||
mod noise_frequency_control;
|
||||
pub use noise_frequency_control::*;
|
15
src/mmio_types/background_control.rs
Normal file
15
src/mmio_types/background_control.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct BackgroundControl(u16);
|
||||
impl BackgroundControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=1: u8, priority, with_priority, set_priority);
|
||||
bitfield_int!(u16; 2..=3: u8, char_base_block, with_char_base_block, set_char_base_block);
|
||||
bitfield_bool!(u16; 6, mosaic, with_mosaic, set_mosaic);
|
||||
bitfield_bool!(u16; 7, is_8bpp, with_is_8bpp, set_is_8bpp);
|
||||
bitfield_int!(u16; 8..=12: u8, screen_base_block, with_screen_base_block, set_screen_base_block);
|
||||
bitfield_bool!(u16; 13, affine_overflow_wrapped, with_affine_overflow_wrapped, set_affine_overflow_wrapped);
|
||||
bitfield_int!(u16; 14..=15: u8, screen_size, with_screen_size, set_screen_size);
|
||||
}
|
30
src/mmio_types/blend_control.rs
Normal file
30
src/mmio_types/blend_control.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct BlendControl(u16);
|
||||
impl BlendControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, bg0_1st_target, with_bg0_1st_target, set_bg0_1st_target);
|
||||
bitfield_bool!(u16; 1, bg1_1st_target, with_bg1_1st_target, set_bg1_1st_target);
|
||||
bitfield_bool!(u16; 2, bg2_1st_target, with_bg2_1st_target, set_bg2_1st_target);
|
||||
bitfield_bool!(u16; 3, bg3_1st_target, with_bg3_1st_target, set_bg3_1st_target);
|
||||
bitfield_bool!(u16; 4, obj_1st_target, with_obj_1st_target, set_obj_1st_target);
|
||||
bitfield_bool!(u16; 5, backdrop_1st_target, with_backdrop_1st_target, set_backdrop_1st_target);
|
||||
bitfield_enum!(u16; 6..=7: ColorSpecialEffect, effect, with_effect, set_effect);
|
||||
bitfield_bool!(u16; 8, bg0_2nd_target, with_bg0_2nd_target, set_bg0_2nd_target);
|
||||
bitfield_bool!(u16; 9, bg1_2nd_target, with_bg1_2nd_target, set_bg1_2nd_target);
|
||||
bitfield_bool!(u16; 10, bg2_2nd_target, with_bg2_2nd_target, set_bg2_2nd_target);
|
||||
bitfield_bool!(u16; 11, bg3_2nd_target, with_bg3_2nd_target, set_bg3_2nd_target);
|
||||
bitfield_bool!(u16; 12, obj_2nd_target, with_obj_2nd_target, set_obj_2nd_target);
|
||||
bitfield_bool!(u16; 13, backdrop_2nd_target, with_backdrop_2nd_target, set_backdrop_2nd_target);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ColorSpecialEffect {
|
||||
NoEffect = 0 << 6,
|
||||
AlphaBlend = 1 << 6,
|
||||
BrightnessIncrease = 2 << 6,
|
||||
BrightnessDecrease = 3 << 6,
|
||||
}
|
19
src/mmio_types/color.rs
Normal file
19
src/mmio_types/color.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Color(pub u16);
|
||||
impl Color {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=4: u8, red, with_red, set_red);
|
||||
bitfield_int!(u16; 5..=9: u8, green, with_green, set_green);
|
||||
bitfield_int!(u16; 10..=14: u8, blue, with_blue, set_blue);
|
||||
}
|
||||
impl Color {
|
||||
pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
|
||||
let r = red as u16;
|
||||
let g = green as u16;
|
||||
let b = blue as u16;
|
||||
Self(b << 10 | g << 15 | r)
|
||||
}
|
||||
}
|
36
src/mmio_types/display_control.rs
Normal file
36
src/mmio_types/display_control.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayControl(u16);
|
||||
impl DisplayControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=2: u16, display_mode, with_display_mode, set_display_mode);
|
||||
bitfield_bool!(u16; 4, display_frame1, with_display_frame1, set_display_frame1);
|
||||
bitfield_bool!(u16; 5, hblank_interval_free, with_hblank_interval_free, set_hblank_interval_free);
|
||||
bitfield_bool!(u16; 6, obj_vram_1d, with_obj_vram_1d, set_obj_vram_1d);
|
||||
bitfield_bool!(u16; 7, forced_blank, with_forced_blank, set_forced_blank);
|
||||
bitfield_bool!(u16; 8, display_bg0, with_display_bg0, set_display_bg0);
|
||||
bitfield_bool!(u16; 9, display_bg1, with_display_bg1, set_display_bg1);
|
||||
bitfield_bool!(u16; 10, display_bg2, with_display_bg2, set_display_bg2);
|
||||
bitfield_bool!(u16; 11, display_bg3, with_display_bg3, set_display_bg3);
|
||||
bitfield_bool!(u16; 12, display_obj, with_display_obj, set_display_obj);
|
||||
bitfield_bool!(u16; 13, display_win0, with_display_win0, set_display_win0);
|
||||
bitfield_bool!(u16; 14, display_win1, with_display_win1, set_display_win1);
|
||||
bitfield_bool!(u16; 15, display_obj_win, with_display_obj_win, set_display_obj_win);
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DisplayMode {
|
||||
_0 = 0,
|
||||
_1 = 1,
|
||||
_2 = 2,
|
||||
_3 = 3,
|
||||
_4 = 4,
|
||||
_5 = 5,
|
||||
_6 = 6,
|
||||
_7 = 7,
|
||||
}
|
||||
*/
|
15
src/mmio_types/display_status.rs
Normal file
15
src/mmio_types/display_status.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DisplayStatus(u16);
|
||||
impl DisplayStatus {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, is_vblank, with_is_vblank, set_is_vblank);
|
||||
bitfield_bool!(u16; 1, is_hblank, with_is_hblank, set_is_hblank);
|
||||
bitfield_bool!(u16; 2, is_vcount, with_is_vcount, set_is_vcount);
|
||||
bitfield_bool!(u16; 3, vblank_irq_enabled, with_vblank_irq_enabled, set_vblank_irq_enabled);
|
||||
bitfield_bool!(u16; 4, hblank_irq_enabled, with_hblank_irq_enabled, set_hblank_irq_enabled);
|
||||
bitfield_bool!(u16; 5, vcount_irq_enabled, with_vcount_irq_enabled, set_vcount_irq_enabled);
|
||||
bitfield_int!(u16; 8..=15: u16, vcount, with_vcount, set_vcount);
|
||||
}
|
44
src/mmio_types/dma_control.rs
Normal file
44
src/mmio_types/dma_control.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct DmaControl(u16);
|
||||
impl DmaControl {
|
||||
const_new!();
|
||||
bitfield_enum!(u16; 5..=6: DestAddrControl, dest_addr, with_dest_addr, set_dest_addr);
|
||||
bitfield_enum!(u16; 7..=8: SrcAddrControl, src_addr, with_src_addr, set_src_addr);
|
||||
bitfield_bool!(u16; 9, dma_repeat, with_dma_repeat, set_dma_repeat);
|
||||
bitfield_bool!(u16; 10, transfer_u32, with_transfer_u32, set_transfer_u32);
|
||||
bitfield_bool!(u16; 11, drq_from_game_pak, with_drq_from_game_pak, set_drq_from_game_pak);
|
||||
bitfield_enum!(u16; 12..=13: DmaStartTiming, start_time, with_start_time, set_start_time);
|
||||
bitfield_bool!(u16; 14, irq_when_done, with_irq_when_done, set_irq_when_done);
|
||||
bitfield_bool!(u16; 15, enabled, with_enabled, set_enabled);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DestAddrControl {
|
||||
Increment = 0 << 5,
|
||||
Decrement = 1 << 5,
|
||||
Fixed = 2 << 5,
|
||||
IncrementReload = 3 << 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum SrcAddrControl {
|
||||
Increment = 0 << 7,
|
||||
Decrement = 1 << 7,
|
||||
Fixed = 2 << 7,
|
||||
/// Never use this value, it is only to guard against UB bit patterns
|
||||
Prohibited = 3 << 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DmaStartTiming {
|
||||
Immediately = 0 << 12,
|
||||
VBlank = 1 << 12,
|
||||
HBlank = 2 << 12,
|
||||
Special = 3 << 12,
|
||||
}
|
23
src/mmio_types/interrupt_flags.rs
Normal file
23
src/mmio_types/interrupt_flags.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct InterruptFlags(pub(crate) u16);
|
||||
impl InterruptFlags {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, vblank, with_vblank, set_vblank);
|
||||
bitfield_bool!(u16; 1, hblank, with_hblank, set_hblank);
|
||||
bitfield_bool!(u16; 2, vcount, with_vcount, set_vcount);
|
||||
bitfield_bool!(u16; 3, timer0, with_timer0, set_timer0);
|
||||
bitfield_bool!(u16; 4, timer1, with_timer1, set_timer1);
|
||||
bitfield_bool!(u16; 5, timer2, with_timer2, set_timer2);
|
||||
bitfield_bool!(u16; 6, timer3, with_timer3, set_timer3);
|
||||
bitfield_bool!(u16; 7, serial, with_serial, set_serial);
|
||||
bitfield_bool!(u16; 8, dma0, with_dma0, set_dma0);
|
||||
bitfield_bool!(u16; 9, dma1, with_dma1, set_dma1);
|
||||
bitfield_bool!(u16; 10, dma2, with_dma2, set_dma2);
|
||||
bitfield_bool!(u16; 11, dma3, with_dma3, set_dma3);
|
||||
bitfield_bool!(u16; 12, keypad, with_keypad, set_keypad);
|
||||
bitfield_bool!(u16; 13, gamepak, with_gamepak, set_gamepak);
|
||||
}
|
||||
// TODO: bit ops for interrupt flags
|
21
src/mmio_types/key_interrupt_control.rs
Normal file
21
src/mmio_types/key_interrupt_control.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyInterruptControl(u16);
|
||||
impl KeyInterruptControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a, with_a, set_a);
|
||||
bitfield_bool!(u16; 1, b, with_b, set_b);
|
||||
bitfield_bool!(u16; 2, select, with_select, set_select);
|
||||
bitfield_bool!(u16; 3, start, with_start, set_start);
|
||||
bitfield_bool!(u16; 4, right, with_right, set_right);
|
||||
bitfield_bool!(u16; 5, left, with_left, set_left);
|
||||
bitfield_bool!(u16; 6, up, with_up, set_up);
|
||||
bitfield_bool!(u16; 7, down, with_down, set_down);
|
||||
bitfield_bool!(u16; 8, r, with_r, set_r);
|
||||
bitfield_bool!(u16; 9, l, with_l, set_l);
|
||||
//
|
||||
bitfield_bool!(u16; 14, enabled, with_enabled, set_enabled);
|
||||
bitfield_bool!(u16; 15, require_all, with_require_all, set_require_all);
|
||||
}
|
68
src/mmio_types/keys.rs
Normal file
68
src/mmio_types/keys.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeysLowActive(u16);
|
||||
impl KeysLowActive {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a_released, with_a_released, set_a_released);
|
||||
bitfield_bool!(u16; 1, b_released, with_b_released, set_b_released);
|
||||
bitfield_bool!(u16; 2, select_released, with_select_released, set_select_released);
|
||||
bitfield_bool!(u16; 3, start_released, with_start_released, set_start_released);
|
||||
bitfield_bool!(u16; 4, right_released, with_right_released, set_right_released);
|
||||
bitfield_bool!(u16; 5, left_released, with_left_released, set_left_released);
|
||||
bitfield_bool!(u16; 6, up_released, with_up_released, set_up_released);
|
||||
bitfield_bool!(u16; 7, down_released, with_down_released, set_down_released);
|
||||
bitfield_bool!(u16; 8, r_released, with_r_released, set_r_released);
|
||||
bitfield_bool!(u16; 9, l_released, with_l_released, set_l_released);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Keys(u16);
|
||||
impl Keys {
|
||||
const_new!();
|
||||
bitfield_bool!(u16; 0, a, with_a, set_a);
|
||||
bitfield_bool!(u16; 1, b, with_b, set_b);
|
||||
bitfield_bool!(u16; 2, select, with_select, set_select);
|
||||
bitfield_bool!(u16; 3, start, with_start, set_start);
|
||||
bitfield_bool!(u16; 4, right, with_right, set_right);
|
||||
bitfield_bool!(u16; 5, left, with_left, set_left);
|
||||
bitfield_bool!(u16; 6, up, with_up, set_up);
|
||||
bitfield_bool!(u16; 7, down, with_down, set_down);
|
||||
bitfield_bool!(u16; 8, r, with_r, set_r);
|
||||
bitfield_bool!(u16; 9, l, with_l, set_l);
|
||||
|
||||
pub const fn x_signum(self) -> i32 {
|
||||
if self.right() {
|
||||
1
|
||||
} else if self.left() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn y_signum(self) -> i32 {
|
||||
if self.down() {
|
||||
1
|
||||
} else if self.up() {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: bit ops for keys
|
||||
|
||||
impl From<KeysLowActive> for Keys {
|
||||
fn from(low_active: KeysLowActive) -> Self {
|
||||
Self(low_active.0 ^ 0b11_1111_1111)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keys> for KeysLowActive {
|
||||
fn from(keys: Keys) -> Self {
|
||||
Self(keys.0 ^ 0b11_1111_1111)
|
||||
}
|
||||
}
|
10
src/mmio_types/mosaic_size.rs
Normal file
10
src/mmio_types/mosaic_size.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct MosaicSize(u8);
|
||||
impl MosaicSize {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=3: u8, horizontal, with_horizontal, set_horizontal);
|
||||
bitfield_int!(u8; 4..=7: u8, vertical, with_vertical, set_vertical);
|
||||
}
|
13
src/mmio_types/noise_frequency_control.rs
Normal file
13
src/mmio_types/noise_frequency_control.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseFrequencyControl(u16);
|
||||
impl NoiseFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=2: u16, div_ratio, with_div_ratio, set_div_ratio);
|
||||
bitfield_bool!(u16; 3, counter_width, with_counter_width, set_counter_width);
|
||||
bitfield_int!(u16; 4..=7: u16, shift_frequency, with_shift_frequency, set_shift_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
12
src/mmio_types/noise_len_env.rs
Normal file
12
src/mmio_types/noise_len_env.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct NoiseLenEnv(u16);
|
||||
impl NoiseLenEnv {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length);
|
||||
bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step);
|
||||
bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing);
|
||||
bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume);
|
||||
}
|
16
src/mmio_types/register_ram_reset_control.rs
Normal file
16
src/mmio_types/register_ram_reset_control.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ResetFlags(pub(crate) u8);
|
||||
impl ResetFlags {
|
||||
const_new!();
|
||||
//bitfield_bool!(u8; 0, ewram, with_ewram, set_ewram);
|
||||
//bitfield_bool!(u8; 1, iwram, with_iwram, set_iwram);
|
||||
bitfield_bool!(u8; 2, palram, with_palram, set_palram);
|
||||
bitfield_bool!(u8; 3, vram, with_vram, set_vram);
|
||||
bitfield_bool!(u8; 4, oam, with_oam, set_oam);
|
||||
bitfield_bool!(u8; 5, sio, with_sio, set_sio);
|
||||
bitfield_bool!(u8; 6, sound, with_sound, set_sound);
|
||||
bitfield_bool!(u8; 7, all_other_io, with_all_other_io, set_all_other_io);
|
||||
}
|
12
src/mmio_types/timer_control.rs
Normal file
12
src/mmio_types/timer_control.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct TimerControl(u8);
|
||||
impl TimerControl {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=1: u8, prescaler_selection, with_prescaler_selection, set_prescaler_selection);
|
||||
bitfield_bool!(u8; 4, chained_counting, with_chained_counting, set_chained_counting);
|
||||
bitfield_bool!(u8; 6, irq_on_overflow, with_irq_on_overflow, set_irq_on_overflow);
|
||||
bitfield_bool!(u8; 7, enabled, with_enabled, set_enabled);
|
||||
}
|
13
src/mmio_types/tone_duty_len_env.rs
Normal file
13
src/mmio_types/tone_duty_len_env.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneDutyLenEnv(u16);
|
||||
impl ToneDutyLenEnv {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=5: u16, sound_length, with_sound_length, set_sound_length);
|
||||
bitfield_int!(u16; 6..=7: u16, wave_pattern, with_wave_pattern, set_wave_pattern);
|
||||
bitfield_int!(u16; 8..=10: u16, envelope_step, with_envelope_step, set_envelope_step);
|
||||
bitfield_bool!(u16; 11, envelope_increasing, with_envelope_increasing, set_envelope_increasing);
|
||||
bitfield_int!(u16; 12..=15: u16, volume, with_volume, set_volume);
|
||||
}
|
11
src/mmio_types/tone_frequency_control.rs
Normal file
11
src/mmio_types/tone_frequency_control.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneFrequencyControl(u16);
|
||||
impl ToneFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
11
src/mmio_types/tone_sweep.rs
Normal file
11
src/mmio_types/tone_sweep.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ToneSweep(u8);
|
||||
impl ToneSweep {
|
||||
const_new!();
|
||||
bitfield_int!(u8; 0..=2: u8, sweep_shift, with_sweep_shift, set_sweep_shift);
|
||||
bitfield_bool!(u8; 3, frequency_decreasing, with_frequency_decreasing, set_frequency_decreasing);
|
||||
bitfield_int!(u8; 4..=6: u8, sweep_time, with_sweep_time, set_sweep_time);
|
||||
}
|
11
src/mmio_types/wave_control.rs
Normal file
11
src/mmio_types/wave_control.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveControl(u8);
|
||||
impl WaveControl {
|
||||
const_new!();
|
||||
bitfield_bool!(u8; 5, two_banks, with_two_banks, set_two_banks);
|
||||
bitfield_bool!(u8; 6, use_bank1, with_use_bank1, set_use_bank1);
|
||||
bitfield_bool!(u8; 7, playing, with_playing, set_playing);
|
||||
}
|
11
src/mmio_types/wave_frequency_control.rs
Normal file
11
src/mmio_types/wave_frequency_control.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveFrequencyControl(u16);
|
||||
impl WaveFrequencyControl {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=10: u16, frequency, with_frequency, set_frequency);
|
||||
bitfield_bool!(u16; 14, auto_stop, with_auto_stop, set_auto_stop);
|
||||
bitfield_bool!(u16; 15, restart, with_restart, set_restart);
|
||||
}
|
11
src/mmio_types/wave_len_volume.rs
Normal file
11
src/mmio_types/wave_len_volume.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WaveLenVolume(u16);
|
||||
impl WaveLenVolume {
|
||||
const_new!();
|
||||
bitfield_int!(u16; 0..=7: u16, length, with_length, set_length);
|
||||
bitfield_int!(u16; 13..=14: u16, volume, with_volume, set_volume);
|
||||
bitfield_bool!(u16; 15, force75, with_force75, set_force75);
|
||||
}
|
14
src/mmio_types/window_enable.rs
Normal file
14
src/mmio_types/window_enable.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct WindowEnable(u8);
|
||||
impl WindowEnable {
|
||||
const_new!();
|
||||
bitfield_bool!(u8; 0, bg0, with_bg0, set_bg0);
|
||||
bitfield_bool!(u8; 1, bg1, with_bg1, set_bg1);
|
||||
bitfield_bool!(u8; 2, bg2, with_bg2, set_bg2);
|
||||
bitfield_bool!(u8; 3, bg3, with_bg3, set_bg3);
|
||||
bitfield_bool!(u8; 4, obj, with_obj, set_obj);
|
||||
bitfield_bool!(u8; 5, effect, with_effect, set_effect);
|
||||
}
|
196
src/oam.rs
196
src/oam.rs
|
@ -1,196 +0,0 @@
|
|||
//! Types and declarations for the Object Attribute Memory (`OAM`).
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// 0th part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-7: row-coordinate
|
||||
/// * Bits 8-9: Rendering style: Normal, Affine, Disabled, Double Area Affine
|
||||
/// * Bits 10-11: Object mode: Normal, SemiTransparent, Object Window
|
||||
/// * Bit 12: Mosaic
|
||||
/// * Bit 13: is 8bpp
|
||||
/// * Bits 14-15: Object Shape: Square, Horizontal, Vertical
|
||||
OBJAttr0, u16
|
||||
}
|
||||
impl OBJAttr0 {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
row_coordinate: 0-7,
|
||||
obj_rendering: 8-9=ObjectRender<Normal, Affine, Disabled, DoubleAreaAffine>,
|
||||
obj_mode: 10-11=ObjectMode<Normal, SemiTransparent, OBJWindow>,
|
||||
mosaic: 12,
|
||||
is_8bpp: 13,
|
||||
obj_shape: 14-15=ObjectShape<Square, Horizontal, Vertical>,
|
||||
}
|
||||
}
|
||||
|
||||
/// What style of rendering for this object
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectRender {
|
||||
/// Standard, non-affine rendering
|
||||
Normal = 0,
|
||||
/// Affine rendering
|
||||
Affine = 1,
|
||||
/// Object disabled (saves cycles for elsewhere!)
|
||||
Disabled = 2,
|
||||
/// Affine with double render space (helps prevent clipping)
|
||||
DoubleAreaAffine = 3,
|
||||
}
|
||||
|
||||
/// What mode to ues for the object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectMode {
|
||||
/// Show the object normally
|
||||
Normal = 0,
|
||||
/// The object becomes the "Alpha Blending 1st target" (see Alpha Blending)
|
||||
SemiTransparent = 1,
|
||||
/// Use the object's non-transparent pixels as part of a mask for the object
|
||||
/// window (see Windows).
|
||||
OBJWindow = 2,
|
||||
}
|
||||
|
||||
/// What shape the object's appearance should be.
|
||||
///
|
||||
/// The specifics also depend on the `ObjectSize` set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectShape {
|
||||
/// Equal parts wide and tall
|
||||
Square = 0,
|
||||
/// Wider than tall
|
||||
Horizontal = 1,
|
||||
/// Taller than wide
|
||||
Vertical = 2,
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// 1st part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-8: column coordinate
|
||||
/// * Bits 9-13:
|
||||
/// * Normal render: Bit 12 holds hflip and 13 holds vflip.
|
||||
/// * Affine render: The affine parameter selection.
|
||||
/// * Bits 14-15: Object Size
|
||||
OBJAttr1, u16
|
||||
}
|
||||
impl OBJAttr1 {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
col_coordinate: 0-8,
|
||||
affine_index: 9-13,
|
||||
hflip: 12,
|
||||
vflip: 13,
|
||||
obj_size: 14-15=ObjectSize<Zero, One, Two, Three>,
|
||||
}
|
||||
}
|
||||
|
||||
/// The object's size.
|
||||
///
|
||||
/// Also depends on the `ObjectShape` set.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ObjectSize {
|
||||
/// * Square: 8x8px
|
||||
/// * Horizontal: 16x8px
|
||||
/// * Vertical: 8x16px
|
||||
Zero = 0,
|
||||
/// * Square: 16x16px
|
||||
/// * Horizontal: 32x8px
|
||||
/// * Vertical: 8x32px
|
||||
One = 1,
|
||||
/// * Square: 32x32px
|
||||
/// * Horizontal: 32x16px
|
||||
/// * Vertical: 16x32px
|
||||
Two = 2,
|
||||
/// * Square: 64x64px
|
||||
/// * Horizontal: 64x32px
|
||||
/// * Vertical: 32x64px
|
||||
Three = 3,
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// 2nd part of an object's attributes.
|
||||
///
|
||||
/// * Bits 0-9: Base Tile Index (tile offset from CBB4)
|
||||
/// * Bits 10-11: Priority
|
||||
/// * Bits 12-15: Palbank (if using 4bpp)
|
||||
OBJAttr2, u16
|
||||
}
|
||||
impl OBJAttr2 {
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
tile_id: 0-9,
|
||||
priority: 10-11,
|
||||
palbank: 12-15,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ObjectAttributes {
|
||||
pub attr0: OBJAttr0,
|
||||
pub attr1: OBJAttr1,
|
||||
pub attr2: OBJAttr2,
|
||||
}
|
||||
|
||||
/// The object attributes, but there are gaps in the array, so we must not
|
||||
/// expose this directly.
|
||||
const OBJ_ATTR_APPROX: VolBlock<[u16; 4], Safe, Safe, 128> = unsafe { VolBlock::new(0x700_0000) };
|
||||
// TODO: VolSeries
|
||||
|
||||
pub fn write_obj_attributes(slot: usize, attributes: ObjectAttributes) -> Option<()> {
|
||||
OBJ_ATTR_APPROX.get(slot).map(|va| unsafe {
|
||||
let va_u16 = va.cast::<u16>();
|
||||
va_u16.cast::<OBJAttr0>().write(attributes.attr0);
|
||||
va_u16.offset(1).cast::<OBJAttr1>().write(attributes.attr1);
|
||||
va_u16.offset(2).cast::<OBJAttr2>().write(attributes.attr2);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_obj_attributes(slot: usize) -> Option<ObjectAttributes> {
|
||||
OBJ_ATTR_APPROX.get(slot).map(|va| unsafe {
|
||||
let va_u16 = va.cast::<u16>();
|
||||
let attr0 = va_u16.cast::<OBJAttr0>().read();
|
||||
let attr1 = va_u16.offset(1).cast::<OBJAttr1>().read();
|
||||
let attr2 = va_u16.offset(2).cast::<OBJAttr2>().read();
|
||||
ObjectAttributes { attr0, attr1, attr2 }
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AffineParameters {
|
||||
pub pa: i16,
|
||||
pub pb: i16,
|
||||
pub pc: i16,
|
||||
pub pd: i16,
|
||||
}
|
||||
// TODO: find the correct fixed-point type here.
|
||||
|
||||
/// The object attributes, but there are gaps in the array, so we must not
|
||||
/// expose this directly.
|
||||
const AFFINE_PARAMS_APPROX: VolBlock<[i16; 16], Safe, Safe, 32> =
|
||||
unsafe { VolBlock::new(0x700_0000) };
|
||||
// TODO: VolSeries
|
||||
|
||||
pub fn write_affine_parameters(slot: usize, params: AffineParameters) -> Option<()> {
|
||||
AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe {
|
||||
let va_i16 = va.cast::<i16>();
|
||||
va_i16.offset(3).write(params.pa);
|
||||
va_i16.offset(7).write(params.pb);
|
||||
va_i16.offset(11).write(params.pc);
|
||||
va_i16.offset(15).write(params.pd);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_affine_parameters(slot: usize) -> Option<AffineParameters> {
|
||||
AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe {
|
||||
let va_i16 = va.cast::<i16>();
|
||||
let pa = va_i16.offset(3).read();
|
||||
let pb = va_i16.offset(7).read();
|
||||
let pc = va_i16.offset(11).read();
|
||||
let pd = va_i16.offset(15).read();
|
||||
AffineParameters { pa, pb, pc, pd }
|
||||
})
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
//! Module that allows interacting with palette memory, (`PALRAM`).
|
||||
//!
|
||||
//! The `PALRAM` contains 256 `Color` values for Background use, and 256 `Color`
|
||||
//! values for Object use.
|
||||
//!
|
||||
//! Each block of `PALRAM` can be viewed as "8 bits per pixel" (8bpp), where
|
||||
//! there's a single palette of 256 entries. It can also be viewed as "4 bits
|
||||
//! per pixel" (4bpp), where there's 16 "palbank" entries that each have 16
|
||||
//! slots. **Both** interpretations are correct, simultaneously. If you're a
|
||||
//! real palette wizard you can carefully arrange for some things to use 4bpp
|
||||
//! mode while other things use 8bpp mode and have it all look good.
|
||||
//!
|
||||
//! ## Transparency
|
||||
//!
|
||||
//! In 8bpp mode the 0th palette index is "transparent" when used in an image
|
||||
//! (giving you 255 usable slots). In 4bpp mode the 0th palbank index _of each
|
||||
//! palbank_ is considered a transparency pixel (giving you 15 usable slots per
|
||||
//! palbank).
|
||||
//!
|
||||
//! ## Clear Color
|
||||
//!
|
||||
//! The 0th palette index of the background palette holds the color that the
|
||||
//! display will show if no background or object draws over top of a given pixel
|
||||
//! during rendering.
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: PalIndex newtypes?
|
||||
|
||||
/// The `PALRAM` for background colors, 256 slot view.
|
||||
pub const PALRAM_BG: VolBlock<Color, Safe, Safe, 256> = unsafe { VolBlock::new(0x500_0000) };
|
||||
|
||||
/// The `PALRAM` for object colors, 256 slot view.
|
||||
pub const PALRAM_OBJ: VolBlock<Color, Safe, Safe, 256> = unsafe { VolBlock::new(0x500_0200) };
|
||||
|
||||
/// Obtains the address of the specified 8bpp background palette slot.
|
||||
pub const fn index_palram_bg_8bpp(slot: u8) -> VolAddress<Color, Safe, Safe> {
|
||||
PALRAM_BG.index(slot as usize)
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 8bpp object palette slot.
|
||||
pub const fn index_palram_obj_8bpp(slot: u8) -> VolAddress<Color, Safe, Safe> {
|
||||
PALRAM_OBJ.index(slot as usize)
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 4bpp background palbank and palslot.
|
||||
///
|
||||
/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation
|
||||
/// will wrap.
|
||||
pub const fn index_palram_bg_4bpp(palbank: u8, palslot: u8) -> VolAddress<Color, Safe, Safe> {
|
||||
PALRAM_BG.index(palbank.wrapping_mul(16).wrapping_add(palslot) as usize)
|
||||
}
|
||||
|
||||
/// Obtains the address of the specified 4bpp object palbank and palslot.
|
||||
///
|
||||
/// Accesses `palbank * 16 + palslot`, if this is out of bounds the computation
|
||||
/// will wrap.
|
||||
pub const fn index_palram_obj_4bpp(palbank: u8, palslot: u8) -> VolAddress<Color, Safe, Safe> {
|
||||
PALRAM_OBJ.index(palbank.wrapping_mul(16).wrapping_add(palslot) as usize)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
//! Module for things related to ROM.
|
18
src/rsrt0.S
18
src/rsrt0.S
|
@ -1,4 +1,6 @@
|
|||
.arm
|
||||
.global __start
|
||||
|
||||
__start:
|
||||
b .Linit
|
||||
|
||||
|
@ -7,19 +9,19 @@ __start:
|
|||
|
||||
.Linit:
|
||||
@ Set address of user IRQ handler
|
||||
ldr r0, =MainIrqHandler
|
||||
ldr r1, =0x03FFFFFC
|
||||
str r0, [r1]
|
||||
@ldr r0, =MainIrqHandler
|
||||
@ldr r1, =0x03FFFFFC
|
||||
@str r0, [r1]
|
||||
|
||||
@ set IRQ stack pointer
|
||||
mov r0, #0x12
|
||||
msr CPSR_c, r0
|
||||
ldr sp, =0x3007fa0
|
||||
ldr sp, =0x03007FA0
|
||||
|
||||
@ set user stack pointer
|
||||
mov r0, #0x1f
|
||||
msr CPSR_c, r0
|
||||
ldr sp, =0x3007f00
|
||||
ldr sp, =0x03007F00
|
||||
|
||||
@ copy .data and .text_iwram section to IWRAM
|
||||
ldr r0, =__iwram_lma @ source address
|
||||
|
@ -31,12 +33,15 @@ __start:
|
|||
addne r2, #3
|
||||
asrne r2, #2
|
||||
addne r2, #0x04000000
|
||||
swine 0xb0000
|
||||
swine 0xB0000
|
||||
|
||||
@ jump to user code
|
||||
ldr r0, =main
|
||||
bx r0
|
||||
@ main should be `fn() -> !`, but it doesn't hurt to guard
|
||||
1: b 1b
|
||||
|
||||
/*
|
||||
.arm
|
||||
.global MainIrqHandler
|
||||
.align 4, 0
|
||||
|
@ -89,3 +94,4 @@ MainIrqHandler:
|
|||
@ Return to BIOS IRQ handler
|
||||
bx lr
|
||||
.pool
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use super::{Error, MediaType, RawSaveAccess};
|
||||
use crate::{
|
||||
io::dma::*,
|
||||
prelude::*,
|
||||
save::{lock_media, MediaInfo, Timeout},
|
||||
sync::with_irqs_disabled,
|
||||
};
|
||||
|
@ -21,48 +21,50 @@ fn disable_dmas(func: impl FnOnce()) {
|
|||
with_irqs_disabled(|| unsafe {
|
||||
// Disable other DMAs. This avoids our read/write from being interrupted
|
||||
// by a higher priority DMA channel.
|
||||
let dma0_ctl = DMA0::control();
|
||||
let dma1_ctl = DMA1::control();
|
||||
let dma2_ctl = DMA2::control();
|
||||
DMA0::set_control(dma0_ctl.with_enabled(false));
|
||||
DMA1::set_control(dma1_ctl.with_enabled(false));
|
||||
DMA2::set_control(dma2_ctl.with_enabled(false));
|
||||
let dma0_ctl = DMA0CNT_H.read();
|
||||
let dma1_ctl = DMA1CNT_H.read();
|
||||
let dma2_ctl = DMA2CNT_H.read();
|
||||
DMA0CNT_H.write(dma0_ctl.with_enabled(false));
|
||||
DMA1CNT_H.write(dma1_ctl.with_enabled(false));
|
||||
DMA2CNT_H.write(dma2_ctl.with_enabled(false));
|
||||
|
||||
// Executes the body of the function with DMAs and IRQs disabled.
|
||||
func();
|
||||
|
||||
// Continues higher priority DMAs if they were enabled before.
|
||||
DMA0::set_control(dma0_ctl);
|
||||
DMA1::set_control(dma1_ctl);
|
||||
DMA2::set_control(dma2_ctl);
|
||||
DMA0CNT_H.write(dma0_ctl);
|
||||
DMA1CNT_H.write(dma1_ctl);
|
||||
DMA2CNT_H.write(dma2_ctl);
|
||||
});
|
||||
}
|
||||
|
||||
/// Sends a DMA command to EEPROM.
|
||||
fn dma_send(source: &[u32], ct: u16) {
|
||||
disable_dmas(|| unsafe {
|
||||
DMA3::set_source(source.as_ptr());
|
||||
DMA3::set_dest(0x0DFFFF00 as *mut _);
|
||||
DMA3::set_count(ct);
|
||||
let dma3_ctl = DMAControlSetting::new()
|
||||
.with_dest_address_control(DMADestAddressControl::Increment)
|
||||
.with_source_address_control(DMASrcAddressControl::Increment)
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
DMA3SAD.write(source.as_ptr() as usize);
|
||||
DMA3DAD.write(0x0DFFFF00);
|
||||
DMA3CNT_L.write(ct);
|
||||
let dma3_ctl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Increment)
|
||||
.with_enabled(true);
|
||||
DMA3::set_control(dma3_ctl);
|
||||
DMA3CNT_H.write(dma3_ctl);
|
||||
});
|
||||
}
|
||||
|
||||
/// Receives a DMA packet from EEPROM.
|
||||
fn dma_receive(source: &mut [u32], ct: u16) {
|
||||
disable_dmas(|| unsafe {
|
||||
DMA3::set_source(0x0DFFFF00 as *const _);
|
||||
DMA3::set_dest(source.as_ptr() as *mut _);
|
||||
DMA3::set_count(ct);
|
||||
let dma3_ctl = DMAControlSetting::new()
|
||||
.with_dest_address_control(DMADestAddressControl::Increment)
|
||||
.with_source_address_control(DMASrcAddressControl::Increment)
|
||||
DMA3SAD.write(0x0DFFFF00);
|
||||
DMA3DAD.write(source.as_mut_ptr() as usize);
|
||||
DMA3CNT_L.write(ct);
|
||||
let dma3_ctl = DmaControl::new()
|
||||
.with_dest_addr(DestAddrControl::Increment)
|
||||
.with_src_addr(SrcAddrControl::Increment)
|
||||
.with_enabled(true);
|
||||
DMA3::set_control(dma3_ctl);
|
||||
DMA3CNT_H.write(dma3_ctl);
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -124,6 +124,7 @@ pub fn detect_chip_id() -> Result<u16, Error> {
|
|||
|
||||
/// Information relating to a particular flash chip that could be found in a
|
||||
/// Game Pak.
|
||||
#[allow(dead_code)]
|
||||
struct ChipInfo {
|
||||
/// The wait state required to read from the chip.
|
||||
read_wait: u8,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use super::Error;
|
||||
use crate::{
|
||||
io::timers::*,
|
||||
prelude::*,
|
||||
sync::{RawMutex, RawMutexGuard, Static},
|
||||
};
|
||||
use voladdress::*;
|
||||
|
@ -45,7 +45,7 @@ pub struct Timeout {
|
|||
_lock_guard: RawMutexGuard<'static>,
|
||||
active: bool,
|
||||
timer_l: VolAddress<u16, Safe, Safe>,
|
||||
timer_h: VolAddress<TimerControlSetting, Safe, Safe>,
|
||||
timer_h: VolAddress<TimerControl, Safe, Safe>,
|
||||
}
|
||||
impl Timeout {
|
||||
/// Creates a new timeout from the timer passed to [`set_timer_for_timeout`].
|
||||
|
@ -66,17 +66,17 @@ impl Timeout {
|
|||
active: id != TimerId::None,
|
||||
timer_l: match id {
|
||||
TimerId::None => unsafe { VolAddress::new(0) },
|
||||
TimerId::T0 => TM0CNT_L,
|
||||
TimerId::T1 => TM1CNT_L,
|
||||
TimerId::T2 => TM2CNT_L,
|
||||
TimerId::T3 => TM3CNT_L,
|
||||
TimerId::T0 => unsafe { VolAddress::new(TIMER0_COUNTER.as_usize()) },
|
||||
TimerId::T1 => unsafe { VolAddress::new(TIMER1_COUNTER.as_usize()) },
|
||||
TimerId::T2 => unsafe { VolAddress::new(TIMER2_COUNTER.as_usize()) },
|
||||
TimerId::T3 => unsafe { VolAddress::new(TIMER3_COUNTER.as_usize()) },
|
||||
},
|
||||
timer_h: match id {
|
||||
TimerId::None => unsafe { VolAddress::new(0) },
|
||||
TimerId::T0 => TM0CNT_H,
|
||||
TimerId::T1 => TM1CNT_H,
|
||||
TimerId::T2 => TM2CNT_H,
|
||||
TimerId::T3 => TM3CNT_H,
|
||||
TimerId::T0 => TIMER0_CONTROL,
|
||||
TimerId::T1 => TIMER1_CONTROL,
|
||||
TimerId::T2 => TIMER2_CONTROL,
|
||||
TimerId::T3 => TIMER3_CONTROL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -85,9 +85,8 @@ impl Timeout {
|
|||
pub fn start(&self) {
|
||||
if self.active {
|
||||
self.timer_l.write(0);
|
||||
let timer_ctl =
|
||||
TimerControlSetting::new().with_tick_rate(TimerTickRate::CPU1024).with_enabled(true);
|
||||
self.timer_h.write(TimerControlSetting::new());
|
||||
let timer_ctl = TimerControl::new().with_prescaler_selection(3).with_enabled(true);
|
||||
self.timer_h.write(TimerControl::new());
|
||||
self.timer_h.write(timer_ctl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! A module containing functions and utilities useful for synchronizing state.
|
||||
|
||||
use crate::io::irq::{IrqEnableSetting, IME};
|
||||
use crate::prelude::*;
|
||||
|
||||
mod locks;
|
||||
mod statics;
|
||||
|
@ -55,7 +55,7 @@ pub unsafe extern "C" fn __sync_synchronize() {}
|
|||
/// for game functionality.
|
||||
pub fn with_irqs_disabled<T>(mut func: impl FnOnce() -> T) -> T {
|
||||
let current_ime = IME.read();
|
||||
unsafe { IME.write(IrqEnableSetting::IRQ_NO) };
|
||||
unsafe { IME.write(false) };
|
||||
// prevents the contents of the function from being reordered before IME is disabled.
|
||||
memory_write_hint(&mut func);
|
||||
|
||||
|
|
66
src/vram.rs
66
src/vram.rs
|
@ -1,66 +0,0 @@
|
|||
//! Module for all things relating to the Video RAM.
|
||||
//!
|
||||
//! Note that the GBA has six different display modes available, and the
|
||||
//! _meaning_ of Video RAM depends on which display mode is active. In all
|
||||
//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Note that all possible bit patterns are technically allowed within Video
|
||||
//! Memory. If you write the "wrong" thing into video memory you don't crash the
|
||||
//! GBA, instead you just get graphical glitches (or perhaps nothing at all).
|
||||
//! Accordingly, the "safe" functions here will check that you're in bounds, but
|
||||
//! they won't bother to check that you've set the video mode they're designed
|
||||
//! for.
|
||||
|
||||
pub(crate) use super::*;
|
||||
|
||||
pub mod affine;
|
||||
pub mod bitmap;
|
||||
pub mod text;
|
||||
|
||||
use text::TextScreenblockEntry;
|
||||
|
||||
/// The start of VRAM.
|
||||
///
|
||||
/// Depending on what display mode is currently set there's different ways that
|
||||
/// your program should interpret the VRAM space. Accordingly, we give the raw
|
||||
/// value as just being a `usize`. Specific video mode types then wrap this as
|
||||
/// being the correct thing.
|
||||
pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
||||
|
||||
pub const PAGE1_OFFSET: usize = 0xA000;
|
||||
|
||||
/// The character base blocks.
|
||||
pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], Safe, Safe, 6> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
/// The screen entry base blocks.
|
||||
pub const SCREEN_BASE_BLOCKS: VolBlock<[u8; 0x800], Safe, Safe, 32> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
newtype! {
|
||||
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
Tile4bpp, pub [u32; 8], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// An 8x8 tile with 8bpp, packed as `u32` values for proper alignment.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
Tile8bpp, pub [u32; 16], no frills
|
||||
}
|
||||
|
||||
/// Gives the specified charblock in 4bpp view.
|
||||
pub fn get_4bpp_character_block(slot: usize) -> VolBlock<Tile4bpp, Safe, Safe, 512> {
|
||||
unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).as_usize()) }
|
||||
}
|
||||
|
||||
/// Gives the specified charblock in 8bpp view.
|
||||
pub fn get_8bpp_character_block(slot: usize) -> VolBlock<Tile8bpp, Safe, Safe, 256> {
|
||||
unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).as_usize()) }
|
||||
}
|
||||
|
||||
pub fn get_screen_block(slot: usize) -> VolBlock<TextScreenblockEntry, Safe, Safe, 1024> {
|
||||
unsafe { VolBlock::new(SCREEN_BASE_BLOCKS.index(slot).as_usize()) }
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//! Module for affine things.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// A screenblock entry for use in Affine mode.
|
||||
AffineScreenblockEntry, u8
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 16x16 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock16x16, [AffineScreenblockEntry; 16*16], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 32x32 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock32x32, [AffineScreenblockEntry; 32*32], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 64x64 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock64x64, [AffineScreenblockEntry; 64*64], no frills
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A 128x128 screenblock for use in Affine mode.
|
||||
#[derive(Clone, Copy)]
|
||||
AffineScreenblock128x128, [AffineScreenblockEntry; 128*128], no frills
|
||||
}
|
|
@ -1,454 +0,0 @@
|
|||
//! Module for the Bitmap video modes.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A bitmap video mode with full color and full resolution.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
///
|
||||
/// Because it takes so much space to have full color and full resolution at the
|
||||
/// same time, there's no alternate page available when using mode 3.
|
||||
///
|
||||
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||
pub struct Mode3;
|
||||
|
||||
impl Mode3 {
|
||||
/// The screen's width in this mode.
|
||||
pub const WIDTH: usize = 240;
|
||||
|
||||
/// The screen's height in this mode.
|
||||
pub const HEIGHT: usize = 160;
|
||||
|
||||
const VRAM: VolBlock<Color, Safe, Safe, { 256 * 160 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
const WORDS_BLOCK: VolBlock<u32, Safe, Safe, { (256 * 160) / 2 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
/// Gets the address of the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
fn get(col: usize, row: usize) -> Option<VolAddress<Color, Safe, Safe>> {
|
||||
Self::VRAM.get(col + row * Self::WIDTH)
|
||||
}
|
||||
|
||||
/// Reads the color of the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn read(col: usize, row: usize) -> Option<Color> {
|
||||
Self::get(col, row).map(VolAddress::<Color, Safe, Safe>::read)
|
||||
}
|
||||
|
||||
/// Writes a color to the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn write(col: usize, row: usize, color: Color) -> Option<()> {
|
||||
Self::get(col, row).map(|va| va.write(color))
|
||||
}
|
||||
|
||||
/// Clear the screen to the color specified.
|
||||
///
|
||||
/// Takes ~430,000 cycles (~1.5 frames).
|
||||
pub fn clear_to(color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
for va in Self::WORDS_BLOCK.iter() {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the screen to the color specified using DMA3.
|
||||
///
|
||||
/// Takes ~61,500 frames (~73% of VBlank)
|
||||
pub fn dma_clear_to(color: Color) {
|
||||
use crate::io::dma::DMA3;
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
unsafe {
|
||||
DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::WORDS_BLOCK.len() as u16)
|
||||
};
|
||||
}
|
||||
|
||||
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||
///
|
||||
/// Works fine with out of bounds points. It only draws to in bounds
|
||||
/// locations.
|
||||
pub fn draw_line(c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
|
||||
let mut col = c1;
|
||||
let mut row = r1;
|
||||
let w = c2 - c1;
|
||||
let h = r2 - r1;
|
||||
let mut dx1 = 0;
|
||||
let mut dx2 = 0;
|
||||
let mut dy1 = 0;
|
||||
let mut dy2 = 0;
|
||||
let mut longest = w.abs();
|
||||
let mut shortest = h.abs();
|
||||
if w < 0 {
|
||||
dx1 = -1;
|
||||
} else if w > 0 {
|
||||
dx1 = 1;
|
||||
};
|
||||
if h < 0 {
|
||||
dy1 = -1;
|
||||
} else if h > 0 {
|
||||
dy1 = 1;
|
||||
};
|
||||
if w < 0 {
|
||||
dx2 = -1;
|
||||
} else if w > 0 {
|
||||
dx2 = 1;
|
||||
};
|
||||
if !(longest > shortest) {
|
||||
core::mem::swap(&mut longest, &mut shortest);
|
||||
if h < 0 {
|
||||
dy2 = -1;
|
||||
} else if h > 0 {
|
||||
dy2 = 1
|
||||
};
|
||||
dx2 = 0;
|
||||
}
|
||||
let mut numerator = longest >> 1;
|
||||
|
||||
(0..(longest + 1)).for_each(|_| {
|
||||
Self::write(col as usize, row as usize, color);
|
||||
numerator += shortest;
|
||||
if !(numerator < longest) {
|
||||
numerator -= longest;
|
||||
col += dx1;
|
||||
row += dy1;
|
||||
} else {
|
||||
col += dx2;
|
||||
row += dy2;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to select what page to read from or write to in Mode 4 and Mode 5.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Page {
|
||||
/// Page 0
|
||||
Zero,
|
||||
/// Page 1
|
||||
One,
|
||||
}
|
||||
|
||||
/// A bitmap video mode with full resolution and paletted color.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
/// * **Pages:** 2
|
||||
///
|
||||
/// Because the pixels use palette indexes there's enough space to have two
|
||||
/// pages.
|
||||
///
|
||||
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||
pub struct Mode4;
|
||||
|
||||
impl Mode4 {
|
||||
/// The screen's width in this mode.
|
||||
pub const WIDTH: usize = 240;
|
||||
|
||||
/// The screen's height in this mode.
|
||||
pub const HEIGHT: usize = 160;
|
||||
|
||||
const PAGE0_INDEXES: VolBlock<u8, Safe, Safe, { 256 * 160 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
const PAGE1_INDEXES: VolBlock<u8, Safe, Safe, { 256 * 160 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||
|
||||
const PAGE0_WORDS: VolBlock<u32, Safe, Safe, { (256 * 160) / 4 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
const PAGE1_WORDS: VolBlock<u32, Safe, Safe, { (256 * 160) / 4 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||
|
||||
/// Reads the color of the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn read(page: Page, col: usize, row: usize) -> Option<u8> {
|
||||
match page {
|
||||
Page::Zero => Self::PAGE0_INDEXES,
|
||||
Page::One => Self::PAGE1_INDEXES,
|
||||
}
|
||||
.get(col + row * Self::WIDTH)
|
||||
.map(VolAddress::<u8, Safe, Safe>::read)
|
||||
}
|
||||
|
||||
/// Writes a color to the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn write(page: Page, col: usize, row: usize, pal8bpp: u8) -> Option<()> {
|
||||
// Note(Lokathor): Byte writes to VRAM aren't permitted, we have to jump
|
||||
// through some hoops.
|
||||
if col < Self::WIDTH && row < Self::HEIGHT {
|
||||
let real_index = col + row * Self::WIDTH;
|
||||
let rounded_down_index = real_index & !1;
|
||||
let address: VolAddress<u16, Safe, Safe> = unsafe {
|
||||
match page {
|
||||
Page::Zero => Self::PAGE0_INDEXES,
|
||||
Page::One => Self::PAGE1_INDEXES,
|
||||
}
|
||||
.index(rounded_down_index)
|
||||
.cast::<u16>()
|
||||
};
|
||||
if real_index != rounded_down_index {
|
||||
// even byte, change the high bits
|
||||
let old_val = address.read();
|
||||
address.write((old_val & 0xFF) | ((pal8bpp as u16) << 8));
|
||||
} else {
|
||||
// odd byte, change the low bits
|
||||
let old_val = address.read();
|
||||
address.write((old_val & 0xFF00) | pal8bpp as u16);
|
||||
}
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the screen to the palette index specified.
|
||||
///
|
||||
/// Takes ~215,000 cycles (~76% of a frame)
|
||||
pub fn clear_to(page: Page, pal8bpp: u8) {
|
||||
let pal8bpp_32 = pal8bpp as u32;
|
||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||
let words = match page {
|
||||
Page::Zero => Self::PAGE0_WORDS,
|
||||
Page::One => Self::PAGE1_WORDS,
|
||||
};
|
||||
for va in words.iter() {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the screen to the palette index specified using DMA3.
|
||||
///
|
||||
/// Takes ~30,800 frames (~37% of VBlank)
|
||||
pub fn dma_clear_to(page: Page, pal8bpp: u8) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let pal8bpp_32 = pal8bpp as u32;
|
||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||
let words_address = match page {
|
||||
Page::Zero => Self::PAGE0_WORDS.index(0).as_usize(),
|
||||
Page::One => Self::PAGE1_WORDS.index(0).as_usize(),
|
||||
};
|
||||
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
|
||||
}
|
||||
|
||||
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||
///
|
||||
/// Works fine with out of bounds points. It only draws to in bounds
|
||||
/// locations.
|
||||
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, pal8bpp: u8) {
|
||||
let mut col = c1;
|
||||
let mut row = r1;
|
||||
let w = c2 - c1;
|
||||
let h = r2 - r1;
|
||||
let mut dx1 = 0;
|
||||
let mut dx2 = 0;
|
||||
let mut dy1 = 0;
|
||||
let mut dy2 = 0;
|
||||
let mut longest = w.abs();
|
||||
let mut shortest = h.abs();
|
||||
if w < 0 {
|
||||
dx1 = -1;
|
||||
} else if w > 0 {
|
||||
dx1 = 1;
|
||||
};
|
||||
if h < 0 {
|
||||
dy1 = -1;
|
||||
} else if h > 0 {
|
||||
dy1 = 1;
|
||||
};
|
||||
if w < 0 {
|
||||
dx2 = -1;
|
||||
} else if w > 0 {
|
||||
dx2 = 1;
|
||||
};
|
||||
if !(longest > shortest) {
|
||||
core::mem::swap(&mut longest, &mut shortest);
|
||||
if h < 0 {
|
||||
dy2 = -1;
|
||||
} else if h > 0 {
|
||||
dy2 = 1
|
||||
};
|
||||
dx2 = 0;
|
||||
}
|
||||
let mut numerator = longest >> 1;
|
||||
|
||||
(0..(longest + 1)).for_each(|_| {
|
||||
Self::write(page, col as usize, row as usize, pal8bpp);
|
||||
numerator += shortest;
|
||||
if !(numerator < longest) {
|
||||
numerator -= longest;
|
||||
col += dx1;
|
||||
row += dy1;
|
||||
} else {
|
||||
col += dx2;
|
||||
row += dy2;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode 5 is a bitmap mode with full color and reduced resolution.
|
||||
///
|
||||
/// * **Width:** 160
|
||||
/// * **Height:** 128
|
||||
/// * **Pages:** 2
|
||||
///
|
||||
/// Because of the reduced resolutions there's enough space to have two pages.
|
||||
///
|
||||
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||
pub struct Mode5;
|
||||
|
||||
impl Mode5 {
|
||||
/// The screen's width in this mode.
|
||||
pub const WIDTH: usize = 160;
|
||||
|
||||
/// The screen's height in this mode.
|
||||
pub const HEIGHT: usize = 128;
|
||||
|
||||
const PAGE0_PIXELS: VolBlock<Color, Safe, Safe, { 160 * 128 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
const PAGE1_PIXELS: VolBlock<Color, Safe, Safe, { 160 * 128 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||
|
||||
const PAGE0_WORDS: VolBlock<u32, Safe, Safe, { (160 * 128) / 2 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||
|
||||
const PAGE1_WORDS: VolBlock<u32, Safe, Safe, { (160 * 128) / 2 }> =
|
||||
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||
|
||||
/// Reads the color of the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn read(page: Page, col: usize, row: usize) -> Option<Color> {
|
||||
match page {
|
||||
Page::Zero => Self::PAGE0_PIXELS,
|
||||
Page::One => Self::PAGE1_PIXELS,
|
||||
}
|
||||
.get(col + row * Self::WIDTH)
|
||||
.map(VolAddress::<Color, Safe, Safe>::read)
|
||||
}
|
||||
|
||||
/// Writes a color to the pixel specified.
|
||||
///
|
||||
/// ## Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds
|
||||
pub fn write(page: Page, col: usize, row: usize, color: Color) -> Option<()> {
|
||||
match page {
|
||||
Page::Zero => Self::PAGE0_PIXELS,
|
||||
Page::One => Self::PAGE1_PIXELS,
|
||||
}
|
||||
.get(col + row * Self::WIDTH)
|
||||
.map(|va| va.write(color))
|
||||
}
|
||||
|
||||
/// Clear the screen to the color specified.
|
||||
///
|
||||
/// Takes ~215,000 cycles (~76% of a frame)
|
||||
pub fn clear_to(page: Page, color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
let words = match page {
|
||||
Page::Zero => Self::PAGE0_WORDS,
|
||||
Page::One => Self::PAGE1_WORDS,
|
||||
};
|
||||
for va in words.iter() {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the screen to the color specified using DMA3.
|
||||
///
|
||||
/// Takes ~30,800 frames (~37% of VBlank)
|
||||
pub fn dma_clear_to(page: Page, color: Color) {
|
||||
use crate::io::dma::DMA3;
|
||||
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
let words_address = match page {
|
||||
Page::Zero => Self::PAGE0_WORDS.index(0).as_usize(),
|
||||
Page::One => Self::PAGE1_WORDS.index(0).as_usize(),
|
||||
};
|
||||
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
|
||||
}
|
||||
|
||||
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||
///
|
||||
/// Works fine with out of bounds points. It only draws to in bounds
|
||||
/// locations.
|
||||
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
|
||||
let mut col = c1;
|
||||
let mut row = r1;
|
||||
let w = c2 - c1;
|
||||
let h = r2 - r1;
|
||||
let mut dx1 = 0;
|
||||
let mut dx2 = 0;
|
||||
let mut dy1 = 0;
|
||||
let mut dy2 = 0;
|
||||
let mut longest = w.abs();
|
||||
let mut shortest = h.abs();
|
||||
if w < 0 {
|
||||
dx1 = -1;
|
||||
} else if w > 0 {
|
||||
dx1 = 1;
|
||||
};
|
||||
if h < 0 {
|
||||
dy1 = -1;
|
||||
} else if h > 0 {
|
||||
dy1 = 1;
|
||||
};
|
||||
if w < 0 {
|
||||
dx2 = -1;
|
||||
} else if w > 0 {
|
||||
dx2 = 1;
|
||||
};
|
||||
if !(longest > shortest) {
|
||||
core::mem::swap(&mut longest, &mut shortest);
|
||||
if h < 0 {
|
||||
dy2 = -1;
|
||||
} else if h > 0 {
|
||||
dy2 = 1
|
||||
};
|
||||
dx2 = 0;
|
||||
}
|
||||
let mut numerator = longest >> 1;
|
||||
|
||||
(0..(longest + 1)).for_each(|_| {
|
||||
Self::write(page, col as usize, row as usize, color);
|
||||
numerator += shortest;
|
||||
if !(numerator < longest) {
|
||||
numerator -= longest;
|
||||
col += dx1;
|
||||
row += dy1;
|
||||
} else {
|
||||
col += dx2;
|
||||
row += dy2;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
//! Module for tiled mode types and operations.
|
||||
|
||||
use super::*;
|
||||
|
||||
newtype! {
|
||||
/// A screenblock entry for use in Text mode.
|
||||
TextScreenblockEntry, u16
|
||||
}
|
||||
impl TextScreenblockEntry {
|
||||
/// Generates a default entry with the specified tile index.
|
||||
pub const fn from_tile_id(id: u16) -> Self {
|
||||
Self::new().with_tile_id(id)
|
||||
}
|
||||
|
||||
phantom_fields! {
|
||||
self.0: u16,
|
||||
tile_id: 0-9,
|
||||
hflip: 10,
|
||||
vflip: 11,
|
||||
palbank: 12-15,
|
||||
}
|
||||
}
|
||||
|
||||
newtype! {
|
||||
/// A screenblock for use in Text mode.
|
||||
#[derive(Clone, Copy)]
|
||||
TextScreenblock, [TextScreenblockEntry; 32 * 32], no frills
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_text_screen_block_size() {
|
||||
assert_eq!(core::mem::size_of::<TextScreenblock>(), 0x800);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
@echo off
|
||||
|
||||
echo -------
|
||||
echo -------
|
||||
|
||||
set Wildcard=*.rs
|
||||
|
||||
echo TODOS FOUND:
|
||||
findstr -s -n -i -l "TODO" %Wildcard%
|
||||
|
||||
echo -------
|
||||
echo -------
|
Loading…
Reference in a new issue