.CR 6502 .TF wozaci.hex,HEX,8 .LF wozaci.list ;------------------------------------------------------------------------- ; ; The WOZ Apple Cassette Interface for the Apple 1 ; Written by Steve Wozniak somewhere around 1976 ; ;------------------------------------------------------------------------- ;------------------------------------------------------------------------- ; Memory declaration ;------------------------------------------------------------------------- HEX1L .EQ $24 End address of dump block HEX1H .EQ $25 HEX2L .EQ $26 Begin address of dump block HEX2H .EQ $27 SAVEINDEX .EQ $28 Save index in input buffer LASTSTATE .EQ $29 Last input state IN .EQ $0200 Input buffer FLIP .EQ $C000 Output flip-flop TAPEIN .EQ $C081 Tape input KBD .EQ $D010 PIA.A keyboard input KBDCR .EQ $D011 PIA.A keyboard control register ESCAPE .EQ $FF1A Escape back to monitor ECHO .EQ $FFEF Echo character to terminal ;------------------------------------------------------------------------- ; Constants ;------------------------------------------------------------------------- CR .EQ $8D Carriage Return ESC .EQ $9B ASCII ESC ;------------------------------------------------------------------------- ; Let's get started ;------------------------------------------------------------------------- .OR $C100 WOZACI LDA #"*" Print the Tape prompt JSR ECHO LDA #CR And drop the cursor one line JSR ECHO LDY #-1 Reset the input buffer index NEXTCHAR INY KBDWAIT LDA KBDCR Wait for a key BPL KBDWAIT Still no key! LDA KBD Read key from keyboard STA IN,Y Save it into buffer JSR ECHO And type it on the screen CMP #ESC BEQ WOZACI Start from scratch if ESC! CMP #CR BNE NEXTCHAR Read keys until CR LDX #-1 Initialize parse buffer pointer ;------------------------------------------------------------------------- ; Start parsing first or a new tape command ;------------------------------------------------------------------------- NEXTCMD LDA #0 Clear begin and end values STA HEX1L STA HEX1H STA HEX2L STA HEX2H NEXTCHR INX Increment input pointer LDA IN,X Get next char from input line CMP #"R" Read command? BEQ READ Yes! CMP #"W" Write command? BEQ WRITE Yes! (note: CY=1) CMP #"." Separator? BEQ SEP Yes! CMP #CR End of line? BEQ GOESC Escape to monitor! We're done CMP #" " Ignore spaces BEQ NEXTCHR EOR #"0" Map digits to 0-9 CMP #9+1 Is it a decimal digit? BCC DIG Yes! ADC #$88 Map letter "A"-"F" to $FA-$FF CMP #$FA Hex letter? BCC WOZACI No! Character not hex! DIG ASL Hex digit to MSD of A ASL ASL ASL LDY #4 Shift count HEXSHIFT ASL Hex digit left, MSB to carry ROL HEX1L Rotate into LSD ROL HEX1H Rotate into MSD DEY Done 4 shifts? BNE HEXSHIFT No! Loop BEQ NEXTCHR Handle next character ;------------------------------------------------------------------------- ; Return to monitor, prints \ first ;------------------------------------------------------------------------- GOESC JMP ESCAPE Escape back to monitor ;------------------------------------------------------------------------- ; Separating . found. Copy HEX1 to Hex2. Doesn't clear HEX1!!! ;------------------------------------------------------------------------- SEP LDA HEX1L Copy hex value 1 to hex value 2 STA HEX2L LDA HEX1H STA HEX2H BCS NEXTCHR Always taken! ;------------------------------------------------------------------------- ; Write a block of memory to tape ;------------------------------------------------------------------------- WRITE LDA #64 Write 10 second header JSR WHEADER WRNEXT DEY Compensate timing for extra work LDX #0 Get next byte to write LDA (HEX2L,X) LDX #8*2 Shift 8 bits (decremented twice) WBITLOOP ASL Shift MSB to carry JSR WRITEBIT Write this bit BNE WBITLOOP Do all 8 bits! JSR INCADDR Increment address LDY #30 Compensate timer for extra work BCC WRNEXT Not done yet! Write next byte RESTIDX LDX SAVEINDEX Restore index in input line BCS NEXTCMD Always taken! ;------------------------------------------------------------------------- ; Read from tape ;------------------------------------------------------------------------- READ JSR FULLCYCLE Wait until full cycle is detected LDA #22 Introduce some delay to allow JSR WHEADER the tape speed to stabilize JSR FULLCYCLE Synchronize with full cycle NOTSTART LDY #31 Try to detect the much shorter JSR CMPLEVEL start bit BCS NOTSTART Start bit not detected yet! JSR CMPLEVEL Wait for 2nd phase of start bit LDY #58 Set threshold value in middle RDBYTE LDX #8 Receiver 8 bits RDBIT PHA JSR FULLCYCLE Detect a full cycle PLA ROL Roll new bit into result LDY #57 Set threshold value in middle DEX Decrement bit counter BNE RDBIT Read next bit! STA (HEX2L,X) Save new byte JSR INCADDR Increment address LDY #53 Compensate threshold with workload BCC RDBYTE Do next byte if not done yet! BCS RESTIDX Always taken! Restore parse index FULLCYCLE JSR CMPLEVEL Wait for two level changes CMPLEVEL DEY Decrement time counter LDA TAPEIN Get Tape In data CMP LASTSTATE Same as before? BEQ CMPLEVEL Yes! STA LASTSTATE Save new data CPY #128 Compare threshold RTS ;------------------------------------------------------------------------- ; Write header to tape ; ; The header consists of an asymmetric cycle, starting with one phase of ; approximately (66+47)x5=565us, followed by a second phase of ; approximately (44+47)x5=455us. ; Total cycle duration is approximately 1020us ~ 1kHz. The actual ; frequencywill be a bit lower because of the additional workload between ; the twoloops. ; The header ends with a short phase of (30+47)x5=385us and a normal ; phase of (44+47)x5=455us. This start bit must be detected by the read ; routine to trigger the reading of the actual data. ;------------------------------------------------------------------------- WHEADER STX SAVEINDEX Save index in input line HCOUNT LDY #66 Extra long delay JSR WDELAY CY is constantly 1, writing a 1 BNE HCOUNT Do this 64 * 256 time! ADC #-2 Decrement A (CY=1 all the time) BCS HCOUNT Not all done! LDY #30 Write a final short bit (start) ;------------------------------------------------------------------------- ; Write a full bit cycle ; ; Upon entry Y contains a compensated value for the first phase of 0 ; bit length. All subsequent loops don't have to be time compensated. ;------------------------------------------------------------------------- WRITEBIT JSR WDELAY Do two equal phases LDY #44 Load 250us counter - compensation WDELAY DEY Delay 250us (one phase of 2kHz) BNE WDELAY BCC WRITE1 Write a '1' (2kHz) LDY #47 Additional delay for '0' (1kHz) WDELAY0 DEY (delay 250us) BNE WDELAY0 WRITE1 LDY FLIP,X Flip the output bit LDY #41 Reload 250us cntr (compensation) DEX Decrement bit counter RTS ;------------------------------------------------------------------------- ; Increment current address and compare with last address ;------------------------------------------------------------------------- INCADDR LDA HEX2L Compare current address with CMP HEX1L end address LDA HEX2H SBC HEX1H INC HEX2L And increment current address BNE NOCARRY No carry to MSB! INC HEX2H NOCARRY RTS ;------------------------------------------------------------------------- .LI OFF