From b927a348bd84b0d856496dd9d948164fb561cfc4 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 22 Dec 2018 00:26:52 -0700 Subject: [PATCH] sensible goals --- .../src/00-introduction/02-goals_and_style.md | 29 +-- book/src/01-quirks/02-fixed_only.md | 4 +- book/src/02-concepts/09-sram.md | 5 + book/src/04-non-video/01-buttons.md | 79 +++++- book/src/05-examples/01-hello_magic.md | 1 - book/src/05-examples/02-hello_world.md | 1 - book/src/05-examples/03-light_cycle.md | 1 - book/src/05-examples/04-bg_demo.md | 1 - book/src/SUMMARY.md | 4 - examples/hello_world.rs | 243 ------------------ src/base/builtins.rs | 14 +- 11 files changed, 107 insertions(+), 275 deletions(-) delete mode 100644 book/src/05-examples/01-hello_magic.md delete mode 100644 book/src/05-examples/02-hello_world.md delete mode 100644 book/src/05-examples/03-light_cycle.md delete mode 100644 book/src/05-examples/04-bg_demo.md delete mode 100644 examples/hello_world.rs diff --git a/book/src/00-introduction/02-goals_and_style.md b/book/src/00-introduction/02-goals_and_style.md index 98fd86b..eac5366 100644 --- a/book/src/00-introduction/02-goals_and_style.md +++ b/book/src/00-introduction/02-goals_and_style.md @@ -2,26 +2,17 @@ So, what's this book actually gonna teach you? -I'm _not_ gonna tell you how to use a crate that already exists. +My goal is certainly not just showing off the crate. Programming for the GBA is +weird enough that I'm trying to teach you all the rest of the stuff you need to +know along the way. If I do my job right then you'd be able to write your own +crate for GBA stuff just how you think it should all go by the end. -Don't get me wrong, there _is_ a [gba](https://crates.io/crates/gba) crate, and -it's on crates.io and all that jazz. - -However, unlike most crates that come with a tutorial book, I don't want to just -teach you how to use the crate. What I want is to teach you what you need to -know so that you could build the crate yourself, from scratch, if it didn't -already exist for you. Let's call it the [Handmade -Hero](https://handmadehero.org/) school of design. Much more than you might find -in other Rust crate books, I'll be attempting to show a lot of the _why_ in -addition to just the _how_. Once you know how to do it all on your own, you can -decide for yourself if the `gba` crate does it well, or if you think you can -come up with something that suits your needs better. - -Overall the book is sorted for easy review once you're trying to program -something, and the GBA has a few interconnected concepts, so some parts of the -book end up having to refer you to portions that you haven't read yet. The -chapters and sections are sorted so that _minimal_ future references are -required, but it's unavoidable that it'll happen sometimes. +Overall the book is sorted more for easy review once you're trying to program +something. The GBA has a few things that can stand on their own and many other +things are a mass of interconnected concepts, so some parts of the book end up +having to refer you to portions that you haven't read yet. The chapters and +sections are sorted so that _minimal_ future references are required, but it's +unavoidable that it'll happen sometimes. The actual "tutorial order" of the book is the [Examples](../05-examples/00-index.md) chapter. Each section of that chapter diff --git a/book/src/01-quirks/02-fixed_only.md b/book/src/01-quirks/02-fixed_only.md index bb0e401..e6ddf0f 100644 --- a/book/src/01-quirks/02-fixed_only.md +++ b/book/src/01-quirks/02-fixed_only.md @@ -402,9 +402,9 @@ silly but it's so much faster than doing an actual division). Also, again signed values can be annoying, because if the value _just happens_ to be `i32::MIN` then when you negate it you'll have... _still_ a negative value. I'm not 100% on this, but I think the correct thing to do at that point -is to give `$t::MIN` as out output num value. +is to give `$t::MIN` as the output num value. -Did you get all that? Good, because this is involves casting, we will need to +Did you get all that? Good, because this involves casting, so we will need to implement it three times, which calls for another macro. ```rust diff --git a/book/src/02-concepts/09-sram.md b/book/src/02-concepts/09-sram.md index 65ec4d2..fbb6202 100644 --- a/book/src/02-concepts/09-sram.md +++ b/book/src/02-concepts/09-sram.md @@ -14,3 +14,8 @@ for full details of how the `WAITCNT` register works. The game pak SRAM also has only an 8-bit bus, so have fun with that. The GBA Direct Memory Access (DMA) unit cannot access SRAM. + +Also, you [should not write to SRAM with code executing from +ROM](https://problemkaputt.de/gbatek.htm#gbacartbackupsramfram). Instead, you +should move the code to WRAM and execute the save code from there. We'll cover +how to handle that eventually. diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index d78abf5..2aa065c 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -3,4 +3,81 @@ It's all well and good to just show a picture, even to show an animation, but if we want a game we have to let the user interact with something. -TODO +## Key Input Register + +* KEYINPUT, `0x400_0130`, `u16`, read only + +This little `u16` stores the status of _all_ the buttons on the GBA, all at +once. There's only 10 of them, and we have 16 bits to work with, so that sounds +easy. However, there's a bit of a catch. The register follows a "low-active" +convention, where pressing a button _clears_ that bit until it's released. + +```rust +const NO_BUTTONS_PRESSED: u16 = 0b0000_0011_1111_1111; +``` + +The buttons are, going up in order from the 0th bit: + +* A +* B +* Select +* Start +* Right +* Left +* Up +* Down +* R +* L + +Bits above that are not used. However, since the arrow left and right can never +be pressed at the same time, and the arrows up and down can never be pressed at +the same time, this register will never read as zero. + +When programming, we usually are thinking of what buttons we want to have _be +pressed_ instead of buttons we want to have _not be pressed_. This means that we +need an inversion to happen somewhere along the line. The easiest moment of +inversion is immediately as you read in from the register and wrap the value up +in a newtype. + +```rust +pub fn read_key_input() -> KeyInput { + KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) +} +``` + +Now the KeyInput you get can be checked for what buttons are pressed by checking +for a set bit like you'd do anywhere else. + +```rust +impl KeyInput { + pub fn a_pressed(self) -> bool { + (self.0 & A_BIT) > 0 + } +} +``` + +Note that the current `KEYINPUT` value changes in real time as the user presses +or releases the buttons. To account for this, it's best to read the value just +once per game frame and then use that single value as if it was the input across +the whole frame. If you've worked with polling input before that should sound +totally normal, but if not just always remember to gather the input once per +frame and then use that value across the whole frame. + +## Key Interrupt Control + +* KEYCNT, `0x400_0132`, `u16`, read/write + +This lets you control what keys will trigger a keypad interrupt. Of course, for +the actual interrupt to fire you also need to set the `IME` and IE` registers +properly. See the [Interrupts](05-interrupts.md) section for details there. + +The main thing to know about this register is that the keys are in _the exact +same order_ as the key input order. However, with this register they use a +high-active convention instead (eg: the bit is active when the button should be +pressed as part of the interrupt). + +In addition to simply having the bits for the buttons, bit 14 is a flag for +enabling keypad interrupts (in addition to the flag in the `IE` register), and +bit 15 decides how having more than one button works. If bit 15 is disabled, +it's an OR combination (eg: "press any key to continue"). If bit 15 is enabled +it's an AND combination (eg: "press A+B+Start+Select to reset"). diff --git a/book/src/05-examples/01-hello_magic.md b/book/src/05-examples/01-hello_magic.md deleted file mode 100644 index ba1a038..0000000 --- a/book/src/05-examples/01-hello_magic.md +++ /dev/null @@ -1 +0,0 @@ -# hello_magic diff --git a/book/src/05-examples/02-hello_world.md b/book/src/05-examples/02-hello_world.md deleted file mode 100644 index 115e729..0000000 --- a/book/src/05-examples/02-hello_world.md +++ /dev/null @@ -1 +0,0 @@ -# hello_world diff --git a/book/src/05-examples/03-light_cycle.md b/book/src/05-examples/03-light_cycle.md deleted file mode 100644 index f72194e..0000000 --- a/book/src/05-examples/03-light_cycle.md +++ /dev/null @@ -1 +0,0 @@ -# light_cycle diff --git a/book/src/05-examples/04-bg_demo.md b/book/src/05-examples/04-bg_demo.md deleted file mode 100644 index 8cc8680..0000000 --- a/book/src/05-examples/04-bg_demo.md +++ /dev/null @@ -1 +0,0 @@ -# bg_demo diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 704ba7a..a697feb 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -35,7 +35,3 @@ * [Link Cable](04-non-video/06-link_cable.md) * [Game Pak](04-non-video/07-game_pak.md) * [Examples](05-examples/00-index.md) - * [hello_magic](05-examples/01-hello_magic.md) - * [hello_world](05-examples/02-hello_world.md) - * [light_cycle](05-examples/03-light_cycle.md) - * [bg_demo](05-examples/04-bg_demo.md) diff --git a/examples/hello_world.rs b/examples/hello_world.rs deleted file mode 100644 index 4258b12..0000000 --- a/examples/hello_world.rs +++ /dev/null @@ -1,243 +0,0 @@ -#![no_std] -#![feature(start)] -#![feature(underscore_const_names)] - -#[macro_export] -macro_rules! newtype { - ($(#[$attr:meta])* $new_name:ident, $old_name:ident) => { - $(#[$attr])* - #[repr(transparent)] - pub struct $new_name($old_name); - }; -} - -#[macro_export] -macro_rules! const_assert { - ($condition:expr) => { - #[deny(const_err)] - #[allow(dead_code)] - const _: usize = 0 - !$condition as usize; - }; -} - -/// Constructs an RGB value with a `const_assert!` that the input is in range. -#[macro_export] -macro_rules! const_rgb { - ($r:expr, $g:expr, $b:expr) => {{ - const_assert!($r <= 31); - const_assert!($g <= 31); - const_assert!($b <= 31); - Color::new($r, $g, $b) - }}; -} - -mod vol_address { - #![allow(unused)] - use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; - /// VolAddress - #[derive(Debug)] - #[repr(transparent)] - pub struct VolAddress { - address: NonZeroUsize, - marker: PhantomData<*mut T>, - } - impl Clone for VolAddress { - fn clone(&self) -> Self { - *self - } - } - impl Copy for VolAddress {} - impl PartialEq for VolAddress { - fn eq(&self, other: &Self) -> bool { - self.address == other.address - } - } - impl Eq for VolAddress {} - impl PartialOrd for VolAddress { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.address.cmp(&other.address)) - } - } - impl Ord for VolAddress { - fn cmp(&self, other: &Self) -> Ordering { - self.address.cmp(&other.address) - } - } - impl VolAddress { - pub const unsafe fn new_unchecked(address: usize) -> Self { - VolAddress { - address: NonZeroUsize::new_unchecked(address), - marker: PhantomData, - } - } - pub const unsafe fn cast(self) -> VolAddress { - VolAddress { - address: self.address, - marker: PhantomData, - } - } - pub unsafe fn offset(self, offset: isize) -> Self { - // TODO: const this - VolAddress { - address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), - marker: PhantomData, - } - } - pub fn is_aligned(self) -> bool { - // TODO: const this - self.address.get() % core::mem::align_of::() == 0 - } - pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { - VolAddressIter { vol_address: self, slots } - } - pub fn read(self) -> T - where - T: Copy, - { - unsafe { (self.address.get() as *mut T).read_volatile() } - } - pub unsafe fn read_non_copy(self) -> T { - (self.address.get() as *mut T).read_volatile() - } - pub fn write(self, val: T) { - unsafe { (self.address.get() as *mut T).write_volatile(val) } - } - } - /// VolAddressIter - #[derive(Debug)] - pub struct VolAddressIter { - vol_address: VolAddress, - slots: usize, - } - impl Clone for VolAddressIter { - fn clone(&self) -> Self { - VolAddressIter { - vol_address: self.vol_address, - slots: self.slots, - } - } - } - impl PartialEq for VolAddressIter { - fn eq(&self, other: &Self) -> bool { - self.vol_address == other.vol_address && self.slots == other.slots - } - } - impl Eq for VolAddressIter {} - impl Iterator for VolAddressIter { - type Item = VolAddress; - - fn next(&mut self) -> Option { - if self.slots > 0 { - let out = self.vol_address; - unsafe { - self.slots -= 1; - self.vol_address = self.vol_address.offset(1); - } - Some(out) - } else { - None - } - } - } - impl FusedIterator for VolAddressIter {} - /// VolAddressBlock - #[derive(Debug)] - pub struct VolAddressBlock { - vol_address: VolAddress, - slots: usize, - } - impl Clone for VolAddressBlock { - fn clone(&self) -> Self { - VolAddressBlock { - vol_address: self.vol_address, - slots: self.slots, - } - } - } - impl PartialEq for VolAddressBlock { - fn eq(&self, other: &Self) -> bool { - self.vol_address == other.vol_address && self.slots == other.slots - } - } - impl Eq for VolAddressBlock {} - impl VolAddressBlock { - pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { - VolAddressBlock { vol_address, slots } - } - pub const fn iter(self) -> VolAddressIter { - VolAddressIter { - vol_address: self.vol_address, - slots: self.slots, - } - } - pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { - // TODO: const this - self.vol_address.offset(slot as isize) - } - pub fn index(self, slot: usize) -> VolAddress { - if slot < self.slots { - unsafe { self.vol_address.offset(slot as isize) } - } else { - panic!("Index Requested: {} >= Bound: {}", slot, self.slots) - } - } - pub fn get(self, slot: usize) -> Option> { - if slot < self.slots { - unsafe { Some(self.vol_address.offset(slot as isize)) } - } else { - None - } - } - } -} -use self::vol_address::*; - -newtype! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - Color, u16 -} - -impl Color { - /// Combines the Red, Blue, and Green provided into a single color value. - pub const fn new(red: u16, green: u16, blue: u16) -> Color { - Color(blue << 10 | green << 5 | red) - } -} - -newtype! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - DisplayControlSetting, u16 -} - -pub const DISPLAY_CONTROL: VolAddress = unsafe { VolAddress::new_unchecked(0x0400_0000) }; -pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3); -pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000); -pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0); - -pub struct Mode3; -impl Mode3 { - const SCREEN_WIDTH: isize = 240; - const SCREEN_HEIGHT: isize = 160; - const PIXELS: VolAddressBlock = - unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x600_0000), (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) as usize) }; - - pub unsafe fn draw_pixel(col: usize, row: usize, color: Color) { - Self::PIXELS.index(col + row * Self::SCREEN_WIDTH as usize).write(color); - } -} - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2); - Mode3::draw_pixel(120, 80, const_rgb!(31, 0, 0)); - Mode3::draw_pixel(136, 80, const_rgb!(0, 31, 0)); - Mode3::draw_pixel(120, 96, const_rgb!(0, 0, 31)); - loop {} - } -} diff --git a/src/base/builtins.rs b/src/base/builtins.rs index db3615e..5966450 100644 --- a/src/base/builtins.rs +++ b/src/base/builtins.rs @@ -65,11 +65,21 @@ pub extern "C" fn __clzsi2(mut x: usize) -> usize { #[test] fn __clzsi2_test() { - let mut i = 1 << 63; + let mut i: usize = core::usize::MAX; while i > 0 { - assert_eq!(__clzsi2(i), i.leading_zeros() as usize); + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); i >>= 1; } + // check 0 also + i = 0; + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); + // double check for bit patterns that aren't solid 1s + i = 1; + for _ in 0 .. 63 { + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); + i <<= 2; + i += 1; + } } // TODO: add some shims