A7 - Input & Interrupts Pt II

From VO-EM Wiki
Jump to: navigation, search

Last chapter, we set up a basic framework to detect when when a button on the Gamepad has been pressed, and react to that event. In this chapter, we'll be continuing that by learning how to find out specifically which key was pressed, and then react accordingly.

Overview

We've learned how to react when a device requires attention. However, at the moment, our program doesn't really talk to the device at all. Going back to our party metaphor - our friend has rung the doorbell, and we've stopped working and acknowledged it, but we haven't opened the door or greeted them. We don't even know which friend is at the door yet.

In this chapter, we will learn to read the input of a device, and modify the flow of our program based on its status.

Requirements

For this chapter, we're going to need the program we created last time. Here it is:

IRQ7		rfe		     		
IRQ6		rfe		
IRQ5		rfe		     			
IRQ4		rfe							
IRQ3		rfe	
IRQ2		j       input           ;handle gamepad input
IRQ1		rfe	
IRQ0		rfe                     	

listenTo   .wordu   2#000001000000000000000001 ;our psw settings
           .start   main 
  		
main    lw       r1,listenTo           ;load our psw settings
        movi2s   psw,r1                ;move into psw register
loop    addi     r2,r2,1               ;add 1 to register 2
        j        loop                  ;loop endlessly
 		
 		
input   addi     r1,r1,1               ;add 1 to register 1
        rfe                            ;go back to the main program

We're also going to need our multiplying program from the subroutine chapter:

mult_r3     .equ    0	  
	 
multiply    sw      mult_r3(r30),r3      
            clr     r3       
mult_loop   beqz    r2,mult_end 
            add     r3,r3,r1 
            subi    r2,r2,1  
            j       mult_loop 
mult_end    add     r1,r0,r3
            lw      r3,mult_r3(r30)
            jr      r31

Which also means that we'll need to load the RAM offset into r30. We'll put

RAM_OFFSET .equ    16#8000
           addui   r30,r0,RAM_OFFSET

Somewhere in the main program.

And of course, we're going to need our tools and runtimes just like always!

The Gamepad

We've mentioned the Gamepad a few times without really going into detail about it. Physically speaking, it is a gameboy-style array of buttons. However, all our CPU can see is a few bytes of memory, like any other, at a certain address.

Specifically, the gamepad is mapped to 0xFFFFFF00, and occupies 4 bytes. These bytes always contain the current status of the buttons on the gamepad.

The first byte (0xFFFFFF00) is the left/right axis on the directional pad. It reads zero if neither left or right is being pressed, -1 (0xFF) if left is being pressed, and 1 (0x01) if right is being pressed. The second byte (0xFFFFFF01) works the same for the up/down axis, with -1 meaning "up" and 1 meaning "down". These numbers are chosen because they make it very easy to move characters on the screen with the directional pad - just adding the value of these bytes to the x and y positions of an instance will move it appropriately on the screen. However, we'll come back to instances in a later tutorial.

The third byte (0xFFFFFF02) represents the A and B buttons. It will read 1 if A is pressed, 2 if B is pressed, and 3 if both are pressed.

The fourth byte (0xFFFFFF03) works the same for the Start and Select buttons, respectively.

With any program running in the debugger, try bringing up the Memory Status page by pressing 3, then switching to the Hardware registers by pressing the U key. Play with the Arrow keys and Z (A) and X (B) buttons, as well as the Spacebar (Start) and S key (Select). Can you see the memory values at 0xFFFFFF00 moving?

Making our program

For this tutorial, we're going to allow the user to control the input for the "multiply" subroutine - instead of programming in our own "a" and "b" arguments to multiply, the user will select them with the directional pad and press the "A" button to multiply their chosen numbers together.

First, we're going to need to load up the memory address offset of the gamepad into a register so that we can load its data. We'll make a word:

GP_OFFSET   .wordu   16#FFFFFF00

And then, in the "main" part of the program, we'll load that word into a register:

lw          r10,GP_OFFSET 

I put it in r10 because it's easy to remember and out of the way.

Next, we need to look at our "input" subroutine. This routine is called whenever the gamepad requests the CPU's attention, meaning whenever it's called, there should be a button press in one of the Gamepad's memory bytes for us to read.

We're going to store the user's "a" and "b" arguments in registers 6 and 7, respectively.

So:

input       lb       r5,(r10)              ;load the first byte of the gamepad register (up/down)
            add      r6,r6,r5              ;add it to 'a'
            lb       r5,1(r10)             ;load the second byte (left/right)
            add      r7,r7,r5              ;add it to 'b'

Pay attention to the third line - the 1(r10) means "contents of r10 + 1". Since the contents of r10 point to the first byte of the Gamepad's memory, adding 1 makes it point to the second byte.

