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:
Lokathor 2021-04-08 23:57:30 -06:00 committed by GitHub
parent 8efef6ebc5
commit fd3a308e8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1899 additions and 3900 deletions

View file

@ -1,3 +1,9 @@
[build]
target = "thumbv4t-none-eabi"
[unstable]
build-std = ["core"]
[target.thumbv4t-none-eabi]
rustflags = ["-Clink-arg=-Tlinker.ld"]
runner = "mgba"

View file

@ -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

View file

@ -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 = []

View file

@ -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

View file

@ -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;
}
}

View file

@ -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!");
}

View file

@ -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() {}

View file

@ -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

View file

@ -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)) };
}

View file

@ -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
View 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
View 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,
];

View file

@ -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)]

View file

@ -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::*;

File diff suppressed because it is too large Load diff

View file

@ -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 {}
}

View file

@ -1 +0,0 @@
//! Module for External Work RAM (`EWRAM`).

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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) };

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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)
);
}
}

View file

@ -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;

View file

@ -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,
}
}

View file

@ -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
}
}

View file

@ -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,
}

View file

@ -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,
}
}

View file

@ -1 +0,0 @@
//! Module for Internal Work RAM (`IWRAM`).

View file

@ -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)
}
}
}
}
*/

View file

@ -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
View 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)
};

View 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
View 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::*;

View 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);
}

View 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
View 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)
}
}

View 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,
}
*/

View 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);
}

View 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,
}

View 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

View 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
View 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)
}
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View file

@ -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 }
})
}

View file

@ -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)
}

View file

@ -1 +0,0 @@
//! Module for things related to ROM.

View file

@ -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
*/

View file

@ -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);
});
}

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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()) }
}

View file

@ -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
}

View file

@ -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;
}
});
}
}

View file

@ -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);
}

View file

@ -1,12 +0,0 @@
@echo off
echo -------
echo -------
set Wildcard=*.rs
echo TODOS FOUND:
findstr -s -n -i -l "TODO" %Wildcard%
echo -------
echo -------