Solo and Arcade Mode Game Project
Two undergraduates from Cornell show you how to build a piano arcade game.
Arcade fans live everywhere, but the closest game center with their favorite arcade may be thousands of miles away. For these struggling arcade fans (with an engineering background, maybe), designing and building an arcade at home might solve the problem. Utilizing the PIC32 microcontroller (MCU) from Microchip Technology [1], with additional components including a wooden box, wooden sticks, and potentiometers, we can replicate an arcade experience at home.
WoodPlay is a stand-alone electronic instrument with a built-in rhythm game. It operates in two modes—Self-play (Piano) and Arcade. In the Self-play mode, players manipulate the device’s keys and buttons to produce 12 pitches on the chromatic scale. The Arcade mode is a rhythm game that requires players to press the keys in a sequence dictated by notes on the screen. It contains three rhythm game maps with different difficulty levels, varied in note patterns and intensity. As designers, we can easily modify the maps according to our own preferences.
HARDWARE DESIGN
The hardware that we utilized includes a PIC32 microcontroller with the following peripherals: a keypad to select pitch groups; a TFT to display the user interface; a 12-bit dual output Digital-to-Analog Converter with an 3.5mm socket for audio outputs; four potentiometers for key-press detection; a port expander; and an op-amp for each of four active low-pass filters between the potentiometers and the microcontroller. Top and side views of the assembled apparatus discussed in detail within this article are shown in Figure 1 and Figure 2, respectively.
Physical Setup: The physical setup consists of a wooden box as the base, wooden popsicle sticks fashioned into four “piano keys,” and a cardboard box to protect the electronic circuitry. Half of each key is enclosed in the wooden box in which the sensing occurs, and the other half protrudes outside the box for user interaction.
Toward the end of their inner half, the keys are glued to the side of a potentiometer—a position sensor that measures rotational displacement. A key press causes rotation of the corresponding potentiometer, resulting in a change of reading on the connected ADC. The end of the inner half of the key is placed on a piece of sponge glued to the ceiling of the wooden box. The sponge is compressed when the key is pressed down. This provides the force to rebound the stick to the horizontal position upon release (Figure 3).
The TFT screen and the keypad are connected to a half-sized breadboard placed on top of the wooden base. The cardboard box below it houses the PIC32 and the four low-pass filter circuits—one for each of the potentiometer outputs. We utilize a port expander for I/O connections. The audio socket is extended to the top left corner of the cardboard box for easier connection.
— ADVERTISMENT—
—Advertise Here—
Potentiometer Filter Circuit: There are four identical potentiometer filter circuits—one for each of the four potentiometers. Each potentiometer filter circuit, shown in Figure 4, is used to filter the noise from the output signal of the potentiometer, before sending it to the ADC [2]. We use a low-pass filter with a cutoff frequency of 1kHz to attenuate high-frequency noise signals beyond that frequency. The MCP6242 operational amplifier is configured as a unity-gain buffer, the output of which goes into the PIC32 ADC.
ADC Setup: We use the PIC32’s internal ADC to determine which one (or more) of the four wooden keys is pressed. It is configured to read the output signals of the four potentiometer filter circuits. The keys are labeled A, B, C, and D (Figure 1) to be distinguishable to the players. To ensure that multiple keys can be detected if they are pressed at the same time, we configured the ADC to scan multiple ADC inputs in quick succession. Once the ADC channel is configured, we can read the ADC inputs by examining the four scanned values, stored in a hardware-defined array in order—Key A first, Key B second, and so on. By reading the first four buffers, we get the results of the four potentiometer values. We read the ADC inputs every 350ms to ensure that this ADC reading thread doesn’t occupy too much CPU time, allowing other threads time to execute.
USER EXPERIENCE
When WoodPlay is turned on, a menu on the TFT presents the choice of two modes—Self-play (Piano) and Arcade—and the corresponding keypad buttons to press to begin playing (Figure 5).
Self-Play (Piano) Mode: After the Self-play (Piano) mode is chosen, the screen displays a drawing of the 12 pitches on a chromatic scale. By default, the four physical wooden “keys” A-D shown in Figure 1 are not mapped to any pitches as shown Figure 6. We use keypad buttons 1, 2, and 3 to determine to which group of pitches the four wooden keys are mapped, and to produce sounds of different frequencies accordingly. The TFT marks which note each key is mapped to under the current group selected. Figure 7 and Figure 8 show the keys mapping to group 1 and group 2 of pitches, respectively. We indicate on the bottom of the TFT that players can press 0 on the keypad to return to the main menu.
Arcade Mode: If the players choose the Arcade mode, a second-level menu presents the user with a selection of difficulty levels (Figure 9).
There are three levels of difficulty; a higher level indicates a longer and more intense sequence of notes, which is more demanding for the players. The notes are represented as green circles falling down from the top of the screen on four lanes, with each lane mapped to a wooden key. A red horizontal line spans the lanes near the bottom, indicating the lower bound for notes. The players should hit the corresponding wooden key when the note falls right on the bottom line (Figure 10).
If the user presses the key within a reasonable amount of time before the note passes the line, “PERFECT” will appear next to it. If the key press is off by a certain amount or there is no attempt at all, “MISS” will be shown. Different sounds also are produced on a “PERFECT” and a “MISS” for a clearer indication.
At the end of each difficulty level, the screen displays a summary of the number of notes that a player hit perfectly or missed (Figure 11). At the bottom of the screen, it says that players can press 0 on the keypad to return to the main menu.
SOFTWARE DESIGN
Logical Structure: We modularize our code using threads. The threads we create can be grouped into three categories:
— ADVERTISMENT—
—Advertise Here—
- Player input and response (“press wooden key” and “press keypad”)
- User interface drawing (“menu,” “self-play draw,” “cleanup,” and “arcade draw”)
- Interrupt and timing (“ISR” and “timer”)
The logical structure of our software design is shown in Figure 12. The major responsibilities of each thread are explained in the bullet points, and the arrows indicate the direction of the control flow.

