mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-23 07:56:33 +11:00
Merge #71
71: Dev merge (testing bors) r=Lokathor a=Lokathor Co-authored-by: Lokathor <zefria@gmail.com>
This commit is contained in:
commit
eb157e00dc
40 changed files with 2107 additions and 867 deletions
20
.travis.yml
20
.travis.yml
|
@ -6,12 +6,18 @@ cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.cargo
|
- $HOME/.cargo
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- staging
|
||||||
|
- trying
|
||||||
|
- master
|
||||||
|
- lokathor
|
||||||
|
|
||||||
rust:
|
rust:
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- rustup component add rust-src
|
- rustup component add rust-src
|
||||||
- rustup component add clippy --toolchain=nightly
|
|
||||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||||
- (test -x $HOME/.cargo/bin/cargo-xbuild || cargo install cargo-xbuild)
|
- (test -x $HOME/.cargo/bin/cargo-xbuild || cargo install cargo-xbuild)
|
||||||
- (test -x $HOME/.cargo/bin/cargo-make || cargo install cargo-make)
|
- (test -x $HOME/.cargo/bin/cargo-make || cargo install cargo-make)
|
||||||
|
@ -31,15 +37,13 @@ script:
|
||||||
- export PATH="$PATH:/opt/devkitpro/tools/bin"
|
- export PATH="$PATH:/opt/devkitpro/tools/bin"
|
||||||
- cd ..
|
- cd ..
|
||||||
# Run all verificaions, both debug and release
|
# Run all verificaions, both debug and release
|
||||||
#- cargo clippy
|
- cargo test --lib
|
||||||
#- cargo clippy --release
|
- cargo test --lib --release
|
||||||
- cargo test --no-fail-fast --lib
|
- cargo test --tests
|
||||||
- cargo test --no-fail-fast --lib --release
|
- cargo test --tests --release
|
||||||
- cargo test --no-fail-fast --tests
|
|
||||||
- cargo test --no-fail-fast --tests --release
|
|
||||||
# Let cargo make take over the rest
|
# Let cargo make take over the rest
|
||||||
- cargo make justrelease
|
- cargo make justrelease
|
||||||
# Test build the book so that a failed book build kills this run
|
# Test a build of the book so that a failed book build kills this run
|
||||||
- cd book && mdbook build
|
- cd book && mdbook build
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gba"
|
name = "gba"
|
||||||
description = "A crate (and book) for making GBA games with Rust."
|
description = "A crate (and book) for making GBA games with Rust."
|
||||||
version = "0.3.2"
|
version = "0.4.0-pre"
|
||||||
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
authors = ["Lokathor <zefria@gmail.com>", "Thomas Winwood <twwinwood@gmail.com>"]
|
||||||
repository = "https://github.com/rust-console/gba"
|
repository = "https://github.com/rust-console/gba"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -9,10 +9,11 @@ keywords = ["gba"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
#publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
typenum = "1.10"
|
typenum = "1.10"
|
||||||
|
voladdress = "0.2"
|
||||||
gba-proc-macro = "0.5"
|
gba-proc-macro = "0.5"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -146,7 +146,7 @@ So, again using the `hello_magic` example, we had
|
||||||
And instead we could declare
|
And instead we could declare
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
const MAGIC_LOCATION: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
const MAGIC_LOCATION: VolAddress<u16> = unsafe { VolAddress::new(0x400_0000) };
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using A VolAddress Value
|
### Using A VolAddress Value
|
||||||
|
@ -300,7 +300,7 @@ Now we can have something like:
|
||||||
```rust
|
```rust
|
||||||
const OTHER_MAGIC: VolAddressBlock<u16> = unsafe {
|
const OTHER_MAGIC: VolAddressBlock<u16> = unsafe {
|
||||||
VolAddressBlock::new_unchecked(
|
VolAddressBlock::new_unchecked(
|
||||||
VolAddress::new_unchecked(0x600_0000),
|
VolAddress::new(0x600_0000),
|
||||||
240 * 160
|
240 * 160
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,4 +2,8 @@
|
||||||
# Rust GBA Guide
|
# Rust GBA Guide
|
||||||
|
|
||||||
* [Development Setup](development-setup.md)
|
* [Development Setup](development-setup.md)
|
||||||
|
* [Volatile](volatile.md)
|
||||||
|
* [The Hardware Memory Map](the-hardware-memory-map.md)
|
||||||
|
* [IO Registers](io-registers.md)
|
||||||
|
* [Bitmap Video](bitmap-video.md)
|
||||||
* [GBA Assembly](gba-asm.md)
|
* [GBA Assembly](gba-asm.md)
|
||||||
|
|
214
book/src/bitmap-video.md
Normal file
214
book/src/bitmap-video.md
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
# Bitmap Video
|
||||||
|
|
||||||
|
Our first video modes to talk about are the bitmap video modes.
|
||||||
|
|
||||||
|
It's not because they're the best and fastest, it's because they're the
|
||||||
|
_simplest_. You can get going and practice with them really quickly. Usually
|
||||||
|
after that you end up wanting to move on to the other video modes because they
|
||||||
|
have better hardware support, so you can draw more complex things with the small
|
||||||
|
number of cycles that the GBA allows.
|
||||||
|
|
||||||
|
## The Three Bitmap Modes
|
||||||
|
|
||||||
|
As I said in the Hardware Memory Map section, the Video RAM lives in the address
|
||||||
|
space at `0x600_0000`. Depending on our video mode the display controller will
|
||||||
|
consider this memory to be in one of a few totally different formats.
|
||||||
|
|
||||||
|
### Mode 3
|
||||||
|
|
||||||
|
The screen is 160 rows, each 240 pixels long, of `u16` color values.
|
||||||
|
|
||||||
|
This is "full" resolution, and "full" color. It adds up to 76,800 bytes. VRAM is
|
||||||
|
only 96,304 bytes total though. There's enough space left over after the bitmap
|
||||||
|
for some object tile data if you want to use objects, but basically Mode3 is
|
||||||
|
using all of VRAM as one huge canvas.
|
||||||
|
|
||||||
|
### Mode 4
|
||||||
|
|
||||||
|
The screen is 160 rows, each 240 pixels long, of `u8` palette values.
|
||||||
|
|
||||||
|
This has half as much space per pixel. What's a palette value? That's an index
|
||||||
|
into the background PALRAM which says what the color of that pixel should be. We
|
||||||
|
still have the full color space available, but we can only use 256 colors at the
|
||||||
|
same time.
|
||||||
|
|
||||||
|
What did we get in exchange for this? Well, now there's a second "page". The
|
||||||
|
second page starts `0xA000` bytes into VRAM (in both Mode 4 and Mode 5). It's an
|
||||||
|
entire second set of pixel data. You determine if Page 0 or Page 1 is shown
|
||||||
|
using bit 4 of DISPCNT. When you swap which page is being displayed it's called
|
||||||
|
page flipping or flipping the page, or something like that.
|
||||||
|
|
||||||
|
Having two pages is cool, but Mode 4 has a big drawback: it's part of VRAM so
|
||||||
|
that "can't write 1 byte at a time" rule applies. This means that to set a
|
||||||
|
single byte we need to read a `u16`, adjust just one side of it, and then write
|
||||||
|
that `u16` back. We can hide the complication behind a method call, but it
|
||||||
|
simply takes longer to do all that, so editing pixels ends up being
|
||||||
|
unfortunately slow compared to the other bitmap modes.
|
||||||
|
|
||||||
|
### Mode 5
|
||||||
|
|
||||||
|
The screen is 128 rows, each 160 pixels long, of `u16` color values.
|
||||||
|
|
||||||
|
Mode 5 has two pages like Mode 4 does, but instead of keeping full resolution we
|
||||||
|
keep full color. The pixels are displayed in the top left and it's just black on
|
||||||
|
the right and bottom edges. You can use the background control registers to
|
||||||
|
shift it around, maybe center it, but there's no way to get around the fact that
|
||||||
|
not having full resolution is kinda awkward.
|
||||||
|
|
||||||
|
## Using Mode 3
|
||||||
|
|
||||||
|
Let's have a look at how this comes together. We'll call this one
|
||||||
|
`hello_world.rs`, since it's our first real program.
|
||||||
|
|
||||||
|
### Module Attributes and Imports
|
||||||
|
|
||||||
|
At the top of our file we're still `no_std` and we're still using
|
||||||
|
`feature(start)`, but now we're using the `gba` crate so we're 100% safe code!
|
||||||
|
Often enough we'll need a little `unsafe`, but for just bitmap drawing we don't
|
||||||
|
need it.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use gba::{
|
||||||
|
fatal,
|
||||||
|
io::{
|
||||||
|
display::{DisplayControlSetting, DisplayMode, DISPCNT, VBLANK_SCANLINE, VCOUNT},
|
||||||
|
keypad::read_key_input,
|
||||||
|
},
|
||||||
|
vram::bitmap::Mode3,
|
||||||
|
Color,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Panic Handler
|
||||||
|
|
||||||
|
Before we had a panic handler that just looped forever. Now that we're using the
|
||||||
|
`gba` crate we can rely on the debug output channel from `mGBA` to get a message
|
||||||
|
into the real world. There's macros setup for each message severity, and they
|
||||||
|
all accept a format string and arguments, like how `println` works. The catch is
|
||||||
|
that a given message is capped at a length of 255 bytes, and it should probably
|
||||||
|
be ASCII only.
|
||||||
|
|
||||||
|
In the case of the `fatal` message level, it also halts the emulator.
|
||||||
|
|
||||||
|
Of course, if the program is run on real hardware then the `fatal` message won't
|
||||||
|
stop the program, so we still need the infinite loop there too.
|
||||||
|
|
||||||
|
(not that this program _can_ panic, but `rustc` doesn't know that so it demands
|
||||||
|
we have a `panic_handler`)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
// This kills the emulation with a message if we're running within mGBA.
|
||||||
|
fatal!("{}", info);
|
||||||
|
// If we're _not_ running within mGBA then we still need to not return, so
|
||||||
|
// loop forever doing nothing.
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Waiting Around
|
||||||
|
|
||||||
|
Like I talked about before, sometimes we need to wait around a bit for the right
|
||||||
|
moment to start doing work. However, we don't know how to do the good version of
|
||||||
|
waiting for VBlank and VDraw to start, so we'll use the really bad version of it
|
||||||
|
for now.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Performs a busy loop until VBlank starts.
|
||||||
|
///
|
||||||
|
/// This is very inefficient, and please keep following the lessons until we
|
||||||
|
/// cover how interrupts work!
|
||||||
|
pub fn spin_until_vblank() {
|
||||||
|
while VCOUNT.read() < VBLANK_SCANLINE {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a busy loop until VDraw starts.
|
||||||
|
///
|
||||||
|
/// This is very inefficient, and please keep following the lessons until we
|
||||||
|
/// cover how interrupts work!
|
||||||
|
pub fn spin_until_vdraw() {
|
||||||
|
while VCOUNT.read() >= VBLANK_SCANLINE {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup in `main`
|
||||||
|
|
||||||
|
In main we set the display control value we want and declare a few variables
|
||||||
|
we're going to use in our primary loop.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
const SETTING: DisplayControlSetting =
|
||||||
|
DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||||
|
DISPCNT.write(SETTING);
|
||||||
|
|
||||||
|
let mut px = Mode3::WIDTH / 2;
|
||||||
|
let mut py = Mode3::HEIGHT / 2;
|
||||||
|
let mut color = Color::from_rgb(31, 0, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stuff During VDraw
|
||||||
|
|
||||||
|
When a frame starts we want to read the keys, then adjust as much of the game
|
||||||
|
state as we can without touching VRAM.
|
||||||
|
|
||||||
|
Once we're ready, we do our spin loop until VBlank starts.
|
||||||
|
|
||||||
|
In this case, we're going to adjust `px` and `py` depending on the arrow pad
|
||||||
|
input, and also we'll cycle around the color depending on L and R being pressed.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
loop {
|
||||||
|
// read our keys for this frame
|
||||||
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
|
// adjust game state and wait for vblank
|
||||||
|
px = px.wrapping_add(2 * this_frame_keys.x_tribool() as usize);
|
||||||
|
py = py.wrapping_add(2 * this_frame_keys.y_tribool() as usize);
|
||||||
|
if this_frame_keys.l() {
|
||||||
|
color = Color(color.0.rotate_left(5));
|
||||||
|
}
|
||||||
|
if this_frame_keys.r() {
|
||||||
|
color = Color(color.0.rotate_right(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we wait
|
||||||
|
spin_until_vblank();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stuff During VBlank
|
||||||
|
|
||||||
|
When VBlank starts we want want to update video memory to display the new
|
||||||
|
frame's situation.
|
||||||
|
|
||||||
|
In our case, we're going to paint a little square of the current color, but also
|
||||||
|
if you go off the map it resets the screen.
|
||||||
|
|
||||||
|
At the end, we spin until VDraw starts so we can do the whole thing again.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// draw the new game and wait until the next frame starts.
|
||||||
|
if px >= Mode3::WIDTH || py >= Mode3::HEIGHT {
|
||||||
|
// out of bounds, reset the screen and position.
|
||||||
|
Mode3::dma_clear_to(Color::from_rgb(0, 0, 0));
|
||||||
|
px = Mode3::WIDTH / 2;
|
||||||
|
py = Mode3::HEIGHT / 2;
|
||||||
|
} else {
|
||||||
|
// draw the new part of the line
|
||||||
|
Mode3::write(px, py, color);
|
||||||
|
Mode3::write(px, py + 1, color);
|
||||||
|
Mode3::write(px + 1, py, color);
|
||||||
|
Mode3::write(px + 1, py + 1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we wait again
|
||||||
|
spin_until_vdraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -171,12 +171,14 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Throw that into your project skeleton, build the program, and give it a run. You
|
Throw that into your project skeleton, build the program, and give it a run in
|
||||||
should see a red, green, and blue dot close-ish to the middle of the screen. If
|
an emulator. I suggest [mgba](https://mgba.io/2019/01/26/mgba-0.7.0/), it has
|
||||||
you don't, something _already_ went wrong. Double check things, phone a friend,
|
some developer tools we'll use later on. You should see a red, green, and blue
|
||||||
write your senators, try asking `Lokathor` or `Ketsuban` on the [Rust Community
|
dot close-ish to the middle of the screen. If you don't, something _already_
|
||||||
|
went wrong. Double check things, phone a friend, write your senators, try asking
|
||||||
|
`Lokathor` or `Ketsuban` on the [Rust Community
|
||||||
Discord](https://discordapp.com/invite/aVESxV8), until you're eventually able to
|
Discord](https://discordapp.com/invite/aVESxV8), until you're eventually able to
|
||||||
get your three dots going.
|
get your three dots going.
|
||||||
|
|
||||||
Of course, I'm sure you want to know why those numbers are the numbers to use.
|
Of course, I'm sure you want to know why those particular numbers are the
|
||||||
Well that's what the whole rest of the book is about!
|
numbers to use. Well that's what the whole rest of the book is about!
|
||||||
|
|
|
@ -17,21 +17,32 @@ sometimes. Accordingly, you should know how assembly works on the GBA.
|
||||||
Version](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf)
|
Version](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf)
|
||||||
of the documentation available, if you'd like that.
|
of the documentation available, if you'd like that.
|
||||||
|
|
||||||
|
* In addition to the `ARM7TDMI` book, which is specific to the GBA's CPU, you'll
|
||||||
|
need to find a copy of the ARM Architecture Reference Manual if you want
|
||||||
|
general ARM knowledge. The ARM Infocenter has the
|
||||||
|
[ARMv5](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0100i/index.html)
|
||||||
|
version of said manual hosted on their site. Unfortunately, they don't seem to
|
||||||
|
host the `ARMv4T` version of the manual any more.
|
||||||
|
|
||||||
* The [GBATek: ARM CPU
|
* The [GBATek: ARM CPU
|
||||||
Overview](https://problemkaputt.de/gbatek.htm#armcpuoverview) also has quite a
|
Overview](https://problemkaputt.de/gbatek.htm#armcpuoverview) also has quite a
|
||||||
bit of info. Some of it is a duplication of what you'd find in the ARM
|
bit of info. Some of it is a duplication of what you'd find in the ARM
|
||||||
Infocenter reference manual. Some of it is specific to the GBA's chip. Some of
|
Infocenter reference manuals. Some of it is information that's specific to the
|
||||||
it is specific to the ARM chips within the DS and DSi. It's a bit of a jumbled
|
GBA's layout and how the CPU interacts with other parts (such as how its
|
||||||
mess, and as with the rest of GBATEK, the explanations are in a "sparse" style
|
timings and the display adapter's timings line up). Some of it is specific to
|
||||||
(to put it nicely), so I wouldn't take it as your only source.
|
the ARM chips _within the DS and DSi_, so be careful to make sure that you
|
||||||
|
don't wander into the wrong section. GBATEK is always a bit of a jumbled mess,
|
||||||
|
and the explanations are often "sparse" (to put it nicely), so I'd advise that
|
||||||
|
you also look at the official ARM manuals.
|
||||||
|
|
||||||
* The [Compiler Explorer](https://rust.godbolt.org/z/ndCnk3) can be used to
|
* The [Compiler Explorer](https://rust.godbolt.org/z/ndCnk3) can be used to
|
||||||
quickly look at assembly versions of your Rust code. That link there will load
|
quickly look at assembly versions of your Rust code. That link there will load
|
||||||
up an essentially blank `no_std` file with `opt-level=3` set and targeting
|
up an essentially blank `no_std` file with `opt-level=3` set and targeting
|
||||||
`thumbv6m-none-eabi`. That's _not_ the same target as the GBA (it's two ISA
|
`thumbv6m-none-eabi`. That's _not_ the same target as the GBA (it's two ISA
|
||||||
revisions later, ARMv6 instead of ARMv4), but it's the closest CPU target that
|
revisions later, `ARMv6` instead of `ARMv4`), but it's the closest CPU target
|
||||||
is bundled with rustc, so it's the closest you can get with the compiler
|
that is bundled with `rustc`, so it's the closest you can get with the
|
||||||
explorer website. If you're very dedicated I suppose you could setup a [local
|
compiler explorer website. If you're very dedicated I suppose you could setup
|
||||||
|
a [local
|
||||||
instance](https://github.com/mattgodbolt/compiler-explorer#running-a-local-instance)
|
instance](https://github.com/mattgodbolt/compiler-explorer#running-a-local-instance)
|
||||||
of compiler explorer and then add the extra target definition and so on, but
|
of compiler explorer and then add the extra target definition and so on, but
|
||||||
that's _probably_ overkill.
|
that's _probably_ overkill.
|
||||||
|
|
237
book/src/io-registers.md
Normal file
237
book/src/io-registers.md
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
# IO Registers
|
||||||
|
|
||||||
|
As I said before, the IO registers are how you tell the GBA to do all the things
|
||||||
|
you want it to do. If you want a hint at what's available, they're all listed
|
||||||
|
out in the [GBA I/O Map](https://problemkaputt.de/gbatek.htm#gbaiomap) section
|
||||||
|
of GBATEK. Go have a quick look.
|
||||||
|
|
||||||
|
Each individual IO register has a particular address just like we talked about
|
||||||
|
in the Hardware Memory Map section. They also have a size (listed in bytes), and
|
||||||
|
a note on if they're read only, write only, or read-write. Finally, each
|
||||||
|
register has a name and a one line summary. Unfortunately for us, the names are
|
||||||
|
all C style names with heavy shorthand. I'm not normally a fan of shorthand
|
||||||
|
names, but the `gba` crate uses the register names from GBATEK as much as
|
||||||
|
possible, since they're the most commonly used set of names among GBA
|
||||||
|
programmers. That way, if you're reading other guides and they say to set the
|
||||||
|
`BG2CNT` register, then you know exactly what register to look for within the
|
||||||
|
`gba` docs.
|
||||||
|
|
||||||
|
## Register Bits
|
||||||
|
|
||||||
|
There's only about 100 registers, but there's a lot more than 100 details we
|
||||||
|
want to have control over on the GBA. How does that work? Well, let's use a
|
||||||
|
particular register to talk about it. The first one on the list is `DISPCNT`,
|
||||||
|
the "Display Control" register. It's one of the most important IO registers, so
|
||||||
|
this is a "two birds with one stone" situation.
|
||||||
|
|
||||||
|
Naturally there's a whole lot of things involved in the LCD that we want to
|
||||||
|
control, and it's all "one" value, but that value is actually many "fields"
|
||||||
|
packed into one value. When learning about an IO register, you have to look at
|
||||||
|
its bit pattern breakdown. For `DISPCNT` the GBATEK entry looks like this:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
4000000h - DISPCNT - LCD Control (Read/Write)
|
||||||
|
Bit Expl.
|
||||||
|
0-2 BG Mode (0-5=Video Mode 0-5, 6-7=Prohibited)
|
||||||
|
3 Reserved / CGB Mode (0=GBA, 1=CGB; can be set only by BIOS opcodes)
|
||||||
|
4 Display Frame Select (0-1=Frame 0-1) (for BG Modes 4,5 only)
|
||||||
|
5 H-Blank Interval Free (1=Allow access to OAM during H-Blank)
|
||||||
|
6 OBJ Character VRAM Mapping (0=Two dimensional, 1=One dimensional)
|
||||||
|
7 Forced Blank (1=Allow FAST access to VRAM,Palette,OAM)
|
||||||
|
8 Screen Display BG0 (0=Off, 1=On)
|
||||||
|
9 Screen Display BG1 (0=Off, 1=On)
|
||||||
|
10 Screen Display BG2 (0=Off, 1=On)
|
||||||
|
11 Screen Display BG3 (0=Off, 1=On)
|
||||||
|
12 Screen Display OBJ (0=Off, 1=On)
|
||||||
|
13 Window 0 Display Flag (0=Off, 1=On)
|
||||||
|
14 Window 1 Display Flag (0=Off, 1=On)
|
||||||
|
15 OBJ Window Display Flag (0=Off, 1=On)
|
||||||
|
```
|
||||||
|
|
||||||
|
So what we're supposed to understand here is that we've got a `u16`, and then we
|
||||||
|
set the individual bits for the things that we want. In the `hello_magic`
|
||||||
|
example you might recall that we set this register to the value `0x0403`. That
|
||||||
|
was a bit of a trick on my part because hex numbers usually look far more
|
||||||
|
mysterious than decimal or binary numbers. If we converted it to binary it'd
|
||||||
|
look like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
0b100_0000_0011
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you can just go down the list of settings to see what bits are what:
|
||||||
|
|
||||||
|
* Bits 0-2 (BG Mode) are `0b011`, so that's Video Mode 3
|
||||||
|
* Bit 10 (Display BG2) is enabled
|
||||||
|
* Everything else is disabled
|
||||||
|
|
||||||
|
Naturally, trying to remember exactly what bit does what can be difficult. In
|
||||||
|
the `gba` crate we attempt as much as possible to make types that wrap over a
|
||||||
|
`u16` or `u32` and then have getters and setters _as if_ all the inner bits were
|
||||||
|
different fields.
|
||||||
|
|
||||||
|
* If it's a single bit then the getter/setter will use `bool`.
|
||||||
|
* If it's more than one bit and each pattern has some non-numeric meaning then
|
||||||
|
it'll use an `enum`.
|
||||||
|
* If it's more than one bit and numeric in nature then it'll just use the
|
||||||
|
wrapped integer type. Note that you generally won't get the full range of the
|
||||||
|
inner number type, and any excess gets truncated down to fit in the bits
|
||||||
|
available.
|
||||||
|
|
||||||
|
All the getters and setters are defined as `const` functions, so you can make
|
||||||
|
constant declarations for the exact setting combinations that you want.
|
||||||
|
|
||||||
|
## Some Important IO Registers
|
||||||
|
|
||||||
|
It's not easy to automatically see what registers will be important for getting
|
||||||
|
started and what registers can be saved to learn about later.
|
||||||
|
|
||||||
|
We'll go over three IO registers here that will help us the most to get started,
|
||||||
|
then next lesson we'll cover how that Video Mode 3 bitmap drawing works, and
|
||||||
|
then by the end of the next lesson we'll be able to put it all together into
|
||||||
|
something interactive.
|
||||||
|
|
||||||
|
### DISPCNT: Display Control
|
||||||
|
|
||||||
|
The [DISPCNT](https://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) register
|
||||||
|
lets us affect the major details of our video output. There's a lot of other
|
||||||
|
registers involved too, but it all starts here.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new(0x400_0000) };
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, the display control register is, like most registers,
|
||||||
|
complicated enough that we make it a dedicated type with getters and setters for
|
||||||
|
the "phantom" fields. In this case it's mostly a bunch of `bool` values we can
|
||||||
|
set, and also the video mode is an `enum`.
|
||||||
|
|
||||||
|
We already looked at the bit listing above, let's go over what's important right
|
||||||
|
now and skip the other bits:
|
||||||
|
|
||||||
|
* BG Mode sets how the whole screen is going to work and even how the display
|
||||||
|
adapter is going to interpret the bit layout of video memory for pixel
|
||||||
|
processing. We'll start with Mode 3, which is the simplest to learn.
|
||||||
|
* The "Forced Blank" bit is one of the very few bits that starts _on_ at the
|
||||||
|
start of the main program. When it's enabled it prevents the display adapter
|
||||||
|
from displaying anything at all. You use this bit when you need to do a very
|
||||||
|
long change to video memory and you don't want the user to see the
|
||||||
|
intermediate states being partly drawn.
|
||||||
|
* The "Screen Display" bits let us enable different display layers. We care
|
||||||
|
about BG2 right now because the bitmap modes (3, 4, and 5) are all treated as
|
||||||
|
if they were drawing into BG2 (even though it's the only BG layer available in
|
||||||
|
those modes).
|
||||||
|
|
||||||
|
There's a bunch of other stuff, but we'll get to those things later. They're not
|
||||||
|
relevent right now, and there's enough to learn already. Already we can see that
|
||||||
|
when the `hello_magic` demo says
|
||||||
|
|
||||||
|
```rust
|
||||||
|
(0x400_0000 as *mut u16).write_volatile(0x0403);
|
||||||
|
```
|
||||||
|
|
||||||
|
We could re-write that more sensibly like this
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const SETTING: DisplayControlSetting =
|
||||||
|
DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||||
|
DISPCNT.write(SETTING);
|
||||||
|
```
|
||||||
|
|
||||||
|
### VCOUNT: Vertical Display Counter
|
||||||
|
|
||||||
|
The [VCOUNT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||||
|
register lets us find out what row of pixels (called a **scanline**) is
|
||||||
|
currently being processed.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const VCOUNT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0006) };
|
||||||
|
```
|
||||||
|
|
||||||
|
You see, the display adapter is constantly running its own loop, along side the
|
||||||
|
CPU. It starts at the very first pixel of the very first scanline, takes 4
|
||||||
|
cycles to determine what color that pixel is, and then processes the next
|
||||||
|
pixel. Each scanline is 240 pixels long, followed by 68 "virtual" pixels so that
|
||||||
|
you have just a moment to setup for the next scanline to be drawn if you need
|
||||||
|
it. 272 cycles (68*4) is not a lot of time, but it's enough that you could
|
||||||
|
change some palette colors or move some objects around if you need to.
|
||||||
|
|
||||||
|
* Horizontal pixel value `0..240`: "HDraw"
|
||||||
|
* Horizontal pixel value `240..308`: "HBlank"
|
||||||
|
|
||||||
|
There's no way to check the current horizontal counter, but there is a way to
|
||||||
|
have the CPU interrupt the normal code when the HBlank period starts, which
|
||||||
|
we'll learn about later.
|
||||||
|
|
||||||
|
Once a complete scanline has been processed (including the blank period), the
|
||||||
|
display adapter keeps going with the next scanline. Similar to how the
|
||||||
|
horizontal processing works, there's 160 scanlines in the real display, and then
|
||||||
|
it's followed by 68 "virtual" scanlines to give you time for adjusting video
|
||||||
|
memory between the frames of the game.
|
||||||
|
|
||||||
|
* Vertical Count `0..160`: "VDraw"
|
||||||
|
* Vertical Count `160..228`: "VBlank"
|
||||||
|
|
||||||
|
Once every scanline has been processed (including the vblank period), the
|
||||||
|
display adapter starts the whole loop over again with scanline 0. A total of
|
||||||
|
280,896 cycles per display loop (4 * 308 * 228), and about 59.59ns per CPU
|
||||||
|
cycle, gives us a full speed display rate of 59.73fps. That's close enough to
|
||||||
|
60fps that I think we can just round up a bit whenever we're not counting it
|
||||||
|
down to the exact cycle timings.
|
||||||
|
|
||||||
|
However, there's a bit of a snag. If we change video memory during the middle of
|
||||||
|
a scanline the display will _immediately_ start processing using the new state
|
||||||
|
of video memory. The picture before the change and after the change won't look
|
||||||
|
like a single, clean picture. Instead you'll get what's called "[screen
|
||||||
|
tearing](https://en.wikipedia.org/wiki/Screen_tearing)", which is usually
|
||||||
|
considered to be the mark of a badly programmed game.
|
||||||
|
|
||||||
|
To avoid this we just need to only adjust video memory during one of the blank
|
||||||
|
periods. If you're really cool you can adjust things during HBlank, but we're
|
||||||
|
not that cool yet. Starting out our general program flow will be:
|
||||||
|
|
||||||
|
1) Gather input for the frame (next part of this lesson) and update the game
|
||||||
|
state, getting everything ready for when VBlank actually starts.
|
||||||
|
2) Once VBlank starts we update all of the video memory as fast as we can.
|
||||||
|
3) Once we're done drawing we again wait for the VDraw period to begin and then
|
||||||
|
do it all again.
|
||||||
|
|
||||||
|
Now, it's not the most efficient way, but to get our timings right we can just
|
||||||
|
read from `VCOUNT` over and over in a "busy loop". Once we read a value of 160
|
||||||
|
we know that we've entered VBlank. Once it goes back to 0 we know that we're
|
||||||
|
back in VDraw.
|
||||||
|
|
||||||
|
Doing a busy loop like this actually drains the batteries way more than
|
||||||
|
necessary. It keeps the CPU active constantly, which is what uses a fair amount
|
||||||
|
of the power. Normally you're supposed to put the CPU to sleep if you're just
|
||||||
|
waiting around for something to happen. However, that also requires learning
|
||||||
|
about some more concepts to get right. So to keep things easier starting out
|
||||||
|
we'll do the bad/lazy version and then upgrade our technique later.
|
||||||
|
|
||||||
|
### KEYINPUT: Key Input Reading
|
||||||
|
|
||||||
|
The [KEYINPUT](https://problemkaputt.de/gbatek.htm#gbakeypadinput) register is
|
||||||
|
the last one we've got to learn about this lesson. It lets you check the status
|
||||||
|
of all 10 buttons on the GBA.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const KEYINPUT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0130) };
|
||||||
|
```
|
||||||
|
|
||||||
|
There's little to say here. It's a read only register, and the data just
|
||||||
|
contains one bit per button. The only thing that's a little weird about it is
|
||||||
|
that the bits follow a "low active" convention, so if the button is pressed then
|
||||||
|
the bit is 0, and if the button is released the bit is 1.
|
||||||
|
|
||||||
|
You _could_ work with that directly, but I think it's a lot easier to think
|
||||||
|
about having `true` for pressed and `false` for not pressed. So the `gba` crate
|
||||||
|
flips the bits when you read the keys:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Gets the current state of the keys
|
||||||
|
pub fn read_key_input() -> KeyInput {
|
||||||
|
KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can treat the KeyInput values like a totally normal bitset.
|
379
book/src/the-hardware-memory-map.md
Normal file
379
book/src/the-hardware-memory-map.md
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
# The Hardware Memory Map
|
||||||
|
|
||||||
|
So we saw `hello_magic.rs` and then we learned what `volatile` was all about,
|
||||||
|
but we've still got a few things that are a bit mysterious. You can't just cast
|
||||||
|
a number into a pointer and start writing to it! That's totally crazy! That's
|
||||||
|
writing to un-allocated memory! Against the rules!
|
||||||
|
|
||||||
|
Well, _kinda_. It's true that you're not allowed to write _anywhere at all_, but
|
||||||
|
those locations were carefully selected locations.
|
||||||
|
|
||||||
|
You see, on a modern computer if you need to check if a key is pressed you ask
|
||||||
|
the Operating System (OS) to please go check for you. If you need to play a
|
||||||
|
sound, you ask the OS to please play the sound on a default sound output. If you
|
||||||
|
need to show a picture you ask the OS to give you access to the video driver so
|
||||||
|
that you can ask the video driver to please put some pixels on the screen.
|
||||||
|
That's mostly fine, except how does the OS actually do it? It doesn't have an OS
|
||||||
|
to go ask, it has to stop somewhere.
|
||||||
|
|
||||||
|
Ultimately, every piece of hardware is mapped into somewhere in the address
|
||||||
|
space of the CPU. You can't actually tell that this is the case as a normal user
|
||||||
|
because your program runs inside a virtualized address space. That way you can't
|
||||||
|
go writing into another program's memory and crash what they're doing or steal
|
||||||
|
their data (well, hopefully, it's obviously not perfect). Outside of the
|
||||||
|
virtualization layer the OS is running directly in the "true" address space, and
|
||||||
|
it can access the hardware on behalf of a program whenever it's asked to.
|
||||||
|
|
||||||
|
How does directly accessing the hardware work, _precisely_? It's just the same
|
||||||
|
as accessing the RAM. Each address holds some bits, and the CPU picks an address
|
||||||
|
and loads in the bits. Then the program gets the bits and has to decide what
|
||||||
|
they mean. The "driver" of a hardware device is just the layer that translates
|
||||||
|
between raw bits in the outside world and more meaningful values inside of the
|
||||||
|
program.
|
||||||
|
|
||||||
|
Of course, memory mapped hardware can change its bits at any time. The user can
|
||||||
|
press and release a key and you can't stop them. This is where `volatile` comes
|
||||||
|
in. Whenever there's memory mapped hardware you want to access it with
|
||||||
|
`volatile` operations so that you can be sure that you're sending the data every
|
||||||
|
time, and that you're getting fresh data every time.
|
||||||
|
|
||||||
|
## GBA Specifics
|
||||||
|
|
||||||
|
That's enough about the general concept of memory mapped hardware, let's get to
|
||||||
|
some GBA specifics. The GBA has the following sections in its memory map.
|
||||||
|
|
||||||
|
* BIOS
|
||||||
|
* External Work RAM (EWRAM)
|
||||||
|
* Internal Work RAM (IWRAM)
|
||||||
|
* IO Registers
|
||||||
|
* Palette RAM (PALRAM)
|
||||||
|
* Video RAM (VRAM)
|
||||||
|
* Object Attribute Memory (OAM)
|
||||||
|
* Game Pak ROM (ROM)
|
||||||
|
* Save RAM (SRAM)
|
||||||
|
|
||||||
|
Each of these has a few key points of interest:
|
||||||
|
|
||||||
|
* **Bus Width:** Also just called "bus", this is how many little wires are
|
||||||
|
_physically_ connecting a part of the address space to the CPU. If you need to
|
||||||
|
transfer more data than fits in the bus you have to do repeated transfers
|
||||||
|
until it all gets through.
|
||||||
|
* **Read/Write Modes:** Most parts of the address space can be read from in 8,
|
||||||
|
16, or 32 bits at a time (there's a few exceptions we'll see). However, a
|
||||||
|
significant portion of the address space can't accept 8 bit writes. Usually
|
||||||
|
this isn't a big deal, but standard `memcopy` routine switches to doing a
|
||||||
|
byte-by-byte copy in some situations, so we'll have to be careful about using
|
||||||
|
it in combination with those regions of the memory.
|
||||||
|
* **Access Speed:** On top of the bus width issue, not all memory can be
|
||||||
|
accessed at the same speed. The "fast" parts of memory can do a read or write
|
||||||
|
in 1 cycle, but the slower parts of memory can take a few cycles per access.
|
||||||
|
These are called "wait cycles". The exact timings depend on what you configure
|
||||||
|
the system to use, which is also limited by what your cartridge physically
|
||||||
|
supports. You'll often see timings broken down into `N` cycles (non-sequential
|
||||||
|
memory access) and `S` cycles (sequential memory access, often faster). There
|
||||||
|
are also `I` cycles (internal cycles) which happen whenever the CPU does an
|
||||||
|
internal operation that's more than one cycle to complete (like a multiply).
|
||||||
|
Don't worry, you don't have to count exact cycle timings unless you're on the
|
||||||
|
razor's edge of the GBA's abilities. For more normal games you just have to be
|
||||||
|
mindful of what you're doing and it'll be fine.
|
||||||
|
|
||||||
|
Let's briefly go over the major talking points of each memory region. All of
|
||||||
|
this information is also available in GBATEK, mostly in their [memory
|
||||||
|
map](http://www.akkit.org/info/gbatek.htm#gbamemorymap) section (though somewhat
|
||||||
|
spread through the rest of the document too).
|
||||||
|
|
||||||
|
Though I'm going to list the location range of each memory space below, most of
|
||||||
|
the hardware locations are actually mirrored at several points throughout the
|
||||||
|
address space.
|
||||||
|
|
||||||
|
### BIOS
|
||||||
|
|
||||||
|
* **Location:** `0x0` to `0x3FFF`
|
||||||
|
* **Bus:** 32-bit
|
||||||
|
* **Access:** Memory protected read-only (see text).
|
||||||
|
* **Wait Cycles:** None
|
||||||
|
|
||||||
|
The "basic input output system". This contains a grab bag of utilities that do
|
||||||
|
various tasks. The code is optimized for small size rather than great speed, so
|
||||||
|
you can sometimes write faster versions of these routines. Also, calling a bios
|
||||||
|
function has more overhead than a normal function call. You can think of bios
|
||||||
|
calls as being similar to system calls to the OS on a desktop computer. Useful,
|
||||||
|
but costly.
|
||||||
|
|
||||||
|
As a side note, not only is BIOS memory read only, but it's memory protected so
|
||||||
|
that you can't even read from bios memory unless the system is currently
|
||||||
|
executing a function that's in bios memory. If you try then the system just
|
||||||
|
gives back a nonsensical value that's not really what you asked for. If you
|
||||||
|
really want to know what's inside, there's actually a bug in one bios call
|
||||||
|
(`MidiKey2Freq`) that lets you read the bios section one byte at a time.
|
||||||
|
|
||||||
|
Also, there's not just one bios! Of course there's the official bios from
|
||||||
|
Nintendo that's used on actual hardware, but since that's code instead of
|
||||||
|
hardware it's protected by copyright. Since a bios is needed to run a GBA
|
||||||
|
emulator properly, people have come up with their own open source versions or
|
||||||
|
they simply make the emulator special case the bios and act _as if_ the function
|
||||||
|
call had done the right thing.
|
||||||
|
|
||||||
|
* The [TempGBA](https://github.com/Nebuleon/TempGBA) repository has an easy to
|
||||||
|
look at version written in assembly. It's API and effects are close enough to
|
||||||
|
the Nintendo version that most games will run just fine.
|
||||||
|
* You can also check out the [mGBA
|
||||||
|
bios](https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c) if you want
|
||||||
|
to see the C version of what various bios functions are doing.
|
||||||
|
|
||||||
|
### External Work RAM (EWRAM)
|
||||||
|
|
||||||
|
* **Location:** `0x200_0000` to `0x203_FFFF` (256k)
|
||||||
|
* **Bus:** 16-bit
|
||||||
|
* **Access:** Read-write, any size.
|
||||||
|
* **Wait Cycles:** 2
|
||||||
|
|
||||||
|
The external work ram is a sizable amount of space, but the 2 wait cycles per
|
||||||
|
access and 16-bit bus mean that you should probably think of it as being a
|
||||||
|
"heap" to avoid putting things in if you don't have to.
|
||||||
|
|
||||||
|
The GBA itself doesn't use this for anything, so any use is totally up to you.
|
||||||
|
|
||||||
|
At the moment, the linker script and `crt0.s` files provided with the `gba`
|
||||||
|
crate also have no defined use for the EWRAM, so it's 100% on you to decide how
|
||||||
|
you wanna use them.
|
||||||
|
|
||||||
|
(Note: There is an undocumented control register that lets you adjust the wait
|
||||||
|
cycles on EWRAM. Using it, you can turn EWRAM from the default 2 wait cycles
|
||||||
|
down to 1. However, not all GBA-like things support it. The GBA and GBA SP do,
|
||||||
|
the GBA Micro and DS do not. Emulators might or might not depending on the
|
||||||
|
particular emulator. See the [GBATEK system
|
||||||
|
control](https://problemkaputt.de/gbatek.htm#gbasystemcontrol) page for a full
|
||||||
|
description of that register, though probably only once you've read more of this
|
||||||
|
tutorial book and know how to make sense of IO registers and such.)
|
||||||
|
|
||||||
|
### Internal Work RAM (IWRAM)
|
||||||
|
|
||||||
|
* **Location:** `0x300_0000` to `0x300_7FFF` (32k)
|
||||||
|
* **Bus:** 32-bit
|
||||||
|
* **Access:** Read-write, any size.
|
||||||
|
* **Wait Cycles:** 0
|
||||||
|
|
||||||
|
This is where the "fast" memory for general purposes lives. By default the
|
||||||
|
system uses the 256 _bytes_ starting at `0x300_7F00` _and up_ for system and
|
||||||
|
interrupt purposes, while Rust's program stack starts at that same address _and
|
||||||
|
goes down_ from there.
|
||||||
|
|
||||||
|
Even though your stack exists in this space, it's totally reasonable to use the
|
||||||
|
bottom parts of this memory space for whatever quick scratch purposes, same as
|
||||||
|
EWRAM. 32k is fairly huge, and the stack going down from the top and the scratch
|
||||||
|
data going up from the bottom are unlikely to hit each other. If they do you
|
||||||
|
were probably well on your way to a stack overflow anyway.
|
||||||
|
|
||||||
|
The linker script and `crt0.s` file provided with the `gba` crate use the bottom
|
||||||
|
of IWRAM to store the `.data` and `.bss` [data
|
||||||
|
segments](https://en.wikipedia.org/wiki/Data_segment). That's where your global
|
||||||
|
variables get placed (both `static` and `static mut`). The `.data` segment holds
|
||||||
|
any variable that's initialized to non-zero, and the `.bss` section is for any
|
||||||
|
variable initialized to zero. When the GBA is powered on, some code in the
|
||||||
|
`crt0.s` file runs and copies the initial `.data` values into place within IWRAM
|
||||||
|
(all of `.bss` starts at 0, so there's no copy for those variables).
|
||||||
|
|
||||||
|
If you have no global variables at all, then you don't need to worry about those
|
||||||
|
details, but if you do have some global variables then you can use the _address
|
||||||
|
of_ the `__bss_end` symbol defined in the top of the `gba` crate as a marker for
|
||||||
|
where it's safe for you to start using IWRAM without overwriting your globals.
|
||||||
|
|
||||||
|
### IO Registers
|
||||||
|
|
||||||
|
* **Location:** `0x400_0000` to `0x400_03FE`
|
||||||
|
* **Bus:** 32-bit
|
||||||
|
* **Access:** different for each IO register
|
||||||
|
* **Wait Cycles:** 0
|
||||||
|
|
||||||
|
The IO Registers are where most of the magic happens, and it's where most of the
|
||||||
|
variety happens too. Each IO register is a specific width, usually 16-bit but
|
||||||
|
sometimes 32-bit. Most of them are fully read/write, but some of them are read
|
||||||
|
only or write only. Some of them have individual bits that are read only even
|
||||||
|
when the rest of the register is writable. Some of them can be written to, but
|
||||||
|
the write doesn't change the value you read back, it sets something else.
|
||||||
|
Really.
|
||||||
|
|
||||||
|
The IO registers are how you control every bit of hardware besides the CPU
|
||||||
|
itself. Reading the buttons, setting display modes, enabling timers, all of that
|
||||||
|
goes through different IO registers. Actually, even a few parts of the CPU's
|
||||||
|
operation can be controlled via IO register.
|
||||||
|
|
||||||
|
We'll go over IO registers more in the next section, including a few specific
|
||||||
|
registers, and then we'll constantly encounter more IO registers as we explore
|
||||||
|
each new topic through the rest of the book.
|
||||||
|
|
||||||
|
### Palette RAM (PALRAM)
|
||||||
|
|
||||||
|
* **Location:** `0x500_0000` to `0x500_03FF` (1k)
|
||||||
|
* **Bus:** 16-bit
|
||||||
|
* **Access:** Read any, single bytes mirrored (see text).
|
||||||
|
* **Wait Cycles:** Video Memory Wait (see text)
|
||||||
|
|
||||||
|
This is where the GBA stores color palette data. There's 256 slots for
|
||||||
|
Background color, and then 256 slots for Object color.
|
||||||
|
|
||||||
|
GBA colors are 15 bits each, with five bits per channel and the highest bit
|
||||||
|
being totally ignored, so we store them as `u16` values:
|
||||||
|
|
||||||
|
* `X_BBBBB_GGGGG_RRRRR`
|
||||||
|
|
||||||
|
Of note is the fact that the 256 palette slots can be viewed in two different
|
||||||
|
ways. There's two different formats for images in video memory: "8 bit per
|
||||||
|
pixel" (8bpp) and "4 bit per pixel mode" (4bpp).
|
||||||
|
|
||||||
|
* **8bpp:** Each pixel in the image is 8 bits and indexes directly into the full
|
||||||
|
256 entry palette array. An index of 0 means that pixel should be transparent,
|
||||||
|
so there's 255 possible colors.
|
||||||
|
* **4bpp:** Each pixel in the image is 4 bits and indexes into a "palbank" of 16
|
||||||
|
colors within the palette data. Some exterior control selects the palbank to
|
||||||
|
be used. An index of 0 still means that the pixel should be transparent, so
|
||||||
|
there's 15 possible colors.
|
||||||
|
|
||||||
|
Different images can use different modes all at once, as long as you can fit all
|
||||||
|
the colors you want to use into your palette layout.
|
||||||
|
|
||||||
|
PALRAM can't be written to in individual bytes. This isn't normally a problem at
|
||||||
|
all, because you wouldn't really want to write half of a color entry anyway. If
|
||||||
|
you do try to write a single byte then it gets "mirrored" into both halves of
|
||||||
|
the `u16` that would be associated with that address. For example, if you tried
|
||||||
|
to write `0x01u8` to either `0x500_0000` or `0x500_0001` then you'd actually
|
||||||
|
_effectively_ be writing `0x0101u16` to `0x500_0000`.
|
||||||
|
|
||||||
|
PALRAM follows what we'll call the "Video Memory Wait" rule: If you to access
|
||||||
|
the memory during a vertical blank or horizontal blank period there's 0 wait
|
||||||
|
cycles, and if you try to access the memory while the display controller is
|
||||||
|
drawing there is a 1 cycle wait inserted _if_ the display controller was using
|
||||||
|
that memory at that moment.
|
||||||
|
|
||||||
|
### Video RAM (VRAM)
|
||||||
|
|
||||||
|
* **Location:** `0x600_0000` to `0x601_7FFF` (96k or 64k+32k depending on mode)
|
||||||
|
* **Bus:** 16-bit
|
||||||
|
* **Access:** Read any, single bytes _sometimes_ mirrored (see text).
|
||||||
|
* **Wait Cycles:** Video Memory Wait (see text)
|
||||||
|
|
||||||
|
Video RAM is the memory for what you want the display controller to be
|
||||||
|
displaying. The GBA actually has 6 different display modes (numbered 0 through
|
||||||
|
5), and depending on the mode you're using the layout that you should imagine
|
||||||
|
VRAM having changes. Because there's so much involved here, I'll leave more
|
||||||
|
precise details to the following sections which talk about how to use VRAM in
|
||||||
|
each mode.
|
||||||
|
|
||||||
|
VRAM can't be written to in individual bytes. If you try to write a single byte
|
||||||
|
to background VRAM the byte gets mirrored like with PALRAM, and if you try with
|
||||||
|
object VRAM the write gets ignored entirely. Exactly what address ranges those
|
||||||
|
memory types are depends on video mode, but just don't bother with individual
|
||||||
|
byte writes to VRAM. If you want to change a single byte of data (and you might)
|
||||||
|
then the correct style is to read the full `u16`, mask out the old data, mask in
|
||||||
|
your new value, and then write the whole `u16`.
|
||||||
|
|
||||||
|
VRAM follows the same "Video Memory Wait" rule that PALRAM has.
|
||||||
|
|
||||||
|
### Object Attribute Memory (OAM)
|
||||||
|
|
||||||
|
* **Location:** `0x700_0000` to `0x700_03FF` (1k)
|
||||||
|
* **Bus:** 32-bit
|
||||||
|
* **Access:** Read any, single bytes no effect (see text).
|
||||||
|
* **Wait Cycles:** Video Memory Wait (see text)
|
||||||
|
|
||||||
|
This part of memory controls the "Objects" (OBJ) on the screen. An object is
|
||||||
|
_similar to_ the concept of a "sprite". However, because of an object's size
|
||||||
|
limitations, a single sprite might require more than one object to be drawn
|
||||||
|
properly. In general, if you want to think in terms of sprites at all, you
|
||||||
|
should think of sprites as being a logical / programming concept, and objects as
|
||||||
|
being a hardware concept.
|
||||||
|
|
||||||
|
While VRAM has the _image_ data for each object, this part of memory has the
|
||||||
|
_control_ data for each object. An objects "attributes" describe what part of
|
||||||
|
the VRAM to use, where to place is on the screen, any special graphical effects
|
||||||
|
to use, all that stuff. Each object has 6 bytes of attribute data (arranged as
|
||||||
|
three `u16` values), and there's a total of 128 objects (indexed 0 through 127).
|
||||||
|
|
||||||
|
But 6 bytes each times 128 entries out of 1024 bytes leaves us with 256 bytes
|
||||||
|
left over. What's the other space used for? Well, it's a little weird, but after
|
||||||
|
every three `u16` object attribute fields there's one `i16` "affine parameter"
|
||||||
|
field mixed in. It takes four such fields to make a complete set of affine
|
||||||
|
parameters (a 2x2 matrix), so we get a total of 32 affine parameter entries
|
||||||
|
across all of OAM. "Affine" might sound fancy but it just means a transformation
|
||||||
|
where anything that started parallel stays parallel after the transform. The
|
||||||
|
affine parameters can be used to scale, rotate, and/or skew a background or
|
||||||
|
object as it's being displayed on the screen. It takes more computing power than
|
||||||
|
the non-affine display, so you can't display as many different things at once
|
||||||
|
when using the affine modes.
|
||||||
|
|
||||||
|
OAM can't ever be written to with individual bytes. The write just has no effect
|
||||||
|
at all.
|
||||||
|
|
||||||
|
OAM follows the same "Video Memory Wait" rule that PALRAM has, **and** you can
|
||||||
|
also only freely access OAM during a horizontal blank if you set a special
|
||||||
|
"HBlank Interval Free" bit in one of the IO registers (the "Display Control"
|
||||||
|
register, which we'll talk about next lesson). The reason that you might _not_
|
||||||
|
want to set that bit is because when it's enabled you can't draw as many objects
|
||||||
|
at once. You don't lose the use of an exact number of objects, you actually lose
|
||||||
|
the use of a number of display adapter drawing cycles. Since not all objects
|
||||||
|
take the same number of cycles to render, it depends on what you're drawing.
|
||||||
|
GBATEK [has the details](https://problemkaputt.de/gbatek.htm#lcdobjoverview) if
|
||||||
|
you want to know precisely.
|
||||||
|
|
||||||
|
### Game Pak ROM (ROM)
|
||||||
|
|
||||||
|
* **Location:** Special (max of 32MB)
|
||||||
|
* **Bus:** 16-bit
|
||||||
|
* **Access:** Special
|
||||||
|
* **Wait Cycles:** Special
|
||||||
|
|
||||||
|
This is where your actual game is located! As you might guess, since each
|
||||||
|
cartridge is different, the details here depend quite a bit on the cartridge
|
||||||
|
that you use for your game. Even a simple statement like "you can't write to the
|
||||||
|
ROM region" isn't true for some carts if they have FlashROM.
|
||||||
|
|
||||||
|
The _most important_ thing to concern yourself with when considering the ROM
|
||||||
|
portion of memory is the 32MB limit. That's compiled code, images, sound,
|
||||||
|
everything put together. The total has to stay under 32MB.
|
||||||
|
|
||||||
|
The next most important thing to consider is that 16-bit bus. It means that we
|
||||||
|
compile our programs using "Thumb state" code instead of "ARM state" code.
|
||||||
|
Details about this can be found in the GBA Assembly section of the book, but
|
||||||
|
just be aware that there's two different types of assembly on the GBA. You can
|
||||||
|
switch between them, but the default for us is always Thumb state.
|
||||||
|
|
||||||
|
Another detail which you actually _don't_ have to think about much, but that you
|
||||||
|
might care if you're doing precise optimization, is that the ROM address space
|
||||||
|
is actually mirrored across three different locations:
|
||||||
|
|
||||||
|
* `0x800_0000` to `0x9FF_FFFF`: Wait State 0
|
||||||
|
* `0xA00_0000` to `0xBFF_FFFF`: Wait State 1
|
||||||
|
* `0xC00_0000` to `0xDFF_FFFF`: Wait State 2
|
||||||
|
|
||||||
|
These _don't_ mean 0, 1, and 2 wait cycles, they mean the wait cycles associated
|
||||||
|
with ROM mirrors 0, 1, and 2. On some carts the game will store different parts
|
||||||
|
of the data into different chips that are wired to be accessible through
|
||||||
|
different parts of the mirroring. The actual wait cycles used are even
|
||||||
|
configurable via an IO register called the
|
||||||
|
[WAITCNT](https://problemkaputt.de/gbatek.htm#gbasystemcontrol) ("Wait Control",
|
||||||
|
I don't know why C programmers have to give everything the worst names it's not
|
||||||
|
1980 any more).
|
||||||
|
|
||||||
|
### Save RAM (SRAM)
|
||||||
|
|
||||||
|
* **Location:** Special (max of 64k)
|
||||||
|
* **Bus:** 8-bit
|
||||||
|
* **Access:** Special
|
||||||
|
* **Wait Cycles:** Special
|
||||||
|
|
||||||
|
The Save RAM is also part of the cart that you've got your game on, so it also
|
||||||
|
depends on your hardware.
|
||||||
|
|
||||||
|
SRAM _starts_ at `0xE00_0000` and you can save up to however much the hardware
|
||||||
|
supports, to a maximum of 64k. However, you can only read and write SRAM one
|
||||||
|
_byte_ at a time. What's worse, while you can _write_ to SRAM using code
|
||||||
|
executing anywhere, you can only _read_ with code that's executing out of either
|
||||||
|
Internal or External Work RAM, not from with code that's executing out of ROM.
|
||||||
|
This means that you need to copy the code for doing the read into some scratch
|
||||||
|
space (either at startup or on the fly, doesn't matter) and call that function
|
||||||
|
you've carefully placed. It's a bit annoying, but soon enough a routine for it
|
||||||
|
all will be provided in the `gba` crate and we won't have to worry too much
|
||||||
|
about it.
|
||||||
|
|
||||||
|
(TODO: Provide the routine that I just claimed we would provide.)
|
48
book/src/volatile.md
Normal file
48
book/src/volatile.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Volatile
|
||||||
|
|
||||||
|
I know that you just got your first program running and you're probably excited
|
||||||
|
to learn more about GBA stuff, but first we have to cover a subject that's not
|
||||||
|
quite GBA specific.
|
||||||
|
|
||||||
|
In the `hello_magic.rs` file we had these lines
|
||||||
|
|
||||||
|
```rust
|
||||||
|
(0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
||||||
|
(0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
||||||
|
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
||||||
|
```
|
||||||
|
|
||||||
|
You've probably seen or heard of the
|
||||||
|
[write](https://doc.rust-lang.org/core/ptr/fn.write.html) function before, but
|
||||||
|
you'd be excused if you've never heard of its cousin function,
|
||||||
|
[write_volatile](https://doc.rust-lang.org/core/ptr/fn.write_volatile.html).
|
||||||
|
|
||||||
|
What's the difference? Well, when the compiler sees normal reads and writes, it
|
||||||
|
assumes that those go into plain old memory locations. CPU registers, RAM,
|
||||||
|
wherever it is that the value's being placed. The compiler assumes that it's
|
||||||
|
safe to optimize away some of the reads and writes, or maybe issue the reads and
|
||||||
|
writes in a different order from what you wrote. Normally this is okay, and it's
|
||||||
|
exactly what we want the compiler to be doing, quietly making things faster for us.
|
||||||
|
|
||||||
|
However, some of the time we access values from parts of memory where it's
|
||||||
|
important that each access happen, and in the exact order that we say. In our
|
||||||
|
`hello_magic.rs` example, we're writing directly into the video memory of the
|
||||||
|
display. The compiler sees that the rest of the Rust program never read out of
|
||||||
|
those locations, so it might think "oh, we can skip those writes, they're
|
||||||
|
pointless". It doesn't know that we're having a side effect besides just storing
|
||||||
|
some value at an address.
|
||||||
|
|
||||||
|
By declaring a particular read or write to be `volatile` then we can force the
|
||||||
|
compiler to issue that access. Further, we're guaranteed that all `volatile`
|
||||||
|
access will happen in exactly the order it appears in the program relative to
|
||||||
|
other `volatile` access. However, non-volatile access can still be re-ordered
|
||||||
|
relative to a volatile access. In other words, for parts of the memory that are
|
||||||
|
volatile, we must _always_ use a volatile read or write for our program to
|
||||||
|
perform properly.
|
||||||
|
|
||||||
|
For exactly this reason, we've got the [voladdress](https://docs.rs/voladdress/)
|
||||||
|
crate. It used to be part of the GBA crate, but it became big enough to break
|
||||||
|
out into a stand alone crate. It doesn't even do too much, it just makes it a
|
||||||
|
lot less error prone to accidentally forget to use volatile with our memory
|
||||||
|
mapped addresses. We just call `read` and `write` on any `VolAddress` that we
|
||||||
|
happen to see and the right thing will happen.
|
1
bors.toml
Normal file
1
bors.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
status = ["continuous-integration/travis-ci/push"]
|
56
crt0.s
56
crt0.s
|
@ -6,6 +6,11 @@ __start:
|
||||||
.fill 188, 1, 0
|
.fill 188, 1, 0
|
||||||
|
|
||||||
.Linit:
|
.Linit:
|
||||||
|
@ Set address of user IRQ handler
|
||||||
|
ldr r0, =MainIrqHandler
|
||||||
|
ldr r1, =0x03FFFFFC
|
||||||
|
str r0, [r1]
|
||||||
|
|
||||||
@ set IRQ stack pointer
|
@ set IRQ stack pointer
|
||||||
mov r0, #0x12
|
mov r0, #0x12
|
||||||
msr CPSR_cf, r0
|
msr CPSR_cf, r0
|
||||||
|
@ -31,4 +36,55 @@ __start:
|
||||||
@ jump to user code
|
@ jump to user code
|
||||||
ldr r0, =main
|
ldr r0, =main
|
||||||
bx r0
|
bx r0
|
||||||
|
|
||||||
|
.arm
|
||||||
|
.global MainIrqHandler
|
||||||
|
.align 4, 0
|
||||||
|
MainIrqHandler:
|
||||||
|
@ Load base I/O register address
|
||||||
|
mov r2, #0x04000000
|
||||||
|
add r2, r2, #0x200
|
||||||
|
|
||||||
|
@ Save IRQ stack pointer and IME
|
||||||
|
mrs r0, spsr
|
||||||
|
ldrh r1, [r2, #8]
|
||||||
|
stmdb sp!, {r0-r2,lr}
|
||||||
|
|
||||||
|
@ Disable all interrupts by writing to IME
|
||||||
|
mov r0, #0
|
||||||
|
strh r0, [r2, #8]
|
||||||
|
|
||||||
|
@ Acknowledge all received interrupts that were enabled in IE
|
||||||
|
ldr r3, [r2, #0]
|
||||||
|
and r0, r3, r3, lsr #16
|
||||||
|
strh r0, [r2, #2]
|
||||||
|
|
||||||
|
@ Switch to system mode
|
||||||
|
mrs r2, cpsr
|
||||||
|
bic r2, r2, #0x1F
|
||||||
|
orr r2, r2, #0x1F
|
||||||
|
msr cpsr_cf, r2
|
||||||
|
|
||||||
|
@ Jump to user specified IRQ handler
|
||||||
|
ldr r2, =__IRQ_HANDLER
|
||||||
|
ldr r1, [r2]
|
||||||
|
stmdb sp!, {lr}
|
||||||
|
adr lr, MainIrqHandler_Return
|
||||||
|
bx r1
|
||||||
|
MainIrqHandler_Return:
|
||||||
|
ldmia sp!, {lr}
|
||||||
|
|
||||||
|
@ Switch to IRQ mode
|
||||||
|
mrs r2, cpsr
|
||||||
|
bic r2, r2, #0x1F
|
||||||
|
orr r2, r2, #0x92
|
||||||
|
msr cpsr_cf, r2
|
||||||
|
|
||||||
|
@ Restore IRQ stack pointer and IME
|
||||||
|
ldmia sp!, {r0-r2,lr}
|
||||||
|
strh r1, [r2, #8]
|
||||||
|
msr spsr_cf, r0
|
||||||
|
|
||||||
|
@ Return to BIOS IRQ handler
|
||||||
|
bx lr
|
||||||
.pool
|
.pool
|
||||||
|
|
|
@ -16,3 +16,8 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
static __IRQ_HANDLER: extern "C" fn() = irq_handler;
|
||||||
|
|
||||||
|
extern "C" fn irq_handler() {}
|
||||||
|
|
|
@ -3,22 +3,82 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use gba::{
|
use gba::{
|
||||||
io::display::{DisplayControlSetting, DisplayMode, DISPCNT},
|
fatal,
|
||||||
|
io::{
|
||||||
|
display::{DisplayControlSetting, DisplayMode, DISPCNT, VBLANK_SCANLINE, VCOUNT},
|
||||||
|
keypad::read_key_input,
|
||||||
|
},
|
||||||
vram::bitmap::Mode3,
|
vram::bitmap::Mode3,
|
||||||
Color,
|
Color,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
// This kills the emulation with a message if we're running within mGBA.
|
||||||
|
fatal!("{}", info);
|
||||||
|
// If we're _not_ running within mGBA then we still need to not return, so
|
||||||
|
// loop forever doing nothing.
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a busy loop until VBlank starts.
|
||||||
|
///
|
||||||
|
/// This is very inefficient, and please keep following the lessons until we
|
||||||
|
/// cover how interrupts work!
|
||||||
|
pub fn spin_until_vblank() {
|
||||||
|
while VCOUNT.read() < VBLANK_SCANLINE {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a busy loop until VDraw starts.
|
||||||
|
///
|
||||||
|
/// This is very inefficient, and please keep following the lessons until we
|
||||||
|
/// cover how interrupts work!
|
||||||
|
pub fn spin_until_vdraw() {
|
||||||
|
while VCOUNT.read() >= VBLANK_SCANLINE {}
|
||||||
|
}
|
||||||
|
|
||||||
#[start]
|
#[start]
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
const SETTING: DisplayControlSetting =
|
||||||
|
DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true);
|
||||||
DISPCNT.write(SETTING);
|
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));
|
let mut px = Mode3::WIDTH / 2;
|
||||||
Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31));
|
let mut py = Mode3::HEIGHT / 2;
|
||||||
loop {}
|
let mut color = Color::from_rgb(31, 0, 0);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// read our keys for this frame
|
||||||
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
|
// adjust game state and wait for vblank
|
||||||
|
px = px.wrapping_add(2 * this_frame_keys.x_tribool() as usize);
|
||||||
|
py = py.wrapping_add(2 * this_frame_keys.y_tribool() as usize);
|
||||||
|
if this_frame_keys.l() {
|
||||||
|
color = Color(color.0.rotate_left(5));
|
||||||
|
}
|
||||||
|
if this_frame_keys.r() {
|
||||||
|
color = Color(color.0.rotate_right(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we wait
|
||||||
|
spin_until_vblank();
|
||||||
|
|
||||||
|
// draw the new game and wait until the next frame starts.
|
||||||
|
if px >= Mode3::WIDTH || py >= Mode3::HEIGHT {
|
||||||
|
// out of bounds, reset the screen and position.
|
||||||
|
Mode3::dma_clear_to(Color::from_rgb(0, 0, 0));
|
||||||
|
px = Mode3::WIDTH / 2;
|
||||||
|
py = Mode3::HEIGHT / 2;
|
||||||
|
} else {
|
||||||
|
// draw the new part of the line
|
||||||
|
Mode3::write(px, py, color);
|
||||||
|
Mode3::write(px, py + 1, color);
|
||||||
|
Mode3::write(px + 1, py, color);
|
||||||
|
Mode3::write(px + 1, py + 1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we wait again
|
||||||
|
spin_until_vdraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
147
examples/irq.rs
Normal file
147
examples/irq.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
|
use gba::{
|
||||||
|
io::{
|
||||||
|
display::{DisplayControlSetting, DisplayMode, DisplayStatusSetting, DISPCNT, DISPSTAT},
|
||||||
|
irq::{self, IrqEnableSetting, IrqFlags, BIOS_IF, IE, IME},
|
||||||
|
keypad::read_key_input,
|
||||||
|
timers::{TimerControlSetting, TimerTickRate, TM0CNT_H, TM0CNT_L, TM1CNT_H, TM1CNT_L},
|
||||||
|
},
|
||||||
|
vram::bitmap::Mode3,
|
||||||
|
Color,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||||
|
const RED: Color = Color::from_rgb(31, 0, 0);
|
||||||
|
const GREEN: Color = Color::from_rgb(0, 31, 0);
|
||||||
|
const BLUE: Color = Color::from_rgb(0, 0, 31);
|
||||||
|
const YELLOW: Color = Color::from_rgb(31, 31, 0);
|
||||||
|
const PINK: Color = Color::from_rgb(31, 0, 31);
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_timers() {
|
||||||
|
let init_val: u16 = u32::wrapping_sub(0x1_0000, 64) as u16;
|
||||||
|
const TIMER_SETTINGS: TimerControlSetting =
|
||||||
|
TimerControlSetting::new().with_overflow_irq(true).with_enabled(true);
|
||||||
|
|
||||||
|
TM0CNT_L.write(init_val);
|
||||||
|
TM0CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU1024));
|
||||||
|
TM1CNT_L.write(init_val);
|
||||||
|
TM1CNT_H.write(TIMER_SETTINGS.with_tick_rate(TimerTickRate::CPU64));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
DISPCNT.write(DisplayControlSetting::new().with_mode(DisplayMode::Mode3).with_bg2(true));
|
||||||
|
Mode3::clear_to(BLACK);
|
||||||
|
|
||||||
|
// Set the IRQ handler to use.
|
||||||
|
irq::set_irq_handler(irq_handler);
|
||||||
|
|
||||||
|
// Enable all interrupts that are set in the IE register.
|
||||||
|
IME.write(IrqEnableSetting::UseIE);
|
||||||
|
|
||||||
|
// Request that VBlank, HBlank and VCount will generate IRQs.
|
||||||
|
const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||||
|
.with_vblank_irq_enable(true)
|
||||||
|
.with_hblank_irq_enable(true)
|
||||||
|
.with_vcounter_irq_enable(true);
|
||||||
|
DISPSTAT.write(DISPLAY_SETTINGS);
|
||||||
|
|
||||||
|
// Start two timers with overflow IRQ generation.
|
||||||
|
start_timers();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
|
// The VBlank IRQ must be enabled at minimum, or else the CPU will halt
|
||||||
|
// at the call to vblank_interrupt_wait() as the VBlank IRQ will never
|
||||||
|
// be triggered.
|
||||||
|
let mut flags = IrqFlags::new().with_vblank(true);
|
||||||
|
|
||||||
|
// Enable interrupts based on key input.
|
||||||
|
if this_frame_keys.a() {
|
||||||
|
flags = flags.with_hblank(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.b() {
|
||||||
|
flags = flags.with_vcounter(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.l() {
|
||||||
|
flags = flags.with_timer0(true);
|
||||||
|
}
|
||||||
|
if this_frame_keys.r() {
|
||||||
|
flags = flags.with_timer1(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
IE.write(flags);
|
||||||
|
|
||||||
|
// Puts the CPU into low power mode until a VBlank IRQ is received. This
|
||||||
|
// will yield considerably better power efficiency as opposed to spin
|
||||||
|
// waiting.
|
||||||
|
gba::bios::vblank_interrupt_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut PIXEL: usize = 0;
|
||||||
|
|
||||||
|
fn write_pixel(color: Color) {
|
||||||
|
unsafe {
|
||||||
|
Mode3::write(PIXEL, 0, color);
|
||||||
|
PIXEL = (PIXEL + 1) % (Mode3::WIDTH * Mode3::HEIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn irq_handler(flags: IrqFlags) {
|
||||||
|
if flags.vblank() {
|
||||||
|
vblank_handler();
|
||||||
|
}
|
||||||
|
if flags.hblank() {
|
||||||
|
hblank_handler();
|
||||||
|
}
|
||||||
|
if flags.vcounter() {
|
||||||
|
vcounter_handler();
|
||||||
|
}
|
||||||
|
if flags.timer0() {
|
||||||
|
timer0_handler();
|
||||||
|
}
|
||||||
|
if flags.timer1() {
|
||||||
|
timer1_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vblank_handler() {
|
||||||
|
write_pixel(BLUE);
|
||||||
|
|
||||||
|
// When using `interrupt_wait()` or `vblank_interrupt_wait()`, IRQ handlers must acknowledge
|
||||||
|
// the IRQ on the BIOS Interrupt Flags register.
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_vblank(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hblank_handler() {
|
||||||
|
write_pixel(GREEN);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_hblank(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vcounter_handler() {
|
||||||
|
write_pixel(RED);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_vcounter(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timer0_handler() {
|
||||||
|
write_pixel(YELLOW);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_timer0(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timer1_handler() {
|
||||||
|
write_pixel(PINK);
|
||||||
|
|
||||||
|
BIOS_IF.write(BIOS_IF.read().with_timer1(true));
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
#![feature(start)]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
|
|
||||||
use gba::{
|
|
||||||
io::{
|
|
||||||
display::{spin_until_vblank, spin_until_vdraw, DisplayControlSetting, DisplayMode, DISPCNT},
|
|
||||||
keypad::read_key_input,
|
|
||||||
},
|
|
||||||
vram::bitmap::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(DisplayMode::Mode3).with_bg2(true);
|
|
||||||
DISPCNT.write(SETTING);
|
|
||||||
|
|
||||||
let mut px = Mode3::SCREEN_WIDTH / 2;
|
|
||||||
let mut py = Mode3::SCREEN_HEIGHT / 2;
|
|
||||||
let mut color = Color::from_rgb(31, 0, 0);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// read the input for this frame
|
|
||||||
let this_frame_keys = read_key_input();
|
|
||||||
|
|
||||||
// adjust game state and wait for vblank
|
|
||||||
px = px.wrapping_add(2 * this_frame_keys.column_direction() as usize);
|
|
||||||
py = py.wrapping_add(2 * this_frame_keys.row_direction() as usize);
|
|
||||||
spin_until_vblank();
|
|
||||||
|
|
||||||
// draw the new game and wait until the next frame starts.
|
|
||||||
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
|
||||||
if px >= Mode3::SCREEN_WIDTH || py >= Mode3::SCREEN_HEIGHT {
|
|
||||||
// out of bounds, reset the screen and position.
|
|
||||||
Mode3::clear_to(BLACK);
|
|
||||||
color = color.rotate_left(5);
|
|
||||||
px = Mode3::SCREEN_WIDTH / 2;
|
|
||||||
py = Mode3::SCREEN_HEIGHT / 2;
|
|
||||||
} else {
|
|
||||||
let color_here = Mode3::read_pixel(px, py);
|
|
||||||
if color_here != Some(BLACK) {
|
|
||||||
// crashed into our own line, reset the screen
|
|
||||||
Mode3::dma_clear_to(BLACK);
|
|
||||||
color = color.rotate_left(5);
|
|
||||||
} else {
|
|
||||||
// draw the new part of the line
|
|
||||||
Mode3::write_pixel(px, py, color);
|
|
||||||
Mode3::write_pixel(px, py + 1, color);
|
|
||||||
Mode3::write_pixel(px + 1, py, color);
|
|
||||||
Mode3::write_pixel(px + 1, py + 1, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spin_until_vdraw();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
color = "Never"
|
||||||
error_on_line_overflow = false
|
error_on_line_overflow = false
|
||||||
fn_args_density = "Compressed"
|
fn_args_density = "Compressed"
|
||||||
merge_imports = true
|
merge_imports = true
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
use_try_shorthand = true
|
use_try_shorthand = true
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
max_width = 150
|
max_width = 100
|
||||||
color = "Never"
|
use_small_heuristics = "Max"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Holds fundamental types/ops which the rest of the crate it built on.
|
//! Holds fundamental types/ops which the rest of the crate is built on.
|
||||||
|
//!
|
||||||
|
//! These things should probably, in time, be extracted out into their own
|
||||||
|
//! crate (or convert to using a robust existing crate).
|
||||||
|
|
||||||
pub mod fixed_point;
|
pub mod fixed_point;
|
||||||
//pub(crate) use self::fixed_point::*;
|
//pub(crate) use self::fixed_point::*;
|
||||||
|
|
||||||
pub mod volatile;
|
|
||||||
pub(crate) use self::volatile::*;
|
|
||||||
|
|
|
@ -19,10 +19,7 @@ pub struct Fx<T, F: Unsigned> {
|
||||||
impl<T, F: Unsigned> Fx<T, F> {
|
impl<T, F: Unsigned> Fx<T, F> {
|
||||||
/// Uses the provided value directly.
|
/// Uses the provided value directly.
|
||||||
pub fn from_raw(r: T) -> Self {
|
pub fn from_raw(r: T) -> Self {
|
||||||
Fx {
|
Fx { num: r, phantom: PhantomData }
|
||||||
num: r,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unwraps the inner value.
|
/// Unwraps the inner value.
|
||||||
|
@ -32,60 +29,42 @@ impl<T, F: Unsigned> Fx<T, F> {
|
||||||
|
|
||||||
/// Casts the base type, keeping the fractional bit quantity the same.
|
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||||
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||||
Fx {
|
Fx { num: op(self.num), phantom: PhantomData }
|
||||||
num: op(self.num),
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
|
impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn add(self, rhs: Fx<T, F>) -> Self::Output {
|
fn add(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
Fx {
|
Fx { num: self.num + rhs.num, phantom: PhantomData }
|
||||||
num: self.num + rhs.num,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
|
impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn sub(self, rhs: Fx<T, F>) -> Self::Output {
|
fn sub(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
Fx {
|
Fx { num: self.num - rhs.num, phantom: PhantomData }
|
||||||
num: self.num - rhs.num,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
|
impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn shl(self, rhs: u32) -> Self::Output {
|
fn shl(self, rhs: u32) -> Self::Output {
|
||||||
Fx {
|
Fx { num: self.num << rhs, phantom: PhantomData }
|
||||||
num: self.num << rhs,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
|
impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn shr(self, rhs: u32) -> Self::Output {
|
fn shr(self, rhs: u32) -> Self::Output {
|
||||||
Fx {
|
Fx { num: self.num >> rhs, phantom: PhantomData }
|
||||||
num: self.num >> rhs,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
|
impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn neg(self) -> Self::Output {
|
fn neg(self) -> Self::Output {
|
||||||
Fx {
|
Fx { num: -self.num, phantom: PhantomData }
|
||||||
num: -self.num,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,18 +73,12 @@ macro_rules! fixed_point_methods {
|
||||||
impl<F: Unsigned> Fx<$t, F> {
|
impl<F: Unsigned> Fx<$t, F> {
|
||||||
/// Gives the smallest positive non-zero value.
|
/// Gives the smallest positive non-zero value.
|
||||||
pub fn precision() -> Self {
|
pub fn precision() -> Self {
|
||||||
Fx {
|
Fx { num: 1, phantom: PhantomData }
|
||||||
num: 1,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a value with the integer part shifted into place.
|
/// Makes a value with the integer part shifted into place.
|
||||||
pub fn from_int_part(i: $t) -> Self {
|
pub fn from_int_part(i: $t) -> Self {
|
||||||
Fx {
|
Fx { num: i << F::U8, phantom: PhantomData }
|
||||||
num: i << F::U8,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the fractional bit quantity, keeping the base type the same.
|
/// Changes the fractional bit quantity, keeping the base type the same.
|
||||||
|
@ -140,21 +113,12 @@ macro_rules! fixed_point_signed_multiply {
|
||||||
let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
|
let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
|
||||||
if pre_shift < 0 {
|
if pre_shift < 0 {
|
||||||
if pre_shift == core::i32::MIN {
|
if pre_shift == core::i32::MIN {
|
||||||
Fx {
|
Fx { num: core::$t::MIN, phantom: PhantomData }
|
||||||
num: core::$t::MIN,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Fx {
|
Fx { num: (-((-pre_shift) >> F::U8)) as $t, phantom: PhantomData }
|
||||||
num: (-((-pre_shift) >> F::U8)) as $t,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Fx {
|
Fx { num: (pre_shift >> F::U8) as $t, phantom: PhantomData }
|
||||||
num: (pre_shift >> F::U8) as $t,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,10 +156,7 @@ macro_rules! fixed_point_signed_division {
|
||||||
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
Fx {
|
Fx { num: divide_result as $t, phantom: PhantomData }
|
||||||
num: divide_result as $t,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -213,10 +174,7 @@ macro_rules! fixed_point_unsigned_division {
|
||||||
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
Fx {
|
Fx { num: divide_result as $t, phantom: PhantomData }
|
||||||
num: divide_result as $t,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,298 +0,0 @@
|
||||||
//! Holds types for correct handling of volatile memory.
|
|
||||||
|
|
||||||
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
|
||||||
|
|
||||||
// TODO: striding block/iter
|
|
||||||
|
|
||||||
/// Abstracts the use of a volatile hardware address.
|
|
||||||
///
|
|
||||||
/// If you're trying to do anything other than abstract a volatile hardware
|
|
||||||
/// device then you _do not want to use this type_. Use one of the many other
|
|
||||||
/// smart pointer types.
|
|
||||||
///
|
|
||||||
/// A volatile address doesn't store a value in the normal way: It maps to some
|
|
||||||
/// real hardware _other than_ RAM, and that hardware might have any sort of
|
|
||||||
/// strange rules. The specifics of reading and writing depend on the hardware
|
|
||||||
/// being mapped. For example, a particular address might be read only (ignoring
|
|
||||||
/// writes), write only (returning some arbitrary value if you read it),
|
|
||||||
/// "normal" read write (where you read back what you wrote), or some complex
|
|
||||||
/// read-write situation where writes have an effect but you _don't_ read back
|
|
||||||
/// what you wrote.
|
|
||||||
///
|
|
||||||
/// As you imagine it can be very unsafe. The design of this type is set up so
|
|
||||||
/// that _creation_ is unsafe, and _use_ is safe. This gives an optimal
|
|
||||||
/// experience, since you'll use memory locations a lot more often than you try
|
|
||||||
/// to name them, on average.
|
|
||||||
///
|
|
||||||
/// `VolAddress` is _not_ a thread safe type. If your device is multi-threaded
|
|
||||||
/// then you must arrange for synchronization in some other way. A `VolAddress`
|
|
||||||
/// _can_ be used to share data between an interrupt running on a core and a
|
|
||||||
/// thread running on that core as long as all access of that location is
|
|
||||||
/// volatile (if you're using the `asm!` macro add the "volatile" option, if
|
|
||||||
/// you're linking in ASM with the linker that's effectively volatile since the
|
|
||||||
/// compiler doesn't get a chance to mess with it).
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// In order for values of this type to operate correctly they must follow quite
|
|
||||||
/// a few safety limits:
|
|
||||||
///
|
|
||||||
/// * The declared address must be non-null (it uses the `NonNull` optimization
|
|
||||||
/// for better iteration results). This shouldn't be a big problem, since
|
|
||||||
/// hardware can't really live at the null address.
|
|
||||||
/// * The declared address must be aligned for the declared type of `T`.
|
|
||||||
/// * The declared address must _always_ read as something that's a valid bit
|
|
||||||
/// pattern for `T`. Don't pick any enums or things like that if your hardware
|
|
||||||
/// doesn't back it up. If there's _any_ doubt at all, you must instead read
|
|
||||||
/// or write an unsigned int of the correct bit size and then parse the bits
|
|
||||||
/// by hand.
|
|
||||||
/// * The declared address must be a part of the address space that Rust's
|
|
||||||
/// allocator and/or stack frames will never use. If you're not sure, please
|
|
||||||
/// re-read the hardware specs of your device and its memory map until you
|
|
||||||
/// know.
|
|
||||||
///
|
|
||||||
/// The exact points of UB are if the address is ever 0, or if you ever `read`
|
|
||||||
/// or `write` with the invalid pointer. For example, if you offset to some
|
|
||||||
/// crazy (non-zero) value and then never use it that won't be an immediate
|
|
||||||
/// trigger of UB.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct VolAddress<T> {
|
|
||||||
address: NonZeroUsize,
|
|
||||||
marker: PhantomData<*mut T>,
|
|
||||||
}
|
|
||||||
// Note(Lokathor): We have to hand implement all these traits because if we use
|
|
||||||
// `derive` then they only get derived if the inner `T` has the trait. However,
|
|
||||||
// since we're acting like a pointer to `T`, the capability we offer isn't
|
|
||||||
// affected by whatever type `T` ends up being.
|
|
||||||
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> {
|
|
||||||
/// Constructs a new address.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must follow the standard safety rules as outlined in the type docs.
|
|
||||||
pub const unsafe fn new_unchecked(address: usize) -> Self {
|
|
||||||
VolAddress {
|
|
||||||
address: NonZeroUsize::new_unchecked(address),
|
|
||||||
marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Casts the type of `T` into type `Z`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must follow the standard safety rules as outlined in the type docs.
|
|
||||||
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
|
|
||||||
VolAddress {
|
|
||||||
address: self.address,
|
|
||||||
marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Offsets the address by `offset` slots (like `pointer::wrapping_offset`).
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must follow the standard safety rules as outlined in the type docs.
|
|
||||||
pub const unsafe fn offset(self, offset: isize) -> Self {
|
|
||||||
VolAddress {
|
|
||||||
address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::<T>())),
|
|
||||||
marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks that the current target type of this address is aligned at this
|
|
||||||
/// address value.
|
|
||||||
///
|
|
||||||
/// Technically it's a safety violation to even make a `VolAddress` that isn't
|
|
||||||
/// aligned. However, I know you're gonna try doing the bad thing, and it's
|
|
||||||
/// better to give you a chance to call `is_aligned` and potentially back off
|
|
||||||
/// from the operation or throw a `debug_assert!` or something instead of
|
|
||||||
/// triggering UB. Eventually this will be `const fn`, which will potentially
|
|
||||||
/// let you spot errors without even having to run your program.
|
|
||||||
pub const fn is_aligned(self) -> bool {
|
|
||||||
self.address.get() % core::mem::align_of::<T>() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Makes an iterator starting here across the given number of slots.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The normal safety rules must be correct for each address iterated over.
|
|
||||||
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
|
|
||||||
VolAddressIter { vol_address: self, slots }
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-const and never can be.
|
|
||||||
|
|
||||||
/// Reads a `Copy` value out of the address.
|
|
||||||
///
|
|
||||||
/// The `Copy` bound is actually supposed to be `!Drop`, but rust doesn't
|
|
||||||
/// allow negative trait bounds. If your type isn't `Copy` you can use the
|
|
||||||
/// `read_non_copy` fallback to do an unsafe read.
|
|
||||||
///
|
|
||||||
/// That said, I don't think that you legitimately have hardware that maps to
|
|
||||||
/// a Rust type with a `Drop` impl. If you do please tell me, I'm interested
|
|
||||||
/// to hear about it.
|
|
||||||
pub fn read(self) -> T
|
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
unsafe { (self.address.get() as *mut T).read_volatile() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a value out of the address with no trait bound.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This is _not_ a move, it forms a bit duplicate of the current address
|
|
||||||
/// value. If `T` has a `Drop` trait that does anything it is up to you to
|
|
||||||
/// ensure that repeated drops do not cause UB (such as a double free).
|
|
||||||
pub unsafe fn read_non_copy(self) -> T {
|
|
||||||
(self.address.get() as *mut T).read_volatile()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a value to the address.
|
|
||||||
///
|
|
||||||
/// Semantically, the value is moved into the `VolAddress` and then forgotten,
|
|
||||||
/// so if `T` has a `Drop` impl then that will never get executed. This is
|
|
||||||
/// "safe" under Rust's safety rules, but could cause something unintended
|
|
||||||
/// (eg: a memory leak).
|
|
||||||
pub fn write(self, val: T) {
|
|
||||||
unsafe { (self.address.get() as *mut T).write_volatile(val) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator that produces a series of `VolAddress` values.
|
|
||||||
#[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> {}
|
|
||||||
|
|
||||||
/// This type is like `VolAddress`, but for when you have a block of values all
|
|
||||||
/// in a row.
|
|
||||||
///
|
|
||||||
/// This is similar to the idea of an array or a slice, but called a "block"
|
|
||||||
/// because you could _also_ construct a `[VolAddress]`, and we want to avoid
|
|
||||||
/// any accidental confusion.
|
|
||||||
#[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> {
|
|
||||||
/// Constructs a new `VolAddressBlock`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The given `VolAddress` must be valid when offset by each of `0 .. slots`
|
|
||||||
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
|
|
||||||
VolAddressBlock { vol_address, slots }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gives an iterator over this block's slots.
|
|
||||||
pub const fn iter(self) -> VolAddressIter<T> {
|
|
||||||
VolAddressIter {
|
|
||||||
vol_address: self.vol_address,
|
|
||||||
slots: self.slots,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unchecked indexing into the block.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The slot given must be in bounds.
|
|
||||||
pub const unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
|
|
||||||
self.vol_address.offset(slot as isize)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked "indexing" style access of the block, giving either a `VolAddress` or a panic.
|
|
||||||
pub fn index(self, slot: usize) -> VolAddress<T> {
|
|
||||||
if slot < self.slots {
|
|
||||||
unsafe { self.vol_address.offset(slot as isize) }
|
|
||||||
} else {
|
|
||||||
panic!("Index Requested: {} >= Slot Count: {}", slot, self.slots)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked "getting" style access of the block, giving an Option value.
|
|
||||||
pub fn get(self, slot: usize) -> Option<VolAddress<T>> {
|
|
||||||
if slot < self.slots {
|
|
||||||
unsafe { Some(self.vol_address.offset(slot as isize)) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
19
src/bios.rs
19
src/bios.rs
|
@ -11,6 +11,7 @@
|
||||||
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use io::irq::IrqFlags;
|
||||||
|
|
||||||
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
||||||
//functions that never return must panic, the functions that return nothing
|
//functions that never return must panic, the functions that return nothing
|
||||||
|
@ -184,16 +185,16 @@ pub fn stop() {
|
||||||
/// * The first argument controls if you want to ignore all current flags and
|
/// * The first argument controls if you want to ignore all current flags and
|
||||||
/// wait until a new flag is set.
|
/// wait until a new flag is set.
|
||||||
/// * The second argument is what flags you're waiting on (same format as the
|
/// * The second argument is what flags you're waiting on (same format as the
|
||||||
/// IE/IF registers).
|
/// [`IE`](io::irq::IE)/[`IF`](io::irq::IF) registers).
|
||||||
///
|
///
|
||||||
/// If you're trying to handle more than one interrupt at once this has less
|
/// If you're trying to handle more than one interrupt at once this has less
|
||||||
/// overhead than calling `halt` over and over.
|
/// overhead than calling `halt` over and over.
|
||||||
///
|
///
|
||||||
/// When using this routing your interrupt handler MUST update the BIOS
|
/// When using this routing your interrupt handler MUST update the BIOS
|
||||||
/// Interrupt Flags `0x300_7FF8` in addition to the usual interrupt
|
/// Interrupt Flags at [`BIOS_IF`](io::irq::BIOS_IF) in addition to
|
||||||
/// acknowledgement.
|
/// the usual interrupt acknowledgement.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -203,19 +204,19 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x04"
|
asm!(/* ASM */ "swi 0x04"
|
||||||
:/* OUT */ // none
|
:/* OUT */ // none
|
||||||
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags.0)
|
||||||
:/* CLO */ // none
|
:/* CLO */ // none
|
||||||
:/* OPT */ "volatile"
|
:/* OPT */ "volatile"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO(lokathor): newtype this flag business.
|
|
||||||
|
|
||||||
/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait.
|
/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait.
|
||||||
///
|
///
|
||||||
/// This is as per `interrupt_wait(true, 1)` (aka "wait for a new vblank"). You
|
/// This is as per `interrupt_wait(true, IrqFlags::new().with_vblank(true))`
|
||||||
/// must follow the same guidelines that `interrupt_wait` outlines.
|
/// (aka "wait for a new vblank"). You must follow the same guidelines that
|
||||||
|
/// [`interrupt_wait`](interrupt_wait) outlines.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn vblank_interrupt_wait() {
|
pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
||||||
|
@ -225,7 +226,7 @@ pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* ASM */ "swi 0x04"
|
asm!(/* ASM */ "swi 0x05"
|
||||||
:/* OUT */ // none
|
:/* OUT */ // none
|
||||||
:/* INP */ // none
|
:/* INP */ // none
|
||||||
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod background;
|
||||||
pub mod color_blend;
|
pub mod color_blend;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
|
pub mod irq;
|
||||||
pub mod keypad;
|
pub mod keypad;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod timers;
|
pub mod timers;
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// BG0 Control. Read/Write. Display Mode 0/1 only.
|
/// BG0 Control. Read/Write. Display Mode 0/1 only.
|
||||||
pub const BG0CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0008) };
|
pub const BG0CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new(0x400_0008) };
|
||||||
/// BG1 Control. Read/Write. Display Mode 0/1 only.
|
/// BG1 Control. Read/Write. Display Mode 0/1 only.
|
||||||
pub const BG1CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000A) };
|
pub const BG1CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new(0x400_000A) };
|
||||||
/// BG2 Control. Read/Write. Display Mode 0/1/2 only.
|
/// BG2 Control. Read/Write. Display Mode 0/1/2 only.
|
||||||
pub const BG2CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000C) };
|
pub const BG2CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new(0x400_000C) };
|
||||||
/// BG3 Control. Read/Write. Display Mode 0/2 only.
|
/// BG3 Control. Read/Write. Display Mode 0/2 only.
|
||||||
pub const BG3CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new_unchecked(0x400_000E) };
|
pub const BG3CNT: VolAddress<BackgroundControlSetting> = unsafe { VolAddress::new(0x400_000E) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// Allows configuration of a background layer.
|
/// Allows configuration of a background layer.
|
||||||
|
@ -66,24 +66,24 @@ pub enum BGSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BG0 X-Offset. Write only. Text mode only. 9 bits.
|
/// BG0 X-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG0HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0010) };
|
pub const BG0HOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_0010) };
|
||||||
/// BG0 Y-Offset. Write only. Text mode only. 9 bits.
|
/// BG0 Y-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG0VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0012) };
|
pub const BG0VOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_0012) };
|
||||||
|
|
||||||
/// BG1 X-Offset. Write only. Text mode only. 9 bits.
|
/// BG1 X-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG1HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0014) };
|
pub const BG1HOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_0014) };
|
||||||
/// BG1 Y-Offset. Write only. Text mode only. 9 bits.
|
/// BG1 Y-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG1VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0016) };
|
pub const BG1VOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_0016) };
|
||||||
|
|
||||||
/// BG2 X-Offset. Write only. Text mode only. 9 bits.
|
/// BG2 X-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG2HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0018) };
|
pub const BG2HOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_0018) };
|
||||||
/// BG2 Y-Offset. Write only. Text mode only. 9 bits.
|
/// BG2 Y-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG2VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001A) };
|
pub const BG2VOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_001A) };
|
||||||
|
|
||||||
/// BG3 X-Offset. Write only. Text mode only. 9 bits.
|
/// BG3 X-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG3HOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001C) };
|
pub const BG3HOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_001C) };
|
||||||
/// BG3 Y-Offset. Write only. Text mode only. 9 bits.
|
/// BG3 Y-Offset. Write only. Text mode only. 9 bits.
|
||||||
pub const BG3VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_001E) };
|
pub const BG3VOFS: VolAddress<u16> = unsafe { VolAddress::new(0x400_001E) };
|
||||||
|
|
||||||
// TODO: affine backgrounds
|
// TODO: affine backgrounds
|
||||||
// BG2X_L
|
// BG2X_L
|
||||||
|
@ -100,14 +100,14 @@ pub const BG3VOFS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00
|
||||||
// BG3PD
|
// BG3PD
|
||||||
|
|
||||||
// TODO: windowing
|
// TODO: windowing
|
||||||
// pub const WIN0H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0040) };
|
// pub const WIN0H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0040) };
|
||||||
// pub const WIN1H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0042) };
|
// pub const WIN1H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0042) };
|
||||||
// pub const WIN0V: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0044) };
|
// pub const WIN0V: VolAddress<u16> = unsafe { VolAddress::new(0x400_0044) };
|
||||||
// pub const WIN1V: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0046) };
|
// pub const WIN1V: VolAddress<u16> = unsafe { VolAddress::new(0x400_0046) };
|
||||||
// pub const WININ: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0048) };
|
// pub const WININ: VolAddress<u16> = unsafe { VolAddress::new(0x400_0048) };
|
||||||
// pub const WINOUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_004A) };
|
// pub const WINOUT: VolAddress<u16> = unsafe { VolAddress::new(0x400_004A) };
|
||||||
|
|
||||||
// TODO: blending
|
// TODO: blending
|
||||||
// pub const BLDCNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0050) };
|
// pub const BLDCNT: VolAddress<u16> = unsafe { VolAddress::new(0x400_0050) };
|
||||||
// pub const BLDALPHA: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0052) };
|
// pub const BLDALPHA: VolAddress<u16> = unsafe { VolAddress::new(0x400_0052) };
|
||||||
// pub const BLDY: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0054) };
|
// pub const BLDY: VolAddress<u16> = unsafe { VolAddress::new(0x400_0054) };
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Color Special Effects Selection (R/W)
|
/// Color Special Effects Selection (R/W)
|
||||||
pub const BLDCNT: VolAddress<ColorEffectSetting> = unsafe { VolAddress::new_unchecked(0x400_0050) };
|
pub const BLDCNT: VolAddress<ColorEffectSetting> = unsafe { VolAddress::new(0x400_0050) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
ColorEffectSetting, u16
|
ColorEffectSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,17 +30,23 @@ impl ColorEffectSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype_enum! {
|
newtype_enum! {
|
||||||
|
/// TODO: docs
|
||||||
ColorSpecialEffect = u16,
|
ColorSpecialEffect = u16,
|
||||||
|
/// TODO: docs
|
||||||
None = 0,
|
None = 0,
|
||||||
|
/// TODO: docs
|
||||||
AlphaBlending = 1,
|
AlphaBlending = 1,
|
||||||
|
/// TODO: docs
|
||||||
BrightnessIncrease = 2,
|
BrightnessIncrease = 2,
|
||||||
|
/// TODO: docs
|
||||||
BrightnessDecrease = 3,
|
BrightnessDecrease = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alpha Blending Coefficients (R/W) (not W)
|
/// Alpha Blending Coefficients (R/W) (not W)
|
||||||
pub const BLDALPHA: VolAddress<AlphaBlendingSetting> = unsafe { VolAddress::new_unchecked(0x400_0052) };
|
pub const BLDALPHA: VolAddress<AlphaBlendingSetting> = unsafe { VolAddress::new(0x400_0052) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
AlphaBlendingSetting, u16
|
AlphaBlendingSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +59,10 @@ impl AlphaBlendingSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Brightness (Fade-In/Out) Coefficient (W) (not R/W)
|
/// Brightness (Fade-In/Out) Coefficient (W) (not R/W)
|
||||||
pub const BLDY: VolAddress<BrightnessSetting> = unsafe { VolAddress::new_unchecked(0x400_0054) };
|
pub const BLDY: VolAddress<BrightnessSetting> = unsafe { VolAddress::new(0x400_0054) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
BrightnessSetting, u32
|
BrightnessSetting, u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use super::*;
|
||||||
/// LCD Control. Read/Write.
|
/// LCD Control. Read/Write.
|
||||||
///
|
///
|
||||||
/// The "force vblank" bit is always set when your Rust code first executes.
|
/// The "force vblank" bit is always set when your Rust code first executes.
|
||||||
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new(0x400_0000) };
|
||||||
|
|
||||||
newtype!(
|
newtype!(
|
||||||
/// Setting for the display control register.
|
/// Setting for the display control register.
|
||||||
|
@ -96,7 +96,7 @@ pub fn display_control() -> DisplayControlSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display Status and IRQ Control. Read/Write.
|
/// Display Status and IRQ Control. Read/Write.
|
||||||
pub const DISPSTAT: VolAddress<DisplayStatusSetting> = unsafe { VolAddress::new_unchecked(0x400_0004) };
|
pub const DISPSTAT: VolAddress<DisplayStatusSetting> = unsafe { VolAddress::new(0x400_0004) };
|
||||||
|
|
||||||
newtype!(
|
newtype!(
|
||||||
/// A newtype over display status and interrupt control values.
|
/// A newtype over display status and interrupt control values.
|
||||||
|
@ -122,30 +122,13 @@ impl DisplayStatusSetting {
|
||||||
/// Gives the current scanline that the display controller is working on. If
|
/// 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
|
/// this is at or above the `VBLANK_SCANLINE` value then the display controller
|
||||||
/// is in a "vertical blank" period.
|
/// is in a "vertical blank" period.
|
||||||
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
pub const VCOUNT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0006) };
|
||||||
|
|
||||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||||
pub const VBLANK_SCANLINE: u16 = 160;
|
pub const VBLANK_SCANLINE: u16 = 160;
|
||||||
|
|
||||||
/// 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 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Global mosaic effect control. Write-only.
|
/// Global mosaic effect control. Write-only.
|
||||||
pub const MOSAIC: VolAddress<MosaicSetting> = unsafe { VolAddress::new_unchecked(0x400_004C) };
|
pub const MOSAIC: VolAddress<MosaicSetting> = unsafe { VolAddress::new(0x400_004C) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// Allows control of the Mosaic effect.
|
/// Allows control of the Mosaic effect.
|
||||||
|
|
|
@ -127,13 +127,13 @@ pub enum DMAStartTiming {
|
||||||
pub struct DMA0;
|
pub struct DMA0;
|
||||||
impl DMA0 {
|
impl DMA0 {
|
||||||
/// DMA 0 Source Address, read only.
|
/// DMA 0 Source Address, read only.
|
||||||
const DMA0SAD: VolAddress<*const u32> = unsafe { VolAddress::new_unchecked(0x400_00B0) };
|
const DMA0SAD: VolAddress<*const u32> = unsafe { VolAddress::new(0x400_00B0) };
|
||||||
/// DMA 0 Destination Address, read only.
|
/// DMA 0 Destination Address, read only.
|
||||||
const DMA0DAD: VolAddress<*mut u32> = unsafe { VolAddress::new_unchecked(0x400_00B4) };
|
const DMA0DAD: VolAddress<*mut u32> = unsafe { VolAddress::new(0x400_00B4) };
|
||||||
/// DMA 0 Word Count, read only.
|
/// DMA 0 Word Count, read only.
|
||||||
const DMA0CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00B8) };
|
const DMA0CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00B8) };
|
||||||
/// DMA 0 Control, read/write.
|
/// DMA 0 Control, read/write.
|
||||||
const DMA0CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new_unchecked(0x400_00BA) };
|
const DMA0CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new(0x400_00BA) };
|
||||||
|
|
||||||
/// Assigns the source register.
|
/// Assigns the source register.
|
||||||
///
|
///
|
||||||
|
@ -188,13 +188,13 @@ impl DMA0 {
|
||||||
pub struct DMA1;
|
pub struct DMA1;
|
||||||
impl DMA1 {
|
impl DMA1 {
|
||||||
/// DMA 1 Source Address, read only.
|
/// DMA 1 Source Address, read only.
|
||||||
const DMA1SAD: VolAddress<*const u32> = unsafe { VolAddress::new_unchecked(0x400_00BC) };
|
const DMA1SAD: VolAddress<*const u32> = unsafe { VolAddress::new(0x400_00BC) };
|
||||||
/// DMA 1 Destination Address, read only.
|
/// DMA 1 Destination Address, read only.
|
||||||
const DMA1DAD: VolAddress<*mut u32> = unsafe { VolAddress::new_unchecked(0x400_00C0) };
|
const DMA1DAD: VolAddress<*mut u32> = unsafe { VolAddress::new(0x400_00C0) };
|
||||||
/// DMA 1 Word Count, read only.
|
/// DMA 1 Word Count, read only.
|
||||||
const DMA1CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00C4) };
|
const DMA1CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00C4) };
|
||||||
/// DMA 1 Control, read/write.
|
/// DMA 1 Control, read/write.
|
||||||
const DMA1CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new_unchecked(0x400_00C6) };
|
const DMA1CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new(0x400_00C6) };
|
||||||
|
|
||||||
/// Assigns the source register.
|
/// Assigns the source register.
|
||||||
///
|
///
|
||||||
|
@ -249,13 +249,13 @@ impl DMA1 {
|
||||||
pub struct DMA2;
|
pub struct DMA2;
|
||||||
impl DMA2 {
|
impl DMA2 {
|
||||||
/// DMA 2 Source Address, read only.
|
/// DMA 2 Source Address, read only.
|
||||||
const DMA2SAD: VolAddress<*const u32> = unsafe { VolAddress::new_unchecked(0x400_00C8) };
|
const DMA2SAD: VolAddress<*const u32> = unsafe { VolAddress::new(0x400_00C8) };
|
||||||
/// DMA 2 Destination Address, read only.
|
/// DMA 2 Destination Address, read only.
|
||||||
const DMA2DAD: VolAddress<*mut u32> = unsafe { VolAddress::new_unchecked(0x400_00CC) };
|
const DMA2DAD: VolAddress<*mut u32> = unsafe { VolAddress::new(0x400_00CC) };
|
||||||
/// DMA 2 Word Count, read only.
|
/// DMA 2 Word Count, read only.
|
||||||
const DMA2CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00D0) };
|
const DMA2CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00D0) };
|
||||||
/// DMA 2 Control, read/write.
|
/// DMA 2 Control, read/write.
|
||||||
const DMA2CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new_unchecked(0x400_00D2) };
|
const DMA2CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new(0x400_00D2) };
|
||||||
|
|
||||||
/// Assigns the source register.
|
/// Assigns the source register.
|
||||||
///
|
///
|
||||||
|
@ -311,13 +311,13 @@ impl DMA2 {
|
||||||
pub struct DMA3;
|
pub struct DMA3;
|
||||||
impl DMA3 {
|
impl DMA3 {
|
||||||
/// DMA 3 Source Address, read only.
|
/// DMA 3 Source Address, read only.
|
||||||
const DMA3SAD: VolAddress<*const u32> = unsafe { VolAddress::new_unchecked(0x400_00D4) };
|
const DMA3SAD: VolAddress<*const u32> = unsafe { VolAddress::new(0x400_00D4) };
|
||||||
/// DMA 3 Destination Address, read only.
|
/// DMA 3 Destination Address, read only.
|
||||||
const DMA3DAD: VolAddress<*mut u32> = unsafe { VolAddress::new_unchecked(0x400_00D8) };
|
const DMA3DAD: VolAddress<*mut u32> = unsafe { VolAddress::new(0x400_00D8) };
|
||||||
/// DMA 3 Word Count, read only.
|
/// DMA 3 Word Count, read only.
|
||||||
const DMA3CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00DC) };
|
const DMA3CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00DC) };
|
||||||
/// DMA 3 Control, read/write.
|
/// DMA 3 Control, read/write.
|
||||||
const DMA3CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new_unchecked(0x400_00DE) };
|
const DMA3CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new(0x400_00DE) };
|
||||||
|
|
||||||
/// Assigns the source register.
|
/// Assigns the source register.
|
||||||
///
|
///
|
||||||
|
|
180
src/io/irq.rs
Normal file
180
src/io/irq.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
//! Module containing a wrapper for interrupt request (IRQ) handling.
|
||||||
|
//!
|
||||||
|
//! When an interrupt is executed, the CPU will be set to IRQ mode and code
|
||||||
|
//! execution will jump to the physical interrupt vector, located in BIOS. The
|
||||||
|
//! BIOS interrupt handler will then save several registers to the IRQ stack
|
||||||
|
//! pointer and execution will jump to the user interrupt handler starting at
|
||||||
|
//! `0x0300_7FFC`, in ARM mode.
|
||||||
|
//!
|
||||||
|
//! Currently, the user interrupt handler is defined in `crt0.s`. It is set up
|
||||||
|
//! to execute a user-specified interrupt handler after saving some registers.
|
||||||
|
//! This handler is declared as a static function pointer on the Rust side, and
|
||||||
|
//! can be set by using [`set_irq_handler`](irq::set_irq_handler).
|
||||||
|
//!
|
||||||
|
//! ## Notes
|
||||||
|
//! * The interrupt will only be triggered if [`IME`](irq::IME) is enabled, the
|
||||||
|
//! flag corresponding to the interrupt is enabled on the [`IE`](irq::IE)
|
||||||
|
//! register, and the "IRQ Enable" flag is set on the register related to the
|
||||||
|
//! interrupt, which varies. For example, to enable interrupts on VBlank you
|
||||||
|
//! would set the
|
||||||
|
//! [`vblank_irq_enable`](io::display::DisplayStatusSetting::vblank_irq_enable)
|
||||||
|
//! flag on the [`DISPSTAT`](io::display::DISPCNT) register.
|
||||||
|
//! * If you intend to use [`interrupt_wait`](bios::interrupt_wait) or
|
||||||
|
//! [`vblank_interrupt_wait`](bios::vblank_interrupt_wait) to wait for an
|
||||||
|
//! interrupt, your interrupt handler MUST update the BIOS Interrupt Flags at
|
||||||
|
//! [`BIOS_IF`](irq::BIOS_IF) in addition to the usual interrupt
|
||||||
|
//! acknowledgement (which is handled for you by the user interrupt handler).
|
||||||
|
//! This is done by setting the corresponding IRQ flag on
|
||||||
|
//! [`BIOS_IF`](irq::BIOS_IF) at the end of the interrupt handler.
|
||||||
|
//! * You can change the low-level details of the interrupt handler by editing
|
||||||
|
//! the `MainIrqHandler` routine in `crt0.s`. For example, you could declare
|
||||||
|
//! an external static variable in Rust holding a table of interrupt function
|
||||||
|
//! pointers and jump directly into one of them in assembly, without the need
|
||||||
|
//! to write the branching logic in Rust. However, note that the main
|
||||||
|
//! interrupt handler MUST acknowledge all interrupts received by setting
|
||||||
|
//! their corresponding bits to `1` in the [`IF`](irq::IF) register.
|
||||||
|
//! * If you wait on one or more interrupts, be sure at least one of them is
|
||||||
|
//! able to be triggered or the call to wait will never return.
|
||||||
|
//! * If you wait on multiple interrupts and those interrupts fire too quickly,
|
||||||
|
//! it is possible that the call to wait will never return as interrupts will
|
||||||
|
//! be constantly received before control is returned to the caller. This
|
||||||
|
//! usually only happens when waiting on multiple timer interrupts with very
|
||||||
|
//! fast overflow rates.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! extern "C" fn irq_handler(flags: IrqFlags) {
|
||||||
|
//! if flags.vblank() {
|
||||||
|
//! // Run drawing logic here.
|
||||||
|
//!
|
||||||
|
//! // Acknowledge the IRQ on the BIOS Interrupt Flags register.
|
||||||
|
//! BIOS_IF.write(BIOS_IF.read().with_vblank(true));
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main_loop() {
|
||||||
|
//! // Set the IRQ handler to use.
|
||||||
|
//! irq::set_irq_handler(irq_handler);
|
||||||
|
//!
|
||||||
|
//! // Handle only the VBlank interrupt.
|
||||||
|
//! const FLAGS: IrqFlags = IrqFlags::new().with_vblank(true);
|
||||||
|
//! IE.write(flags);
|
||||||
|
//!
|
||||||
|
//! // Enable all interrupts that are set in the IE register.
|
||||||
|
//! IME.write(IrqEnableSetting::UseIE);
|
||||||
|
//!
|
||||||
|
//! // Enable IRQ generation during VBlank.
|
||||||
|
//! const DISPLAY_SETTINGS: DisplayStatusSetting = DisplayStatusSetting::new()
|
||||||
|
//! .with_vblank_irq_enable(true);
|
||||||
|
//! DISPSTAT.write(DISPLAY_SETTINGS);
|
||||||
|
//!
|
||||||
|
//! loop {
|
||||||
|
//! // Sleep the CPU until a VBlank IRQ is generated.
|
||||||
|
//! bios::vblank_interrupt_wait();
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Implementation Details
|
||||||
|
//!
|
||||||
|
//! This is the setup the provided user interrupt handler in `crt0.s` will do
|
||||||
|
//! when an interrupt is received, in order. It is based on the _Recommended
|
||||||
|
//! User Interrupt Handling_ portion of the GBATEK reference.
|
||||||
|
//!
|
||||||
|
//! 1. Save the status of [`IME`](irq::IME).
|
||||||
|
//! 2. Save the IRQ stack pointer and change to system mode to use the user
|
||||||
|
//! stack instead of the IRQ stack (to prevent stack overflow).
|
||||||
|
//! 3. Disable interrupts by setting [`IME`](irq::IME) to 0, so other interrupts
|
||||||
|
//! will not preempt the main interrupt handler.
|
||||||
|
//! 4. Acknowledge all IRQs that occurred and were enabled in the
|
||||||
|
//! [`IE`](irq::IE) register by writing the bits to the [`IF`](irq::IF)
|
||||||
|
//! register.
|
||||||
|
//! 5. Save the user stack pointer, switch to Thumb mode and jump to the
|
||||||
|
//! user-specified interrupt handler. The IRQ flags that were set are passed
|
||||||
|
//! as an argument in `r0`.
|
||||||
|
//! 6. When the handler returns, restore the user stack pointer and switch back
|
||||||
|
//! to IRQ mode.
|
||||||
|
//! 7. Restore the IRQ stack pointer and the status of [`IME`](irq::IME).
|
||||||
|
//! 8. Return to the BIOS interrupt handler.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
/// A newtype over all interrupt flags.
|
||||||
|
IrqFlags, pub u16
|
||||||
|
);
|
||||||
|
|
||||||
|
impl IrqFlags {
|
||||||
|
phantom_fields! {
|
||||||
|
self.0: u16,
|
||||||
|
vblank: 0,
|
||||||
|
hblank: 1,
|
||||||
|
vcounter: 2,
|
||||||
|
timer0: 3,
|
||||||
|
timer1: 4,
|
||||||
|
timer2: 5,
|
||||||
|
timer3: 6,
|
||||||
|
serial: 7,
|
||||||
|
dma0: 8,
|
||||||
|
dma1: 9,
|
||||||
|
dma2: 10,
|
||||||
|
dma3: 11,
|
||||||
|
keypad: 12,
|
||||||
|
game_pak: 13,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt Enable Register. Read/Write.
|
||||||
|
///
|
||||||
|
/// After setting up interrupt handlers, set the flags on this register type corresponding to the
|
||||||
|
/// IRQs you want to handle.
|
||||||
|
pub const IE: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x400_0200) };
|
||||||
|
|
||||||
|
/// Interrupt Request Flags / IRQ Acknowledge. Read/Write.
|
||||||
|
///
|
||||||
|
/// The main user interrupt handler will acknowledge the interrupt that was set
|
||||||
|
/// by writing to this register, so there is usually no need to modify it.
|
||||||
|
/// However, if the main interrupt handler in `crt0.s` is changed, then the
|
||||||
|
/// handler must write a `1` bit to all bits that are enabled on this register
|
||||||
|
/// when it is called.
|
||||||
|
pub const IF: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x400_0200) };
|
||||||
|
|
||||||
|
newtype_enum! {
|
||||||
|
/// Setting to control whether interrupts are enabled.
|
||||||
|
IrqEnableSetting = u32,
|
||||||
|
/// Disable all interrupts.
|
||||||
|
DisableAll = 0,
|
||||||
|
/// Enable interrupts according to the flags set in the [`IE`](irq::IE) register.
|
||||||
|
UseIE = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt Master Enable Register. Read/Write.
|
||||||
|
pub const IME: VolAddress<IrqEnableSetting> = unsafe { VolAddress::new(0x400_0208) };
|
||||||
|
|
||||||
|
/// BIOS Interrupt Flags. Read/Write.
|
||||||
|
///
|
||||||
|
/// When using either [`interrupt_wait`](bios::interrupt_wait) or
|
||||||
|
/// [`vblank_interrupt_wait`](bios::vblank_interrupt_wait), the corresponding
|
||||||
|
/// interrupt handler MUST set the flag of the interrupt it has handled on this
|
||||||
|
/// register in addition to the usual interrupt acknowledgement.
|
||||||
|
pub const BIOS_IF: VolAddress<IrqFlags> = unsafe { VolAddress::new(0x0300_7FF8) };
|
||||||
|
|
||||||
|
/// A function pointer for use as an interrupt handler.
|
||||||
|
pub type IrqHandler = extern "C" fn(IrqFlags);
|
||||||
|
|
||||||
|
/// Sets the function to run when an interrupt is executed. The function will
|
||||||
|
/// receive the interrupts that were acknowledged by the main interrupt handler
|
||||||
|
/// as an argument.
|
||||||
|
pub fn set_irq_handler(handler: IrqHandler) {
|
||||||
|
unsafe {
|
||||||
|
__IRQ_HANDLER = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn default_handler(_flags: IrqFlags) {}
|
||||||
|
|
||||||
|
// Inner definition of the interrupt handler. It is referenced in `crt0.s`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[no_mangle]
|
||||||
|
static mut __IRQ_HANDLER: IrqHandler = default_handler;
|
|
@ -8,7 +8,7 @@ use super::*;
|
||||||
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
||||||
/// to work with) then call `read_key_input()` rather than reading this register
|
/// to work with) then call `read_key_input()` rather than reading this register
|
||||||
/// directly. It will perform the necessary bit flip operation for you.
|
/// directly. It will perform the necessary bit flip operation for you.
|
||||||
pub const KEYINPUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0130) };
|
pub const KEYINPUT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0130) };
|
||||||
|
|
||||||
/// A "tribool" value helps us interpret the arrow pad.
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -50,9 +50,10 @@ impl KeyInput {
|
||||||
KeyInput(self.0 ^ other.0)
|
KeyInput(self.0 ^ other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
/// Right/left tribool.
|
||||||
/// value (right).
|
///
|
||||||
pub fn column_direction(self) -> TriBool {
|
/// Right is Plus and Left is Minus
|
||||||
|
pub fn x_tribool(self) -> TriBool {
|
||||||
if self.right() {
|
if self.right() {
|
||||||
TriBool::Plus
|
TriBool::Plus
|
||||||
} else if self.left() {
|
} else if self.left() {
|
||||||
|
@ -62,9 +63,10 @@ impl KeyInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the arrow pad value as a tribool, with Plus being increased row
|
/// Up/down tribool.
|
||||||
/// value (down).
|
///
|
||||||
pub fn row_direction(self) -> TriBool {
|
/// Down is Plus and Up is Minus
|
||||||
|
pub fn y_tribool(self) -> TriBool {
|
||||||
if self.down() {
|
if self.down() {
|
||||||
TriBool::Plus
|
TriBool::Plus
|
||||||
} else if self.up() {
|
} else if self.up() {
|
||||||
|
@ -86,7 +88,7 @@ pub fn read_key_input() -> KeyInput {
|
||||||
/// Use this to configure when a keypad interrupt happens.
|
/// Use this to configure when a keypad interrupt happens.
|
||||||
///
|
///
|
||||||
/// See the `KeyInterruptSetting` type for more.
|
/// See the `KeyInterruptSetting` type for more.
|
||||||
pub const KEYCNT: VolAddress<KeyInterruptSetting> = unsafe { VolAddress::new_unchecked(0x400_0132) };
|
pub const KEYCNT: VolAddress<KeyInterruptSetting> = unsafe { VolAddress::new(0x400_0132) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// Allows configuration of when a keypad interrupt fires.
|
/// Allows configuration of when a keypad interrupt fires.
|
||||||
|
@ -101,8 +103,9 @@ newtype! {
|
||||||
/// of the interrupt firing.
|
/// of the interrupt firing.
|
||||||
///
|
///
|
||||||
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
||||||
/// fire. You must still set the `IME` to have interrupts at all, and you must
|
/// fire. You must still set the [`IME`](irq::IME) to have interrupts at all,
|
||||||
/// further set `IE` for keypad interrupts to be possible.
|
/// and you must further set [`IE`](irq::IE) for keypad interrupts to be
|
||||||
|
/// possible.
|
||||||
KeyInterruptSetting, u16
|
KeyInterruptSetting, u16
|
||||||
}
|
}
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
///! Module for sound registers.
|
//! Module for sound registers.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
//TODO within these "read/write" registers only some bits are actually read/write!
|
//TODO within these "read/write" registers only some bits are actually read/write!
|
||||||
|
|
||||||
/// Sound Channel 1 Sweep Register (`NR10`). Read/Write.
|
/// Sound Channel 1 Sweep Register (`NR10`). Read/Write.
|
||||||
pub const SOUND1CNT_L: VolAddress<SweepRegisterSetting> = unsafe { VolAddress::new_unchecked(0x400_0060) };
|
pub const SOUND1CNT_L: VolAddress<SweepRegisterSetting> = unsafe { VolAddress::new(0x400_0060) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
SweepRegisterSetting, u16
|
SweepRegisterSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +22,10 @@ impl SweepRegisterSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 1 Duty/Length/Envelope (`NR11`, `NR12`). Read/Write.
|
/// Sound Channel 1 Duty/Length/Envelope (`NR11`, `NR12`). Read/Write.
|
||||||
pub const SOUND1CNT_H: VolAddress<DutyLenEnvelopeSetting> = unsafe { VolAddress::new_unchecked(0x400_0062) };
|
pub const SOUND1CNT_H: VolAddress<DutyLenEnvelopeSetting> = unsafe { VolAddress::new(0x400_0062) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
DutyLenEnvelopeSetting, u16
|
DutyLenEnvelopeSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +41,10 @@ impl DutyLenEnvelopeSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 1 Frequency/Control (`NR13`, `NR14`). Read/Write.
|
/// Sound Channel 1 Frequency/Control (`NR13`, `NR14`). Read/Write.
|
||||||
pub const SOUND1CNT_X: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0064) };
|
pub const SOUND1CNT_X: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new(0x400_0064) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
FrequencyControlSetting, u32 // TODO: u16 or u32?
|
FrequencyControlSetting, u32 // TODO: u16 or u32?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +58,17 @@ impl FrequencyControlSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 2 Channel 2 Duty/Length/Envelope (`NR21`, `NR22`). Read/Write.
|
/// Sound Channel 2 Channel 2 Duty/Length/Envelope (`NR21`, `NR22`). Read/Write.
|
||||||
pub const SOUND2CNT_L: VolAddress<DutyLenEnvelopeSetting> = unsafe { VolAddress::new_unchecked(0x400_0068) };
|
pub const SOUND2CNT_L: VolAddress<DutyLenEnvelopeSetting> = unsafe { VolAddress::new(0x400_0068) };
|
||||||
|
|
||||||
/// Sound Channel 2 Frequency/Control (`NR23`, `NR24`). Read/Write.
|
/// Sound Channel 2 Frequency/Control (`NR23`, `NR24`). Read/Write.
|
||||||
pub const SOUND2CNT_H: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new_unchecked(0x400_006C) };
|
pub const SOUND2CNT_H: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new(0x400_006C) };
|
||||||
|
|
||||||
/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write.
|
/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write.
|
||||||
pub const SOUND3CNT_L: VolAddress<StopWaveRAMSelectSetting> = unsafe { VolAddress::new_unchecked(0x400_0070) };
|
pub const SOUND3CNT_L: VolAddress<StopWaveRAMSelectSetting> =
|
||||||
|
unsafe { VolAddress::new(0x400_0070) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
StopWaveRAMSelectSetting, u16
|
StopWaveRAMSelectSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +82,10 @@ impl StopWaveRAMSelectSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 3 Length/Volume (`NR23`, `NR24`). Read/Write.
|
/// Sound Channel 3 Length/Volume (`NR23`, `NR24`). Read/Write.
|
||||||
pub const SOUND3CNT_H: VolAddress<LengthVolumeSetting> = unsafe { VolAddress::new_unchecked(0x400_0072) };
|
pub const SOUND3CNT_H: VolAddress<LengthVolumeSetting> = unsafe { VolAddress::new(0x400_0072) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
LengthVolumeSetting, u16
|
LengthVolumeSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,29 +99,30 @@ impl LengthVolumeSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 3 Frequency/Control (`NR33`, `NR34`). Read/Write.
|
/// Sound Channel 3 Frequency/Control (`NR33`, `NR34`). Read/Write.
|
||||||
pub const SOUND3CNT_X: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0074) };
|
pub const SOUND3CNT_X: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new(0x400_0074) };
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM0_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0090) };
|
pub const WAVE_RAM0_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0090) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM0_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0092) };
|
pub const WAVE_RAM0_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0092) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM1_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0094) };
|
pub const WAVE_RAM1_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0094) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM1_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0096) };
|
pub const WAVE_RAM1_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_0096) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM2_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0098) };
|
pub const WAVE_RAM2_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0098) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM2_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_009A) };
|
pub const WAVE_RAM2_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_009A) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM3_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_009C) };
|
pub const WAVE_RAM3_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_009C) };
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
/// Channel 3 Wave Pattern RAM (W/R)
|
||||||
pub const WAVE_RAM3_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_009E) };
|
pub const WAVE_RAM3_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_009E) };
|
||||||
|
|
||||||
/// Sound Channel 4 Length/Envelope (`NR41`, `NR42`). Read/Write.
|
/// Sound Channel 4 Length/Envelope (`NR41`, `NR42`). Read/Write.
|
||||||
pub const SOUND4CNT_L: VolAddress<LengthEnvelopeSetting> = unsafe { VolAddress::new_unchecked(0x400_0078) };
|
pub const SOUND4CNT_L: VolAddress<LengthEnvelopeSetting> = unsafe { VolAddress::new(0x400_0078) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
LengthEnvelopeSetting, u32 // TODO: is this u32?
|
LengthEnvelopeSetting, u32 // TODO: is this u32?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,9 +137,10 @@ impl LengthEnvelopeSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound Channel 4 Frequency/Control (`NR43`, `NR44`). Read/Write.
|
/// Sound Channel 4 Frequency/Control (`NR43`, `NR44`). Read/Write.
|
||||||
pub const SOUND4CNT_H: VolAddress<NoiseFrequencySetting> = unsafe { VolAddress::new_unchecked(0x400_007C) };
|
pub const SOUND4CNT_H: VolAddress<NoiseFrequencySetting> = unsafe { VolAddress::new(0x400_007C) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
NoiseFrequencySetting, u32 // TODO: is this u32?
|
NoiseFrequencySetting, u32 // TODO: is this u32?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,18 +158,20 @@ impl NoiseFrequencySetting {
|
||||||
// TODO: unify FIFO as
|
// TODO: unify FIFO as
|
||||||
|
|
||||||
/// Sound A FIFO, Data 0 and Data 1 (W)
|
/// Sound A FIFO, Data 0 and Data 1 (W)
|
||||||
pub const FIFO_A_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00A0) };
|
pub const FIFO_A_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A0) };
|
||||||
/// Sound A FIFO, Data 2 and Data 3 (W)
|
/// Sound A FIFO, Data 2 and Data 3 (W)
|
||||||
pub const FIFO_A_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00A2) };
|
pub const FIFO_A_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A2) };
|
||||||
/// Sound B FIFO, Data 0 and Data 1 (W)
|
/// Sound B FIFO, Data 0 and Data 1 (W)
|
||||||
pub const FIFO_B_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00A4) };
|
pub const FIFO_B_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A4) };
|
||||||
/// Sound B FIFO, Data 2 and Data 3 (W)
|
/// Sound B FIFO, Data 2 and Data 3 (W)
|
||||||
pub const FIFO_B_H: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00A6) };
|
pub const FIFO_B_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A6) };
|
||||||
|
|
||||||
/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write.
|
/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write.
|
||||||
pub const SOUNDCNT_L: VolAddress<NonWaveVolumeEnableSetting> = unsafe { VolAddress::new_unchecked(0x400_0080) };
|
pub const SOUNDCNT_L: VolAddress<NonWaveVolumeEnableSetting> =
|
||||||
|
unsafe { VolAddress::new(0x400_0080) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
NonWaveVolumeEnableSetting, u16
|
NonWaveVolumeEnableSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,9 +186,10 @@ impl NonWaveVolumeEnableSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DMA Sound Control/Mixing. Read/Write.
|
/// DMA Sound Control/Mixing. Read/Write.
|
||||||
pub const SOUNDCNT_H: VolAddress<WaveVolumeEnableSetting> = unsafe { VolAddress::new_unchecked(0x400_0082) };
|
pub const SOUNDCNT_H: VolAddress<WaveVolumeEnableSetting> = unsafe { VolAddress::new(0x400_0082) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
WaveVolumeEnableSetting, u16
|
WaveVolumeEnableSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,16 +211,21 @@ impl WaveVolumeEnableSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype_enum! {
|
newtype_enum! {
|
||||||
|
/// TODO: docs
|
||||||
NumberSoundVolume = u16,
|
NumberSoundVolume = u16,
|
||||||
|
/// TODO: docs
|
||||||
Quarter = 0,
|
Quarter = 0,
|
||||||
|
/// TODO: docs
|
||||||
Half = 1,
|
Half = 1,
|
||||||
|
/// TODO: docs
|
||||||
Full = 2,
|
Full = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound on/off (`NR52`). Read/Write.
|
/// Sound on/off (`NR52`). Read/Write.
|
||||||
pub const SOUNDCNT_X: VolAddress<SoundMasterSetting> = unsafe { VolAddress::new_unchecked(0x400_0084) };
|
pub const SOUNDCNT_X: VolAddress<SoundMasterSetting> = unsafe { VolAddress::new(0x400_0084) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
SoundMasterSetting, u16
|
SoundMasterSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,9 +241,10 @@ impl SoundMasterSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sound on/off (`NR52`). Read/Write.
|
/// Sound on/off (`NR52`). Read/Write.
|
||||||
pub const SOUNDBIAS: VolAddress<SoundPWMSetting> = unsafe { VolAddress::new_unchecked(0x400_0088) };
|
pub const SOUNDBIAS: VolAddress<SoundPWMSetting> = unsafe { VolAddress::new(0x400_0088) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
SoundPWMSetting, u16
|
SoundPWMSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,28 +28,28 @@ use super::*;
|
||||||
// TODO: striding blocks?
|
// TODO: striding blocks?
|
||||||
|
|
||||||
/// Timer 0 Counter/Reload. Special (see module).
|
/// Timer 0 Counter/Reload. Special (see module).
|
||||||
pub const TM0CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0100) };
|
pub const TM0CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0100) };
|
||||||
|
|
||||||
/// Timer 1 Counter/Reload. Special (see module).
|
/// Timer 1 Counter/Reload. Special (see module).
|
||||||
pub const TM1CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0104) };
|
pub const TM1CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0104) };
|
||||||
|
|
||||||
/// Timer 2 Counter/Reload. Special (see module).
|
/// Timer 2 Counter/Reload. Special (see module).
|
||||||
pub const TM2CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0108) };
|
pub const TM2CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_0108) };
|
||||||
|
|
||||||
/// Timer 3 Counter/Reload. Special (see module).
|
/// Timer 3 Counter/Reload. Special (see module).
|
||||||
pub const TM3CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_010C) };
|
pub const TM3CNT_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_010C) };
|
||||||
|
|
||||||
/// Timer 0 Control. Read/Write.
|
/// Timer 0 Control. Read/Write.
|
||||||
pub const TM0CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0102) };
|
pub const TM0CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new(0x400_0102) };
|
||||||
|
|
||||||
/// Timer 1 Control. Read/Write.
|
/// Timer 1 Control. Read/Write.
|
||||||
pub const TM1CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0106) };
|
pub const TM1CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new(0x400_0106) };
|
||||||
|
|
||||||
/// Timer 2 Control. Read/Write.
|
/// Timer 2 Control. Read/Write.
|
||||||
pub const TM2CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_010A) };
|
pub const TM2CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new(0x400_010A) };
|
||||||
|
|
||||||
/// Timer 3 Control. Read/Write.
|
/// Timer 3 Control. Read/Write.
|
||||||
pub const TM3CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new_unchecked(0x400_010E) };
|
pub const TM3CNT_H: VolAddress<TimerControlSetting> = unsafe { VolAddress::new(0x400_010E) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// Allows control of a timer unit.
|
/// Allows control of a timer unit.
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Window 0 Horizontal Dimensions (W)
|
/// Window 0 Horizontal Dimensions (W)
|
||||||
pub const WIN0H: VolAddress<HorizontalWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_0040) };
|
pub const WIN0H: VolAddress<HorizontalWindowSetting> = unsafe { VolAddress::new(0x400_0040) };
|
||||||
|
|
||||||
/// Window 1 Horizontal Dimensions (W)
|
/// Window 1 Horizontal Dimensions (W)
|
||||||
pub const WIN1H: VolAddress<HorizontalWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_0042) };
|
pub const WIN1H: VolAddress<HorizontalWindowSetting> = unsafe { VolAddress::new(0x400_0042) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
HorizontalWindowSetting, u16
|
HorizontalWindowSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +22,13 @@ impl HorizontalWindowSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Window 0 Vertical Dimensions (W)
|
/// Window 0 Vertical Dimensions (W)
|
||||||
pub const WIN0V: VolAddress<VerticalWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_0044) };
|
pub const WIN0V: VolAddress<VerticalWindowSetting> = unsafe { VolAddress::new(0x400_0044) };
|
||||||
|
|
||||||
/// Window 1 Vertical Dimensions (W)
|
/// Window 1 Vertical Dimensions (W)
|
||||||
pub const WIN1V: VolAddress<VerticalWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_0046) };
|
pub const WIN1V: VolAddress<VerticalWindowSetting> = unsafe { VolAddress::new(0x400_0046) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
VerticalWindowSetting, u16
|
VerticalWindowSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,9 +41,10 @@ impl VerticalWindowSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control of Inside of Window(s) (R/W)
|
/// Control of Inside of Window(s) (R/W)
|
||||||
pub const WININ: VolAddress<InsideWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_0048) };
|
pub const WININ: VolAddress<InsideWindowSetting> = unsafe { VolAddress::new(0x400_0048) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
InsideWindowSetting, u16
|
InsideWindowSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +67,10 @@ impl InsideWindowSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control of Outside of Windows & Inside of OBJ Window (R/W)
|
/// Control of Outside of Windows & Inside of OBJ Window (R/W)
|
||||||
pub const WINOUT: VolAddress<OutsideWindowSetting> = unsafe { VolAddress::new_unchecked(0x400_004A) };
|
pub const WINOUT: VolAddress<OutsideWindowSetting> = unsafe { VolAddress::new(0x400_004A) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
|
/// TODO: docs
|
||||||
OutsideWindowSetting, u16
|
OutsideWindowSetting, u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
102
src/lib.rs
102
src/lib.rs
|
@ -3,7 +3,7 @@
|
||||||
#![feature(cfg_target_vendor)]
|
#![feature(cfg_target_vendor)]
|
||||||
#![allow(clippy::cast_lossless)]
|
#![allow(clippy::cast_lossless)]
|
||||||
#![deny(clippy::float_arithmetic)]
|
#![deny(clippy::float_arithmetic)]
|
||||||
//#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! This crate helps you write GBA ROMs.
|
//! This crate helps you write GBA ROMs.
|
||||||
//!
|
//!
|
||||||
|
@ -19,93 +19,11 @@
|
||||||
//! do, it's a giant bag of Undefined Behavior.
|
//! do, it's a giant bag of Undefined Behavior.
|
||||||
|
|
||||||
pub(crate) use gba_proc_macro::phantom_fields;
|
pub(crate) use gba_proc_macro::phantom_fields;
|
||||||
|
pub(crate) use voladdress::{read_only::ROVolAddress, VolAddress, VolBlock};
|
||||||
|
|
||||||
/// Assists in defining a newtype wrapper over some base type.
|
pub mod macros;
|
||||||
///
|
|
||||||
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
|
||||||
/// of your docs and derives in front of your newtype in the same way you would
|
|
||||||
/// for a normal struct. Then the inner type to be wrapped it name.
|
|
||||||
///
|
|
||||||
/// The macro _assumes_ that you'll be using it to wrap numeric types and that
|
|
||||||
/// it's safe to have a `0` value, so it automatically provides a `const fn`
|
|
||||||
/// method for `new` that just wraps `0`. Also, it derives Debug, Clone, Copy,
|
|
||||||
/// Default, PartialEq, and Eq. If all this is not desired you can add `, no
|
|
||||||
/// frills` to the invocation.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```
|
|
||||||
/// newtype! {
|
|
||||||
/// /// Records a particular key press combination.
|
|
||||||
/// KeyInput, u16
|
|
||||||
/// }
|
|
||||||
/// newtype! {
|
|
||||||
/// /// You can't derive most stuff above array size 32, so we add
|
|
||||||
/// /// the `, no frills` modifier to this one.
|
|
||||||
/// BigArray, [u8; 200], no frills
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! newtype {
|
|
||||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
|
|
||||||
$(#[$attr])*
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $new_name($v $old_name);
|
|
||||||
impl $new_name {
|
|
||||||
/// A `const` "zero value" constructor
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
$new_name(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
|
|
||||||
$(#[$attr])*
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $new_name($v $old_name);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assists in defining a newtype that's an enum.
|
|
||||||
///
|
|
||||||
/// First give `NewType = OldType,`, then define the tags and their explicit
|
|
||||||
/// values with zero or more entries of `TagName = base_value,`. In both cases
|
|
||||||
/// you can place doc comments or other attributes directly on to the type
|
|
||||||
/// declaration or the tag declaration.
|
|
||||||
///
|
|
||||||
/// The generated enum will get an appropriate `repr` attribute as well as Debug, Clone, Copy,
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```
|
|
||||||
/// newtype_enum! {
|
|
||||||
/// /// The Foo
|
|
||||||
/// Foo = u16,
|
|
||||||
/// /// The Bar
|
|
||||||
/// Bar = 0,
|
|
||||||
/// /// The Zap
|
|
||||||
/// Zap = 1,
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! newtype_enum {
|
|
||||||
(
|
|
||||||
$(#[$struct_attr:meta])*
|
|
||||||
$new_name:ident = $old_name:ident,
|
|
||||||
$($(#[$tag_attr:meta])* $tag_name:ident = $base_value:expr,)*
|
|
||||||
) => {
|
|
||||||
$(#[$struct_attr])*
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr($old_name)]
|
|
||||||
pub enum $new_name {
|
|
||||||
$(
|
|
||||||
$(#[$tag_attr])*
|
|
||||||
$tag_name = $base_value,
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub(crate) use self::base::*;
|
|
||||||
|
|
||||||
pub mod bios;
|
pub mod bios;
|
||||||
|
|
||||||
|
@ -133,13 +51,16 @@ extern "C" {
|
||||||
/// Memory in IWRAM _before_ this location is not free to use, you'll trash
|
/// Memory in IWRAM _before_ this location is not free to use, you'll trash
|
||||||
/// your globals and stuff. Memory here or after is freely available for use
|
/// your globals and stuff. Memory here or after is freely available for use
|
||||||
/// (careful that you don't run into your own stack of course).
|
/// (careful that you don't run into your own stack of course).
|
||||||
static __bss_end: u8;
|
///
|
||||||
|
/// The actual value is unimportant, you just want to use the _address of_
|
||||||
|
/// this location as the start of your IWRAM usage.
|
||||||
|
pub static __bss_end: u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
/// A color on the GBA is an RGB 5.5.5 within a `u16`
|
||||||
#[derive(PartialOrd, Ord, Hash)]
|
#[derive(PartialOrd, Ord, Hash)]
|
||||||
Color, u16
|
Color, pub u16
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
|
@ -150,13 +71,6 @@ impl Color {
|
||||||
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
||||||
Color(b << 10 | g << 5 | r)
|
Color(b << 10 | g << 5 | r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does a left rotate of the bits.
|
|
||||||
///
|
|
||||||
/// This has no particular meaning but is a wild way to cycle colors.
|
|
||||||
pub const fn rotate_left(self, n: u32) -> Color {
|
|
||||||
Color(self.0.rotate_left(n))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
203
src/macros.rs
Normal file
203
src/macros.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
//! Contains the macros for the crate.
|
||||||
|
//!
|
||||||
|
//! Because (unlike everything else in Rust) a macro has to be declared before
|
||||||
|
//! use, we place them in their own module and then declare that module at the
|
||||||
|
//! start of the crate.
|
||||||
|
|
||||||
|
/// Assists in defining a newtype wrapper over some base type.
|
||||||
|
///
|
||||||
|
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
||||||
|
/// of your docs and derives in front of your newtype in the same way you would
|
||||||
|
/// for a normal struct. Then the inner type to be wrapped it name.
|
||||||
|
///
|
||||||
|
/// The macro _assumes_ that you'll be using it to wrap numeric types and that
|
||||||
|
/// it's safe to have a `0` value, so it automatically provides a `const fn`
|
||||||
|
/// method for `new` that just wraps `0`. Also, it derives Debug, Clone, Copy,
|
||||||
|
/// Default, PartialEq, and Eq. If all this is not desired you can add `, no
|
||||||
|
/// frills` to the invocation.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// newtype! {
|
||||||
|
/// /// Records a particular key press combination.
|
||||||
|
/// KeyInput, u16
|
||||||
|
/// }
|
||||||
|
/// newtype! {
|
||||||
|
/// /// You can't derive most stuff above array size 32, so we add
|
||||||
|
/// /// the `, no frills` modifier to this one.
|
||||||
|
/// BigArray, [u8; 200], no frills
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! newtype {
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($v $old_name);
|
||||||
|
impl $new_name {
|
||||||
|
/// A `const` "zero value" constructor
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
$new_name(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($v $old_name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assists in defining a newtype that's an enum.
|
||||||
|
///
|
||||||
|
/// First give `NewType = OldType,`, then define the tags and their explicit
|
||||||
|
/// values with zero or more entries of `TagName = base_value,`. In both cases
|
||||||
|
/// you can place doc comments or other attributes directly on to the type
|
||||||
|
/// declaration or the tag declaration.
|
||||||
|
///
|
||||||
|
/// The generated enum will get an appropriate `repr` attribute as well as
|
||||||
|
/// Debug, Clone, Copy, PartialEq, and Eq
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// newtype_enum! {
|
||||||
|
/// /// The Foo
|
||||||
|
/// Foo = u16,
|
||||||
|
/// /// The Bar
|
||||||
|
/// Bar = 0,
|
||||||
|
/// /// The Zap
|
||||||
|
/// Zap = 1,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! newtype_enum {
|
||||||
|
(
|
||||||
|
$(#[$struct_attr:meta])*
|
||||||
|
$new_name:ident = $old_name:ident,
|
||||||
|
$($(#[$tag_attr:meta])* $tag_name:ident = $base_value:expr,)*
|
||||||
|
) => {
|
||||||
|
$(#[$struct_attr])*
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr($old_name)]
|
||||||
|
pub enum $new_name {
|
||||||
|
$(
|
||||||
|
$(#[$tag_attr])*
|
||||||
|
$tag_name = $base_value,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers a fatal message to the mGBA output, halting emulation.
|
||||||
|
///
|
||||||
|
/// This works basically like `println`. mGBA is a C program and all, so you
|
||||||
|
/// should only attempt to print non-null ASCII values through this. There's
|
||||||
|
/// also a maximum length of 255 bytes per message.
|
||||||
|
///
|
||||||
|
/// This has no effect if you're not using mGBA.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! fatal {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
use $crate::mgba::{MGBADebug, MGBADebugLevel};
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(mut mgba) = MGBADebug::new() {
|
||||||
|
let _ = write!(mgba, $($arg)*);
|
||||||
|
mgba.send(MGBADebugLevel::Fatal);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers an error message to the mGBA output.
|
||||||
|
///
|
||||||
|
/// This works basically like `println`. mGBA is a C program and all, so you
|
||||||
|
/// should only attempt to print non-null ASCII values through this. There's
|
||||||
|
/// also a maximum length of 255 bytes per message.
|
||||||
|
///
|
||||||
|
/// This has no effect if you're not using mGBA.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! error {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
use $crate::mgba::{MGBADebug, MGBADebugLevel};
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(mut mgba) = MGBADebug::new() {
|
||||||
|
let _ = write!(mgba, $($arg)*);
|
||||||
|
mgba.send(MGBADebugLevel::Error);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers a warning message to the mGBA output.
|
||||||
|
///
|
||||||
|
/// This works basically like `println`. mGBA is a C program and all, so you
|
||||||
|
/// should only attempt to print non-null ASCII values through this. There's
|
||||||
|
/// also a maximum length of 255 bytes per message.
|
||||||
|
///
|
||||||
|
/// This has no effect if you're not using mGBA.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! warn {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
use $crate::mgba::{MGBADebug, MGBADebugLevel};
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(mut mgba) = MGBADebug::new() {
|
||||||
|
let _ = write!(mgba, $($arg)*);
|
||||||
|
mgba.send(MGBADebugLevel::Warning);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers an info message to the mGBA output.
|
||||||
|
///
|
||||||
|
/// This works basically like `println`. mGBA is a C program and all, so you
|
||||||
|
/// should only attempt to print non-null ASCII values through this. There's
|
||||||
|
/// also a maximum length of 255 bytes per message.
|
||||||
|
///
|
||||||
|
/// This has no effect if you're not using mGBA.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! info {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
use $crate::mgba::{MGBADebug, MGBADebugLevel};
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(mut mgba) = MGBADebug::new() {
|
||||||
|
let _ = write!(mgba, $($arg)*);
|
||||||
|
mgba.send(MGBADebugLevel::Info);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers a debug message to the mGBA output.
|
||||||
|
///
|
||||||
|
/// This works basically like `println`. mGBA is a C program and all, so you
|
||||||
|
/// should only attempt to print non-null ASCII values through this. There's
|
||||||
|
/// also a maximum length of 255 bytes per message.
|
||||||
|
///
|
||||||
|
/// This has no effect if you're not using mGBA.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! debug {
|
||||||
|
($($arg:tt)*) => {{
|
||||||
|
use $crate::mgba::{MGBADebug, MGBADebugLevel};
|
||||||
|
use core::fmt::Write;
|
||||||
|
if let Some(mut mgba) = MGBADebug::new() {
|
||||||
|
let _ = write!(mgba, $($arg)*);
|
||||||
|
mgba.send(MGBADebugLevel::Debug);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Using timers 0 and 1, performs a crude timing of the expression given.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! time_this01 {
|
||||||
|
($x:expr) => {{
|
||||||
|
use $crate::io::timers::*;
|
||||||
|
const NORMAL_ON: TimerControlSetting = TimerControlSetting::new().with_enabled(true);
|
||||||
|
const CASCADE_ON: TimerControlSetting =
|
||||||
|
TimerControlSetting::new().with_enabled(true).with_tick_rate(TimerTickRate::Cascade);
|
||||||
|
const OFF: TimerControlSetting = TimerControlSetting::new();
|
||||||
|
TM1CNT_H.write(CASCADE_ON);
|
||||||
|
TM0CNT_H.write(NORMAL_ON);
|
||||||
|
$x;
|
||||||
|
TM0CNT_H.write(OFF);
|
||||||
|
TM1CNT_H.write(OFF);
|
||||||
|
let end_low = TM0CNT_L.read() as u32;
|
||||||
|
let end_high = TM1CNT_L.read() as u32;
|
||||||
|
end_high << 16 | end_low
|
||||||
|
}};
|
||||||
|
}
|
|
@ -24,13 +24,13 @@ pub struct MGBADebug {
|
||||||
bytes_written: u8,
|
bytes_written: u8,
|
||||||
}
|
}
|
||||||
impl MGBADebug {
|
impl MGBADebug {
|
||||||
const ENABLE_ADDRESS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x4fff780) };
|
const ENABLE_ADDRESS: VolAddress<u16> = unsafe { VolAddress::new(0x4fff780) };
|
||||||
const ENABLE_ADDRESS_INPUT: u16 = 0xC0DE;
|
const ENABLE_ADDRESS_INPUT: u16 = 0xC0DE;
|
||||||
const ENABLE_ADDRESS_OUTPUT: u16 = 0x1DEA;
|
const ENABLE_ADDRESS_OUTPUT: u16 = 0x1DEA;
|
||||||
|
|
||||||
const OUTPUT_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(0x4fff600) };
|
const OUTPUT_BASE: VolAddress<u8> = unsafe { VolAddress::new(0x4fff600) };
|
||||||
|
|
||||||
const SEND_ADDRESS: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x4fff700) };
|
const SEND_ADDRESS: VolAddress<u16> = unsafe { VolAddress::new(0x4fff700) };
|
||||||
const SEND_FLAG: u16 = 0x100;
|
const SEND_FLAG: u16 = 0x100;
|
||||||
|
|
||||||
/// Gives a new MGBADebug, if running within `mGBA`
|
/// Gives a new MGBADebug, if running within `mGBA`
|
||||||
|
@ -55,8 +55,6 @@ impl MGBADebug {
|
||||||
/// it might accidentally be discarded.
|
/// it might accidentally be discarded.
|
||||||
pub fn send(&mut self, level: MGBADebugLevel) {
|
pub fn send(&mut self, level: MGBADebugLevel) {
|
||||||
if level == MGBADebugLevel::Fatal {
|
if level == MGBADebugLevel::Fatal {
|
||||||
Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Error as u16);
|
|
||||||
|
|
||||||
// Note(Lokathor): A Fatal send causes the emulator to halt!
|
// Note(Lokathor): A Fatal send causes the emulator to halt!
|
||||||
Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Fatal as u16);
|
Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Fatal as u16);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use typenum::consts::{U128, U32};
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// 0th part of an object's attributes.
|
/// 0th part of an object's attributes.
|
||||||
///
|
///
|
||||||
|
@ -137,7 +139,8 @@ pub struct ObjectAttributes {
|
||||||
|
|
||||||
/// The object attributes, but there are gaps in the array, so we must not
|
/// The object attributes, but there are gaps in the array, so we must not
|
||||||
/// expose this directly.
|
/// expose this directly.
|
||||||
const OBJ_ATTR_APPROX: VolAddressBlock<[u16; 4]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x700_0000), 128) };
|
const OBJ_ATTR_APPROX: VolBlock<[u16; 4], U128> = unsafe { VolBlock::new(0x700_0000) };
|
||||||
|
// TODO: VolSeries
|
||||||
|
|
||||||
pub fn write_obj_attributes(slot: usize, attributes: ObjectAttributes) -> Option<()> {
|
pub fn write_obj_attributes(slot: usize, attributes: ObjectAttributes) -> Option<()> {
|
||||||
OBJ_ATTR_APPROX.get(slot).map(|va| unsafe {
|
OBJ_ATTR_APPROX.get(slot).map(|va| unsafe {
|
||||||
|
@ -169,7 +172,8 @@ pub struct AffineParameters {
|
||||||
|
|
||||||
/// The object attributes, but there are gaps in the array, so we must not
|
/// The object attributes, but there are gaps in the array, so we must not
|
||||||
/// expose this directly.
|
/// expose this directly.
|
||||||
const AFFINE_PARAMS_APPROX: VolAddressBlock<[i16; 16]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x700_0000), 32) };
|
const AFFINE_PARAMS_APPROX: VolBlock<[i16; 16], U32> = unsafe { VolBlock::new(0x700_0000) };
|
||||||
|
// TODO: VolSeries
|
||||||
|
|
||||||
pub fn write_affine_parameters(slot: usize, params: AffineParameters) -> Option<()> {
|
pub fn write_affine_parameters(slot: usize, params: AffineParameters) -> Option<()> {
|
||||||
AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe {
|
AFFINE_PARAMS_APPROX.get(slot).map(|va| unsafe {
|
||||||
|
|
|
@ -23,18 +23,17 @@
|
||||||
//! display will show if no background or object draws over top of a given pixel
|
//! display will show if no background or object draws over top of a given pixel
|
||||||
//! during rendering.
|
//! during rendering.
|
||||||
|
|
||||||
use super::{
|
use super::*;
|
||||||
base::volatile::{VolAddress, VolAddressBlock},
|
|
||||||
Color,
|
use typenum::consts::U256;
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: PalIndex newtypes?
|
// TODO: PalIndex newtypes?
|
||||||
|
|
||||||
/// The `PALRAM` for background colors, 256 slot view.
|
/// The `PALRAM` for background colors, 256 slot view.
|
||||||
pub const PALRAM_BG: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0000), 256) };
|
pub const PALRAM_BG: VolBlock<Color, U256> = unsafe { VolBlock::new(0x500_0000) };
|
||||||
|
|
||||||
/// The `PALRAM` for object colors, 256 slot view.
|
/// The `PALRAM` for object colors, 256 slot view.
|
||||||
pub const PALRAM_OBJ: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x500_0200), 256) };
|
pub const PALRAM_OBJ: VolBlock<Color, U256> = unsafe { VolBlock::new(0x500_0200) };
|
||||||
|
|
||||||
/// Obtains the address of the specified 8bpp background palette slot.
|
/// Obtains the address of the specified 8bpp background palette slot.
|
||||||
pub const fn index_palram_bg_8bpp(slot: u8) -> VolAddress<Color> {
|
pub const fn index_palram_bg_8bpp(slot: u8) -> VolAddress<Color> {
|
||||||
|
|
18
src/vram.rs
18
src/vram.rs
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
pub(crate) use super::*;
|
pub(crate) use super::*;
|
||||||
|
|
||||||
|
use typenum::consts::{U256, U32, U512, U6};
|
||||||
|
|
||||||
pub mod affine;
|
pub mod affine;
|
||||||
pub mod bitmap;
|
pub mod bitmap;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
@ -27,12 +29,14 @@ pub mod text;
|
||||||
/// being the correct thing.
|
/// being the correct thing.
|
||||||
pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
||||||
|
|
||||||
|
pub const PAGE1_OFFSET: usize = 0xA000;
|
||||||
|
|
||||||
/// The character base blocks.
|
/// The character base blocks.
|
||||||
pub const CHAR_BASE_BLOCKS: VolAddressBlock<[u8; 0x4000]> = unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 6) };
|
pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], U6> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// The screen entry base blocks.
|
/// The screen entry base blocks.
|
||||||
pub const SCREEN_BASE_BLOCKS: VolAddressBlock<[u8; 0x800]> =
|
pub const SCREEN_BASE_BLOCKS: VolBlock<[u8; 0x800], U32> =
|
||||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), 32) };
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
||||||
|
@ -47,11 +51,11 @@ newtype! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the specified charblock in 4bpp view.
|
/// Gives the specified charblock in 4bpp view.
|
||||||
pub fn get_4bpp_character_block(slot: usize) -> VolAddressBlock<Tile4bpp> {
|
pub fn get_4bpp_character_block(slot: usize) -> VolBlock<Tile4bpp, U512> {
|
||||||
unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::<Tile4bpp>(), 512) }
|
unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).to_usize()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the specified charblock in 8bpp view.
|
/// Gives the specified charblock in 8bpp view.
|
||||||
pub fn get_8bpp_character_block(slot: usize) -> VolAddressBlock<Tile8bpp> {
|
pub fn get_8bpp_character_block(slot: usize) -> VolBlock<Tile8bpp, U256> {
|
||||||
unsafe { VolAddressBlock::new_unchecked(CHAR_BASE_BLOCKS.index(slot).cast::<Tile8bpp>(), 256) }
|
unsafe { VolBlock::new(CHAR_BASE_BLOCKS.index(slot).to_usize()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,154 +1,213 @@
|
||||||
//! Module for the Bitmap video modes.
|
//! Module for the Bitmap video modes.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use core::ops::{Div, Mul};
|
||||||
|
use typenum::consts::{U128, U160, U2, U256, U4};
|
||||||
|
|
||||||
/// Mode 3 is a bitmap mode with full color and full resolution.
|
/// A bitmap video mode with full color and full resolution.
|
||||||
///
|
///
|
||||||
/// * **Width:** 240
|
/// * **Width:** 240
|
||||||
/// * **Height:** 160
|
/// * **Height:** 160
|
||||||
///
|
///
|
||||||
/// Because the memory requirements are so large, there's only a single page
|
/// Because it takes so much space to have full color and full resolution at the
|
||||||
/// available instead of two pages like the other video modes have.
|
/// same time, there's no alternate page available when using mode 3.
|
||||||
///
|
///
|
||||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||||
/// must have BG2 enabled in addition to being within Mode 3.
|
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||||
pub struct Mode3;
|
pub struct Mode3;
|
||||||
|
|
||||||
impl Mode3 {
|
impl Mode3 {
|
||||||
/// The physical width in pixels of the GBA screen.
|
/// The screen's width in this mode.
|
||||||
pub const SCREEN_WIDTH: usize = 240;
|
pub const WIDTH: usize = 240;
|
||||||
|
|
||||||
/// The physical height in pixels of the GBA screen.
|
/// The screen's height in this mode.
|
||||||
pub const SCREEN_HEIGHT: usize = 160;
|
pub const HEIGHT: usize = 160;
|
||||||
|
|
||||||
/// The number of pixels on the screen.
|
const VRAM: VolBlock<Color, <U256 as Mul<U160>>::Output> =
|
||||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// The Mode 3 VRAM.
|
const WORDS_BLOCK: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
|
/// Gets the address of the pixel specified.
|
||||||
///
|
///
|
||||||
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
/// ## Failure
|
||||||
/// 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_PIXEL_COUNT) };
|
|
||||||
|
|
||||||
/// 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_PIXEL_COUNT / 2).iter() };
|
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// Gives `None` if out of bounds
|
||||||
///
|
fn get(col: usize, row: usize) -> Option<VolAddress<Color>> {
|
||||||
/// Gives `None` if out of bounds.
|
Self::VRAM.get(col + row * Self::WIDTH)
|
||||||
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).
|
/// Reads the color of the pixel specified.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds
|
||||||
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
pub fn read(col: usize, row: usize) -> Option<Color> {
|
||||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
Self::get(col, row).map(VolAddress::read)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the whole screen to the desired color.
|
/// Writes a color to the pixel specified.
|
||||||
|
///
|
||||||
|
/// ## Failure
|
||||||
|
///
|
||||||
|
/// Gives `None` if out of bounds
|
||||||
|
pub fn write(col: usize, row: usize, color: Color) -> Option<()> {
|
||||||
|
Self::get(col, row).map(|va| va.write(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the screen to the color specified.
|
||||||
|
///
|
||||||
|
/// Takes ~430,000 cycles (~1.5 frames).
|
||||||
pub fn clear_to(color: Color) {
|
pub fn clear_to(color: Color) {
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
for va in Self::BULK_ITER {
|
for va in Self::WORDS_BLOCK.iter() {
|
||||||
va.write(bulk_color)
|
va.write(bulk_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the whole screen to the desired color using DMA3.
|
/// Clears the screen to the color specified using DMA3.
|
||||||
|
///
|
||||||
|
/// Takes ~61,500 frames (~73% of VBlank)
|
||||||
pub fn dma_clear_to(color: Color) {
|
pub fn dma_clear_to(color: Color) {
|
||||||
use crate::io::dma::DMA3;
|
use crate::io::dma::DMA3;
|
||||||
|
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, (Self::SCREEN_PIXEL_COUNT / 2) as u16) };
|
unsafe {
|
||||||
|
DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::WORDS_BLOCK.len() as u16)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||||
|
///
|
||||||
|
/// Works fine with out of bounds points. It only draws to in bounds
|
||||||
|
/// locations.
|
||||||
|
pub fn draw_line(c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
|
||||||
|
let mut col = c1;
|
||||||
|
let mut row = r1;
|
||||||
|
let w = c2 - c1;
|
||||||
|
let h = r2 - r1;
|
||||||
|
let mut dx1 = 0;
|
||||||
|
let mut dx2 = 0;
|
||||||
|
let mut dy1 = 0;
|
||||||
|
let mut dy2 = 0;
|
||||||
|
let mut longest = w.abs();
|
||||||
|
let mut shortest = h.abs();
|
||||||
|
if w < 0 {
|
||||||
|
dx1 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx1 = 1;
|
||||||
|
};
|
||||||
|
if h < 0 {
|
||||||
|
dy1 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy1 = 1;
|
||||||
|
};
|
||||||
|
if w < 0 {
|
||||||
|
dx2 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx2 = 1;
|
||||||
|
};
|
||||||
|
if !(longest > shortest) {
|
||||||
|
core::mem::swap(&mut longest, &mut shortest);
|
||||||
|
if h < 0 {
|
||||||
|
dy2 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy2 = 1
|
||||||
|
};
|
||||||
|
dx2 = 0;
|
||||||
|
}
|
||||||
|
let mut numerator = longest >> 1;
|
||||||
|
|
||||||
|
(0..(longest + 1)).for_each(|_| {
|
||||||
|
Self::write(col as usize, row as usize, color);
|
||||||
|
numerator += shortest;
|
||||||
|
if !(numerator < longest) {
|
||||||
|
numerator -= longest;
|
||||||
|
col += dx1;
|
||||||
|
row += dy1;
|
||||||
|
} else {
|
||||||
|
col += dx2;
|
||||||
|
row += dy2;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Mode3 Iter Scanlines / Pixels?
|
/// Used to select what page to read from or write to in Mode 4 and Mode 5.
|
||||||
//TODO: Mode3 Line Drawing?
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Page {
|
||||||
|
/// Page 0
|
||||||
|
Zero,
|
||||||
|
/// Page 1
|
||||||
|
One,
|
||||||
|
}
|
||||||
|
|
||||||
/// Mode 4 is a bitmap mode with 8bpp paletted color.
|
/// A bitmap video mode with full resolution and paletted color.
|
||||||
///
|
///
|
||||||
/// * **Width:** 240
|
/// * **Width:** 240
|
||||||
/// * **Height:** 160
|
/// * **Height:** 160
|
||||||
/// * **Pages:** 2
|
/// * **Pages:** 2
|
||||||
///
|
///
|
||||||
/// VRAM has a minimum write size of 2 bytes at a time, so writing individual
|
/// Because the pixels use palette indexes there's enough space to have two
|
||||||
/// palette entries for the pixels is more costly than with the other bitmap
|
/// pages.
|
||||||
/// modes.
|
|
||||||
///
|
///
|
||||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||||
/// must have BG2 enabled in addition to being within Mode 4.
|
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||||
pub struct Mode4;
|
pub struct Mode4;
|
||||||
|
|
||||||
impl Mode4 {
|
impl Mode4 {
|
||||||
/// The physical width in pixels of the GBA screen.
|
/// The screen's width in this mode.
|
||||||
pub const SCREEN_WIDTH: usize = 240;
|
pub const WIDTH: usize = 240;
|
||||||
|
|
||||||
/// The physical height in pixels of the GBA screen.
|
/// The screen's height in this mode.
|
||||||
pub const SCREEN_HEIGHT: usize = 160;
|
pub const HEIGHT: usize = 160;
|
||||||
|
|
||||||
/// The number of pixels on the screen.
|
const PAGE0_INDEXES: VolBlock<u8, <U256 as Mul<U160>>::Output> =
|
||||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// Used for bulk clearing operations.
|
const PAGE1_INDEXES: VolBlock<u8, <U256 as Mul<U160>>::Output> =
|
||||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 4;
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
const PAGE0_WORDS: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
|
||||||
const PAGE0_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
const PAGE1_WORDS: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
|
||||||
const PAGE0_BLOCK: VolAddressBlock<u8> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
/// Reads the color of the pixel specified.
|
||||||
const PAGE1_BASE: VolAddress<u8> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) };
|
|
||||||
|
|
||||||
// TODO: newtype this?
|
|
||||||
const PAGE1_BLOCK: VolAddressBlock<u8> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) };
|
|
||||||
|
|
||||||
/// private iterator over the page0 pixels, four at a time
|
|
||||||
const BULK_ITER0: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
|
||||||
|
|
||||||
/// private iterator over the page1 pixels, four at a time
|
|
||||||
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds
|
||||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> {
|
pub fn read(page: Page, col: usize, row: usize) -> Option<u8> {
|
||||||
// Note(Lokathor): byte _reads_ from VRAM are okay.
|
match page {
|
||||||
if page1 {
|
Page::Zero => Self::PAGE0_INDEXES,
|
||||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Page::One => Self::PAGE1_INDEXES,
|
||||||
} else {
|
|
||||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
|
||||||
}
|
}
|
||||||
|
.get(col + row * Self::WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the pixel at the given (col,row).
|
/// Writes a color to the pixel specified.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds
|
||||||
pub fn write_pixel(page1: bool, col: usize, row: usize, pal8bpp: u8) -> Option<()> {
|
pub fn write(page: Page, col: usize, row: usize, pal8bpp: u8) -> Option<()> {
|
||||||
// Note(Lokathor): byte _writes_ to VRAM are not permitted. We must jump
|
// Note(Lokathor): Byte writes to VRAM aren't permitted, we have to jump
|
||||||
// through hoops when we attempt to write just a single byte.
|
// through some hoops.
|
||||||
if col < Self::SCREEN_WIDTH && row < Self::SCREEN_HEIGHT {
|
if col < Self::WIDTH && row < Self::HEIGHT {
|
||||||
let real_index = col + row * Self::SCREEN_WIDTH;
|
let real_index = col + row * Self::WIDTH;
|
||||||
let rounded_down_index = real_index & !1;
|
let rounded_down_index = real_index & !1;
|
||||||
let address: VolAddress<u16> = unsafe {
|
let address: VolAddress<u16> = unsafe {
|
||||||
if page1 {
|
match page {
|
||||||
Self::PAGE1_BASE.offset(rounded_down_index as isize).cast()
|
Page::Zero => Self::PAGE0_INDEXES,
|
||||||
} else {
|
Page::One => Self::PAGE1_INDEXES,
|
||||||
Self::PAGE0_BASE.offset(rounded_down_index as isize).cast()
|
|
||||||
}
|
}
|
||||||
|
.index_unchecked(rounded_down_index)
|
||||||
|
.cast::<u16>()
|
||||||
};
|
};
|
||||||
if real_index == rounded_down_index {
|
if real_index == rounded_down_index {
|
||||||
// even byte, change the high bits
|
// even byte, change the high bits
|
||||||
|
@ -165,145 +224,237 @@ impl Mode4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a "wide" pairing of palette entries to the location specified.
|
/// Clear the screen to the palette index specified.
|
||||||
///
|
///
|
||||||
/// The page is imagined to be a series of `u16` values rather than `u8`
|
/// Takes ~215,000 cycles (~76% of a frame)
|
||||||
/// values, allowing you to write two palette entries side by side as a single
|
pub fn clear_to(page: Page, pal8bpp: u8) {
|
||||||
/// write operation.
|
|
||||||
pub fn write_wide_pixel(page1: bool, wide_col: usize, row: usize, wide_pal8bpp: u16) -> Option<()> {
|
|
||||||
if wide_col < Self::SCREEN_WIDTH / 2 && row < Self::SCREEN_HEIGHT {
|
|
||||||
let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2;
|
|
||||||
let address: VolAddress<u16> = unsafe {
|
|
||||||
if page1 {
|
|
||||||
Self::PAGE1_BASE.cast::<u16>().offset(wide_index as isize)
|
|
||||||
} else {
|
|
||||||
Self::PAGE0_BASE.cast::<u16>().offset(wide_index as isize)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some(address.write(wide_pal8bpp))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the page to the desired color.
|
|
||||||
pub fn clear_page_to(page1: bool, pal8bpp: u8) {
|
|
||||||
let pal8bpp_32 = pal8bpp as u32;
|
let pal8bpp_32 = pal8bpp as u32;
|
||||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||||
for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } {
|
let words = match page {
|
||||||
|
Page::Zero => Self::PAGE0_WORDS,
|
||||||
|
Page::One => Self::PAGE1_WORDS,
|
||||||
|
};
|
||||||
|
for va in words.iter() {
|
||||||
va.write(bulk_color)
|
va.write(bulk_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the page to the desired color using DMA3.
|
/// Clears the screen to the palette index specified using DMA3.
|
||||||
pub fn dma_clear_page_to(page1: bool, pal8bpp: u8) {
|
///
|
||||||
|
/// Takes ~30,800 frames (~37% of VBlank)
|
||||||
|
pub fn dma_clear_to(page: Page, pal8bpp: u8) {
|
||||||
use crate::io::dma::DMA3;
|
use crate::io::dma::DMA3;
|
||||||
|
|
||||||
let pal8bpp_32 = pal8bpp as u32;
|
let pal8bpp_32 = pal8bpp as u32;
|
||||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||||
let write_target = if page1 {
|
let words_address = unsafe {
|
||||||
VRAM_BASE_USIZE as *mut u32
|
match page {
|
||||||
} else {
|
Page::Zero => Self::PAGE0_WORDS.index_unchecked(0).to_usize(),
|
||||||
(VRAM_BASE_USIZE + 0xA000) as *mut u32
|
Page::One => Self::PAGE1_WORDS.index_unchecked(0).to_usize(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) };
|
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||||
|
///
|
||||||
|
/// Works fine with out of bounds points. It only draws to in bounds
|
||||||
|
/// locations.
|
||||||
|
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, pal8bpp: u8) {
|
||||||
|
let mut col = c1;
|
||||||
|
let mut row = r1;
|
||||||
|
let w = c2 - c1;
|
||||||
|
let h = r2 - r1;
|
||||||
|
let mut dx1 = 0;
|
||||||
|
let mut dx2 = 0;
|
||||||
|
let mut dy1 = 0;
|
||||||
|
let mut dy2 = 0;
|
||||||
|
let mut longest = w.abs();
|
||||||
|
let mut shortest = h.abs();
|
||||||
|
if w < 0 {
|
||||||
|
dx1 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx1 = 1;
|
||||||
|
};
|
||||||
|
if h < 0 {
|
||||||
|
dy1 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy1 = 1;
|
||||||
|
};
|
||||||
|
if w < 0 {
|
||||||
|
dx2 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx2 = 1;
|
||||||
|
};
|
||||||
|
if !(longest > shortest) {
|
||||||
|
core::mem::swap(&mut longest, &mut shortest);
|
||||||
|
if h < 0 {
|
||||||
|
dy2 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy2 = 1
|
||||||
|
};
|
||||||
|
dx2 = 0;
|
||||||
|
}
|
||||||
|
let mut numerator = longest >> 1;
|
||||||
|
|
||||||
|
(0..(longest + 1)).for_each(|_| {
|
||||||
|
Self::write(page, col as usize, row as usize, pal8bpp);
|
||||||
|
numerator += shortest;
|
||||||
|
if !(numerator < longest) {
|
||||||
|
numerator -= longest;
|
||||||
|
col += dx1;
|
||||||
|
row += dy1;
|
||||||
|
} else {
|
||||||
|
col += dx2;
|
||||||
|
row += dy2;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Mode4 Iter Scanlines / Pixels?
|
|
||||||
//TODO: Mode4 Line Drawing?
|
|
||||||
|
|
||||||
/// Mode 5 is a bitmap mode with full color and reduced resolution.
|
/// Mode 5 is a bitmap mode with full color and reduced resolution.
|
||||||
///
|
///
|
||||||
/// * **Width:** 160
|
/// * **Width:** 160
|
||||||
/// * **Height:** 128
|
/// * **Height:** 128
|
||||||
/// * **Pages:** 2
|
/// * **Pages:** 2
|
||||||
///
|
///
|
||||||
/// Because of the reduced resolution, we're allowed two pages for display.
|
/// Because of the reduced resolutions there's enough space to have two pages.
|
||||||
///
|
///
|
||||||
/// As with all bitmap modes, the image itself utilizes BG2 for display, so you
|
/// As with all the bitmap video modes, the bitmap is considered to be BG2, so
|
||||||
/// must have BG2 enabled in addition to being within Mode 3.
|
/// you have to enable BG2 as well if you want to see the bitmap.
|
||||||
pub struct Mode5;
|
pub struct Mode5;
|
||||||
|
|
||||||
impl Mode5 {
|
impl Mode5 {
|
||||||
/// The physical width in pixels of the GBA screen.
|
/// The screen's width in this mode.
|
||||||
pub const SCREEN_WIDTH: usize = 160;
|
pub const WIDTH: usize = 160;
|
||||||
|
|
||||||
/// The physical height in pixels of the GBA screen.
|
/// The screen's height in this mode.
|
||||||
pub const SCREEN_HEIGHT: usize = 128;
|
pub const HEIGHT: usize = 128;
|
||||||
|
|
||||||
/// The number of pixels on the screen.
|
const PAGE0_PIXELS: VolBlock<Color, <U160 as Mul<U128>>::Output> =
|
||||||
pub const SCREEN_PIXEL_COUNT: usize = Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT;
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// Used for bulk clearing operations.
|
const PAGE1_PIXELS: VolBlock<Color, <U160 as Mul<U128>>::Output> =
|
||||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2;
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
const PAGE0_WORDS: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
|
||||||
const PAGE0_BASE: VolAddress<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE) };
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
const PAGE1_WORDS: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
|
||||||
const PAGE0_BLOCK: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE, Self::SCREEN_PIXEL_COUNT) };
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + PAGE1_OFFSET) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
/// Reads the color of the pixel specified.
|
||||||
const PAGE1_BASE: VolAddress<Color> = unsafe { VolAddress::new_unchecked(VRAM_BASE_USIZE + 0xA000) };
|
|
||||||
|
|
||||||
// TODO: newtype this?
|
|
||||||
const PAGE1_BLOCK: VolAddressBlock<Color> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE, Self::SCREEN_PIXEL_COUNT) };
|
|
||||||
|
|
||||||
/// private iterator over the page0 pixels, four at a time
|
|
||||||
const BULK_ITER0: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE0_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
|
||||||
|
|
||||||
/// private iterator over the page1 pixels, four at a time
|
|
||||||
const BULK_ITER1: VolAddressIter<u32> = unsafe { VolAddressBlock::new_unchecked(Self::PAGE1_BASE.cast::<u32>(), Self::SCREEN_U32_COUNT).iter() };
|
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds
|
||||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> {
|
pub fn read(page: Page, col: usize, row: usize) -> Option<Color> {
|
||||||
if page1 {
|
match page {
|
||||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Page::Zero => Self::PAGE0_PIXELS,
|
||||||
} else {
|
Page::One => Self::PAGE1_PIXELS,
|
||||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
|
||||||
}
|
}
|
||||||
|
.get(col + row * Self::WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the pixel at the given (col,row).
|
/// Writes a color to the pixel specified.
|
||||||
///
|
///
|
||||||
/// # Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds
|
||||||
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> {
|
pub fn write(page: Page, col: usize, row: usize, color: Color) -> Option<()> {
|
||||||
if page1 {
|
match page {
|
||||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
Page::Zero => Self::PAGE0_PIXELS,
|
||||||
} else {
|
Page::One => Self::PAGE1_PIXELS,
|
||||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
|
||||||
}
|
}
|
||||||
|
.get(col + row * Self::WIDTH)
|
||||||
|
.map(|va| va.write(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the whole screen to the desired color.
|
/// Clear the screen to the color specified.
|
||||||
pub fn clear_page_to(page1: bool, color: Color) {
|
///
|
||||||
|
/// Takes ~215,000 cycles (~76% of a frame)
|
||||||
|
pub fn clear_to(page: Page, color: Color) {
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
for va in if page1 { Self::BULK_ITER1 } else { Self::BULK_ITER0 } {
|
let words = match page {
|
||||||
|
Page::Zero => Self::PAGE0_WORDS,
|
||||||
|
Page::One => Self::PAGE1_WORDS,
|
||||||
|
};
|
||||||
|
for va in words.iter() {
|
||||||
va.write(bulk_color)
|
va.write(bulk_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the whole screen to the desired color using DMA3.
|
/// Clears the screen to the color specified using DMA3.
|
||||||
pub fn dma_clear_page_to(page1: bool, color: Color) {
|
///
|
||||||
|
/// Takes ~30,800 frames (~37% of VBlank)
|
||||||
|
pub fn dma_clear_to(page: Page, color: Color) {
|
||||||
use crate::io::dma::DMA3;
|
use crate::io::dma::DMA3;
|
||||||
|
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
let write_target = if page1 {
|
let words_address = unsafe {
|
||||||
VRAM_BASE_USIZE as *mut u32
|
match page {
|
||||||
} else {
|
Page::Zero => Self::PAGE0_WORDS.index_unchecked(0).to_usize(),
|
||||||
(VRAM_BASE_USIZE + 0xA000) as *mut u32
|
Page::One => Self::PAGE1_WORDS.index_unchecked(0).to_usize(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
unsafe { DMA3::fill32(&bulk_color, write_target, Self::SCREEN_U32_COUNT as u16) };
|
unsafe { DMA3::fill32(&bulk_color, words_address as *mut u32, Self::PAGE0_WORDS.len() as u16) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a line between the two points given `(c1,r1,c2,r2,color)`.
|
||||||
|
///
|
||||||
|
/// Works fine with out of bounds points. It only draws to in bounds
|
||||||
|
/// locations.
|
||||||
|
pub fn draw_line(page: Page, c1: isize, r1: isize, c2: isize, r2: isize, color: Color) {
|
||||||
|
let mut col = c1;
|
||||||
|
let mut row = r1;
|
||||||
|
let w = c2 - c1;
|
||||||
|
let h = r2 - r1;
|
||||||
|
let mut dx1 = 0;
|
||||||
|
let mut dx2 = 0;
|
||||||
|
let mut dy1 = 0;
|
||||||
|
let mut dy2 = 0;
|
||||||
|
let mut longest = w.abs();
|
||||||
|
let mut shortest = h.abs();
|
||||||
|
if w < 0 {
|
||||||
|
dx1 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx1 = 1;
|
||||||
|
};
|
||||||
|
if h < 0 {
|
||||||
|
dy1 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy1 = 1;
|
||||||
|
};
|
||||||
|
if w < 0 {
|
||||||
|
dx2 = -1;
|
||||||
|
} else if w > 0 {
|
||||||
|
dx2 = 1;
|
||||||
|
};
|
||||||
|
if !(longest > shortest) {
|
||||||
|
core::mem::swap(&mut longest, &mut shortest);
|
||||||
|
if h < 0 {
|
||||||
|
dy2 = -1;
|
||||||
|
} else if h > 0 {
|
||||||
|
dy2 = 1
|
||||||
|
};
|
||||||
|
dx2 = 0;
|
||||||
|
}
|
||||||
|
let mut numerator = longest >> 1;
|
||||||
|
|
||||||
|
(0..(longest + 1)).for_each(|_| {
|
||||||
|
Self::write(page, col as usize, row as usize, color);
|
||||||
|
numerator += shortest;
|
||||||
|
if !(numerator < longest) {
|
||||||
|
numerator -= longest;
|
||||||
|
col += dx1;
|
||||||
|
row += dy1;
|
||||||
|
} else {
|
||||||
|
col += dx2;
|
||||||
|
row += dy2;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Mode5 Iter Scanlines / Pixels?
|
|
||||||
//TODO: Mode5 Line Drawing?
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue