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.