protocol is really more of a configuration than a defined protocol — as I just stated, it uses open-baudmode communications and a text header that starts with the “!” character. For
example, when we want to send a command to the PSC, we use
the header “!SC” at the beginning of each command message.
Let’s be logical, shall we, and use “!PSX” as the header for our
PlayStation Helper. Okay then, let’s wait for the header:
Main:
char = RX_BYTE
IF char <> “!” THEN Main
char = RX_BYTE
IF char <> “P” THEN Main
char = RX_BYTE
IF char <> “S” THEN Main
char = RX_BYTE
IF char <> “X” THEN Main
You see, I told you it was simple. We grab one character at a time, compare it to the header sequence, and jump
back to Main if anything is out of whack. Now, if you’re new
to SX/B you’re probably wondering how this can work, that
is, having a comparison between incoming serial bytes.
This works fine because the SX is running assembly
language and even at the 4 MHz clock we’re using each
instruction only takes 0.25 microseconds! At 38.4k baud,
each bit is 26 microseconds long so there is plenty of time
during the stop bit to get the comparison done. Remember,
this code gets compiled to assembly language. Here’s a
small section of the compiled code:
Main:
CALL @__RX_BYTE
MOV char, W
CJNE char, #”!”, @Main
The first line calls the RX_BYTE subroutine — note
that @ is used so the subroutine call can cross code pages.
On return, the value that was received is retrieved from the
W (working) register; this takes one cycle. The comparison
is just one line of assembly code, but is a compound statement that takes either four or six cycles, depending on the
comparison result. Still, in the worst case, we’ve only
consumed seven cycles — 1.75 microseconds — during the
26 microsecond window between bytes. I’m not suggesting
we go crazy and try to squeeze a whole lot more (in actual
fact a few more cycles are consumed with the call to and
return from the RX_BYTE subroutine), but I want you to
rest easy that when compiled, we can do the comparison as
shown without any fear of missing the next serial byte.
Okay, speaking of serial bytes, let’s look at the code
that handles that:
RX_BYTE:
SERIN Sio, Baud, temp1
IF temp1 >= “a” THEN
IF temp1 <= “z” THEN
temp1 = temp1 - $20
ENDIF
ENDIF
RETURN temp1
This subroutine actually serves two purposes: it
receives the serial byte and if the byte is a lowercase letter,
it gets converted to uppercase. This subroutine points out
20 January 2006
one of the changes in SX/B as it has matured and
developed an expanding customer base, specifically the
ability to return a value to the subroutine caller. As we saw
in the compiled code above, the W register is used as the
mechanism for handling the return value.
Let me emphasize one final time the reason for this
subroutine: SERIN is a complex statement that requires
several lines of assembly code. If we were to use SERIN
every place in the program that required serial input, we
would use a lot of code space with redundant code. And, by
encapsulating SERIN in a subroutine, we’re able to add the
lowercase-to-uppercase conversion feature.
Now that we have the header, the next step is to
process receive and process the command byte sent by the
host controller:
Get_Command:
char = RX_BYTE
IF char = “V” THEN Show_Version
IF char = “T” THEN Get_Type
IF char = “S” THEN Get_Status
IF char = “B” THEN Get_Buttons
IF char = “J” THEN Get_Joysticks
IF char = “C” THEN Config_IoPort
IF char = “W” THEN Write_IoPort
IF char = “R” THEN Read_IoPort
GOTO Main
After receiving the command byte, the program simply
compares it to the list of commands available to the program.
You may think that LOOKDOWN and BRANCH would be more
efficient, but in practice, it doesn’t use any less code (after
being compiled) and it’s not quite as easy to follow in my book.
The first command is “V” for version; this is a good idea
to include in your designs, especially if you’re selling them
as products and making incremental improvements.
Providing a version number allows the end user to design
around the features available in the product he has. On
receiving the “V” command, the PlayStation Helper will send
back a three-byte version string. Here’s the top level code:
Show_Version:
WAIT_MS 1
TX_OUT Version
GOTO Main
There’s no big mystery here; the only thing you may
wonder about is the WAIT_MS 1 line. This inserts a one millisecond delay before returning the version string so that
the BASIC Stamp can load up its SERIN instruction to
receive the data from the SX. Here’s the code for WAIT_MS:
WAIT_MS:
temp1 = __PARAM1
IF __PARAMCNT = 1 THEN
temp2 = 1
ELSE
temp2 = __PARAM2
ENDIF
IF temp1 > 0 THEN
IF temp2 > 0 THEN
PAUSE temp1 * temp2
ENDIF
ENDIF
RETURN