GamODROID-C0: An ODROID-Based Portable Retro Gaming Console

ODROID Magazine ODROID-Based Portable Retro Gaming Console

This article is about yet another homemade portable gaming console as a sequel to the first one that I built (http://bit.ly/2yFj4th). On the first build, I used an ODROID-W (pi clone) and a brand new GameBoy case. For this new project, I wanted something more powerful to run N64, Dreamcast and PSX games, but also some native Linux game. There are not a lot of low power consumption options with sufficient CPU+GPU for that, so I chose an ODROID-C0. Moreover, instead of using and transforming an existing case, I used a 3d printed one designed by myself with optimized dimensions and form factor. I want to thank the ODROID community (forum.ODROID.com), and in particular @meveric for his debian distribution and ODROID-optimized packages to make the GamODROID-C0.

Components

Here is a list of all components I used for this build:

Main parts:

  • ODROID-C0
  • 8GB eMMC module
  • 128 Gb MicroSD XC (SanDisk Ultra, XC I, class 10)
  • 3.5″ NTSC/PAL TFT Display (http://bit.ly/2yUyXgd)
  • A 4x6cm prototype PCB board

Audio Parts :

  • Stereo 2.8W Class D audio amp
  • 2 PSP 2000/3000 speakers
  • A cheap USB sound card with a small USB cable

Battery Parts:

  • 2 LiPo batteries : Keeppower 16650 3.7v 2500mA protected
  • 2 MOLEX connectors, 50079-8100
  • 2 MOLEX receptacle, 51021-0200

Control parts :

Cooling parts :

  • 2 PS3 GPU copper heatsink
  • 4 15x15mm copper heatsink
  • Some 1mm Thermal Pad
  • Some Silicon Thermal paste

Various other electronic parts:

  • A 3mm blue led
  • Some wires from an old IDE ribbon cable
  • Some breadboard connection wires
  • 3 resistors

Decoration parts:

  • Some Nail polish templates for colors (black, yellow, red, green, blue)
  • 200, 600 and 1200 sandpaper
  • XTC 3D (http://bit.ly/2fG3l5O)
  • White satin spray paint

Anticipated power consumption

The main sources of power drain are the ODROID-C0, the display and the audio system (soundcard and audio amp). Before starting, I measured the consumption of these 3 components:

  • ODROID-C0 : 200-400 mAh depending on CPU and GPU usage
  • Audio system : 310 mAh
  • Display : 420 mAh

It’s a total of 1130 mAh at 5v, so 5650 mAh / hour. The batteries I used are (at least) 3.7v x 5000 mA for a total of 18500 mA. The console should last more than 3h in all cases.

Why use a display with such poor resolution ?

There are several reasons for that: low power, 60 FPS, easy wiring, and it’s blurry like old TV which makes cool hardware anti-aliasing.

Why use cylindrical batteries?

It’s more a matter of space optimization regarding the capacity I wanted. Using a more classical flat battery would have forced me to make a case deeper than 2cm, although that was my first intention.

Why use a prototype board to mount additional components?

The goal was to easily mount all the components as one unique motherboard, and I can actually say, it was useful!

Why the need for an analog multiplexer?

The ODROID-C0 provides only 2 analog inputs, and one is already used to report the battery level. Thus, only 1 analog input was available for a total of 4 analog axis (2 thumb sticks with 2 directions each). The only way of reading 4 analog axis with one analog input was a multiplexer. And fortunately, the ODROID-C0 has enough digital pins to use 2 of them for analog channels switching.

Why use eMMC module vs. microSD?

The eMMC is much faster than a microSD. It allows the console to boot in a few seconds even with Xorg, a window manager, and Emulation Station with lots of games. I use the eMMC for the operating system, and the microSD for the games and video previews.

3D printed case

The console case has been modeled with Freecad. I designed it specifically for this project and the very specific size of the motherboard and all components. It was my first 3D model and first 3D print, so it may contains errors. However, the Freecad files are available on GitHub (http://bit.ly/2fGJWRU) and the STL files are freely distributed on Thingverse (http://bit.ly/2xW9FAh).

ODROID Figure 1 - Front internal view of the case. The black points are marks to make holes for skewing.
Figure 1 - Front internal view of the case. The black points are marks to make holes for skewing.
ODROID Figures 2 and 3 - Back internal view of the case. You can see batteries space at the bottom and some striations for CPU + GPU thermal dissipation.
Figures 2 and 3 - Back internal view of the case. You can see batteries space at the bottom and some striations for CPU + GPU thermal dissipation.

ODROID Figures 2 and 3 - Back internal view of the case. You can see batteries space at the bottom and some striations for CPU + GPU thermal dissipation.

The whole case is nearly the same as a Nintendo DS. It may not be obvious, but using the dimensions of a well known console allowed me to find good and cheap protection cases. As you can see on photos later, I used an NDS case to protect my GamODROID-C0, which I found for a few Euros.

To obtain a nice finish, I first used 600 and 1200 sandpaper on all parts. Then, I used a product called XTC-3D. It’s awesome and give a nice brilliant finish, but it’s still not a good enough finish for me. I used some 1200 sandpaper again before using a white satin aerosol painting. This gave me the finish that you see on photos below.

For small parts like buttons and the D-pad, I used some nail polish. It’s very cheap and actually provided great brilliant finish. I finalized the buttons and D-pad with some transparent nail varnish to protect colors, since buttons are the most used part of the console.

ODROID Figure 4 - Hardware Assembly
Figure 4 - Hardware Assembly

My goal was to build a one-piece motherboard in order to make it more robust and easier to put inside the case. I also built small boards for buttons, D-pad and start/select buttons.

Display hack

The hack is roughly the same as the one I did for my Retroboy console (http://bit.ly/2yFj4th). However, there was some differences on the connector side : V-in and composite output was reversed this time. Figure 5 shows the original display, as found on the Adafruit website.

ODROID Figure 5 - 3.5” display
Figure 5 - 3.5” display

I first removed the white connector, then wired the V-in directly to the voltage regulator output and added two wires for powering through one of the ODROID 5V pin, as shown in Figure 6.

ODROID Figure 6 - Closeup of the ODROID 5V wiring
Figure 6 - Closeup of the ODROID 5V wiring

Sound card

I chose a cheap USB sound card with a wire between the board and the USB connector. It was important because it was easier to unsolder.

ODROID Figure 7 - USB sound card
Figure 7 - USB sound card

I started to dismantle wires, connectors and then re-drilled the holes. I prepared the ODROID board by adding pins to the first USB connector, as shown in Figures 8 and 9. Finally, I soldered the sound card directly on the pins, as shown in Figure 10.

ODROID Figures 8, 9 and 10 - Modifications to the sound card
Figures 8, 9 and 10 - Modifications to the sound card

ODROID Figures 8, 9 and 10 - Modifications to the sound card

ODROID Figures 8, 9 and 10 - Modifications to the sound card

Extension board with USB port

I put the extension board just below the USB sound card. I first soldered a USB connector, then I wired it to the second ODROID USB connector through the extension board. Note that I also soldered the extension board to the ODROID motherboard to make the whole thing more robust.

ODROID Figures 11, 12 and 13 - Attaching the extension board to the ODROID
Figures 11, 12 and 13 - Attaching the extension board to the ODROID

ODROID Figures 11, 12 and 13 - Attaching the extension board to the ODROID

ODROID Figures 11, 12 and 13 - Attaching the extension board to the ODROID

Finishing audio on the extension board

Having a sound card with analog output is nice, but a 3.5 audio jack and a good amp to drive the speakers is better, which was exactly the next step: wiring and soldering components on the extension board.

ODROID Figures 14, 15 and 16 - Wiring and soldering the components onto the extension board)
Figures 14, 15 and 16 - Wiring and soldering the components onto the extension board)

ODROID Figures 14, 15 and 16 - Wiring and soldering the components onto the extension board)

ODROID Figures 14, 15 and 16 - Wiring and soldering the components onto the extension board)

Analog multiplexer wiring

ODROID Figure 17 - Soldering diagram for the extension board
Figure 17 - Soldering diagram for the extension board

The soldering of this small piece started to add a lot of wires and ended up filling the extension board. I had to use the following: Vdd (Vin), Vss (ground), x (analog output), x0, x1, x2, x3 (analog inputs), A, B (digital switches). C was not needed as 2 switches were enough to switch the first 4 outputs. Vee and INH has been wired to ground. Note that I made a voltage divider bridge between x (output) and the analog input of the ODROID. This is because the PSP analog sticks and MC14051B operate in 5V whereas the ODROID-C0 analog input accept a maximum of 1.8v.

ODROID Figures 18 and 19 - Closeup of the analog multiplexer wiring
Figures 18 and 19 - Closeup of the analog multiplexer wiring

ODROID Figures 18 and 19 - Closeup of the analog multiplexer wiring

Volume buttons

You may have noticed on the previous photo that there were also 2 push buttons on one edge of the extension board. I wired them to GPIO pins to control audio volume, as shown in Figure 20.

ODROID Figure 20 - Volume button wiring
Figure 20 - Volume button wiring

Start/Select buttons

I used push buttons for start and select buttons. I mounted them on an small additional board together with a blue led for battery monitoring.

ODROID Figure 21 - Start and select button wiring
Figure 21 - Start and select button wiring

Batteries

As indicated before, I used a pair of protected cylindrical LiPo batteries. I wired them in parallel to get 5000 mA. I had to solder some wires directly onto the batteries and added a Molex connector to be able to connect the two wired batteries to the ODROID-C0 LiPo connector.

ODROID Figure 22 - Details of the battery wiring
Figure 22 - Details of the battery wiring

Mounting the components

At this time, I had done everything on the hardware side. I started to mount in the front part of the case the display, analog sticks, D-pad, a-b-x-y boards and L1 + R1 buttons. The display is not glued but maintained with two traversal bars. As you will see in Figure 23, these bars allowed me to also block and drive all of the wires.

ODROID Figures 23 and 24 - Steps of the final assembly of the components inside the case
Figures 23 and 24 - Steps of the final assembly of the components inside the case

ODROID Figures 23 and 24 - Steps of the final assembly of the components inside the case

The next step for the front part of the case is adding speakers, start/select buttons board and wiring everything with a common ground. The final steps before closing are to add a heat-sink, putting L2+R2 buttons and the motherboard in the back part of the case, then soldering everything to GPIO. Note also the yellow wire which is the composite output of the ODROID that go to the display input 1.

ODROID Figures 25 and 26 - All of the components are fitted into the case before closing
Figures 25 and 26 - All of the components are fitted into the case before closing

ODROID Figures 25 and 26 - All of the components are fitted into the case before closing

ODROID Figures 27, 28 and 29 - The outside of the case after final assembly
Figures 27, 28 and 29 - The outside of the case after final assembly

ODROID Figures 27, 28 and 29 - The outside of the case after final assembly

ODROID Figures 27, 28 and 29 - The outside of the case after final assembly

Software

I created a script that constructed 80% of the system including a copy of specific configuration files. The other 20% are for ROMs and personal customization. If someone wants to do the same, it should be quite easy to adapt and re-run the script.

Before starting to comment the install script, here are the preparatory install steps that I did:

  • Deployment of @meveric’s minimal Debian Jessie image on the eMMC (http://bit.ly/2yF2PML)
  • Created two partitions on the 128GB microSD: 4 Gb for save states and the rest for ROMs, which will be mounted at /mnt/states and /mnt/ressources). I did 2 partitions because I had the intention to create a read-only system except for states, but I finally kept a full read/write system.
  • Created a GameODROID folder in /root and copied the install script and its dependencies

Installation Script

The installation script and all dependencies can be found on GitHub at http://bit.ly/2fGJWRU. It is organized with functions dedicated for each steps.

The first executed function creates custom mount points, copies custom fstab and activates tmpfs:

function fstab
{
 echo "fstab and filesystem"
 
 mkdir -p /mnt/states
 mkdir -p /mnt/ressources
 
 cp /root/GameODROID/fstab /etc/fstab
 
 sed -i "s/#RAMLOCK=yes/RAMLOCK=yes/" /etc/default/tmpfs
 sed -i "s/#RAMSHM=yes/RAMSHM=yes/" /etc/default/tmpfs
}

The custom fstab file allows to change mount options in order to optimize for speed (noatime, discard) and use a small tmpfs partition for /var/log:

tmpfs /var/log tmpfs nodev,nosuid,noatime,size=20M 0 0

After this first function, the system is rebooted, then upgraded and rebooted again:

function uptodate
{
 echo "update"
 apt-get update
 apt-get upgrade
 apt-get dist-upgrade
}

The final step of this stage is to install all of the necessary base packages (function syspackages). There is nothing special here except for two things:

  • evilwm : I had to use a window manager because some native games can’t find the native screen resolution without it. I found that evilwm was a very good candidate for the console, since it is very light and invisible with default settings.
  • Antimicro-ODROID : it’s a very nice piece of software that I did not know about before. It allows me to map any keyboard and mouse event to the joypad.
  • Python package evdev: used to configure reicast input
  • I used an ODROID C1/C0 specific xorg configuration file supplied by @meveric (http://bit.ly/2xaSonP)

Games

This part correspond to the functions “emulators”, “emulators_glupen64_meveric” and nativegames. Except for Dreamcast games for which I used reicase, all other emulators are part of Retroarch:

  • pcsx-rearmed (PSX)
  • fbalpha (CPS2)
  • gambatte (Gameboy color)
  • gpsp (Gameboy advance)
  • mednafen-pce-fast (Pc-Engine + Cdrom)
  • nestopia (Nes)
  • picodrive (Sega 32X, SegaCD)
  • pocketnes (Snes)
  • genesis-plus-gx (GameGear, Genesis, MasterSystem)
  • mednafen-ngp (Neogeo pocket color)

For native games, I selected those that were enjoyable with a gamepad and running correctly on the ODROID-C0 with a small screen:

  • hurrican
  • hcraft
  • frogatto
  • SuperMario War
  • astromenace
  • neverball
  • shmupacabra
  • aquaria
  • Revolt
  • Open JK3
  • openjazz
  • supertuxkart
  • mars
  • puzzlemoppet
  • opentyrian
  • pushover

Game launcher

This corresponds to the function “userinterface”. Initially, I wanted to use Attract mode. Unfortunately, the implementation of GLES on ODROID-C0/C1 does not seem to include glBlendEquationSeparateOES() and glBlendFuncSeparateOES() functions, which are mandatory to compile libFSML, which in turn is mandatory to compile Attract mode. Thus, I used the latest Emulation Station version with video preview support. Since I wanted to change the default splash screen with a custom one, I had to replace “splash_svg.cpp” file in “EmulationStation/data/converted”. This file is a simple C array that contains the bytes of an SVG file. Despite the classical configuration of systems, I create a specific one that list two scripts to change the display: internal screen or HDMI (see the composite.sh and hdmi.sh scripts).

Specific tools

This correspond to the function “localtools”. This is mainly to handle the custom GPIO gamepad. I had to wrote a small program in C that creates a gamepad through Linux’s uinput and poll GPIO to generate events. I used polling instead of IRQ because the SoC does not have enough IRQ to handle all the buttons. I named this tool gpio_joypad and the source code is on GitHub at http://bit.ly/2xaTdgp. It also handles the analog multiplexer to get left and right analog thumb sticks values.

Boot config file

This correspond to the function “bootini”. This function consists in copying a customized boot.ini file to the boot partition. The important changes I made are:

  • Keeping only two video modes : cvbs480 (activated by default) and vga (commented out)
  • Disabled cec and vpu
  • Modified kernel arguments:
    • “cvbsmode=480cvbs” to get a 60Hz NTSC resolution instead of 50 Hz PAL
    • “max_freq=1824” to overclock the SoC (needed for N64 and Dreamcast emulators)
    • “quiet loglevel=3 rd.systemd.show_status=false udev.log-priority=3” to make the boot as quiet as possible

Initially, I wanted to display the splash screen early during the boot process. It is well documented on ODROID wiki, but unfortunately it works only for 720p resolutions.

Launch everything at start

This correspond to the function “startup”. The automatic startup of X and Emulationstation at boot consisted in a custom tty1 service in systemd that launch agetty with autologin, a BASH profile that launch X when tty variable = tty1, and finally a xinitrc that start the window manager and Emulation Station.

/etc/systemd/system/getty@tty1.service.d/override.conf

[Service]
 
ExecStart=
 
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
The bash /root/.profile :
# ~/.profile: executed by Bourne-compatible login shells.
 
if [ "$BASH" ]; then
 if [ -f ~/.bashrc ]; then
 . ~/.bashrc
 fi
fi
 
if [ "$(tty)" = "/dev/tty1" ] ; then
 /usr/local/bin/battery.sh &
 /usr/local/bin/gpio-joypad &
 startx -- -nocursor 2>&1 &
fi
 
mesg n

/root/.xinitrc

# a WM is needed some software are correctly sized in full screen
# e.g : emulationstation, rvgl
evilwm & pid=$!
 
emulationstation.sh &
 
# this allows not to shutdown X when emulation is killed
# We want that because we have to kill it after gamelaunch
# else it does not reappear on screen (SDL_Createwindow() does never end)
wait $pid
Note that the bash profile start the joypad driver (gpio_joypad) and the battery monitoring script (battery.sh) before starting X.
The battery monitoring script is not very accurate, but I dit not found any way to make a better monitoring to switch on the led on low battery or when charging:
#!/bin/bash
 
PIN=75
GPIO=/sys/class/gpio
ACCESS=$GPIO/gpio$PIN
LOWBAT=780
CHARGING=1020
 
if [ ! -d $ACCESS ] ; then
 echo $PIN > $GPIO/export
 echo out > $ACCESS/direction
 echo 0 > $ACCESS/value
fi
 
while true
do
 ADCVAL=$(cat /sys/class/saradc/saradc_ch0)
# echo "value : $ADCVAL" 
 # charging
 if [ $ADCVAL -gt $CHARGING ]; then
 echo 1 > $ACCESS/value
 else
 # low bat
 if [ $ADCVAL -lt $LOWBAT ]; then
 echo 1 > $ACCESS/value
 sleep 1
 echo 0 > $ACCESS/value
 else
 echo 0 > $ACCESS/value
 fi
 fi
 
 sleep 2
done

Finalize and clean up

This correspond to the function “optimize_system”. In this function, the BASH login message is hidden (to make the boot process as silent as possible) and packages cache is cleaned (apt-get clean). There are also two configuration files that are deployed. The custom journald.conf is here to write logs in ram instead of disk for better performance:

[Journal]
Storage=volatile
I also created a specific alsa configuration file to add latency and buffers, so most sound stutering are avoided for n64 and dreamcast games:
pcm.!default {
 type plug
 slave.pcm "softvol"
 ttable.0.1 0.8
 ttable.1.0 0.8
}
pcm.dmixer {
 type dmix
 ipc_key 1024
 slave {
 pcm "hw:1,0"
 period_time 0
 period_size 2048
 buffer_size 65536
 rate 44100
 }
 bindings {
 0 0
 1 1
 }
}
pcm.dsnooper {
 type dsnoop
 ipc_key 1024
 slave {
 pcm "hw:1,0"
 channels 2
 period_time 0
 period_size 2048
 buffer_size 65536
 rate 44100
 }
 bindings {
 0 0
 1 1
 }
}
pcm.softvol {
 type softvol
 slave { pcm "dmixer" }
 control {
 name "Master"
 card 1
 }
}
ctl.!default {
 type hw
 card 1
}
ctl.softvol {
 type hw
 card 1
}
ctl.dmixer {
 type hw
 card 1
}

Global Retroarch configuration

Despite changing buttons and path, I had to adapt some videos parameters of retroarch (root/.config/retroarch/retroarch.cfg) to optimize performance and better suit the GamODROID-C0 hardware:

video_refresh_rate = "59.950001"
video_monitor_index = "0"
video_fullscreen_x = "720"
video_fullscreen_y = "480"
video_vsync = "true"
video_threaded = "true"
video_force_aspect = "true"

Core-specific configuration

I also did some adjustments on a of the emulator cores:

Allowing 6 buttons for SegaCD and 32X:

picodrive_input1 = "6 button pad"
Changing glupen64 parameters to optimize rendering on the ODROID SoC:
glupen64-cpucore = "dynamic_recompiler"
glupen64-rspmode = "HLE"
glupen64-43screensize = "320x240"
glupen64-BilinearMode = "standard"
Allowing PSX analog joypad support:
pcsx_rearmed_pad1type = "analog"

For the Dreamcast emulator, I used reicast-joyconfig (http://bit.ly/2fLE1yH) to generate the gamepad config and copied the resulting file to /root/.config/reicast/joy.conf. I also changed the fullscreen resolution to adapt it to the CVBS display:

[x11]
fullscreen = 1
height = 480
width = 720

Keyboard and mouse mapping for native games

Some native games work fine, but require either a mouse or a keyboard for special keys such as Esc, Enter, Space, Shift and the arrow keys. To map these keys to the console gamepad, I used antimicro. It’s a very nice and easy-to-use program to map any mouse and keyboard key to any gamepad buttons.

Scraping videos

Emulation Station has an integrated scraper for game informations and pictures, but not for videos. Moreover, if video previews are supported depending on the chosen themes, they are played through VLC, which is not accelerated on the ODROID-C0/C1 SoC. The consequence is that 320×240@30 FPS in h.264 is the biggest playable size. I wrote and used a custom script available on GitHub at http://bit.ly/2fGFSkU, which parses the Emulation Station game folder and scrapes videos from www.gamesdatabase.org.

Lessons learned

  • There is no way to correctly monitor the battery on an ODROID-C0
  • With only a Mali 450 GPU, even with overclock, it is still too slow for a lot of N64 and Dreamcast games
  • There are some crashes that seem to be related to the graphics driver, such as Emulation Station not exiting properly, and hurrican sometimes does not start with the correct resolution
  • It is not possible to use a proper interrupt-based joypad driver, since there are not enough IRQs available on the SoC.
  • There is a need for a window manager, otherwise fullscreen is not available for games and Emulation Station
  • Reicast seems to emulate the GDRom noise, but I actually find it annoying

ODROID Figure 30 - Sega emulator running on the GamODROID-C0
Figure 30 - Sega emulator running on the GamODROID-C0

You can check out the GamODROID-C0 in action at https://youtu.be/3hxYhH7AFYU. For comments, questions and suggestions, please visit the original blog post at http://bit.ly/2khNDTz.

Be the first to comment

Leave a Reply