From 2f3becee59a0927343ee5021c780007b3ee50aaf Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 20 Dec 2018 17:20:59 -0700 Subject: [PATCH 01/13] Update Volatile description to match new definition --- .../src/00-introduction/02-goals_and_style.md | 2 +- book/src/01-quirks/03-volatile_destination.md | 562 +++++++++--------- 2 files changed, 281 insertions(+), 283 deletions(-) diff --git a/book/src/00-introduction/02-goals_and_style.md b/book/src/00-introduction/02-goals_and_style.md index 268afbe..98fd86b 100644 --- a/book/src/00-introduction/02-goals_and_style.md +++ b/book/src/00-introduction/02-goals_and_style.md @@ -21,7 +21,7 @@ Overall the book is sorted for easy review once you're trying to program something, and the GBA has a few interconnected concepts, so some parts of the book end up having to refer you to portions that you haven't read yet. The chapters and sections are sorted so that _minimal_ future references are -required, but it's unavoidable. +required, but it's unavoidable that it'll happen sometimes. The actual "tutorial order" of the book is the [Examples](../05-examples/00-index.md) chapter. Each section of that chapter diff --git a/book/src/01-quirks/03-volatile_destination.md b/book/src/01-quirks/03-volatile_destination.md index f03c62c..ce50a98 100644 --- a/book/src/01-quirks/03-volatile_destination.md +++ b/book/src/01-quirks/03-volatile_destination.md @@ -1,321 +1,293 @@ # Volatile Destination -TODO: replace all this one "the rant" is finalized - -There's a reasonable chance that you've never heard of `volatile` before, so -what's that? Well, it's a term that can be used in more than one context, but -basically it means "get your grubby mitts off my stuff you over-eager compiler". +TODO: update this when we can make more stuff `const` ## Volatile Memory -The first, and most common, form of volatile thing is volatile memory. Volatile -memory can change without your program changing it, usually because it's not a -location in RAM, but instead some special location that represents an actual -hardware device, or part of a hardware device perhaps. The compiler doesn't know -what's going on in this situation, but when the program is actually run and the -CPU gets an instruction to read or write from that location, instead of just -accessing some place in RAM like with normal memory, it accesses whatever bit of -hardware and does _something_. The details of that something depend on the -hardware, but what's important is that we need to actually, definitely execute -that read or write instruction. - -This is not how normal memory works. Normally when the compiler -sees us write values into variables and read values from variables, it's free to -optimize those expressions and eliminate some of the reads and writes if it can, -and generally try to save us time. Maybe it even knows some stuff about the data -dependencies in our expressions and so it does some of the reads or writes out -of order from what the source says, because the compiler knows that it won't -actually make a difference to the operation of the program. A good and helpful -friend, that compiler. - -Volatile memory works almost the opposite way. With volatile memory we -need the compiler to _definitely_ emit an instruction to do a read or write and -they need to happen _exactly_ in the order that we say to do it. Each volatile -read or write might have any sort of side effect that the compiler -doesn't know about, and it shouldn't try to be clever about the optimization. Just do what we -say, please. - -In Rust, we don't mark volatile things as being a separate type of thing, -instead we use normal raw pointers and then call the -[read_volatile](https://doc.rust-lang.org/core/ptr/fn.read_volatile.html) and -[write_volatile](https://doc.rust-lang.org/core/ptr/fn.write_volatile.html) -functions (also available as methods, if you like), which then delegate to the -LLVM -[volatile_load](https://doc.rust-lang.org/core/intrinsics/fn.volatile_load.html) -and -[volatile_store](https://doc.rust-lang.org/core/intrinsics/fn.volatile_store.html) -intrinsics. In C and C++ you can tag a pointer as being volatile and then any -normal read and write with it becomes the volatile version, but in Rust we have -to remember to use the correct alternate function instead. - -I'm told by the experts that this makes for a cleaner and saner design from a -_language design_ perspective, but it really kinda screws us when doing low -level code. References, both mutable and shared, aren't volatile, so they -compile into normal reads and writes. This means we can't do anything we'd -normally do in Rust that utilizes references of any kind. Volatile blocks of -memory can't use normal `.iter()` or `.iter_mut()` based iteration (which give -`&T` or `&mut T`), and they also can't use normal `Index` and `IndexMut` sugar -like `a + x[i]` or `x[i] = 7`. - -Unlike with normal raw pointers, this pain point never goes away. There's no way -to abstract over the difference with Rust as it exists now, you'd need to -actually adjust the core language by adding an additional pointer type (`*vol -T`) and possibly a reference type to go with it (`&vol T`) to get the right -semantics. And then you'd need an `IndexVol` trait, and you'd need -`.iter_vol()`, and so on for every other little thing. It would be a lot of -work, and the Rust developers just aren't interested in doing all that for such -a limited portion of their user population. We'll just have to deal with not -having any syntax sugar. - -### VolatilePtr - -No syntax sugar doesn't mean we can't at least make things a little easier for -ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`. -One of those "manual" newtypes I mentioned where we can't use our nice macro. +The compiler is an eager friend, so when it sees a read or a write that won't +have an effect, it eliminates that read or write. For example, if we write ```rust -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +let mut x = 5; +x = 7; +``` + +The compiler won't actually ever put 5 into `x`. It'll skip straight to putting +7 in `x`, because we never read from `x` when it's 5, so that's a safe change to +make. Normally, values are stored in RAM, which has no side effects when you +read and write from it. RAM is purely for keeping notes about values you'll need +later on. + +However, what if we had a bit of hardware where we wanted to do a write and that +did something _other than_ keeping the value for us to look at later? As you saw +in the `hello_magic` example, we have to use a `write_volatile` operation. +Volatile means "just do it anyway". The compiler thinks that it's pointless, but +we know better, so we can force it to really do exactly what we say by using +`write_volatile` instead of `write`. + +This is kinda error prone though, right? Because it's just a raw pointer, so we +might forget to use `write_volatile` at some point. + +Instead, we want a type that's always going to use volatile reads and writes. +Also, we want a pointer type that lets our reads and writes to be as safe as +possible once we've unsafely constructed the initial value. + +### Constructing The VolAddress Type + +First, we want a type that stores a location within the address space. This can +be a pointer, or a `usize`, and we'll use a `usize` because that's easier to +work with in a `const` context (and we want to have `const` when we can get it). +We'll also have our type use `NonZeroUsize` instead of just `usize` so that +`Option>` stays as a single machine word. This helps quite a bit +when we want to iterate over the addresses of a block of memory (such as +locations within the palette memory). Hardware is never at the null address +anyway. Also, if we had _just_ an address number then we wouldn't be able to +track what type the address is for. We need some +[PhantomData](https://doc.rust-lang.org/core/marker/struct.PhantomData.html), +and specifically we need the phantom data to be for `*mut T`: + +* If we used `*const T` that'd have the wrong + [variance](https://doc.rust-lang.org/nomicon/subtyping.html). +* If we used `&mut T` then that's fusing in the ideas of _lifetime_ and + _exclusive access_ to our type. That's potentially important, but that's also + an abstraction we'll build _on top of_ this `VolAddress` type if we need it. + +One abstraction layer at a time, so we start with just a phantom pointer. This gives us a type that looks like this: + +```rust +#[derive(Debug)] #[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -``` - -Obviously we want to be able to read and write: - -```rust -impl VolatilePtr { - /// Performs a `read_volatile`. - pub unsafe fn read(self) -> T { - self.0.read_volatile() - } - - /// Performs a `write_volatile`. - pub unsafe fn write(self, data: T) { - self.0.write_volatile(data); - } -``` - -And we want a way to jump around when we do have volatile memory that's in -blocks. This is where we can get ourselves into some trouble if we're not -careful. We have to decide between -[offset](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset) and -[wrapping_offset](https://doc.rust-lang.org/std/primitive.pointer.html#method.wrapping_offset). -The difference is that `offset` optimizes better, but also it can be Undefined -Behavior if the result is not "in bounds or one byte past the end of the same -allocated object". I asked [ubsan](https://github.com/ubsan) (who is the expert -that you should always listen to on matters like this) what that means exactly -when memory mapped hardware is involved (since we never allocated anything), and -the answer was that you _can_ use an `offset` in statically memory mapped -situations like this as long as you don't use it to jump to the address of -something that Rust itself allocated at some point. Cool, we all like being able -to use the one that optimizes better. Unfortunately, the downside to using -`offset` instead of `wrapping_offset` is that with `offset`, it's Undefined -Behavior _simply to calculate the out of bounds result_ (with `wrapping_offset` -it's not Undefined Behavior until you _use_ the out of bounds result). We'll -have to be quite careful when we're using `offset`. - -```rust - /// Performs a normal `offset`. - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.offset(count)) - } -``` - -Now, one thing of note is that doing the `offset` isn't `const`. The math for it -is something that's possible to do in a `const` way of course, but Rust -basically doesn't allow you to fiddle raw pointers much during `const` right -now. Maybe in the future that will improve. - -If we did want to have a `const` function for finding the correct address within -a volatile block of memory we'd have to do all the math using `usize` values, -and then cast that value into being a pointer once we were done. It'd look -something like this: - -```rust -const fn address_index(address: usize, index: usize) -> usize { - address + (index * std::mem::size_of::()) +pub struct VolAddress { + address: NonZeroUsize, + marker: PhantomData<*mut T>, } ``` -But, back to methods for `VolatilePtr`, well we sometimes want to be able to -cast a `VolatilePtr` between pointer types. Since we won't be able to do that -with `as`, we'll have to write a method for it: +Now, because of how `derive` is specified, it derives traits _if the generic +parameter_ supports those traits. Since our type is like a pointer, the traits +it supports are distinct from whatever traits the target type supports. So we'll +provide those implementations manually. ```rust - /// Performs a cast into some new pointer type. - pub fn cast(self) -> VolatilePtr { - VolatilePtr(self.0 as *mut Z) +impl Clone for VolAddress { + fn clone(&self) -> Self { + *self } +} +impl Copy for VolAddress {} +impl PartialEq for VolAddress { + fn eq(&self, other: &Self) -> bool { + self.address == other.address + } +} +impl Eq for VolAddress {} +impl PartialOrd for VolAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } +} +impl Ord for VolAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.address.cmp(&other.address) + } +} ``` -### Volatile Iterating +Boilerplate junk, not interesting. There's a reason that you derive those traits +99% of the time in Rust. -How about that `Iterator` stuff I said we'd be missing? We can actually make -_an_ Iterator available, it's just not the normal "iterate by shared reference -or unique reference" Iterator. Instead, it's more like a "throw out a series of -`VolatilePtr` values" style Iterator. Other than that small difference it's -totally normal, and we'll be able to use map and skip and take and all those -neat methods. +### Constructing A VolAddress Value -So how do we make this thing we need? First we check out the [Implementing -Iterator](https://doc.rust-lang.org/core/iter/index.html#implementing-iterator) -section in the core documentation. It says we need a struct for holding the -iterator state. Right-o, probably something like this: +Okay so here's the next core concept: If we unsafely _construct_ a +`VolAddress`, then we can safely _use_ the value once it's been properly +created. ```rust -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct VolatilePtrIter { - vol_ptr: VolatilePtr, +// you'll need these features enabled and a recent nightly +#![feature(const_int_wrapping)] +#![feature(min_const_unsafe_fn)] + +impl VolAddress { + pub const unsafe fn new_unchecked(address: usize) -> Self { + VolAddress { + address: NonZeroUsize::new_unchecked(address), + marker: PhantomData, + } + } + pub const unsafe fn cast(self) -> VolAddress { + VolAddress { + address: self.address, + marker: PhantomData, + } + } + pub unsafe fn offset(self, offset: isize) -> Self { + VolAddress { + address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), + marker: PhantomData, + } + } +} +``` + +So what are the unsafety rules here? + +* Non-null, obviously. +* Must be aligned for `T` +* Must always produce valid bit patterns for `T` +* Must not be part of the address space that Rust's stack or allocator will ever + uses. + +So, again using the `hello_magic` example, we had + +```rust +(0x400_0000 as *mut u16).write_volatile(0x0403); +``` + +And instead we could declare + +```rust +const MAGIC_LOCATION: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; +``` + +### Using A VolAddress Value + +Now that we've named the magic location, we want to write to it. + +```rust +impl VolAddress { + pub fn read(self) -> T + where + T: Copy, + { + unsafe { (self.address.get() as *mut T).read_volatile() } + } + pub unsafe fn read_non_copy(self) -> T { + (self.address.get() as *mut T).read_volatile() + } + pub fn write(self, val: T) { + unsafe { (self.address.get() as *mut T).write_volatile(val) } + } +} +``` + +So if the type is `Copy` we can `read` it as much as we want. If, somehow, the +type isn't `Copy`, then it might be `Drop`, and that means if we read out a +value over and over we could cause the `drop` method to trigger UB. Since the +end user might really know what they're doing, we provide an unsafe backup +`read_non_copy`. + +On the other hand, we can `write` to the location as much as we want. Even if +the type isn't `Copy`, _not running `Drop` is safe_, so a `write` is always +safe. + +Now we can write to our magical location. + +```rust +MAGIC_LOCATION.write(0x0403); +``` + +### VolAddress Iteration + +We've already seen that sometimes we want to have a base address of some sort +and then offset from that location to another. What if we wanted to iterate over +_all the locations_. That's not particularly hard. + +```rust +impl VolAddress { + pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { + VolAddressIter { vol_address: self, slots } + } +} + +#[derive(Debug)] +pub struct VolAddressIter { + vol_address: VolAddress, slots: usize, } -``` +impl Clone for VolAddressIter { + fn clone(&self) -> Self { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } +} +impl PartialEq for VolAddressIter { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } +} +impl Eq for VolAddressIter {} +impl Iterator for VolAddressIter { + type Item = VolAddress; -And then we just implement -[core::iter::Iterator](https://doc.rust-lang.org/core/iter/trait.Iterator.html) -on that struct. Wow, that's quite the trait though! Don't worry, we only need to -implement two small things and then the rest of it comes free as a bunch of -default methods. - -So, the code that we _want_ to write looks like this: - -```rust -impl Iterator for VolatilePtrIter { - type Item = VolatilePtr; - - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option { if self.slots > 0 { - let out = Some(self.vol_ptr); - self.slots -= 1; - self.vol_ptr = unsafe { self.vol_ptr.offset(1) }; - out + let out = self.vol_address; + unsafe { + self.slots -= 1; + self.vol_address = self.vol_address.offset(1); + } + Some(out) } else { None } } } +impl FusedIterator for VolAddressIter {} ``` -Except we _can't_ write that code. What? The problem is that we used -`derive(Clone, Copy` on `VolatilePtr`. Because of a quirk in how `derive` works, -this means `VolatilePtr` will only be `Copy` if the `T` is `Copy`, _even -though the pointer itself is always `Copy` regardless of what it points to_. -Ugh, terrible. We've got three basic ways to handle this: +### VolAddressBlock -* Make the `Iterator` implementation be for ``, and then hope that we - always have types that are `Clone`. -* Hand implement every trait we want `VolatilePtr` (and `VolatilePtrIter`) to - have so that we can override the fact that `derive` is basically broken in - this case. -* Make `VolatilePtr` store a `usize` value instead of a pointer, and then cast - it to `*mut T` when we actually need to read and write. This would require us - to also store a `PhantomData` so that the type of the address is tracked - properly, which would make it a lot more verbose to construct a `VolatilePtr` - value. - -None of those options are particularly appealing. I guess we'll do the first one -because it's the least amount of up front trouble, and I don't _think_ we'll -need to be iterating non-Clone values. All we do to pick that option is add the -bound to the very start of the `impl` block, where we introduce the `T`: +Obviously, having a base address and a length exist separately is error prone. +There's a good reason for slices to keep their pointer and their length +together. We want something like that, which we'll call a "block" because +"array" and "slice" are already things in Rust. ```rust -impl Iterator for VolatilePtrIter { - type Item = VolatilePtr; - - fn next(&mut self) -> Option> { - if self.slots > 0 { - let out = Some(self.vol_ptr.clone()); - self.slots -= 1; - self.vol_ptr = unsafe { self.vol_ptr.clone().offset(1) }; - out - } else { - None - } - } -} -``` - -What's going on here? Okay so our iterator has a number of slots that it'll go -over, and then when it's out of slots it starts producing `None` forever. That's -actually pretty simple. We're also masking some unsafety too. In this case, -we'll rely on the person who made the `VolatilePtrIter` to have selected the -correct number of slots. This gives us a new method for `VolatilePtr`: - -```rust - pub unsafe fn iter_slots(self, slots: usize) -> VolatilePtrIter { - VolatilePtrIter { - vol_ptr: self, - slots, - } - } -``` - -With this design, making the `VolatilePtrIter` at the start is `unsafe` (we have -to trust the caller that the right number of slots exists), and then using it -after that is totally safe (if the right number of slots was given we'll never -screw up our end of it). - -### VolatilePtr Formatting - -Also, just as a little bonus that we probably won't use, we could enable our new -pointer type to be formatted as a pointer value. - -```rust -impl core::fmt::Pointer for VolatilePtr { - /// Formats exactly like the inner `*mut T`. - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{:p}", self.0) - } -} -``` - -Neat! - -### VolatilePtr Complete - -That was a lot of small code blocks, let's look at it all put together: - -```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -impl VolatilePtr { - pub unsafe fn read(self) -> T { - self.0.read_volatile() - } - pub unsafe fn write(self, data: T) { - self.0.write_volatile(data); - } - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.offset(count)) - } - pub fn cast(self) -> VolatilePtr { - VolatilePtr(self.0 as *mut Z) - } - pub unsafe fn iter_slots(self, slots: usize) -> VolatilePtrIter { - VolatilePtrIter { - vol_ptr: self, - slots, - } - } -} -impl core::fmt::Pointer for VolatilePtr { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{:p}", self.0) - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct VolatilePtrIter { - vol_ptr: VolatilePtr, +#[derive(Debug)] +pub struct VolAddressBlock { + vol_address: VolAddress, slots: usize, } -impl Iterator for VolatilePtrIter { - type Item = VolatilePtr; - fn next(&mut self) -> Option> { - if self.slots > 0 { - let out = Some(self.vol_ptr.clone()); - self.slots -= 1; - self.vol_ptr = unsafe { self.vol_ptr.clone().offset(1) }; - out +impl Clone for VolAddressBlock { + fn clone(&self) -> Self { + VolAddressBlock { + vol_address: self.vol_address, + slots: self.slots, + } + } +} +impl PartialEq for VolAddressBlock { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } +} +impl Eq for VolAddressBlock {} + +impl VolAddressBlock { + pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { + VolAddressBlock { vol_address, slots } + } + pub const fn iter(self) -> VolAddressIter { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } + pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { + self.vol_address.offset(slot as isize) + } + pub fn index(self, slot: usize) -> VolAddress { + if slot < self.slots { + unsafe { self.vol_address.offset(slot as isize) } + } else { + panic!("Index Requested: {} >= Bound: {}", slot, self.slots) + } + } + pub fn get(self, slot: usize) -> Option> { + if slot < self.slots { + unsafe { Some(self.vol_address.offset(slot as isize)) } } else { None } @@ -323,6 +295,26 @@ impl Iterator for VolatilePtrIter { } ``` +Now we can have something like: + +```rust +const OTHER_MAGIC: VolAddressBlock = unsafe { + VolAddressBlock::new_unchecked( + VolAddress::new_unchecked(0x600_0000), + 240 * 160 + ) +}; + +OTHER_MAGIC.index(120 + 80 * 240).write_volatile(0x001F); +OTHER_MAGIC.index(136 + 80 * 240).write_volatile(0x03E0); +OTHER_MAGIC.index(120 + 96 * 240).write_volatile(0x7C00); +``` + +### Docs? + +If you wanna see these types and methods with a full docs write up you should +check the GBA crate's source. + ## Volatile ASM In addition to some memory locations being volatile, it's also possible for @@ -343,3 +335,9 @@ safely (otherwise the GBA won't ever actually wake back up from the low power state), but the `asm!` you use once you're ready is just a single instruction with no return value. The compiler can't tell what's going on, so you just have to say "do it anyway". + +Note that if you use a linker script to include any ASM with your Rust program +(eg: the `crt0.s` file that we setup in the "Development Setup" section), all of +that ASM is "volatile" for these purposes. Volatile isn't actually a _hardware_ +concept, it's just an LLVM concept, and the linker script runs after LLVM has +done its work. From 4586d2d95f68b90c20e6ef5c00307672dbe6430b Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 20 Dec 2018 18:08:54 -0700 Subject: [PATCH 02/13] module organization --- book/src/04-non-video/01-buttons.md | 1 + examples/hello_world.rs | 183 ++++++++++++++++++++--- src/base.rs | 10 ++ src/{ => base}/builtins.rs | 0 src/{fixed.rs => base/fixed_point.rs} | 0 src/{core_extras.rs => base/volatile.rs} | 4 +- src/io_registers.rs | 126 ---------------- src/lib.rs | 118 ++++++++++++++- 8 files changed, 287 insertions(+), 155 deletions(-) create mode 100644 src/base.rs rename src/{ => base}/builtins.rs (100%) rename src/{fixed.rs => base/fixed_point.rs} (100%) rename src/{core_extras.rs => base/volatile.rs} (98%) delete mode 100644 src/io_registers.rs diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 8eb4e80..d78abf5 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -3,3 +3,4 @@ It's all well and good to just show a picture, even to show an animation, but if we want a game we have to let the user interact with something. +TODO diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 549569a..4258b12 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -31,21 +31,166 @@ macro_rules! const_rgb { }}; } -// TODO: kill this -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -impl VolatilePtr { - pub unsafe fn read(&self) -> T { - core::ptr::read_volatile(self.0) +mod vol_address { + #![allow(unused)] + use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; + /// VolAddress + #[derive(Debug)] + #[repr(transparent)] + pub struct VolAddress { + address: NonZeroUsize, + marker: PhantomData<*mut T>, } - pub unsafe fn write(&self, data: T) { - core::ptr::write_volatile(self.0, data); + impl Clone for VolAddress { + fn clone(&self) -> Self { + *self + } } - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) + impl Copy for VolAddress {} + impl PartialEq for VolAddress { + fn eq(&self, other: &Self) -> bool { + self.address == other.address + } + } + impl Eq for VolAddress {} + impl PartialOrd for VolAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } + } + impl Ord for VolAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.address.cmp(&other.address) + } + } + impl VolAddress { + pub const unsafe fn new_unchecked(address: usize) -> Self { + VolAddress { + address: NonZeroUsize::new_unchecked(address), + marker: PhantomData, + } + } + pub const unsafe fn cast(self) -> VolAddress { + VolAddress { + address: self.address, + marker: PhantomData, + } + } + pub unsafe fn offset(self, offset: isize) -> Self { + // TODO: const this + VolAddress { + address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), + marker: PhantomData, + } + } + pub fn is_aligned(self) -> bool { + // TODO: const this + self.address.get() % core::mem::align_of::() == 0 + } + pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { + VolAddressIter { vol_address: self, slots } + } + pub fn read(self) -> T + where + T: Copy, + { + unsafe { (self.address.get() as *mut T).read_volatile() } + } + pub unsafe fn read_non_copy(self) -> T { + (self.address.get() as *mut T).read_volatile() + } + pub fn write(self, val: T) { + unsafe { (self.address.get() as *mut T).write_volatile(val) } + } + } + /// VolAddressIter + #[derive(Debug)] + pub struct VolAddressIter { + vol_address: VolAddress, + slots: usize, + } + impl Clone for VolAddressIter { + fn clone(&self) -> Self { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } + } + impl PartialEq for VolAddressIter { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } + } + impl Eq for VolAddressIter {} + impl Iterator for VolAddressIter { + type Item = VolAddress; + + fn next(&mut self) -> Option { + 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 FusedIterator for VolAddressIter {} + /// VolAddressBlock + #[derive(Debug)] + pub struct VolAddressBlock { + vol_address: VolAddress, + slots: usize, + } + impl Clone for VolAddressBlock { + fn clone(&self) -> Self { + VolAddressBlock { + vol_address: self.vol_address, + slots: self.slots, + } + } + } + impl PartialEq for VolAddressBlock { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } + } + impl Eq for VolAddressBlock {} + impl VolAddressBlock { + pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { + VolAddressBlock { vol_address, slots } + } + pub const fn iter(self) -> VolAddressIter { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } + pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { + // TODO: const this + self.vol_address.offset(slot as isize) + } + pub fn index(self, slot: usize) -> VolAddress { + if slot < self.slots { + unsafe { self.vol_address.offset(slot as isize) } + } else { + panic!("Index Requested: {} >= Bound: {}", slot, self.slots) + } + } + pub fn get(self, slot: usize) -> Option> { + if slot < self.slots { + unsafe { Some(self.vol_address.offset(slot as isize)) } + } else { + None + } + } } } +use self::vol_address::*; newtype! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -64,7 +209,7 @@ newtype! { DisplayControlSetting, u16 } -pub const DISPLAY_CONTROL: VolatilePtr = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting); +pub const DISPLAY_CONTROL: VolAddress = unsafe { VolAddress::new_unchecked(0x0400_0000) }; pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3); pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000); pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0); @@ -72,10 +217,12 @@ pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST pub struct Mode3; impl Mode3 { const SCREEN_WIDTH: isize = 240; - const PIXELS: VolatilePtr = VolatilePtr(0x600_0000 as *mut Color); + const SCREEN_HEIGHT: isize = 160; + const PIXELS: VolAddressBlock = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x600_0000), (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) as usize) }; - pub unsafe fn draw_pixel_unchecked(col: isize, row: isize, color: Color) { - Self::PIXELS.offset(col + row * Self::SCREEN_WIDTH).write(color); + pub unsafe fn draw_pixel(col: usize, row: usize, color: Color) { + Self::PIXELS.index(col + row * Self::SCREEN_WIDTH as usize).write(color); } } @@ -88,9 +235,9 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { fn main(_argc: isize, _argv: *const *const u8) -> isize { unsafe { DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2); - Mode3::draw_pixel_unchecked(120, 80, const_rgb!(31, 0, 0)); - Mode3::draw_pixel_unchecked(136, 80, const_rgb!(0, 31, 0)); - Mode3::draw_pixel_unchecked(120, 96, const_rgb!(0, 0, 31)); + Mode3::draw_pixel(120, 80, const_rgb!(31, 0, 0)); + Mode3::draw_pixel(136, 80, const_rgb!(0, 31, 0)); + Mode3::draw_pixel(120, 96, const_rgb!(0, 0, 31)); loop {} } } diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 0000000..4c7b530 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,10 @@ +//! Holds fundamental types/ops which the rest of the crate it built on. + +pub mod fixed_point; +//pub(crate) use self::fixed_point::*; + +pub mod volatile; +pub(crate) use self::volatile::*; + +pub mod builtins; +//pub(crate) use self::builtins::*; diff --git a/src/builtins.rs b/src/base/builtins.rs similarity index 100% rename from src/builtins.rs rename to src/base/builtins.rs diff --git a/src/fixed.rs b/src/base/fixed_point.rs similarity index 100% rename from src/fixed.rs rename to src/base/fixed_point.rs diff --git a/src/core_extras.rs b/src/base/volatile.rs similarity index 98% rename from src/core_extras.rs rename to src/base/volatile.rs index 679b42f..5e12e28 100644 --- a/src/core_extras.rs +++ b/src/base/volatile.rs @@ -1,6 +1,4 @@ -//! Things that I wish were in core, but aren't. - -//TODO(Lokathor): reorganize as gba::core_extras::fixed_point and gba::core_extras::volatile ? +//! Holds types for correct handling of volatile memory. use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; diff --git a/src/io_registers.rs b/src/io_registers.rs deleted file mode 100644 index 4ba89dc..0000000 --- a/src/io_registers.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! The module for all things relating to the IO Register portion of the GBA's -//! memory map. -//! -//! Here we define many constants for the volatile pointers to the various IO -//! registers. Each raw register constant is named according to the name given -//! to it in GBATEK's [GBA I/O -//! Map](http://problemkaputt.de/gbatek.htm#gbaiomap). They program in C, and so -//! of course all the names terrible and missing as many vowels as possible. -//! However, being able to look it up online is the most important thing here, -//! so oh well. -//! -//! In addition to the const `VolatilePtr` values, we will over time be adding -//! safe wrappers around each register, including newtypes and such so that you -//! can easily work with whatever each specific register is doing. - -// TODO(lokathor): IO Register newtypes. - -use gba_proc_macro::register_bit; - -use super::*; - -/// LCD Control. Read/Write. -/// -/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) -pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; - -newtype!( - /// A newtype over the various display control options that you have on a GBA. - #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - DisplayControlSetting, - u16 -); - -#[allow(missing_docs)] -impl DisplayControlSetting { - pub const BG_MODE_MASK: u16 = 0b111; - - pub fn mode(self) -> DisplayControlMode { - match self.0 & Self::BG_MODE_MASK { - 0 => DisplayControlMode::Tiled0, - 1 => DisplayControlMode::Tiled1, - 2 => DisplayControlMode::Tiled2, - 3 => DisplayControlMode::Bitmap3, - 4 => DisplayControlMode::Bitmap4, - 5 => DisplayControlMode::Bitmap5, - _ => unreachable!(), - } - } - pub fn set_mode(&mut self, new_mode: DisplayControlMode) { - self.0 &= !Self::BG_MODE_MASK; - self.0 |= match new_mode { - DisplayControlMode::Tiled0 => 0, - DisplayControlMode::Tiled1 => 1, - DisplayControlMode::Tiled2 => 2, - DisplayControlMode::Bitmap3 => 3, - DisplayControlMode::Bitmap4 => 4, - DisplayControlMode::Bitmap5 => 5, - }; - } - - register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode); - register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled); - register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free); - register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d); - register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank); - register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0); - register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1); - register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2); - register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3); - register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object); - register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0); - register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1); - register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window); -} - -/// The six display modes available on the GBA. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DisplayControlMode { - /// This basically allows for the most different things at once (all layers, - /// 1024 tiles, two palette modes, etc), but you can't do affine - /// transformations. - Tiled0, - /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, - /// and BG2 runs as if in `Tiled2`. - Tiled1, - /// This allows affine transformations, but only uses BG2 and BG3. - Tiled2, - /// This is the basic bitmap draw mode. The whole screen is a single bitmap. - /// Uses BG2 only. - Bitmap3, - /// This uses _paletted color_ so that there's enough space to have two pages - /// at _full resolution_, allowing page flipping. Uses BG2 only. - Bitmap4, - /// This uses _reduced resolution_ so that there's enough space to have two - /// pages with _full color_, allowing page flipping. Uses BG2 only. - Bitmap5, -} - -/// Assigns the given display control setting. -pub fn set_display_control(setting: DisplayControlSetting) { - DISPCNT.write(setting); -} -/// Obtains the current display control setting. -pub fn display_control() -> DisplayControlSetting { - DISPCNT.read() -} - -/// Vertical Counter (LY) -pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; - -/// Obtains the current VCount value. -pub fn vcount() -> u16 { - VCOUNT.read() -} - -/// Performs a busy loop until VBlank starts. -pub fn wait_until_vblank() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() < SCREEN_HEIGHT as u16 {} -} - -/// Performs a busy loop until VDraw starts. -pub fn wait_until_vdraw() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() >= SCREEN_HEIGHT as u16 {} -} diff --git a/src/lib.rs b/src/lib.rs index be313d4..ce9c46a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,15 +57,9 @@ macro_rules! newtype { }; } -pub mod builtins; - -pub mod fixed; - +pub mod base; +pub(crate) use self::base::*; pub mod bios; - -pub mod core_extras; -pub(crate) use crate::core_extras::*; - pub mod io; pub mod video_ram; @@ -226,3 +220,111 @@ mod tests { } } */ + +use gba_proc_macro::register_bit; + +/// LCD Control. Read/Write. +/// +/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) +pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; + +newtype!( + /// A newtype over the various display control options that you have on a GBA. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + DisplayControlSetting, + u16 +); + +#[allow(missing_docs)] +impl DisplayControlSetting { + pub const BG_MODE_MASK: u16 = 0b111; + + pub fn mode(self) -> DisplayControlMode { + match self.0 & Self::BG_MODE_MASK { + 0 => DisplayControlMode::Tiled0, + 1 => DisplayControlMode::Tiled1, + 2 => DisplayControlMode::Tiled2, + 3 => DisplayControlMode::Bitmap3, + 4 => DisplayControlMode::Bitmap4, + 5 => DisplayControlMode::Bitmap5, + _ => unreachable!(), + } + } + pub fn set_mode(&mut self, new_mode: DisplayControlMode) { + self.0 &= !Self::BG_MODE_MASK; + self.0 |= match new_mode { + DisplayControlMode::Tiled0 => 0, + DisplayControlMode::Tiled1 => 1, + DisplayControlMode::Tiled2 => 2, + DisplayControlMode::Bitmap3 => 3, + DisplayControlMode::Bitmap4 => 4, + DisplayControlMode::Bitmap5 => 5, + }; + } + + register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode); + register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled); + register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free); + register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d); + register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank); + register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0); + register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1); + register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2); + register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3); + register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object); + register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0); + register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1); + register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window); +} + +/// The six display modes available on the GBA. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DisplayControlMode { + /// This basically allows for the most different things at once (all layers, + /// 1024 tiles, two palette modes, etc), but you can't do affine + /// transformations. + Tiled0, + /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, + /// and BG2 runs as if in `Tiled2`. + Tiled1, + /// This allows affine transformations, but only uses BG2 and BG3. + Tiled2, + /// This is the basic bitmap draw mode. The whole screen is a single bitmap. + /// Uses BG2 only. + Bitmap3, + /// This uses _paletted color_ so that there's enough space to have two pages + /// at _full resolution_, allowing page flipping. Uses BG2 only. + Bitmap4, + /// This uses _reduced resolution_ so that there's enough space to have two + /// pages with _full color_, allowing page flipping. Uses BG2 only. + Bitmap5, +} + +/// Assigns the given display control setting. +pub fn set_display_control(setting: DisplayControlSetting) { + DISPCNT.write(setting); +} +/// Obtains the current display control setting. +pub fn display_control() -> DisplayControlSetting { + DISPCNT.read() +} + +/// Vertical Counter (LY) +pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; + +/// Obtains the current VCount value. +pub fn vcount() -> u16 { + VCOUNT.read() +} + +/// Performs a busy loop until VBlank starts. +pub fn wait_until_vblank() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() < crate::video_ram::SCREEN_HEIGHT as u16 {} +} + +/// Performs a busy loop until VDraw starts. +pub fn wait_until_vdraw() { + // TODO: make this the better version with BIOS and interrupts and such. + while vcount() >= crate::video_ram::SCREEN_HEIGHT as u16 {} +} From 5bb6a927fdea46bb00d6ebb435cac0f746ab4222 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Thu, 20 Dec 2018 18:13:46 -0700 Subject: [PATCH 03/13] for now we have to justrelease, waiting on compiler-builtins --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2f5bf99..0026b53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: - cargo test --no-fail-fast --tests - cargo test --no-fail-fast --tests --release # Let cargo make take over the rest - - cargo make build-all + - cargo make justrelease # Test build the book so that a failed book build kills this run - cd book && mdbook build From b927a348bd84b0d856496dd9d948164fb561cfc4 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 22 Dec 2018 00:26:52 -0700 Subject: [PATCH 04/13] sensible goals --- .../src/00-introduction/02-goals_and_style.md | 29 +-- book/src/01-quirks/02-fixed_only.md | 4 +- book/src/02-concepts/09-sram.md | 5 + book/src/04-non-video/01-buttons.md | 79 +++++- book/src/05-examples/01-hello_magic.md | 1 - book/src/05-examples/02-hello_world.md | 1 - book/src/05-examples/03-light_cycle.md | 1 - book/src/05-examples/04-bg_demo.md | 1 - book/src/SUMMARY.md | 4 - examples/hello_world.rs | 243 ------------------ src/base/builtins.rs | 14 +- 11 files changed, 107 insertions(+), 275 deletions(-) delete mode 100644 book/src/05-examples/01-hello_magic.md delete mode 100644 book/src/05-examples/02-hello_world.md delete mode 100644 book/src/05-examples/03-light_cycle.md delete mode 100644 book/src/05-examples/04-bg_demo.md delete mode 100644 examples/hello_world.rs diff --git a/book/src/00-introduction/02-goals_and_style.md b/book/src/00-introduction/02-goals_and_style.md index 98fd86b..eac5366 100644 --- a/book/src/00-introduction/02-goals_and_style.md +++ b/book/src/00-introduction/02-goals_and_style.md @@ -2,26 +2,17 @@ So, what's this book actually gonna teach you? -I'm _not_ gonna tell you how to use a crate that already exists. +My goal is certainly not just showing off the crate. Programming for the GBA is +weird enough that I'm trying to teach you all the rest of the stuff you need to +know along the way. If I do my job right then you'd be able to write your own +crate for GBA stuff just how you think it should all go by the end. -Don't get me wrong, there _is_ a [gba](https://crates.io/crates/gba) crate, and -it's on crates.io and all that jazz. - -However, unlike most crates that come with a tutorial book, I don't want to just -teach you how to use the crate. What I want is to teach you what you need to -know so that you could build the crate yourself, from scratch, if it didn't -already exist for you. Let's call it the [Handmade -Hero](https://handmadehero.org/) school of design. Much more than you might find -in other Rust crate books, I'll be attempting to show a lot of the _why_ in -addition to just the _how_. Once you know how to do it all on your own, you can -decide for yourself if the `gba` crate does it well, or if you think you can -come up with something that suits your needs better. - -Overall the book is sorted for easy review once you're trying to program -something, and the GBA has a few interconnected concepts, so some parts of the -book end up having to refer you to portions that you haven't read yet. The -chapters and sections are sorted so that _minimal_ future references are -required, but it's unavoidable that it'll happen sometimes. +Overall the book is sorted more for easy review once you're trying to program +something. The GBA has a few things that can stand on their own and many other +things are a mass of interconnected concepts, so some parts of the book end up +having to refer you to portions that you haven't read yet. The chapters and +sections are sorted so that _minimal_ future references are required, but it's +unavoidable that it'll happen sometimes. The actual "tutorial order" of the book is the [Examples](../05-examples/00-index.md) chapter. Each section of that chapter diff --git a/book/src/01-quirks/02-fixed_only.md b/book/src/01-quirks/02-fixed_only.md index bb0e401..e6ddf0f 100644 --- a/book/src/01-quirks/02-fixed_only.md +++ b/book/src/01-quirks/02-fixed_only.md @@ -402,9 +402,9 @@ silly but it's so much faster than doing an actual division). Also, again signed values can be annoying, because if the value _just happens_ to be `i32::MIN` then when you negate it you'll have... _still_ a negative value. I'm not 100% on this, but I think the correct thing to do at that point -is to give `$t::MIN` as out output num value. +is to give `$t::MIN` as the output num value. -Did you get all that? Good, because this is involves casting, we will need to +Did you get all that? Good, because this involves casting, so we will need to implement it three times, which calls for another macro. ```rust diff --git a/book/src/02-concepts/09-sram.md b/book/src/02-concepts/09-sram.md index 65ec4d2..fbb6202 100644 --- a/book/src/02-concepts/09-sram.md +++ b/book/src/02-concepts/09-sram.md @@ -14,3 +14,8 @@ for full details of how the `WAITCNT` register works. The game pak SRAM also has only an 8-bit bus, so have fun with that. The GBA Direct Memory Access (DMA) unit cannot access SRAM. + +Also, you [should not write to SRAM with code executing from +ROM](https://problemkaputt.de/gbatek.htm#gbacartbackupsramfram). Instead, you +should move the code to WRAM and execute the save code from there. We'll cover +how to handle that eventually. diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index d78abf5..2aa065c 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -3,4 +3,81 @@ It's all well and good to just show a picture, even to show an animation, but if we want a game we have to let the user interact with something. -TODO +## Key Input Register + +* KEYINPUT, `0x400_0130`, `u16`, read only + +This little `u16` stores the status of _all_ the buttons on the GBA, all at +once. There's only 10 of them, and we have 16 bits to work with, so that sounds +easy. However, there's a bit of a catch. The register follows a "low-active" +convention, where pressing a button _clears_ that bit until it's released. + +```rust +const NO_BUTTONS_PRESSED: u16 = 0b0000_0011_1111_1111; +``` + +The buttons are, going up in order from the 0th bit: + +* A +* B +* Select +* Start +* Right +* Left +* Up +* Down +* R +* L + +Bits above that are not used. However, since the arrow left and right can never +be pressed at the same time, and the arrows up and down can never be pressed at +the same time, this register will never read as zero. + +When programming, we usually are thinking of what buttons we want to have _be +pressed_ instead of buttons we want to have _not be pressed_. This means that we +need an inversion to happen somewhere along the line. The easiest moment of +inversion is immediately as you read in from the register and wrap the value up +in a newtype. + +```rust +pub fn read_key_input() -> KeyInput { + KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) +} +``` + +Now the KeyInput you get can be checked for what buttons are pressed by checking +for a set bit like you'd do anywhere else. + +```rust +impl KeyInput { + pub fn a_pressed(self) -> bool { + (self.0 & A_BIT) > 0 + } +} +``` + +Note that the current `KEYINPUT` value changes in real time as the user presses +or releases the buttons. To account for this, it's best to read the value just +once per game frame and then use that single value as if it was the input across +the whole frame. If you've worked with polling input before that should sound +totally normal, but if not just always remember to gather the input once per +frame and then use that value across the whole frame. + +## Key Interrupt Control + +* KEYCNT, `0x400_0132`, `u16`, read/write + +This lets you control what keys will trigger a keypad interrupt. Of course, for +the actual interrupt to fire you also need to set the `IME` and IE` registers +properly. See the [Interrupts](05-interrupts.md) section for details there. + +The main thing to know about this register is that the keys are in _the exact +same order_ as the key input order. However, with this register they use a +high-active convention instead (eg: the bit is active when the button should be +pressed as part of the interrupt). + +In addition to simply having the bits for the buttons, bit 14 is a flag for +enabling keypad interrupts (in addition to the flag in the `IE` register), and +bit 15 decides how having more than one button works. If bit 15 is disabled, +it's an OR combination (eg: "press any key to continue"). If bit 15 is enabled +it's an AND combination (eg: "press A+B+Start+Select to reset"). diff --git a/book/src/05-examples/01-hello_magic.md b/book/src/05-examples/01-hello_magic.md deleted file mode 100644 index ba1a038..0000000 --- a/book/src/05-examples/01-hello_magic.md +++ /dev/null @@ -1 +0,0 @@ -# hello_magic diff --git a/book/src/05-examples/02-hello_world.md b/book/src/05-examples/02-hello_world.md deleted file mode 100644 index 115e729..0000000 --- a/book/src/05-examples/02-hello_world.md +++ /dev/null @@ -1 +0,0 @@ -# hello_world diff --git a/book/src/05-examples/03-light_cycle.md b/book/src/05-examples/03-light_cycle.md deleted file mode 100644 index f72194e..0000000 --- a/book/src/05-examples/03-light_cycle.md +++ /dev/null @@ -1 +0,0 @@ -# light_cycle diff --git a/book/src/05-examples/04-bg_demo.md b/book/src/05-examples/04-bg_demo.md deleted file mode 100644 index 8cc8680..0000000 --- a/book/src/05-examples/04-bg_demo.md +++ /dev/null @@ -1 +0,0 @@ -# bg_demo diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 704ba7a..a697feb 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -35,7 +35,3 @@ * [Link Cable](04-non-video/06-link_cable.md) * [Game Pak](04-non-video/07-game_pak.md) * [Examples](05-examples/00-index.md) - * [hello_magic](05-examples/01-hello_magic.md) - * [hello_world](05-examples/02-hello_world.md) - * [light_cycle](05-examples/03-light_cycle.md) - * [bg_demo](05-examples/04-bg_demo.md) diff --git a/examples/hello_world.rs b/examples/hello_world.rs deleted file mode 100644 index 4258b12..0000000 --- a/examples/hello_world.rs +++ /dev/null @@ -1,243 +0,0 @@ -#![no_std] -#![feature(start)] -#![feature(underscore_const_names)] - -#[macro_export] -macro_rules! newtype { - ($(#[$attr:meta])* $new_name:ident, $old_name:ident) => { - $(#[$attr])* - #[repr(transparent)] - pub struct $new_name($old_name); - }; -} - -#[macro_export] -macro_rules! const_assert { - ($condition:expr) => { - #[deny(const_err)] - #[allow(dead_code)] - const _: usize = 0 - !$condition as usize; - }; -} - -/// Constructs an RGB value with a `const_assert!` that the input is in range. -#[macro_export] -macro_rules! const_rgb { - ($r:expr, $g:expr, $b:expr) => {{ - const_assert!($r <= 31); - const_assert!($g <= 31); - const_assert!($b <= 31); - Color::new($r, $g, $b) - }}; -} - -mod vol_address { - #![allow(unused)] - use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; - /// VolAddress - #[derive(Debug)] - #[repr(transparent)] - pub struct VolAddress { - address: NonZeroUsize, - marker: PhantomData<*mut T>, - } - impl Clone for VolAddress { - fn clone(&self) -> Self { - *self - } - } - impl Copy for VolAddress {} - impl PartialEq for VolAddress { - fn eq(&self, other: &Self) -> bool { - self.address == other.address - } - } - impl Eq for VolAddress {} - impl PartialOrd for VolAddress { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.address.cmp(&other.address)) - } - } - impl Ord for VolAddress { - fn cmp(&self, other: &Self) -> Ordering { - self.address.cmp(&other.address) - } - } - impl VolAddress { - pub const unsafe fn new_unchecked(address: usize) -> Self { - VolAddress { - address: NonZeroUsize::new_unchecked(address), - marker: PhantomData, - } - } - pub const unsafe fn cast(self) -> VolAddress { - VolAddress { - address: self.address, - marker: PhantomData, - } - } - pub unsafe fn offset(self, offset: isize) -> Self { - // TODO: const this - VolAddress { - address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), - marker: PhantomData, - } - } - pub fn is_aligned(self) -> bool { - // TODO: const this - self.address.get() % core::mem::align_of::() == 0 - } - pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { - VolAddressIter { vol_address: self, slots } - } - pub fn read(self) -> T - where - T: Copy, - { - unsafe { (self.address.get() as *mut T).read_volatile() } - } - pub unsafe fn read_non_copy(self) -> T { - (self.address.get() as *mut T).read_volatile() - } - pub fn write(self, val: T) { - unsafe { (self.address.get() as *mut T).write_volatile(val) } - } - } - /// VolAddressIter - #[derive(Debug)] - pub struct VolAddressIter { - vol_address: VolAddress, - slots: usize, - } - impl Clone for VolAddressIter { - fn clone(&self) -> Self { - VolAddressIter { - vol_address: self.vol_address, - slots: self.slots, - } - } - } - impl PartialEq for VolAddressIter { - fn eq(&self, other: &Self) -> bool { - self.vol_address == other.vol_address && self.slots == other.slots - } - } - impl Eq for VolAddressIter {} - impl Iterator for VolAddressIter { - type Item = VolAddress; - - fn next(&mut self) -> Option { - 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 FusedIterator for VolAddressIter {} - /// VolAddressBlock - #[derive(Debug)] - pub struct VolAddressBlock { - vol_address: VolAddress, - slots: usize, - } - impl Clone for VolAddressBlock { - fn clone(&self) -> Self { - VolAddressBlock { - vol_address: self.vol_address, - slots: self.slots, - } - } - } - impl PartialEq for VolAddressBlock { - fn eq(&self, other: &Self) -> bool { - self.vol_address == other.vol_address && self.slots == other.slots - } - } - impl Eq for VolAddressBlock {} - impl VolAddressBlock { - pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { - VolAddressBlock { vol_address, slots } - } - pub const fn iter(self) -> VolAddressIter { - VolAddressIter { - vol_address: self.vol_address, - slots: self.slots, - } - } - pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { - // TODO: const this - self.vol_address.offset(slot as isize) - } - pub fn index(self, slot: usize) -> VolAddress { - if slot < self.slots { - unsafe { self.vol_address.offset(slot as isize) } - } else { - panic!("Index Requested: {} >= Bound: {}", slot, self.slots) - } - } - pub fn get(self, slot: usize) -> Option> { - if slot < self.slots { - unsafe { Some(self.vol_address.offset(slot as isize)) } - } else { - None - } - } - } -} -use self::vol_address::*; - -newtype! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - Color, u16 -} - -impl Color { - /// Combines the Red, Blue, and Green provided into a single color value. - pub const fn new(red: u16, green: u16, blue: u16) -> Color { - Color(blue << 10 | green << 5 | red) - } -} - -newtype! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - DisplayControlSetting, u16 -} - -pub const DISPLAY_CONTROL: VolAddress = unsafe { VolAddress::new_unchecked(0x0400_0000) }; -pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3); -pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000); -pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0); - -pub struct Mode3; -impl Mode3 { - const SCREEN_WIDTH: isize = 240; - const SCREEN_HEIGHT: isize = 160; - const PIXELS: VolAddressBlock = - unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(0x600_0000), (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) as usize) }; - - pub unsafe fn draw_pixel(col: usize, row: usize, color: Color) { - Self::PIXELS.index(col + row * Self::SCREEN_WIDTH as usize).write(color); - } -} - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2); - Mode3::draw_pixel(120, 80, const_rgb!(31, 0, 0)); - Mode3::draw_pixel(136, 80, const_rgb!(0, 31, 0)); - Mode3::draw_pixel(120, 96, const_rgb!(0, 0, 31)); - loop {} - } -} diff --git a/src/base/builtins.rs b/src/base/builtins.rs index db3615e..5966450 100644 --- a/src/base/builtins.rs +++ b/src/base/builtins.rs @@ -65,11 +65,21 @@ pub extern "C" fn __clzsi2(mut x: usize) -> usize { #[test] fn __clzsi2_test() { - let mut i = 1 << 63; + let mut i: usize = core::usize::MAX; while i > 0 { - assert_eq!(__clzsi2(i), i.leading_zeros() as usize); + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); i >>= 1; } + // check 0 also + i = 0; + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); + // double check for bit patterns that aren't solid 1s + i = 1; + for _ in 0 .. 63 { + assert_eq!(__clzsi2(i) as u32, i.leading_zeros()); + i <<= 2; + i += 1; + } } // TODO: add some shims From 35ed03cb448e3176b44af33e2660495f35e66d61 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sun, 23 Dec 2018 14:45:38 -0700 Subject: [PATCH 05/13] cleanup, hello world runs 100% safe now --- Cargo.toml | 2 +- examples/hello_world.rs | 23 +++++++ src/io.rs | 1 + src/io/display.rs | 110 ++++++++++++++++++++++++++++++++++ src/lib.rs | 130 +++++++--------------------------------- src/video.rs | 83 +++++++++++++++++++++++++ src/video_ram.rs | 83 ------------------------- 7 files changed, 239 insertions(+), 193 deletions(-) create mode 100644 examples/hello_world.rs create mode 100644 src/io/display.rs create mode 100644 src/video.rs delete mode 100644 src/video_ram.rs diff --git a/Cargo.toml b/Cargo.toml index 400c908..d3d18c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ publish = false [dependencies] typenum = "1.10" -gba-proc-macro = "0.2.1" +gba-proc-macro = "0.3" #[dev-dependencies] #quickcheck="0.7" diff --git a/examples/hello_world.rs b/examples/hello_world.rs new file mode 100644 index 0000000..e1abae3 --- /dev/null +++ b/examples/hello_world.rs @@ -0,0 +1,23 @@ +#![no_std] +#![feature(start)] + +use gba::{ + io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + video::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(DisplayControlMode::Bitmap3).with_display_bg2(true); + 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)); + Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31)); + loop {} +} diff --git a/src/io.rs b/src/io.rs index 5149b5a..c0f5424 100644 --- a/src/io.rs +++ b/src/io.rs @@ -10,4 +10,5 @@ use super::*; use gba_proc_macro::register_bit; +pub mod display; pub mod keypad; diff --git a/src/io/display.rs b/src/io/display.rs new file mode 100644 index 0000000..e3ada94 --- /dev/null +++ b/src/io/display.rs @@ -0,0 +1,110 @@ +//! Contains types and definitions for display related IO registers. + +use super::*; + +/// LCD Control. Read/Write. +/// +/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) +pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; + +newtype!( + /// A newtype over the various display control options that you have on a GBA. + #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] + DisplayControlSetting, + u16 +); + +#[allow(missing_docs)] +impl DisplayControlSetting { + pub const BG_MODE_MASK: u16 = 0b111; + + pub fn mode(self) -> DisplayControlMode { + // TODO: constify + match self.0 & Self::BG_MODE_MASK { + 0 => DisplayControlMode::Tiled0, + 1 => DisplayControlMode::Tiled1, + 2 => DisplayControlMode::Tiled2, + 3 => DisplayControlMode::Bitmap3, + 4 => DisplayControlMode::Bitmap4, + 5 => DisplayControlMode::Bitmap5, + _ => unreachable!(), + } + } + pub const fn with_mode(self, new_mode: DisplayControlMode) -> Self { + Self((self.0 & !Self::BG_MODE_MASK) | (new_mode as u16)) + } + + register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode); + register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled); + register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free); + register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d); + register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank); + register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0); + register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1); + register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2); + register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3); + register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object); + register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0); + register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1); + register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window); +} + +/// The six display modes available on the GBA. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DisplayControlMode { + /// This basically allows for the most different things at once (all layers, + /// 1024 tiles, two palette modes, etc), but you can't do affine + /// transformations. + Tiled0 = 0, + /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, + /// and BG2 runs as if in `Tiled2`. + Tiled1 = 1, + /// This allows affine transformations, but only uses BG2 and BG3. + Tiled2 = 2, + /// This is the basic bitmap draw mode. The whole screen is a single bitmap. + /// Uses BG2 only. + Bitmap3 = 3, + /// This uses _paletted color_ so that there's enough space to have two pages + /// at _full resolution_, allowing page flipping. Uses BG2 only. + Bitmap4 = 4, + /// This uses _reduced resolution_ so that there's enough space to have two + /// pages with _full color_, allowing page flipping. Uses BG2 only. + Bitmap5 = 5, +} + +/// Assigns the given display control setting. +pub fn set_display_control(setting: DisplayControlSetting) { + DISPCNT.write(setting); +} +/// Obtains the current display control setting. +pub fn display_control() -> DisplayControlSetting { + DISPCNT.read() +} + +/// If the `VCOUNT` register reads equal to or above this then you're in vblank. +pub const VBLANK_SCANLINE: u16 = 160; + +/// Vertical Counter (LY). +/// +/// 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 +/// is in a "vertical blank" period. +pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; + +/// 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 {} +} diff --git a/src/lib.rs b/src/lib.rs index ce9c46a..aa57ef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,27 @@ pub(crate) use self::base::*; pub mod bios; pub mod io; -pub mod video_ram; +pub mod video; + +newtype! { + /// A color on the GBA is an RGB 5.5.5 within a `u16` + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + Color, u16 +} + +impl Color { + /// Constructs a color from the channel values provided (should be 0..=31). + /// + /// No actual checks are performed, so illegal channel values can overflow + /// into each other and produce an unintended color. + pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color { + Color(b << 10 | g << 5 | r) + } +} + +// +// After here is totally unsorted nonsense +// /// Performs unsigned divide and remainder, gives None if dividing by 0. pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> { @@ -220,111 +240,3 @@ mod tests { } } */ - -use gba_proc_macro::register_bit; - -/// LCD Control. Read/Write. -/// -/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) -pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; - -newtype!( - /// A newtype over the various display control options that you have on a GBA. - #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] - DisplayControlSetting, - u16 -); - -#[allow(missing_docs)] -impl DisplayControlSetting { - pub const BG_MODE_MASK: u16 = 0b111; - - pub fn mode(self) -> DisplayControlMode { - match self.0 & Self::BG_MODE_MASK { - 0 => DisplayControlMode::Tiled0, - 1 => DisplayControlMode::Tiled1, - 2 => DisplayControlMode::Tiled2, - 3 => DisplayControlMode::Bitmap3, - 4 => DisplayControlMode::Bitmap4, - 5 => DisplayControlMode::Bitmap5, - _ => unreachable!(), - } - } - pub fn set_mode(&mut self, new_mode: DisplayControlMode) { - self.0 &= !Self::BG_MODE_MASK; - self.0 |= match new_mode { - DisplayControlMode::Tiled0 => 0, - DisplayControlMode::Tiled1 => 1, - DisplayControlMode::Tiled2 => 2, - DisplayControlMode::Bitmap3 => 3, - DisplayControlMode::Bitmap4 => 4, - DisplayControlMode::Bitmap5 => 5, - }; - } - - register_bit!(CGB_MODE_BIT, u16, 0b1000, cgb_mode); - register_bit!(PAGE_SELECT_BIT, u16, 0b1_0000, page1_enabled); - register_bit!(HBLANK_INTERVAL_FREE_BIT, u16, 0b10_0000, hblank_interval_free); - register_bit!(OBJECT_MEMORY_1D, u16, 0b100_0000, object_memory_1d); - register_bit!(FORCE_BLANK_BIT, u16, 0b1000_0000, force_blank); - register_bit!(DISPLAY_BG0_BIT, u16, 0b1_0000_0000, display_bg0); - register_bit!(DISPLAY_BG1_BIT, u16, 0b10_0000_0000, display_bg1); - register_bit!(DISPLAY_BG2_BIT, u16, 0b100_0000_0000, display_bg2); - register_bit!(DISPLAY_BG3_BIT, u16, 0b1000_0000_0000, display_bg3); - register_bit!(DISPLAY_OBJECT_BIT, u16, 0b1_0000_0000_0000, display_object); - register_bit!(DISPLAY_WINDOW0_BIT, u16, 0b10_0000_0000_0000, display_window0); - register_bit!(DISPLAY_WINDOW1_BIT, u16, 0b100_0000_0000_0000, display_window1); - register_bit!(OBJECT_WINDOW_BIT, u16, 0b1000_0000_0000_0000, display_object_window); -} - -/// The six display modes available on the GBA. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DisplayControlMode { - /// This basically allows for the most different things at once (all layers, - /// 1024 tiles, two palette modes, etc), but you can't do affine - /// transformations. - Tiled0, - /// This is a mix of `Tile0` and `Tile2`: BG0 and BG1 run as if in `Tiled0`, - /// and BG2 runs as if in `Tiled2`. - Tiled1, - /// This allows affine transformations, but only uses BG2 and BG3. - Tiled2, - /// This is the basic bitmap draw mode. The whole screen is a single bitmap. - /// Uses BG2 only. - Bitmap3, - /// This uses _paletted color_ so that there's enough space to have two pages - /// at _full resolution_, allowing page flipping. Uses BG2 only. - Bitmap4, - /// This uses _reduced resolution_ so that there's enough space to have two - /// pages with _full color_, allowing page flipping. Uses BG2 only. - Bitmap5, -} - -/// Assigns the given display control setting. -pub fn set_display_control(setting: DisplayControlSetting) { - DISPCNT.write(setting); -} -/// Obtains the current display control setting. -pub fn display_control() -> DisplayControlSetting { - DISPCNT.read() -} - -/// Vertical Counter (LY) -pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; - -/// Obtains the current VCount value. -pub fn vcount() -> u16 { - VCOUNT.read() -} - -/// Performs a busy loop until VBlank starts. -pub fn wait_until_vblank() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() < crate::video_ram::SCREEN_HEIGHT as u16 {} -} - -/// Performs a busy loop until VDraw starts. -pub fn wait_until_vdraw() { - // TODO: make this the better version with BIOS and interrupts and such. - while vcount() >= crate::video_ram::SCREEN_HEIGHT as u16 {} -} diff --git a/src/video.rs b/src/video.rs new file mode 100644 index 0000000..d6bb6c7 --- /dev/null +++ b/src/video.rs @@ -0,0 +1,83 @@ +//! Module for all things relating to the Video RAM. +//! +//! Note that the GBA has six different display modes available, and the +//! _meaning_ of Video RAM depends on which display mode is active. In all +//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`. +//! +//! # Safety +//! +//! Note that all possible bit patterns are technically allowed within Video +//! Memory. If you write the "wrong" thing into video memory you don't crash the +//! GBA, instead you just get graphical glitches (or perhaps nothing at all). +//! Accordingly, the "safe" functions here will check that you're in bounds, but +//! they won't bother to check that you've set the video mode they're designed +//! for. + +pub use super::*; + +/// The start of VRAM. +/// +/// Depending on what display mode is currently set there's different ways that +/// your program should interpret the VRAM space. Accordingly, we give the raw +/// value as just being a `usize`. Specific video mode types then wrap this as +/// being the correct thing. +pub const VRAM_BASE_USIZE: usize = 0x600_0000; + +/// Mode 3 is a bitmap mode with full color and full resolution. +/// +/// * **Width:** 240 +/// * **Height:** 160 +/// +/// Because the memory requirements are so large, there's only a single page +/// available instead of two pages like the other video modes have. +/// +/// As with all bitmap modes, the bitmap itself utilizes BG2 for display, so you +/// must have that BG enabled in addition to being within Mode 3. +pub struct Mode3; +impl Mode3 { + /// The physical width in pixels of the GBA screen. + pub const SCREEN_WIDTH: usize = 240; + + /// The physical height in pixels of the GBA screen. + pub const SCREEN_HEIGHT: usize = 160; + + /// The Mode 3 VRAM. + /// + /// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel, + /// or use the helpers provided in this module. + pub const VRAM: VolAddressBlock = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) }; + + /// private iterator over the pixels, two at a time + const BULK_ITER: VolAddressIter = + unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2).iter() }; + + /// Reads the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn read_pixel(col: usize, row: usize) -> Option { + Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read) + } + + /// Writes the pixel at the given (col,row). + /// + /// # Failure + /// + /// Gives `None` if out of bounds. + pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> { + Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color)) + } + + /// Clears the whole screen to the desired color. + pub fn clear_to(color: Color) { + let color32 = color.0 as u32; + let bulk_color = color32 << 16 | color32; + for va in Self::BULK_ITER { + va.write(bulk_color) + } + } + + // TODO: dma_clear_to? +} diff --git a/src/video_ram.rs b/src/video_ram.rs deleted file mode 100644 index 232249f..0000000 --- a/src/video_ram.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Module for all things relating to the Video RAM. -//! -//! Note that the GBA has six different display modes available, and the -//! _meaning_ of Video RAM depends on which display mode is active. In all -//! cases, Video RAM is **96kb** from `0x0600_0000` to `0x0601_7FFF`. -//! -//! # Safety -//! -//! Note that all possible bit patterns are technically allowed within Video -//! Memory. If you write the "wrong" thing into video memory you don't crash the -//! GBA, instead you just get graphical glitches (or perhaps nothing at all). -//! Accordingly, the "safe" functions here will check that you're in bounds, but -//! they won't bother to check that you've set the video mode they're designed -//! for. - -pub use super::*; - -// TODO: kill all this too - -/// The physical width in pixels of the GBA screen. -pub const SCREEN_WIDTH: isize = 240; - -/// The physical height in pixels of the GBA screen. -pub const SCREEN_HEIGHT: isize = 160; - -/// The start of VRAM. -/// -/// Depending on what display mode is currently set there's different ways that -/// your program should interpret the VRAM space. Accordingly, we give the raw -/// value as just being a `usize`. -pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000; - -const MODE3_VRAM: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) }; - -/// Draws a pixel to the screen while in Display Mode 3, with bounds checks. -/// -/// # Panics -/// -/// If `col` or `row` are out of bounds this will panic. -pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) { - assert!(col >= 0 && col < SCREEN_WIDTH); - assert!(row >= 0 && row < SCREEN_HEIGHT); - unsafe { mode3_draw_pixel_unchecked(col, row, color) } -} - -/// Draws a pixel to the screen while in Display Mode 3. -/// -/// Coordinates are relative to the top left corner. -/// -/// If you're in another mode you'll get something weird drawn, but it's memory -/// safe in the rust sense as long as you stay in bounds. -/// -/// # Safety -/// -/// * `col` must be in `0..SCREEN_WIDTH` -/// * `row` must be in `0..SCREEN_HEIGHT` -pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) { - MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color); -} - -/// Reads the given pixel of video memory according to Mode 3 placement. -/// -/// # Failure -/// -/// If the location is out of bounds you get `None`. -pub fn mode3_read_pixel(col: isize, row: isize) -> Option { - if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT { - unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) } - } else { - None - } -} - -/// Clears the entire screen to the color specified. -pub unsafe fn mode3_clear_screen(color: u16) { - // TODO: use DMA? - let color = color as u32; - let bulk_color = color << 16 | color; - let block: VolAddressBlock = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize); - for b in block.iter() { - b.write(bulk_color); - } -} From 7ab96c35f26f1171120a343c700e034be3efbca5 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sun, 23 Dec 2018 15:10:14 -0700 Subject: [PATCH 06/13] light cycle is also 100% safe now --- examples/hello_world.rs | 1 + examples/light_cycle.rs | 188 ++++++++-------------------------------- src/lib.rs | 8 ++ 3 files changed, 46 insertions(+), 151 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index e1abae3..1f5a360 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,5 +1,6 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] use gba::{ io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, diff --git a/examples/light_cycle.rs b/examples/light_cycle.rs index c593c19..ee367f9 100644 --- a/examples/light_cycle.rs +++ b/examples/light_cycle.rs @@ -1,5 +1,15 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] + +use gba::{ + io::{ + display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT}, + keypad::read_key_input, + }, + video::Mode3, + Color, +}; #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { @@ -8,168 +18,44 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - DISPCNT.write(MODE3 | BG2); - } + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + DISPCNT.write(SETTING); - let mut px = SCREEN_WIDTH / 2; - let mut py = SCREEN_HEIGHT / 2; - let mut color = rgb16(31, 0, 0); + 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 = key_input(); + let this_frame_keys = read_key_input(); // adjust game state and wait for vblank - px += 2 * this_frame_keys.column_direction() as isize; - py += 2 * this_frame_keys.row_direction() as isize; - wait_until_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. - unsafe { - if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT { - // out of bounds, reset the screen and position. - mode3_clear_screen(0); + 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::clear_to(BLACK); color = color.rotate_left(5); - px = SCREEN_WIDTH / 2; - py = SCREEN_HEIGHT / 2; } else { - let color_here = mode3_read_pixel(px, py); - if color_here != 0 { - // crashed into our own line, reset the screen - mode3_clear_screen(0); - color = color.rotate_left(5); - } else { - // draw the new part of the line - mode3_draw_pixel(px, py, color); - mode3_draw_pixel(px, py + 1, color); - mode3_draw_pixel(px + 1, py, color); - mode3_draw_pixel(px + 1, py + 1, color); - } + // 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); } } - wait_until_vdraw(); + spin_until_vdraw(); } } - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct VolatilePtr(pub *mut T); -impl VolatilePtr { - pub unsafe fn read(&self) -> T { - core::ptr::read_volatile(self.0) - } - pub unsafe fn write(&self, data: T) { - core::ptr::write_volatile(self.0, data); - } - pub unsafe fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) - } -} - -pub const DISPCNT: VolatilePtr = VolatilePtr(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 SCREEN_HEIGHT: isize = 160; - -pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { - blue << 10 | green << 5 | red -} - -pub unsafe fn mode3_clear_screen(color: u16) { - let color = color as u32; - let bulk_color = color << 16 | color; - let mut ptr = VolatilePtr(VRAM as *mut u32); - for _ in 0..SCREEN_HEIGHT { - for _ in 0..(SCREEN_WIDTH / 2) { - ptr.write(bulk_color); - ptr = ptr.offset(1); - } - } -} - -pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) { - VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); -} - -pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 { - VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read() -} - -pub const KEYINPUT: VolatilePtr = VolatilePtr(0x400_0130 as *mut u16); - -/// A newtype over the key input state of the GBA. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -#[repr(transparent)] -pub struct KeyInputSetting(u16); - -/// A "tribool" value helps us interpret the arrow pad. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] -pub enum TriBool { - Minus = -1, - Neutral = 0, - Plus = 1, -} - -pub fn key_input() -> KeyInputSetting { - unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b0000_0011_1111_1111) } -} - -pub const KEY_A: u16 = 1 << 0; -pub const KEY_B: u16 = 1 << 1; -pub const KEY_SELECT: u16 = 1 << 2; -pub const KEY_START: u16 = 1 << 3; -pub const KEY_RIGHT: u16 = 1 << 4; -pub const KEY_LEFT: u16 = 1 << 5; -pub const KEY_UP: u16 = 1 << 6; -pub const KEY_DOWN: u16 = 1 << 7; -pub const KEY_R: u16 = 1 << 8; -pub const KEY_L: u16 = 1 << 9; - -impl KeyInputSetting { - pub fn contains(&self, key: u16) -> bool { - (self.0 & key) != 0 - } - - pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting { - KeyInputSetting(self.0 ^ other.0) - } - - pub fn column_direction(&self) -> TriBool { - if self.contains(KEY_RIGHT) { - TriBool::Plus - } else if self.contains(KEY_LEFT) { - TriBool::Minus - } else { - TriBool::Neutral - } - } - - pub fn row_direction(&self) -> TriBool { - if self.contains(KEY_DOWN) { - TriBool::Plus - } else if self.contains(KEY_UP) { - TriBool::Minus - } else { - TriBool::Neutral - } - } -} - -pub const VCOUNT: VolatilePtr = VolatilePtr(0x0400_0006 as *mut u16); - -pub fn vcount() -> u16 { - unsafe { VCOUNT.read() } -} - -pub fn wait_until_vblank() { - while vcount() < SCREEN_HEIGHT as u16 {} -} - -pub fn wait_until_vdraw() { - while vcount() >= SCREEN_HEIGHT as u16 {} -} diff --git a/src/lib.rs b/src/lib.rs index aa57ef1..35d464c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(test), no_std)] #![feature(asm)] #![feature(const_int_wrapping)] +#![feature(const_int_rotate)] #![feature(min_const_unsafe_fn)] #![warn(missing_docs)] #![allow(clippy::cast_lossless)] @@ -78,6 +79,13 @@ impl Color { pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color { 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)) + } } // From 7d8c82ddbcc734acc7b19b7751dd3193f3b9116a Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sun, 23 Dec 2018 15:52:42 -0700 Subject: [PATCH 07/13] mgba panic handler 100% safe, justrelease only tho --- examples/mgba_panic_handler.rs | 113 +++++---------------------------- src/lib.rs | 2 +- src/mgba.rs | 79 +++++++++++++++++++++++ 3 files changed, 97 insertions(+), 97 deletions(-) create mode 100644 src/mgba.rs diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs index 8620d26..17e7057 100644 --- a/examples/mgba_panic_handler.rs +++ b/examples/mgba_panic_handler.rs @@ -1,109 +1,30 @@ #![no_std] #![feature(start)] +#![forbid(unsafe_code)] -#[no_mangle] -pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize { - let mut y: usize; - let mut n: usize = 32; - y = x >> 16; - if y != 0 { - n = n - 16; - x = y; - } - y = x >> 8; - if y != 0 { - n = n - 8; - x = y; - } - y = x >> 4; - if y != 0 { - n = n - 4; - x = y; - } - y = x >> 2; - if y != 0 { - n = n - 2; - x = y; - } - y = x >> 1; - if y != 0 { - n - 2 - } else { - n - x - } -} +use gba::{ + io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT}, + video::Mode3, + Color, +}; #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - unsafe { - const DEBUG_ENABLE_MGBA: *mut u16 = 0x4fff780 as *mut u16; - const DEBUG_OUTPUT_BASE: *mut u8 = 0x4fff600 as *mut u8; - const DEBUG_SEND_MGBA: *mut u16 = 0x4fff700 as *mut u16; - const DEBUG_SEND_FLAG: u16 = 0x100; - const DEBUG_FATAL: u16 = 0; - const DEBUG_ERROR: u16 = 1; - DEBUG_ENABLE_MGBA.write_volatile(0xC0DE); - if DEBUG_ENABLE_MGBA.read_volatile() == 0x1DEA { - // Give the location - if let Some(location) = info.location() { - let mut out_ptr = DEBUG_OUTPUT_BASE; - let line = location.line(); - let mut line_bytes = [ - (line / 10000) as u8, - ((line / 1000) % 10) as u8, - ((line / 1000) % 10) as u8, - ((line / 10) % 10) as u8, - (line % 10) as u8, - ]; - for line_bytes_mut in line_bytes.iter_mut() { - *line_bytes_mut += b'0'; - } - for b in "Panic: " - .bytes() - .chain(location.file().bytes()) - .chain(", Line ".bytes()) - .chain(line_bytes.iter().cloned()) - .take(255) - { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } else { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in "Panic with no location info:".bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); - // Give the payload - if let Some(payload) = info.payload().downcast_ref::<&str>() { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in payload.bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } else { - let mut out_ptr = DEBUG_OUTPUT_BASE; - for b in "no payload".bytes().take(255) { - out_ptr.write_volatile(b); - out_ptr = out_ptr.offset(1); - } - } - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); - DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_FATAL); - } + use core::fmt::Write; + use gba::mgba::{MGBADebug, MGBADebugLevel}; + if let Some(mut mgba) = MGBADebug::new() { + let _ = write!(mgba, "{}", info); + mgba.send(MGBADebugLevel::Fatal); } loop {} } #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { - unsafe { - (0x400_0000 as *mut u16).write_volatile(0x0403); - (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); - panic!("fumoffu!"); - } + const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true); + 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)); + Mode3::write_pixel(120, 96, Color::from_rgb(0, 0, 31)); + panic!("fumoffu!"); } diff --git a/src/lib.rs b/src/lib.rs index 35d464c..193139f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ pub mod base; pub(crate) use self::base::*; pub mod bios; pub mod io; - +pub mod mgba; pub mod video; newtype! { diff --git a/src/mgba.rs b/src/mgba.rs new file mode 100644 index 0000000..24e6648 --- /dev/null +++ b/src/mgba.rs @@ -0,0 +1,79 @@ +//! Special utils for if you're running on the mGBA emulator. + +use super::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +#[allow(missing_docs)] +pub enum MGBADebugLevel { + Fatal = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, +} + +/// Allows writing to the `mGBA` debug output. +#[derive(Debug, PartialEq, Eq)] +pub struct MGBADebug { + bytes_written: u8, +} +impl MGBADebug { + const ENABLE_ADDRESS: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff780) }; + const ENABLE_ADDRESS_INPUT: u16 = 0xC0DE; + const ENABLE_ADDRESS_OUTPUT: u16 = 0x1DEA; + + const OUTPUT_BASE: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff600) }; + + const SEND_ADDRESS: VolAddress = unsafe { VolAddress::new_unchecked(0x4fff700) }; + const SEND_FLAG: u16 = 0x100; + + /// Gives a new MGBADebug, if running within `mGBA` + /// + /// # Fails + /// + /// If you're not running in the `mGBA` emulator. + pub fn new() -> Option { + Self::ENABLE_ADDRESS.write(Self::ENABLE_ADDRESS_INPUT); + if Self::ENABLE_ADDRESS.read() == Self::ENABLE_ADDRESS_OUTPUT { + Some(MGBADebug { bytes_written: 0 }) + } else { + None + } + } + + /// Once output is buffered you must send it out with a level. + /// + /// If the `Fatal` level is selected, the buffer is sent out as `Error` + /// followed by a blank message being sent as `Error`. This is done because + /// the `Fatal` message appears in a popup without showing up in the log, so + /// it might accidentally be discarded. + pub fn send(&mut self, level: MGBADebugLevel) { + if level == MGBADebugLevel::Fatal { + Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Error as u16); + Self::SEND_ADDRESS.write(Self::SEND_FLAG | MGBADebugLevel::Fatal as u16); + } else { + Self::SEND_ADDRESS.write(Self::SEND_FLAG | level as u16); + } + } +} + +impl core::fmt::Write for MGBADebug { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + unsafe { + let mut current = Self::OUTPUT_BASE.offset(self.bytes_written as isize); + let mut str_iter = s.bytes(); + while self.bytes_written < 255 { + match str_iter.next() { + Some(byte) => { + current.write(byte); + current = current.offset(1); + self.bytes_written += 1; + } + None => return Ok(()), + } + } + Err(core::fmt::Error) + } + } +} From ce3b8717287db6ecab24a492055ebbc79470a730 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 11:57:57 -0700 Subject: [PATCH 08/13] more button info --- book/src/04-non-video/01-buttons.md | 17 +++++++++++++++++ src/io/display.rs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 2aa065c..9111bfd 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -63,6 +63,23 @@ the whole frame. If you've worked with polling input before that should sound totally normal, but if not just always remember to gather the input once per frame and then use that value across the whole frame. +### Detecting New Presses + +The keypad only tells you what's _currently_ pressed, but if you want to check +what's _newly_ pressed it's not too much harder. + +All that you do is store the last frame's keys and compare them to the current +keys with an `XOR`. In the `gba` crate it's called `KeyInput::difference`. Once +you've got the difference between last frame and this frame, you know what +changes happened. + +* If something is in the difference and _not pressed_ in the last frame, that + means it was newly pressed. +* If something is in the difference and _pressed_ in the last frame that means + it was newly released. +* If something is not in the difference then there's no change between last + frame and this frame. + ## Key Interrupt Control * KEYCNT, `0x400_0132`, `u16`, read/write diff --git a/src/io/display.rs b/src/io/display.rs index e3ada94..ce7cda6 100644 --- a/src/io/display.rs +++ b/src/io/display.rs @@ -85,7 +85,7 @@ pub fn display_control() -> DisplayControlSetting { /// If the `VCOUNT` register reads equal to or above this then you're in vblank. pub const VBLANK_SCANLINE: u16 = 160; -/// Vertical Counter (LY). +/// Vertical Counter (LY). Read only. /// /// 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 From 29ccbf4ba9d500b319d382f2f16ef2810c1aa77b Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 11:59:52 -0700 Subject: [PATCH 09/13] formatting --- book/src/04-non-video/01-buttons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 9111bfd..6fe773a 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -85,7 +85,7 @@ changes happened. * KEYCNT, `0x400_0132`, `u16`, read/write This lets you control what keys will trigger a keypad interrupt. Of course, for -the actual interrupt to fire you also need to set the `IME` and IE` registers +the actual interrupt to fire you also need to set the `IME` and `IE` registers properly. See the [Interrupts](05-interrupts.md) section for details there. The main thing to know about this register is that the keys are in _the exact From d0f43feae2b0d6e7af1e7aac9818b45abda90dfa Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 12:03:10 -0700 Subject: [PATCH 10/13] toolchain nightly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0026b53..090a2b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ rust: before_script: - rustup component add rust-src - - rustup component add clippy + - rustup component add clippy --toolchain=nightly - (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-make || cargo install cargo-make) From 65bee772a1461911557b0e90c969d1d62a12751a Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 12:08:11 -0700 Subject: [PATCH 11/13] zero thing cleanup --- book/src/04-non-video/01-buttons.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 6fe773a..899eb93 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -29,9 +29,11 @@ The buttons are, going up in order from the 0th bit: * R * L -Bits above that are not used. However, since the arrow left and right can never -be pressed at the same time, and the arrows up and down can never be pressed at -the same time, this register will never read as zero. +Bits above that are not used. However, since the left and right directions, as +well as the up and down directions, can never be pressed at the same time, the +`KEYINPUT` register should never read as zero. Of course, the register _might_ +read as zero if someone is using an emulator that allows for such inputs, so I +wouldn't go so far as to make it be `NonZeroU16` or anything like that. When programming, we usually are thinking of what buttons we want to have _be pressed_ instead of buttons we want to have _not be pressed_. This means that we From 2f26869b4f430c58b5c74e5c5a7717abef6f1c9c Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 12:11:08 -0700 Subject: [PATCH 12/13] key input clarity --- book/src/04-non-video/01-buttons.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/04-non-video/01-buttons.md b/book/src/04-non-video/01-buttons.md index 899eb93..586f6c3 100644 --- a/book/src/04-non-video/01-buttons.md +++ b/book/src/04-non-video/01-buttons.md @@ -62,8 +62,8 @@ Note that the current `KEYINPUT` value changes in real time as the user presses or releases the buttons. To account for this, it's best to read the value just once per game frame and then use that single value as if it was the input across the whole frame. If you've worked with polling input before that should sound -totally normal, but if not just always remember to gather the input once per -frame and then use that value across the whole frame. +totally normal. If not, just remember to call `read_key_input` once per frame +and then use that `KeyInput` value across the whole frame. ### Detecting New Presses From 91a958fc7708deae6e4429c67558ec7c09a37916 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 24 Dec 2018 12:12:33 -0700 Subject: [PATCH 13/13] apparently clippy refuses to work in CI right now --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 090a2b7..ec6794f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ rust: before_script: - rustup component add rust-src - - rustup component add clippy --toolchain=nightly + #- rustup component add clippy --toolchain=nightly - (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-make || cargo install cargo-make) @@ -29,8 +29,8 @@ script: - export PATH="$PATH:/opt/devkitpro/tools/bin" - cd .. # Run all verificaions, both debug and release - - cargo clippy - - cargo clippy --release + #- cargo clippy + #- cargo clippy --release - cargo test --no-fail-fast --lib - cargo test --no-fail-fast --lib --release - cargo test --no-fail-fast --tests