hello2

Okay so let's have a look again:

hello1

#![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:


# #![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;
#}

Next we make some const values for the actual pixel drawing


# #![allow(unused_variables)]
#fn main() {
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.


# #![allow(unused_variables)]
#fn main() {
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.


# #![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);
}
#}

So now we've got this:

hello2

#![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.