Previously I created a subroutine to print a string (6502, Print via Subroutine) that works just fine. It prints an entire string in one CIO call. However, it always puts an end of line (EOL) character at the end, thus moving the cursor to the next row and left-most column. Thats fine most of time, but there are times when you want to display something right after something else – like “Score: xxx”. You could build the string then print it out, or you could just print each component separately. Or maybe you want to print something and have the cursor remain at the next print position – like an input form “Name? “.
I set out to print a string one character at a time and not end up with the EOL. Printing one character at a time means calling CIO over and over which is much slower than the previous print method I used. But it does have an one advantage in that it allows you to do something with each character such as make a sound (click click click). I thought it would be easy to convert the print string subroutine to do it character by character; it was not. In completing this task I learned about assembly pointers and the importance of page zero (first 256 bytes of memory) addresses. I had to read a lot and got some helpful explanations from members of AtariAge (http://www.atariage.com).
Instructions
This involves the following instructions; some new, and some recent that I didn’t document before:
- LDX – LoaD the X register with a value.
- LDY – LoaD the Y register with a value.
- STX – STore the X register value in a location.
- STY – STore the Y register value in a location.
- BEQ – Branch if EQual. Alters run location if the processors zero flag is set, otherwise nothing happens.
- BNE – Branch if Not Equal. Alters run location if the processors zero flag is clear, otherwise nothing happens.
- .BYTE – Directive telling the Assembler a data byte (or more) is coming.
In this exercise I also created a subroutine to position the cursor. It adds a few bytes of code to the overall size for this example. I examined code size after adding successive calls. Having it in a subroutine starts paying off at the fifth call.
The Code
10 *= $3600 15 PRINDX = *+1 20 PRSPTR = $CB 39 ; Defines 70 CIOV = $E456 71 ICHID = $0340 ;IOCB 0 S: 72 ICCOM = $0342 ;IOCB Command 73 ICBAL = $0344 ;Xfer Buffer Adr 74 ICBAH = $0345 75 ICBLL = $0348 ;Buffer Len 76 ICBLH = $0349 77 ROWCRS = 84 78 COLCRS = 85 80 EOL = $9B 81 EOS = $00 82 CLS = $7D 0300 ; Test printing 0302 TEST 0310 LDA #SCLR/256 0312 LDX #SCLR&255 0314 JSR PRTCH 0320 LDX #0 0322 LDY #0 0324 JSR CURXY 0326 LDA #SRULE/256 0328 LDX #SRULE&255 0330 JSR PRTCH 0340 LDX #0 0350 LDY #2 0360 JSR CURXY 0370 LDA #SHI/256 0380 LDX #SHI&255 0390 JSR PRTCH 0430 LDA #SBYE/256 0440 LDX #SBYE&255 0450 JSR PRTCH 0460 RTS 5000 ; Print Subroutine 5005 PRTCH 5010 ; Store Starting String Address 5015 STX PRSPTR 5020 STA PRSPTR+1 5030 LDA #$00 5040 STA PRINDX 5050 ; Set IOCB Command 5052 LDX #$00 5055 LDA #$0B 5060 STA ICCOM,X 5070 ; Set Max Buffer Length 5072 LDA #$00 5080 STA ICBLL,X 5090 STA ICBLH,X 5100 ; Call CIO to Print 5105 PRCHLP LDY PRINDX 5108 LDA (PRSPTR),Y 5115 BEQ PRCHDN 5116 JSR CIOV 5120 INC PRINDX 5130 BNE PRCHLP 5140 PRCHDN RTS 6000 ; Position Subroutine 6005 CURXY 6010 STX COLCRS 6020 STY ROWCRS 6030 RTS 010000 ; String Data 010001 SCLR .BYTE CLS 010002 SRULE .BYTE "012345678901234567890123456789",EOS 010005 SHI .BYTE "Hello World: ",EOS 010010 SBYE .BYTE "Goodbye!",EOS
Breakdown
Again I won’t break down every line, just the ones that are new concepts. Line 5108 is the magic of the print routine. It requires the page zero address to work, which is noted by line 20.
- Line 15: Reserves one byte of memory in a location named PRINDX. Because the program origin is set to $3600 in line 10, the address of PRINDX will be $3601.
- Line 20: Assigns $CB to PRSPTR. This is the page zero address I use to store the string address to be printed. A pointer – it points to the real string address in memory. A page zero address is needed for the “LDA (addr),Y” command which uses Indexed Indirect addressing.
- Lines 70 to 76: Definitions for CIO and IOCB addresses.
- Lines 77 and 78: Definitions for cursor ROW and COLumn memory addresses.
- Lines 80 to 82: Definitions related to printing strings.
- Lines 310 to 314: Loads the Accumulator and X register with the address of the string to be printed (SCLR) and calls the print subroutine (PRTCH).
- Lines 320 to 324: Loads the X and Y registers with the desired screen coordinates, then calls the cursor position subroutine (CURXY).
- Lines 326 to 330: Loads the Accumulator and X register with the address of the string to be printed (SRULE) and calls the print subroutine (PRTCH).
- Lines 340 to 360: Loads the X and Y registers with the desired screen coordinates, then calls the cursor position subroutine (CURXY).
- Lines 370 to 390: Loads the Accumulator and X register with the address of the string to be printed (SHI) and calls the print subroutine (PRTCH).
- Lines 430 to 450: Loads the Accumulator and X register with the address of the string to be printed (SBYE) and calls the print subroutine (PRTCH).
- Line 5005: Start of the print char string routine. This prints the string one character at a time up to the end of string (0).
- Lines 5015 and 5025: Store the address bytes of the string to be printed (from the Accumulator and X register) in the page zero pointer location ($CB).
- Lines 5030 and 5040: Load the accumulator with 0 and store it in the PRINDX location. This will be the value of the current character position within the string.
- Lines 5052 to 5060: Load the X register with 0 and Accumulator with 11 ($0B). $0B is the IOCB command to print a buffer. Store $0B in the IOCB command address.
- Lines 5072 to 5090: Load the Accumulator with 0 and store it in the IOCB buffer length addresses. If the value is 0 only 1 character is printed. The character to be printed must be loaded into the Accumulator prior to calling CIO.
- Line 5105: This is the start of the print loop. Each character to be printed will pass through this loop. First the Y register is loaded with the current index that was stored in PRINDX.
- Line 5108: Load the Accumulator with the value of the address pointed to by the contents of location PRSPTR, but offset by the current print index (the value in the Y register).
- Line 5115: If the value loaded was 0 the string is done, so branch to the end of the print subroutine (location PRCHDN).
- Line 5116: Everything is setup to print, call CIO to make it happen.
- Line 5120: Increment the current print index position (value in location PRINDX) by one.
- Line 5130: Jump back to the top of the loop to print the next character.
- Line 5140: End of the print subroutine, labeled with PRCHDN. Issue RTS and control returns to line following the JSR that initiated the PRTCH subroutine.
- Line 6005: Start of the cursor position subroutine.
- Line 6010: Store the value in the X register into the cursor column address.
- Line 6020: Store the value in the Y register into the cursor row address.
- Line 6030: End of the cursor position subroutine. Issue RTS and control returns to the line following the JSR that initiated the PRTCH subroutine.
- Line 10001 to 10010: String data used for output.
Assembly
After assembly with Mac/65, I drop back to DOS (SpartaDOS), then execute the program (assembled as “PRINTCH.COM”). As you can see in this screen shot, it works. The screen is cleared, then the ruler line is printed at 0,0 (upper left). Then 2 lines down “Hello World: ” is printed, followed by “Goodbye!”.
What to tackle next? Hrm…
Pingback: 6502, Printing Subroutines Again | Unfinished Bitness