Wednesday, July 29, 2026

Apple II+ converting and sending binary data via Cassette IN

Apple II+ Hi-res

Nothing amazingly new here.  Just having fun messing with old tech. 

Hi-Res graphics 

The old Apple II+ has low-resolution and high-resolution graphics.  Eventually, the Apple //e would get "double hi-res".

High resolution for the Apple II+ is 240 pixels wide x 192 vertically.  You enter into it with the HGR command, and then a special software switch (POKE -16302,0) tickles things so that the bottom lines are shown as graphics instead of text. 

By default, the HGR command also clears the graphics RAM.  There was a command called HCOLOR to set up which color would be assigned, and HPLOT x,y would set up a bit (or pair of bits?) in memory to turn a pixel on.  The coordinate positioning was x=0 (left) to x=239 (right), y=0 (top) to y=191 (bottom).

There are two pages of RAM.  The first stretches from $2000 to $3fff.  The second page goes $4000 to $5fff.

The pixels on each line are not individually color-able, the way we see in recent tech.  Instead, the Apple used a bitwise mechanism, whereby bit 7 (0x80 or 128 decimal) determined which color set would be rendered (through clever means affecting the video out wave), and then bits 6..0 (0x40 64, 0x20 32, 16, ..., 0x01 1) were pixel on/off settings.  Combinations of those would yield different colors.

For my purposes, I just wanted to be able to take an image on a PC, and be able to send it to the Apple.

Cassette IN 

I'd previously figured out how to use the Cassette interface of the Apple to LOAD a BASIC program, or BLOAD a binary set of data.  

The code started with https://github.com/pyrex8/apple_ii_basic_2_wav. When it saves an example, tokenized BASIC program, there are two sections generated: program length and checksum in one, and then tokenized bytes and checksum in the other.

In each section there's a leading "tone", a 770Hz series of high and low values.  That is followed by data sent at 20 kHz.  It starts with a "short zero" sync bit (4 cycles low, 5 cycles high), and then each data byte's bits in most- to least-significant order.  Data bytes are sent with a 0 bit,  5 cycles low and 5 cycles high, or a 1 bit, 10 cycles low and 10 cycles high.  

Along the way, a checksum is computed starting with 0xff and then XOR'd with each emitted data byte.  That checksum is sent at the end of the waveform.

To read the program, I connected headphone out on my Linux box to Cassette IN on the Apple II+.  In Applesoft, I would just run the command LOAD, and then hit play in Audacity.  It took some experimentation to get the volume right, or else the program wouldn't load.

I modified the code to read a binary file and generate a binary loadable waveform.  Unlike the BASIC program, binary data is just one section, similar to the second section of a BASIC program.  To read it on the Apple II, I would enter the monitor (CALL -151) and then at the "*" prompt, enter startAddr.endAddrR (e.g., 2000.3FFFR) before hitting Play in Audacity.

Data conversion

To generate the memory representation for Hi-Res graphics, I took data from several pages, eventually ending up with the code below.

The X computation is fairly easy.  As an example, X values 0..6 are represented by bits 0..6 of a byte.  Similarly, bits 7..13 are bits 0..6 of the next byte in the line, and so on.  Bit 7, for my purposes, is always set to 0.  In short, given a starting address for a line of bytes, use x/7 to get the byte offset, and then x mod 7 to get the bit.

The Y computation is where it's tricky.  There are three "blocks" of memory for each of the 64 lines of graphics.  The starting set (of page 1) starts at $2000.  Rows 64..127 are at $2028.  Rows 128..191 are at $2050.

Then, within each block, you have eight 8-line chunks.  Each of those starts at an offset of 0x80 * multiplier.

So, line 0 is at $2000, line 8 starts at $2080, line 16 starts at $2100, and so on.

Similarly, line 64 is at $2028, line 72 starts at $20a8, line 80 starts at $2128, and so on.

Then, within each of those chunks, the y+1 offsets are a multiplier of 0x400.

Examples

0 $2000

1 $2400

8 $2080

9 $2480

64 $2028

65 $2428

etc.

