71: Dev merge (testing bors) r=Lokathor a=Lokathor



Co-authored-by: Lokathor <zefria@gmail.com>
This commit is contained in:
bors[bot] 2019-02-15 04:44:16 +00:00
commit eb157e00dc
40 changed files with 2107 additions and 867 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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
View 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
View file

@ -0,0 +1 @@
status = ["continuous-integration/travis-ci/push"]

56
crt0.s
View file

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

View file

@ -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() {}

View file

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

View file

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

View file

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

View file

@ -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::*;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
View 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;

View file

@ -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)]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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