Page 1 of 1

X16 XMODEM (work in progress, tribute to Ward)

Posted: Mon Oct 14, 2024 6:45 pm
by voidstar
Ward Christensen recently passed away. Co-inventor/developer of the microcomputer BBS concept, and also developer of XMODEM (sort of a "poor man's" alternative to KERMIT). So I've been thinking more about how to do an xmodem implementation for the X16. I know it's been done on the C64 (and probably some Apple][ terminal software). I haven't had time yet to fully look into this - but a friend recently provided the following notes on an approach, if anyone else is inclined to tug on this... we could probably incorporate the general workflow in BASTERM (a BASIC version of this, POKE'd of of course but inlined with regular text-mode interactions)

***********************************************************
Provided by CommodoreJohn in Oct. 2024 (based on his earlier C64 6502 work)

(Apologies for the idiosyncratic approach to commenting, I was in A
Mood when I wrote this...)

; SwiftLink XMODEM PRG loader
; Lives at $CF00 - SYS 52992 to activate.

.START $cf00
.ORG $cf00

; SwiftLink base address.
sl .SET $de00
; Kernal CHROUT entry point.
chrout: .SET $ffd2

; XMODEM procedure:
; Receive SOH
; Receive block #
; Receive ~block #
; Receive 128 bytes
; Receive checksum
; Send ACK, repeat
;
; If EOT (or anything but SOH) is received, transfer is over.
; Block # sequence is not checked; as long as the checksums add
; up, we consider the transfer to be going as planned and
; continue ACK-ing until a non-SOH packet is received.
; Status values:
; Start of transmission
; Address LSB received
; Address received

start: ; Initialize SwiftLink/program
; state.
lda #$0b ; Polled I/O, no parity.
sta status ; Zero bits 6-7 of status byte.
usrinit: ; Entry point for USR version.
sta sl+2
lda #$1f ; 8 bits, 1 stop bit, 38.4 Kbaud (?)
sta sl+3
ldx #$15 ; Send inital NAK.
jsr sendbyte ; Initiate transfer and begin main loop.
loop:
jsr waitbyte ; Time for alphabet soup - get the letter!
dex ; Was it SOH?
bne done ; If not, we're done, for one reason or
; another.
stx chksum ; Clear checksum.
jsr waitbyte ; Time for number soup - get the number!
; Joke's on you, we don't care about the number!
jsr waitbyte ; Time for antinumber soup - get the
; antinumber!
; Don't combine the number and the antinumber, to
; prevent explosions.
ldy #$80 ; Start the count, Count!
packetloop:
jsr waitbyte ; Time for byte soup - take a byte!
bit status ; What's the status, Gladys?
bvc ptrbyte ; If status = $00/$80, we don't have
; the pointer yet. Otherwise, this is a
; normal data byte.
ptr stx $0100 ; So store it in place.
inc ptr+1 ; Increment the pointer.
bne noverflow ; If we need to,
inc ptr+2 ; increment the pointer MSB.
noverflow: ; Update the checksum and continue.
clc
txa
adc chksum ; Sum the Chex.
sta chksum ; Put it back in the box.
lda #$08 ; Watch the receive-buffer flag again.
dey ; ONE byte, ah!
bne packetloop
; ONE HUNDRED TWENTY-EIGHT bytes, ah! ah! ah! ah! ...
jsr waitbyte ; It's Chexum, part of this complete
; breakfast!
cpx chksum ; Check the Chex.
bne badchk ; Uh-oh, this Chex was packed by
; volume, not by weight.
lda #$23 ; Show our progress.
jsr chrout
ldx #$06 ; Bill the Cat, standing by...
jsr sendbyte ; ACK!
bne loop ; And continue.
done: ; Transfer is finished; print
; newline and return.
lda #$0d
jmp chrout ; Tail-call!
ptrbyte: ; Byte is either the LSB or MSB of the
; load address.
sec
bmi ptrmsb ; If status = $80, we already have
; the LSB.
stx ptr+1 ; Save the LSB.
; Falling through shouldn't matter here.
ptrmsb:
stx ptr+2 ; Save the MSB.
ror status ; Update the status.
bmi noverflow ; Continue.

badchk: ; Checksum didn't match; request
; a re-send.
lda ptr+1 ; Decrement the pointer by $80.
eor #$80 ; Chintzy, but saves us a CLC.
sta ptr+1
bpl badnak
dec ptr+2
badnak:
lda #$58 ; Show our lack of progress.
jsr chrout
ldx #$15 ; Send a NAK.
jsr sendbyte
bne loop ; And try again...

; UART routines.
waitbyte: ; Wait for and fetch a byte from the receive buffer.
bit sl+1 ; Is it soup yet?
beq waitbyte ; NO!
ldx sl ; It's soup!
rts ; (kids all come running)

sendbyte: ; Wait on and store a byte to the transmit buffer.
lda #$10 ; Watch the transmit-buffer flag.
txl
bit sl+1 ; Is it still soup?
beq txl ; YES!
stx sl ; It's no longer soup!
lsr ; Watch the receive-buffer flag
again. rts ; (kids exit in film reverse)

; Secondary functionality: USR(address) loads to a specified location,
; treating the "pointer" packet as a normal 128-byte payload packet.
usr jsr $b1aa ; Convert the USR argument to a
; 16-bit integer.
sty ptr+1 ; Save the LSB.
sta ptr+2 ; Save the MSB.
lda #$c0 ; Set the status.
sta status
lda #$0b ; Prep to drop back into the normal
; setup routine.
jmp usrinit

; Storage locations.
chksum: .db $00
status: .db $00