COURS205.TXT
****************************************************************** * * * 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