gba/docs/ch02/the_key_input_register.html
2018-11-17 17:14:42 -07:00

399 lines
21 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>The Key Input Register - Rust GBA Guide</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">var path_to_root = "../";</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="../ch00/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="../ch01/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="../ch01/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="../ch01/volatile.html"><strong aria-hidden="true">3.2.</strong> Volatile</a></li><li><a href="../ch01/io_registers.html"><strong aria-hidden="true">3.3.</strong> IO Registers</a></li><li><a href="../ch01/the_display_control_register.html"><strong aria-hidden="true">3.4.</strong> The Display Control Register</a></li><li><a href="../ch01/video_memory_intro.html"><strong aria-hidden="true">3.5.</strong> Video Memory Intro</a></li><li><a href="../ch01/hello2.html"><strong aria-hidden="true">3.6.</strong> hello2</a></li></ol></li><li><a href="../ch02/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="../ch02/the_key_input_register.html" class="active"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="../ch02/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="../ch02/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li><li><a href="../ch03/index.html"><strong aria-hidden="true">5.</strong> Ch 3: Memory and Objects</a></li></ol>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust GBA Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
<p>The Key Input Register is our next IO register. Its shorthand name is
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
at <code>0x4000130</code>. The entire register is obviously read only, you can't tell the
GBA what buttons are pressed.</p>
<p>Each button is exactly one bit:</p>
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
</tbody></table>
<p>The higher bits above are not used at all.</p>
<p>Similar to other old hardware devices, the convention here is that a button's
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
user is not touching the device at all the KEYINPUT value will read
<code>0b0000_0011_1111_1111</code>. There's similar values for when the user is pressing as
many buttons as possible, but since the left/right and up/down keys are on an
arrow pad the value can never be 0 since you can't ever press every single key
at once.</p>
<p>When dealing with key input, the register always shows the exact key values at
any moment you read it. Obviously that's what it should do, but what it means to
you as a programmer is that you should usually gather input once at the top of a
game frame and then use that single input poll as the input values across the
whole game frame.</p>
<p>Of course, you might want to know if a user's key state changed from frame to
frame. That's fairly easy too: We just store the last frame keys as well as the
current frame keys (it's only a <code>u16</code>) and then we can xor the two values.
Anything that shows up in the xor result is a key that changed. If it's changed
and it's now down, that means it was pushed this frame. If it's changed and it's
now up, that means it was released this frame.</p>
<p>The other major thing you might frequently want is to know &quot;which way&quot; the arrow
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
Except that often time we'll have situations where the direction just needs to
be multiplied by a speed and applied as a delta to a position. We want to
support that as well as we can too.</p>
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
<p>Let's get down to some code. First we want to make a way to read the address as
a <code>u16</code> and then wrap that in our newtype which will implement methods for
reading and writing the key bits.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEYINPUT: *mut u16 = 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);
pub fn read_key_input() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read_volatile()) }
}
#}</code></pre></pre>
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
how we think of things as a game designer and even as a player. That is, usually
you'd say &quot;if you press A, then X happens&quot; instead of &quot;if you don't press A,
then X does not happen&quot;.</p>
<p>Normally we'd pick a constant for the bit we want, <code>&amp;</code> it with our value, and
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the &quot;true&quot;
state we still pick the same constant and we still do the <code>&amp;</code>, but we test with
<code>== 0</code>. Practically the same, right? Well, since I'm asking a rhetorical
question like that you can probably already guess that it's not the same. I was
shocked to learn this too.</p>
<p>All we have to do is ask our good friend
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
compiles. The link there has the page set for the <code>stable</code> 1.30 compiler just so
that the link results stay consistent if you read this book in a year or
something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which is a
slightly later version of ARM than the actual GBA, but it's close enough for
just checking. Of course, in a full program small functions like these will
probably get inlined into the calling code and disappear entirely as they're
folded and refolded by the compiler, but we can just check.</p>
<p>It turns out that the <code>!=0</code> test is 4 instructions and the <code>==0</code> test is 6
instructions. Since we want to get savings where we can, and we'll probably
check the keys of an input often enough, we'll just always use a <code>!=0</code> test and
then adjust how we initially read the register to compensate. By using xor with
a mask for only the 10 used bits we can flip the &quot;low when pressed&quot; values so
that the entire result has active bits in all positions where a key is pressed.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn read_key_input() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b0000_0011_1111_1111) }
}
#}</code></pre></pre>
<p>Now we add a method for seeing if a key is pressed. In the full library there's
a more advanced version of this that's built up via macro, but for this example
we'll just name a bunch of <code>const</code> values and then have a method that takes a
value and says if that bit is on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEY_A: u16 = 1 &lt;&lt; 0;
pub const KEY_B: u16 = 1 &lt;&lt; 1;
pub const KEY_SELECT: u16 = 1 &lt;&lt; 2;
pub const KEY_START: u16 = 1 &lt;&lt; 3;
pub const KEY_RIGHT: u16 = 1 &lt;&lt; 4;
pub const KEY_LEFT: u16 = 1 &lt;&lt; 5;
pub const KEY_UP: u16 = 1 &lt;&lt; 6;
pub const KEY_DOWN: u16 = 1 &lt;&lt; 7;
pub const KEY_R: u16 = 1 &lt;&lt; 8;
pub const KEY_L: u16 = 1 &lt;&lt; 9;
impl KeyInputSetting {
pub fn contains(&amp;self, key: u16) -&gt; bool {
(self.0 &amp; key) != 0
}
}
#}</code></pre></pre>
<p>Because each key is a unique bit you can even check for more than one key at
once by just adding two key values together.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
#}</code></pre></pre>
<p>And we wanted to save the state of an old frame and compare it to the current
frame to see what was different:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn difference(&amp;self, other: KeyInputSetting) -&gt; KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
#}</code></pre></pre>
<p>Anything that's &quot;in&quot; the difference output is a key that <em>changed</em>, and then if
the key reads as pressed this frame that means it was just pressed. The exact
mechanics of all the ways you might care to do something based on new key
presses is obviously quite varied, but it might be something like this:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let this_frame_diff = this_frame_input.difference(last_frame_input);
if this_frame_diff.contains(KEY_B) &amp;&amp; this_frame_input.contains(KEY_B) {
// the user just pressed B, react in some way
}
#}</code></pre></pre>
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> as often as possible
just because it's easier on the GBA's CPU if we stick to its native number size.
Having it be an enum lets us use <code>match</code> and be sure that we've covered all our
cases.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
/// A &quot;tribool&quot; value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = +1,
}
#}</code></pre></pre>
<p>Now, how do we determine <em>which way</em> is plus or minus? Well... I don't know.
Really. I'm not sure what the best one is because the GBA really wants the
origin at 0,0 with higher rows going down and higher cols going right. On the
other hand, all the normal math you and I learned in school is oriented with
increasing Y being upward on the page. So, at least for this demo, we're going
to go with what the GBA wants us to do and give it a try. If we don't end up
confusing ourselves then we can stick with that. Maybe we can cover it over
somehow later on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn column_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
#}</code></pre></pre>
<p>So then in our game, every frame we can check for <code>column_direction</code> and
<code>row_direction</code> and then apply those to the player's current position to make
them move around the screen.</p>
<p>With that settled I think we're all done with user input for now. There's some
other things to eventually know about like key interrupts that you can set and
stuff, but we'll cover that later on because it's not necessary right now.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ch02/index.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../ch02/the_vcount_register.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="../ch02/index.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="../ch02/the_vcount_register.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>