8-Bit Guy released a SNES game pad adapter as part of his PETSCII Robots game launch.
Seemed a great project to demonstrate how we can do low-level programming in TRSE!
Missed earlier Turb0 Rascal posts? Previously we looked at getting started with TRSE and then the basic structure of Turbo Rascal programs.
One of the really cool things about TRSE is it crosses the entire spectrum of low to high level. At the lowest we have access to the target machine hardware directly and via inline assembler. One the other hand we have now a growing Object Orientation ability, which means we can create very abstracted classes and libraries.
Obviously in this example we are leaning on the features offered by the former, but at some point I hope to turn this example code into a useful class.
Hardware-Interfacing: SNES Adapter Unit Code
On the Commodore machines, accessing the hardware is as simple as knowing which memory addresses do what.
The user port is at address $DD01, but before we can use it we need to tell our machine that certain pins are inputs or outputs. This will be familiar to anyone who has done any Arduino or Raspberry Pi hardware hacking!
As the SNES controller has 12 buttons in total, we set up variables for each. Yes, I am wasting valuable memory – ideally they would have been bit flags but it is what it is.
Unlike, say, an Atari joystick, the SNES controller uses a Shift Register to send the status of each button one at a time. So to read the controller takes a POKE
to pulse the GPIO to tell the controller to give us an update then a POKE
pulse on the adapter pin for each individual read via PEEK
.
unit SNES;
var
// Memory location of the C64 user port B
const USER_PORT: address=$DD01;
// Port B data direction register
const PORTB_DIR: address=$DD03;
// Direction and button constants
SNES_A: byte=0;
SNES_B: byte=0;
SNES_X: byte=0;
SNES_Y: byte=0;
SNES_UP: byte=0;
SNES_DOWN: byte=0;
SNES_LEFT: byte=0;
SNES_RIGHT: byte=0;
SNES_LS: byte=0;
SNES_RS: byte=0;
SNES_SELECT: byte=0;
SNES_START: byte=0;
procedure init();
begin
// Initialise GPIO pins
// Set pins 3 and 5 as output
poke(PORTB_DIR, 0, %00101000);
end;
// READ pin 6
function read_data():byte;
var
pin_value: byte=0;
begin
// Check the pin value then ping the clock
if((peek(USER_PORT,0) & %01000000) = %01000000) then
begin
pin_value:=0;
end
else
begin
pin_value:=1;
end;
// Pulse the clock on pin 3
poke(USER_PORT, 0, %00001000);
poke(USER_PORT, 0, %00000000);
// Return pin status
read_data:=pin_value;
end;
// Get the 12 value of the
// buttons on the SNES pad
procedure snes_read();
begin
// Pulse GPIO pin 5
poke(USER_PORT, 0, %00100000);
poke(USER_PORT, 0, %00000000);
// Read from the serialized data
SNES_B :=read_data();
SNES_Y :=read_data();
SNES_SELECT :=read_data();
SNES_START :=read_data();
SNES_UP :=read_data();
SNES_DOWN :=read_data();
SNES_LEFT :=read_data();
SNES_RIGHT :=read_data();
SNES_A :=read_data();
SNES_X :=read_data();
SNES_LS :=read_data();
SNES_RS :=read_data();
end;
end.
Using the SNES Unit
Using the unit is really easy. First you tell TRSE that you are using the SNES:
@use "snes"
Then you get access to the snes_read()
function which populates the current status of the controller.
After that it just takes a check to see if any of the buttons are being pressed and take action accordingly:
// Loop forever
while(1) do
begin
SNES::snes_read();
snes_print();
if(SNES::SNES_A=1) then
begin
moveto(10,23,hi(SCREEN_CHAR_LOC));
printString("AAAA! ", 0, 11);
end
else
begin
moveto(10,23,hi(SCREEN_CHAR_LOC));
printString(" ", 0, 11);
end;
end;
Testing With a Game?
If you have been following me on social media, or have been following the TRSE chatter, you might have seen I have been developing a cross-platform text unit for TRSE (more on that in a future post) that has a demo game included.
How about we add SNES control to the game?
// Read controller
SNES::snes_read();
// Check the pressed buttons
if(SNES::SNES_UP=1 and y>0) then dec(y);
if(SNES::SNES_DOWN=1 and y<23) then inc(y);
if(SNES::SNES_LEFT=1 and x>0) then dec(x);
if(SNES::SNES_RIGHT=1 and x<21) then inc(x);
// Find out if the space we want to move to
// is empty or if it contains anything special
charat:=txt::get_char_at(x,y);
// $20 is space
case charat of
$20: txt::put_char_at(x,y,64);
It works!

Ah crap, perhaps it works too well. Instead of one move to the left it massively over-shoots as if we held the button down for a long time.
The problem is my SNES controller code is checking to see if the button is held down, not checking for a press.
Not an ideal solution but I can quickly modify my code to make sure the button is released like so:
// "de-bounce"
while(SNES::SNES_UP+SNES::SNES_DOWN+SNES::SNES_LEFT+SNES::SNES_RIGHT<>0) do
begin
// Read controller
SNES::snes_read();
end;

Bottom Line
OK, the game isn’t going to win awards, but hopefully my point is made that using TRSE we can not just do the usual things of text, sprites, etc but also we have full access to the target computer hardware, and not only that, but we can use that feature in as high or as low level as we want by building up libraries of code in Units!
Next up, we develop a new, cross-platform game!
Remember to check out (and share!) Part 1 and Part 2