Friday, June 27, 2014

NES Controllers

The NES has serial ports for the controllers. The advantage of this is that it is possible to have different controllers and other peripherals. The downside is that you need to read the input one bit at a time. The serial ports are located at memory addresses $4016 and $4017. The order that the bits are read is dependent on the controller that is plugged in, but as most games use the normal NES controller, the bit order is A, B, Select, Start, Up, Down, Left, Right.

The controllers need to be told to send their state to the serial port. This is done by a method known as strobing a port. Essentially this is simply writing a 1 to the port you wish to read followed by a 0 to the port you want to read. You then read the port 8 times for a standard controller, possibly more for other controllers. The low order bit (1) is used to tell you the state of the button being read while the next bit (2) indicates if a controller is attached. Other bits may also have significance, but I have not been able to find any information on this.

It is possible to have controller reading routines that incorporate the logic as the bits are tested, but I prefer having the bits combined into a single byte that can then be tested using boolean operations. This is the code for the first controller, the second controller works the same except you use $4017 instead of $4016.

ReadController1:
; strobe the controllers so will send button bits
LDA #$01
STA $4016
LDA #$00
STA $4016
; now loop through buttons to build joystick value
; a,b,select,start,up,down,left,right
LDY #$00
LDX #$08
ReadController1_pollJoy1:
LDA $4016 ; read bit
AND #$01 ; check if set (button down)
BEQ ReadController1_adjustJoyBits
INY ; as bit 0 is 0 will set bit!
ReadController1_adjustJoyBits:
TYA
DEX ; loop through button bits
; done here so won't over-shift flagset
BEQ ReadController1_done
ASL A ; shift button flag bits over
TAY
JMP ReadController1_pollJoy1
ReadController1_done:
RTS

One potential problem with the controller is that it is possible for the sound chip to cause interference with the serial bits resulting in the wrong value. This is a fairly easy to solve problem. Simply read the controller until the value returned is the same as the previous read.

A couple of convenience functions that I like to have, which are handy to have for things such as title screens and other times where you need to wait for button presses, are WaitForButtonPress and WaitForButtonPressEnd. These simply wait for a button press or the end of a button press.

WaitForButtonPress:
JSR ReadController1
AND #$F0
BEQ WaitForButtonPress
RTS

WaitForButtonPressEnd:
JSR ReadController1
AND #$F0
BNE WaitForButtonPressEnd
RTS

I also wrote a function that displays what controller buttons are pressed but as this is fairly simple code, I am not including it here. If you are interested in the code, it is still part of the Trivia source code (NES Trivia included in the game zip file.) even though it is not actually used.

The next couple of weeks will take a look at my first RPG Maker VXAce game. First a postmortem of the game and then a look at my thoughts on RPG Maker and what I am hoping the next version of this tool would have to make it perfect for my needs (not that my wishes are likely to happen but you never know).

Saturday, June 21, 2014

NES String Utilities

There are a number of additional functions that may not be necessary but are nice to have if you are creating a text heavy game. One print-related function that I love to have, even if it is a very simple function, is a way of repeatedly printing an indicated character an indicated number of times. On the NES it is simply a matter of a loop sending the desired character to the PPU. Still, having it as a separate function will save a few bytes per use so it doesn't hurt to have as its own function.

PrintRepeatedChar: 
STA $2007
DEX 
BNE PrintRepeatedChar
RTS

If you are manipulating strings on the fly, something that will be used in the RPG that is my ultimate goal, appending characters and strings is handy. Appending a character simply requires that the terminating zero be replaced with the character to be appended and the byte after that character be replaced with a terminating zero. As this code is fairly similar to the AppendString function I will not bother showing the code here but feel free to look it up in the source file.

The AppendString function adds string pointed to by SOURCE_PTR to the end of the string pointed to by DEST_PTR. Note that DEST_PTR is modified  in the process to set up a call to StrNCpy. Our first goal is to find end index of DEST_PTR which points to the string being appended.

AppendString: 
LDY #0 
AppendString_loop: 
LDA [DEST_PTR],Y 
BEQ AppendString_doAppend 
INY 
CPY #$FF
BEQ AppendString_done 
JMP AppendString_loop


