C64 Bedtime Coding (ENG) – Printing (#08)

(Article by Phaze101)

Printing

 

Key Notes

I had to take a break because work got in the way and I couldn’t do otherwise. Like everyone else I have commitments and need to do what is more important.

Secondly, I wanted to set up a team to help me out with my articles. I was overwhelmed by the number of responses I got regarding this matter. I wish to thank all those that replied back to me and wanted to help out.

Future Articles

Articles are becoming longer and more complex. I will still try to maintain a weekly article, but I won’t exclude the possibility of occasionally skipping a week. Also, some topics need further research. I am not an encyclopaedia and do not know it all. This can take time.

Longer articles also need more time to be translated to Italian. Hopefully this will become easier with a team behind me.

Windows / Linux / MAC

This may be a controversial topic but I ask you to consider it from my point of view. I want to teach assembly in the simplest way possible. It is key to someone trying to learn assembly. Unfortunately, CBM Prog Studio only runs on Windows. I believe it is the easiest IDE program for a beginner. Initially I considered whether I should use CBM Prog Studio or Kick Assembler. Kick Assembler runs on all platforms, but it is not as easy as CBM Prog Studio and it has no IDE. In the end I decided to go with CBM Prog Studio.

I realize that there might be some of you that use only Linux or Mac and they might feel excluded, but this still doesn’t justify the reason not to use CBM Prg Studio. I have decided to keep things as simple as possible since I know that learning assembly is not easy. If the tool I use is not easy enough it will make things harder.  Using something like Kick Assembler with all its complexities won’t help at all. I see it as a future article, but not for now.

Another look at Screen Clearing / Fill

The previous article covered screen clearing or fill. In this section I will cover this subject a bit more. Now that you understood how the basic method works, I want to show you a different method. More than the method itself it is the way of thinking. In assembly, you will often need to think differently.

All methods we saw previously started from top left-hand corner of the screen, which takes 1000 bytes of memory and starts at address location \$0400 till the bottom right at \$07E7 (1024-2023). Now, how about instead of starting from top left hand corner we start from bottom right hand corner. This means location 2023 or \$07E7.

So, let’s write some code that will go downwards from 2023 to 1024 to clear / fill the screen. This is actually going to be a fill.

This method does not go beyond the 1000 characters hence it is good for game code, since the 24 bytes beyond \$07E7 that are reserved for sprites, are not touched.

And here is the screen output

Remember to type SYS 49152 to run the program.

 

Let us examine it

Lines 52 to 55

These are the same as Method 3 as per Article 7. The only difference is that we are loading the memory locations with \$0700 and not \$0400. This means we are starting from the end, or more specifically from the bottom segment of the screen (characters 768 – 999).

Lines 57 to 59

This should be obvious but covering just in case. We are initialising index register Y with \$E7 which is 231. This is the bottom right hand corner when added to the address \$0700, that is, \$07E7.

We are clearing the screen in 4 segments and so we loop 4 times, counting backwards. Hence, we initialise index register X with 4.

The A register is loaded with our fill character.

Line 60

This is an interesting line. I will come back to this line later. Try to remove this line and see what happens. You will understand why, as we go along.

Lines 62 to 68

These lines are identical to Article 7, Method 3. The only difference is that in line 66 we decrement the Zero Page memory location at \$FC since we at going from bottom to top of the screen. Also, we are decrementing Y as we are filling characters from bottom to top.

The main advantage of this method is that the loops on X and Y are designed to stop when their values are \$00. When either X or Y reaches zero, the Zero Flag of the status register is set. This means that there is no need to compare their value with a CPX or CPY instruction, which normally sets the Zero flag if the compared value is equal. One can look at this is that you get a free CPX #\$00 after each DEX and a free CPY #\$00 after each DEY. So, when X or Y reach \$00, the BNE (Branch if Not Equal) instruction will automatically fall through to the next instruction because the Zero Flag will be set.

Line 60 again

When we remove this line, why do we end up with one memory location not filled in? Remember we are doing a decrement here. When X register is 4, we do a decrement on Y Register in line 64 and then compare to check if Y is zero and so, we never get to write at that location which is \$0700. While in the successive loops that is X is 3 or less we do get to write when the Y register is 0. Try to work it out in your head or pretend to be the CPU to execute such code and you will see what I mean.

Line 66 decrements the value at \$FC to be \$06, Y is still zero and X becomes 3. As soon as we loop we write the contents of our A register to \$0600 since Y is 0. The next decrement of Y, Y becomes \$255 hence we then write the contents of the accumulator to \$06FF and then we continue to decrease this till we reach \$0601. When it is 0 is has already been written at \$0600 so we do not need to worry, and we decrement again the value at \$FC. I hope this make sense. To summarize, the subroutine essentially writes bytes in the following sequence of addresses:

(before loop) \$0700,
(X = 4) \$07E7, \$07E6, …, \$0701,
(X = 3) \$0600, \$06FF, \$06FE, …, \$0601,
(X = 2), \$0500, \$05FF, \$05FE, …, \$0501,
(X = 1), \$400, \$4FF, \$4FE, …, \$401

Screen Clearing / Fill Update

 

My dear friend Colin Vella pointed out to me a variant of my Screen Clear / Fill that might be more intuitive for beginners. He was right it could be. What he pointed out to me was interesting although to be honest technically I am not so comfortable with it.

Here is the updated code

The difference between my code and the suggested alternative by Colin is that we swap line 63 and 64. This does work since STA does not set the Zero Flag. Hence, the BNE instruction on line 65 will trigger according to the Zero flag set or cleared by DEY in line 63.

What I am not comfortable with, is that normally before a branch we use an instruction that sets status register flags, which is not the case here. This approach can easily lead to bugs in your code and should be avoided when possible.

An important change is that the index register Y is set to \$E8 instead of \$E7, to account for the fact that in the loop, we are first decrementing Y and then writing the fill value.

Finally, the last change is the elimination of STA \$0700, since in this variant, the routine writes to the memory in a linear fashion, starting from \$07E7 all the way down to \$0400.

Besides that, everything else remains the same.

 

Assembler Directives

We have arrived at a point where we will need to use more assembler directives. These are not CPU instructions and do not form part of the 6502 Instruction Set but are related to the assembler. The assembler has features that makes our life easier and this is what Assembler Directives are for.

Directive =

The equal symbol is used to define constants in an assembler program. Constants are fixed and they cannot be reassigned to another value.

So, the following

ChrOut = \$FFD2

This means that from now on the hex address \$FFD2 can be referred to as ChrOut. During assembly, all instances of “ChrOut” are replaced by \$FF02.

We have already used the Equal symbol directive with

  • = \$C000

For the assembler, * is a special symbol that refers to the memory address entry point where the assembler should output the machine code. Therefore, when we say * = \$C000 we are telling the assembler to write the following code starting at the memory address \$C000.

Low Byte (<), High Byte (>)

We stated several times that the 6502 needs 16bit address to be stored in a Low Byte, High Byte format.

If we want to get the Low Byte and High Byte of a memory address, we use the < and > symbols. For example, suppose we have some text labelled “String” at memory address \$8040.

LDA #<String

If we refer to the label with the symbol < in front of it, it means get the low byte of the address of the label, that is \$40.

LDA #>String

If we refer to the label with the symbol > in front of it, it  means get the high byte of the address of the label, that is \$80.

Plus (+) or Minus (-)

We can use arithmetic operations to define constant using mathematical expressions. However, these constants are evaluated be the assembler and the final values are used in the code. For example, if we have

ScrStart = \$400
ScrEnd  =  ScrStart + \$3E7


LDA        #ScrEnd

Them the assembler will replace ScrEnd with \$7E7 and generate an instruction for LDA #\$7E7.

Text Directive

Text (String Text) can be stored as either PETSCII or CBM codes, for example:

text “a”,”b” – note  “ ” are  used for PETSCII character

text ‘a’,’b’    – note  ‘ ‘  are used for screen display code

 

 

 

 

Print

 

In this article I will not be able to cover all the printing methods that I would like to talk about. We cover what is available by the Kernal and the Basic rom.

Print – Method 1

Printing to the screen using assembly and the fact that Commodore uses screen codes is always a challenge.

The first Kernal routine we are going to use is called ChrOut and it is found at address \$FFD2. ChrOut prints characters one at the time hence you will need a loop to print a whole string. Also, it prints at the current cursor position wherever this happens to be. The character to be printed needs to be loaded in the accumulator before calling the routine.

In this article I have changed my Main entry routine. I have started to organise the code better in blocks. Main is the entry point of the program and it is from where it starts execution.

Here is the Main entry routine.

Line 38 in the listing above, is a call to the routine that clears the screen. This is followed by a call to the routine that prints some text to the screen and as per previous examples, we call the WaitForSpace routine to wait for the user to press the Spacebar and return back to Basic.

As you are already familiar with clearing the screen and waiting for the spacebar, we are really interested in the PrintText routine.

Here is the code for our print routine.

Explanation

 

Lines 56 and 57

Here we are defining our constants. We assign the constant ChrOut the value of \$FFD2 and the constant ZP for Zero Page with the first available address for us to use.

I like to define the constants as close as possible to the routine, but this is not always possible. It depends from the routine. Another good location for constants is at the top, after the declaration of the program entry point.

Please note that String is a zero terminated string.

Lines 62 to 65

This code is similar to previous examples, except that we are using assembler directives. We are loading and storing the low byte of the address where the label String is defined, and we are doing the same for the Hight byte. ZP and ZP+1 means we are using and storing the values at Zero Page addresses \$FB and \$FC.

Lines 67 to 72

We have initialised register Y with 0 and in line 70 we get the value stored at the address value that we have loaded previously in the Zero Page addresses \$FB and \$FC, in other words, the location of the label String.

The text data stored at label String is a sequence of characters that ends with a 0. Hence, when looping through the label data, if the value at the address is a 0 then we know we have reached the end of the string and we jump to the Finish label which returns us to the routine after PrintText. If not, then we call the Kernal ROM routine to print that character in the A register.

Lines 73 to 76

Once we print the character, we increment Y to get the next character. If Y is not equal to 0 then we loop.  If Y is equal to 0 then we need to increment the High Byte at address \$FB since our string is longer than 256 characters.

The good thing about this routine is that is can do more than 256 characters and actually it can fill the whole screen if we want to.

As stated, the program returns to the Main routine when the last character of the string is a 0. The WaitForSpace routine is then invoked to wait for us to press the Space Bar.

After compiling and running the program, remember to type SYS 49152 to execute the code when it is loaded in the emulator.

Here is a screen capture of the screen

Here is a full listing of the code

Breakout Bonus

Since RetroProgramming Italia is running a Breakout programming contest, here is a string that will draw our game screen.

Please note we haven’t covered colour, but you should still be able to get an idea of what is happening.

Also note that each line is 40 characters long and this will only work if you are using CBM Prg Studio.

Here is what you should see.

Print Method 2

This routine will be based on two Rom routines.

The first one is called the Set Cursor routine, located is at address \$E50C. This routine requires that you pass the X value or row value of the screen in the X register. You also pass the column value in the Y register. These needs to be setup before you call the routine.

The second routine is called Print String, located at address \$AB1E.  It prints a string of not longer than 256 characters. Before you call this routine, you need to set the A register with the Low Byte address of the String and the Y register with the high byte String address.

Here is a listing of our routine that prints some text

Overview

This routine sets the cursor at row 12 Column 8 to print a string

Explanation

 

Line 53 and 54

We set our constants to the 2 routines SetCursor and PrintString.

Lines 59 to 61

This is the set cursor position part. We load the X register with row number 12 and the Y register with the column number 8. We then call the ROM routine SetCursor to position the cursor at row 12 and column 8.

Lines 63 to 65

This is the part where we print our string. We load the low byte of the string in the A register and the High byte in the Y register. We then call the ROM routine PrintString to print the string at the position we set previously with SetCursor.

Here is the output when you run this program with SYS 49152:

These two routines are quite handy when you have one string to handle but it becomes complex when you have more since you will need to create some sort of table. This is an advanced topic that I would rather discuss in a future article.

It is possible to design a full screen using this approach, for example, by printing 4 strings of 250 characters each. However, for something more complex, like scrolling text in an adventure game, this approach is very impractical.

Here is a listing of the full code

Next Article – 18th May or Later

That’s it for now. This article should get you to started thinking differently in terms of how to approach 6502 assembler. I think that for assembly language you need to think completely differently than how you would think in other programming languages. This article only touches the surface on text printing, but enough to get you started.

The next article may be published next week or the week after. It all depends on personal commitments and free time. Right now, I am a bit overloaded, but I would like to continue to commit every week. As always life gets in our way and we sometimes cannot do otherwise.

The good news is that now I have a team behind me that can help me out with things.

As always if you have any questions please message me. That is all for now.

Coding is Fun 😊

Phaze 101

English

Article 7
https://sys64738.org/2019/04/c64-bedtime-coding-eng-clearing-the-screen-07/

Article 6
https://sys64738.org/2019/04/c64-bedtime-coding-eng-first-steps-06/

Article 5
https://sys64738.org/2019/04/c64-bedtime-coding-eng-the-instruction-set-05/

Article 4
https://sys64738.org/2019/03/c64-bedtime-coding-eng-addressing-modes-04/

Article 3
https://sys64738.org/2019/03/c64-bedtime-coding-eng-the-cpu-registers-03/

Article 2
https://sys64738.org/2019/03/c64-bedtime-coding-eng-machine-language-02/

Article 1
https://sys64738.org/2019/03/c64-bedtime-coding-introduction-basics-01/

 

Have your say