mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +11:00
sensible goals
This commit is contained in:
parent
5bb6a927fd
commit
b927a348bd
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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").
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# hello_magic
|
|
@ -1 +0,0 @@
|
|||
# hello_world
|
|
@ -1 +0,0 @@
|
|||
# light_cycle
|
|
@ -1 +0,0 @@
|
|||
# bg_demo
|
|
@ -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)
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue