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!
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
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:
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);
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;
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!