; XMODEM/CRC Receiver for AVR ; ; 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. Windows HyperTerminal worked quite well! ; ; (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. ;************************************************************************** ; ;-------------------------- The Code ---------------------------- ; ; Register usage ; (these are repurposed from the Main program and will generate ; assembler warnings) ; .def crc = R22 ;\ CRC lo byte (two byte variable) .def crch = R23 ;/ CRC hi byte .def blkno = R24 ; block number .def retry = R6 ; retry counter .def retry2 = R25 ; 2nd counter .def bflag = R7 ; block flag .def zsav = R8 ; Z save loc .def zsavh = R9 ; .def rzsav = R10 ; RAMPZ save ; ; ; non-zero page variables and buffers ; ; .equ Rbuff = 0x0200 ; temp 132 byte receive buffer ; ; XMODEM Control Character Constants .equ SOH = 0x01 ; start block .equ EOT = 0x04 ; end of text marker .equ ACK = 0x06 ; good block acknowledged .equ NAK = 0x15 ; bad block acknowledged .equ CAN = 0x18 ; cancel (not standard, not supported) .equ CR = 0x0d ; carriage return .equ LF = 0x0a ; line feed .equ ESC = 0x1b ; ESC to exit ; ;^^^^^^^^^^^^^^^^^^^^^^ Start of Program ^^^^^^^^^^^^^^^^^^^^^^ ; ; Xmodem/CRC upload routine ; By Daryl Rictor, Oct 30, 2012 ; ; v 1.1 2nd release - 48K Rom size ; XModem: clr zsav ldi i, 0x40 mov zsavh, i ; start of flash XModem1: mov rzsav, zero out rampz, zero ; start on bank zero ldi xl, 0x00 ldi xh, 0x10 ; Temp RAM page buffer ldi yh, high(rbuff) rcall PrintMsg ; send prompt and info ldi blkno, 0x01 ; set block # to 1 mov bflag, blkno ; set flag to get address from block 1 StartCrc: ldi i, 'C' ; "C" start with CRC mode rcall Put_Chr ; send it ldi retry2, 0xff ; set loop counter for ~3 sec delay clr crc clr crch ; init CRC value rcall GetByte ; wait for input brcs GotByte ; byte received, process it brcc StartCrc ; resend "C" StartBlk: ldi retry2, 0xff ; set loop counter for ~3 sec delay clr crc ; clr crch ; init CRC value rcall GetByte ; get first byte of block brcc StartBlk ; timed out, keep waiting... GotByte: cpi i, ESC ; quitting? brne GotByte1 ; no jmp Reset ; YES - do BRK or change to RTS if desired GotByte1: cpi i, SOH ; start of block? breq BegBlk ; yes cpi i, EOT ; brne BadCrc ; Not SOH or EOT, so flush buffer & send NAK rjmp Done ; EOT - all done! BegBlk: clr yl ; byte counter GetBlk: ldi retry2, 0xff ; 3 sec window to receive characters GetBlk1: rcall GetByte ; get next character brcc BadCrc ; chr rcv error, flush and send NAK GetBlk2: st Y+, i ; (buff) good char, save it in the rcv buffer cpi yl, 0x84 ; <01> <128 bytes> brne GetBlk ; get 132 characters clr yl ; ld i, Y+ ; (buff) get block # from buffer cp i, blkno ; compare to expected block # breq GoodBlk1 ; matched! rcall Print_Err ; Unexpected block number - abort rcall Flush ; mismatched - flush buffer and then do BRK jmp Reset ; unexpected block # - fatal error - BRK or RTS GoodBlk1: ldi j, 0xff eor i, j ; 1's comp of block # ld j, Y cp i, j ; compare with expected 1's comp of block # breq GoodBlk2 ; matched! write_err: rcall Print_Err ; Unexpected block number - abort rcall Flush ; mismatched - flush buffer and then do BRK jmp Reset ; bad 1's comp of block# GoodBlk2: ldi yl, 0x02 ; CalcCrc: ld i, Y+ ; calculate the CRC for the 128 bytes of data rcall UpdCrc ; could inline sub here for speed cpi yl, 0x82 ; 128 bytes brne CalcCrc ; ld i, Y+ ; get hi CRC from buffer cp i, crch ; compare to calculated hi CRC brne BadCrc ; bad crc, send NAK ld i, Y ; get lo CRC from buffer cp i, crc ; compare to calculated lo CRC breq GoodCrc ; good CRC BadCrc: rcall Flush ; flush the input port ldi i, NAK ; rcall Put_Chr ; send NAK to resend block rjmp StartBlk ; start over, get the block again GoodCrc: ldi yl, 0x02 ; Write1: ld i, Y+ ; move buffer to RAM Holding Area st X+, i cpi yl, 0x82 ; brne Write1 tst xl brne IncBlk ; if not zero, get next block ; write 256 bytes to Flash movw zl, zsav ; restore flash address out rampz, rzsav ; and bank address ldi xl, 0x00 ldi xh, 0x10 ; start of buffer cpi blkno, 0x02 ; first page write, don't test zh breq write11 cpi zh, 0x00 ; exceeded flash space? brne write11 mov rzsav, one out rampz, rzsav Write11: in j, sreg ; save sreg cli ; disable irq's fploop: ld r0, X+ ld r1, X+ stbf1: in i, spmcsr ; get status andi i, 0x01 ; mask spmen brne stbf1 ; wait for spm to complete out spmcsr, one ; enable SPM only - write to int buffer spm adiw zl, 0x02 ; inc Z pointer (word pointer) tst zl brne fploop ; store all 256 bytes 128 words dec zh ; back to current page erase1: in i, spmcsr ; get status andi i, 0x01 ; mask spmen brne erase1 ; wait for spm to complete ldi i, 0x03 ; SPM En + PGERS (erase page) out spmcsr, i spm write2: in i, spmcsr ; get status andi i, 0x01 ; mask spmen brne write2 ; wait for spm to complete ldi i, 0x05 ; SPM En + PGWRT (write page) out spmcsr, i spm inc zh ; go to next page write3: in i, spmcsr ; get status andi i, 0x01 ; mask spmen brne write3 ; wait for spm to complete ldi i, 0x11 ; SPM En + RWWSRE (Enable RWW reads) out spmcsr, i spm movw zsav, zl ; save page address in rzsav, rampz ; save bank address ldi xl, 0x00 ldi xh, 0x10 ; start of buffer out sreg, j ; restore sreg & int state IncBlk: inc blkno ; done. Inc the block # ldi i, ACK ; send ACK call Put_Chr ; rjmp StartBlk ; get next block Done: ldi i, ACK ; last block, send ACK and exit. rcall Put_Chr ; rcall Flush ; get leftover characters, if any rcall Print_Good ; out rampz, zero ; reset rampz to zero ret ; ; ;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; ; subroutines ; ; ; GetByte: ; wait for chr input and cycle timing loop clr retry ; set low value of timing loop StartCrcLp: rcall Get_chr ; get chr from serial port, don't wait brcs GetByte1 ; got one, so exit ldi j, 0x24 ; delay loop getb2234: lpm i, Z dec j brne getb2234 ; delay dec retry ; no character received, so dec counter brne StartCrcLp ; dec retry2 ; dec hi byte of counter brne StartCrcLp ; look for character again clc ; if loop times out, CLC, else SEC and return GetByte1: ret ; with character in "A" ; Flush: ; flush receive buffer ldi retry2, 0x70 ; flush until empty for ~1 sec. Flush1: rcall GetByte ; read the port brcs Flush ; if chr recvd, wait for another ret ; else done ; PrintMsg: in rzsav, rampz out rampz, one ldi zh, high(Msg*2) ; PRINT starting message ldi zl, low(Msg*2) ; PrtMsg1: elpm i, Z+ tst i breq PrtMsg2 rcall Put_Chr rjmp PrtMsg1 PrtMsg2: out rampz, rzsav ret Msg: .db "Begin XMODEM/CRC transfer. Press to abort..." .db CR, LF, 0x00, 0x00 ; Print_Err: in rzsav, rampz out rampz, one ldi zh, high(ErrMsg*2) ; PRINT Error message ldi zl, low(ErrMsg*2) ; PrtErr1: elpm i, Z+ tst i breq PrtErr2 rcall Put_Chr rjmp PrtErr1 PrtErr2: out rampz, rzsav ret ErrMsg: .db "Upload Error! " .db CR, LF, 0x00, 0x00 ; Print_Good: in rzsav, rampz out rampz, one ldi zh, high(GoodMsg*2) ; PRINT Good Transfer message ldi zl, low(GoodMsg*2) ; Prtgood1: elpm i, Z+ tst i breq Prtgood2 rcall Put_Chr rjmp Prtgood1 Prtgood2: out rampz, rzsav ret GoodMsg: .db "Upload Successful!" .db CR, LF, 0x00, 0x00 ; ; ;====================================================================== ; 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. ; ; input chr from USART (no waiting) ; Get_Chr: clc ; no chr present lds i, ucsr0a ; get Serial port status sbrs i, RXC0 ; mask rcvr full bit rjmp Get_Chr2 ; if not chr, done lds i, udr0 ; else get chr sec ; and set the Carry Flag Get_Chr2: ret ; done ; ; output to OutPut Port ; Put_Chr: lds j, ucsr0a ; sbrs j, udre0 rjmp Put_chr ; is tx buffer empty sts udr0, i ; put character to Port ret ; done ;========================================================================= ; ; ; CRC subroutines ; ; UpdCrc: eor i, crch ; Quick CRC computation with lookup tables in rzsav, rampz out rampz, one ldi zl, low(CRCHI*2) ; updates the two bytes at crc & crc+1 ldi zh, high(CRCHI*2) ; with the byte send in the "A" register add zl, i ; with the byte send in the "A" register adc zh, zero elpm crch, Z eor crch, crc dec zh ; assumes crclo is 256 bytes lower in mem elpm crc, Z out rampz, rzsav ret ; low byte CRC lookup table crclo: .db 0x00,0x21,0x42,0x63,0x84,0xA5,0xC6,0xE7,0x08,0x29,0x4A,0x6B,0x8C,0xAD,0xCE,0xEF .db 0x31,0x10,0x73,0x52,0xB5,0x94,0xF7,0xD6,0x39,0x18,0x7B,0x5A,0xBD,0x9C,0xFF,0xDE .db 0x62,0x43,0x20,0x01,0xE6,0xC7,0xA4,0x85,0x6A,0x4B,0x28,0x09,0xEE,0xCF,0xAC,0x8D .db 0x53,0x72,0x11,0x30,0xD7,0xF6,0x95,0xB4,0x5B,0x7A,0x19,0x38,0xDF,0xFE,0x9D,0xBC .db 0xC4,0xE5,0x86,0xA7,0x40,0x61,0x02,0x23,0xCC,0xED,0x8E,0xAF,0x48,0x69,0x0A,0x2B .db 0xF5,0xD4,0xB7,0x96,0x71,0x50,0x33,0x12,0xFD,0xDC,0xBF,0x9E,0x79,0x58,0x3B,0x1A .db 0xA6,0x87,0xE4,0xC5,0x22,0x03,0x60,0x41,0xAE,0x8F,0xEC,0xCD,0x2A,0x0B,0x68,0x49 .db 0x97,0xB6,0xD5,0xF4,0x13,0x32,0x51,0x70,0x9F,0xBE,0xDD,0xFC,0x1B,0x3A,0x59,0x78 .db 0x88,0xA9,0xCA,0xEB,0x0C,0x2D,0x4E,0x6F,0x80,0xA1,0xC2,0xE3,0x04,0x25,0x46,0x67 .db 0xB9,0x98,0xFB,0xDA,0x3D,0x1C,0x7F,0x5E,0xB1,0x90,0xF3,0xD2,0x35,0x14,0x77,0x56 .db 0xEA,0xCB,0xA8,0x89,0x6E,0x4F,0x2C,0x0D,0xE2,0xC3,0xA0,0x81,0x66,0x47,0x24,0x05 .db 0xDB,0xFA,0x99,0xB8,0x5F,0x7E,0x1D,0x3C,0xD3,0xF2,0x91,0xB0,0x57,0x76,0x15,0x34 .db 0x4C,0x6D,0x0E,0x2F,0xC8,0xE9,0x8A,0xAB,0x44,0x65,0x06,0x27,0xC0,0xE1,0x82,0xA3 .db 0x7D,0x5C,0x3F,0x1E,0xF9,0xD8,0xBB,0x9A,0x75,0x54,0x37,0x16,0xF1,0xD0,0xB3,0x92 .db 0x2E,0x0F,0x6C,0x4D,0xAA,0x8B,0xE8,0xC9,0x26,0x07,0x64,0x45,0xA2,0x83,0xE0,0xC1 .db 0x1F,0x3E,0x5D,0x7C,0x9B,0xBA,0xD9,0xF8,0x17,0x36,0x55,0x74,0x93,0xB2,0xD1,0xF0 ; hi byte CRC lookup table crchi: .db 0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x81,0x91,0xA1,0xB1,0xC1,0xD1,0xE1,0xF1 .db 0x12,0x02,0x32,0x22,0x52,0x42,0x72,0x62,0x93,0x83,0xB3,0xA3,0xD3,0xC3,0xF3,0xE3 .db 0x24,0x34,0x04,0x14,0x64,0x74,0x44,0x54,0xA5,0xB5,0x85,0x95,0xE5,0xF5,0xC5,0xD5 .db 0x36,0x26,0x16,0x06,0x76,0x66,0x56,0x46,0xB7,0xA7,0x97,0x87,0xF7,0xE7,0xD7,0xC7 .db 0x48,0x58,0x68,0x78,0x08,0x18,0x28,0x38,0xC9,0xD9,0xE9,0xF9,0x89,0x99,0xA9,0xB9 .db 0x5A,0x4A,0x7A,0x6A,0x1A,0x0A,0x3A,0x2A,0xDB,0xCB,0xFB,0xEB,0x9B,0x8B,0xBB,0xAB .db 0x6C,0x7C,0x4C,0x5C,0x2C,0x3C,0x0C,0x1C,0xED,0xFD,0xCD,0xDD,0xAD,0xBD,0x8D,0x9D .db 0x7E,0x6E,0x5E,0x4E,0x3E,0x2E,0x1E,0x0E,0xFF,0xEF,0xDF,0xCF,0xBF,0xAF,0x9F,0x8F .db 0x91,0x81,0xB1,0xA1,0xD1,0xC1,0xF1,0xE1,0x10,0x00,0x30,0x20,0x50,0x40,0x70,0x60 .db 0x83,0x93,0xA3,0xB3,0xC3,0xD3,0xE3,0xF3,0x02,0x12,0x22,0x32,0x42,0x52,0x62,0x72 .db 0xB5,0xA5,0x95,0x85,0xF5,0xE5,0xD5,0xC5,0x34,0x24,0x14,0x04,0x74,0x64,0x54,0x44 .db 0xA7,0xB7,0x87,0x97,0xE7,0xF7,0xC7,0xD7,0x26,0x36,0x06,0x16,0x66,0x76,0x46,0x56 .db 0xD9,0xC9,0xF9,0xE9,0x99,0x89,0xB9,0xA9,0x58,0x48,0x78,0x68,0x18,0x08,0x38,0x28 .db 0xCB,0xDB,0xEB,0xFB,0x8B,0x9B,0xAB,0xBB,0x4A,0x5A,0x6A,0x7A,0x0A,0x1A,0x2A,0x3A .db 0xFD,0xED,0xDD,0xCD,0xBD,0xAD,0x9D,0x8D,0x7C,0x6C,0x5C,0x4C,0x3C,0x2C,0x1C,0x0C .db 0xEF,0xFF,0xCF,0xDF,0xAF,0xBF,0x8F,0x9F,0x6E,0x7E,0x4E,0x5E,0x2E,0x3E,0x0E,0x1E ; ; ; End of File ;