Once we know the length of the string we are appending we adjust the DEST_PTR to point to the end of the string. We can then simply call the StrNCpy function to finish. It is possible to save a few cycles by jumping to StrNCpy and taking advantage of its RTS but this is considered a bad practice that should only be used for optimizing.

AppendString_doAppend: 
; index in TEMP so it can be used with math instructions 
STY TEMP 
; Subtract index from 0 so we know maximum number of characters that 
; can be copied 
LDA #0 
SEC SBC TEMP 
; This is placed in X for subsequent StrNCpy call 
TAX 
; Add index to DEST_PTR 
LDA DEST_PTR 
CLC 
ADC TEMP 
BCC AppendString_StrNCpy 
INC DEST_PTR+1 
AppendString_StrNCpy: 
STA DEST_PTR ; now do the append via StrNCpy 
JSR StrNCpy 
AppendString_done: 
RTS


SubStr copies the middle portion of an existing string into a new string. Creating a new string from the middle portion of an existing string is another handy function but is really just a call to StrNCpy with an adjusted SOURCE_PTR. This is fairly straightforward code so will not be included here but is in the source file.

Finally the library includes a StrLength function. As we have done something like this in the Append String function we really don't need to show the code here. The big difference is we use SOURCE_PTR instead of DEST_PTR.

Now we have an adequate string library for our upcoming projects. Before we can get to creating the trivia game we still have one NES topic to cover. Controller input.

Friday, June 13, 2014

StrNCpy and StrNCmp

Copying strings is pretty much just your regular memory copy operation with a couple of twists. First, we have no idea how many bytes need to be copied. Second, we need to make sure to write a zero at the end of the string so that the copied string will always be a valid string.

This function requires two pointers be set up. The SOURCE_PTR points to string to be copied while the DEST_PTR pointer where string is copied to. We also use the X Register to hold the maximum length of the string (including null terminator) to be copied, with a length of 0 being an entire page.

First we set the character to copy to zero. We then use zero page indirect addressing to load a character from the source string. If this character is a zero then the copying is done. Because our last action is always to write an ending zero, we can branch right to our ending code. If not a zero then we write the character to the destination location.

StrNCpy:
        LDY #0
StrNCpy_loop:
        LDA [SOURCE_PTR],Y
        BEQ StrNCpy_done
        STA [DEST_PTR],Y

The X Register is deducted to make sure the maximum copy length had not been reached. If it has been then we force an overwrite of the last character with a zero.

        DEX
        BEQ StrNCpy_lengthReached
        INY
        JMP StrNCpy_loop
StrNCpy_lengthReached:
        LDA #0  ; Make sure last character a zero
StrNCpy_done:
        STA [DEST_PTR],Y
        RTS

String comparison is fairly similar which makes it a fairly costly operation. The longer the strings being compared the longer the operation will take. This is why it irks me when I see programmers use strings when integer constants could be used. This is thankfully not as bad a situation as it could be. Many string class libraries use hash values when comparing strings. This technique has the cost of computing a hash value, but if strings are immutable as they are in many modern languages then the hash value will only be computed once. While hash values result in a simple and fast integer comparison for non-matching strings, hashes are not  unique so if a match is found then you still need to check each character to make sure the strings match.

We are not computing or checking hash values, but are checking if the source and destination pointers are the same. This takes a few cycles but can save a lot of work. This also lets us cheat by using string pointers as our integer constants for prompts and other text-based menus.

StrNCmp:
        LDY #0
        LDA SOURCE_PTR
        CMP DEST_PTR
        BNE StrNCmp_compareLoop
        LDA SOURCE_PTR+1
        CMP DEST_PTR+1
        BNE StrNCmp_compareLoop
        ; If we reach here, same pointer so must match
        LDA #0
        RTS

The second phase is a byte by byte comparison of the strings. The logic here is pretty similar to the string copy except we are comparing instead of copying.

StrNCmp_compareLoop:
        LDA [SOURCE_PTR],Y
        BEQ StrNCmp_done
        CMP [DEST_PTR],Y
        BNE StrNCmp_done
        DEX             ; Make sure not comparing beyond limit
        BEQ StrNCmp_done
        INY             ; Now set next byte and continue
        JMP StrNCmp_compareLoop

