{"id":45,"date":"2023-03-26T11:32:55","date_gmt":"2023-03-26T16:32:55","guid":{"rendered":"https:\/\/fortycreek.org\/?p=45"},"modified":"2023-03-27T15:40:52","modified_gmt":"2023-03-27T20:40:52","slug":"xmodem-for-z80","status":"publish","type":"post","link":"https:\/\/fortycreek.org\/index.php\/2023\/03\/26\/xmodem-for-z80\/","title":{"rendered":"XModem for Z80"},"content":{"rendered":"\n<p>I wanted to implement a XModem to incorporate into the Z80 monitor program for my CPUVille Z80.  It needed to be small to fit in the 2K ROM monitor, the whole thing ends up being 264 additional bytes.  Here is the code for that:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>; based on 6502 version http:\/\/www.6502.org\/source\/io\/xmodem\/xmodem-receive.txt\n; re-coded by Tom Lovie 2023-03-23\n; sub write_string and put_chr are already in ROM\n\nmonitor_warm_start:     equ     0450h\n\nSOH     equ 01h         ; start block\nEOT     equ 04h         ; end of text marker\nACK     equ 06h         ; good block acknowledged\nNAK     equ 15h         ; bad block acknowledged\nCAN     equ 18h         ; cancel (not standard, not supported)\nCR      equ 0dh         ; carriage return\nLF      equ 0ah         ; line feed\nESC     equ 1bh         ; ESC to exit\n\nrbuff:  equ 0c00h       ; page aligned receive buffer\nrbuffp: equ 0ch         ; page of receive buffer\nsbuff:  equ 1000h       ; storage buffer to place received data\nsbuffp: equ 10h         ; storage buffer page\n\n        org 0800h\n\n                call XModem\n                jp monitor_warm_start\n\nXModem:\n                ld hl,msg\n                call write_string\n                ld hl,sbuff\n                ld (ptr),hl     ; initialize the storage pointer\n                ld a,01h\n                ld (blkno),a    ; start at block number 1\nStartX:         ld a,NAK        ; Start in CRC mode - no fallback\n                call put_chr    ; send it\n                ld a,00h        ; loop counter in a\n                call GetByte    ; try to get a byte\n                jr c,GotByte\n                jr nc,StartX    ; if not try again\n\nStartBlk:       ld a,00h        ; loop counter in a\n                call GetByte    ; try to get a byte\n                jr nc,StartBlk\nGotByte:        cp ESC          ; want to quit\n                ret z\n                cp SOH          ; start of block?\n                jr z,BegBlk\n                cp EOT          ; end of text\n                jr nz,BadCrc\n                jr Done\nBegBlk:         ld hl,rbuff     ; start hl at the receive buffer\nGetBlk:         ld a,00h        ; 3 second window to receive char\nGetBlk1:        call GetByte    ; get next char\n                jr nc,BadCrc    ; just sending NAK\nGetBlk2:        ld (hl),a       ; store the character in buffer\n                inc hl          ; increment the buffer\n                ld a,83h\n                cp l            ; &lt;01&gt;&lt;FE&gt;&lt;128 bytes&gt;&lt;CHKSUM&gt;\n                jr nz,GetBlk    ; get 131 characters (0x83)\n                ld l,00h        ; start at beginning of buffer \n                ld a,(blkno)    ; actual block number\n                cp (hl)         ; sent block number\n                jr z,GoodBlk1   ; block number is expected\n                jr ErrorOut     ; error out of the xmodem routine\nGoodBlk1:       xor 0ffh        ; compliment the actual block no\n                inc hl\n                cp (hl)         ; compare to second byte\n                jr z,GoodBlk2   ; block number compliment \n                jr ErrorOut     ; error out of the xmodem routine\nGoodBlk2:       ld h,rbuffp     ; point hl at the receive buffer\n                ld l,81h        ; last byte\n                ld a,00h        ; initialize a\nCalcCrc:        add (hl)        ; compute running total start 82h\n                dec l\n                jr nz,CalcCrc\n                add a,(hl)      ; do the block number as well\n                inc a           ; because blockno + cpl = 255.\n                ld l,82h        ; (hl) is the checksum\n                cp (hl)\n                jr z,GoodCrc\nBadCrc:         call Flush      ; flush serial buffer\n                ld a,NAK        ; and send\n                call put_chr    ; a NAK\n                jr StartBlk     ; restart the block\nGoodCrc:        ld l,02h        ; hl is now pointing data\n                ld de,(ptr)     ; de is now pointing storage\n                ld c,80h        ; 128 bytes\n                ld b,00h        ;\n                ldir            ; copy the block\n                ld (ptr),de     ; store the current pos\n                ld a,(blkno)    ; load the block number\n                inc a\n                ld (blkno),a    ; store the block number back\n                ld a,ACK        ; send ACK\n                call put_chr\n                jp StartBlk     ; get next block\nDone:           ld a,ACK\n                call put_chr\n                call Flush\n                ld hl,good      ; load success message\n                call write_string\n                ret\n\nErrorOut:       ld hl,err       ; print error message and exit\n                call write_string\n                call Flush      ; discard remaining buffer\n                ret             ; return after fatal error\n\n; subroutine to wait a set amount of time to get a byte\n; Byte will be in A, destroys BC (delay loop)\nGetByte:        ld b,a\n                ld c,a\nGetByteLoop:    call get_chr\n                ret c           ; return if got chr (carry set)\n                dec c\n                jr nz,GetByteLoop\n                dec b\n                jr nz,GetByteLoop       ; delay loop\n                or a                    ; clear carry flag\n                ret\n\n; subroutine to flush the receive buffer\n; destroys A\nFlush:          ld a,80h\n                call GetByte\n                jr c,Flush\n                ret\n\n\n;Get one byte from the serial port if available.\n;Returns with byte in A reg with carry flag set, if carry flag clear means no character available\nget_chr:        or      a           ;clear carry flag\n                in      a,(3)       ;get status\n                and     002h        ;check RxRDY bit\n                ret     z           ;not ready, quit\n                in      a,(2)       ;get char\n                scf                 ;set carry flag we got a char\n                ret\n\nptr:    dfw 0\nblkno:  dfb 0\nerr:    dfb \"Up Err!\",CR,LF,0\ngood:   dfb \"Up Ok!\",CR,LF,0\nmsg:    dfb CR,LF,\"X\/CSUM &lt;Esc&gt; to q\",CR,LF,0\n\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I wanted to implement a XModem to incorporate into the Z80 monitor program for my CPUVille Z80. It needed to be small to fit in the 2K ROM monitor, the whole thing ends up being 264 additional bytes. Here is the code for that:<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-45","post","type-post","status-publish","format-standard","hentry","category-z80"],"_links":{"self":[{"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/posts\/45","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/comments?post=45"}],"version-history":[{"count":2,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/posts\/45\/revisions"}],"predecessor-version":[{"id":49,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/posts\/45\/revisions\/49"}],"wp:attachment":[{"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/media?parent=45"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/categories?post=45"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fortycreek.org\/index.php\/wp-json\/wp\/v2\/tags?post=45"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}