Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

   Assembly

     Introduction

     Keyboard Input

       Part I

       Part II

       Part III

     Basic Graphics

     C & Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

TIGCC Assembly Lessons

TIGCC Assembly Programming Lessons

Lesson 2: The Basics of Keyboard Input

Step 3 - Keyboard Input via OSdeque

Okay, ngetchx is nice but is not perfect. We need something better; something that will work when we have to do more than just wait for keyboard input. So let's check out OSdequeue.

OSdequeue is a system function for taking an item out of a queue. In case you're not familiar with queues, here are the basics. It's like a line at a theme park or a store. The first thing to get into the queue is the first thing that comes out. Each thing to enter the queue is said to be enqueued. Each thing leaving the queue is said to be dequeued. So, how does this apply to keyboard input? The AMS maintains a keyboard queue where key presses you make are automatically enqueued. So, all we have to do is dequeue the key presses as they become available. The upside to OSdequeue over ngetchx is we do not have to wait for keys to be pressed before it's finished.

So, let's take a look at a sample program using OSdequeue. Start TIGCC and create a new project. Create a C source file and a GNU assembly file. Name the C file main and the assembly file kbdqueue. The main code is the same as before. You can grab it from lesson 1 if you don't have it handy. Edit the assembly file as follows:

kbdqueue.s


    .include "os.h"

    .equ    AMS_jumptable,0xC8
    
    .text
    .xdef asm_main

|-------------------------------------------------------------------------------
| ams_main - our program starts here
|

asm_main:
    movem.l %d3/%a3-%a5,-(%sp)      | preserve registers
    link    %fp,#-32                | allocate stack frame
    movea.l AMS_jumptable,%a5       | load the jumptable

    movea.l 4*ClrScr(%a5),%a4       | ClrScr();
    jsr     (%a4)

    clr.l   %d3                     | clear wait counter to 0

    bsr     kbd_queue               | %a0 = kbd_queue();
    movea.l %a0,%a3                 | save keyboard queue in a3

waitKey:
    move.l  %a3,-(%sp)              | %d0 = OSdequeue((%fp,-2), %a3);
    pea     (%fp,-2)

    movea.l 4*OSdequeue(%a5),%a4
    jsr     (%a4)
    
    addq.l  #8,%sp                  | reset stack pointer

    tst.w   %d0                     | if (%d0 != 0)
    bne     noKey                   | goto noKey

    move.w  (%fp,-2),-(%sp)         | sprintf((%fp,-32), str_key, (%fp,-2));
    pea     str_key(%pc)
    pea     (%fp,-32)

    movea.l 4*sprintf(%a5),%a4
    jsr     (%a4)

    move.w  #1,-(%sp)               | DrawStr(0, 10, (%fp,-32), 1)
    pea     (%fp,-32)
    move.w  #10,-(%sp)
    clr.w   -(%sp)

    movea.l 4*DrawStr(%a5),%a4
    jsr     (%a4)

    adda.l  #20,%sp                 | reset stack pointer

    movea.l 4*ngetchx(%a5),%a4      | ngetchx();
    jsr     (%a4)

    unlk    %fp                     | deallocate stack frame
    movem.l (%sp)+,%d3/%a3-%a5      | restore registers
    rts                             | exit program

noKey:
    addq.l  #1,%d3                  | increment d3 counter

    move.l  %d3,-(%sp)              | sprintf((%fp,-32), str_nokey, %d3);
    pea     str_nokey(%pc)          | push the wait string
    pea     (%fp,-32)               | push our stack space address

    movea.l 4*sprintf(%a5),%a4
    jbsr    (%a4)

    move.w  #4,-(%sp)               | DrawStr(0, 0, (%fp,-32), 4);
    pea     (%fp,-32)
    clr.l   -(%sp)

    movea.l 4*DrawStr(%a5),%a4
    jsr     (%a4)
    
    adda.l  #22,%sp                 | reset stack pointer
    bra     waitKey                 | goto waitKey

|-------------------------------------------------------------------------------
| Data Section
|
    .data

str_nokey:
    .string "Waiting... %-10lu"

str_key:
    .string "Key '%hd' was pressed..."

Step 3a - Run the Program

Save the project files and build the program. Send it to TiEmu and run it. It will look something like this.

TI-89 AMS 2.05 kbdqueue.89z TI-92+ AMS 2.05 kbdqueue.9xz

Step 3b - Program Analysis

Before we begin our analysis, let's just go over what the program will do. We are going to keep a running count of how many times we look for a key press. For every time we don't find one, we will print a string saying how many times we have checked. Once we do find one, we will print the keycode and wait for the user to press another key before exiting.

I'm not going to go over every line because of a lot of it is stuff you have already seen. I just want to touch on the new stuff. Let's start at the top of asm_main.

asm_main:
    movem.l %d3/%a3-%a5,-(%sp)      | preserve registers
    link    %fp,#-32                | allocate stack frame

We have seen movem before, but I want to make sure you understand the register list. We are saving the registers d3, a3, a4, and a5. Take a look back at lesson 1 where we first discussed the movem instruction if you have trouble understanding how I got this.