In the end, it's a pretty simple computation with some divs, mods, bit shifting, etc.  I'm guessing a tighter code representation can be found by inspecting the HPLOT code itself.  Here's my python way of doing it.  The "expensive" part is the div 7 and mod 7 that happens in the mapx() code.  A pre-generated 240-wide lookup array could be used if I really cared, but today's machines are so fast that it doesn't really matter.

 

Given the mapxy function, it was pretty trivial to convert an image.  I used Adobe Photoshop Elements to convert/downscale an image to 240x192 pixels, remove its color, and save.  


 

Then, I used Python pillow (PIL) to load the image, iterate over all its pixels, and generated an in-memory representation as a bytearray.  I just threw it together as a bytearray of $4000 size, not worrying about the fact that I wouldn't use the first half of the array.  As I iterated over each x,y coordinate, I called mapxy to set up a byte address and bitmask.  Then, I'd logically OR the bitmask over the existing bytearray byte.

When completed, I saved the bytearray as a .bin file.

The resulting file can be inspected, sort of, with the "od" command.  The bitwise ordering isn't 100% representative of the visual representation, because of the 0x80 bit and LSB-to-MSB bit ordering, but at least the byte position can give a sense of where the black and white lines are.

 

From there, I could use the prior code to convert the .bin file to .wav form.  

Since "od" only provides hexadecimal and octal output, I wrote a quick "readbits.py" to see the binary representation of the memory, and only printed non-zero values.

Then, a comparison with the waveform could be done.  Here's an example.  The lead-up 770Hz tone (6 seconds) and "short zero" bit and other leading bytes are not shown.  In the lower window, you can see that at addresses $2039, $203a, ... we have bytearray values 0x38, 0x70, 0x7f, 0x7f, 0x0f.  Those are seen here in 0 and 1 bit waveform.


Given those, I could run * 2000.3FFFR on the Apple monitor, and Play the .wav file from Audacity to get the image loaded.

Here's what it looks like at cassette load speed:



 

In subsequent experiements, I started in DOS 3.3 and was able to BSAVE the binary value to a 5.25" floppy disk file.  Then, I could write something like this:

10 PRINT CHR$(4), "BLOAD FILENAME"

to execute the BLOAD command from within a BASIC program.

Here's what it looks like at 5.25" floppy BLOAD speed:


 


Thursday, July 23, 2026

Apple II+

I got an Apple II+ from an estate sale and am working to get it running.

Internals 

The internals looked all clean from initial inspection.  However, of the four peripheral cards inside, one was clearly broken, with part of it left laid down on the motherboard.

The broken card was an Epson printer board.  Part of it was still in one of the expansion port slots.  I was able to grab the "teeth" part by hand, and gently wiggle it out of the port slot.  With some careful positioning, I was able to put the parts back together using CA glue, though obviously that wouldn't fix it electronically.

A second board inside is a Video 7 card.  It had a few connection pins hanging off of it, and eventually went to something like a VGA port.  I couldn't find any documentation on it, so I've removed it.

A third board was a RAM expansion, typical of the day, taking me up from 48kB to 64 kB memory.  I kept that one plugged in.

The fourth board is an Apple Disk II board.  There were two legacy 5.25" floppy drives attached.  After some experimentation, I found that Drive 1 was working, but Drive 2 wasn't reliable.

Before turning on the system, I had to inspect and repair a few things.

First, I was hearing a rattling sound inside the main case.  I could already see that there was a paper clip magnetized to the speaker.  Overall, I was afraid something else down there might short out the board.  

I figured out how to fully open the case and get things apart.  That started with removing the obvious case screws on the bottom.  Then, I had to carefully loosen screws at the back of the motherboard that were holding on to a thin, metal "clip", which I am guessing was either a grounding or shielding mechanism.

After undoing the normal set of items within (PSU, keyboard,  speaker), I was able to get the case top off of the case bottom.

Removing the motherboard for inspection meant that I had to undo the plastic clip posts in the middle of the board.  I broke one of them.  They can be ordered online for an enormous shipping price, so I've left it as is.

Finally, after getting the motherboard off, I found an old 1980s quarter, a 1980s nickel, and another metal paperclip.

I also found a little, white plastic clip.  It turns out that that was part of the spacebar assembly.

Otherwise, the computer looked to be in really good, clean condition.  Only a small wipe-down was needed to clear out old dust.  This kind of computer does not have an internal battery, so there wasn't any chance of having that explode within.  And, none of the capacitors appeared to have failed.

