;In-circuit programmer for the Atmel 89C2051 microcontroller ;A 87C51 based 89C2051 programmer. Download code via serial ;port. Command switches programmed 89C2051 into circuit ;via 4066 analog switches (80 ohm on resistance). Reset is ;controlled by programmer, and crystal is local in programmer. ;this is an early BETA copy, version 0.003. Use at your own risk! ;please report bugs to paul@ece.orst.edu. Updates available at: ;http://www.ece.orst.edu/~paul/8051-goodies/goodies-index.html#atmel ;this version (0.003) was made publicly available on 28 Sept 95. ;The user interface is particularly unfriendly, so be wary. This ;will improve in the next couple versions... ;ram memory is assumed to be available both between 0000-1FFF ;AND 2000-3FFF. This will likely get cleaned up in a more ;elegant way in a later version. Other strange hardware ;dependencies may exist... they will get cleaned up. This ;BETA is the first version that seems to have all the major ;functions working correctly! ;uart txd pin of 89C2051 is logically or'd with the txd of the ;programmer while the chip is switched in-curcuit, so debugging ;information can be sent to the same console as the programming. ;rxd pin of 89C2051 not connected to programmer, however. ;This software is in the public domain. I, Paul Stoffregen, give ;no warranty, expressed or implied for this software and/or ;documentation provided, including, without limitation, warranty ;of merchantability and fitness for a particular purpose. ;timer reload calculation ; baud_const = 256 - (crystal / (12 * 16 * baud)) .equ baud_const, 253 ;19200 baud w/ 11.0592 MHz ;memory locations for the 8255 chip .equ port_a, 0xE000 .equ port_b, 0xE001 .equ port_c, 0xE002 .equ port_pgm, 0xE003 .equ stack, 0x30 .org 0 ljmp poweron ;reset vector .org 3 ;ext int0 vector .org 11 ;timer0 vector .org 19 ;ext int1 vector .org 27 ;timer1 vector .org 35 ;uart vector .org 43 ;timer2 vector (8052) .org 48 ;finally we can begin main: mov r6, #0 mov r7, #0x20 acall newline acall delay clr tr0 ;fill buffer with 0xFF mov dptr, #0 inimain: mov a, #0xFF movx @dptr, a inc dptr mov a, dph cjne a, #0x20, inimain ajmp mainloop ;the main program loop: mainloop: lcall cin lcall upper cjne a, #'D', main2 lcall dnld sjmp mainloop main2: cjne a, #'?', main3 mov dptr, #help lcall pstr sjmp mainloop main3: cjne a, #'P', main4 lcall prog sjmp mainloop main4: cjne a, #'I', main5 lcall in sjmp mainloop main5: cjne a, #'R', main6 lcall read sjmp mainloop main6: cjne a, #'H', main7 lcall hexdump sjmp mainloop main7: cjne a, #'E', main8 lcall erase sjmp mainloop main8: cjne a, #'S', main9 lcall readid sjmp mainloop main9: cjne a, #'T', main10 lcall testpvolt sjmp mainloop main10: ljmp mainloop help: .db 13,10 .db "Atmel 89C2051 In-circuit programmer commands",13,10 .db "BETA VERSION 0.003 (28 Sept 95)",13,10 .db "Paul Stoffregen (paul@ece.orst.edu)",13,10,13,10 .db " D - download Intel HEX file into buffer",13,10 .db " P - program the chip from the buffer",13,10 .db " E - erase chip",13,10 .db " I - in-circuit operation",13,10 .db " R - read back into ram",13,10 .db " S - read ID bytes",13,10 .db " H - hexdump ram",13,10 .db " ? - This help",13,10 .db 13,10,0 out: ;switch the 89C2051 out of the circuit setb p1.0 ;disconnect from circuit clr p3.5 ;stop the oscillator clr p3.3 ;force xtal1 to low lcall volt0 ;make sure reset is low lcall volt5 ;force reset to high clr p3.4 ;block its access to our serial line mov dptr, #port_a mov a, #00001111b ;port a = 00001111 movx @dptr, a mov dptr, #port_pgm mov a, #10001011b ;port a=out, b=in, c=in movx @dptr, a mov a, #00001111b mov dptr, #port_a movx @dptr, a ;port a = 00001111 ret read: ;read the 89C2051 back into memory 2800 to 2FFF lcall out mov dptr, #port_pgm mov a, #10001011b ;port a=out, b=in, c=in movx @dptr, a mov dptr, #port_a mov a, #11100111b ;read mode movx @dptr, a mov dptr, #0x2800 read1: push dpl push dph mov dptr, #port_b movx a, @dptr ;read in data from 89C2051 pop dph pop dpl movx @dptr, a ;write to ram inc dptr setb p3.3 ;advance addr counter nop nop nop nop clr p3.3 mov a, dpl cjne a, #0, read1 mov a, dph cjne a, #0x30, read1 ret readid: ;try to read the product id bytes mov dptr, #id_msg lcall pstr lcall out mov dptr, #port_pgm mov a, #10001011b ;port a=out, b=in, c=in movx @dptr, a mov dptr, #port_a mov a, #01000111b ;read id mode movx @dptr, a mov dptr, #port_b movx a, @dptr ;read in data from 89C2051 lcall phex mov a, #' ' lcall cout setb p3.3 ;advance addr counter nop nop nop nop clr p3.3 mov dptr, #port_b movx a, @dptr ;read in data from 89C2051 lcall phex mov a, #' ' lcall cout setb p3.3 ;advance addr counter nop nop nop nop clr p3.3 mov dptr, #port_b movx a, @dptr ;read in data from 89C2051 lcall phex lcall newline ret id_msg: .db 13,10,"ID Bytes: ",0 in: ;put the 89C2051 into the circuit and start it lcall volt5 ;make sure reset is at 5 volts setb p3.5 ;start the oscillator setb p3.3 ;and allow it to drive the chip setb p3.4 ;allow the 89C2051's TxD pin to ;drive our serial output too lcall dly_50us ;wait a bit mov dptr, #port_pgm mov a, #10011011b ;port a=in, b=in, c=in movx @dptr, a clr p1.0 ;connect it to the external circuit lcall dly_50us ;wait a little while lcall volt0 ;begin operation! ;at this point, the Atmel 89C2051 should be in the ;circuit and running. Now we wait for a keypress, and ;then we'll yank it out of the circuit again. lcall cin lcall out ret prog: ;program the 89C2051 using the data in memory ;from 2000 to 27FF lcall out mov dptr, #p_msg1 lcall pstr mov dptr, #0x2000 prog1: movx a, @dptr ;get the value to program now mov b, a ;and keep it around in b cjne a, #0xFF, prog1b ljmp prog_skip ;skip programming if 0xFF prog1b: push dph push dpl mov a, dpl ; jnz prog2 mov a, #13 lcall cout mov dptr, #p_msg2 lcall pstr pop dpl pop dph push dph push dpl mov a, dph lcall phex mov a, dpl lcall phex prog2: mov dptr, #port_pgm mov a, #10001001b ;port a=out, b=out, c=in movx @dptr, a mov dptr, #port_b mov a, b movx @dptr, a ;drive port 1 with data byte mov dptr, #port_a mov a, #11110111b movx @dptr, a lcall volt12 ;raise reset to 12 volts mov dptr, #port_a mov a, #11110011b movx @dptr, a ;begin prog pulse lcall dly_50us mov a, #11110111b movx @dptr, a ;end prog pulse ;the text description seems to suggest keeping reset at ;12 volts until it's done writing, but the detailed timing ;diagram says it's ok to lower reset back to 5 volts ;only 10us after the end of our prog pulse... lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us progwait: mov dptr, #port_c movx a, @dptr ;read port #3 anl a, #00000001b ;the lsb is our busy line ;should check here to make sure it's not stuck... typical ;program time is 1.2ms, worst case is 2ms jz progwait lcall volt5 ;make sure we're not still at 12 volts ;get ready to read mov dptr, #port_pgm mov a, #10001011b ;port a=out, b=in, c=in movx @dptr, a mov dptr, #port_a mov a, #11100111b ;read mode movx @dptr, a nop nop nop mov dptr, #port_b movx a, @dptr ;read data back from chip push acc ;push it into stack for now mov dptr, #port_a mov a, #11110111b ;disable read mode movx @dptr, a nop nop nop nop ;and now all we need to do is increment the addr counter setb p3.3 nop nop nop clr p3.3 ;now let's look at the data we verified pop acc cjne a, b, prog_err prog_next: pop dpl pop dph pskip2: inc dptr mov a, dpl cjne a, #0, prog_j mov a, dph cjne a, #0x28, prog_j lcall newline mov dptr, #p_msg3 lcall pstr ret prog_skip: ;don't forget to increment the addr counter when ;we skip a location because the buffer is 0xFF setb p3.3 nop nop nop clr p3.3 sjmp pskip2 p_msg1: .db "Programming Flash ROM:",13,10,0 p_msg2: .db "Location: ",0 p_msg3: .db "Finished Programming",13,10,0 prog_j: ;jump up to do the next location ljmp prog1 prog_err: mov r3, a ;keep bogus value in r3 mov r4, b ;keep correct value in r4 mov dptr, #pgm1 lcall pstr pop dpl pop dph push dph push dpl mov a, dph lcall phex mov a, dpl lcall phex mov dptr, #pgm2 lcall pstr mov a, r4 lcall phex mov dptr, #pgm3 lcall pstr mov a, r3 lcall phex lcall newline ljmp prog_next pgm1: .db " Program verify error at ",0 pgm2: .db " correct: ",0 pgm3: .db " actual: ",0 erase: ;erase the 89C2051 lcall out mov dptr, #er_msg1 lcall pstr mov dptr, #port_a mov a, #01001111b ;erase code movx @dptr, a nop nop lcall volt12 ;go to 12 volts mov dptr, #port_a mov a, #01001011b ;erase pulse movx @dptr, a er_dly: mov r3, #40 er_dly2:lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us lcall dly_50us djnz r3, er_dly2 mov dptr, #port_a mov a, #01001111b ;end erase pulse movx @dptr, a nop nop lcall volt5 ;back to 5 volts mov dptr, #port_a mov a, #11111111b movx @dptr, a nop nop mov dptr, #done lcall pstr ret er_msg1:.db "Erasing Flash ROM...",0 done: .db "done",13,10,0 testpvolt: lcall volt12 mov dptr, #progmsg1 acall pstr mov a, #'1' acall cout mov a, #'2' acall cout mov dptr, #progmsg2 acall pstr acall cin lcall volt0 mov dptr, #progmsg1 acall pstr mov a, #'0' acall cout mov dptr, #progmsg2 acall pstr acall cin lcall volt5 mov dptr, #progmsg1 acall pstr mov a, #'5' acall cout mov dptr, #progmsg2 acall pstr acall cin ret progmsg1:.db "Reset now at ",0 progmsg2:.db " volts, press a key",13,10,0 volt0: ;drive reset to zero volts setb p1.1 nop clr p1.2 lcall dly_50us lcall dly_50us ret volt5: ;drive reset to five volts setb p1.1 setb p1.2 lcall dly_50us lcall dly_50us ret volt12: ;drive reset to twelve volts setb p1.2 nop clr p1.1 lcall dly_50us lcall dly_50us ret ;approx 0.1 second delay (11.0592 MHz crystal) delay: mov r3, #200 delay2: nop mov r2, #228 djnz r2, * djnz r3, delay2 ret dly_50us: nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop ret cin: jnb ri, cin clr ri mov a, sbuf ret cout: jnb ti, cout ;not ok to change carry bit here! mov sbuf, a clr ti ret newline:push acc mov a, #13 acall cout mov a, #10 acall cout pop acc ret phex: phex8: push acc swap a anl a, #15 add a, #246 jnc phex_b add a, #7 phex_b: add a, #58 acall cout pop acc phex1: push acc anl a, #15 add a, #246 jnc phex_c add a, #7 phex_c: add a, #58 acall cout pop acc ret space: mov a, #' ' acall cout ret PSTR: ;print string PUSH ACC PSTR1: CLR A MOVC A,@A+DPTR JZ PSTR2 mov c, acc.7 anl a, #01111111b acall cout Jc pstr2 inc dptr SJMP PSTR1 PSTR2: POP ACC RET poweron: setb p1.0 ;disconnect from the circuit setb p1.1 ;drive reset to 5 volts setb p1.2 MOV SP, #stack clr psw.3 clr psw.4 lcall out ;take the chip out of the circuit mov th1, #baud_const orl PCON,#10000000b ; set double baud rate MOV TMOD,#00100001b ; T0=16 bit, T1=8 bit auto reload ; both are timers, software control MOV SCON,#01010010b ; Set Serial for mode 1 & ; Enable reception, ti=1, ri=0 ORL TCON,#01010101b ; Start both timers, both int are ; falling edge trigger mov dptr, #welcome acall pstr ajmp main welcome:.db "Atmel 89C2051 In-circuit Programmer",13,10 .db " - Paul Stoffregen, July 1995",13,10,13,10,0 ;location of parameter table used by download command ;sixteen bytes of internal memory are required .equ dnld_parm, 0x20 ;16 byte parameter table: (eight 16 bit values) ; * 0 = lines received ; * 1 = bytes received ; * 2 = bytes written ; * 3 = bytes unable to write ; * 4 = incorrect checksums ; * 5 = unexpected begin of line ; * 6 = unexpected hex digits (while waiting for bol) ; * 7 = unexpected non-hex digits (in middle of a line) dnld: mov dptr, #dnlds1 acall pstr ;"begin sending file to abort" mov r0, #dnld_parm mov r2, #16 dnld0: mov @r0, #0 ;initialize all parameters to 0 inc r0 djnz r2, dnld0 ;look for begining of line marker ':' dnld1: acall cin cjne a, #27, dnld2 ;Test for escape sjmp dnld_esc dnld2: cjne a, #':', dnld2b mov r1, #0 acall dnld_inc sjmp dnld3 dnld2b: ;check to see if it's a hex digit, error if it is lcall asc2hex jc dnld1 mov r1, #6 acall dnld_inc sjmp dnld1 ;begin taking in the line of data dnld3: mov a, #'.' acall cout mov r4, #0 ;r4 will count up checksum acall dnld_ghex mov r0, a ;R0 = # of data bytes mov r4, a acall dnld_ghex mov dph, a ;High byte of load address add a, r4 mov r4, a acall dnld_ghex mov dpl, a ;Low byte of load address add a, r4 mov r4, a acall dnld_ghex ;Record type mov r2, a add a, r4 mov r4, a mov a, r2 cjne a, #1, dnld4 ;End record? sjmp dnld_end dnld4: dnld5: acall dnld_ghex ;Get data byte mov r2, a mov r1, #1 acall dnld_inc ;count total data bytes received mov a, r2 add a, r4 mov r4, a mov a, r2 lcall smart_wr ;c=1 if an error writing clr a addc a, #2 mov r1, a ; 2 = bytes written ; 3 = bytes unable to write acall dnld_inc inc dptr djnz r0, dnld5 acall dnld_ghex ;get checksum add a, r4 jz dnld1 ;should always add to zero mov r1, #4 acall dnld_inc sjmp dnld1 dnld_end: ;handles the proper end of download marker mov dptr, #dnlds3 acall pstr ;"download went ok..." sjmp dnld_sum dnld_esc: ;handle esc received in the download stream mov dptr, #dnlds2 acall pstr ;"download aborted." sjmp dnld_sum dnld_inc: ;increment parameter specified by R1 ;note, values in Acc and R1 are destroyed mov a, r1 anl a, #00000111b ;just in case rl a add a, #dnld_parm mov r1, a ;now r1 points to lsb inc @r1 mov a, @r1 jnz dnldin2 inc r1 inc @r1 dnldin2:ret dnld_gp: ;get parameter, and inc to next one (@r1) ;carry clear if parameter is zero. ;16 bit value returned in dptr setb c mov dpl, @r1 inc r1 mov dph, @r1 inc r1 mov a, dpl jnz dnldgp2 mov a, dph jnz dnldgp2 clr c dnldgp2:ret ;a spacial version of ghex just for the download. Does not ;look for carriage return or backspace. Handles ESC key by ;poping the return address (I know, nasty, but it saves many ;bytes of code in this 4k ROM) and then jumps to the esc ;key handling. This ghex doesn't echo characters, and if it ;sees ':', it pops the return and jumps to an error handler ;for ':' in the middle of a line. Non-hex digits also jump ;to error handlers, depending on which digit. dnld_ghex: dnldgh1:acall cin lcall upper cjne a, #27, dnldgh3 dnldgh2:pop acc pop acc sjmp dnld_esc dnldgh3:cjne a, #':', dnldgh5 dnldgh4:mov r1, #5 ;handle unexpected beginning of line acall dnld_inc pop acc pop acc ajmp dnld3 ;and now we're on a new line! dnldgh5:lcall asc2hex jnc dnldgh6 mov r1, #7 acall dnld_inc sjmp dnldgh1 dnldgh6:mov r2, a ;keep first digit in r2 dnldgh7:acall cin lcall upper cjne a, #27, dnldgh8 sjmp dnldgh2 dnldgh8:cjne a, #':', dnldgh9 sjmp dnldgh4 dnldgh9:lcall asc2hex jnc dnldghA mov r1, #7 acall dnld_inc sjmp dnldgh7 dnldghA:xch a, r2 swap a orl a, r2 ret ;dnlds4 = "Summary:" ;dnlds5 = " lines received" ;dnlds6a = " bytes received" ;dnlds6b = " bytes written" dnld_sum: ;print out download summary mov dptr, #dnlds4 acall pstr mov r1, #dnld_parm acall dnld_gp acall space lcall pint16u mov dptr, #dnlds5 acall pstr acall dnld_gp acall space lcall pint16u mov dptr, #dnlds6a acall pstr acall dnld_gp acall space lcall pint16u mov dptr, #dnlds6b acall pstr dnld_err: ;now print out error summary mov r2, #5 dnlder2:acall dnld_gp jc dnlder3 ;any errors? djnz r2, dnlder2 ;no errors, so we print the nice message mov dptr, #dnlds13 acall pstr ret dnlder3: ;there were errors, so now we print 'em mov dptr, #dnlds7 acall pstr ;but let's not be nasty... only print if necessary mov r1, #(dnld_parm+6) acall dnld_gp jnc dnlder4 acall space lcall pint16u mov dptr, #dnlds8 acall pstr dnlder4:acall dnld_gp jnc dnlder5 acall space lcall pint16u mov dptr, #dnlds9 acall pstr dnlder5:acall dnld_gp jnc dnlder6 acall space lcall pint16u mov dptr, #dnlds10 acall pstr dnlder6:acall dnld_gp jnc dnlder7 acall space lcall pint16u mov dptr, #dnlds11 acall pstr dnlder7:acall dnld_gp jnc dnlder8 lcall pint16u mov dptr, #dnlds12 acall pstr dnlder8:acall newline ret ;dnlds7: = "Errors:" ;dnlds8: = " bytes unable to write" ;dnlds9: = " incorrect checksums" ;dnlds10: = " unexpected begin of line" ;dnlds11: = " unexpected hex digits" ;dnlds12: = " unexpected non-hex digits" ;dnlds13: = "No errors detected" ;a smart-write routine which does a destructive test to ;try to figure out if the memory is SRAM or Flash ROM ;and then does the correct one. Carry bit will indicate ;if the value was successfully written, C=1 if not written. smart_wr: movx @dptr, a ;ok, not so smart just yet clr c ret esc: ;checks to see if is waiting on serial port ;C=clear if no , C=set if pressed ;buffer is flushed push acc clr c jnb ri,esc2 mov a,sbuf cjne a,#27,esc1 setb c esc1: clr ri esc2: pop acc ret pint8u: ;prints the unsigned 8 bit value in Acc in base 10 push b push acc sjmp pint8b pint8: ;prints the signed 8 bit value in Acc in base 10 push b push acc jnb acc.7, pint8b mov a, #'-' lcall cout pop acc push acc cpl a add a, #1 pint8b: mov b, #100 div ab jz pint8c add a, #'0' lcall cout pint8c: mov a, b mov b, #10 div ab jz pint8d add a, #'0' lcall cout pint8d: mov a, b add a, #'0' lcall cout pop acc pop b ret ;print 16 bit unsigned integer in DPTR, using base 10. pint16u: ;warning, destroys r2, r3, r4, r5, psw.5 push acc mov a, r0 push acc clr psw.5 mov r2, dpl mov r3, dph pint16a:mov r4, #16 ;ten-thousands digit mov r5, #39 lcall pint16x jz pint16b add a, #'0' lcall cout setb psw.5 pint16b:mov r4, #232 ;thousands digit mov r5, #3 lcall pint16x jnz pint16c jnb psw.5, pint16d pint16c:add a, #'0' lcall cout setb psw.5 pint16d:mov r4, #100 ;hundreds digit mov r5, #0 lcall pint16x jnz pint16e jnb psw.5, pint16f pint16e:add a, #'0' lcall cout setb psw.5 pint16f:mov a, r2 ;tens digit mov r3, b mov b, #10 div ab jnz pint16g jnb psw.5, pint16h pint16g:add a, #'0' lcall cout pint16h:mov a, b ;and finally the ones digit mov b, r3 add a, #'0' lcall cout pop acc mov r0, a pop acc ret ;ok, it's a cpu hog and a nasty way to divide, but this code ;requires only 21 bytes! Divides r2-r3 by r4-r5 and leaves ;quotient in r2-r3 and returns remainder in acc. If Intel ;had made a proper divide, then this would be much easier. pint16x:mov r0, #0 pint16y:inc r0 clr c mov a, r2 subb a, r4 mov r2, a mov a, r3 subb a, r5 mov r3, a jnc pint16y dec r0 mov a, r2 add a, r4 mov r2, a mov a, r3 addc a, r5 mov r3, a mov a, r0 ret upper: ;converts the ascii code in Acc to uppercase, if it is lowercase push acc clr c subb a, #97 jc upper2 ;is it a lowercase character subb a, #26 jnc upper2 pop acc add a, #224 ;convert to uppercase ret upper2: pop acc ;don't change anything ret pbin: mov r0, #8 pbin2: rlc a mov f0, c push acc mov a, #'0' addc a, #0 lcall cout pop acc mov c, f0 djnz r0, pbin2 rlc a ret lenstr: mov r0, #0 ;returns length of a string in r0 push acc lenstr1:clr a movc a,@a+dptr jz lenstr2 mov c,acc.7 inc r0 Jc lenstr2 inc dptr sjmp lenstr1 lenstr2:pop acc ret dnlds1: .db 13,10,13,10,"Begin ascii transmission of " .db "Intel HEX format file, " .db "or to abort",13,10,13,10,0 dnlds2: .db 13,10,"Download aborted",13,10,13,10,0 dnlds3: .db 13,10,"Download completed",13,10,13,10,0 dnlds4: .db "Summary:",13,10,0 dnlds5: .db " lines received",13,10,0 dnlds6a:.db " bytes received",13,10,0 dnlds6b:.db " bytes written",13,10,0 dnlds7: .db "Errors:",13,10,0 dnlds8: .db " bytes unable to write",13,10,0 dnlds9: .db " incorrect checksums",13,10,0 dnlds10:.db " unexpected begin of line",13,10,0 dnlds11:.db " unexpected hex digits",13,10,0 dnlds12:.db " unexpected non-hex digits",13,10,0 dnlds13:.db "No errors detected",13,10,13,10,0 asc2hex: ;carry set if invalid input clr c push b subb a,#'0' mov b,a subb a,#10 jc a2h1 mov a,b subb a,#7 mov b,a a2h1: mov a,b clr c anl a,#11110000b ;just in case jz a2h2 setb c a2h2: mov a,b pop b ret hexdump: dump: mov r2, #16 ;number of lines to print lcall newline lcall newline dump1: mov a, r7 lcall phex mov a, r6 lcall phex mov a,#':' lcall cout lcall space mov r3, #16 ;r3 counts # of bytes to print mov dpl, r6 mov dph, r7 dump2: clr a movc a, @a+dptr inc dptr lcall phex ;print each byte in hex lcall space djnz r3, dump2 lcall space ;print a couple extra space lcall space mov r3, #16 mov dpl, r6 mov dph, r7 dump3: clr a movc a, @a+dptr inc dptr anl a, #01111111b ;avoid unprintable characters clr c subb a, #20h jnc dump4 ;avoid control characters mov a, #(' ' - 20h) dump4: add a, #20h lcall cout djnz r3, dump3 lcall newline mov r6, dpl mov r7, dph djnz r2, dump1 ;loop back up to print next line dump5: lcall newline ret