;++ ; ; PIC16F84 chip programmer. ; ; By John Wilson . ; ; Copyright (C) 1999 by D Bit. All rights reserved. ; ; 02/28/99 JMBW Created. ; 03/04/99 JMBW Basic functions work for PIC16F84. ; 03/06/99 JMBW /INHX8M, /INHX8S, /INHX32 switches work. ; ;-- .radix 8 ; locals ; fbufl= 1024d ;.HEX file buffer length ; bel= 7 lf= 12 cr= 15 ; rs232_base=word ptr 0400h ;base I/O addr of each COM port timer_low=word ptr 046Ch ;low word of BIOS time ; ; INS8250 bits that we care about: break= 100 ;break (in LCR), 1 => +12, 0 => -12 rts= 2 ;request to send (in MCR), 1 => -12, 0 => +12 dtr= 1 ;data terminal ready (in MCR), 1 => +12, 0 => -12 cts= 20 ;clear to send (in MSR), 1 => -12, 0 => +12 ; callr macro dest ;;call and return jmp &dest endm ; icall macro dest ;;indirect call call word ptr ds:&dest endm ; ascic macro lab,str ;;counted ASCII string with label and local x &lab db x,&str,cr,lf x= $-&lab-1 endm ; ; Macro to define an entry in a keyword table (for GETW/TBLUK): ; db length to match ; db total length ; db string ; dw addr ; kw macro text,addr kh= 0 ki= 0 irpc kc,&text ki= ki+1 ifidn <&kc>,<-> kh= ki endif endm ;; irpc ife kh .err No hyphen in string: &text exitm endif ;; ife kh db kh-1,ki-1 ;;len to match, total len irpc kc,&text ;;keyword text ifdif <&kc>,<-> db '&kc' endif endm ;; irpc dw &addr endm ; ; It's a .COM file so everything is in one segment (so don't enlarge FBUFL too ; much or it won't fit). ; ; Throughout program: ; DF clear (string instructions move forwards) ; DS same as CS ; ES scratch, generally used to point at BUFSEG (rarely touched otherwise) ; code segment assume cs:code org 100h ; start: cld ;DF=0 mov dx,offset banner ;point at banner mov ah,09h ;func=print int 21h cmp sp,offset pdl ;space for stack? jb nomem ;no mov sp,offset pdl ;yes, back it up mov bx,sp ;copy mov cl,4 ;shift count shr bx,cl ;find # paragraphs to keep mov ah,4Ah ;func=setblock int 21h jnc jcl1 ;success nomem: mov si,offset allerr ;memory alloc error fatal: lodsb ;read length byte cbw ;AH=0 mov dx,si ;point at string mov cx,ax ;copy length mov bx,0002h ;handle=STDERR mov ah,40h ;func=write int 21h mov ax,4C01h ;func=punt int 21h jcl1: ; parse JCL from command line call set84 ;assume part type is PIC16F84 mov si,80h ;point at it lodsb ;get length byte xor ah,ah ;AH=0 mov cx,ax ;copy @@1: call skip ;skip white space jc @@4 ;done cmp al,'/' ;switch? jne @@3 ;no, must be filename inc si ;skip dec cx call getw ;get switch name jc @@2 ;lone "/", error mov ax,offset jcltab ;point at keyword table call tbluk ;look it up jc @@2 ;invalid switch, error call ax ;handle it jmp short @@1 ;continue parsing @@2: mov si,offset badjcl ;syntax error jmp fatal @@3: ; filename call getw ;parse it jc @@2 ;huh? shouldn't happen xchg dx,ds:fnctr ;set length, get old length test dx,dx ;shouldn't be any jnz @@2 ;we already had one filename, error mov ds:fnptr,bx ;save ptr jmp short @@1 ;continue parsing @@4: ; look up COM port's base I/O addr mov bx,ds:com ;get COM port index xor ax,ax ;load 0 mov es,ax ;point at BIOS data mov ax,es:rs232_base[bx] ;look up base I/O addr of COM port test ax,ax ;if any jnz @@5 ;yes mov si,offset nxport ;nonexistent port jmp fatal @@5: ; set addr of each port we care about mov ds:port,ax ;(e.g. 3F8) save base port just for fun add ax,3 ;(e.g. 3FB) line ctrl reg mov ds:lcr,ax inc ax ;(e.g. 3FC) modem ctrl reg mov ds:mcr,ax inc ax ;(e.g. 3FE) modem status reg inc ax mov ds:msr,ax ; allocate memory mov bx,ds:bufmax ;get max offset into buf mov ax,ds:bufmax+2 add bx,1+0Fh ;find size, round to paragraph boundary adc ax,0 mov cl,4 ;shift count shr bx,cl ;convert to paragraph count shl al,cl or bh,al ;combine (must fit) mov ah,48h ;func=GETBLOCK int 21h jnc gotmem jmp nomem ;failed gotmem: mov ds:bufseg,ax ;save seg addr icall clear ;clear buffer mov al,ds:inhx8m ;sum up hex file type flags add al,ds:inhx8s add al,ds:inhx32 test al,not 1 ;should add up to 0 or 1 jz @@1 ;OK mov si,offset swtcnf ;switch conflict jmp fatal @@1: ; actually do whatever we're here to do mov al,ds:blkflg ;see if any are set or al,ds:eraflg or al,ds:rdflg or al,ds:vfyflg or al,ds:wrflg jnz @@2 ;got something inc byte ptr ds:wrflg ;default cmd is /WRITE @@2: test byte ptr ds:eraflg,-1 ;/ERASE? jz @@3 icall erase ;erase part @@3: test byte ptr ds:blkflg,-1 ;/BLANK? jz @@4 icall verify ;verify part against cleared buffer @@4: test byte ptr ds:wrflg,-1 ;/WRITE? jz @@5 call load ;load file icall write ;write to part @@5: test byte ptr ds:vfyflg,-1 ;/VERIFY? jz @@6 call load ;load file icall verify ;verify part against file contents @@6: test byte ptr ds:rdflg,-1 ;/READ? jz @@7 icall read ;read part into file call dump ;dump to file @@7: int 20h ; ; Routines to handle command line switches (generally just set a flag for later ; use): ; blkcmd: ; /BLANK mov byte ptr ds:blkflg,1 ;set flag ret ; irp x,<1,2,3,4> com&x: ; /COMn, set COM port where COM84 burner is attached mov word ptr ds:com,(&x-1)*2 ret endm ; IRP ; eracmd: ; /ERASE mov byte ptr ds:eraflg,1 ;set flag ret ; help: ; /?, print help message mov dx,offset jclhlp ;command line help mov ah,09h ;func=print int 21h int 20h ;happy exit, don't return ; h8mcmd: ; /INHX8M mov byte ptr ds:inhx8m,1 ;merged .HEX file ret ; h8scmd: ; /INHX8S mov byte ptr ds:inhx8s,1 ;split .HXL/.HXH files ret ; h32cmd: ; /INHX32 mov byte ptr ds:inhx32,1 ;single .HEX file with 32-bit addrs ret ; nercmd: ; /NOERASE mov byte ptr ds:nerflg,1 ;set flag ret ; rdcmd: ; /READ mov byte ptr ds:rdflg,1 ;set flag ret ; vfycmd: ; /VERIFY mov byte ptr ds:vfyflg,1 ;set flag ret ; wrcmd: ; /WRITE mov byte ptr ds:wrflg,1 ;set flag ret ; subttl command routines ;+ ; ; Load file data into buffer. ; ;- load: test byte ptr ds:inhx8s,-1 ;split? jnz @@5 ;yes mov si,offset hexext ;default extension call ifile ;open input file @@1: ; read next record call rdhex ;read next record mov al,ds:hextyp ;get record type test al,al ;data? jnz @@3 ;no mov es,ds:bufseg ;get seg addr of buf mov cl,ds:hexlen ;get # data bytes xor ch,ch ;CH=0 jcxz @@1 ;none, ignore this rec mov di,ds:hexadr ;get starting byte addr mov ax,cx ;copy length dec ax ;-1 (known NZ) add ax,di ;find addr of last byte of xfr jc @@2 ;overflow, won't fit cmp ax,ds:bufmax ;in range? ja @@2 ;no mov si,offset hexdat ;point at data rep movsb ;copy into buf jmp short @@1 ;around for more @@2: mov si,offset adrran ;addr out of range jmp fatal @@3: cmp al,04h ;setting MSW of addr? je @@1 ;yes, ignore ;;; 01=EOF, 03=segment, others=error mov bx,ds:hexhnd ;get handle test bx,bx ;STDIN? jz @@4 mov ah,3Eh ;no, func=close int 21h @@4: ret @@5: ; split input files mov si,offset hxlext ;default extension for low byte xor bx,bx ;offset +0 of each word call @@6 ;do low bytes mov si,offset hxhext ;default extension for high byte mov bx,1 ;offset +1 of each word ;callr ;do high bytes, return @@6: ; load next file -- SI=default ext, BX=even/odd flag push bx ;save even/odd flag call ifile ;open input file pop bx ;restore @@7: ; read next record push bx ;save even/odd flag call rdhex ;read next record pop bx ;restore mov al,ds:hextyp ;get record type test al,al ;data? jnz @@10 ;no mov es,ds:bufseg ;get seg addr of buf mov cl,ds:hexlen ;get # data bytes xor ch,ch ;CH=0 jcxz @@7 ;none, ignore this rec mov di,ds:hexadr ;get starting word addr add di,di ;*2 = byte addr add di,bx ;+1 if high byte mov ax,cx ;copy length dec ax ;-1 (known NZ) add ax,ax ;*2 since we're doing alternate bytes add ax,di ;find addr of last byte of xfr jc @@9 ;overflow, won't fit cmp ax,ds:bufmax ;in range? ja @@9 ;no mov si,offset hexdat ;point at data @@8: movsb ;copy a byte inc di ;skip intervening byte loop @@8 ;loop through all jmp short @@7 ;around for more @@9: mov si,offset adrran ;addr out of range jmp fatal @@10: cmp al,04h ;setting MSW of addr? je @@7 ;yes, ignore ;;; 01=EOF, 03=segment, others=error mov bx,ds:hexhnd ;get handle test bx,bx ;STDIN? jz @@11 mov ah,3Eh ;no, func=close int 21h @@11: ret ;+ ; ; Open input file (handle in HEXHND). ; ; si default extension ; ;- ifile: mov dx,ds:fnctr ;get filename length test dx,dx ;we have a file, right? jz @@1 ;no, use STDIN mov bx,ds:fnptr ;get filename pointer call defext ;form filename mov ax,3D00h ;func=open /RONLY int 21h jnc @@2 mov si,offset operr ;error msg jmp fatal @@1: mov dx,offset stdin ;print msg mov ah,09h ;func=print int 21h xor ax,ax ;handle=STDIN @@2: mov ds:hexhnd,ax ;save handle mov word ptr ds:fctr,0 ;nothing in buf yet ret ;+ ; ; Dump buffer to file(s). ; ;- dump: test byte ptr ds:inhx8s,-1 ;split? jnz @@6 ;yes mov si,offset hexext ;default extension call ofile ;open output file test byte ptr ds:inhx32,-1 ;32-bit addressing? jz @@1 ; set MSW of addr to zeros xor ax,ax ;load 0 mov ds:hexadr,ax ;addr field is meaningless mov word ptr ds:hexdat,ax ;2 bytes of addr in data field mov byte ptr ds:hexlen,2 ;2 data bytes mov byte ptr ds:hextyp,04h ;type=set high addr call wrhex ;write record @@1: mov si,ds:dmplst ;get list of ranges @@2: ; write next range (SI=DMPLST ptr) lodsw ;get a word cmp ax,-1 ;end of list? je @@7 ;yes mov ds:hexadr,ax ;save @@3: ; write next record (SI points at 2nd word of DMPLST entry) mov cx,[si] ;fetch end of range push si ;save mov si,ds:hexadr ;get byte addr sub cx,si ;find # bytes until last byte of range jc @@5 ;last rec finished this range, skip cmp cx,0Fh ;enough to do 10h bytes? jb @@4 ;no, stop short mov cx,0Fh ;at least, so take 10h (0Fh is offset of last) @@4: inc cx ;+1 to get length of range mov ds:hexlen,cl ;save length mov ds:hextyp,ch ;type=data push ds ;copy DS to ES pop es mov di,offset hexdat ;point at data field mov ds,ds:bufseg ;DS:SI points into buffer push cx ;save length rep movsb ;copy push es ;restore DS pop ds call wrhex ;write the rec pop cx ;restore length add ds:hexadr,cx ;advance ptr pop si ;restore DMPLST ptr jmp short @@3 ;do next rec of range @@5: ; finished this range pop si ;restore DMPLST ptr lodsw ;skip end of range jmp short @@2 ;go start next range, if any @@6: jmp short @@9 @@7: ; finished xor ax,ax ;load 0 mov ds:hexadr,ax ;clear addr field mov ds:hexlen,al ;no data inc ax ;+1 mov ds:hextyp,al ;type=EOF call wrhex ;write EOF rec call flush ;flush last buffer to file, if needed mov bx,ds:hexhnd ;get handle cmp bx,0001h ;STDOUT? jz @@8 mov ah,3Eh ;no, func=close int 21h @@8: ret @@9: ; split output files mov si,offset hxlext ;default extension for low byte xor bx,bx ;offset +0 of each word call @@10 ;do low bytes mov si,offset hxhext ;default extension for high byte mov bx,1 ;offset +1 of each word ;callr ;do high bytes, return @@10: ; dump next file -- SI=default ext, BX=even/odd flag push bx ;save even/odd flag call ofile ;open output file pop bx ;restore mov si,ds:dmplst ;get list of ranges @@11: ; write next range (SI=DMPLST ptr, BX=even/odd flag) lodsw ;get a word cmp ax,-1 ;end of list? je @@7 ;yes or ax,bx ;skip to odd byte if appropriate mov ds:hexad1,ax ;save @@12: ; write next record (SI points at 2nd word of DMPLST entry) mov cx,[si] ;fetch end of range push si ;save mov si,ds:hexad1 ;get byte addr sub cx,si ;find # bytes until last byte of range jc @@15 ;last rec finished this range, skip add cx,1+1 ;+1 to get length of range, +1 to round up to ;next word (OK if we get only 1/2 of last word) rcr cx,1 ;we're only doing alternate bytes (catch CF) cmp cx,10h ;enough to do 10h bytes? jb @@13 ;no, stop short mov cx,10h ;at least, so take 10h (0Fh is offset of last) @@13: mov ds:hexlen,cl ;save length mov ds:hextyp,ch ;type=data mov ax,ds:hexad1 ;get addr shr ax,1 ;find word addr mov ds:hexadr,ax ;save push ds ;copy DS to ES pop es mov di,offset hexdat ;point at data field mov ds,ds:bufseg ;DS:SI points into buffer push cx ;save length @@14: movsb ;copy a byte inc si ;skip intervening byte loop @@14 push es ;restore DS pop ds push bx ;save even/odd flag call wrhex ;write the rec pop bx ;restore pop cx ;restore length add cx,cx ;word count *2 to get change in byte addr add ds:hexad1,cx ;advance ptr pop si ;restore DMPLST ptr jmp short @@12 ;do next rec of range @@15: ; finished this range pop si ;restore DMPLST ptr lodsw ;skip end of range jmp short @@11 ;go start next range, if any ;+ ; ; Open output file (handle in HEXHND). ; ; si default extension ; ;- ofile: mov dx,ds:fnctr ;get filename length test dx,dx ;we have a file, right? jz @@1 ;no, use STDOUT mov bx,ds:fnptr ;get filename pointer call defext ;form filename xor cx,cx ;mode=default mov ah,3Ch ;func=create int 21h jnc @@2 mov si,offset operr ;error msg jmp fatal @@1: mov dx,offset stdout ;print msg mov ah,09h ;func=print int 21h mov ax,0001h ;handle=STDOUT @@2: mov ds:hexhnd,ax ;save handle mov word ptr ds:fptr,offset fbuf ;nothing written to buf yet mov word ptr ds:fctr,fbufl ret ;+ ; ; Copy a filename into buffer and add a default extension if needed. ; ; bx pointer ; dx length (assumed non-zero) ; si .ASCIZ extension ; ; dx returns ptr to buffer (containing .ASCIZ filename) ; ;- defext: push ds ;copy DS to ES pop es mov di,offset hexdat ;pt at buf xchg bx,si ;swap ptrs mov cx,dx ;copy length xor dl,dl ;DL=0 (no '.' yet) @@1: ; loop through all chars; DL=1 if '.' seen lodsb ;get a char stosb ;save cmp al,'/' ;pathname separator? je @@5 cmp al,'\' je @@5 cmp al,'.' ;extension separator (or part of path)? je @@6 @@2: loop @@1 ;loop through all characters xor al,al ;load 0 in case we have extension already test dl,dl ;seen a . since last / or \? mov dx,offset hexdat ;[point at buf] jnz @@4 ;yes, just add the 0 mov si,bx ;get ptr to default extension mov bx,di ;save ptr @@3: lodsb ;copy a byte @@4: stosb test al,al ;is that it? jnz @@3 ;loop if not ret @@5: ; / or \ xor dl,dl ;clear "." flag jmp short @@2 ;(period in path doesn't count as extension) @@6: ; . mov dl,1 ;set "." flag jmp short @@2 ;+ ; ; Skip white space (only!). ; ; CF=1 if EOL, otherwise: ; al first non-blank char ; si,cx updated to point at char in AL ; ;- skip: jcxz @@2 ;EOL already @@1: lodsb ;get a byte cmp al,' ' ;blank or ctrl char? ja @@3 ;no, stop (CF=0) loop @@1 ;loop @@2: stc ;hit EOL ret @@3: dec si ;unget ret ;+ ; ; Parse a word from the input line. ; ; ds:si current position ; cx # chars left ; ; On return: ; si points at posn after last char of word ; cx updated ; bx points at begn of word if CF=0 ; dx length of word ; ;- getw: jcxz @@2 ;EOL already @@1: ; look for beginning of word mov bx,si ;in case word starts here lodsb ;get a char cmp al,' ' ;blank or ctrl? ja @@4 ;no loop @@1 ;loop @@2: stc ;no luck ret @@3: ; look for end of word lodsb ;get a char @@4: cmp al,' ' ;blank or ctrl? jbe @@6 ;yes, end of word cmp al,'/' ;switch? je @@6 cmp al,'=' ;value? je @@6 cmp al,'a' ;lower case? jb @@5 cmp al,'z' ;hm? ja @@5 and al,not 40 ;yes, convert mov [si-1],al ;put back @@5: loop @@3 ;loop inc si ;compensate for next inst @@6: dec si ;unget mov dx,si ;calc length sub dx,bx ;CF=0 ret ;+ ; ; Look up a keyword in a table. ; ; ds:bx keyword } from GETW ; dx length } ; ss:ax table ; ; Returns CF=1 if not found, otherwise AX=number from table. ; ; This routine doesn't require that DS=CS, so it may be used to parse ; environment strings. ; ; si,cx preserved either way. ; ;- tbluk: push cx ;save push si push ds mov si,ax ;pt at table push ds ;copy DS to ES pop es push cs ;and CS to DS pop ds xor ch,ch ;ch=0 @@1: lodsw ;get length,,length to match test al,al ;end? jz @@4 mov cl,ah ;assume bad length cmp al,dl ;is ours long enough? ja @@2 ;no sub ah,dl ;too long? jc @@2 ;yes mov cl,dl ;just right mov di,bx ;point at keyword repe cmpsb ;match? je @@3 add cl,ah ;no, add extra length @@2: add si,cx ;skip to end lodsw ;skip addr jmp short @@1 ;loop @@3: ; got it mov cl,ah ;get extra length add si,cx ;skip to end lodsw ;get dispatch addr stc ;makes CF=0 below @@4: ; not found cmc ;CF=-CF pop ds ;restore regs pop si pop cx ret ;+ ; ; Print a 16-bit number as 4 hex digits. ; ; ax number ; es:di buf ptr (updated on return) ; ;- prhex4: push ax ;save mov al,ah ;get high byte call prhex2 ;do it first pop ax ;restore ;callr ;do low byte, return ; prhex2: ; as above, 2 digits from AL to ES:DI mov ah,al ;copy and ax,0FF0h ;isolate high, low bytes mov cl,4 ;shift count shr al,cl ;right-justify high digit cmp al,0Ah ;CF=1 if 0-9 sbb al,69h ;AL=96-9F or A1-A6 (AF=1 if 0-9, CF=1 always) das ;low byte -6 if 0-9, high byte -60h stosb ;save mov al,ah ;as above for low digit cmp al,0Ah sbb al,69h das stosb ;save ret ; subttl PIC16F84-specific routines ;+ ; ; Set parameters for 16F84. ; ;- set84: mov word ptr ds:bufmax,2140h*2-1 ;size of PIC16F84 image mov word ptr ds:bufmax+2,0 mov word ptr ds:prgsiz,1024d ;size of program EEPROM (words) mov word ptr ds:datsiz,64d ;size of data EEPROM (bytes) mov word ptr ds:dmplst,offset dump84 ;list of ranges to dump mov word ptr ds:read,offset rd84 ;read part to buf mov word ptr ds:write,offset wr84 ;write buf to part mov word ptr ds:verify,offset vfy84 ;verify part against buf mov word ptr ds:erase,offset era84 ;erase part mov word ptr ds:clear,offset clb84 ;clear buf ret ;+ ; ; Read entire 16F84 program memory. ; ;- rd84: call penter ;enter programming mode mov es,ds:bufseg ;point at buf xor di,di ;init offset @@1: ; read next word of program memory push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore stosw ;save word push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore mov ax,di ;copy byte addr shr ax,1 ;/2 = word addr cmp ax,ds:prgsiz ;done all? jb @@1 ; switch to config space mov ax,3FFFh ;all ones, no op if we really wrote it xor cl,cl ;cmd=load configuration (set PC to 2000h) call cmdx84 mov di,2000h*2 ;point at config data @@2: ; read next word of config memory push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore stosw ;save word push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore cmp di,2008h*2 ;done all? jb @@2 call pexit ;off call penter ;on again to clear PC mov di,2100h*2 ;point at EEPROM memory @@3: ; read next byte of data memory push di ;save mov cl,05 ;cmd=read data from data memory call cmdr84 pop di ;restore xor ah,ah ;clear unused high byte stosw ;save byte push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore mov ax,di ;copy byte addr sub ax,2100h*2 ;byte offset into data EEPROM space shr ax,1 ;byte addr /2 = word addr cmp ax,ds:datsiz ;done all? jb @@3 callr pexit ;exit programming mode, return ;+ ; ; Write entire 16F84 program memory. ; ;- wr84: test byte ptr ds:nerflg,-1 ;/NOERASE? jnz @@1 ;yes, skip call era84 ;no, erase chip first @@1: call penter ;enter programming mode mov es,ds:bufseg ;point at buf xor di,di ;init offset @@2: ; read next data word to see if it's already at the right value ; (avoids a slow 10 msec programming cycle if so) push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? je @@3 ;yes, skip all this dec di ;no, back up dec di ; write next data word mov ax,es:[di] ;fetch data word push di ;save mov cl,02 ;cmd=load data for program memory call cmdx84 mov cl,10 ;cmd=begin programming call cmd84 call wait10 ;wait 10 ms pop di ;restore ; verify word push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? je @@3 ;yes jmp verr84 ;verify error, report it @@3: push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore mov ax,di ;copy byte addr shr ax,1 ;/2 = word addr cmp ax,ds:prgsiz ;done all? jb @@2 ; switch to config space mov ax,3FFFh ;all ones, no op if we really wrote it xor cl,cl ;cmd=load configuration (set PC to 2000h) call cmdx84 mov di,2000h*2 ;point at config data @@4: ; read next word of config memory cmp di,2004h*2 ;do 2000-2003 and 2007 jb @@5 cmp di,2007h*2 jae @@5 scasw ;2004-2006, skip the word jmp short @@6 @@5: push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? je @@6 ;yes, skip all this dec di ;no, back up dec di ; write next data word mov ax,es:[di] ;fetch data word push di ;save mov cl,02 ;cmd=load data for program memory call cmdx84 mov cl,10 ;cmd=begin programming call cmd84 call wait10 ;wait 10 ms pop di ;restore ; verify word push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? jne @@9 ;no @@6: push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore cmp di,2008h*2 ;done all? jb @@4 call pexit ;off call penter ;on again to clear PC mov di,2100h*2 ;point at EEPROM memory @@7: ; read next byte of data memory push di ;save mov cl,05 ;cmd=read data from data memory call cmdr84 pop di ;restore scasb ;matches? je @@8 ;yes, skip all this dec di ;no, back up ; write next data byte mov al,es:[di] ;fetch data byte xor ah,ah ;MSB=0 push di ;save mov cl,03 ;cmd=load data for data memory call cmdx84 mov cl,10 ;cmd=begin programming call cmd84 call wait10 ;wait 10 ms pop di ;restore ; verify byte push di ;save mov cl,05 ;cmd=read data from data memory call cmdr84 pop di ;restore scasb ;matches? jne @@9 ;no @@8: push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore inc di ;skip unused high byte mov ax,di ;copy byte addr sub ax,2100h*2 ;byte offset into data EEPROM space shr ax,1 ;byte addr /2 = word addr cmp ax,ds:datsiz ;done all? jb @@7 callr pexit ;exit programming mode, return @@9: xor ah,ah ;clear high byte inc di ;bump to next word as expected jmp verr84 ;verify error, report it ;+ ; ; Verify entire 16F84 program memory. ; ;- vfy84: call penter ;enter programming mode mov es,ds:bufseg ;point at buf xor di,di ;init offset @@1: push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? jne @@4 ;no push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore mov ax,di ;copy byte addr shr ax,1 ;/2 = word addr cmp ax,ds:prgsiz ;done all? jb @@1 ; switch to config space mov ax,3FFFh ;all ones, no op if we really wrote it xor cl,cl ;cmd=load configuration (set PC to 2000h) call cmdx84 mov di,2000h*2 ;point at config data @@2: ; verify next word of config memory push di ;save mov cl,04 ;cmd=read data from program memory call cmdr84 pop di ;restore scasw ;matches? jne @@4 ;no push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore cmp di,2008h*2 ;done all? jb @@2 call pexit ;off call penter ;on again to clear PC mov di,2100h*2 ;point at EEPROM memory @@3: ; verify next byte of data memory push di ;save mov cl,05 ;cmd=read data from data memory call cmdr84 pop di ;restore scasb ;matches? lea di,[di+1] ;[advance to next word] jne @@4 ;no push di ;save again mov cl,06 ;cmd=increment PC call cmd84 pop di ;restore mov ax,di ;copy byte addr sub ax,2100h*2 ;byte offset into data EEPROM space shr ax,1 ;byte addr /2 = word addr cmp ax,ds:datsiz ;done all? jb @@3 callr pexit ;exit programming mode, return @@4: ;jmp short verr84 ;verify error, report it ;+ ; ; Report verification error. ; Chip is still in program/verify mode. ; ; ax value read ; es:di points one word past value expected in BUFSEG ; ;- verr84: dec di ;point at failed word dec di push ax ;save value we got push word ptr es:[di] ;value we expected shr di,1 ;byte addr /2 = word addr push di ;address call pexit ;exit programming mode push ds ;copy DS to ES pop es mov di,offset veradr ;address pop ax ;get addr call prhex4 ;convert to hex mov di,offset verexp ;same for value expected pop ax call prhex4 mov di,offset vergot ;value we got pop ax call prhex4 mov si,offset vermsg ;point at string jmp fatal ;+ ; ; Clear 16F84 memory. ; ; Doing bulk erases of the program and data memory individually as described in ; the regular part of the doc doesn't seem to work (program memory comes out as ; all 2000h instead of 3FFFh for one thing), and in any case there's officially ; no way to clear the config word. But, the procedure in section 4.1 of the ; Microchip DS30262B PIC16F8X programming document, which uses otherwise ; undocumented command opcodes 01 and 07 to disable code protection, also ; clears the entire device. ; ;- era84: call penter ;enter programming mode mov ax,3FFFh ;all ones, no op if we really wrote it xor cl,cl ;cmd=load configuration (set PC to 2000h) call cmdx84 mov cx,7 ;# times to increment PC to get to 2007h @@1: push cx ;save mov cl,06 ;cmd=increment PC call cmd84 pop cx ;restore loop @@1 mov cl,01 ;cmd=who knows? (from section 4.1 of prog doc) call cmd84 mov cl,07 ;cmd=who knows what else? call cmd84 mov cl,10 ;cmd=begin programming call cmd84 call wait10 ;wait 10 ms mov cl,01 ;again, mystery cmds from Microchip doc call cmd84 mov cl,07 call cmd84 ; clear data memory call pexit ;reset PC call penter mov cx,ds:datsiz ;get # data locations @@2: push cx ;save mov ax,3FFFh ;load all ones mov cl,03 ;cmd=load data for data memory call cmdx84 mov cl,10 ;cmd=begin programming call cmd84 call wait10 ;wait 10 ms mov cl,06 ;cmd=increment PC call cmd84 pop cx ;restore loop @@2 ;loop through all data memory callr pexit ;exit programming mode, return ;+ ; ; Clear buffer to match a blank 16F84 EEPROM. ; ;- clb84: push es ;save mov es,ds:bufseg ;point at buf mov cx,2100h ;size in words mov ax,3FFFh ;14 bits of ones xor di,di ;offset=0 rep stosw ;clear whole buf xor ah,ah ;all ones in RH, 0 in LH (8-bit bytes) mov cl,40h ;fill EPROM area with all 00FFs rep stosw pop es ;restore ret ;+ ; ; Send command to 16F84. ; ; bx SDELAY timer value (updated on return) ; cl command ; ;- cmd84: mov al,cl ;copy command mov cx,6 ;6 bits callr psend ;send it, return ;+ ; ; Send command to 16F84 with transmitted data word. ; ; ax data word (in low 14 bits) ; bx SDELAY timer value (updated on return) ; cl command ; ;- cmdx84: push ax ;save mov al,cl ;copy command mov cx,6 ;6 bits call psend ;send it call sdelay ;wait 2 more 838.1 ns clocks (1 usec for sure) call sdelay pop ax ;restore word and ah,77 ;trim high 2 bits add ax,ax ;center between two zeros mov cx,16d ;16 bits callr psend ;send it, return ;+ ; ; Send command to 16F84 with received data word. ; ; ax returns data word (in low 14 bits, zeros in high two) ; bx SDELAY timer value (updated on return) ; cl command ; ;- cmdr84: mov al,cl ;copy command mov cx,6 ;6 bits call psend ;send it call sdelay ;wait 4 more 838.1 ns clocks (1 usec for sure) call sdelay mov cx,16d ;16 bits call precv ;receive it shr ax,1 ;right-justify and ah,77 ;trim high 2 bits ret ; subttl serial I/O routines, actual hardware access ;+ ; ; Enter programming mode. ; ; bx returns SDELAY timer value (all synced up) ; ;- penter: mov dx,ds:lcr ;point at line ctrl reg in al,dx ;fetch value and al,not break ;should be clear, but just in case out dx,al ;power probably off, hold /MCLR to -12 push ax ;save push dx mov dx,ds:mcr ;point at modem ctrl reg xor al,al ;clear /RTS, /DTR out dx,al ;(holds RB<7:6> to -12) pop dx ;restore LCR addr, value pop ax or al,break ;power on if not already, let /MCLR float up out dx,al mov cx,2 ;# edges to count call ldelay ;long delay to let voltage reg and PIC wake up call sdel0 ;set up for delay call sdelay ;wait for next 838.1 ns tick ret ;+ ; ; Exit programming mode. ; ;- pexit: mov dx,ds:mcr ;point at modem ctrl reg xor al,al ;clear /RTS, /DTR out dx,al ;(set RB<7:6> back to -12) mov dx,ds:lcr ;point at line ctrl reg in al,dx ;fetch value and al,not break ;power probably off, set /MCLR to -12 out dx,al call sdelay ;make sure the chip sees this, in case we're callr sdelay ;about to turn it back on ;+ ; ; Send a bit sequence. ; ; ax bits to send, LSB first ; cx # bits (1-16.) ; bx SDELAY value (must already be synced up), updated on return ; ;- psend: call sdelay ;extend time since prev falling edge to >=1 us mov dx,ds:mcr ;point at modem ctrl reg @@1: push ax ;save data and al,dtr ;isolate LSB as DTR (for RB7) if dtr ne 1 ;(DTR happens to be b0) .err ;next data bit goes in DTR endif or al,rts ;assert RTS as rising edge of clock out dx,al ;write data bit, raise RTS (RB6) to +12 call waithb ;wait a half bit time and al,not rts ;drop RTS (RB6) to -12 out dx,al ;(falling edge of clock) call waithb ;wait the other half bit time pop ax ;restore shr ax,1 ;right a bit loop @@1 ;loop through all bits ret ;+ ; ; Receive a bit sequence. ; ; ax returns bits received, LSB was first ; cx # bits (1-16.) ; bx SDELAY value (must already be synced up), updated on return ; ;- precv: push cx ;save @@1: mov dx,ds:mcr ;point at modem ctrl reg mov al,rts ;drop DTR (our RB7 driver), raise RTS (RB6) out dx,al call waithb ;wait a half bit time xor al,al ;drop RTS (RB6) to -12 out dx,al mov dx,ds:msr ;point at modem status reg shr di,1 ;make space for new bit in al,dx ;fetch new data bit (on RB7) test al,cts ;1 or 0? jz @@2 ;0 or di,8000h ;set MSB @@2: call waithb ;wait the other half bit time loop @@1 ;loop through all pop ax ;catch word length mov cl,16d ;word size sub cl,al ;find # bits to right-justify, if any shr di,cl ;do it (zeros in LH) mov ax,di ;copy ret ;+ ; ; Init BX for first call to SDELAY. ; ; bx returns starting (byte-swapped) time value ; all others preserved ; ;- sdel0: push ax ;save cli ;ints off mov al,06h ;;latch timer 0, mode 3 out 43h,al in al,40h ;;read it mov bh,al ;;(save with bytes swapped) in al,40h sti ;;ints on mov bl,al ;BX=byte-swapped starting time pop ax ;restore ret ;+ ; ; Synchronous delay until next 838.1 ns clock tick. ; ; bx starting timer value, updated on return ; all others preserved ; ;- sdelay: push ax ;save cli ;ints off mov al,06h ;;latch timer 0, mode 3 out 43h,al in al,40h ;;read it mov ah,al in al,40h sti ;;ints on cmp ax,bx ;changed yet? je short sdelay ;spin until it does mov bx,ax ;set new starting value pop ax ;restore ret ;+ ; ; Wait a half-bit time. ; ; bx SDELAY timer value, updated on return ; all others preserved ; ;- waithb: callr sdelay ;just one tick seems to be enough ;(it's way more than enough for the PIC, I'm ;just worried about the response time of the ;UART and RS232 drivers/receivers) ;+ ; ; Wait 10 msec. ; ; bx SDELAY timer value, updated on return ; all others preserved ; ;- wait10: push cx ;save mov cx,11932d ;count for 10 msec @@10: call sdelay ;wait 838.1 nsec loop @@10 ;count edges pop cx ;restore ret ;+ ; ; Long delay. Could use a counter with SDELAY but this is just easier. ; ; cx # of BIOS 54 msec clock edges to count ; ax trashed, others preserved ; ;- ldelay: push es ;save xor ax,ax ;load 0 mov es,ax ;point at BIOS data @@1: mov ax,es:timer_low ;get time value @@2: cmp ax,es:timer_low ;changed? je @@2 ;loop if not loop @@1 ;count it pop es ;restore ret ; subttl input file handling ;+ ; ; Read next record from hex input file. ; ; HEXTYP, HEXLEN, HEXADR, HEXDAT are set up with data (to be interpreted by ; caller). ; ;- rdhex: push es ;save push ds ;copy DS to ES pop es @@1: ; : call getc ;get next char cmp al,':' ;start of rec? jne @@1 ;ignore mov byte ptr ds:hexchk,0 ;init checksum ; LL (length of data field) call rdhex2 ;get length of data field mov ds:hexlen,al ;save ; AAAA (starting address) call rdhex2 ;high byte push ax ;save call rdhex2 ;low byte pop bx ;catch mov ah,bl mov ds:hexadr,ax ;save ; TT (type of record) call rdhex2 ;type code mov ds:hextyp,al ; DDDDDD... (optional data byte(s)) mov cl,ds:hexlen ;get # data bytes xor ch,ch ;CH=0 jcxz @@3 mov di,offset hexdat ;point at buf @@2: push cx ;save call rdhex2 ;get next data byte stosb ;save pop cx ;restore loop @@2 ;loop through all @@3: ; CC (checksum) call rdhex2 ;read and discard check byte test byte ptr ds:hexchk,-1 ;should add up to 00 jnz @@4 pop es ;restore ret @@4: mov si,offset badchk ;bad checksum jmp fatal ;+ ; ; Get next pair of hex digits. ; ; al returns value ; di preserved ; ;- rdhex2: call rdhex1 ;get high digit mov cl,4 ;bit count shl al,cl ;make space for low digit push ax ;save call rdhex1 ;get low digit pop bx ;restore or al,bl ;combine add ds:hexchk,al ;add to checksum ret ;+ ; ; Get next hex digit. ; ; al returns value ; di preserved ; ;- rdhex1: call getc ;get a char sub al,'0' ;see if digit cmp al,10d ;is it? jb @@2 ;yes sub al,'A'-'0' ;A-F? cmp al,6 jb @@1 ;yes sub al,'a'-'A' ;a-f? cmp al,6 jae rdhex1 ;no, ignore @@1: add al,0Ah ;A-F => 0Ah-0Fh @@2: ret ;+ ; ; Get next character from FBUF. ; ; Since .HEX files should always stop us before we hit EOF, we give a fatal ; error if that happens. ; ; al returns char ; di preserved ; ;- getc: sub word ptr ds:fctr,1 ;update count jc @@1 ;nothing in buf, go reload mov si,ds:fptr ;get ptr lodsb ;get char mov ds:fptr,si ;save and al,177 ;(trim parity bit in case botched serial xfr) ret @@1: mov dx,offset fbuf ;point at buf mov ds:fptr,dx ;(rewind ptr) mov cx,fbufl ;buf size mov bx,ds:hexhnd ;file handle mov ah,3Fh ;func=read int 21h jc @@2 ;error test ax,ax ;EOF? jz @@3 mov ds:fctr,ax ;save length jmp short getc @@2: mov si,offset rderr ;error jmp fatal @@3: mov si,offset unxeof ;unexpected EOF jmp fatal ;+ ; ; Write next record to hex output file. ; ; HEXTYP, HEXLEN, HEXADR, HEXDAT must be set up on entry. ; ;- wrhex: ; : mov al,':' ;start of rec call putc mov byte ptr ds:hexchk,0 ;init checksum ; LL (length of data field) mov al,ds:hexlen ;length call wrhex2 ; AAAA (starting address) mov al,byte ptr ds:hexadr+1 ;high byte call wrhex2 mov al,byte ptr ds:hexadr ;low byte call wrhex2 ; TT (type of record) mov al,ds:hextyp ;tpye code call wrhex2 ; DDDDDD... (optional data byte(s)) mov cl,ds:hexlen ;get # data bytes xor ch,ch ;CH=0 jcxz @@2 mov si,offset hexdat ;point at buf @@1: push cx ;save lodsb ;get next data byte call wrhex2 pop cx ;restore loop @@1 ;loop through all @@2: ; CC (checksum) mov al,ds:hexchk ;get checksum neg al ;two's complement call wrhex2 mov al,cr ; call putc mov al,lf callr putc ;+ ; ; Write a byte to the output file as 2 hex digits. ; ; al byte to write ; si preserved ; ;- wrhex2: add ds:hexchk,al ;add to checksum mov ah,al ;copy and ax,0FF0h ;isolate high, low bytes mov cl,4 ;shift count shr al,cl ;right-justify high digit cmp al,0Ah ;CF=1 if 0-9 sbb al,69h ;AL=96-9F or A1-A6 (AF=1 if 0-9, CF=1 always) das ;low byte -6 if 0-9, high byte -60h push ax ;save call putc ;send high digit pop ax ;restore mov al,ah ;as above for low digit cmp al,0Ah sbb al,69h das ;callr ;and return ;+ ; ; Put next character in FBUF. ; ; al char to send ; si preserved ; ;- putc: mov di,ds:fptr ;get ptr mov [di],al ;save inc word ptr ds:fptr dec word ptr ds:fctr ;update count jz @@1 ;buf is full, go flush ret @@1: ;callr ;flush buf, return ;+ ; ; Flush buffer to output file, prepare for more output. ; ; si preserved ; ;- flush: mov dx,offset fbuf ;point at buf mov cx,dx ;copy xchg cx,ds:fptr ;rewind ptr, get value sub cx,dx ;find length jz @@1 ;nothing, skip mov bx,ds:hexhnd ;file handle mov ah,40h ;func=write int 21h jc @@2 ;error @@1: mov word ptr ds:fctr,fbufl ;buf is empty ret @@2: mov si,offset wrerr ;error jmp fatal ; subttl pure data ; banner db 'P84 V1.00 PIC16F84 programmer ' db 'By John Wilson ',cr,lf db 'Copyright (C) 1999 by D Bit. All rights reserved.',cr,lf db 'This program may be freely distributed provided proper' db ' credit is given.',cr,lf db '$' ; jcltab label byte ;command line switches kw ,help ;print help kw ,blkcmd ;test to see if part is blank kw ,com1 ;set port to COM1: kw ,com2 ;set port to COM2: kw ,com3 ;set port to COM3: kw ,com4 ;set port to COM4: kw ,eracmd ;erase (only) kw ,help ;print help kw ,h8mcmd ;file type is Intel hex merged kw ,h8scmd ;file type is Intel hex split kw ,h32cmd ;file type is Intel hex 32 bit (addressing) kw ,nercmd ;don't erase before writing kw ,set84 ;chip type is 16F84 kw ,rdcmd ;read kw ,vfycmd ;verify (only) kw ,wrcmd ;write db 0 ;end of list ; jclhlp label byte ;command line help db cr,lf db 'Usage: P84 [filename[.HEX]] [/switches]',cr,lf db cr,lf db 'Command switches: (can specify more than one)',cr,lf db '/BLANK verify that part is blank',cr,lf db '/ERASE erase part (only)',cr,lf db "/NOERASE don't erase before writing",cr,lf db '/READ read part into file',cr,lf db '/VERIFY just verify (compare) file against part',cr,lf db '/WRITE write file to part (default if no other switches)' db cr,lf,cr,lf db 'Command qualifiers:',cr,lf db '/COMn specify port for COM84 board (n=1-4)',cr,lf db '/INHX8M use merged .HEX file (default)',cr,lf db '/INHX8S use split .HXL/.HXH files for low/high bytes',cr,lf db '/INHX32 use merged .HEX file w/32-bit addressing',cr,lf db '$' ; ; Counted error messages, passed to FATAL: ; ascic badjcl,'?Syntax error' ascic allerr,'?Memory allocation error' ascic operr,'?File open error' ascic rderr,'?File read error' ascic wrerr,'?File write error' ascic unxeof,'?Unexpected end of input file' ascic badchk,'?Bad checksum in hex record' ascic adrran,'?Hex record addr out of range for selected part' ascic nxport,'?Nonexistent port' ascic swtcnf,'?Switch conflict' ; ; Buffer ranges to be dumped out by DUMP: ; dump84 label word ;PIC16F84 ranges dw 0,400h*2-1 ;program memory, words 0-3FFh dw 2000h*2,2004h*2-1 ;ID memory, words 2000h-2003h dw 2007h*2,2008h*2-1 ;config memory, words at 2007h ;PIP-02 gets confused if we send all of the ;2000-2007 range (loses fuse data) dw 2100h*2,2140h*2-1 ;data EEPROM (piss-poor MPASM support) ;this range is implied by example in desc. of ;"DE" pseudo-op, but not actually specified dw -1 ;end of list ; vermsg db verlen,'?Verify failed at address ' veradr db 'HHHH: expected ' verexp db 'HHHH, got ' vergot db 'HHHH',cr,lf,bel verlen= $-vermsg-1 ; ; File extensions: ; hexext db '.HEX',0 hxlext db '.HXL',0 hxhext db '.HXH',0 ; stdin db '(reading STDIN, type ^C to abort)',cr,lf,'$' stdout db '(writing STDOUT, remove this and preceding lines)',cr,lf,'$' ; subttl impure storage ; fnptr dw ? ;pointer to filename in command line fnctr dw 0 ;length of filename in command line com dw 0 ;COM port index (0/2/4/6 = COM1/2/3/4) ; inhx8m db 0 ;NZ => merged Intel hex file inhx8s db 0 ;NZ => split high/low Intel hex files inhx32 db 0 ;NZ => merged Intel hex file w/32-bit addrs ; blkflg db 0 ;NZ => /BLANK command eraflg db 0 ;NZ => /ERASE command nerflg db 0 ;NZ => /NOERASE qualifier (to write cmd) rdflg db 0 ;NZ => /READ command vfyflg db 0 ;NZ => /VERIFY command wrflg db 0 ;NZ => /WRITE command ; subttl pure storage ; port dw 1 dup(?) ;(e.g. 3F8) base port addr lcr dw 1 dup(?) ;(e.g. 3FB) line ctrl reg mcr dw 1 dup(?) ;(e.g. 3FC) modem ctrl reg msr dw 1 dup(?) ;(e.g. 3FE) modem status reg ; ; Addrs of part-specific routines: read dw 1 dup(?) ;read part to buf write dw 1 dup(?) ;write buf to part verify dw 1 dup(?) ;verify part against buf erase dw 1 dup(?) ;erase part clear dw 1 dup(?) ;clear buf ; bufseg dw 1 dup(?) ;seg addr of memory buffer bufmax dw 2 dup(?) ;last valid offset of buf (DWORD) ;32-bit addressing isn't really supported yet, ;I'll finish it if/when I need to add support ;for burning a device that would use it prgsiz dw 1 dup(?) ;size of program memory in words datsiz dw 1 dup(?) ;size of data memory in bytes dmplst dw 1 dup(?) ;list of ranges dumped by DUMP ;(word pairs giving byte ranges, -1 terminates) ; hexhnd dw 1 dup(?) ;.HEX file handle ; fptr dw 1 dup(?) ;pointer into FBUF fctr dw 1 dup(?) ;# chars left in FBUF fbuf db fbufl dup(?) ;file buffer ; ; Stuff from hex record: hexchk db 1 dup(?) ;checksum of hex record hexlen db 1 dup(?) ;length of data field hexadr dw 1 dup(?) ;address field hextyp db 1 dup(?) ;type field hexad1 dw 1 dup(?) ;separate addr ctr for DUMP with INHX8S hexdat db 256d dup(?) ;up to 256. data bytes ; dw 200h dup(?) ;stack org $+(10h-(($-start) and 0Fh)) ;round to paragraph boundary pdl label word ;stack ends here ;memory past this point is released ; code ends end start