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
;