Apple I Tape Interface

14 replies [Last post]
Offline
Joined: Mar 9 2005
Posts: 79

I'm reasearching the Apple I tape interface. The function of the LS02, LS10, and LM311 is obvious. Are the 6301-IJ, 7623L chips ROM, or are they 6301 microcontrollers? I know the wonder book contains notes on the tape interface...somewhere, so I will begin the 230 page search.

Anytime now a bunch of really cool Apple I documets will get added to the archive. Smile

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Offline
Joined: Mar 9 2005
Posts: 79
http://coinop.org/kb_dl.aspx/

http://coinop.org/kb_dl.aspx/kb/gametech/rom-ram_reference.html

the 6301-1 is a 256x4 memory. I don't suppose Apple ever released schematics or assembler listings for the casette card?

From the pictures I'm going to map what I can, and try to fill in the blanks based on what "should" be there.

It was easy to rule out a 6301 microcontroller, considering it has 40 pins. Wink

Offline
Joined: Mar 9 2005
Posts: 79
Questions:

Questions:

There is a schematic of a casette interface on page 64 of the
Wonderbook. The schematic appears to be preliminary to the Apple I.

I need someone with an Apple I card to identify the 4 resistors under
the capacitor. I would also like an ID on the two transistors (they
are boxed in white). Any markings will help. Please also look at the
trace that wraps around the transistors from the red diode. Does it
connect to the collector of both transistors? (I'm assuming at least
the bottom is NPN due to the connections)

Anyone with any documentation on the casette interface would help.
The Wonderbook is not literally very clear in this area. I have all
the schematic drawn but the address decoding logic. I'm almost done
prooving it out and will post it sunday night. My goal is to have
gerber files ready sunday evening to get two of these casette boards
made. Verification of resistor values is not critical for this, but knowing the transistor situation is. A picture from a lower left angle of the board would help me.

NOTE: the traces seen over the chips are what is on the bottom of the board.

Any questions or comments? Its 3:30AM in Alaska. The east coast is waking up... Wink

Thanks for the help. Wink

Offline
Joined: Dec 20 2003
Posts: 249
Reduce the size of that picture

Otherwise it will take dialup people forever to see it. Instead of image use thumb. Here is the schematic for the apple 1 cassette:

Apple 1 cassette interface schematics

Hope this helps,

Vince

__________________

replica 1 The Apple 1 replica and the new Micro-KIM!
brielcomputers.com

Offline
Joined: Mar 9 2005
Posts: 79
Yes, that helps. I wish I'd

Yes, that helps. I wish I'd found that earlier! Wink Amazingly my schematic matches that one exactly except for the component values. Its fairly easy to rule out which traces pass under the chips based on previous traces.

However, in the note section next to the chip select on pin 21 it says to jumper R to C. pin 21 on the interface is chip select T according to the apple manual. Did I miss something? I can't edit the post to change the picture size.

To a degree that is reasonable I'm making the card clone look exactly like the one pictured. Smile

