hello world chapter is complete

This commit is contained in:
Lokathor 2018-11-10 23:39:26 -07:00
parent f838103528
commit 0b9d8e9b29
25 changed files with 877 additions and 76 deletions

View file

@ -30,7 +30,7 @@ do this stuff.
dots as well as other support files: dots as well as other support files:
* crt0.s * crt0.s
* linker.ld * linker.ld
* thumbv4-nintendo-agb.json * thumbv4-none-agb.json
* build.rs * build.rs
5) Run `arm-none-eabi-as crt0.s -o crt0.o` to build the `crt0.s` into a `crt0.o` 5) Run `arm-none-eabi-as crt0.s -o crt0.o` to build the `crt0.s` into a `crt0.o`
@ -38,7 +38,7 @@ do this stuff.
`build.bat` file it's set to simply run every single time because it's a `build.bat` file it's set to simply run every single time because it's a
cheap enough operation. cheap enough operation.
6) Build with `cargo xbuild --target thumbv4-nintendo-agb.json` 6) Build with `cargo xbuild --target thumbv4-none-agb.json`
* The file extension is significant, and `cargo xbuild` takes it as a flag to * The file extension is significant, and `cargo xbuild` takes it as a flag to
compile dependencies with the same sysroot, so you can include crates compile dependencies with the same sysroot, so you can include crates
normally. Well, crates that can run inside a GBA at least (Which means they normally. Well, crates that can run inside a GBA at least (Which means they
@ -47,7 +47,7 @@ do this stuff.
helpful because it has debug symbols). helpful because it has debug symbols).
7) Also you can patch up the output to be a "real" ROM file: 7) Also you can patch up the output to be a "real" ROM file:
* `arm-none-eabi-objcopy -O binary target/thumbv4-nintendo-agb/debug/gbatest target/output.gba` * `arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/debug/gbatest target/output.gba`
* `gbafix target/output.gba` * `gbafix target/output.gba`
8) Alternately, you can use the provided `build.bat` file (or write a similar 8) Alternately, you can use the provided `build.bat` file (or write a similar

View file

@ -8,3 +8,4 @@
* [IO Registers](ch1/io_registers.md) * [IO Registers](ch1/io_registers.md)
* [The Display Control](ch1/the_display_control.md) * [The Display Control](ch1/the_display_control.md)
* [Video Memory Intro](ch1/video_memory_intro.md) * [Video Memory Intro](ch1/video_memory_intro.md)
* [hello2](ch1/hello2.md)

View file

@ -37,10 +37,12 @@ Now you'll need some particular files each time you want to start a new project.
You can find them in the root of the [rust-console/gba You can find them in the root of the [rust-console/gba
repo](https://github.com/rust-console/gba). repo](https://github.com/rust-console/gba).
* `thumbv4-nintendo-agb.json` describes the overall GBA to cargo-xbuild so it * `thumbv4-none-agb.json` describes the overall GBA to cargo-xbuild so it knows
knows what to do. This is actually a somewhat made up target name since what to do. This is actually a somewhat made up target name since there's no
there's no official target name, but this is the best name to pick based on official target name. The GBA is essentially the same as a normal
[how target names are decided](https://wiki.osdev.org/Target_Triplet). `thumbv4-none-eabi` device, but we give it the "agb" signifier so that later
on we'll be able to use rust's `cfg` ability to allow our code to know if it's
specifically targeting a GBA or some other similar device (like an NDS).
* `crt0.s` describes some ASM startup stuff. If you have more ASM to place here * `crt0.s` describes some ASM startup stuff. If you have more ASM to place here
later on this is where you can put it. You also need to build it into a later on this is where you can put it. You also need to build it into a
`crt0.o` file before it can actually be used, but we'll cover that below. `crt0.o` file before it can actually be used, but we'll cover that below.
@ -57,7 +59,7 @@ Once you've got something to build, you perform the following steps:
might as well do it every time so that you never forget to because it's a might as well do it every time so that you never forget to because it's a
practically instant operation. practically instant operation.
* `cargo xbuild --target thumbv4-nintendo-agb.json` * `cargo xbuild --target thumbv4-none-agb.json`
* This builds your Rust source. It accepts _most of_ the normal options, such * This builds your Rust source. It accepts _most of_ the normal options, such
as `--release`, and options, such as `--bin foo` or `--examples`, that you'd as `--release`, and options, such as `--bin foo` or `--examples`, that you'd
expect `cargo` to accept. expect `cargo` to accept.
@ -83,7 +85,7 @@ emulators can also do it.
However, if you want a "real" ROM that works in all emulators and that you could However, if you want a "real" ROM that works in all emulators and that you could
transfer to a flash cart there's a little more to do. transfer to a flash cart there's a little more to do.
* `arm-none-eabi-objcopy -O binary target/thumbv4-nintendo-agb/MODE/BIN_NAME target/ROM_NAME.gba` * `arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba`
* This will perform an [objcopy](https://linux.die.net/man/1/objcopy) on our * This will perform an [objcopy](https://linux.die.net/man/1/objcopy) on our
program. Here I've named the program `arm-none-eabi-objcopy`, which is what program. Here I've named the program `arm-none-eabi-objcopy`, which is what
devkitpro calls their version of `objcopy` that's specific to the GBA in the devkitpro calls their version of `objcopy` that's specific to the GBA in the
@ -97,7 +99,7 @@ transfer to a flash cart there's a little more to do.
`cargo` arranges stuff in the `target/` directory, and between RLS and `cargo` arranges stuff in the `target/` directory, and between RLS and
`cargo doc` and stuff it gets kinda crowded, so it goes like this: `cargo doc` and stuff it gets kinda crowded, so it goes like this:
* Since our program was built for a non-local target, first we've got a * Since our program was built for a non-local target, first we've got a
directory named for that target, `thumbv4-nintendo-agb/` directory named for that target, `thumbv4-none-agb/`
* Next, the "MODE" is either `debug/` or `release/`, depending on if we had * Next, the "MODE" is either `debug/` or `release/`, depending on if we had
the `--release` flag included. You'll probably only be packing release the `--release` flag included. You'll probably only be packing release
mode programs all the way into GBA roms, but it works with either mode. mode programs all the way into GBA roms, but it works with either mode.

View file

@ -177,3 +177,8 @@ If you ever use volatile stuff on other platforms it's important to note that
volatile doesn't make things thread-safe, you still need atomic for that. volatile doesn't make things thread-safe, you still need atomic for that.
However, the GBA doesn't have threads, so we don't have to worry about thread However, the GBA doesn't have threads, so we don't have to worry about thread
safety concerns. safety concerns.
Accordingly, our first bit of code for our library will be a _newtype_ over a
normal `*mut T` so that it has volatile reads and writes as the default. We'll
cover the details later on when we try writing a `hello2` program, once we know
more of what's going on.

114
book/src/ch1/hello2.md Normal file
View file

@ -0,0 +1,114 @@
# hello2
Okay so let's have a look again:
`hello1`
```rust
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
```
Now let's clean this up so that it's clearer what's going on.
First we'll label that display control stuff:
```rust
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
```
Next we make some const values for the actual pixel drawing
```rust
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
```
And then we want a small helper function for putting together a color value.
Happily, this one can even be declared as a const function. At the time of
writing, we've got the "minimal const fn" support in nightly. It really is quite
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
when it comes to the GBA's tiny CPU.
```rust
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
```
Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
just a one-liner, having the "important parts" be labeled as function arguments
usually helps you think about it a lot better.
```rust
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
```
So now we've got this:
`hello2`
```rust
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
```
Exact same program that we started with, but much easier to read.
Of course, in the full `gba` crate that this book is a part of we have these and
other elements all labeled and sorted out for you. Still, for educational
purposes it's often best to do it yourself at least once.

View file

@ -27,9 +27,10 @@ own demos and chapters later on, so that's all we'll say about them for now.
Modes 3, 4, and 5 are "Bitmap" modes. These let you write individual pixels to Modes 3, 4, and 5 are "Bitmap" modes. These let you write individual pixels to
locations on the screen. locations on the screen.
* **Mode 3** is full resolution (240w x 160h) RBG15 color. You might not be used to * **Mode 3** is full resolution (240w x 160h) RBG15 color. You might not be used
RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5 to RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5
bits for each color channel, and the highest bit is totally ignored. bits for each color channel stored within a `u16` value, and the highest bit is
simply ignored.
* **Mode 4** is full resolution paletted color. Instead of being a `u16` color, each * **Mode 4** is full resolution paletted color. Instead of being a `u16` color, each
pixel value is a `u8` palette index entry, and then the display uses the pixel value is a `u8` palette index entry, and then the display uses the
palette memory (which we'll talk about later) to store the actual color data. palette memory (which we'll talk about later) to store the actual color data.
@ -99,3 +100,6 @@ First let's [convert that to
binary](https://www.wolframalpha.com/input/?i=0x0403), and we get binary](https://www.wolframalpha.com/input/?i=0x0403), and we get
`0b100_0000_0011`. So, that's setting Mode 3 with background 2 enabled and `0b100_0000_0011`. So, that's setting Mode 3 with background 2 enabled and
nothing else special. nothing else special.
However, I think we can do better than that. This is a prime target for more
newtyping as we attempt a `hello2` program.

View file

@ -18,20 +18,96 @@ don't know to follow the special rules.
## RGB15 ## RGB15
TODO As I said before, RGB15 stores a color within a `u16` value using 5 bits for
each color channel.
```rust
pub const RED: u16 = 0b0_00000_00000_11111;
pub const GREEN: u16 = 0b0_00000_11111_00000;
pub const BLUE: u16 = 0b0_11111_00000_00000;
```
In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we
write palette index values, and then the color values go into the PALRAM.
## Mode 3 ## Mode 3
TODO Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's
160 rows of 240 pixels each, with the base address being the top left corner. A
particular pixel uses normal "2d indexing" math:
```rust
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
```
To draw a pixel, we just write a value at the address for the row and col that
we want to draw to.
## Mode 4 ## Mode 4
TODO Mode 4 introduces page flipping. Instead of one giant page at `0x0600_0000`,
there's Page 0 at `0x0600_0000` and then Page 1 at `0x0600_A000`. The resolution
for each page is the same as above, but instead of writing `u16` values, the
memory is treated as `u8` indexes into PALRAM. The PALRAM starts at
`0x0500_0000`, and there's enough space for 256 palette entries (each a `u16`).
To set the color of a palette entry we just do a normal `u16` write_volatile.
```rust
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
```
To draw a pixel we set the palette entry that we want the pixel to use. However,
we must remember the "minimum size" write limitation that applies to VRAM. So,
if we want to change just a single pixel at a time we must
1) Read the full `u16` it's a part of.
2) Clear the half of the `u16` we're going to replace
3) Write the half of the `u16` we're going to replace with the new value
4) Write that result back to the address.
So, the math for finding a byte offset is the same as Mode 3 (since they're both
a 2d grid). If the byte offset is EVEN it'll be the high bits of the `u16` at
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
the `u16` at half the byte.
Does that make sense?
* If we want to write pixel (0,0) the byte offset is 0, so we change the high
bits of `u16` offset 0. Then we want to write to (1,0), so the byte offset is
1, so we change the low bits of `u16` offset 0. The pixels are next to each
other, and the target bytes are next to each other, good so far.
* If we want to write to (5,6) that'd be byte `5 + 6 * 240 = 1445`, so we'd
target the low bits of `u16` offset `floor(1445/2) = 722`.
As you can see, trying to write individual pixels in Mode 4 is mostly a bad
time. Fret not! We don't _have_ to write individual bytes. If our data is
arranged correctly ahead of time we can just write `u16` or `u32` values
directly. The video hardware doesn't care, it'll get along just fine.
## Mode 5 ## Mode 5
TODO Mode 5 is also a two page mode, but instead of compressing the size of a pixel's
data to fit in two pages, we compress the resolution.
Mode 5 is full `u16` color, but only 160w x 128h per page.
## In Conclusion... ## In Conclusion...
TODO So what got written into VRAM in `hello1`?
```rust
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
```
So at pixels `(120,80)`, `(136,80)`, and `(120,96)` we write three values. Once
again we probably need to [convert them](https://www.wolframalpha.com/) into
binary to make sense of it.
* 0x001F: 0b11111
* 0x03E0: 0b11111_00000
* 0x7C00: 0b11111_00000_00000
Ah, of course, a red pixel, a green pixel, and a blue pixel.

View file

@ -3,12 +3,12 @@
arm-none-eabi-as crt0.s -o crt0.o arm-none-eabi-as crt0.s -o crt0.o
@rem Build all examples, both debug and release @rem Build all examples, both debug and release
cargo xbuild --examples --target thumbv4-nintendo-agb.json cargo xbuild --examples --target thumbv4-none-agb.json
cargo xbuild --examples --target thumbv4-nintendo-agb.json --release cargo xbuild --examples --target thumbv4-none-agb.json --release
@echo Packing examples into ROM files... @echo Packing examples into ROM files...
@for %%I in (.\examples\*.*) do @( @for %%I in (.\examples\*.*) do @(
echo %%~nI echo %%~nI
arm-none-eabi-objcopy -O binary target/thumbv4-nintendo-agb/release/examples/%%~nI target/example-%%~nI.gba >nul arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/release/examples/%%~nI target/example-%%~nI.gba >nul
gbafix target/example-%%~nI.gba >nul gbafix target/example-%%~nI.gba >nul
) )

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html" class="active"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html" class="active"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
@ -169,8 +169,12 @@ cargo will figure it all out for you.</p>
You can find them in the root of the <a href="https://github.com/rust-console/gba">rust-console/gba You can find them in the root of the <a href="https://github.com/rust-console/gba">rust-console/gba
repo</a>.</p> repo</a>.</p>
<ul> <ul>
<li><code>thumbv4-nintendo-agb.json</code> describes the overall GBA to cargo-xbuild so it knows <li><code>thumbv4-none-agb.json</code> describes the overall GBA to cargo-xbuild so it knows
what to do.</li> what to do. This is actually a somewhat made up target name since there's no
official target name. The GBA is essentially the same as a normal
<code>thumbv4-none-eabi</code> device, but we give it the &quot;agb&quot; signifier so that later
on we'll be able to use rust's <code>cfg</code> ability to allow our code to know if it's
specifically targeting a GBA or some other similar device (like an NDS).</li>
<li><code>crt0.s</code> describes some ASM startup stuff. If you have more ASM to place here <li><code>crt0.s</code> describes some ASM startup stuff. If you have more ASM to place here
later on this is where you can put it. You also need to build it into a later on this is where you can put it. You also need to build it into a
<code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li> <code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li>
@ -190,7 +194,7 @@ practically instant operation.</li>
</ul> </ul>
</li> </li>
<li> <li>
<p><code>cargo xbuild --target thumbv4-nintendo-agb.json</code></p> <p><code>cargo xbuild --target thumbv4-none-agb.json</code></p>
<ul> <ul>
<li>This builds your Rust source. It accepts <em>most of</em> the normal options, such <li>This builds your Rust source. It accepts <em>most of</em> the normal options, such
as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd
@ -219,7 +223,7 @@ emulators can also do it.</p>
transfer to a flash cart there's a little more to do.</p> transfer to a flash cart there's a little more to do.</p>
<ul> <ul>
<li> <li>
<p><code>arm-none-eabi-objcopy -O binary target/thumbv4-nintendo-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p> <p><code>arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p>
<ul> <ul>
<li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our <li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our
program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what
@ -235,7 +239,7 @@ bare memory dump of the program.</li>
<code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this: <code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this:
<ul> <ul>
<li>Since our program was built for a non-local target, first we've got a <li>Since our program was built for a non-local target, first we've got a
directory named for that target, <code>thumbv4-nintendo-agb/</code></li> directory named for that target, <code>thumbv4-none-agb/</code></li>
<li>Next, the &quot;MODE&quot; is either <code>debug/</code> or <code>release/</code>, depending on if we had <li>Next, the &quot;MODE&quot; is either <code>debug/</code> or <code>release/</code>, depending on if we had
the <code>--release</code> flag included. You'll probably only be packing release the <code>--release</code> flag included. You'll probably only be packing release
mode programs all the way into GBA roms, but it works with either mode.</li> mode programs all the way into GBA roms, but it works with either mode.</li>

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html" class="active"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html" class="active"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
@ -293,6 +293,10 @@ write to <code>d</code> will <em>always</em> happen after the write to <code>c</
volatile doesn't make things thread-safe, you still need atomic for that. volatile doesn't make things thread-safe, you still need atomic for that.
However, the GBA doesn't have threads, so we don't have to worry about thread However, the GBA doesn't have threads, so we don't have to worry about thread
safety concerns.</p> safety concerns.</p>
<p>Accordingly, our first bit of code for our library will be a <em>newtype</em> over a
normal <code>*mut T</code> so that it has volatile reads and writes as the default. We'll
cover the details later on when we try writing a <code>hello2</code> program, once we know
more of what's going on.</p>
</main> </main>

289
docs/ch1/hello2.html Normal file
View file

@ -0,0 +1,289 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>hello2 - Rust GBA Tutorials</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">var path_to_root = "../";</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html" class="active"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust GBA Tutorials</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<a class="header" href="#hello2" id="hello2"><h1>hello2</h1></a>
<p>Okay so let's have a look again:</p>
<p><code>hello1</code></p>
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -&gt; isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
</code></pre></pre>
<p>Now let's clean this up so that it's clearer what's going on.</p>
<p>First we'll label that display control stuff:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
#}</code></pre></pre>
<p>Next we make some const values for the actual pixel drawing</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
#}</code></pre></pre>
<p>And then we want a small helper function for putting together a color value.</p>
<p>Happily, this one can even be declared as a const function. At the time of
writing, we've got the &quot;minimal const fn&quot; support in nightly. It really is quite
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
when it comes to the GBA's tiny CPU.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const fn rgb16(red: u16, green: u16, blue: u16) -&gt; u16 {
blue &lt;&lt; 10 | green &lt;&lt; 5 | red
}
#}</code></pre></pre>
<p>Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
just a one-liner, having the &quot;important parts&quot; be labeled as function arguments
usually helps you think about it a lot better.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
#}</code></pre></pre>
<p>So now we've got this:</p>
<p><code>hello2</code></p>
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -&gt; isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -&gt; u16 {
blue &lt;&lt; 10 | green &lt;&lt; 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
</code></pre></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ch1/video_memory_intro.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="../ch1/video_memory_intro.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
</nav>
</div>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html" class="active"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html" class="active"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html" class="active"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html" class="active"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html" class="active"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html" class="active"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
@ -157,9 +157,10 @@ own demos and chapters later on, so that's all we'll say about them for now.</p>
<p>Modes 3, 4, and 5 are &quot;Bitmap&quot; modes. These let you write individual pixels to <p>Modes 3, 4, and 5 are &quot;Bitmap&quot; modes. These let you write individual pixels to
locations on the screen.</p> locations on the screen.</p>
<ul> <ul>
<li><strong>Mode 3</strong> is full resolution (240w x 160h) RBG15 color. You might not be used to <li><strong>Mode 3</strong> is full resolution (240w x 160h) RBG15 color. You might not be used
RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5 to RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5
bits for each color channel, and the highest bit is totally ignored.</li> bits for each color channel stored within a <code>u16</code> value, and the highest bit is
simply ignored.</li>
<li><strong>Mode 4</strong> is full resolution paletted color. Instead of being a <code>u16</code> color, each <li><strong>Mode 4</strong> is full resolution paletted color. Instead of being a <code>u16</code> color, each
pixel value is a <code>u8</code> palette index entry, and then the display uses the pixel value is a <code>u8</code> palette index entry, and then the display uses the
palette memory (which we'll talk about later) to store the actual color data. palette memory (which we'll talk about later) to store the actual color data.
@ -172,9 +173,10 @@ reduced resolution to compensate (video memory is only so big!). The screen is
effectively only 160w x 128h in this mode.</li> effectively only 160w x 128h in this mode.</li>
</ul> </ul>
<a class="header" href="#cgb-mode" id="cgb-mode"><h2>CGB Mode</h2></a> <a class="header" href="#cgb-mode" id="cgb-mode"><h2>CGB Mode</h2></a>
<p>Bit 3 is read only. It's on if you're running in CGB mode. Since we're making <p>Bit 3 is effectively read only. Technically it can be flipped using a BIOS call,
GBA games you'd think that it'll never be on at all, but I guess you can change but when you write to the display control normally it won't write to this bit,
it with BIOS stuff. Still, basically not an important bit.</p> so we'll call it effectively read only.</p>
<p>This bit is on if the CPU is in CGB mode.</p>
<a class="header" href="#page-flipping" id="page-flipping"><h2>Page Flipping</h2></a> <a class="header" href="#page-flipping" id="page-flipping"><h2>Page Flipping</h2></a>
<p>Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or <p>Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or
5, and is just ignored otherwise. It's very easy to remember: when the bit is 0 5, and is just ignored otherwise. It's very easy to remember: when the bit is 0
@ -213,6 +215,8 @@ some nifty graphical effects.</p>
binary</a>, and we get binary</a>, and we get
<code>0b100_0000_0011</code>. So, that's setting Mode 3 with background 2 enabled and <code>0b100_0000_0011</code>. So, that's setting Mode 3 with background 2 enabled and
nothing else special.</p> nothing else special.</p>
<p>However, I think we can do better than that. This is a prime target for more
newtyping as we attempt a <code>hello2</code> program.</p>
</main> </main>

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html" class="active"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="../ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="../ch1/video_memory_intro.html" class="active"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="../ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
@ -151,15 +151,88 @@ trying to set an individual byte, and we also have to be careful if we use
<code>memcopy</code> or <code>memset</code> as well, because they're byte oriented by default and <code>memcopy</code> or <code>memset</code> as well, because they're byte oriented by default and
don't know to follow the special rules.</p> don't know to follow the special rules.</p>
<a class="header" href="#rgb15" id="rgb15"><h2>RGB15</h2></a> <a class="header" href="#rgb15" id="rgb15"><h2>RGB15</h2></a>
<p>TODO</p> <p>As I said before, RGB15 stores a color within a <code>u16</code> value using 5 bits for
each color channel.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const RED: u16 = 0b0_00000_00000_11111;
pub const GREEN: u16 = 0b0_00000_11111_00000;
pub const BLUE: u16 = 0b0_11111_00000_00000;
#}</code></pre></pre>
<p>In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we
write palette index values, and then the color values go into the PALRAM.</p>
<a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a> <a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a>
<p>TODO</p> <p>Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's
160 rows of 240 pixels each, with the base address being the top left corner. A
particular pixel uses normal &quot;2d indexing&quot; math:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
#}</code></pre></pre>
<p>To draw a pixel, we just write a value at the address for the row and col that
we want to draw to.</p>
<a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a> <a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a>
<p>TODO</p> <p>Mode 4 introduces page flipping. Instead of one giant page at <code>0x0600_0000</code>,
there's Page 0 at <code>0x0600_0000</code> and then Page 1 at <code>0x0600_A000</code>. The resolution
for each page is the same as above, but instead of writing <code>u16</code> values, the
memory is treated as <code>u8</code> indexes into PALRAM. The PALRAM starts at
<code>0x0500_0000</code>, and there's enough space for 256 palette entries (each a <code>u16</code>).</p>
<p>To set the color of a palette entry we just do a normal <code>u16</code> write_volatile.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
#}</code></pre></pre>
<p>To draw a pixel we set the palette entry that we want the pixel to use. However,
we must remember the &quot;minimum size&quot; write limitation that applies to VRAM. So,
if we want to change just a single pixel at a time we must</p>
<ol>
<li>Read the full <code>u16</code> it's a part of.</li>
<li>Clear the half of the <code>u16</code> we're going to replace</li>
<li>Write the half of the <code>u16</code> we're going to replace with the new value</li>
<li>Write that result back to the address.</li>
</ol>
<p>So, the math for finding a byte offset is the same as Mode 3 (since they're both
a 2d grid). If the byte offset is EVEN it'll be the high bits of the <code>u16</code> at
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
the <code>u16</code> at half the byte.</p>
<p>Does that make sense?</p>
<ul>
<li>If we want to write pixel (0,0) the byte offset is 0, so we change the high
bits of <code>u16</code> offset 0. Then we want to write to (1,0), so the byte offset is
1, so we change the low bits of <code>u16</code> offset 0. The pixels are next to each
other, and the target bytes are next to each other, good so far.</li>
<li>If we want to write to (5,6) that'd be byte <code>5 + 6 * 240 = 1445</code>, so we'd
target the low bits of <code>u16</code> offset <code>floor(1445/2) = 722</code>.</li>
</ul>
<p>As you can see, trying to write individual pixels in Mode 4 is mostly a bad
time. Fret not! We don't <em>have</em> to write individual bytes. If our data is
arranged correctly ahead of time we can just write <code>u16</code> or <code>u32</code> values
directly. The video hardware doesn't care, it'll get along just fine.</p>
<a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a> <a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a>
<p>TODO</p> <p>Mode 5 is also a two page mode, but instead of compressing the size of a pixel's
data to fit in two pages, we compress the resolution.</p>
<p>Mode 5 is full <code>u16</code> color, but only 160w x 128h per page.</p>
<a class="header" href="#in-conclusion" id="in-conclusion"><h2>In Conclusion...</h2></a> <a class="header" href="#in-conclusion" id="in-conclusion"><h2>In Conclusion...</h2></a>
<p>TODO</p> <p>So what got written into VRAM in <code>hello1</code>?</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
#}</code></pre></pre>
<p>So at pixels <code>(120,80)</code>, <code>(136,80)</code>, and <code>(120,96)</code> we write three values. Once
again we probably need to <a href="https://www.wolframalpha.com/">convert them</a> into
binary to make sense of it.</p>
<ul>
<li>0x001F: 0b11111</li>
<li>0x03E0: 0b11111_00000</li>
<li>0x7C00: 0b11111_00000_00000</li>
</ul>
<p>Ah, of course, a red pixel, a green pixel, and a blue pixel.</p>
</main> </main>
@ -172,6 +245,10 @@ don't know to follow the special rules.</p>
<a rel="next" href="../ch1/hello2.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div> <div style="clear: both"></div>
</nav> </nav>
@ -186,6 +263,10 @@ don't know to follow the special rules.</p>
<a href="../ch1/hello2.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav> </nav>
</div> </div>

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="introduction.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="introduction.html" class="active"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">

View file

@ -72,7 +72,7 @@
</script> </script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li></ol></li></ol> <ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch0/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch1/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch1/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch1/io_registers.html"><strong aria-hidden="true">3.2.</strong> IO Registers</a></li><li><a href="ch1/the_display_control.html"><strong aria-hidden="true">3.3.</strong> The Display Control</a></li><li><a href="ch1/video_memory_intro.html"><strong aria-hidden="true">3.4.</strong> Video Memory Intro</a></li><li><a href="ch1/hello2.html"><strong aria-hidden="true">3.5.</strong> hello2</a></li></ol></li></ol>
</nav> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
@ -181,8 +181,12 @@ cargo will figure it all out for you.</p>
You can find them in the root of the <a href="https://github.com/rust-console/gba">rust-console/gba You can find them in the root of the <a href="https://github.com/rust-console/gba">rust-console/gba
repo</a>.</p> repo</a>.</p>
<ul> <ul>
<li><code>thumbv4-nintendo-agb.json</code> describes the overall GBA to cargo-xbuild so it knows <li><code>thumbv4-none-agb.json</code> describes the overall GBA to cargo-xbuild so it knows
what to do.</li> what to do. This is actually a somewhat made up target name since there's no
official target name. The GBA is essentially the same as a normal
<code>thumbv4-none-eabi</code> device, but we give it the &quot;agb&quot; signifier so that later
on we'll be able to use rust's <code>cfg</code> ability to allow our code to know if it's
specifically targeting a GBA or some other similar device (like an NDS).</li>
<li><code>crt0.s</code> describes some ASM startup stuff. If you have more ASM to place here <li><code>crt0.s</code> describes some ASM startup stuff. If you have more ASM to place here
later on this is where you can put it. You also need to build it into a later on this is where you can put it. You also need to build it into a
<code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li> <code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li>
@ -202,7 +206,7 @@ practically instant operation.</li>
</ul> </ul>
</li> </li>
<li> <li>
<p><code>cargo xbuild --target thumbv4-nintendo-agb.json</code></p> <p><code>cargo xbuild --target thumbv4-none-agb.json</code></p>
<ul> <ul>
<li>This builds your Rust source. It accepts <em>most of</em> the normal options, such <li>This builds your Rust source. It accepts <em>most of</em> the normal options, such
as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd
@ -231,7 +235,7 @@ emulators can also do it.</p>
transfer to a flash cart there's a little more to do.</p> transfer to a flash cart there's a little more to do.</p>
<ul> <ul>
<li> <li>
<p><code>arm-none-eabi-objcopy -O binary target/thumbv4-nintendo-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p> <p><code>arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p>
<ul> <ul>
<li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our <li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our
program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what
@ -247,7 +251,7 @@ bare memory dump of the program.</li>
<code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this: <code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this:
<ul> <ul>
<li>Since our program was built for a non-local target, first we've got a <li>Since our program was built for a non-local target, first we've got a
directory named for that target, <code>thumbv4-nintendo-agb/</code></li> directory named for that target, <code>thumbv4-none-agb/</code></li>
<li>Next, the &quot;MODE&quot; is either <code>debug/</code> or <code>release/</code>, depending on if we had <li>Next, the &quot;MODE&quot; is either <code>debug/</code> or <code>release/</code>, depending on if we had
the <code>--release</code> flag included. You'll probably only be packing release the <code>--release</code> flag included. You'll probably only be packing release
mode programs all the way into GBA roms, but it works with either mode.</li> mode programs all the way into GBA roms, but it works with either mode.</li>
@ -443,6 +447,10 @@ write to <code>d</code> will <em>always</em> happen after the write to <code>c</
volatile doesn't make things thread-safe, you still need atomic for that. volatile doesn't make things thread-safe, you still need atomic for that.
However, the GBA doesn't have threads, so we don't have to worry about thread However, the GBA doesn't have threads, so we don't have to worry about thread
safety concerns.</p> safety concerns.</p>
<p>Accordingly, our first bit of code for our library will be a <em>newtype</em> over a
normal <code>*mut T</code> so that it has volatile reads and writes as the default. We'll
cover the details later on when we try writing a <code>hello2</code> program, once we know
more of what's going on.</p>
<a class="header" href="#io-registers" id="io-registers"><h1>IO Registers</h1></a> <a class="header" href="#io-registers" id="io-registers"><h1>IO Registers</h1></a>
<p>The GBA has a large number of <strong>IO Registers</strong> (not to be confused with CPU <p>The GBA has a large number of <strong>IO Registers</strong> (not to be confused with CPU
registers). These are special memory locations from <code>0x04000000</code> to registers). These are special memory locations from <code>0x04000000</code> to
@ -498,9 +506,10 @@ own demos and chapters later on, so that's all we'll say about them for now.</p>
<p>Modes 3, 4, and 5 are &quot;Bitmap&quot; modes. These let you write individual pixels to <p>Modes 3, 4, and 5 are &quot;Bitmap&quot; modes. These let you write individual pixels to
locations on the screen.</p> locations on the screen.</p>
<ul> <ul>
<li><strong>Mode 3</strong> is full resolution (240w x 160h) RBG15 color. You might not be used to <li><strong>Mode 3</strong> is full resolution (240w x 160h) RBG15 color. You might not be used
RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5 to RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5
bits for each color channel, and the highest bit is totally ignored.</li> bits for each color channel stored within a <code>u16</code> value, and the highest bit is
simply ignored.</li>
<li><strong>Mode 4</strong> is full resolution paletted color. Instead of being a <code>u16</code> color, each <li><strong>Mode 4</strong> is full resolution paletted color. Instead of being a <code>u16</code> color, each
pixel value is a <code>u8</code> palette index entry, and then the display uses the pixel value is a <code>u8</code> palette index entry, and then the display uses the
palette memory (which we'll talk about later) to store the actual color data. palette memory (which we'll talk about later) to store the actual color data.
@ -513,9 +522,10 @@ reduced resolution to compensate (video memory is only so big!). The screen is
effectively only 160w x 128h in this mode.</li> effectively only 160w x 128h in this mode.</li>
</ul> </ul>
<a class="header" href="#cgb-mode" id="cgb-mode"><h2>CGB Mode</h2></a> <a class="header" href="#cgb-mode" id="cgb-mode"><h2>CGB Mode</h2></a>
<p>Bit 3 is read only. It's on if you're running in CGB mode. Since we're making <p>Bit 3 is effectively read only. Technically it can be flipped using a BIOS call,
GBA games you'd think that it'll never be on at all, but I guess you can change but when you write to the display control normally it won't write to this bit,
it with BIOS stuff. Still, basically not an important bit.</p> so we'll call it effectively read only.</p>
<p>This bit is on if the CPU is in CGB mode.</p>
<a class="header" href="#page-flipping" id="page-flipping"><h2>Page Flipping</h2></a> <a class="header" href="#page-flipping" id="page-flipping"><h2>Page Flipping</h2></a>
<p>Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or <p>Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or
5, and is just ignored otherwise. It's very easy to remember: when the bit is 0 5, and is just ignored otherwise. It's very easy to remember: when the bit is 0
@ -554,6 +564,8 @@ some nifty graphical effects.</p>
binary</a>, and we get binary</a>, and we get
<code>0b100_0000_0011</code>. So, that's setting Mode 3 with background 2 enabled and <code>0b100_0000_0011</code>. So, that's setting Mode 3 with background 2 enabled and
nothing else special.</p> nothing else special.</p>
<p>However, I think we can do better than that. This is a prime target for more
newtyping as we attempt a <code>hello2</code> program.</p>
<a class="header" href="#video-memory-intro" id="video-memory-intro"><h1>Video Memory Intro</h1></a> <a class="header" href="#video-memory-intro" id="video-memory-intro"><h1>Video Memory Intro</h1></a>
<p>The GBA's Video RAM is 96k stretching from <code>0x0600_0000</code> to <code>0x0601_7FFF</code>.</p> <p>The GBA's Video RAM is 96k stretching from <code>0x0600_0000</code> to <code>0x0601_7FFF</code>.</p>
<p>The Video RAM can only be accessed totally freely during a Vertical Blank <p>The Video RAM can only be accessed totally freely during a Vertical Blank
@ -569,15 +581,186 @@ trying to set an individual byte, and we also have to be careful if we use
<code>memcopy</code> or <code>memset</code> as well, because they're byte oriented by default and <code>memcopy</code> or <code>memset</code> as well, because they're byte oriented by default and
don't know to follow the special rules.</p> don't know to follow the special rules.</p>
<a class="header" href="#rgb15" id="rgb15"><h2>RGB15</h2></a> <a class="header" href="#rgb15" id="rgb15"><h2>RGB15</h2></a>
<p>TODO</p> <p>As I said before, RGB15 stores a color within a <code>u16</code> value using 5 bits for
each color channel.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const RED: u16 = 0b0_00000_00000_11111;
pub const GREEN: u16 = 0b0_00000_11111_00000;
pub const BLUE: u16 = 0b0_11111_00000_00000;
#}</code></pre></pre>
<p>In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we
write palette index values, and then the color values go into the PALRAM.</p>
<a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a> <a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a>
<p>TODO</p> <p>Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's
160 rows of 240 pixels each, with the base address being the top left corner. A
particular pixel uses normal &quot;2d indexing&quot; math:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
#}</code></pre></pre>
<p>To draw a pixel, we just write a value at the address for the row and col that
we want to draw to.</p>
<a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a> <a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a>
<p>TODO</p> <p>Mode 4 introduces page flipping. Instead of one giant page at <code>0x0600_0000</code>,
there's Page 0 at <code>0x0600_0000</code> and then Page 1 at <code>0x0600_A000</code>. The resolution
for each page is the same as above, but instead of writing <code>u16</code> values, the
memory is treated as <code>u8</code> indexes into PALRAM. The PALRAM starts at
<code>0x0500_0000</code>, and there's enough space for 256 palette entries (each a <code>u16</code>).</p>
<p>To set the color of a palette entry we just do a normal <code>u16</code> write_volatile.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
#}</code></pre></pre>
<p>To draw a pixel we set the palette entry that we want the pixel to use. However,
we must remember the &quot;minimum size&quot; write limitation that applies to VRAM. So,
if we want to change just a single pixel at a time we must</p>
<ol>
<li>Read the full <code>u16</code> it's a part of.</li>
<li>Clear the half of the <code>u16</code> we're going to replace</li>
<li>Write the half of the <code>u16</code> we're going to replace with the new value</li>
<li>Write that result back to the address.</li>
</ol>
<p>So, the math for finding a byte offset is the same as Mode 3 (since they're both
a 2d grid). If the byte offset is EVEN it'll be the high bits of the <code>u16</code> at
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
the <code>u16</code> at half the byte.</p>
<p>Does that make sense?</p>
<ul>
<li>If we want to write pixel (0,0) the byte offset is 0, so we change the high
bits of <code>u16</code> offset 0. Then we want to write to (1,0), so the byte offset is
1, so we change the low bits of <code>u16</code> offset 0. The pixels are next to each
other, and the target bytes are next to each other, good so far.</li>
<li>If we want to write to (5,6) that'd be byte <code>5 + 6 * 240 = 1445</code>, so we'd
target the low bits of <code>u16</code> offset <code>floor(1445/2) = 722</code>.</li>
</ul>
<p>As you can see, trying to write individual pixels in Mode 4 is mostly a bad
time. Fret not! We don't <em>have</em> to write individual bytes. If our data is
arranged correctly ahead of time we can just write <code>u16</code> or <code>u32</code> values
directly. The video hardware doesn't care, it'll get along just fine.</p>
<a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a> <a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a>
<p>TODO</p> <p>Mode 5 is also a two page mode, but instead of compressing the size of a pixel's
data to fit in two pages, we compress the resolution.</p>
<p>Mode 5 is full <code>u16</code> color, but only 160w x 128h per page.</p>
<a class="header" href="#in-conclusion-1" id="in-conclusion-1"><h2>In Conclusion...</h2></a> <a class="header" href="#in-conclusion-1" id="in-conclusion-1"><h2>In Conclusion...</h2></a>
<p>TODO</p> <p>So what got written into VRAM in <code>hello1</code>?</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
#}</code></pre></pre>
<p>So at pixels <code>(120,80)</code>, <code>(136,80)</code>, and <code>(120,96)</code> we write three values. Once
again we probably need to <a href="https://www.wolframalpha.com/">convert them</a> into
binary to make sense of it.</p>
<ul>
<li>0x001F: 0b11111</li>
<li>0x03E0: 0b11111_00000</li>
<li>0x7C00: 0b11111_00000_00000</li>
</ul>
<p>Ah, of course, a red pixel, a green pixel, and a blue pixel.</p>
<a class="header" href="#hello2" id="hello2"><h1>hello2</h1></a>
<p>Okay so let's have a look again:</p>
<p><code>hello1</code></p>
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -&gt; isize {
unsafe {
(0x04000000 as *mut u16).write_volatile(0x0403);
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
</code></pre></pre>
<p>Now let's clean this up so that it's clearer what's going on.</p>
<p>First we'll label that display control stuff:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
#}</code></pre></pre>
<p>Next we make some const values for the actual pixel drawing</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
#}</code></pre></pre>
<p>And then we want a small helper function for putting together a color value.</p>
<p>Happily, this one can even be declared as a const function. At the time of
writing, we've got the &quot;minimal const fn&quot; support in nightly. It really is quite
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
when it comes to the GBA's tiny CPU.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const fn rgb16(red: u16, green: u16, blue: u16) -&gt; u16 {
blue &lt;&lt; 10 | green &lt;&lt; 5 | red
}
#}</code></pre></pre>
<p>Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
just a one-liner, having the &quot;important parts&quot; be labeled as function arguments
usually helps you think about it a lot better.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
#}</code></pre></pre>
<p>So now we've got this:</p>
<p><code>hello2</code></p>
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -&gt; isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -&gt; u16 {
blue &lt;&lt; 10 | green &lt;&lt; 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
</code></pre></pre>
</main> </main>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

34
examples/hello2.rs Normal file
View file

@ -0,0 +1,34 @@
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(MODE3 | BG2);
mode3_pixel(120, 80, rgb16(31, 0, 0));
mode3_pixel(136, 80, rgb16(0, 31, 0));
mode3_pixel(120, 96, rgb16(0, 0, 31));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}

View file

@ -1,13 +1,13 @@
//! Things that I wish were in core, but aren't. //! Things that I wish were in core, but aren't.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
/// A simple wrapper to any `*mut T` so that the basic "read" and "write" /// A simple wrapper to any `*mut T` so that the basic "read" and "write"
/// operations are volatile. /// operations are volatile.
/// ///
/// Accessing the GBA's IO registers and video ram and specific other places on /// Accessing the GBA's IO registers and video ram and specific other places on
/// **must** be done with volatile operations. Having this wrapper makes that /// **must** be done with volatile operations. Having this wrapper makes that
/// more clear for all the global const values into IO registers. /// more clear for all the global const values into IO registers.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct VolatilePtr<T>(pub *mut T); pub struct VolatilePtr<T>(pub *mut T);
impl<T> core::fmt::Pointer for VolatilePtr<T> { impl<T> core::fmt::Pointer for VolatilePtr<T> {

View file

@ -28,5 +28,5 @@ pub mod video_ram;
/// Combines the Red, Blue, and Green provided into a single color value. /// Combines the Red, Blue, and Green provided into a single color value.
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
red | green << 5 | blue << 10 blue << 10 | green << 5 | red
} }

View file

@ -28,6 +28,13 @@ pub const SCREEN_HEIGHT: isize = 160;
/// value as just being a `usize`. /// value as just being a `usize`.
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000; pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
pub fn mode3_pixel(col: isize, row: isize, color: u16) {
assert!(col >= 0 && col < SCREEN_WIDTH);
assert!(row >= 0 && row < SCREEN_HEIGHT);
unsafe { mode3_pixel_unchecked(col, row, color) }
}
/// Draws a pixel to the screen while in Display Mode 3. /// Draws a pixel to the screen while in Display Mode 3.
/// ///
/// Coordinates are relative to the top left corner. /// Coordinates are relative to the top left corner.
@ -39,13 +46,6 @@ pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
/// ///
/// * `col` must be in `0..SCREEN_WIDTH` /// * `col` must be in `0..SCREEN_WIDTH`
/// * `row` must be in `0..SCREEN_HEIGHT` /// * `row` must be in `0..SCREEN_HEIGHT`
pub unsafe fn mode3_plot_unchecked(col: isize, row: isize, color: u16) { pub unsafe fn mode3_pixel_unchecked(col: isize, row: isize, color: u16) {
core::ptr::write_volatile((VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH), color); core::ptr::write_volatile((VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH), color);
} }
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
pub fn mode3_plot(col: isize, row: isize, color: u16) {
assert!(col >= 0 && col < SCREEN_WIDTH);
assert!(row >= 0 && row < SCREEN_HEIGHT);
unsafe { mode3_plot_unchecked(col, row, color) }
}

View file

@ -18,7 +18,7 @@
"linker": "arm-none-eabi-ld", "linker": "arm-none-eabi-ld",
"linker-flavor": "ld", "linker-flavor": "ld",
"linker-is-gnu": true, "linker-is-gnu": true,
"llvm-target": "thumbv4-nintendo-agb", "llvm-target": "thumbv4-none-agb",
"os": "none", "os": "none",
"panic-strategy": "abort", "panic-strategy": "abort",
"pre-link-args": { "pre-link-args": {