cleanup, hello world runs 100% safe now

This commit is contained in:
Lokathor 2018-12-23 14:45:38 -07:00
parent b927a348bd
commit 35ed03cb44
7 changed files with 239 additions and 193 deletions

View file

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

View file

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

View file

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

View file

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