Jim Butterfield, Associate Editor
Same Game, Different Players
It's sometimes hard to recognize a simple, obvious fact: Machine language runs in the same machine as the other languages, such as BASIC. Thus, if you want to figure out how to do something in machine language, you need only figure out how it's done in BASIC.
Programmers often have a blind spot. They feel that once they abandon BASIC they must relearn all about their machine from the beginning. But it's the same machine, and most things work essentially the same way.
I sometimes have questions from programmers that almost baffle me, since I have trouble recognizing this blind spot. For example: "How do I set the background color to white on the 64?" Just POKE 53281,1. "No, I mean in machine language." Yes; just put value 1 into address 53281, whatever that works out to be in hexadecimal. "But that's BASIC—I want machine language." That's neither BASIC nor machine language—that's how the 64 sets color. I get the feeling that some programmers somehow see a barrier that isn't there.
Special characters seem to be a major obstacle. Users often view Commodore's "programmed cursor" as something special to BASIC. It's not; it's part of the operating system. Providing that the normal output path at subroutine $FFD2 is used, all the control characters work as they would in BASIC. Want to clear the screen? Do a LDA #$93 : JSR $FFD2. Want to print the next characters in black? Code LDA #$90 : JSR $FFD2 and then go ahead and print. Want to home the cursor, print in reverse font, switch to text mode, or whatever? Use the same special characters as for BASIC.
Tables of these special characters have been printed on numerous occasions, and I could include one here, but I'd rather give you a special procedure to let the computer tell you the character to use. For most keyboard-generated characters, this will work splendidly.
In BASIC, choose the programmable key you want and type the following partial line:
Don't press RETURN yet. Now, touch the key you're interested in; use SHIFT or CTRL if appropriate. The key's graphic representation will appear in reverse video directly behind the quotation mark. Complete the line by pressing the quotes again and closing the parentheses, giving:
PRINT ASC(" … ")
Now press RETURN. You'll be given the value of that key. Use the hexadecimal equivalent in your machine language program: It will do the same thing.
Using this technique, you'll discover that the code to turn all printed output to blue is decimal 31, hex $1F; to home the cursor, decimal 19, hex $13, and so on. A couple of codes that you can't discover this way include Return (you should know this one) as decimal 13, hex $0D; Delete (rarely needed) as decimal 20, hex $14; Set text mode as decimal 14, hex $0E; and Set graphics mode as decimal 142, hex $8E.
The above character-finding technique also works on the function keys of the VIC-20 and Commodore 64. You won't usually want to print these, of course, but it's often useful to detect these keys after reading the keyboard with subroutine GETIN at $FFE4. The function keys can give you very user-friendly programs.
The same sort of question crops up for outputting to devices. Users ask, "How do I make my printer do certain lines in text mode?" When questioned as to how they do it in BASIC, the reply is something like, "Easy: I just prefix each new line with a cursor-down character." Fine. The same character exists in machine language (decimal 17, hex $11). Send it at the right time and the printer will do the appropriate thing.
It seems odd having to explain that peripheral devices don't even know what languages are sending data to it; when the right characters are delivered, the appropriate thing happens. But many users have a mental block. Somehow, machine language is suspected of making all the mechanical parts work in a different manner. 'Tain't so. It's the same machine and the same system.
Disk systems are especially tricky in some users' minds. Although it seems natural to them to open a data channel for writing using a name such as 0:DFILE,S,W in BASIC, they come unglued when it's time to do the same job in machine language. They have the name DFILE but somehow can't cope with the idea of tacking on a, S, W behind it before opening the file.
The same mental gap occurs when it's time to scratch a file. In BASIC, users know that all they have to do is to open the command channel (secondary address 15), and then send "S0-.FILENAME" to this command channel in order to scratch the file. It works the same way in machine language, of course.
Yet it sometimes seems that all we need to do is pick up a book on machine language and all the knowledge we have learned about the machine fades away into the distance.
Sometimes, the confusion is understandable because of the way BASIC sends values. If BASIC outputs a value K (with a statement such as PRINT# … K) it breaks the value into separate digits. In other words, if K is 13, BASIC will send a space, a numeric 1 character, and a numeric 3 character. A machine language programmer with a value of 13 to send might just load it into the A register and send it. But that's not a value—that's just a carriage return character. We must convert the value to decimal, and then the characters to ASCII, before sending.
On the other hand, if BASIC sends a character with CHR$(..), such as is done with the M-R and M-W commands, machine language can send the value directly.
So how would we initiate a block read in machine language? First, examine how it would be done in BASIC. To do a direct block read, we must open the command channel and open a data channel. Let's assume that we have done this using OPEN 15,8,15 and OPEN 1,8,2,"#".
When this has been done, we finally give the command for the block read with:
PRINT#15, "U1 : 2, O," ;25, 14
This would read drive 0, track 25, sector 14. The value of 2, by the way, is the secondary address of the data file. Command Ul, by the way, is preferable to its equivalent B-R for doing a block read.
In machine language, we would open the print path with LDX #15 and JSR $FFC9. We would then send the U, followed by 1, then the colon, the two, the comma, the zero, and the secnd comma.
Now comes the part where we need to be careful—not tricky, just careful. The track number, in this case 25, must be broken into two digits, the two and the five. That's not hard: Such a simple division could be accomplished by repeatedly comparing the value to 10, subtracting if necessary, and counting how many times we subtract. Two subtractions leave five: We send space, ASCII two, ASCII five. Now we do the same thing with the sector value and we're done. To keep precisely to the BASIC syntax, we'd also send a Return before disconnecting from the print path.
Yes, it does work like BASIC. Yes, I'd work out a logic flow in BASIC before diving directly into machine language. But ultimately, I'd feel quite secure: If it works in BASIC, it must also work in machine language.
Once you get a wholesome feeling for your machine, the language you use becomes less significant. After all, a language—any language—is just a tool to help you get the job done.