Offline
Joined: Dec 20 2003
Posts: 249
Yes, R (pin 21) to C (74154 p

Yes, R (expansion pin 21) to C (74154 pin 14) jumper would be the address decoder for $C000. In order for the apple 1 to use the cassette interface the address decoder The apple 1 gave hard-wired memory decoding options. Always fun to try and reverse engineer a board but good job. If you look at the schematic of the apple 1 at IC B9 the 74154 /CSC is the C Woz is referring to. Looking closer at the 74154 you can see every output is for 4K blocks for the ability to actually address upto the full 64K of memory range. The 44pin card only had enough room for 3 memory selects so Woz left the choice up to the user but for the cassette, R needs to be jumpered to C so that the cassette interface code is at C100 (because A4 is selected on the card). Sorry, I'm going on, hope this helps too.

Vince

__________________

replica 1 The Apple 1 replica and the new Micro-KIM!
brielcomputers.com

Offline
Joined: Mar 15 2004
Posts: 17
ROM disasm

The 256 bytes of cassette interface ROM contain console input and evaluation of parameters (about 130 bytes, the code is very clear and some routines are similar to the monitor ROM, or same only using differet registers (X instead of Y or so), ECHO subroutine from monitor ROM is used) and tape I/O itself (cca 100 bytes) - code is not so clear here, with many nested loops and entry points to subroutines.
As soon as I will found it, I will post the tape interface source code here (if nobody will be faster than me).

__________________

Jiri T. Dolezal, M.D.
Prague, Czech Republic, Europe

Offline
Joined: Mar 9 2005
Posts: 79
Go ahead and post your findin

Go ahead and post your findings. Knowing the locations in memory of the cassette hardware would help figuring out those loops. I bet they are for encoding and decoding the data. The Apple 1 cassette adaptor is similar to the Disk II and IWM, it requires very specific processor timing to read the data.

All right, I made my images much smaller this time. Smile

I blured the top of this board. Tell me what it does. The surface mount chip on this boad has 4k of ram. Smile

Here is the board I drew up last weekend. I placed the components within +-.050 of the origional board. The traces, an autorouter took care of that. I am not TOO crazy. Wink

Here is the real one in case you forgot.

And the real one with mine on top of it. (to see how close the parts are) Even the labeling is "close". Wink I didn't spend more than 10 seconds on this, so its not very well alligned.

My last attempt to slow down the internet:

Have fun. I'll upload the gerber files once it's been tested. I don't want to be responsible for people wasing their money on an untested board. Wink

Offline
Joined: Apr 10 2005
Posts: 5
First I would like to say HI!

First I would like to say HI! I'm new around here. I will soon be
building a Replica 1.

Has anyone produced a working cassette interface replica?

iceandfire's picture
Offline
Joined: Dec 20 2003
Posts: 67
Welcome!

Welcome to the forum, rasterscan, and to the world of the Apple-I. Up to now, no one has announced that they have a working cassette interface for the Replica-I. It seemed easy for Woz in the 70's but the duplication of his efforts seems almost imposible. Here's hoping all those out there with the skills needed will have lots of luck with this projsct soon.

Larry

__________________

Larry Nelson

Offline
Joined: Mar 15 2004
Posts: 17
Go ahead and post your findings.

>> Go ahead and post your findings. Knowing the locations in memory of the cassette hardware would help figuring out those loops.
>> I bet they are for encoding and decoding the data.

You are right, of course.
Here is it (use non-proportional font):

</p>
<p>Apple 1 Tape ROM disassembly</p>
<p>written by WOZ, 1976<br />
disasm by dex, 2005</p>
<p>FROM:STL<br />
     STH<br />
TO:  XAML<br />
     XAMH</p>
<p>C100 RESET_T     LDA #AA   '*'<br />
C102             JSR FFEF  ECHO<br />
C105             LDA #8D   'CR'<br />
C107             JSR FFEF  ECHO<br />
C10A             LDY #FF<br />
C10C NOCR        INY<br />
C10D NEXTCHAR_T  LDA D011  KBD_CR<br />
C110             BPL C10D  NEXTCHAR_T<br />
C112             LDA D010  KBD<br />
C115             STA 200,Y IN<br />
C118             JSR FFEF  ECHO<br />
C11B             CMP #9B   'ESC'<br />
C11D             BEQ C100  RESET_T<br />
C11F             CMP #88   'CR'<br />
C121             BNE C10C  NOCR<br />
C123             LDX #FF<br />
C125 NXBLOCK     LDA #00<br />
C127             STA 24    XAML<br />
C129             STA 25    XAMH<br />
C12B             STA 26    STL<br />
C12D             STA 27    STH<br />
C12F NEXTHEX_T   INX<br />
C130 NEXTITEM_T  LDA 200,X IN<br />
C133             CMP #D7   'R'<br />
C135             BEQ C18D  READ<br />
C137             CMP #D7   'W'<br />
C139             BEQ C170  WRITE<br />
C13B             CMP #AE   '.'<br />
C12D             BEQ C166  STOREHEX<br />
C137             CMP #8D   'CR'<br />
C141             BEQ C163  ESCAPE_T<br />
C143             CMP #A0<br />
C145             BEQ C12F  NEXTHEX_T<br />
C147 HEX         EOR #B0<br />
C149             CMP #0A<br />
C14B             BCC C153  DIG_T<br />
C14D             ADC #88<br />
C14F             CMP #FA<br />
C151             BCC C100  RESET_T<br />
C153 DIG_T       ASL A<br />
C154             ASL A<br />
C155             ASL A<br />
C156             ASL A<br />
C157             LDY #04<br />
C159 HEXSHIFT_T  ASL A<br />
C15A             ROL 24    XAML<br />
C15C             ROL 25    XAMH<br />
C15E             DEY<br />
C15F             BNE C159  HEXSHIFT_T<br />
C161             BEQ C12F  NEXTHEX_T<br />
------------------------------------<br />
C163 ESCAPE_T    JMP FF1A  ESCAPE<br />
---------------------------------<br />
C166 STOREHEX    LDA 24    XAML<br />
C168             STA 26    STL<br />
C16A             LDA 25    XAMH<br />
C16C             STA 27    STH<br />
C16E             BCS C12F  NEXTHEX_T<br />
------------------------------------</p>
<p>C170 WRITE       LDA #40<br />
C172 WLOOP1      JSR C1CC  ENTRY1<br />
C175             DEY<br />
C176             LDX #00<br />
C178             LDA (26),X STL<br />
C17A             LDX #10<br />
C17C WLOOP2      ASL A<br />
C17D             JSR C1DB  WAIT1<br />
C180             BNE C17C  WLOOP2<br />
C182             JSR C1F1  TEST_END<br />
C185             LDY #1E<br />
C187             BCC C175  WLOOP1<br />
C189 LOOP1       LDX 28<br />
C18B             BCS C125  NXBLOCK</p>
<p>C18D READ        JSR C1BC  ENTRY2<br />
C190             LDA #16<br />
C192             JSR C1CC  ENTRY1<br />
C195             JSR C1BC  ENTRY2<br />
C198 RLOOP1      LDY #1F<br />
C19A             JSR C1BF  READPORT<br />
C19D             BCS C198  RLOOP1<br />
C19F             JSR C1BF  READPORT<br />
C1A2             LDY #3A<br />
C1A4 RLOOP2      LDX #08<br />
C1A6 RLOOP3      PHA<br />
C1A7             JSR C1BC  ENTRY2<br />
C1AA             PLA<br />
C1AB             ROL A<br />
C1AC             LDY #39<br />
C1AE             DEX<br />
C1AF             BNE C1A6  RLOOP3<br />
C1B1             STA (26),X STL<br />
C1B3             JSR C1F1  TEST_END<br />
C1B6             LDY #35<br />
C1B8             BCC C1A4  RLOOP2<br />
C1BA             BCS C189  LOOP1<br />
--------------------------------<br />
C1BC ENTRY2      JSR C1BF  READPORT<br />
C1BF READPORT    DEY<br />
C1C0             LDA CO81  PORT<br />
C1C3             CMP 29    H<br />
C1C5             BEQ C1BF  READPORT<br />
C1C7             STA 29    H<br />
C1C9             CPY #80<br />
C1CB             RTS<br />
--------------------<br />
C1CC ENTRY1      STX 28    L<br />
C1CE LOOP3       LDY #42<br />
C1D0             JSR C1E0  WAIT3<br />
C1D3             BNE C1CE  LOOP3<br />
C1D5             ADC #FE<br />
C1D7             BCS C1CE  LOOP3<br />
C1D9             LDY #1F<br />
C1DB WAIT1       JSR C1E0  WAIT3<br />
C1DE             LDY #2C<br />
C1E0 WAIT2       DEY<br />
C1E1             BNE C1E0  WAIT2<br />
C1E3             BCC C1EA  ENTRY3<br />
C1E5             LDY #2F<br />
C1E7 WAIT3       DEY<br />
C1E8             BNE C1E7  WAIT3<br />
C1EA ENTRY3      LDY C000,X PORT_C0XX<br />
C1ED             LDY #29<br />
C1EF             DEX<br />
C1F0             RTS<br />
--------------------<br />
C1F1 TEST_END    LDA 26<br />
C1F3             CMP 24<br />
C1F5             LDA 27<br />
C1F7             SBC 25<br />
C1F9             INC 26<br />
C1FB             BNE C1FF  RTS.1<br />
C1FD             INC 27<br />
C1FF RTS.1       RTS<br />
====================<br />

__________________

Jiri T. Dolezal, M.D.
Prague, Czech Republic, Europe

Offline
Joined: May 11 2005
Posts: 10
Auto router (not always your friend)

Logjam,

Your Cassette board looks great, but I do want to draw your attention to one small detail that may cause a great deal of trouble on your board. The power distribution traces, are way too small. Notice on the original board how wide the Vcc and Gnd traces are. Also note how direct they are routed.

Auto routers can be made to work for the signal traces, but power traces usually need special attention. Please consider revising your design in this area.

Sincerely,
Rich

Offline
Joined: May 12 2005
Posts: 5
Re: Go ahead and post your findin

logjam wrote:

Go ahead and post your findings. Knowing the locations in memory of the cassette hardware would help figuring out those loops. I bet they are for encoding and decoding the data. The Apple 1 cassette adaptor is similar to the Disk II and IWM, it requires very specific processor timing to read the data.

I don't want to steal belegost1's thunder, but since I had already written an insanely detailed annotation of the Apple-1 cassette ROM code while working on my emulation of the cassette interface for MESS, I figure that others should be allowed to benefit from it.

<br />
;;; Notes on interface hardware:<br />
;;;<br />
;;; Selected by $Cxxx.<br />
;;;<br />
;;; ROMs enabled by $C1xx and $C0xx.<br />
;;;<br />
;;; Output flip-flop to tapeout toggled by each access to $C0xx.<br />
;;;<br />
;;; Tapein input not enabled for accesses to $C00x-$C07x.  Accesses to<br />
;;; these addresses always access the corresponding ROM address<br />
;;; ($0x-$7x).<br />
;;;<br />
;;; Accesses to $C08x-$C0Fx enable the tapein input.<br />
;;;<br />
;;; When tapein input is low, the low bit of the address passes<br />
;;; through normally, so the location accessed will be the<br />
;;; corresponding ROM address ($8x-$Fx).<br />
;;;<br />
;;; When tapein input is high, the low bit of the address is ignored<br />
;;; and is always treated as 0.  In this case an access of $C08x-$C0Fx<br />
;;; will hit ROM address $8y-$Fy, where y is (x & ~1).<br />
;;;<br />
;;; Of course, only read accesses will show any effect; write accesses<br />
;;; are no-ops except for their effect on the tapeout output flip-flop.<br />
;;;<br />
;;; Tapein input comes from a National Semiconductor LM311 voltage<br />
;;; comparator, which simply turns the audio input analog waveform into<br />
;;; a digital square wave, based on its zero crossings.</p>
<p>;;; Disassembly of the Apple I cassette interface ROM.</p>
<p>;;; Variables:<br />
;;;<br />
;;; $200-$2FF: command buffer<br />
;;; $24: L -- low byte of address in command;<br />
;;;	 A2L -- low byte of 2nd address in range.<br />
;;; $25: H -- high byte of address in command;<br />
;;;	 A2H -- high byte of 2nd address in range.<br />
;;; $26: A1L -- low byte of 1st address in range.<br />
;;; $27: A1H -- high byte of 1st address in range.<br />
;;; $28: saved X register<br />
;;; $29: LASTIN -- last tape line level read.</p>
<p>;;; Prepare to read a command from the keyboard.</p>
<p>C100: A9 AA    lda  #$AA	; Prompt for command: "*\r"<br />
C102: 20 EF FF jsr  $FFEF<br />
C105: A9 8D    lda  #$8D<br />
C107: 20 EF FF jsr  $FFEF<br />
C10A: A0 FF    ldy  #$FF	; Y is command buffer index, will start at 0.</p>
<p>	;; Command entry loop:<br />
C10C: C8       iny<br />
C10D: AD 11 D0 lda  $D011	; Loop until key ready.<br />
C110: 10 FB    bpl  $C10D<br />
C112: AD 10 D0 lda  $D010	; Load key character.<br />
C115: 99 00 02 sta  $0200,y	; Add character to buffer.<br />
C118: 20 EF FF jsr  $FFEF	; Display it.<br />
C11B: C9 9B    cmp  #$9B	; : cancel and prompt for new command.<br />
C11D: F0 E1    beq  $C100<br />
C11F: C9 8D    cmp  #$8D	; :	 enter command<br />
C121: D0 E9    bne  $C10C	; Otherwise continue reading command.<br />
	;; (Note there is no check for command buffer overflow;<br />
 	;; entering more than 256 bytes will overwrite the start of<br />
 	;; the buffer.)</p>
<p>;;; Prepare to parse and execute a command.</p>
<p>C123: A2 FF    ldx  #$FF	; X is command buffer index, will start at 0.</p>
<p>C125: A9 00    lda  #$00	; Clear variables L/A2L, H/A2H, A1L, A1H.<br />
C127: 85 24    sta  $24<br />
C129: 85 25    sta  $25<br />
C12B: 85 26    sta  $26<br />
C12D: 85 27    sta  $27</p>
<p>	;; Command parsing loop:<br />
C12F: E8       inx<br />
C130: BD 00 02 lda  $0200,x<br />
C133: C9 D2    cmp  #$D2	; "R"<br />
C135: F0 56    beq  $C18D<br />
C137: C9 D7    cmp  #$D7	; "W"<br />
C139: F0 35    beq  $C170<br />
C13B: C9 AE    cmp  #$AE	; "."<br />
C13D: F0 27    beq  $C166<br />
C13F: C9 8D    cmp  #$8D	; :	marks end of command;<br />
C141: F0 20    beq  $C163	;       return to Monitor.<br />
C143: C9 A0    cmp  #$A0	; " ": spaces are simply skipped.<br />
C145: F0 E8    beq  $C12F<br />
C147: 49 B0    eor  #$B0	; Map digit characters to values 0-9.<br />
C149: C9 0A    cmp  #$0A	; Is character a digit?<br />
C14B: 90 06    bcc  $C153	; If so, skip hex mapping.<br />
C14D: 69 88    adc  #$88	; Map hex letters A-F to values $FA-$FF.<br />
C14F: C9 FA    cmp  #$FA	; Was character a hex letter?<br />
C151: 90 AD    bcc  $C100	; If not, bad command--get a new one.<br />
C153: 0A       asl  a		; Shift hex digit value to A's high nybble.<br />
C154: 0A       asl  a<br />
C155: 0A       asl  a<br />
C156: 0A       asl  a<br />
	;; Loop to shift digit from A's high nybble into H:L variables.<br />
C157: A0 04    ldy  #$04	; 4 iterations.<br />
C159: 0A       asl  a		; Shift A's high bit to carry.<br />
C15A: 26 24    rol  $24		; Rotate carry into L's low bit, L's high bit into carry.<br />
C15C: 26 25    rol  $25		; Rotate carry into H's low bit.<br />
C15E: 88       dey<br />
C15F: D0 F8    bne  $C159	; End of loop.<br />
C161: F0 CC    beq  $C12F	; Digit finished; parse next command character.</p>
<p>C163: 4C 1A FF jmp  $FF1A	; Command finished; jump to Monitor.</p>
<p>	;; A "." marks a 2-address range.<br />
C166: A5 24    lda  $24		; Save L, H in A1L, A1H.<br />
C168: 85 26    sta  $26<br />
C16A: A5 25    lda  $25<br />
C16C: 85 27    sta  $27<br />
C16E: B0 BF    bcs  $C12F	; Parse next command char.<br />
	;; (An equal CMP sets carry, so this branch is always taken.)</p>
<p>;;; End of command parsing code.</p>
<p>;;; Tape I/O code follows.</p>
<p>;;; Tape Write<br />
;;; ----------<br />
;;;<br />
;;; We write a 10-second record-header to the tape, followed by the<br />
;;; data.<br />
;;;<br />
;;; Tape bits are formed by formed by two alternating half-bit pulses.<br />
;;; A "half-bit" is a pulse (half-cycle) of the proper length for the<br />
;;; bit value to write.<br />
;;;<br />
;;; The 10-second record-header has 8192 + X header bits, plus 1 sync<br />
;;; bit.  Each header bit is composed of two 593-clock half-bits.  The<br />
;;; sync bit has a 181-clock first half and a 239-clock second half.<br />
;;;<br />
;;; (At an effective processor clock speed (with RAM refresh waits) of<br />
;;; 980 KHz, the header bits would form a 826 Hz square wave.)<br />
;;;<br />
;;; Data is written one byte at a time, from low to high addresses.<br />
;;; Each byte is written most-significant bit first.<br />
;;;<br />
;;; Each data bit is composed of two half-bits, 239 clocks each for a<br />
;;; "0" bit, 474 clocks each for a "1" bit.<br />
;;;<br />
;;; (With the same clock speed as above, we get 2050 Hz for the "0"<br />
;;; bits and 1034 Hz for the "1" bits.)<br />
;;;<br />
;;; (The first half-bit after the end of each byte is 227 clocks for a<br />
;;; "0" half-bit, 462 clocks for a "1" half-bit.  This goes up by 4<br />
;;; clocks at the end of a 256-byte page.  It seems Wozniak was just a<br />
;;; little off in his path equalization, though this may have been<br />
;;; deliberate.)<br />
;;;<br />
;;; When we start, carry is set, due to the last equal compare.  Also,<br />
;;; X is set to the index of the last command char examined.  This<br />
;;; makes very little difference in practice, since this index is<br />
;;; almost always small.</p>
<p>;;; Write the 10-second record-header.<br />
C170: A9 40    lda  #$40	; Call TAPE_HDR() with A = 64.<br />
C172: 20 CC C1 jsr  $C1CC	; (TAPE_HDR() saves X register in $28.)<br />
;;; Write the data.<br />
	;; (Y = 40 on first call to WRBIT.)<br />
C175: 88       dey<br />
C176: A2 00    ldx  #$00<br />
C178: A1 26    lda  ($26,x)	; Load the next data byte from A1.<br />
	;; Shift each data bit into carry flag and write it to tape.<br />
C17A: A2 10    ldx  #$10	; X = 16 (# of half-bits per byte)<br />
C17C: 0A       asl  a<br />
C17D: 20 DB C1 jsr  $C1DB	; Call WRBIT() for this bit.<br />
C180: D0 FA    bne  $C17C<br />
C182: 20 F1 C1 jsr  $C1F1	; Byte done; call CMP_INC() to update A1.<br />
C185: A0 1E    ldy  #$1E	; Y = 29 on next call to WRBIT().<br />
C187: 90 EC    bcc  $C175	; Repeat until address range is finished.</p>
<p>;;; Used for both Tape Write and Tape Read:<br />
C189: A6 28    ldx  $28		; Restore X register saved by TAPE_HDR().<br />
C18B: B0 98    bcs  $C125	; Parse next command, resetting A1 & A2.</p>
<p>;;; Tape Read<br />
;;; ---------<br />
;;;<br />
;;; We can handle a record header of varying length, but it must be<br />
;;; longer than ~3.5 seconds.<br />
;;;<br />
;;; The record header half-bits should be substantially longer than<br />
;;; 402 clocks, and the sync bit's first half-bit should be<br />
;;; substantially shorter than 378 clocks; the length of the second<br />
;;; half-bit doesn't matter.<br />
;;;<br />
;;; (For an effective processor clock speed (with RAM refresh waits) of<br />
;;; 980 KHz, the header-bit square wave should be under 1218 Hz.)<br />
;;;<br />
;;; Data is read one byte at a time, from low to high addresses.  Each<br />
;;; byte is read most-significant bit first.  (Of course, this is the<br />
;;; same order as the data was written.)<br />
;;;<br />
;;; Each data bit (two half-bits) should be substantially shorter than<br />
;;; 700 clocks to be recognized as a "0" and substantially longer than<br />
;;; 731 clocks to be recognized as a "1".<br />
;;;<br />
;;; (For the same clock speed as above, the "0" bits should be over<br />
;;; 1400 Hz and the "1" bits should be under 1340 Hz.)<br />
;;;<br />
;;; When we start, Y may contain old data, but it will have no effect.</p>
<p>;;; Read the record-header.<br />
	;; Lock on to the tapein waveform, initializing LASTIN.<br />
	;; (If no tapein waveform ever shows up, this call will hang.)<br />
C18D: 20 BC C1 jsr  $C1BC	; Call RDBIT().  (Y value doesn't matter.)<br />
	;; Wait for 22*128 header bit periods, each of 1186 clocks.<br />
	;; Thus this is a 3.4-second delay, assuming a 980 KHz effective<br />
	;; clock rate.<br />
	;; The idea is to get far enough into the header to avoid glitches<br />
	;; from the tape leader.<br />
C190: A9 16    lda  #$16	; Call TAPE_HDR() with A = 22.<br />
C192: 20 CC C1 jsr  $C1CC	; (TAPE_HDR() saves X register in $28.)<br />
	;; Lock onto the tape waveform again.<br />
C195: 20 BC C1 jsr  $C1BC	; Call RDBIT() (Y value doesn't matter.)<br />
	;; Start reading record-header bits, looking for the start of the<br />
	;; sync bit.<br />
	;;<br />
	;; Any half-bit shorter than 378 clocks will always qualify,<br />
	;; and a half bit of up to 402 clocks might qualify if the<br />
	;; timing happens to be right.<br />
	;;<br />
	;; The standard header half-bits of 593 clocks should<br />
	;; definitely fail, and the standard sync bit's first half-bit<br />
	;; of 181 clocks should definitely qualify.<br />
C198: A0 1F    ldy  #$1F	; Call RDHALFBIT() with Y = 31.<br />
C19A: 20 BF C1 jsr  $C1BF<br />
C19D: B0 F9    bcs  $C198	; Repeat until we find an early tapein edge.<br />
	;; An early edge signals that we found the sync bit.  Read the<br />
	;; second half of it.<br />
C19F: 20 BF C1 jsr  $C1BF	; Call RDHALFBIT().<br />
;;; Read the data.<br />
C1A2: A0 3A    ldy  #$3A	; Y = 58 on next call to RDBIT().<br />
	;; Read each data bit from tape into carry flag and rotate it into<br />
	;; data byte.<br />
	;;<br />
	;; Any bit shorter than 700 clocks will always read as "0",<br />
	;; and any bit longer than 731 clocks will always read as "1".<br />
	;; Bits which fall in the range of 700-731 clocks could be<br />
	;; read as either "0" or "1" depending on their timing.  This<br />
	;; is the "indeterminate zone" which valid bits must avoid.<br />
	;;<br />
	;; For the standard lengths of 478 clocks for "0" and 948<br />
	;; clocks for "1", there should be no problems.<br />
C1A4: A2 08    ldx  #$08<br />
C1A6: 48       pha  		; Save A, since RDBIT() will clobber it.<br />
C1A7: 20 BC C1 jsr  $C1BC	; Call RDBIT() for this bit.<br />
C1AA: 68       pla  		; Restore A.<br />
C1AB: 2A       rol  a		; Rotate bit from carry into A.<br />
C1AC: A0 39    ldy  #$39	; Y = 57 on next call to RDBIT().<br />
C1AE: CA       dex<br />
C1AF: D0 F5    bne  $C1A6<br />
C1B1: 81 26    sta  ($26,x)	; Data byte complete--store it at A1.<br />
C1B3: 20 F1 C1 jsr  $C1F1	; Call CMP_INC() to update A1.<br />
C1B6: A0 35    ldy  #$35	; Y = 53 on next call to RDBIT().<br />
C1B8: 90 EA    bcc  $C1A4	; Repeat until address range is finished.<br />
C1BA: B0 CD    bcs  $C189	; Restore X reg. and parse next command.</p>
<p>;;; RDBIT()<br />
;;; Read a bit (a full cycle) from the cassette interface.<br />
;;;<br />
;;; This simply perfoms two successive calls to RDHALFBIT().  So it finishes<br />
;;; the pulse begun by the last tapein edge, reads another complete pulse,<br />
;;; and then reads the start of a third pulse.<br />
;;;<br />
;;; Time elapsed:  12*(Yin - Yout) + 10 clocks.<br />
;;;                12*(Yin - Yout) - 6 clocks to 2nd tapein edge; subtract<br />
;;;                up to 12 clocks for late-detection error.<br />
;;;                16 clocks after 2nd edge; add up to 12 clocks for<br />
;;;                late-detection error<br />
;;;<br />
;;; On return, Y has been reduced by the time elapsed to the 2nd tapein<br />
;;; edge (in counts), and may be negative (>= $80).<br />
;;; Carry flag is set if Y is negative.<br />
;;; Zero and neg flags reflect the result of the Y compare with $80.<br />
;;;<br />
;;; If the Y parameter is set to the expected time (in counts) until<br />
;;; the 2nd tapein edge, the Y result will be the difference between<br />
;;; the expected and actual time to the edge (positive for an early<br />
;;; edge, negative for a late edge).  The carry flag will be clear for<br />
;;; an early or on-time edge and set for a late edge.<br />
;;;<br />
;;; If Y is set to the *average* time until the 2nd edge, based on<br />
;;; a 50-50 chance of the data bit being 0 or 1, carry will be clear<br />
;;; for a 0 bit (shorter than average) and set for a 1 bit (longer<br />
;;; than average).  Thus the carry flag will contain the bit value.<br />
;;;<br />
;;; (It is possible in principle for Y to wrap around, but this would<br />
;;; only happen if the 2nd edge was 256 or more counts after the call,<br />
;;; or 3072 clocks, which is longer than any waveform used by the<br />
;;; cassette interface.)</p>
<p>C1BC: 20 BF C1 jsr  $C1BF	; Call RDHALFBIT().<br />
	;; fall through to RDHALFBIT().</p>
<p>;;; RDHALFBIT()<br />
;;; Read a half-bit from the cassette interface.<br />
;;;<br />
;;; This examines the tapein line until it sees an edge.  So it<br />
;;; doesn't read a complete half-bit pulse; it only sees the end of<br />
;;; the pulse that started in the last RDHALFBIT() call and the start<br />
;;; of the next pulse.<br />
;;;<br />
;;; Time elapsed:  12*(Yin - Yout) + 10 clocks.<br />
;;;                12*(Yin - Yout) - 6 clocks to tapein edge; subtract<br />
;;;                up to 12 clocks for late-detection error.<br />
;;;                16 clocks after 2nd edge; add up to 12 clocks for<br />
;;;                late-detection error<br />
;;;<br />
;;; On return, Y has been reduced by the time elapsed to the tapein<br />
;;; edge (in counts), and may be negative (>= $80).<br />
;;; Carry flag is set if Y is negative.<br />
;;; Zero and neg flags reflect the result of the Y compare with $80.<br />
;;;<br />
;;; If the Y parameter is set to the expected time (in counts) until<br />
;;; the next tapein edge, the Y result will be the difference between<br />
;;; the expected and actual time to the edge (positive for an early<br />
;;; edge, negative for a late edge).  The carry flag will be clear for<br />
;;; an early or on-time edge and set for a late edge.</p>
<p>C1BF: 88       dey  		; Decrement Y.<br />
	;; Tapein line is enabled by accesses to address region $C08x-$C0Fx.<br />
	;; When the line is low, the value read is that at the corresponding<br />
	;; ROM address $C18x-$C1Fx.  When the line is high, the value read<br />
	;; is that at ROM address $C18y-$C1Fy, where y is (x & ~1).  So in<br />
	;; this case, we read ROM address $C181 (value $FA) when tapein is<br />
	;; low and ROM address $C180 (value $D0) when tapein is high.  This<br />
	;; effectively indicates the tapein line level.<br />
C1C0: AD 81 C0 lda  $C081	; Read tapein line level.<br />
C1C3: C5 29    cmp  $29		; Repeat until the line level is different<br />
C1C5: F0 F8    beq  $C1BF	;   from LASTIN.<br />
C1C7: 85 29    sta  $29		; Save current tapein level in LASTIN.<br />
C1C9: C0 80    cpy  #$80	; Set carry if Y wrapped around.<br />
C1CB: 60       rts  </p>
<p>;;; TAPE_HDR()<br />
;;; Write tape record header bits and sync bit.<br />
;;; (Also used as a convenient delay when reading the record-header.)<br />
;;;<br />
;;; There will be A*128 + X/2 header bits.<br />
;;;<br />
;;; Each header bit is composed of two long "1" half-bits of 593 clocks each;<br />
;;; every 256th half-bit is 597 clocks.<br />
;;;<br />
;;; The sync bit is a short "0" half-bit of 181 clocks, followed by a normal<br />
;;; 239-clock "0" half-bit, with 15 clocks afterward.<br />
;;;<br />
;;; This is always called with carry set.<br />
;;; A contains the number of 128-bit header-bit blocks.<br />
;;; X is the number of extra header half-bits; its value is saved in $28.<br />
;;; On return, A is 255, X is 254, Y is 41;<br />
;;; carry flag is clear, zero flag clear, neg flag set.</p>
<p>C1CC: 86 28    stx  $28		; Save X register.<br />
	;; Do header bits: (A*256 + X) calls to WRHALFBIT() with Y = 66.<br />
C1CE: A0 42    ldy  #$42	; Call WRHALFBIT() with Y = 66.<br />
C1D0: 20 E0 C1 jsr  $C1E0<br />
C1D3: D0 F9    bne  $C1CE<br />
C1D5: 69 FE    adc  #$FE	; Decrement A.<br />
C1D7: B0 F5    bcs  $C1CE	; Repeat until A wraps around to $FF.<br />
	;; Now do the sync bit; carry is clear.<br />
C1D9: A0 1E    ldy  #$1E	; Call WRBIT() with Y = 30.<br />
	;; fall through to WRBIT().</p>
<p>;;; WRBIT()<br />
;;; Write a bit (a full cycle) to the cassette interface.<br />
;;;<br />
;;; This simply perfoms two successive calls to WRHALFBIT().  Therefore<br />
;;; it finishes the pulse begun by the last tapeout edge, creates another<br />
;;; complete pulse, and then begins a third pulse.<br />
;;;<br />
;;; Time elapsed:<br />
;;;     "0" bit:  5*Y + 262 clocks.<br />
;;;               5*Y + 8 clocks to 1st tapeout edge,<br />
;;;               239 clocks to 2nd tapeout edge, 15 clocks after 2nd edge.<br />
;;;     "1" bit:  5*Y + 732 clocks.<br />
;;;               5*Y + 243 clocks to 1st tapeout edge,<br />
;;;               474 clocks to 2nd tapeout edge, 15 clocks after 2nd edge.<br />
;;;<br />
;;; Parameters and results are as for WRHALFBIT(), except that X is<br />
;;; decremented twice.<br />
C1DB: 20 E0 C1 jsr  $C1E0	; Call WRHALFBIT().<br />
C1DE: A0 2C    ldy  #$2C	; Repeat WRHALFBIT() with Y = 44.<br />
	;; fall through to WRHALFBIT().</p>
<p>;;; WRHALFBIT()<br />
;;; Write a half-bit to the cassette interface.<br />
;;;<br />
;;; What this actually does is to wait for a specified time before toggling<br />
;;; the tapeout line, generating a rising or falling edge.  So this doesn't<br />
;;; write a complete half-bit pulse; rather, it finishes the pulse begun by<br />
;;; the last WRHALFBIT() call and starts a new pulse.<br />
;;;<br />
;;; Time elapsed:<br />
;;;     "0" half-bit:  5*Y + 17 clocks.<br />
;;;		       5*Y + 2 clocks to tapeout edge, 15 clocks after edge.<br />
;;;     "1" half-bit:  5*Y + 252 clocks.<br />
;;;		       5*Y + 237 clocks to tapeout edge, 15 clocks after edge.<br />
;;;<br />
;;; Carry flag = value of half-bit to write (clear for "0", set for "1")<br />
;;; Y = number of counts to tapeout toggle (depends on previous code path)<br />
;;; X = half-bit counter (decremented for each call)<br />
;;; A and carry flag are unchanged; Y is set to 41; X is decremented.<br />
;;; Zero and neg flags reflect the result of the X decrement.</p>
<p>	;; "0" half-bit: delay of (5*Y - 1 + 3) clocks before tapeout toggle.<br />
C1E0: 88       dey  		; Spin Y times (delay (5*Y - 1) clocks).<br />
C1E1: D0 FD    bne  $C1E0<br />
C1E3: 90 05    bcc  $C1EA	; Skip to tapeout toggle if carry clear.<br />
	;; "1" half-bit: extra delay of 235 clocks before tapeout toggle.<br />
C1E5: A0 2F    ldy  #$2F	; Spin 47 times (delay (2 + 5*47 - 1) clocks).<br />
C1E7: 88       dey<br />
C1E8: D0 FD    bne  $C1E7<br />
	;; Tapeout line is toggled by each access to address region $C0xx;<br />
	;; any such access toggles the output flip-flop to the tapeout line.<br />
	;; The direction of access makes no difference.  X has no effect on<br />
	;; this; it's just used here for timing purposes.  (Although unused,<br />
	;; the value loaded will be the ROM byte at $C100+X.)<br />
	;; Remaining delay:  5 + 2 + 2 + 6 = 15 clocks.<br />
C1EA: BC 00 C0 ldy  $C000,x	; Toggle tapeout.<br />
C1ED: A0 29    ldy  #$29	; Y = 41<br />
C1EF: CA       dex<br />
C1F0: 60       rts  </p>
<p>;;; CMP_INC()<br />
;;; Compare A1 to A2, increment A1.<br />
;;; Carry is set if A1 >= A2 before the increment, otherwise clear.<br />
;;;<br />
;;; Time elapsed:  26 clocks (30 clocks if increment of A1H required).<br />
;;;<br />
;;; (This takes advantage of the fact that CMP and SBC on the 6502<br />
;;; use and set the carry flag in the opposite sense from most<br />
;;; processors:	carry = 1 for no borrow, carry = 0 for borrow.)</p>
<p>C1F1: A5 26    lda  $26		; A1L<br />
C1F3: C5 24    cmp  $24		; A1L - A2L<br />
	;; If A1L >= A2L, carry = 1; if A1L < A2L, carry = 0.<br />
C1F5: A5 27    lda  $27		; A1H<br />
C1F7: E5 25    sbc  $25		; A1H - A2H<br />
	;; If A1L >= A2L and A1H >= A2H, carry = 1<br />
	;; If A1L >= A2L and A1H < A2H, carry = 0<br />
	;; If A1L < A2L and A1H > A2H, carry = 1<br />
	;; If A1L < A2L and A1H <= A2H, carry = 0<br />
	;; Now carry flag == (A1 >= A2).<br />
	;; Increment A1 and return.<br />
C1F9: E6 26    inc  $26		; Increment A1L.<br />
C1FB: D0 02    bne  $C1FF<br />
C1FD: E6 27    inc  $27		; Increment A1H.<br />
C1FF: 60       rts<br />

Offline
Joined: May 12 2005
Posts: 5
Apologies for broken code formatting...

Sorry about how the above code came out; something seems to be wrong with how this board's software handles the "code" tag. And what's with the substitution of "l-e-g-o" with "EvilBlockCompany"?

iceandfire's picture
Offline
Joined: Dec 20 2003
Posts: 67
Assembly of Cassette Interface Software

This has to be the most complete (and best) commented disassembly of the cassette code for the Apple-I that I have ever seen. Great Job!

__________________

Larry Nelson