I need to apologise to you all for publishing this article so late. I skipped the date by more than I planned to. As always life got in the way, especially work. I also had to do some other things that I felt I had abandoned for some time. The code of the article was ready immediately after Article 8 but to find time and write it all has taken longer than planned.
Another reason why the articles are taking longer now is because to prepare the code, more research is involved, and the code is getting longer.
Due to this, the articles are going to be released randomly and not on every Saturday. However, when there is a release in English, it will always be on a Saturday. The Italian version will follow later.
You will be delighted to know that from now on, the code will be on Pastebin.
So, for those that like to copy and paste, you now have this option. I might also include the previous code, depending on available time.
In the future I might also use Github since there will be graphics and sounds to be loaded and so far, Pastebin doesn’t permit this. Eventually, it would be cool to have everything in one directory.
From Previous Article
The Commodore 64 doesn’t have a Print At command in Basic. However, it can achieve this in several different ways.
In Article 8, we saw that by using some system routines like SetCursor (\$E50C) and PrintString (\$AB1E), we were able to tell the computer to print a string at a certain position. However, I think you all noticed that this was very slow, even in assembly. We therefore need a faster way of printing to the screen.
To achieve this, we are going to build our own Print At routine and that is what we will be showing in this article.
Print At Concept
We know that the screen starts at \$0400 by default on the C64. We also know that the screen is 40 characters wide by 25 lines which can be considered liked 40 columns by 25 rows.
Let’s say we want to print the famous words “Phaze 101” somewhere on the 13th row and the 16th column, which is roughly the middle of the screen. To make calculations simple, we will start counting columns and rows from 0, and hence we will be printing on row 12 and column 15.
To calculate the offset into the screen buffer, we first multiply 12 by 40 (12 rows each of 40 characters). This gives us the position of the first, leftmost column (0) on row 12 (the 13th row). To get to desired column, we then add 15. Then, we add this offset to the screen base address of \$0400 (1024) and this should give us the starting screen position where to write the first character.
This should be
- 12 * 40 = 480 ;12 row by 40 characters
- + 15 = 495 ;on the 13th row we start at column 14 hence we add this
- + 1024 = 1519 ;Screen base is \$0400 which is 1024 and we add this.
If we start writing to this memory location 1519 our string, we should be able to see “Phaze 101” printed on the middle of the screen.
Addition and Multiplication
That is easier said than done. How do you do addition and multiplication in 6502 assembly language programming when each byte can only hold 256 values?
This is a future article by itself since this is quite an important topic and here, we are talking of whole numbers only.
First, I am going to simplify the above by doing everything using addition. The second thing is that we will use 2 bytes to do our calculations and store our results. With 2 bytes we have from 0 to 65535 possible values.
We can achieve the multiplication above by adding 40, 12 times. Please note here I am using this method because I haven’t covered mathematics in assembly yet. There are other ways of doing this however for our objective to print on the screen it is more than enough.
Adding Numbers in Assembly
To add numbers in 6502 assembly, we will introduce the following instructions: CLD, CLC, ADC
CLD means Clear Decimal Flag. We use this instruction to make sure the CPU is not using decimal mode. We will cover this in more detail in a future article.
CLC means Clear Carry Flag. Before you start any addition in 6502 assembly language, you need to make sure that the carry flag from a previous addition is not added to the next addition.
ADC means ADD with Carry. The instruction adds the contents of a memory address or an immediate number to the accumulator register A. If the carry flag is set (e.g. from a previous addition), then the result will also be incremented by 1. For example, when A is zero and the Carry flag is clear, ADC #\$04 stores \$04 in the accumulator. However, if the Carry flag is set, the accumulator would contain \$05.
The result of ADC is stored in the accumulator and hence you usually need to store it in memory using STA.
8 Bit Addition
Let’s examine the carry flag more closely in an 8 bit addition. In the example that follows, the carry flag is not set
The result of the addition can be stored in a byte. In this case, the carry flag is cleared.
Now let’s look at something that will set the carry flag
The result of the above addition cannot fit in one byte. The part of the result that fits in the accumulator is the lower byte value \$8C. The 9th bit of the result that does not fit in the 8-bit accumulator is stored in the carry flag.
Below is the code for the 1 byte addition above and leaving the value in the carry flag.
So where is the carried value? Well, the carry is still set in the carry flag, and we need to somehow figure out how to store it.
16 Bit Addition
The solution to the previous problem is to use 2 bytes and hence store the carry flag in the second byte.
Let’s look at a program to add 16-bit numbers (2 bytes). Note that this concept can be extended to add 24bit (3bytes) and 32bit numbers (4 bytes).
Let’s assume that memory location \$00FB contains \$FF and that memory location \$00FC contains \$0.
Here is the program that will add \$8D to the memory location \$00FB and store the carry flag in \$00FC.
This will store the result \$8C in \$FB and \$01\$ in FC which will make the number a 2-byte number or 16-bit number.
The most important thing to note here is that ADC means add Accumulator and CARRY. So, if the Carry flag is set, even if the accumulator is 0 we still have a 1.
Back to the Code
Now that we know how to do a 16bit addition, we can go back to the code to do our Print At.
Here is the full listing of the code in Pastebin
The main routine calls 5 subroutines. Four of the 5 subroutines take parameters in the registers. I have written these routines like this to get you in the habit of writing yours like this too.
First one is the SetBgBorder. This sets the background and border colours. The X register is used to set the background colour and Y register is used to set the border colour.
The next subroutine is the ClearScreen. This clears the screen with the value passed in the accumulator which in this case is a space.
Next subroutine is the SetCurs. It needs a Y value (line or row number, starting from 0) of the screen in the Y register and the X value (column number starting from 0) in the X register. In this case we pass row number 12 and column number 9. We will look at this routine in more detail later on.
The next subroutine is the PrintAt. We tell it where the string we want to print is located by passing Low byte and High byte to the A and Y registers respectively. More on this later.
The last routine is the usual one to wait for the spacebar to be pressed.
I will be covering only the SetCurs and PrintAT routines, since the other routines have been covered in earlier articles.
At the bottom of the routine you will find three word (2-byte) locations: Buf1, Buf2 and Buf3. The first one is initialised with the screen base value of \$0400. The others are both set to zero at this moment in time.
Each of the three steps of the formula we covered earlier is a section in the routine as indicated by the comments.
Lines 105 to 114
This is the same addition routine we covered earlier (lines 105 to 112), however, we are doing this loop 12 times and each time adding 40 (\$28). The result of the addition is stored in Buf2 16bit word or 2 bytes.
Lines 118 to 125
Again, we are using addition to add the number of columns which was passed in the X register to Buf2.
Lines 129 to 137
We are adding the Buf1 number which is the start of the screen with the offset stored in Buf2 and storing the result in Buf3.
Buf3 will hold the address on the screen from where we start to write to the screen.
This routine is actually a copying routine. It copies the contents from one memory location (this is where our string is stored) to another which in this case is the screen memory location.
Lines 153 and 154
We are initialising the Zero Page addresses \$00FB and \$00FC with the source address which is the address of our string.
Lines 156 and 159
We are initialising the Zero Page addresses \$00FD and \$00FE with the destination address which is the address held in Buf3 where we will need to start writing to the screen. This was calculated by the previous call to SetCurs.
Lines 161 to 169
This is our copying code. We copy the string to the screen till we encounter a zero byte. Please note that maximum string length is 256 characters.
Here is the output of the program. Remember to enter SYS 49152 to run the program.
That is all for now. We will continue with Advanced PrintAt, however beyond that I have not decided yet.
I have these 3 things in my head that needs to be covered
- Addition, subtraction, multiplication, division of 16-bit numbers
- Random numbers
As always, if you have any questions please message me.
Coding is Fun 😊
Thanks Go To
English proofreading: Colin Vella
Translation to Italian: Davide Aldegheri
Italian proofreading: David La Monaca
Other team members: Pinov Vox and John Sherwood
Retro Programming Italia
You can also read all previous articles on SYS32768.ORG