Original 65C02 assembler
65C02 source code of the XMODEM file transfer protocol by Daryl Rictor (All rights acknowledged).
; XMODEM/CRC Sender/Receiver for the 65C02
;
; By Daryl Rictor Aug 2002
;
; A simple file transfer program to allow transfers between the SBC and a
; console device utilizing the x-modem/CRC transfer protocol. Requires
; ~1200 bytes of either RAM or ROM, 132 bytes of RAM for the receive buffer,
; and 12 bytes of zero page RAM for variable storage.
;
;**************************************************************************
; This implementation of XMODEM/CRC does NOT conform strictly to the
; XMODEM protocol standard in that it (1) does not accurately time character
; reception or (2) fall back to the Checksum mode.
; (1) For timing, it uses a crude timing loop to provide approximate
; delays. These have been calibrated against a 1MHz CPU clock. I have
; found that CPU clock speed of up to 5MHz also work but may not in
; every case. Windows HyperTerminal worked quite well at both speeds!
;
; (2) Most modern terminal programs support XMODEM/CRC which can detect a
; wider range of transmission errors so the fallback to the simple checksum
; calculation was not implemented to save space.
;**************************************************************************
;
; Files transferred via XMODEM-CRC will have the load address contained in
; the first two bytes in little-endian format:
; FIRST BLOCK
; offset(0) = lo(load start address),
; offset(1) = hi(load start address)
; offset(2) = data byte (0)
; offset(n) = data byte (n-2)
;
; Subsequent blocks
; offset(n) = data byte (n)
;
; One note, XMODEM send 128 byte blocks. If the block of memory that
; you wish to save is smaller than the 128 byte block boundary, then
; the last block will be padded with zeros. Upon reloading, the
; data will be written back to the original location. In addition, the
; padded zeros WILL also be written into RAM, which could overwrite other
; data.
;
;-------------------------- The Code ----------------------------
;
; zero page variables (adjust these to suit your needs)
;
;
lastblk = $35 ; flag for last block
blkno = $36 ; block number
errcnt = $37 ; error counter 10 is the limit
bflag = $37 ; block flag
crc = $38 ; CRC lo byte (two byte variable)
crch = $39 ; CRC hi byte
ptr = $3a ; data pointer (two byte variable)
ptrh = $3b ; " "
eofp = $3c ; end of file address pointer (2 bytes)
eofph = $3d ; " " " "
retry = $3e ; retry counter
retry2 = $3f ; 2nd counter
;
;
; non-zero page variables and buffers
;
;
Rbuff = $0300 ; temp 132 byte receive buffer
;(place anywhere, page aligned)
;
;
; tables and constants
;
;
; The crclo & crchi labels are used to point to a lookup table to calculate
; the CRC for the 128 byte data blocks. There are two implementations of these
; tables. One is to use the tables included (defined towards the end of this
; file) and the other is to build them at run-time. If building at run-time,
; then these two labels will need to be un-commented and declared in RAM.
;
;crclo = $7D00 ; Two 256-byte tables for quick lookup
;crchi = $7E00 ; (should be page-aligned for speed)
;
;
;
; XMODEM Control Character Constants
SOH = $01 ; start block
EOT = $04 ; end of text marker
ACK = $06 ; good block acknowledged
NAK = $15 ; bad block acknowledged
CAN = $18 ; cancel (not standard, not supported)
CR = $0d ; carriage return
LF = $0a ; line feed
ESC = $1b ; ESC to exit
;
;^^^^^^^^^^^^^^^^^^^^^^ Start of Program ^^^^^^^^^^^^^^^^^^^^^^
;
; Xmodem/CRC transfer routines
; By Daryl Rictor, August 8, 2002
;
; v1.0 released on Aug 8, 2002.
;
;
*= $FA00 ; Start of program (adjust to your needs)
;
; Enter this routine with the beginning address stored in the zero page address
; pointed to by ptr & ptrh and the ending address stored in the zero page address
; pointed to by eofp & eofph.
;
;
jmp XmodemRcv ; quick jmp table
XModemSend jsr PrintMsg ; send prompt and info
lda #$00 ;
sta errcnt ; error counter set to 0
sta lastblk ; set flag to false
lda #$01 ;
sta blkno ; set block # to 1
Wait4CRC lda #$ff ; 3 seconds
sta retry2 ;
jsr GetByte ;
bcc Wait4CRC ; wait for something to come in...
cmp #"C" ; is it the "C" to start a CRC xfer?
beq SetstAddr ; yes
cmp #ESC ; is it a cancel? <Esc> Key
bne Wait4CRC ; No, wait for another character
jmp PrtAbort ; Print abort msg and exit
SetstAddr ldy #$00 ; init data block offset to 0
ldx #$04 ; preload X to Receive buffer
lda #$01 ; manually load blk number
sta Rbuff ; into 1st byte
lda #$FE ; load 1's comp of block #
sta Rbuff+1 ; into 2nd byte
lda ptr ; load low byte of start address
sta Rbuff+2 ; into 3rd byte
lda ptrh ; load hi byte of start address
sta Rbuff+3 ; into 4th byte
bra Ldbuff1 ; jump into buffer load routine
LdBuffer lda Lastblk ; Was the last block sent?
beq LdBuff0 ; no, send the next one
jmp Done ; yes, we're done
LdBuff0 ldx #$02 ; init pointers
ldy #$00 ;
inc Blkno ; inc block counter
lda Blkno ;
sta Rbuff ; save in 1st byte of buffer
eor #$FF ;
sta Rbuff+1 ; save 1's comp of blkno next
LdBuff1 lda (ptr),y ; save 128 bytes of data
sta Rbuff,x ;
LdBuff2 sec ;
lda eofp ;
sbc ptr ; Are we at the last address?
bne LdBuff4 ; no, inc pointer and continue
lda eofph ;
sbc ptrh ;
bne LdBuff4 ;
inc LastBlk ; Yes, Set last byte flag
LdBuff3 inx ;
cpx #$82 ; Are we at the end of the 128 byte block?
beq SCalcCRC ; Yes, calc CRC
lda #$00 ; Fill rest of 128 bytes with $00
sta Rbuff,x ;
beq LdBuff3 ; Branch always
LdBuff4 inc ptr ; Inc address pointer
bne LdBuff5 ;
inc ptrh ;
LdBuff5 inx ;
cpx #$82 ; last byte in block?
bne LdBuff1 ; no, get the next
SCalcCRC jsr CalcCRC
lda crch ; save Hi byte of CRC to buffer
sta Rbuff,y ;
iny ;
lda crc ; save lo byte of CRC to buffer
sta Rbuff,y ;
Resend ldx #$00 ;
lda #SOH
jsr Put_chr ; send SOH
SendBlk lda Rbuff,x ; Send 132 bytes in buffer to the console
jsr Put_chr ;
inx ;
cpx #$84 ; last byte?
bne SendBlk ; no, get next
lda #$FF ; yes, set 3 second delay
sta retry2 ; and
jsr GetByte ; Wait for Ack/Nack
bcc Seterror ; No chr received after 3 seconds, resend
cmp #ACK ; Chr received... is it:
beq LdBuffer ; ACK, send next block
cmp #NAK ;
beq Seterror ; NAK, inc errors and resend
cmp #ESC ;
beq PrtAbort ; Esc pressed to abort
; fall through to error counter
Seterror inc errcnt ; Inc error counter
lda errcnt ;
cmp #$0A ; are there 10 errors? (Xmodem spec for failure)
bne Resend ; no, resend block
PrtAbort jsr Flush ; yes, too many errors, flush buffer,
jmp Print_Err ; print error msg and exit
Done Jmp Print_Good ; All Done..Print msg and exit
;
;
;
XModemRcv jsr PrintMsg ; send prompt and info
lda #$01
sta blkno ; set block # to 1
sta bflag ; set flag to get address from block 1
StartCrc lda #"C" ; "C" start with CRC mode
jsr Put_Chr ; send it
lda #$FF
sta retry2 ; set loop counter for ~3 sec delay
lda #$00
sta crc
sta crch ; init CRC value
jsr GetByte ; wait for input
bcs GotByte ; byte received, process it
bcc StartCrc ; resend "C"
StartBlk lda #$FF ;
sta retry2 ; set loop counter for ~3 sec delay
jsr GetByte ; get first byte of block
bcc StartBlk ; timed out, keep waiting...
GotByte cmp #ESC ; quitting?
bne GotByte1 ; no
; lda #$FE ; Error code in "A" of desired
brk ; YES - do BRK or change to RTS if desired
GotByte1 cmp #SOH ; start of block?
beq BegBlk ; yes
cmp #EOT ;
bne BadCrc ; Not SOH or EOT, so flush buffer & send NAK
jmp RDone ; EOT - all done!
BegBlk ldx #$00
GetBlk lda #$ff ; 3 sec window to receive characters
sta retry2 ;
GetBlk1 jsr GetByte ; get next character
bcc BadCrc ; chr rcv error, flush and send NAK
GetBlk2 sta Rbuff,x ; good char, save it in the rcv buffer
inx ; inc buffer pointer
cpx #$84 ; <01> <FE> <128 bytes> <CRCH> <CRCL>
bne GetBlk ; get 132 characters
ldx #$00 ;
lda Rbuff,x ; get block # from buffer
cmp blkno ; compare to expected block #
beq GoodBlk1 ; matched!
jsr Print_Err ; Unexpected block number - abort
jsr Flush ; mismatched - flush buffer and then do BRK
; lda #$FD ; put error code in "A" if desired
brk ; unexpected block # - fatal error - BRK or RTS
GoodBlk1 eor #$ff ; 1's comp of block #
inx ;
cmp Rbuff,x ; compare with expected 1's comp of block #
beq GoodBlk2 ; matched!
jsr Print_Err ; Unexpected block number - abort
jsr Flush ; mismatched - flush buffer and then do BRK
; lda #$FC ; put error code in "A" if desired
brk ; bad 1's comp of block#
GoodBlk2 jsr CalcCRC ; calc CRC
lda Rbuff,y ; get hi CRC from buffer
cmp crch ; compare to calculated hi CRC
bne BadCrc ; bad crc, send NAK
iny ;
lda Rbuff,y ; get lo CRC from buffer
cmp crc ; compare to calculated lo CRC
beq GoodCrc ; good CRC
BadCrc jsr Flush ; flush the input port
lda #NAK ;
jsr Put_Chr ; send NAK to resend block
jmp StartBlk ; start over, get the block again
GoodCrc ldx #$02 ;
lda blkno ; get the block number
cmp #$01 ; 1st block?
bne CopyBlk ; no, copy all 128 bytes
lda bflag ; is it really block 1, not block 257, 513 etc.
beq CopyBlk ; no, copy all 128 bytes
lda Rbuff,x ; get target address from 1st 2 bytes of blk 1
sta ptr ; save lo address
inx ;
lda Rbuff,x ; get hi address
sta ptr+1 ; save it
inx ; point to first byte of data
dec bflag ; set the flag so we won't get another address
CopyBlk ldy #$00 ; set offset to zero
CopyBlk3 lda Rbuff,x ; get data byte from buffer
sta (ptr),y ; save to target
inc ptr ; point to next address
bne CopyBlk4 ; did it step over page boundary?
inc ptr+1 ; adjust high address for page crossing
CopyBlk4 inx ; point to next data byte
cpx #$82 ; is it the last byte
bne CopyBlk3 ; no, get the next one
IncBlk inc blkno ; done. Inc the block #
lda #ACK ; send ACK
jsr Put_Chr ;
jmp StartBlk ; get next block
RDone lda #ACK ; last block, send ACK and exit.
jsr Put_Chr ;
jsr Flush ; get leftover characters, if any
jsr Print_Good ;
rts ;
;
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;======================================================================
; I/O Device Specific Routines
;
; Two routines are used to communicate with the I/O device.
;
; "Get_Chr" routine will scan the input port for a character. It will
; return without waiting with the Carry flag CLEAR if no character is
; present or return with the Carry flag SET and the character in the "A"
; register if one was present.
;
; "Put_Chr" routine will write one byte to the output port. Its alright
; if this routine waits for the port to be ready. its assumed that the
; character was send upon return from this routine.
;
; Here is an example of the routines used for a standard 6551 ACIA.
; You would call the ACIA_Init prior to running the xmodem transfer
; routine.
;
ACIA_Data = $7F70 ; Adjust these addresses to point
ACIA_Status = $7F71 ; to YOUR 6551!
ACIA_Command = $7F72 ;
ACIA_Control = $7F73 ;
ACIA_Init lda #$1F ; 19.2K/8/1
sta ACIA_Control ; control reg
lda #$0B ; N parity/echo off/rx int off/ dtr active low
sta ACIA_Command ; command reg
rts ; done
;
; input chr from ACIA (no waiting)
;
Get_Chr clc ; no chr present
lda ACIA_Status ; get Serial port status
and #$08 ; mask rcvr full bit
beq Get_Chr2 ; if not chr, done
Lda ACIA_Data ; else get chr
sec ; and set the Carry Flag
Get_Chr2 rts ; done
;
; output to OutPut Port
;
Put_Chr PHA ; save registers
Put_Chr1 lda ACIA_Status ; serial port status
and #$10 ; is tx buffer empty
beq Put_Chr1 ; no, go back and test it again
PLA ; yes, get chr to send
sta ACIA_Data ; put character to Port
RTS ; done
;=========================================================================
;
; subroutines
;
;
;
GetByte lda #$00 ; wait for chr input and cycle timing loop
sta retry ; set low value of timing loop
StartCrcLp jsr Get_chr ; get chr from serial port, don't wait
bcs GetByte1 ; got one, so exit
dec retry ; no character received, so dec counter
bne StartCrcLp ;
dec retry2 ; dec hi byte of counter
bne StartCrcLp ; look for character again
clc ; if loop times out, CLC, else SEC and return
GetByte1 rts ; with character in "A"
;
Flush lda #$70 ; flush receive buffer
sta retry2 ; flush until empty for ~1 sec.
Flush1 jsr GetByte ; read the port
bcs Flush ; if chr recvd, wait for another
rts ; else done
;
PrintMsg ldx #$00 ; PRINT starting message
PrtMsg1 lda Msg,x
beq PrtMsg2
jsr Put_Chr
inx
bne PrtMsg1
PrtMsg2 rts
Msg .byte "Begin XMODEM/CRC transfer. Press <Esc> to abort..."
.BYTE CR, LF
.byte 0
;
Print_Err ldx #$00 ; PRINT Error message
PrtErr1 lda ErrMsg,x
beq PrtErr2
jsr Put_Chr
inx
bne PrtErr1
PrtErr2 rts
ErrMsg .byte "Transfer Error!"
.BYTE CR, LF
.byte 0
;
Print_Good ldx #$00 ; PRINT Good Transfer message
Prtgood1 lda GoodMsg,x
beq Prtgood2
jsr Put_Chr
inx
bne Prtgood1
Prtgood2 rts
GoodMsg .byte EOT,CR,LF,EOT,CR,LF,EOT,CR,LF,CR,LF
.byte "Transfer Successful!"
.BYTE CR, LF
.byte 0
;
;
;=========================================================================
;
;
; CRC subroutines
;
;
CalcCRC lda #$00 ; yes, calculate the CRC for the 128 bytes
sta crc ;
sta crch ;
ldy #$02 ;
CalcCRC1 lda Rbuff,y ;
eor crc+1 ; Quick CRC computation with lookup tables
tax ; updates the two bytes at crc & crc+1
lda crc ; with the byte send in the "A" register
eor CRCHI,X
sta crc+1
lda CRCLO,X
sta crc
iny ;
cpy #$82 ; done yet?
bne CalcCRC1 ; no, get next
rts ; y=82 on exit
;
; Alternate solution is to build the two lookup tables at run-time. This might
; be desirable if the program is running from ram to reduce binary upload time.
; The following code generates the data for the lookup tables. You would need to
; un-comment the variable declarations for crclo & crchi in the Tables and Constants
; section above and call this routine to build the tables before calling the
; "xmodem" routine.
;
;MAKECRCTABLE
; ldx #$00
; LDA #$00
;zeroloop sta crclo,x
; sta crchi,x
; inx
; bne zeroloop
; ldx #$00
;fetch txa
; eor crchi,x
; sta crchi,x
; ldy #$08
;fetch1 asl crclo,x
; rol crchi,x
; bcc fetch2
; lda crchi,x
; eor #$10
; sta crchi,x
; lda crclo,x
; eor #$21
; sta crclo,x
;fetch2 dey
; bne fetch1
; inx
; bne fetch
; rts
;
; The following tables are used to calculate the CRC for the 128 bytes
; in the xmodem data blocks. You can use these tables if you plan to
; store this program in ROM. If you choose to build them at run-time,
; then just delete them and define the two labels: crclo & crchi.
;
; low byte CRC lookup table (should be page aligned)
*= $FD00
crclo
.byte $00,$21,$42,$63,$84,$A5,$C6,$E7,$08,$29,$4A,$6B,$8C,$AD,$CE,$EF
.byte $31,$10,$73,$52,$B5,$94,$F7,$D6,$39,$18,$7B,$5A,$BD,$9C,$FF,$DE
.byte $62,$43,$20,$01,$E6,$C7,$A4,$85,$6A,$4B,$28,$09,$EE,$CF,$AC,$8D
.byte $53,$72,$11,$30,$D7,$F6,$95,$B4,$5B,$7A,$19,$38,$DF,$FE,$9D,$BC
.byte $C4,$E5,$86,$A7,$40,$61,$02,$23,$CC,$ED,$8E,$AF,$48,$69,$0A,$2B
.byte $F5,$D4,$B7,$96,$71,$50,$33,$12,$FD,$DC,$BF,$9E,$79,$58,$3B,$1A
.byte $A6,$87,$E4,$C5,$22,$03,$60,$41,$AE,$8F,$EC,$CD,$2A,$0B,$68,$49
.byte $97,$B6,$D5,$F4,$13,$32,$51,$70,$9F,$BE,$DD,$FC,$1B,$3A,$59,$78
.byte $88,$A9,$CA,$EB,$0C,$2D,$4E,$6F,$80,$A1,$C2,$E3,$04,$25,$46,$67
.byte $B9,$98,$FB,$DA,$3D,$1C,$7F,$5E,$B1,$90,$F3,$D2,$35,$14,$77,$56
.byte $EA,$CB,$A8,$89,$6E,$4F,$2C,$0D,$E2,$C3,$A0,$81,$66,$47,$24,$05
.byte $DB,$FA,$99,$B8,$5F,$7E,$1D,$3C,$D3,$F2,$91,$B0,$57,$76,$15,$34
.byte $4C,$6D,$0E,$2F,$C8,$E9,$8A,$AB,$44,$65,$06,$27,$C0,$E1,$82,$A3
.byte $7D,$5C,$3F,$1E,$F9,$D8,$BB,$9A,$75,$54,$37,$16,$F1,$D0,$B3,$92
.byte $2E,$0F,$6C,$4D,$AA,$8B,$E8,$C9,$26,$07,$64,$45,$A2,$83,$E0,$C1
.byte $1F,$3E,$5D,$7C,$9B,$BA,$D9,$F8,$17,$36,$55,$74,$93,$B2,$D1,$F0
; hi byte CRC lookup table (should be page aligned)
*= $FE00
crchi
.byte $00,$10,$20,$30,$40,$50,$60,$70,$81,$91,$A1,$B1,$C1,$D1,$E1,$F1
.byte $12,$02,$32,$22,$52,$42,$72,$62,$93,$83,$B3,$A3,$D3,$C3,$F3,$E3
.byte $24,$34,$04,$14,$64,$74,$44,$54,$A5,$B5,$85,$95,$E5,$F5,$C5,$D5
.byte $36,$26,$16,$06,$76,$66,$56,$46,$B7,$A7,$97,$87,$F7,$E7,$D7,$C7
.byte $48,$58,$68,$78,$08,$18,$28,$38,$C9,$D9,$E9,$F9,$89,$99,$A9,$B9
.byte $5A,$4A,$7A,$6A,$1A,$0A,$3A,$2A,$DB,$CB,$FB,$EB,$9B,$8B,$BB,$AB
.byte $6C,$7C,$4C,$5C,$2C,$3C,$0C,$1C,$ED,$FD,$CD,$DD,$AD,$BD,$8D,$9D
.byte $7E,$6E,$5E,$4E,$3E,$2E,$1E,$0E,$FF,$EF,$DF,$CF,$BF,$AF,$9F,$8F
.byte $91,$81,$B1,$A1,$D1,$C1,$F1,$E1,$10,$00,$30,$20,$50,$40,$70,$60
.byte $83,$93,$A3,$B3,$C3,$D3,$E3,$F3,$02,$12,$22,$32,$42,$52,$62,$72
.byte $B5,$A5,$95,$85,$F5,$E5,$D5,$C5,$34,$24,$14,$04,$74,$64,$54,$44
.byte $A7,$B7,$87,$97,$E7,$F7,$C7,$D7,$26,$36,$06,$16,$66,$76,$46,$56
.byte $D9,$C9,$F9,$E9,$99,$89,$B9,$A9,$58,$48,$78,$68,$18,$08,$38,$28
.byte $CB,$DB,$EB,$FB,$8B,$9B,$AB,$BB,$4A,$5A,$6A,$7A,$0A,$1A,$2A,$3A
.byte $FD,$ED,$DD,$CD,$BD,$AD,$9D,$8D,$7C,$6C,$5C,$4C,$3C,$2C,$1C,$0C
.byte $EF,$FF,$CF,$DF,$AF,$BF,$8F,$9F,$6E,$7E,$4E,$5E,$2E,$3E,$0E,$1E
;
;
; End of File
;