Because our directional pad buttons give us a range from -1 to 1, we can just add them to our current values to increment and decrement them - convenient, right?

Next, we need to check if our user is pressing the "A" button. To do that, we need to load the third byte:

            lb       r5,2(r10)             ;load the third byte (a/b)

If you remember from above, the third byte contains the status of both the A and B buttons. So, we need to isolate the "A" button's byte. We do this by using an "AND" operation. "AND" let's you compare two sets of bits, and only keep the 1s if both bits have the same bit set to 1.

So, for example, if we had

0111
1001 

and we AND them together, we get

0001

Do you see why? The only digit where both were set to "1" was the lowest digit, so that's the only one we kept. This is often referred to as "bit masking" when it's used to discard parts of a byte we don't need.

In this case, we only need the very first bit in our byte, so we can mask it with 000000001, like so:

            andi     r5,r5,2#00000001      ;strip it down to just the A button

And then, we can use beqz to test if it's zero just like we use in our multiplication program - if it's zero, we'll skip to the end of the routine. If it's not, we'll multiply the numbers we have together!

            beqz     r5,input_end  ;if A isn't pressed, end 

How do we multiply the numbers together? You'll remember that our multiply subroutine multiplies the values of registers 1 and 2 together, so we'll need to copy our "a" and "b" arguments from r6 and r7 into r1 and r2. Then, we just have to call the "multiply" subroutine.

            add      r1,r0,r6
            add      r2,r0,r7 
            jal      multiply

And finally, we put an "rfe" instruction at the end to get back to our main program:

input_end   rfe                            ;go back to the main program

Putting it all together

Here's all the code for the tutorial put together:

IRQ7		 rfe		     		
IRQ6		 rfe		
IRQ5		 rfe		     			
IRQ4		 rfe							
IRQ3		 rfe	
IRQ2		 j        input           ;handle gamepad input
IRQ1		 rfe	
IRQ0		 rfe                     	

;The values we need
RAM_OFFSET  .equ     16#8000
GP_OFFSET   .wordu   16#FFFFFF00
listenTo    .wordu   2#000001000000000000000001 ;our psw settings
            .start   main 

;Our main program
main        addui    r30,r0,RAM_OFFSET
            lw       r10,GP_OFFSET 
            lw       r1,listenTo           ;load our psw settings
            movi2s   psw,r1                ;move into psw register
loop        wait                           ;wait for something to happen
            j        loop                  ;go back to waiting
 
;The gamepad input handler		
input       lb       r5,(r10)              ;load the first byte of the gamepad register (up/down)
            add      r6,r6,r5
            lb       r5,1(r10)             ;load the second byte (left/right)
            add      r7,r7,r5 
            lb       r5,2(r10)             ;load the third byte (a/b)
            andi     r5,r5,2#00000001      ;strip it down to just the A button
            beqz     r5,input_end
            add      r1,r0,r6
            add      r2,r0,r7
            jal      multiply
input_end   rfe                            ;go back to the main program
		 
;Multiplies r1 by r2; puts the result in r1
mult_r3     .equ    0	  	 
multiply    sw      mult_r3(r30),r3      
            clr     r3       
mult_loop   beqz    r2,mult_end 
            add     r3,r3,r1 
            subi    r2,r2,1  
            j       mult_loop 
mult_end    add     r1,r0,r3
            lw      r3,mult_r3(r30)
            jr      r31

You might notice that I changed the contents of "loop" to have a "wait" instruction instead of incrementing r2. We were incrementing r2 to show that the CPU can indeed do work while it's waiting to be interrupted. However, for this tutorial, we don't have any work for it to do. The "wait" instruction simply sleeps the CPU until an interrupt occurs that it is listening for. On handheld devices, this saves a lot of battery power! It could extend the battery life of the original Gameboy by hours.

Testing

I saved mine as "input2.dls", and assembled it with

java -jar dasm.jar input2.dls -a 

In the debugger, the Directional Pad is mapped to the arrow keys on your keyboard. The A button is mapped to Z, B is mapped to X, Start is mapped to the spacebar, and Select is mapped to S.

What do we expect this program to do? It should:

  • Do nothing while waiting for using input
  • Increment and decrement r6 with the left/right buttons
  • Increment and decrement r7 with the up/down buttons
  • Multiply r6 and r7 together when the A button is pressed, displaying the result in r1

Next time, we'll learn how to put things on the screen in A8 - Introduction to Computer Graphics.

Challenges

There are a few shortcomings in this program! Can you manage to improve it?

  • Because of the way the up/down register works, pressing "up" decreases the register and pressing "down" increases it. This is unintuitive! Can you fix it by changing just one instruction?
  • The program still crashes when the "b" variable is negative. Can you fix it?
  • It would be convenient to have a "clear" button - can you make the program clear both the inputs and the results when the "B" button is pressed?