Intel HEX File Downloader by Ross Archer
[Up to Source Code Repository]
Intel HEX File Downloader
Ross Archer, 9 February 2001
This program is freeware. Use it, modify it, ridicule it in public, or whatever, so long as authorship credit (blame?) is given somewhere to me for the base program.
Although this program has worked on two different 65C02-based boards, with TASM output files, I make no warranty of fitness or anything else. It's free -- so no matter what, happens you get your money's worth.
Feel free to write me at dogbert@mindless.com if you are having difficulties or have suggestions for improvement. This is still a work in progress.
To customize for your 6551-equipped 6502 SBC
[UK users have dispensation to customise it instead.]
; ($7FFB, $7FFA): NMI RAM vector ; ($7FFF, $7FFE): IRQ RAM vector ; (User program may not set a new RESET vector, or we could load ; an unrecoverable program into SRAM if battery backed, which would ; kill the system until the RAM was removed!) ; ; ; Macros for converting 16-bit values to high and low bytes #define hi(x) (x >> 8) #define lo(x) (x & $FF) ; ; 6551 ACIA equates for serial I/O ; #define ACIA_BASE $F000 ; This is where the 6551 ACIA starts #define SDR ACIA_BASE ; RX'ed bytes read, TX bytes written, here #define SSR ACIA_BASE+1 ; Serial data status register. A write here ; causes a programmed reset. #define SCMD ACIA_BASE+2 ; Serial command reg. () #define SCTL ACIA_BASE+3 ; Serial control reg. () ; Quick n'dirty assignments instead of proper definitions of each parameter ; "ORed" together to build the desired flexible configuration. We're going ; to run 19200 baud, no parity, 8 data bits, 1 stop bit. Period. For now. ; #define SCTL_V 00011111b ; 1 stop, 8 bits, 19200 baud #define SCMD_V 00001011b ; No parity, no echo, no tx or rx IRQ, DTR* #define TX_RDY 00010000b ; AND mask for transmitter ready #define RX_RDY 00001000b ; AND mask for receiver buffer full ; ; Zero-page storage #define DPL $00 ; data pointer (two bytes) #define DPH $01 ; high of data pointer #define RECLEN $02 ; record length in bytes #define START_LO $03 #define START_HI $04 #define RECTYPE $05 #define CHKSUM $06 ; record checksum accumulator #define DLFAIL $07 ; flag for download failure #define TEMP $08 ; save hex value ; "Shadow" RAM vectors (note each is $8000 below the actual ROM vector) #define NMIVEC $7FFA ; write actual NMI vector here #define IRQVEC $7FFE ; write IRQ vector here #define ENTRY_POINT $0200 ; where the RAM program MUST have its first instruction org $F800 ; START sei ; disable interrupts cld ; binary mode arithmetic ldx #$FF ; Set up the stack pointer txs ; " lda #hi(START) ; Initialiaze the interrupt vectors sta NMIVEC+1 ; User program at ENTRY_POINT may change sta IRQVEC+1 ; these vectors. Just do change before enabling lda #lo(START) ; the interrupts, or you'll end up back in the d/l monitor. sta NMIVEC sta IRQVEC jsr INITSER ; Set up baud rate, parity, etc. ; Download Intel hex. The program you download MUST have its entry ; instruction (even if only a jump to somewhere else) at ENTRY_POINT. HEXDNLD lda #0 sta DLFAIL ;Start by assuming no D/L failure jsr PUTSTRI .byte 13,10,13,10 .byte "Send 6502 code in" .byte " Intel Hex format" .byte " at 19200,n,8,1 ->" .byte 13,10 .byte 0 ; Null-terminate unless you prefer to crash. HDWRECS jsr GETSER ; Wait for start of record mark ':' cmp #':' bne HDWRECS ; not found yet ; Start of record marker has been found jsr GETHEX ; Get the record length sta RECLEN ; save it sta CHKSUM ; and save first byte of checksum jsr GETHEX ; Get the high part of start address sta START_HI clc adc CHKSUM ; Add in the checksum sta CHKSUM ; jsr GETHEX ; Get the low part of the start address sta START_LO clc adc CHKSUM sta CHKSUM jsr GETHEX ; Get the record type sta RECTYPE ; & save it clc adc CHKSUM sta CHKSUM lda RECTYPE bne HDER1 ; end-of-record ldx RECLEN ; number of data bytes to write to memory ldy #0 ; start offset at 0 HDLP1 jsr GETHEX ; Get the first/next/last data byte sta (START_LO),y ; Save it to RAM clc adc CHKSUM sta CHKSUM ; iny ; update data pointer dex ; decrement count bne HDLP1 jsr GETHEX ; get the checksum clc adc CHKSUM bne HDDLF1 ; If failed, report it ; Another successful record has been processed lda #'#' ; Character indicating record OK = '#' sta SDR ; write it out but don't wait for output jmp HDWRECS ; get next record HDDLF1 lda #'F' ; Character indicating record failure = 'F' sta DLFAIL ; download failed if non-zero sta SDR ; write it to transmit buffer register jmp HDWRECS ; wait for next record start HDER1 cmp #1 ; Check for end-of-record type beq HDER2 jsr PUTSTRI ; Warn user of unknown record type .byte 13,10,13,10 .byte "Unknown record type $", .byte 0 ; null-terminate unless you prefer to crash! lda RECTYPE ; Get it sta DLFAIL ; non-zero --> download has failed jsr PUTHEX ; print it lda #13 ; but we'll let it finish so as not to jsr PUTSER ; falsely start a new d/l from existing lda #10 ; file that may still be coming in for jsr PUTSER ; quite some time yet. jmp HDWRECS ; We've reached the end-of-record record HDER2 jsr GETHEX ; get the checksum clc adc CHKSUM ; Add previous checksum accumulator value beq HDER3 ; checksum = 0 means we're OK! jsr PUTSTRI ; Warn user of bad checksum .byte 13,10,13,10 .byte "Bad record checksum!",13,10 .byte 0 ; Null-terminate or 6502 go bye-bye jmp START HDER3 lda DLFAIL beq HDEROK ;A download failure has occurred jsr PUTSTRI .byte 13,10,13,10 .byte "Download Failed",13,10 .byte "Aborting!",13,10 .byte 0 ; null-terminate every string yada yada. jmp START HDEROK jsr PUTSTRI .byte 13,10,13,10 .byte "Download Successful!",13,10 .byte "Jumping to location $" .byte 0 ; by now, I figure you know what this is for. :) lda #hi(ENTRY_POINT) ; Print the entry point in hex jsr PUTHEX lda #lo(ENTRY_POINT) jsr PUTHEX jsr PUTSTRI .byte 13,10 .byte 0 ; stop lemming-like march of the program ctr. thru data jmp ENTRY_POINT ; jump to canonical entry point ; ; Set up baud rate, parity, stop bits, interrupt control, etc. for ; the serial port. INITSER lda #SCTL_V ; Set baud rate 'n stuff sta SCTL lda #SCMD_V ; set parity, interrupt disable, n'stuff sta SCMD rts ; ; ; SerRdy : Return SERRDY lda SSR ; look at serial status and #RX_RDY ; strip off "character waiting" bit rts ; if zero, nothing waiting. ; Warning: this routine busy-waits until a character is ready. ; If you don't want to wait, call SERRDY first, and then only ; call GETSER once a character is waiting. GETSER lda SSR ; look at serial status and #RX_RDY ; see if anything is ready beq GETSER ; busy-wait until character comes in! lda SDR ; get the character rts ; Busy wait GETHEX jsr GETSER jsr MKNIBL ; Convert to 0..F numeric asl a asl a asl a asl a ; This is the upper nibble and #$F0 sta TEMP jsr GETSER jsr MKNIBL ora TEMP rts ; return with the nibble received ; Convert the ASCII nibble to numeric value from 0-F: MKNIBL cmp #'9'+1 ; See if it's 0-9 or 'A'..'F' (no lowercase yet) bcc MKNNH ; If we borrowed, we lost the carry so 0..9 sbc #7+1 ; Subtract off extra 7 (sbc subtracts off one less) ; If we fall through, carry is set unlike direct entry at MKNNH MKNNH sbc #'0'-1 ; subtract off '0' (if carry clear coming in) and #$0F ; no upper nibble no matter what rts ; and return the nibble ; Put byte in A as hexydecascii PUTHEX pha ; lsr a lsr a lsr a lsr a jsr PRNIBL pla PRNIBL and #$0F ; strip off the low nibble cmp #$0A bcc NOTHEX ; if it's 0-9, add '0' else also add 7 adc #6 ; Add 7 (6+carry=1), result will be carry clear NOTHEX adc #'0' ; If carry clear, we're 0-9 ; Write the character in A as ASCII: PUTSER sta SDR ; write to transmit register WRS1 lda SSR ; get status and #TX_RDY ; see if transmitter is busy beq WRS1 ; if it is, wait rts ;Put the string following in-line until a NULL out to the console PUTSTRI pla ; Get the low part of "return" address (data start address) sta DPL pla sta DPH ; Get the high part of "return" address ; (data start address) ; Note: actually we're pointing one short PSINB ldy #1 lda (DPL),y ; Get the next string character inc DPL ; update the pointer bne PSICHO ; if not, we're pointing to next character inc DPH ; account for page crossing PSICHO ora #0 ; Set flags according to contents of Accumulator beq PSIX1 ; don't print the final NULL jsr PUTSER ; write it out jmp PSINB ; back around PSIX1 inc DPL ; bne PSIX2 ; inc DPH ; account for page crossing PSIX2 jmp (DPL) ; return to byte following final NULL ; ; User "shadow" vectors: GOIRQ jmp (IRQVEC) GONMI jmp (NMIVEC) GORST jmp START ; Allowing user program to change this is a mistake .org $FFFA ; start at $FFFA NMIENT .word GONMI RSTENT .word GORST IRQENT .word GOIRQ .end ; finally. das Ende.
Last page update: March 22, 2001.