mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
cleanup, hello world runs 100% safe now
This commit is contained in:
parent
b927a348bd
commit
35ed03cb44
|
@ -13,7 +13,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
typenum = "1.10"
|
||||
gba-proc-macro = "0.2.1"
|
||||
gba-proc-macro = "0.3"
|
||||
|
||||
#[dev-dependencies]
|
||||
#quickcheck="0.7"
|
||||
|
|
23
examples/hello_world.rs
Normal file
23
examples/hello_world.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
#![no_std]
|
||||
#![feature(start)]
|
||||
|
||||
use gba::{
|
||||
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||
video::Mode3,
|
||||
Color,
|
||||
};
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_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));
|
||||
loop {}
|
||||
}
|
|
@ -10,4 +10,5 @@ use super::*;
|
|||
|
||||
use gba_proc_macro::register_bit;
|
||||
|
||||
pub mod display;
|
||||
pub mod keypad;
|
||||
|
|
110
src/io/display.rs
Normal file
110
src/io/display.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
//! Contains types and definitions for display related IO registers.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// LCD Control. Read/Write.
|
||||
///
|
||||
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
||||
|
||||
newtype!(
|
||||
/// A newtype over the various display control options that you have on a GBA.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
DisplayControlSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl DisplayControlSetting {
|
||||
pub const BG_MODE_MASK: u16 = 0b111;
|
||||
|
||||
pub fn mode(self) -> DisplayControlMode {
|
||||
// TODO: constify
|
||||
match self.0 & Self::BG_MODE_MASK {
|
||||
0 => DisplayControlMode::Tiled0,
|
||||
1 => DisplayControlMode::Tiled1,
|
||||
2 => DisplayControlMode::Tiled2,
|
||||
3 => DisplayControlMode::Bitmap3,
|
||||
4 => DisplayControlMode::Bitmap4,
|
||||
5 => DisplayControlMode::Bitmap5,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub const fn with_mode(self, new_mode: DisplayControlMode) -> Self {
|
||||
Self((self.0 & !Self::BG_MODE_MASK) | (new_mode as u16))
|
||||
}
|
||||
|
||||
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode);
|
||||
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled);
|
||||
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free);
|
||||
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d);
|
||||
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank);
|
||||
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0);
|
||||
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1);
|
||||
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2);
|
||||
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3);
|
||||
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object);
|
||||
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0);
|
||||
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1);
|
||||
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window);
|
||||
}
|
||||
|
||||
/// The six display modes available on the GBA.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum DisplayControlMode {
|
||||
/// This basically allows for the most different things at once (all layers,
|
||||
/// 1024 tiles, two palette modes, etc), but you can't do affine
|
||||
/// transformations.
|
||||
Tiled0 = 0,
|
||||
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
|
||||
/// and BG2 runs as if in `Tiled2`.
|
||||
Tiled1 = 1,
|
||||
/// This allows affine transformations, but only uses BG2 and BG3.
|
||||
Tiled2 = 2,
|
||||
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
|
||||
/// Uses BG2 only.
|
||||
Bitmap3 = 3,
|
||||
/// This uses _paletted color_ so that there's enough space to have two pages
|
||||
/// at _full resolution_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap4 = 4,
|
||||
/// This uses _reduced resolution_ so that there's enough space to have two
|
||||
/// pages with _full color_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap5 = 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()
|
||||
}
|
||||
|
||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||
pub const VBLANK_SCANLINE: u16 = 160;
|
||||
|
||||
/// Vertical Counter (LY).
|
||||
///
|
||||
/// 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> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
||||
|
||||
/// Obtains the current `VCOUNT` value.
|
||||
pub fn vcount() -> u16 {
|
||||
VCOUNT.read()
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
pub fn spin_until_vblank() {
|
||||
// TODO: make this the better version with BIOS and interrupts and such.
|
||||
while vcount() < VBLANK_SCANLINE {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
pub fn spin_until_vdraw() {
|
||||
// TODO: make this the better version with BIOS and interrupts and such.
|
||||
while vcount() >= VBLANK_SCANLINE {}
|
||||
}
|
130
src/lib.rs
130
src/lib.rs
|
@ -62,7 +62,27 @@ pub(crate) use self::base::*;
|
|||
pub mod bios;
|
||||
pub mod io;
|
||||
|
||||
pub mod video_ram;
|
||||
pub mod video;
|
||||
|
||||
newtype! {
|
||||
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
Color, 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
|
||||
//
|
||||
|
||||
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||
|
@ -220,111 +240,3 @@ mod tests {
|
|||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use gba_proc_macro::register_bit;
|
||||
|
||||
/// LCD Control. Read/Write.
|
||||
///
|
||||
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
||||
|
||||
newtype!(
|
||||
/// A newtype over the various display control options that you have on a GBA.
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
DisplayControlSetting,
|
||||
u16
|
||||
);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
impl DisplayControlSetting {
|
||||
pub const BG_MODE_MASK: u16 = 0b111;
|
||||
|
||||
pub fn mode(self) -> DisplayControlMode {
|
||||
match self.0 & Self::BG_MODE_MASK {
|
||||
0 => DisplayControlMode::Tiled0,
|
||||
1 => DisplayControlMode::Tiled1,
|
||||
2 => DisplayControlMode::Tiled2,
|
||||
3 => DisplayControlMode::Bitmap3,
|
||||
4 => DisplayControlMode::Bitmap4,
|
||||
5 => DisplayControlMode::Bitmap5,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub fn set_mode(&mut self, new_mode: DisplayControlMode) {
|
||||
self.0 &= !Self::BG_MODE_MASK;
|
||||
self.0 |= match new_mode {
|
||||
DisplayControlMode::Tiled0 => 0,
|
||||
DisplayControlMode::Tiled1 => 1,
|
||||
DisplayControlMode::Tiled2 => 2,
|
||||
DisplayControlMode::Bitmap3 => 3,
|
||||
DisplayControlMode::Bitmap4 => 4,
|
||||
DisplayControlMode::Bitmap5 => 5,
|
||||
};
|
||||
}
|
||||
|
||||
register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode);
|
||||
register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled);
|
||||
register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free);
|
||||
register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d);
|
||||
register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank);
|
||||
register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0);
|
||||
register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1);
|
||||
register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2);
|
||||
register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3);
|
||||
register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object);
|
||||
register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0);
|
||||
register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1);
|
||||
register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window);
|
||||
}
|
||||
|
||||
/// The six display modes available on the GBA.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DisplayControlMode {
|
||||
/// This basically allows for the most different things at once (all layers,
|
||||
/// 1024 tiles, two palette modes, etc), but you can't do affine
|
||||
/// transformations.
|
||||
Tiled0,
|
||||
/// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`,
|
||||
/// and BG2 runs as if in `Tiled2`.
|
||||
Tiled1,
|
||||
/// This allows affine transformations, but only uses BG2 and BG3.
|
||||
Tiled2,
|
||||
/// This is the basic bitmap draw mode. The whole screen is a single bitmap.
|
||||
/// Uses BG2 only.
|
||||
Bitmap3,
|
||||
/// This uses _paletted color_ so that there's enough space to have two pages
|
||||
/// at _full resolution_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap4,
|
||||
/// This uses _reduced resolution_ so that there's enough space to have two
|
||||
/// pages with _full color_, allowing page flipping. Uses BG2 only.
|
||||
Bitmap5,
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Vertical Counter (LY)
|
||||
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
||||
|
||||
/// Obtains the current VCount value.
|
||||
pub fn vcount() -> u16 {
|
||||
VCOUNT.read()
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
pub fn wait_until_vblank() {
|
||||
// TODO: make this the better version with BIOS and interrupts and such.
|
||||
while vcount() < crate::video_ram::SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
pub fn wait_until_vdraw() {
|
||||
// TODO: make this the better version with BIOS and interrupts and such.
|
||||
while vcount() >= crate::video_ram::SCREEN_HEIGHT as u16 {}
|
||||
}
|
||||
|
|
83
src/video.rs
Normal file
83
src/video.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
//! 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 use super::*;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Mode 3 is a bitmap mode with full color and full resolution.
|
||||
///
|
||||
/// * **Width:** 240
|
||||
/// * **Height:** 160
|
||||
///
|
||||
/// Because the memory requirements are so large, there's only a single page
|
||||
/// available instead of two pages like the other video modes have.
|
||||
///
|
||||
/// As with all bitmap modes, the bitmap itself utilizes BG2 for display, so you
|
||||
/// must have that BG enabled in addition to being within Mode 3.
|
||||
pub struct Mode3;
|
||||
impl Mode3 {
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: usize = 240;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: usize = 160;
|
||||
|
||||
/// The Mode 3 VRAM.
|
||||
///
|
||||
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
||||
/// or use the helpers provided in this module.
|
||||
pub const VRAM: VolAddressBlock<Color> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) };
|
||||
|
||||
/// private iterator over the pixels, two at a time
|
||||
const BULK_ITER: VolAddressIter<u32> =
|
||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2).iter() };
|
||||
|
||||
/// Reads the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(col: usize, row: usize) -> Option<Color> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
||||
}
|
||||
|
||||
/// Writes the pixel at the given (col,row).
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
||||
}
|
||||
|
||||
/// Clears the whole screen to the desired color.
|
||||
pub fn clear_to(color: Color) {
|
||||
let color32 = color.0 as u32;
|
||||
let bulk_color = color32 << 16 | color32;
|
||||
for va in Self::BULK_ITER {
|
||||
va.write(bulk_color)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: dma_clear_to?
|
||||
}
|
|
@ -1,83 +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 use super::*;
|
||||
|
||||
// TODO: kill all this too
|
||||
|
||||
/// The physical width in pixels of the GBA screen.
|
||||
pub const SCREEN_WIDTH: isize = 240;
|
||||
|
||||
/// The physical height in pixels of the GBA screen.
|
||||
pub const SCREEN_HEIGHT: isize = 160;
|
||||
|
||||
/// 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`.
|
||||
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
|
||||
|
||||
const MODE3_VRAM: VolAddress<u16> = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) };
|
||||
|
||||
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `col` or `row` are out of bounds this will panic.
|
||||
pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||
assert!(col >= 0 && col < SCREEN_WIDTH);
|
||||
assert!(row >= 0 && row < SCREEN_HEIGHT);
|
||||
unsafe { mode3_draw_pixel_unchecked(col, row, color) }
|
||||
}
|
||||
|
||||
/// Draws a pixel to the screen while in Display Mode 3.
|
||||
///
|
||||
/// Coordinates are relative to the top left corner.
|
||||
///
|
||||
/// If you're in another mode you'll get something weird drawn, but it's memory
|
||||
/// safe in the rust sense as long as you stay in bounds.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// * `col` must be in `0..SCREEN_WIDTH`
|
||||
/// * `row` must be in `0..SCREEN_HEIGHT`
|
||||
pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) {
|
||||
MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color);
|
||||
}
|
||||
|
||||
/// Reads the given pixel of video memory according to Mode 3 placement.
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
/// If the location is out of bounds you get `None`.
|
||||
pub fn mode3_read_pixel(col: isize, row: isize) -> Option<u16> {
|
||||
if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT {
|
||||
unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the entire screen to the color specified.
|
||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
||||
// TODO: use DMA?
|
||||
let color = color as u32;
|
||||
let bulk_color = color << 16 | color;
|
||||
let block: VolAddressBlock<u32> = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::<u32>(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize);
|
||||
for b in block.iter() {
|
||||
b.write(bulk_color);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue