User Tools

Site Tools


base:xmodem-receive

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

base:xmodem-receive [2015-04-17 04:34] (current)
Line 1: Line 1:
 +====== XMODEM/CRC receive ======
 +<code>
 +; XMODEM/CRC Receiver for the 65C02
 +;
 +; By Daryl Rictor & Ross Archer  Aug 2002
 +;
 +; 21st century code for 20th century CPUs (tm?)
 +
 +; A simple file transfer program to allow upload from a console device
 +; to the SBC utilizing the x-modem/CRC transfer protocol.  Requires just
 +; under 1k of either RAM or ROM, 132 bytes of RAM for the receive buffer,
 +; and 8 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 uploaded via XMODEM-CRC must be
 +; in .o64 format -- the first two bytes are the load address 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)
 +;
 +; The TASS assembler and most Commodore 64-based tools generate this
 +; data format automatically and you can transfer their .obj/.o64 output
 +; file directly.  
 +;   
 +; The only time you need to do anything special is if you have 
 +; a raw memory image file (say you want to load a data
 +; table into memory). For XMODEM you'll have to 
 +; "insert" the start address bytes to the front of the file.
 +; Otherwise, XMODEM would have no idea where to start putting
 +; the data.
 +
 +;-------------------------- The Code ----------------------------
 +;
 +; zero page variables (adjust these to suit your needs)
 +;
 +;
 +crc = $38 ; CRC lo byte  (two byte variable)
 +crch = $39 ; CRC hi byte  
 +
 +ptr = $3a ; data pointer (two byte variable)
 +ptrh = $3b ;   "    "
 +
 +blkno = $3c ; block number 
 +retry = $3d ; retry counter 
 +retry2 = $3e ; 2nd counter
 +bflag = $3f ; block flag 
 +;
 +;
 +; 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 upload routine
 +; By Daryl Rictor, July 31, 2002
 +;
 +; v0.3  tested good minus CRC
 +; v0.4  CRC fixed!!! init to $0000 rather than $FFFF as stated   
 +; v0.5  added CRC tables vs. generation at run time
 +; v 1.0 recode for use with SBC2
 +; v 1.1 added block 1 masking (block 257 would be corrupted)
 +
 + *= $7B00 ; Start of program (adjust to your needs)
 +;
 +XModem 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
 + lda #$00 ;
 + sta crc ;
 + sta crch ; init CRC value
 + 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 Done ; 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 ldy #$02 ; 
 +CalcCrc lda Rbuff,y ; calculate the CRC for the 128 bytes of data
 + jsr UpdCrc ; could inline sub here for speed
 + iny ;
 + cpy #$82 ; 128 bytes
 + bne CalcCrc ;
 + 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
 +Done lda #ACK ; last block, send ACK and exit.
 + jsr Put_Chr ;
 + jsr Flush ; get leftover characters, if any
 + jsr Print_Good ;
 + rts ;
 +;
 +;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 +;
 +; 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 "Upload 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 "Upload Successful!"
 + .BYTE  CR, LF
 +                .byte   0
 +;
 +;
 +;======================================================================
 +;  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
 +;=========================================================================
 +;
 +;
 +;  CRC subroutines 
 +;
 +;
 +UpdCrc 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
 +        rts
 +;
 +; 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)
 + *= $7D00
 +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)
 + *= $7E00
 +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
 +;
 +</code>
base/xmodem-receive.txt ยท Last modified: 2015-04-17 04:34 (external edit)