Next we have a new instruction, link. link, and its counterpart, unlk (UN-LinK) is used to allocate stack frames. A stack frame is a block of memory we keep on the stack. You saw in the last lesson how we grabbed some memory on the stack to use as a temporary string buffer for sprintf. This is the same in principle, but there is an added bonus. We use a register to keep track of this location so we don't have to continually figure out where our stack memory is. We only have to keep track of the values relative to this register, known as the frame pointer. The frame pointer (fp) is the same as a6, just like the stack pointer (sp) is a7. This is just a 68000 tradition.

If you look on page 215 in the Motorola Programmer's Reference, you will see the documentation for link. Basically, we save the old value of the frame pointer on the stack. Then we put the current value of the stack pointer into the frame pointer. Finally, we add the displacement value (i.e. how much memory we want) to the stack pointer. Because the stack grows up, we use negative values. In our program, we are allocating a 32 byte stack frame. We are going to use 2 bytes for the keycode, and 30 bytes for a temporary string buffer for sprintf. We will see this later.

    clr.l   %d3                     | clear wait counter to 0

    bsr     kbd_queue               | %a0 = kbd_queue();
    movea.l %a0,%a3                 | save keyboard queue in a3

I'm skipping over the call to ClrScr since it's obvious. The first opcode clears the d3 register. This will be used as our counter, and it will start at 0.

The next thing to do is call the kbd_queue function. This is not an AMS function, but rather a function of the TIGCC library. This function will get use the address of the system keyboard queue.

Notice the new instruction, bsr (Branch to SubRoutine) rather than jsr which we have used for function calls in the past. Because the kbd_queue function will be included in our program by the linker, it is very close to our code. Jumping is for far away code, while branching is for things that are close to us. Branching is faster than jumping, so we will branch here.

If you look up kbd_queue in the TIGCC docs, you will see it takes no arguments and returns a void pointer. A pointer is always an address, and addresses are returned in the a0 register. Since a0 is a register that may be destroyed by other function calls, we will move it to the a3 register for safe-keeping.

waitKey:
    move.l  %a3,-(%sp)              | %d0 = OSdequeue((%fp,-2), %a3);
    pea     (%fp,-2)

    movea.l 4*OSdequeue(%a5),%a4
    jsr     (%a4)
    
    addq.l  #8,%sp                  | reset stack pointer

Next we have our waitKey label. This is where we will check for key presses. To do that, we call the OSdequeue function. OSdequeue is an AMS function, so we pass its arguments on the stack.

Looking up the function in the TIGCC docs, we see that OSdequeue takes two arguments, a pointer to a short, and the keyboard queue address. We got the keyboard queue address from the call to kbd_queue. It is in the a3 register. Next we need a pointer to a short. This just means we want the address of a word to store the keycode in, if we find it. We can use our stack frame memory here. We'll use the bottom word of the frame pointer. Notice that we didn't have to do any stack math to find out where the memory was. This is the benefit of the frame pointer.

We have another new opcode now, the addq instruction. addq (ADD Quick) is for quick addition of values between 1 and 8. Since we pushed 8 bytes on the stack, this is a perfect instruction for restoring it.

    tst.w   %d0                     | if (%d0 != 0)
    bne     noKey                   | goto noKey

Here we have another new instruction, tst (TeST for equality with zero). This is equivalent to cmpi.w #0,%d0, but I think it's faster. Don't quote me though because I'm not well versed on instruction timings.

If you read the TIGCC docs on OSdequeue, you'll see it returns FALSE if the queue is empty. In case you weren't aware, FALSE is equivalent to 0. So, if we are not FALSE (i.e. we're TRUE), then we goto noKey.

    move.w  (%fp,-2),-(%sp)         | sprintf((%fp,-32), str_key, (%fp,-2));
    pea     str_key(%pc)
    pea     (%fp,-32)

    movea.l 4*sprintf(%a5),%a4
    jsr     (%a4)

This is pretty standard, but I wanted to note the string buffer, which is part of the stack frame. Remember we allocated 32 bytes, we used the bottom word (2 bytes) for the keycode. So the top 30 bytes will be used for our string buffer.

    unlk    %fp                     | deallocate stack frame
    movem.l (%sp)+,%d3/%a3-%a5      | restore registers
    rts                             | exit program

I'm skipping over some stuff. This is just before the noKey label. Stack order is very important, and we must maintain the proper order at all times. If we don't, you will soon see a calculator crash. Since we allocated the stack frame after saving the registers, we need to deallocate it first. This is done with the unlk instruction. Then we can restore our registers before exiting the program.

There is nothing new after the noKey label, so I'll leave you to read the rest of the code. It's pretty standard. Try it out and see what happens.

Step 3c - Conclusions

So, now we have the best of all possible keyboard input schemes, right? Wrong. You may or may not have noticed that we can't tell if the person pressed the 2nd key, or shift, or diamond. Those keys only work in combination with another key. It's also not the fastest possible way to read keys either. It may seem very fast now, but it may be necessary to get even more speed out of the calculator. This will be very important if you write games or intensive apps.

So, let's take a look at one more way of getting keyboard input.

 

Continue with Part III

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit