COURS205.TXT

From Atari Wiki
Jump to navigation Jump to search

******************************************************************
*                                                                *
*             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
   addresses of routines in A1. We take a word of code.
   Is it $FFFF? If so, it means that we are at the end of the
   list, so we escape since the chosen key is not
   valid. Otherwise, we take the address from the table of addresses
   of routines. Is the code from the table the same as that of the
   key? No, we loop (note here the dot before the label. This
   indicates that the label is local. The assembler will relate it to
   the nearest label with the same name. Thus, it is possible to have
   several labels .HERE in a program, or any other name
   provided it is preceded by a dot. In the case of small
   loops, for example, this avoids the need to search for convoluted label names!!!).

   Since the code is identical to that of the key, we jump to
   the routine, and upon return, we start over.

   The advantages of this method are multiple. First, the
   small size of the test routine: if it had been necessary to perform
   tests like:

    cmp.w #$1519,d7
    bne.s .here1
    bsr thing
    bra start
   .here1 cmp.w #$1615,d7
    bne.s .here2
    bsr thing2
    bra start
   .here2
   etc.... the size would have been greater, especially since in the example there are only 4 codes... Imagine with thirty!!! The other
   advantage concerns the debugging of the program. Indeed, nothing
   prevents planning many routines but simply putting
   the address of an RTS, and gradually building these routines.
   Nonetheless, the program will still work. Similarly, the
   system of the end flag ($FFFF) allows very easily
   adding codes and therefore routines.

   That's it for lists and tables!! The applications are innumerable
   and often allow in a few lines of code to perform tests, searches, calculations, conversions, or branches
   that could be done otherwise but with immense difficulty!

   Here are some ideas for applications:

   text encoding (using ASCII code to point to a
   table giving the value to substitute)

   animation (sequentially traversed table giving addresses
   of images to display)

   key management

   mouse management (table with screen zone coordinates)
   drop-down menu

   etc...
   

Back to ASM_Tutorial