Finally we determine the comparison result. We do this the same way the CMP instruction does by simply subtracting the compared byte from the source byte.

StrNCmp_done:
        SEC
        SBC [DEST_PTR],Y
        RTS

That covers the most important parts of our string library but there are still a few functions that I want to write which will be covered next.

Friday, June 6, 2014

NES Printing Strings

The whole purpose of having strings is to be able to display them. The print string function should support multiple screens and allow the location of the text to be specified. Because setting the PPU address to a screen location is handy to have even if we aren't writing a string, the SetScreenXY function will be created first. It will be called using the x / y registers to hold the x / y coordinates and the accumulator holding the quadrant of the screen to draw the text to.

The first thing the function does is multiplies the quadrant  by 4 then adding the high byte of quadrant 0 to this. This is the equivalent of base_address = 1024 + quadrant_base. Next we want to multiply Y by 32 and add it to this address. I take advantage of the fact that the result of this multiplication would result in the upper 5 bits of Y being added to the high order byte so a tiny bit of bit manipulation saves a lot of looping logic. Finally, as we know the lower 5 bits of the low-order byte are clear, we can simply OR the X value with the PPU address. This may seem complicated, but looking at the code should help.

SetScreenXY:
; Calculate PPU Address for screen quadrant
ASL A
ASL A
CLC
ADC #$20
STA PPU_HIGH
; Add high portion of 32*y to high PPU address
TYA
LSR A
LSR A
LSR A
CLC
ADC PPU_HIGH
STA PPU_HIGH
; Calculate low byte of y * 32
TYA
; AND #$07 not necessary as 0 is always shifted in
ASL A
ASL A
ASL A
ASL A
ASL A
; add X to it
STA PPU_LOW
TXA
ORA PPU_LOW
STA PPU_LOW
LDA $2002             ; read PPU status to reset the high/low latch
LDA PPU_HIGH
STA $2006             ; write the high byte of screen address
LDA PPU_LOW
STA $2006             ; write the low byte of screen address
RTS


The PrintString function uses the SOURCE_PTR to hold the string to print, which it prints at the current PPU address. This means that SetScreenXY should be called before printing. Concurrent calls to PrintString will continue printing where the last call left off. The cursor position is not tracked, though if you needed this functionality it would be fairly easy to add. This is not something I will need so the printing functions are being kept to their simplest form. This function is a loop that prints characters until either 256 characters are printed or a zero character is reached. The length-checking code is free as a JMP would be needed if the BNE wasn't used.

PrintString:
LDY #0
PrintString_loop:
LDA [SOURCE_PTR],Y
BEQ PrintString_done
STA $2007
INY
BEQ PrintString_done
JMP PrintString_loop
PrintString_done:
RTS


The problem with these functions is that there a bunch of code required to set up the parameters. Wouldn't it be great if we could call a function like this: "CallPrintStringAt labelOfString, x,y,screenQuardrant"?  This is what macros are for. Different assemblers will have different ways of setting up macros. This is the way it is done in NESAsm:

CallPrintStringAt .macro
LDA #LOW(\1)
STA SOURCE_PTR
LDA #HIGH(\1)
STA SOURCE_PTR+1
LDX #\2
LDY #\3
LDA #\4
JSR SetScreenXY
JSR PrintString
.endm

The \# sections are the parameters. The code is inserted as shown with the parameters being pasted as they are into the macro. For complicated functions that require setting up multiple registers and variables, macros can save a lot of time. They can also make code easier to read. It is important to remember that the code for the macro is inserted into the program every time the macro is used so it is not a substitute for functions. But they certainly make calling functions easier. The downside to macros is that the code is just pasted as is so some optimizations that could be done (such as if you know a register or memory address is already correct) are not.

Printing works great except for one problem. Color. As was explained in earlier posts, setting attributes is kind of complicated and colors are set for 2x2 blocks. The ClearScreen function sets the attributes. We will not cover the ClearScreen function as we have written similar code for HelloNES and HelloSprites already. Next we will be looking at string copying and comparisons which will include a bit of a rant.