32-bit MCUs can do so much these days—even providing all the needed control functionality for a gaming console. Along just those lines, learn how these two Cornell students built a portable game console that combines a Microchip PIC32 MCU embedded in a custom-designed 3D-printed case, printed circuit board and in-house gameplay graphics. The device includes a 320×240 TFT color display.
Our Portable Game Console (PGC-32) is a system that uses the Microchip Technology PIC32 microcontroller (MCU) embedded in a custom-designed 3D-printed case, printed circuit board and in-house gameplay graphics. PGC-32 consists of a handheld game console with two main playing modes: using the joystick to move horizontally and change speeds or using the whole game as a tilting device moving it back and forth to produce a movement effect. This offers gamers the possibility to experiment with completely different game experiences where they can use the joystick to resolve problematic situations and the tilting mode to spice up easier scenarios.
The device includes a 320×240 TFT color display, speaker and vibration motors as output devices while the gamer can interact in the game by using two action buttons, a joystick and an accelerometer Figure 1. It is a project with strong software and hardware components that’s able to integrate different sensors with the MCU and deliver to the gamer real-time interaction with animated graphics. It also fulfilled our desire to design and implement a full working product.
All different components are managed by the Microchip PIC32 MCU (Figure 2). A completely custom PCB holds all the circuitry including 4 AAA batteries that power the game console. PGC-32 also employs a custom 3D printed case that shelters the electronics, which makes it a standalone device.
The graphics of the game are displayed in a 2.2″ screen of 320×280 18-bit color pixels from Adafruit (1480) integrated in its own PCB with the ILI9340 TFT driver and a microSD card port. The pinout of this PCB includes a 4-wire SPI digital interface for the screen communication, a pin selector for the SD card which also uses SPI, a reset line, Vdd (3.3 V), GND and backlight pin. All the pins are used except for the pin selector for the SD card since this is not used and the backlight pin, both kept floating. When communicating with the PIC32 MCU, the TFT acts as a slave in the SPI communication protocol. Adafruit offers extensive libraries for pixel manipulation on the screen which are used in this project, modified by Syed Tahmid Mahbub .
The audio system of PGC-32 consists of the Microchip MCP4822 DAC, the Texas Instruments (TI) LM136 low voltage audio power amplifier and an 8 Ω speaker, including all required passive components (Figure 3). The 12-bit DAC is connected to the PIC32 through a 3-wire SPI interface (missing MISO signal from the DAC to the PIC32). The MCP4822 includes two outputs that can be synchronized by grounding pin 5 (~LDAC), but the PGC-32 only uses one output pin. The DAC is driven by the MCU at 20 MHz, which is the maximum rated clock frequency for the device. The output signal from the MCP4822 is connected to the non-inverting input of the LM386 audio amplifier through a potentiometer used for tuning.
The topology used for the audio amplifier is referenced from its datasheet and achieves a gain of around 5. This amplifier exhibits a bandwidth of 300 kHz and a total harmonic distortion of 0.2%. It is powered by 6 V directly from the batteries. The output of the amplifier is then connected through a giant 250 µF coupling capacitor to the 8 Ω speaker. The speaker used—which is the CVS-2308 model from CUI—has a typical resonant frequency of 1.5 kHz and a power consumption of 0.15 W. This 2-pin speaker has a diameter of 23 mm and a height of 15 mm so it represents one of the biggest components in the PCB which makes its interface with the 3D-printed case critical.
The remaining output devices are two vibration motors that are PWM (Figure 3) controlled by the 2N3904 NPN transistor, whose base is connected to I/O pin from the PIC32 through a 1 kΩ resistor. The vibration motors used are from Adafruit and are connected to the 6 V from the batteries and to the collector of the bipolar junction transistor (BJT).
As input devices, the user can use a joystick, two push buttons and the effects of the Adafruit MPU6050 6-axis accelerometer Figure 4. The joystick is nothing more than two potentiometers that measure the angle of inclination of the stick in both X and Y directions, taking its rest position as reference. The signal out of each potentiometer is connected to an internal 10-bit ADC (analog-to-digital converter) which gives a range of values from 0 to 1,024 representing 0 V and 3.3 V respectively. The reference position of the stick is measured around 512. These values are then processed by the script of the game to determine the respective action. In the case of the action buttons, they are set as low side switches that go to ground when turned on and to Vdd when off due to a 10 kΩ pull-up resistor. These switches are correctly debounced using a finite state machine (FSM) as it is described later in the Software Design section.
The MPU6050 6-axis accelerometer/gyro has been used to add a new tilting game mode where the user can make some actions by precisely tilting the handheld game console to the sides. The user can determine the game mode by using a slide switch that chooses between the two. The data used by the system from the accelerometer is the X and Y directions which are used to determine the movements to each side in the game and are passed to the MCU via I2C (a 2-wire interface). Both the clock and data signals include a pull-up resistor of 2.4 kΩ whose value is determined by the timing requirements of the protocol (400 kHz clock rate). The data from the accelerometer is then transformed to the same scale of the joystick data to guarantee the same game response regardless of the game mode.
All the electronics are integrated in a 2-layered, 140 mm x 91 mm custom PCB (Figure 5). Besides the components previously mentioned, the board includes the batteries’ holder, the Microchip MCP1702 LDO which lowers the voltage from 6 V to 3.3 V to power the PIC32 MCU (Figure 6), a power switch along with an LED that indicates if the board is on, and pin headers and sockets for the attachment of the other components with PID package (Figure 7). Passive devices such as resistors and capacitors selected are SMD (surface mount devices) which offers the possibility of mounting them on both layers of the board.
In the case of the accelerometer, it is directly soldered to a 5-pin header underneath the TFT screen, which saves area on the board. The top layer contains most of the connections of the circuitry with traces ranging from 10 to 50 mils while the bottom layer is a ground plane. The PCB includes two mounting holes to be properly attached to the bottom part of the case. The case—whose design is completely custom—consists of two main parts: top and bottom (Figure 8).
The top part is the one that has more interfaces with the electronics such as the screen, the power switch, the LED, the game mode switch, the joystick, the two push buttons and the speaker. The bottom part has a depth deeper than the depth of the batteries’ holder so it doesn’t really interface with any component. Both parts are 3D-printed and are bolted with each other using four long M4 screws. The design process of the CAD for the case happened almost concurrently with the design of the PCB, and both presented different constraints to the other.
The software component of this embedded system is mainly based on the interaction between the different peripherals of the PIC32 MCU (timers, DMA, output compare and so on), the outside sensors connected to the PCB and an LCD screen that displays a refreshing view of the game. The software implementation that is executed by the PIC32 is divided into two main parts: a dynamic game experience that provides three levels of difficulty and aesthetically pleasing graphics that react smoothly to real-time interaction with the player.
The idea behind the game is quite simple: the player controls an airplane that can move sideways and also increase or decrease its speed in order to evade a stampede of blocks randomly placed in the plane’s path. Additionally, the airplane contains a set of bullets which are visible to the player that can be used to destroy the blocks which also have a “weight” parameter such that, if the airplane shoots at it, two things can happen: If the airplane has more or the same number of bullets compared to the block’s weight, the blocks get destroyed and the airplane’s bullets are reduced for such amount. If the airplane has less bullets than the block’s weight, the airplane is left harmless while the weight of the block is reduced by such amount.
However, not all blocks are hurtful. Some blocks (the ones drawn in green) that are caught by the plane provide it with extra ammunition for future battles. Finally, the player can use a secret weapon that clears all the objects in the plane’s path for approximately two seconds.
Now, let’s dive deeper on how the user interacts with the game using the different peripherals from the PIC32 and the hardware components present on the board. All processing inside the PGC-32 is handled by a threading library written entirely as C macros called Protothread by Adam Dunkels . It is essentially a basic multi-thread system with no preemption (all threads need to yield by explicitly executing such statement) in which threads can be scheduled depending on time delays, specific conditions or simply because a running thread voluntarily yielded its turn.
This system is very efficient since it allows the PIC32 to communicate with multiple sources of input and output concurrently, while ensuring proper communication and performance. The first component is the TFT screen which communicates via SPI with the PIC32 MCU. With the availability of a library that contains a number of helpful functions, the PIC32 is able to display different shapes and colors on the TFT screen and produce complex dynamic animations. Refer to the Graphics section later in the article for more information on this topic.
To control the airplane, the player can choose to play with the joystick or the accelerometer. These two components have their own separate thread, and depending on the playing mode, only one is scheduled by the MCU in order to reduce processing efforts. In the joystick thread, the PIC32 receives two inputs from two different analog-to-digital converters (ADC) channels which represent the position of the joystick in the X and Y axis.
Noise and small variations in these values can cause the airplane to shake because of rapid, short movements. Because of that, a running average filter records the last 8 values from both channels and uses their average as the position for the plane. The reason the buffer for the filter contains 8 values is that division is required for the running average. And shifting left by 3 (1 cycle) is way more effective in reducing processing time than performing an actual division, which takes approximately 50 cycles in this MCU.
On the other hand, the accelerometer thread receives readings from the MPU 6050 via I2C  and converts the values from the horizontal and vertical planes into pixel positions that perfectly fit the playing ground displayed by the TFT. Given that the accelerometer readings are also quite sensitive, this thread also makes use of the running average filter previously explained. With these two features, the player is able to select a desirable playing environment and control the airplane with close-to-zero real-time delay while still displaying animated graphics on the TFT.
As its defense mechanism, the player can use two push buttons which can be used to shoot bullets at some blocks and also clear the field when possible. The latter is called “the destroyer”, which is essentially a blue circle that the plane can catch and then press one of the pushbuttons (blue) to activate it. Each push button has its own thread that manipulates global variables that control the flow of the gameplay in another separate thread.
A common issue that push buttons encounter when being pressed is referred to as switch bounce. This occurs when push buttons often generate unstable open/close transitions when pressed due to mechanical and physical issues. To stabilize these button pushes, each thread is composed by a debouncing state machine (shown in Figure 9) that works around this principle to guarantee that the main game thread uses stable values for button pushes.
To add extra excitement, the PGC-32 contains two vibration motors glued to the case. Upon any firings from the plane or collisions with the blocks, they vibrate at a soft intensity to physically interact with the user. To achieve this, a timer interrupt service routine (ISR) running at 1 kHz sets a hardware PWM signal using an output-compare unit to control the motor. Specifically, the gameplay thread uses the function
SetDCOC3PWM() to activate the previously explained mechanism once it detects specific scenarios like shots or collisions.
The last peripheral integrated with the design is a small sound system that reacts to specific scenarios of the game. Sound processing can be a very expensive operation in terms of processing time, which is why the PGC-32 uses Direct Memory Access (DMA) to directly connect flash memory to the DAC channel of the PIC32, which is then outputted to an audio amplifier circuit.
DMA allows the connection between certain hardware subsystems to main memory (in our case, flash memory), making some memory accesses independent of CPU processing. Minimizing CPU processing in these threads is crucial because a huge majority of the CPU time is taken up by the graphics thread. Ensuring that enough processing time is allocated for graphics purposes guarantees a smooth display of the game in the TFT.
The PGC-32 contains three main windows: pre-game, game and post-game. In all of these scenarios simple graphics are merged together to compose complex features that add strong visual stimulation to the players. Through the use of provided libraries, these different threads are able to draw lines, rectangles, circles and text—all in different colors and thickness.
All that said, the time the CPU takes to display these on the screen is relatively high compared to gameplay processing. By applying elementary graphic techniques for drawing the plane (three rectangles), the falling blocks (squares that change position by erasing a small part of the top and drawing the same part on the bottom), and using DMA for sound processing, the PGC-32 is able to run at a frame rate of 33 fps. For reference, Figure 10 shows the main playing screen containing the plane, the different types of blocks, and the status of the game to the left containing the playing mode, the difficulty, the time remaining on the game (countdown from 60) and if “the destroyer” is available.
RESULTS AND CONCLUSIONS
The final product is a compact embedded system with its own PCB, gameplay graphics that are powered by the PIC32 MCU, and a bright-blue 3-D printed case that essentially puts the cherry on the cake. Our main motivation for this project was to be able to design an idea for a gaming device, carefully divide it into working sub-parts, and finally integrate the system into a final working product. Additionally, the product was tested with people from different age ranges (from ages 10 to 50!) and it behaved perfectly throughout the course of several game sessions. Below is a video showing working full product:
Through extensive incremental and isolated testing, the PGC-32 has proven to merge all peripherals and sensors effectively and deliver neat graphics for each stage of the game. One of the hardest parts when developing this system was manipulating the design in order to reduce CPU processing (for graphics purposes) while still maintaining an accurate display of the gameplay. Additionally, working with graphics and integrating different readings from sensors and timers into the TFT in real-time, without creating any visual scattering, was quite challenging. While testing the final design, the recordings of trial runs playing in the different levels of difficulty showed a negative correlation between difficulty and winning percentage, which verifies the implemented algorithm for such purposes.
The only negative feedback that was received from the design as a whole was the presence of random resets while heavily manipulating the board. Sources of this issue may be related to the physical state of the PCB, specifically the fact that careless soldering can cause two isolated pins to be shorted together. Also, even though the design of the vibration motors circuit was carefully put together to avoid this kind of problem, it’s possible that some EMI produced by the motors caused the resetting event.
Looking at improving the PGC-32 design, we’ve thought of including an SD card that contains fully developed game engines and graphics that can be read by the PIC32. Although this would require some serious research of the MCU and how it can connect to the SD card to properly read the contents of a game, the final product would be a fully functional device that better emulates a unit sold by the gaming industry.
 Tahmid’s blog http://tahmidmc.blogspot.com/
 Protothread library by Adam Dunkels http://dunkels.com/adam/pt/publications.html
 3DOF Stewart Platform I2C Library https://github.com/adamweld/microgoats_stewie
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • SEPTEMBER 2019 #350 – Get a PDF of the issueBecome a Sponsor