Introduction: Presenting the GameSquare!

I wanted to design my own Raspberry Pi handheld gaming system. I logged most of my gaming hours in the early '90s. So I have a soft spot for games from that era, and I particularly like the Wii Classic controller for its SNES-like layout.

I knew from a previous project that Wii controllers can communicate over an I2C bus such as the one available on a Raspberry Pi. I chose a 5" LCD screen based on being a similar width as a Wii Classic Controller.

When designing the case I realized the dimensions were roughly square. So I decided to make it square and the name came naturally as an homage to the GameCube.

The case is 160 mm x 160 mm x 37 mm. The face has D-pad, select (-), home, start (+), A, B, X, and Y. The back has the L and R buttons. The bottom has a headphone jack and micro B USB jack for charging. The right side has access to two of the Raspberry Pi USB ports which is good for connecting a keyboard. The top has heat vents, a power switch for the external amp/speaker, a volume knob, a halt button, a power switch, and an LED that indicates battery high or low. In initial tests I am getting about two hours of play time running on only the battery while using the external amp/speaker and with the Wi-Fi turned on.

Parts List:

Purchase the Inkscape design file for the laser cut case here. My case was laser cut by Ponoko using a P2 sheet of Amber Bamboo.

Step 1: Hardware Assembly

Case:

The case slots together from the laser cut parts, and the screws and standoffs hold it together. The four longer (31 mm) standoffs go in the four corners of the case, and the four shorter standoffs (25 mm) go in the back of the case to hold the controller PCB in place. The screws fit snugly in the laser cut holes, so take care to ease them in the first time.

The two switches, three buttons, one potentiometer, headphone jack, and micro USB jack panel mount on the case. The LCD is hot-glued on the face plate. The controller buttons just sit on the PCB and are carefully wiggled into place when the face plate is fastened. Velcro holds the battery, the speaker, and the Raspberry Pi in place. I also used some Lego to hold the Raspberry Pi in place when plugging in a USB device. Hot glue and electrical tape were used as needed to fasten parts and to prevent accidental short circuits.

LCD screen:

The plastic outer case can be pried open. The third photo above shows where the composite video (yellow), ground (black), and 5V (blue) wires are to be soldered to the control board for the LCD. (I used different colored wires in the final assembly shown in the first and second photos above.)

Wii Classic Controller:

Open the controller case with a tri-wing screwdriver, and separate the PCB with a phillips screwdriver. Desolder all the connections at J1, J2, J3, J4, ZL, and ZR. Save the main PCB and the buttons and membranes on face of the controller. I snipped off an empty corner of the PCB under J4 to make it fit in the case.

Battery:

In initial tests the 2500 mAh LiPo battery provides about 2 hours of play time. When the battery goes low, the LED next to the power switch turns off. The next thing that happens is the I2C bus stops working. This is problematic since system halt (without an attached keyboard) relies on buttons connected to the I2C bus. (If I can stop playing video games) I may add functionality to monitor the Powerboost 1000C LBO pin with a Raspberry Pi pin to force a system halt when it drops to ground (indicating low battery).

Powerboost 1000C:

Powerboost 1000C documentation

PAM8302 audio amplifier:

PAM8302 datasheet

Wiring:

The wiring schematic shows all the relevant parts except the Raspberry Pi 3. The Raspberry Pi pins are simply labeled. This is a good reference for the forty-pin connector. The PP# pins are labeled on the underside of the PCB near the analog audio/video TRRS connector. I made all the Raspberry Pi connections by soldering to pins on the underside.

Step 2: Software

The Raspberry Pi 3 is running the current standard Raspbian image. Here are the relevant contents of my /boot/config.txt file.

gpu_mem_1024=512

overscan_scale=1

overscan_left=-16

overscan_right=0

overscan_top=-16

overscan_bottom=-16

framebuffer_width=400

framebuffer_height=240

sdtv_mode=0

sdtv_aspect=3

I wrote two custom programs: "WiiClassicPi" allows the Wii Classic Controller plus volume knob and halt button to control the Raspberry Pi, and "emu.py" is a bare-bones file browser for launching games.

WiiClassicPi code and configuration:

WiiClassicPi reads the Wii Classic Controller data over I2C and translates it to virtual keyboard presses thru the linux uinput device and system commands.

Set up the uinput device by adding a line for "uinput" in /etc/modules. Make it accessible for all users by adding the following line to /etc/udev/rules.d/99-com.rules:

KERNEL=="uinput",MODE="0666"

After a reboot, confirm that it shows up with read/write privileges for all.

pi@raspberrypi:~ $ ls -l /dev/uinput

crw-rw-rw- 1 root root 10, 223 Feb 2 13:36 /dev/uinput

I installed libsuinput to generate keyboard presses thru /dev/uinput. I installed Wiring Pi for the I2C library. I2C can be enabled on the Raspberry Pi using raspi-config.

The delay times after I2C transactions can be customized at the top of my code. The delay times provide enough time for I2C transactions to occur. They probably can be less, but the values I've chosen seem to work okay. According to wikipedia, input lag becomes distracting around 200 ms, so keep the delay times much less than that.

The valid key codes to be listed in the keys[] array can be found in /usr/include/linux/input.h. The key codes should be listed in the same order as the commented list of Wii Classic buttons.

Notice that the ZL button is mapped along with the select (-) button to perform a system halt, and the LX analog joystick is mapped to the 10 K potentiometer and used to adjust the volume.

Also, line 147 of the code contains a workaround for a glitch. When the Raspberry Pi is running something CPU intensive (like an emulator) the I2C read sometimes results in garbage (0xFF)) in the 2nd and 3rd of the 6 bytes. So the code throws away those reads and moves on. This might cause some noticeable controller latency if too many reads in a row produce garbage, but I haven't noticed any problems.

Compile using: gcc -lwiringPi -lsuinput WiiClassicPi.c -oWiiClassicPi

Run as "./WiiClassicPi verbose" to see controller data.

emu.py code and configuration:

I initially considered using RetroPie, however it would not recognize the virtual keyboard presses from /dev/uinput. I also find it way too complex for such a simple thing as launching an emulator, especially on a small screen.

So I wrote emu.py, a Python 3 barebone curses-based frontend for launching emulators. All the customization happens at the top of the code. The keys are mapped to work with WiiClassicPi (except for the exit key (F1) to be used with an attached keyboard), and the extensions, directories, and launch commands have corresponding entries for each system.

# GUI keys

exit_key = curses.KEY_F1

up_key = curses.KEY_UP

down_key = curses.KEY_DOWN

left_key = curses.KEY_LEFT # back 1 page

right_key = curses.KEY_RIGHT # forward 1 page

launch_key = ord("j")

a_key = ord("x") # back 5 pages

b_key = ord("z") # forward 5 pages

# rom file extensions

file_ext = [".gb", ".gbc", ".nes", ".smc"]

# rom directories

romdir = ["/home/pi/roms/gb", "/home/pi/roms/gbc", "/home/pi/roms/nes", "/home/pi/roms/snes"]

# emulator launch commands

emulator = ["/home/pi/emu/./gambatte_sdl -i j g x z up down left right -s 8",

"/home/pi/emu/./gambatte_sdl -i j g x z up down left right -s 8",

"mednafen",

"/home/pi/emu/./snes9x"]

Comments

author
ThirdEarthDesign (author)2017-05-16

Great job, very neat build :-)

author

Thanks!