I also took apart the PSU and inspected it, and it looked good.  However, I noticed that the PSU had a 110 vs 220 slider switch on the back, and it was in a 220 position when I got it.  I slid it back over to 110 for my purposes.

After doing the reassembly, I found an old TV with Composite In, hooked up the Video Out, and turned it on.  A Ctrl+Reset put me into Applesoft ("]" prompt) and I could type GR and then TEXT, and see a page of mostly "@" signs.

I tried various TVs of days gone by.  Most of them worked.  Some required an RF converter.  Only one TV didn't work -- an old security monitor CRT.  I won't to bother to fix that one.

The keyboard 

The next order of business was fixing the keyboard.  Upon receipt, the "-=" key was broken off its post, and the spacebar was janky.

For the "-=" key, I reopened the whole case, and de-soldered the ALPS switch within.  That allowed me a clean way to put the original key stem and the key switch into a vise, and get it glued together with CA glue.

For the spacebar, there wasn't much documentation on how to fix it.  In the end, I took the white post that I'd found within the case, and pushed it into the space key's left side keycap holder.  Then, I carefully pivoted the metal stabilizer bar (which was in its original position) such that the tips at each end went into the upper hole in the fin posts.  Then, after adding a spring to the middle keycap area, I pushed the spacebar back into place.  It's holding so far.

Finally, I added a spring to the RESET key at the top, right of the board, just to combat accidental keypresses.

I did a small amount of Q-tip cleaning of the board, and then put all the keycaps back where they started from, and all seemed to work.  There are occasionally double keypresses showing, suggesting incomplete debouncing in the software, but overall it works well.

 Video

 I tried various video options.  There is a composite Video Out jack on the back of the box.  I tried these configurations:

1. Video Out -> RF modulator -> TV In, channel 3 or 4.  Worked on Samsung UN50J5200AFXZA large TV.  3-prong power cable, typical 44W.

2. Same as above, worked on Magnavox 20MF200V/17.  12V, 5A max (60W).

3. Same as above to Spectra 58-MBWR.  Failed.  Bad, old TV CRT.

4. Video Out -> directly to Samsung's Composite In.  Worked.

5. Video Out -> Mini AV2HDMI composite In -> HDMI -> AOC 270LM00029.  Worked, but not 4:3 aspect ratio.  Monitor power is 19V, 1.58A.  Converter power is not specific but stated as "USB Power" on a USB Mini connector.  So we'll call that 5V, 2A (10w)?

For the above, it's fun to see it displaying color on an old CRT, but it's more convenient, uses way less energy, and generates less heat if I use a modern LED or LCD display with either a built-in or external upscaler.

Save and Load

I'm trying to experiment with saving programs through the Cassette Out and Cassette In interfaces.

The typical command to save a BASIC program is SAVE, and to load it back in from cassette, use LOAD.

You can also go into the monitor (CALL -151) and then use addr.addrW to write binary to the cassette.

For physical connections, both Cassette ports on the back of the Apple or RCA jacks.  I connected a F-F yellow RCA cable to the Apple's Cassette Out, and then connected the other end to a 3-way converter dongle (3.5mm M plug on one end; red, white, and yellow F RCA connectors on the other). 

I then plugged the 3.5mm plug into my Linux box (Dell Optiplex 9020) at its front port Mic In port.

I then installed Audacity to record data.  The Linux (Ubuntu) system automatically recognized the presence of a plug in the jack.  But, the software encouraged me to install or connect Alps audio drivers.  That was a mistake.  Doing that provided me a non-functioning Alps input device choice in Audacity.  Instead, it was better to choose or verify the original input device using Ubuntu's native Sound control panel, and let Audacity recognize the data in its "Default" input device setting. 

As an initial experiment, saved a chunk of RAM (in monitor, * F600.F800W) and started recording in Audacity.

After a few failed runs using the Mic In port, I went to the rear panel's Line In (blue) plug.  That produced results.  I could see the expected 770 Hz "Header" tone, and then small blip of other data.

The thing that wasn't clear at this point was whether I was getting the right volume.  Recorded at full volume, and played back using the built-in PC speaker, it would be very annoyingly loud.

 

The next trick