I've decided to do something I always wished I had done as a kid: learn low level programming on the Apple ][. So the first thing I had to do was obtain an Apple, specifically a ][+ which is the model I had as a kid. I managed to find one for sale with a disk drive and a serial card and a memory expansion, but he warned me the monitor might not work. He said all it showed was green squiggles. I held out some hope that he was plugging it into the 80 column card when it needed to be at 40 columns or vice versa, but no, I got it home and it's really dead. I'm kind of disappointed because it's a Zenith monitor and I used to have one just like it. But when the other Apple gets here, it will have a working monitor.
What I really want though is an original Apple "Monitor ///" like this one:
I've also bid on some old programming books for the Apple ][ but I haven't won any yet. I don't want to pay too much for those because there's a ton of information about the Apple ][ and the 6502 on the web. However most of the stuff on the web is organized as reference information or tips and it would be nice to have a book that provides how-to information.
I'm going to try and keep a journal of my discoveries. Some of these things you may be like "God damn how could he not know that?" or stuff that is easily found on the web. I probably learned it from the web. But I note it all here for sake of completeness.
CALL -151 takes you from Applesoft or Integer BASIC to the monitor ROM. 3D0G takes you back to BASIC from the ROM. In the monitor ROM, <addr> lists the byte at <addr>. <addr1>.<addr2> lists everything from <addr1> to <addr2>. <addr>L disassembles a chunk of memory starting at <addr> (but there's no guarantee you're disassembling something intended to be executable!) <addr>G starts executing at <addr>, that's how 3D0G for example works.
I remember 3D0G as a bit of voodoo without knowing how it worked - I just knew if you ever got "lost" and found yourself in the ROM prompt, 3D0G would bring you home. I don't remember ever knowing about CALL -151.
3D0G only works if DOS was loaded first. I assume this is because DOS loads the return-to-BASIC code, it's not part of the ROM. It might be interesting to compare the disassembled contents of 3D0 with and without DOS being loaded.
A VCR can interpret Apple ][ video output via its RCA video jack. This is probably old news for A/V guys but I did not know it was the same signalling.
The mini-assembler still works on the ][+ if you load Integer BASIC
from the System Master disk and then CALL -151. Then F666G will
launch the assembler prompt. I don't know the mini-assembler syntax
though. Oh I see. The only thing I was doing wrong was not specifying the
start address with the first opcode:
! 0300:LDA #$BE
Is HGR memory mapped or not? I tried poking some stuff into the pages reserved for HGR and nothing appeared on the screen. Unless I computed something wrong and I'm poking the wrong places. Is there a refresh that has to be called after the memory map changes?
I haven't tried poking values into the memory area for the text display. I'll try that tomorrow.
Ctrl-E from the monitor will dump the register contents.
<addr>S and <addr>T are the STEP and TRACE instructions which were reportedly removed from the ][+.
<slot>Ctrl-P redirects output to that slot number i.e. equivalent to PR# in BASIC. <slot>Ctrl-K redirects input.
FF69G to leave the mini-assembler. (Useful!)
Without loading DOS, the memory around 3D0 is all FF (empty.) When DOS
is loaded 3D0L gives:
03D0 - 4C BF 9D JMP $9DBF
03D3 - 4C 84 9D JMP $9D84
03D6 - 4C FD AA JMP $AAFD
According to Apple2History the last three bytes of page $03 are magic to the Autostart ROM on the ][+. The first two of those three bytes set the jump location where the PC should go when Reset is pressed. The third byte is like a checksum relative to the first two. If the third byte doesn't match don't do a JMP but rather do a cold reset. So it seems I should be able to trigger a cold reset by fiddling with those three bytes, but that's not happening. No matter what I do Reset just takes me back to the Applesoft prompt.
Poking ASCII values into the memory region for 40-column text display gives immediate gratification. i.e. the character appears on the screen.
I experimented with calling RWTS from assembly. If you set the registers A=01 X=00 Y=00 and then B052G, it will attempt to read track 00 sector 00. A=03 is a write and A=04 is a format, be careful with this stuff! This worked, but I'm not real sure where it's putting the data it reads. I have some doco that says: "During the first stage, 86 bytes of 2-encoded nibbles are stored in RWTSBUF2 from higher memory down ($BC55-$BC00). The second stage reads 256 bytes of six-encoded nibbles into RWTSBUF1 ($BB00-$BBFF)." I see changes happening at $BB00 but nothing changes at $BC55. I don't get what's a 2-encoded nibble vs. a 6-encoded nibble? If a sector is 256 bytes then what are those first 86 bytes? Then it says "the post-nibbled data are USUALLY stored in the DOS data sector buffer" but it doesn't say where that buffer is.
Interesting. I inserted a copy-protected game disk and tried to read track 00 sector 00 with B502G. That didn't work. I know that the disk is copy-protected, but we should at least be able to load the boot sector right? But RWTS doesn't load the boot sector. The controller ROM loads the boot sector. How does it do that?
I know these aren't great mysteries. I just haven't read the right stuff yet. There's a parcel of books coming up for auction on Ebay which includes the DOS Manual and Beneath Apple DOS. I must have those books. But I'm sure the bidding will be fierce.
I learned from reading that authors used to copy-protect their disks by installing their own I/O routines. These routines would do strange things like write the data on half- or quarter-track offsets. (Apple DOS only uses full track offsets, so this would "hide" the data from DOS.) Or they would interleave the data tracks with unformatted tracks. Cracker programs from that era would run down all these known tricks and try to undo them.
I suppose another way to crack a protected disk would be read the boot sector off the disk, disassemble it, and figure out what the hell it does next. I wonder how often that was done.
I guess the 6502 doesn't have an opcode to store an immediate value into memory? I see a lot of code using an LDA followed by an STA, and I assume that's to get an immediate byte (or the contents of a memory cell) into a particular memory cell. Which I guess is how every processor does it, even if the processor has a MOVE instruction, but if you wrap it up in a single opcode you can optimize it in the hardware. An LDA/STA combination will take 6 to 8 clock cycles per byte, and that's assuming you loaded an immediate value.
Haven't written much cause I haven't done much for a couple days. I have been finding lots of interesting stuff on the net about mixed-mode graphics and soft switch mappings and so on. I need to get out the Apple and try some of this stuff. But really I'm waiting for the Super Serial Card to arrive and that doesn't get here until Monday.
I did try the cc65 cross-compiler (cc65.org) and it really works! It provides a surprisingly large subset of the standard C library. The binary size is interesting. A fully functional (i.e. statically linked and ready to run) "Hello world" compiles to about 2.6K if you use printf(), or 513 bytes if you use write(). Sounds okay, but you can hand-code the same thing in assembly in 30 bytes. I don't think I'll be using cc65 a lot, that's not the point of this exercise, but it's really neat and it really works.
How can the 6502 be little-endian if it is binary compatible with the 6800? I know the Motorola family is big-endian. Maybe they are just "opcode compatible" meaning that you would have to reassemble your source (swapping the bytes in any static data) but you wouldn't have to rewrite anything.
The other Apple arrived today! Overall this one is in not quite as good shape. It's dirty and someone has put "cheat stickers" for some application on every single key. And the spacebar sticks. But it works. Between the two computers now I have a pretty good stockpile of parts in case anything should blow up. The other thing the new arrival has is a name-brand Apple disk controller and an Apple disk drive. Does that matter? You bet. I had a disk that wouldn't boot with the third-party controller and drive that were in the first computer. So I switched in the Apple Disk ][ controller and the Apple drive, and now the disk boots. I suspect it was due more to the controller than the drive. Why would it matter at all? Remember that almost everything with the Disk ][ is done in software and it's heavily dependent on timing. Also remember that a lot of professional applications used their own I/O routines to protect against crackers. Perhaps the timing on the third-party controller was a little outside the tolerance of the I/O routines on that particular disk.
The first computer will be the work computer and I have named it sneakers. The other, dirtier computer will be the spare. I named it gorgon.
gorgon also came with a joystick. I plugged it in and it works, but somehow the plastic housing connecting the stick itself to the potentiometers has been snapped off - the stick is completely broken away. I'm going to try Super Glue, or maybe epoxy, I just wonder if that will be strong enough to hold against all the torquing.
The Super Serial Card also arrived today, still in the factory shrinkwrap! It felt strange to break the seal, like I was sullying an antique or something. Even though "copyright 1981" is stamped on the PCB, the static bag said 1991 on it, so I figure that's when this particular card was packaged. Not quite as old as I thought. Inside the box was a certificate from Apple for a one-year warranty on the product. I wonder if they will still honor that?
The manual for the Super Serial Card includes the addresses for the firmware softswitches and the I/O routines, plus that stuff is well covered on the web. So programming the card shouldn't be too much of a mystery.
gorgon also arrived with a monitor, but it's not too great either. The screen is small even by the standard of days past and sometimes the image will waver. So I'm still in the market for a monitor, and maybe a joystick, but after that I'll be done chasing hardware.
This serial card turns out to be a hell of a lot of fun. I've been able to attach to either the Applesoft prompt or the ROM prompt from a PC. I've been able to use Keyspan's USB->serial adapter to attach the iBook to the ][+ in the same way. I've attached the ][+ to a PC and then ssh'd into the PC from the iBook and accessed the ][+ via minicom. It's crazy. Attaching the iBook to the Apple ][+ gave me no end of giggles. Something about connecting leading edge Apple to vintage Apple. Photos are coming.
A long time since the last entry. I've been pretty busy. But things have been happening with the Apple ][+ also. I finally secured a Monitor ///, like I was pining for above. Then just a few weeks ago I hooked up everything with the intention of getting some programming done, and the Monitor /// died... luckily it was just a problem with the power switch and it was easily fixed with some solder. It's just a little funny cause I haven't had to solder anything since about 1985, on my old Apple ][+.
Anyway after the monitor was fixed, I messed around with ADT, which is a really sweet program for transferring disks from an Apple to a file image on a Linux or Windows computer via serial line. Or vice versa, taking an image file and blasting it back to disk. I've been using it for the vice versa. This is important because if I want a usable development environment for doing assembly coding I'll need to get it from the web. I've chosen Merlin, which seems to be highly regarded by ex-Apple ][ programmers.
I think I've got a pretty good handle on how the Super Serial Card works. From a programmer's standpoint there's not much to it. There are firmware entry points for initializing the card, reading a byte of input from the other computer, and sending a byte of output to the other computer. The input/output byte is stored in the accumulator register.
If you redirect input to the SSC you can send it a Ctrl-A (or Ctrl-I, depending on the setting of the master jumper) and it opens a mini command line for overriding most of the dip switch settings, plus doing some things like terminal mode. There must be a way to also tickle those settings from assembly, but that's not documented in the official reference manual. Hrm... ADT can change the baud rate on the fly though, so ADT must be doing it. Examining the ADT source may explain how.
It's been more interesting to observe the SSC interacting with other computers in practice. For starters, the SSC has a master jumper block for setting the card to either "terminal" mode or "modem" mode. One effect of setting this jumper is to change the pinout of the connector, either straight-thru (in modem mode) or null (in terminal mode). Except, setting the jumper block also changes the meaning of the dip switches on the card. If you set the card for terminal mode it pretty much assumes you want to talk to a printer and some of the dip switches take on meanings like "line width". In my case, I want to connect two DTE devices (two computers) so I need a null pinout, but I also want the "modem" semantics of the dip switches. What I did was set the jumper block to modem and then put a null adapter on the serial line. This looks like it does what I want.
Also for some reason the Apple ][ sets the high bit on all its ASCII character values. So if I want to interact with the Apple ][+ via minicom I have to tell minicom to only use 7 data bits, which will strip that strange high bit from otherwise normal ASCII values. But if you put the SSC into "terminal" mode and then press ESCAPE to tell the SSC you want to do normal upper/lower case, the SSC will send regular 7-bit ASCII values to the remote host and translate the 7-bit values from the remote host into all 8-bit uppercase values that are normal to the Apple ][. Further, the Apple ][+ can display lower case if you really make it - by sticking a lowercase value in the accumulator and calling the COUT ROM routine. But even then it wants the high bit set - so lowercase "a" is 225 not 97, and so on. So the SSC could make the Apple faithfully recreate upper/lower case as received from the remote host, but it doesn't.
Er. Referring to "terminal mode" above is different from when I was talking about the terminal/modem jumper block earlier. If you hit Ctrl-A and then hit "T" you put the card into terminal mode, meaning that it bypasses BASIC and just sends what you type down the serial line. It's like putting the card into minicom/HyperTerminal mode. So you can type things like CALL -151 and it has no effect on the Apple.
I also tried putting the SSC into terminal mode and then running a getty on a Linux machine and logging into Linux from the Apple ][. This kind of worked but not really. The displayed output was pretty messed up. It upcased everything, and lots of characters were dropped. But I don't think this is a line protocol problem. I think it's a terminal emulation problem. i.e. the SSC doesn't know how to deal with some of the terminal escape codes coming from the Linux host. I had the getty set to do VT102 - there might be an even more primitive setting that would work better with the Apple ][. But what's really needed here is a terminal program for the Apple ][ that will properly "cook" the terminal codes coming from the Linux machine. There's probably something like that available for download, but I think that's something I could write for myself.
This is pretty cool.
In other apple news, I just poured out a jug of apple cider that was so old it started to smell like Woodchuck. That is all.
Soft switches for bank-switching the extra RAM in the language
card:
C088: Switch in LC bank 1, read-only
C08B: Switch in LC bank 1, read-write, but you have to hit this switch
twice in a row.
C08A: Switch out LC bank 1 and put the ROM back.
C080: Switch in LC bank 2, read-only
C083: Switch in LC bank 2, read-write. Must be hit twice.
C082: Switch out LC bank 2 and put the ROM back.
There are two other switches $C081 and $C089, that the Apple2 Programmer's FAQ says "Read ROM instead of RAM, two or more successive reads WR-enables RAM" but I'm not sure why you'd want to do that.
Where have I been? Where have you been?! I had work, real work, imposing on my fake work with the Apple ][+. I'm getting back into it now. I downloaded Merlin to disk via ADT. I haven't gotten into the fancy macro capabilities yet, but it's a pretty usable editor and assembler. The editor is line-oriented, of course, but it tries to make that as painless as possible.
Merlin offers several psuedo-opcodes, one of which is ASC. The
righthand side of an ASC opcode is a string of ASCII chars which are
inlined at the current address. The first character of the operand is
taken to be the delimiter - it is not included in the string and
everything else up to the closing delimiter is. There is no restriction on
what character can be the delimiter, except that it is the first character
of the operand. So you can have:
ASC "HEY"
or:
ASC 'HEY'
or even:
ASC !HEY!
You can also place arbitrary hex values after the string literal, like
this:
ASC "HEY",DC,00
This creates the null-terminated string
HEY\. (Since the Apple ][ can display backslashes but you
can't type them.)
However, you can't do this:
ASC DC,"HEY",00
I think because the assembler is assuming D is the delimiter. But you can
use the HEX pseudo-opcode to do the same thing:
HEX DC
ASC "HEY",00
creates the null-terminated string \HEY.
I'm also impressed, though, with how much a person could get done just with Wozniak's ROM monitor and Wozniak's mini-assembler, when they didn't have something like Merlin. In 1977 the Steves weren't selling "developer tools" except for what was in the ROM. The mini-assembler can't save source listings, but since the ROM monitor has a disassembler, the object code is the source listing. The mini-assembler can't insert a line of code in between existing lines, but the ROM monitor command line has block copy commands that can shift object code in memory without making you re-enter it. The only big thing the mini-assembler is missing is labels. Without labels you would have to keep track of all the jumps and base addresses in your code, and whenever you shifted some code those addresses would all change and you'd have to re-enter the affected lines of code.
In the Apple ][, all modes of the screen display are memory mapped. i.e. if you write a byte to a certain memory location, that byte will appear on the screen in a certain location, provided the Apple has been told via soft switches to display the appropriate mode.
The text display and the lores graphics display share the same memory map. That's why when you draw a bunch of stuff in lores and then do TEXT you get a screen of gibberish. The bytes that were blocks of color mean something totally different in ASCII. You don't get graphic gibberish when you do GR because part of the GR routine is to clear the memory map.
Hires mode has not just one but two seperate memory maps. Only one can be displayed at a time and they can quickly be toggled using a soft switch. The advantage to this is to give the programmer built-in double buffering of his hires display. Where it gets interesting, or painful, is that the memory maps for the hires pages are not laid out in a strictly linear fashion.
The first byte of the first hires page is at $2000. The first seven bits of the byte written at $2000 will effect the first seven pixels of the upper left corner of the hires screen. The lowest i.e. rightmost bit of the byte represents column zero i.e. the leftmost bit of the screen. Confusing yes, but not so much if you're used to little-endian architectures. The highest bit of the byte is the color bit. More about the color bit in a moment, for now just remember that the eighth bit of the byte is not plotted on the screen. So if you write $FF to $2000 you'll see 7 white pixels in a horizontal line at the upper left corner of the screen.
Now it starts to get good. There are 40 bytes from $2000 to $2027 that represent the first line of the hires screen (7 pixels per byte X 40 bytes = 280 pixels per line.) Intutitively the next line would start at $2028 but it doesn't. The next line starts at $2400. It just does. The second line stretches from $2400 to $2427 and then the third line starts at $2800. Each horizontal line steps by $0400 until you get to the ninth line. The pattern would tell you that the ninth line starts at $4000 but it doesn't. The ninth line goes back to $2080, the tenth line starts at $2480, the eleventh line at $2880 and so on. The 17th line starts at $2100 ($2080 + $0080) and the lines step by $0400 to the 25th line, and so on. The entirety of the first hires page covers $2000 to $3FFF and the second hires page uses the same staggered layout but goes from $4000 to $5FFF.
I don't know enough about electrical engineering to understand why, but what I've read is that this baroque layout of the memory map allowed Wozniak to reduce the amount of silicon on the board, and it was all about saving silicon back then. CPU cycles, and programmer happiness, were secondary to bringing down the cost of the board. It's hard to imagine now, but we have to suppose these contortions were worth it. One of the Apple ]['s main contemporaries, the TRS-80, only had one graphics page with no colors and a resolution of 128 x 48, and the Commodore PET 2001 didn't have any graphics modes at all.
The color scheme is just as crazy, if not more. Modern graphics hardware uses 2, 3, or even 4 bytes per pixel to specify the color of that one pixel. 4 bytes per pixel - luxurious! That would take about 420K to give 32-bit color to both hires pages of the Apple ][. Instead Wozniak decided to encode six colors in one bit per byte, sacrificing a measly 840 bytes to color info.
Encode six states in one bit? Number theory says that ain't happening. Well it can, if you cheat. First: black. Easy. If the bit isn't set, the pixel isn't lit. Second: white. Any two horizontally adjacent pixels that are both lit are always white, even if they're not in the same byte of the memory map. By these rules, we are free to set the color of a pixel whose neighbors are dark and we haven't actually used the color bit for anything yet. But we've still got four colors to encode and still only one bit. Smell the funk yet? Here's the next rule: a lit pixel in an even-numbered column will be magenta if the color bit is off, or blue if the color bit is on. A lit pixel in an odd-numbered column will be green if the color bit is off, or red if the color bit is on.
Roll that around for a minute.
So let's say we use a byte $55 and place that in the hires buffer, at $2080. This will draw a line spanning 7 pixels (the eighth bit is the color bit) and plotting 4 magenta pixels out of those 7. (Every other pixel, because pixels that touch are always white.) Now you want to expand your magenta line to span two bytes, or 14 pixels. So you put another $55 at $2081. WRONG! This will draw a GREEN line, and where the leading edge of one byte touches the trailing edge of the previous byte will be two white pixels. Why? Because the Apple ][ only plots seven of each eight bits (eighth bit == color bit!) and so the first pixel of $2080 is in column 0 (an even column), the last pixel of $2080 is in column 6 (still even...) and the first pixel of the neighboring byte $2081 is column 7 - an odd numbered column! Plotting $55 in $2081 will draw in odd numbered columns and change the color. You have to plot either $55 or his bit-shifted buddy $2A depending on where you are in the memory map. And remember the shift is only 7 bits wide!
Wooooo!
As you can imagine this makes it challenging to draw a sprite, give the sprite a desirable color scheme, and keep that color scheme intact as the sprite moves around on the display. I know of one game in particular, Space Eggs, that just doesn't worry about it. The colors of the objects shift mercilessly as they move about the screen.
Here are some pictures that I took about a year ago that I thought I had lost, but then I found them. I meant to take newer ones, since I don't use that crummy orange monitor anymore and I wanted to make the font bigger on the laptop. But here are these, since I never did take new ones.
I know it's hard to make out, but you can tell the terminal window has the
same contents as the Apple ][ screen. See the IN#2 at the top of the
screen.
The same thing, but in the Monitor instead of DOS.
There's the Keyspan adapter that makes it possible. I don't think I had
the null adapter on the line at this point.
Even though the Apple logo has changed colors over the years, I love it that it's still the exact same shape and size, down to the size of the bite. It's a shame they don't nestle the "A" of the Apple against the bite anymore.
I found this little program in the 1981 edition of The DOS Manual. The
description says: "If a file name contains control characters, you
won't see them printed, but they must be typed to use or delete the
file. The following Applesoft program can be used to find any hidden
characters except CTRL-M (RETURN), ESC, CTRL-H (left arrow) and CTRL-U
(right arrow). If you suspect you may have accidentally introduced a
control character into a file name, type this program, SAVE it, and RUN
it. The Applesoft prompt will be displayed. Next type CATALOG and you'll
get a list of all the files, with any control characters shown as flashing
characters. Control characters in program listings can also be found this
way. To re-instate normal printouts, type PR#0."
10 DATA 201,141,240,21,201,136
20 DATA 240,17,201,128,144,13
30 DATA 201,160,176,9,72,132
40 DATA 53,56,233,64,76,249
50 DATA 253,76,240,253
60 FOR I = 768 TO 768 + 27
70 READ V: POKE I,V: NEXT I
80 POKE 54,0: POKE 55,3
90 CALL 1002
Studying this program is educational for beginners to understand how to manipulate low-level I/O on the ][. How does it work? Why doesn't it work for Return, Escape, and the arrow keys? We're used to operating systems that act as referee/babysitter. The invasiveness of programs like the above seems almost obscene to modern eyes, but that's how you get things done with machines of limited power.