sensible goals

This commit is contained in:
Lokathor 2018-12-22 00:26:52 -07:00
parent 5bb6a927fd
commit b927a348bd
11 changed files with 107 additions and 275 deletions

View file

@ -2,26 +2,17 @@
So, what's this book actually gonna teach you? 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 Overall the book is sorted more for easy review once you're trying to program
it's on crates.io and all that jazz. 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
However, unlike most crates that come with a tutorial book, I don't want to just having to refer you to portions that you haven't read yet. The chapters and
teach you how to use the crate. What I want is to teach you what you need to sections are sorted so that _minimal_ future references are required, but it's
know so that you could build the crate yourself, from scratch, if it didn't unavoidable that it'll happen sometimes.
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.
The actual "tutorial order" of the book is the The actual "tutorial order" of the book is the
[Examples](../05-examples/00-index.md) chapter. Each section of that chapter [Examples](../05-examples/00-index.md) chapter. Each section of that chapter

View file

@ -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_ 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 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 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. implement it three times, which calls for another macro.
```rust ```rust

View file

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

View file

@ -3,4 +3,81 @@
It's all well and good to just show a picture, even to show an animation, but if 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. 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").

View file

@ -1 +0,0 @@
# hello_magic

View file

@ -1 +0,0 @@
# hello_world

View file

@ -1 +0,0 @@
# light_cycle

View file

@ -1 +0,0 @@
# bg_demo

View file

@ -35,7 +35,3 @@
* [Link Cable](04-non-video/06-link_cable.md) * [Link Cable](04-non-video/06-link_cable.md)
* [Game Pak](04-non-video/07-game_pak.md) * [Game Pak](04-non-video/07-game_pak.md)
* [Examples](05-examples/00-index.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)

View file

@ -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<T> {
address: NonZeroUsize,
marker: PhantomData<*mut T>,
}
impl<T> Clone for VolAddress<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for VolAddress<T> {}
impl<T> PartialEq for VolAddress<T> {
fn eq(&self, other: &Self) -> bool {
self.address == other.address
}
}
impl<T> Eq for VolAddress<T> {}
impl<T> PartialOrd for VolAddress<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.address.cmp(&other.address))
}
}
impl<T> Ord for VolAddress<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address)
}
}
impl<T> VolAddress<T> {
pub const unsafe fn new_unchecked(address: usize) -> Self {
VolAddress {
address: NonZeroUsize::new_unchecked(address),
marker: PhantomData,
}
}
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
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::<T>())),
marker: PhantomData,
}
}
pub fn is_aligned(self) -> bool {
// TODO: const this
self.address.get() % core::mem::align_of::<T>() == 0
}
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
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<T> {
vol_address: VolAddress<T>,
slots: usize,
}
impl<T> Clone for VolAddressIter<T> {
fn clone(&self) -> Self {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl<T> PartialEq for VolAddressIter<T> {
fn eq(&self, other: &Self) -> bool {
self.vol_address == other.vol_address && self.slots == other.slots
}
}
impl<T> Eq for VolAddressIter<T> {}
impl<T> Iterator for VolAddressIter<T> {
type Item = VolAddress<T>;
fn next(&mut self) -> Option<Self::Item> {
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<T> FusedIterator for VolAddressIter<T> {}
/// VolAddressBlock
#[derive(Debug)]
pub struct VolAddressBlock<T> {
vol_address: VolAddress<T>,
slots: usize,
}
impl<T> Clone for VolAddressBlock<T> {
fn clone(&self) -> Self {
VolAddressBlock {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl<T> PartialEq for VolAddressBlock<T> {
fn eq(&self, other: &Self) -> bool {
self.vol_address == other.vol_address && self.slots == other.slots
}
}
impl<T> Eq for VolAddressBlock<T> {}
impl<T> VolAddressBlock<T> {
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
VolAddressBlock { vol_address, slots }
}
pub const fn iter(self) -> VolAddressIter<T> {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
// TODO: const this
self.vol_address.offset(slot as isize)
}
pub fn index(self, slot: usize) -> VolAddress<T> {
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<VolAddress<T>> {
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<DisplayControlSetting> = 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<Color> =
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 {}
}
}

View file

@ -65,11 +65,21 @@ pub extern "C" fn __clzsi2(mut x: usize) -> usize {
#[test] #[test]
fn __clzsi2_test() { fn __clzsi2_test() {
let mut i = 1 << 63; let mut i: usize = core::usize::MAX;
while i > 0 { while i > 0 {
assert_eq!(__clzsi2(i), i.leading_zeros() as usize); assert_eq!(__clzsi2(i) as u32, i.leading_zeros());
i >>= 1; 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 // TODO: add some shims