Flow chart of logical structure
The most central thread in the whole logical structure is the “press keypad” thread. Because the players press the keypad buttons to enter and exit a mode or a level, which keypad button is pressed is often used as the condition variable to update the mode, pitch group, or difficulty level selection. It sets up the keypad implementation, and specifies the detection logic used to determine the number that the user has pressed. Each keypad button is stored as a unique binary number in a global variable, which is used to determine the mode or level to go to, and which drawing thread to evoke. The special key 0 is utilized as the Reset and Return key. All the start-up variables for each mode or level are reset to 0 when it is pressed.
The “press wooden key” thread takes in user inputs from the four wooden keys and generates the action associated with each press. In Self-play mode, the wooden key thread computes the frequency of the sound to be generated, based on which of the keys A through D are pressed, and which group has been selected. That frequency is communicated to the ISR, which generates a tone to be sent to the DAC. For example, when group 1 is selected and key A is pressed, the output frequency is set to 261.63Hz, which outputs the note C5. We generate the signal using Direct Digital Synthesis (DDS), and send it to the DAC. The analog signal from the DAC is sent to the speaker. (This sound production process is described in detail in the next section.)
In Arcade mode, it decides whether the key press meets the timing deadline of the given note, and gives the feedback of a “PERFECT” or a “MISS” to the player. To detect a hit or miss, for each difficulty level, we use two arrays to store the x and y coordinates of each note (as they are positioned on the TFT display screen). We use another array to store the time at which this note appeared at the top of the TFT. After a note moves below the bottom line, it is removed from the array. When a key is pressed, the thread finds the most recent note in the array (the one closest to the bottom line), and checks the x coordinate to see if it matches the lane to which that key is mapped. If it is a match, it means that the player has pressed the correct key, so the thread then checks the y coordinate of the note in relation to the bottom line, to determine whether it is a hit or a miss.
We include three threads just for drawing the user interface on the TFT: “menu,” “self-play draw,” and “arcade draw.” We define simple functions such as “draw line” and “draw circle” to create images such as the piano key section in the Self-play mode and the level backgrounds in the Arcade mode.
We set 44kHz as the audio synthesis sample rate. Since the internal clock of PIC32 toggles at 40MHz, it toggles 908 times (40MHz/44kHz) per audio sample. At each audio sample time, an interrupt triggers the Interrupt Service Register (ISR). Inside the ISR, the calculated DDS value is sent to the DAC, producing the corresponding sound.
The “timer” thread updates a counter that records the time elapsed since the start of each Arcade-mode game. This counter is used by the “arcade draw” thread to draw new notes at the top of the screen, at the time specified by the designers. For each level, we use an array to store the time when new notes should appear. The more difficult the level, the more values there are, and the intervals between two values gets smaller. When the counter matches one of the numbers in the array, the “arcade draw” thread draws a new note on the top of the screen and starts moving it downwards, along with the rest of the notes on the screen. The timer thread yields every 0.1 seconds; thus, the counter has a granularity of 0.1 seconds. The designers can freely change the rate at which the counter is updated in the “timer” thread, if they want the notes to appear at a faster or slower pace.
SOFTWARE CONFIGURATION AND BACKGROUND MATH
Direct Digital Synthesis: Generating sound at a certain pitch is equivalent to creating a sine wave of a given frequency. A sine wave is generated by projecting a rotating phasor onto the imaginary axis, as shown in Figures 13-15. We see the rotating phasor and its associated angle in red, the projection onto the imaginary axis in green, and the generated sine wave streaming off in orange. Figure 13 shows the phasor at 0 radian (rad), in Figure 14 at around 1rad, and in Figure 15 at around 4rad [3]. After the phasor has rotated 2π radians, it has completed one period of the sine wave, and the process repeats.
To generate a sine wave of a certain frequency, we need to send discrete values of the sine wave to the DAC at a rate, Fs (Hz) using a timer interrupt. Each time the interrupt fires, it rotates the phasor to a new orientation, looks up the value for the sine wave at that phasor angle from a lookup table, and sends that value to the DAC.
The DDS uses a 32-bit number—later referred to as the “accumulator”—to scale the range of phasor angles from [0, 2π] radians to [0, 232-1]. For example, an angle of 0 radians corresponds to an accumulator value of 0, whereas an angle of 2π radians corresponds to an accumulator value of 232-1. Overflowing the accumulator corresponds to completing a full rotation of the phasor.
Because of the isomorphism between the accumulator variable and the phasor angle, rotating the phasor is the same as adding an increment to the accumulator variable. The faster we increment the accumulator variable, the faster the phasor moves around the circle, and the higher the frequency of the sine wave becomes. The value of the increment and the frequency of the sine wave are linearly related.
CONCLUSION
Inspired by our own interests, we constructed a personalized arcade using a PIC32 MCU. The arcade is easy to assemble, substitutes for materials are easy to find, and the gaming levels can be modified simply by changing some of the code design. This project generally met our expectations, with the Self-play mode supporting 12 pitches, and the Arcade mode containing three difficulty levels. However, some improvements could be made if we continue to work on it.
In the Self-play mode, we could expand the supported pitch range by implementing more keypad options; currently, we only use three of the 12 buttons. This might require a bigger TFT screen or a more clever way of displaying the keys to show the entire expanded scale clearly. The design of the Arcade-mode levels currently doesn’t include background music, which is atypical for a rhythm game. Designers can choose their favorite songs and design the note pattern according to their rhythms. We can also add a “GOOD” range between “PERFECT” and “MISS,” and also categorize this “GOOD” range into “early” and “late,” with the former indicating that the player hit the key before the note passed the bottom line, and the latter showing the opposite.
REFERENCES
[1] PIC32MX250F128B Data Sheet: http://ww1.microchip.com/downloads/en/devicedoc/61168b.pdf
[2] V.H. Adams, ECE 4760 Lab 3, PID Control of a 1D Helicopter:
https://people.ece.cornell.edu/land/courses/ece4760/labs/f2021/lab3heilcopter/Lab3/Helicopter-PIC32.html
[3] V.H. Adams, ECE 4760 HW1, Direct Digital Synthesis:
https://vanhunteradams.com/DDS/DDS.html
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • SEPTEMBER 2022 #386 – Get a PDF of the issue
— ADVERTISMENT—
—Advertise Here—
Alga Peng (cp444@cornell.edu) is a fourth year Electrical and Computer Engineering student at Cornell University. Her interests include ASIC design, computer architecture, Hollow Knight, and snowboarding.
Xiangyi Zhao (xz598@cornell.edu) is a fourth year Electrical and Computer Engineering student at Cornell University. Her interests include ASIC design, computer architecture, soul-like games, and skiing.