******************************************************************
* *
* 68000 ASSEMBLER COURSE ON ATARI ST *
* *
* by The Fierce Rabbit (from 44E) *
* *
* Second series *
* *
* Lesson number 5 *
******************************************************************
After this course on macros, we will move on to the use of
arrays in assembler. I hope that after each course you
conduct your own research and exercises, and that you
don't just rush straight onto the next lesson. Even
if the courses now seem short compared to those
from the first series, they nonetheless lift the veil on many
topics. You now have the level to deepen them and I hope you do!
ARRAYS
The use of arrays is not very common in assembler among
beginner programmers. Indeed, the system may
seem quite perilous to handle but proves to be very
powerful and very convenient!
Just like the principle of the stack, that of arrays is simple
but requires a lot of logic to function
properly. Let's start with a simple example: we
will press the F1 to F10 function keys and
each time we will display a letter. Let's start with an
example without an array.
See Listing number 2.
Initially INCLUDE the program start-up routine studied
at the beginning of the second series. Introduction with a small message,
and then waiting for a key press. As we want to test
the function keys and since they do not have an ASCII code,
we take advantage of the fact that Gemdos 7 function returns
in the low order of D0 the ASCII code but also in the high order the scan code. There is more than just that though, because
if you consult the ST bible or simply the description of the
functions in the last pages of the GFA 3.00 doc, you learn
that Gemdos 7 returns in bits 0-7 the ASCII code,
16-23 the keyboard code and 24-31 the keyboard toggle keys state.
Quick reminder about ASCII codes and scan codes. ASCII
codes (American Standard Code for Information Interchange) are
7-bit codes that follow the alphabetical order. This
standard is also known as International Telegraph Code
number 5. The 8th bit is a parity bit, that is to say it only
serves to verify the correct transmission of the code. Since
this system is American, the ASCII encoding does not
take into account accents or characters like the c
cedilla. However, our computer does not need this
eighth bit to check transmissions, so it is
used to increase the number of possible combinations. Returning
to the course 2 of series 1, on magic numbers: with 7 bits
we can count from 0 to 127, one more bit allows us to
count from 0 to 255. Consult a chart of ASCII codes (bible,
developer's book, GFA doc, etc...) and you will realize
that all 'strange' signs are coded on 8 bits and therefore have
codes higher than 127.
However, ASCII codes are not sufficient. Imagine that I press
the key A on my keyboard. In ASCII code I will receive
65. But if an Englishman presses the same key, for him it will be
a Q and he will receive the ASCII code 81. The ASCII code therefore
does not depend on the key, mechanically speaking, but rather on the letter
of this key. There is another coding that corresponds to keys
from a mechanical point of view. This is called the
scan-code.
In terms of the scan-code, a key has a number, totally
independent of the letter that will be assigned to this key. Thus
the key A on your keyboard returns the scan-code $10, and the same
goes for all STs in the world, whether this key is
marked "A", or "Q". It's the placement on the keyboard that
counts.
We therefore test the scan-code of the key we
pressed. If it's Escape, we exit the program.
Otherwise, we will test if it's a function key. The key F1 has a scan-code $3B, F2->$3C, F3->$3D
etc... up to F10->$44. Since scan-codes are consecutive, we therefore test
our result relative to the scan-code of F1, if it's
lower it's therefore not valid, likewise relative to the
scan-code of F10: if it's higher it's not valid either.
These tests being done, we are therefore in the presence of a scan-code
located between $3B and $44, these 2 numbers included. $3B, that's 59
in decimal. To get 65 (ASCII code of A) just add
6. That's what we do next. We thus obtain the ASCII
code of A, B, C etc... depending on the function key that was
pressed. Only the letter remains to be displayed!
Let's imagine now that we want to display "key F1"
when we press this key, "key F2", etc....
Several solutions come to mind. Here is the first one that
comes to mind, and I will just unveil it because it's not
related to arrays. In order not to complicate, we
will not display "key F10", in fact we will only take into account
F1-F9 keys. Remember one of the listings from
series 1. The one that displayed a sentence by making the letters appear
like on the displays of train stations or airports. Take up that listing again (it was
number 3) and remember what happened to the sentence
located at the address TXT. We displayed this sentence
but beforehand it was modified at the column address and the
letter address.
In the present case it is enough to do about the same thing.
Prepare a sentence like this:
TXT DC.B "KEY F"
NUMBER DC.B " ",13,10,0
For each press on a function key, we subtract 10 (in
decimal) from the scan-code, and we put the result at the
address NUMBER. Thus the scan-code of the key F1 ($3B thus 59 in
decimal) will become 49 which is the ASCII code of the letter '1'.
So we will see displayed 'KEY F1'.
Execute this program before continuing, it will make an
excellent exercise!!!!
Let's now move to the array, modifying the display slightly.
A press on: will display:
F1 A
F2 Z
F2 E
F4 R
F5 T
F6 Y
F7 U
F8 I
F9 O
F10 P
First observation: if the scan-codes of the keys still follow each other,
it can be said that the logical link between the displayed letters is a bit weak...
Take listing number 3, and let's begin to study it. The beginning,
up to the comment 'the key is valid', is
strictly identical to the previous listing. Then we begin
the part using the array. The address of this array is passed
in A0 (Load Effective Address), then $3B is subtracted from D0 so
that it has a value from 0 to 9. The array we
use is composed of Words. However, a word is 2 bytes,
and the memory is composed of bytes. To move within an
array in words when our unit is the byte, we must therefore
move by 2's. Our 'counter', which
is here D0, shouldn't then be taking a value such as 0,1,2,3,4 etc...
but rather a value like 0,2,4,6,8...
Since in the course of our operations we have D0 with a value
of the type 0,1,2,3,4... we now need to multiply it by 2. This
is done by the MULU operation which is read Multiply Unsigned. Indeed
it is an unsigned multiplication, it does not
take into account the sign of what is multiplied,
unlike the MULS operation (Multiply signed).
Now, let's closely observe this instruction:
MOVE.W 0(A0,D0.W),D0
It is a MOVE so it's a transfer operation. It takes place over a word
since we have .W This MOVE will take the D0th word from A0
to put it in D1. So, if we press F3, we
get $3D. We subtract $3B and we get 2, we
multiply by 2, and so D0 now equals 4. We will therefore
point to the 4th byte of A0 and take a word starting from
that place. The transfer is indeed always counted with a
number of bytes, whether the table is in bytes, words, or longs.
It's a bit like if you were moving down a street with small
houses, medium or large ones, the transfer will always be measured in meters.
But what does the 0 mean in front of the parenthesis? Well, it’s
the value of a fixed shift to add. Let's take an example: We
have an array in which we 'tap' according to a number that
is provided to us by a key press. Only we need to
take different things depending on whether the key is pressed while
SHIFT is pressed down. It is then possible to say: if
shift is not pressed then it will be the first elements of the
array that will be taken into account, but with shift it will be the
elements at the end. We can then do:
MOVE.W 0(A0,D0.W),D1 or if shift is pressed,
MOVE.W 18(A0,D0.W),D1. This amounts to taking the D0th word
from A0, starting to count 18 bytes from the beginning of A0.
Nevertheless, we must be careful with several things concerning arrays. First of all, paying close attention to the type of data in the
array to properly modify the 'counter' accordingly.
Also, be very careful that the first element is element 0 and not 1. We had already seen in the very
first courses of series 1 the problems that can arise
when counting, sometimes forgetting the 0. This problem is
all the more annoying with arrays because, if instead of subtracting $3B in my example to get a number from 0 to 9, I had only
subtracted $3A and thus obtained 1 to 10, my program would have
worked perfectly. It would have simply displayed anything after a press on F10. However, if you have an array of
200 elements that you call with the keys, key+shifts, +control etc... the verification key by key could
be left aside... In our example, we used words in our table. It would have been perfectly possible to use bytes.
Modify the program a bit: delete the line with MULU, and
modify the datas. Instead of putting DC.W at the address TABLE,
put DC.B. Finally, since our array is now
in bytes and not in words, the addressing
allowing to pick from it must be modified. Instead of MOVE.W 0(A0,D0.W),D1 it
is now necessary to put MOVE.B 0(A0,D0.W),D1
However we must be careful because we have talked about the impossibility of using odd addresses. However, in this last case,
as our table is in bytes, if D0 is worth 3, we find
ourselves with an odd address, and yet it works! Indeed
it works because we take a byte. In fact, the 68000
can perfectly take a byte at an odd address,
however, what it cannot do is take a larger piece of data (word or long) that starts on an odd address and
therefore spans 'normal' places. Let's modify the program once
more. Put the table in word mode, and return to word
addressing (MOVE.W 0(A0,D0.W),D1). So, the mistake will come
from the fact that we forgot MULU, and therefore our
counter will sometimes be odd while our table and our
addressing mode requires an even counter.
Assemble and launch. Pressing F1: everything goes well! Normal,
D0 after subtracting $3B, is valued at 0 which is therefore even. Press
F3: same thing because D0 is worth 2. However, pressing F2 results
in 3 bombs and a return to DEVPACK. Let's debug our program: alternate+D, and scroll down to the line:
MOVE.W 0(A0,D0.W),D1
Place this line at the top of window 1 and press control+B
A breakpoint is set there. Launch with control+R, and press
the F2 key. Breakpoint, here we are under MONST. Looking at the
value of A0 we know the address of our table, which is
an even address. However, if you pressed F2,
you should have 1 as the value of D0, therefore an odd value.
Move a step over MOVE.W 0(A0,D0.W),D1 using Control+Z.
Address error! You just need to exit with Control+C.
Okay, we've seen how to take a word or a byte from an
array. With a little intelligence you should be able to take a long word (instead of doing a MULU by 2
you do one by 4). Let's take a step back and remember
from the previous courses: we studied the principle of this
'tube', of that memory which we are beginning to use
abundantly. If you have a bit of memory actually, you should
remember a remark made at the very beginning, stating that it was necessary
to be careful not to confuse the contents of the tube with the address of
this content. Indeed, it is completely possible to have
IMAGE incbin "A:\HOUSE.PI1"
PTN_IMAGE DC.L IMAGE
At the address IMAGE, we find in the tube the image itself,
but at the address PTN_IMAGE, we find a long word, which turns
out to be the image's address. With a little imagination, we
can therefore imagine an array composed of long words, these long
words being addresses of images, texts, but also (why
not!) routines!!!!!!
Here is the skeleton of a program performing such a thing: In the beginning, same as before, waiting for key press,
verifying the validity of the key, we manipulate to have
a code like 0,1,2,3,4... then we MULU it by 4 as our
table will consist of long words.
LEA TABLE,A0
MOVE.L 0(A0,D0.W),A0
JSR (A0)
BRA START and we start over
We perform a JSR (jump subroutine) instead of a BSR. Why?
try, and look at the appendix on instructions to see the
differences between the two!!!
But what is our table made of? Well, for example
TABLE DC.L EVERYTHING_GREEN
DC.L ALL_BLUE
DC.L QUIT
DC.L DRING
DC.L HELLO
etc....
All these entries being the addresses of the routines. For example
HELLO move.l #message,-(sp)
move.w #9,-(sp)
trap #1
addq.l #6,sp
rts
The EVERYTHING_GREEN routine sets the entire palette to green etc....
It is likewise possible to put in an array the addresses of
phrases and pass the "picked" address to a routine that displays
with gemdos(9), for example.
One last thing, which is closer to the system of the list than to that of the array, but which is also very useful. We studied here
possibilities always stemming from the same evidence:
the data that we use to point in the array, follow each other! Unfortunately, in many cases, they do not...
Here is another method: Imagine the case of a text editor, with several possible actions (erase the text, save
the text, print it, load, overwrite, scroll etc...) called
by combinations of keys. To be in line with the Wordstart
norm (it is the keyboard norm used by Devack: ALT+W=print for
example), I first collected with a very small program the
codes returned by the key combinations Then I made
a list of these codes, a list in words because in the case of key combinations (it is possible to build the combination pressed key/-
control key).
TAB_CODE dc.w $1519,$1615,$1312,$2e03,$FFFF
Then I made a list with the addresses of my routines.
As I didn't have any done at the start, I made a
'fake' one, called JRTS and that does.... only RTS!
TAB_ROUTINE dc.l JRTS,JRTS,JRTS,JRTS
Then I looped to read TAB_CODE, comparing, every
time, the value found in the table with the one from the
key. At the same time I walk through TAB_ROUTINE so that when
I read the 3rd element of TAB_CODE, I am in front of the 3rd element
of TAB_ROUTINE.
Here is the module. D7 contains the word corresponding to the key or
the key combination.
LEA TAB_CODE,A0
LEA TAB_ROUTINE,A1
.HERE MOVE.W (A0)+,D0
CMP.W #$FFFF,D0
BEQ START
MOVE.L (A1)+,A2
CMP.W D0,D7
BNE .HERE
JSR (A2)
BRA START
The address of the list of codes is put in A0 and that of the
routine addresses in A1. We take a