Embedded Simon

Creating the Simon Memory Game as an Embedded System

View on GitHub

Introduction

In this final project, our goal is to create a game to test the player's memory based on the classic Simon memory game. The game will display a series of LEDs, and the player has to memorize the sequence of LEDs and input them back into the board via the touch slider in a limited time.


Image of Board with color regions specified on TSS


The user taps the left side of the board corresponding to the red LED, the middle corresponding to green LED, and the right corresponding to blue LED as the diagram shows above.

At the beginning of the game, the system displays the rules of games to the console. The user will tap the board when they are ready to play. Then, the board is calibrated by the user. For each round in the Simon game, the board first shows the user a randomly generated sequence of LEDs. Then, the board flashes a white LED to tell the user to begin inputting. In a given round the user has a limited time to input the randomly generated sequence. After each round, the generated LED sequence’s length increases by one, and the user is given one extra second to input the sequence. As the deadline passes, another white LED flashes to show the round ends. We then compare the user’s input sequence and our generated sequence. If they are the same, the user wins this round and enters the next round with another LED flash added to the sequence. If the user didn’t get the correct input, the game ends. A yellow LED will be flashed once to tell the user the game over. Finally, we flash cyan LEDs in times of how many rounds the user passed. We also display the corresponding scores to the console.

Next, we ask the user if they wish to play again. If they tap the left side of the board, a new game starts without having to recalibrate the TSS. If they tap the right side of the board, the whole game ends.


Image of the board with end game logic shown on the TSS


We accomplished building this game system as described above. Throughout the project, we learned how to set up the TSS, use TSS interrupts to get user inputs, use PIT to control the time limit of each round, use locks to wait for a round to end and to prevent users from giving invaild input on the board.



System Diagram


Image showing the logic diagram of the project



Project Video

Project Video Demo Link



System description

The main program runs in game_logic.c. We have additional helper files like game_utils.h and utils.h to abstract out functions that will be frequently used. All things about TSS are done in the file tsi_v4_normal.h and tsi_v4_normal.c. We consider each round of a game as a task, and the following round continues only when the user wins the previous round. Thus, we use a while loop to run the game logic instead of separating each round and playing a concurrent program.


Image showing the file structure of the project as a block diagram


Initializing the game

We first initialize the game by initializing the LEDs and turning on the Touch Sensor (TSS) for user input. We also enable the PIT module for timing each round. The main code for initialization TSS is taken from the main loop of the TSS SDK Demo and put into a function we made called tss_init.

We then print out the rules of games to the terminal. We have a while loop waiting for the user to tap on the board when he or she is ready to play. TSS interrupt handler is triggered anytime the user presses the slider. Then, we ask the user to calibrate TSS. It walks the user through pressing the left and right sides of the board. In the calibration, the board measures several values of the touches of the user on the left and right sides of the board to compute the average values for the extremes of the board. It then computes a range of potential values the touch may have from these averaged values. This range is used to define the bounds for the board so we can determine in our code which region corresponds to a red touch, which corresponds to a green touch, and which corresponds to a blue touch. The first quarter of the range is given to the red. The middle two quarters are given to green, and the last quarter is given to blue. Two quarters are given to green because we found that the middle region is the hardest to press in our testing of the calibration. This calibration method also assumes that the touch location values given by the TSS driver scale linearly and not by some other function.


Main Game logic

We have two nests while loops. Each loop in the outer while loop corresponds to one game. The while loop will be terminated when the user chooses not to play again. Each loop in the inner while loop corresponds to one round. This while loop will be terminated when the user loses this round. Each round, we load the timer with the appropriate time and enable the timer and timer interrupt after displaying a random sequence with length corresponding to the round number. We chose to use integers to represent the LED. Thus, we defined RED to be 0, GREEN to be 1, and Blue to be 2 in the game_utils.h. We also have one array for storing the generated sequence and another for storing user input. We initialize the size of each to be 100 because 100 rounds are large enough for users to play. We don’t clear the values in the array after each round since 100 spaces for ints are already allocated by the variable's initialization. Instead, we overwrite the sequence after each round starting from index 0 every time in the array.

We have a variable is_active to lock or unlock reading from user input on board. We set is_active to 1 to enable the TSS interrupt. We update the global variable p_input_lst (user input list) and length_of_inputted_sequence in the TSS interrupt handler. Moreover, there are several conditions in the TSS interrupt handler that the system needs to check including whether this input is for users ready to begin the game, for calibration, for the game input list, or for they want to play the game again. Each condition corresponds to a flag, and we check by if-else statements.

There’s also a while loop inside each round waiting until time hits the limit. This is interrupted by the PIT timer. The PIT timer is loaded at the beginning of a round and counts down to the end of each round. At the end of each round, it checks if the user's inputted sequence matches the generated sequence, and it denotes if the user has passed the current round or not. It then increments the round number and tells the while loop in the main body that the round has ended.

If the user loses the current round, the game ends and the inner while loop in the main terminated. We displayed the score by flashing cyan LED and also printing to the console. We raise a flag to tell the game has ended, so the TSS handler will deal with input to ask if the user wants to play again or not. We also reset all variables at this time. The system is terminated when the user chooses not to play again.


Additional Resouces

In our project, we used a few additional resources. We learned how to use the TSS by exploring the drivers provided by NXP in MCUExpresso and by importing the SDK TSS Demos. We initially tried to use the SDK provided online by NXP for their TSSs but found it to be too complicated for our purposes and not the most Mac-friendly.

As for non-SDK-related resources, we used Google Docs to collaboratively edit the content of our website and Xinyu used Lucidchart to make our diagrams for the site.



Testing

Since our project is a game where players touch the toy sliders to mimic the pattern of LED displays, we mainly use human interaction tests instead of the traditional test cases with one traditional test case called test_logic.c to double-check the functionality of our game logic before human intervention. We also used some user testing to see what the user experience of our game is like.


Automated Testing

In our automated testing with test_logic.c, we test if the game's main logic is correct before we connect the user’s TSS input with the logic. In this case, the proper behavior after build would be a sequence of LEDs displaying randomly, and the number of LEDs increases as each round goes. Moreover, we could see that each round is leaving an amount of time for user input. After getting the expected behavior from our board, we ensured the correct functionality of our game logic and can play with confidence from now on.


Manual Testing

When testing through playing, each team member played the game on our boards to ensure the correctness of our game logic and behaviors. We first calibrate the TSS to divide the board into three regions. After seeing the message "Ok, thank you! Everything is calibrated," we are confident that the calibration process is correct.

Then we would begin playing the game. A white flash is expected to be flashed to serve as an indication that we could begin input. Then the LED sequences begin displaying. After memorizing the sequence of the LEDs every round, we would touch exactly the same numbers and suitable regions corresponding to each color to make sure our actions are correctly recognized for each color. To ensure that the incorrect action does not trigger a response for correct input, we checked and increased the LED sequence in length each round and that the score was incrementing properly. A message "Nice, your inputs were correct! You're doing great!" will be displayed if we have correct input and vice versa. As the round ends, a while flash should be lit as a sign.

When it came to the case that we lose our game in a round, we would test the two instances: continue the game or quit. We will touch the left region of the touch slider to see whether the game will restart and the right area to see whether the game will end; each of these cases has distinct messages to indicate our choice and to see if we handled this logic correctly. Corner cases have also been considered in our testing. When the player plays 100 rounds, the game will automatically stop the game with a surprising message that the player has reached the memory limit.


User Testing

Finally, we used user testing to see what the experience was like for the user playing the game. In this study, we found there to be many issues with our program that we had not considered as the developers of it. We found that since the input to TSS was so finicky that it would often cause the user to lose the round through no fault of their own, that we should let the user know about the unreliability of our sensor inputs. Additionally, since unjustified losses were so frequent, we wanted to make it as easy as possible to start playing the game again after the user lost, as opposed to having to time-consumingly re-run the program and recalibrate the TSS as we did before. We also found that we had to explain to the user how the game worked as they tried to play it, so to avoid this, we added diagrams and some instructions on exactly how to press the board to try to make it as clear as possible to the user how to play the game.


Major Bugs and Other Failures

We encountered many problems throughout our coding, especially in working with peripherals. We initially set up the TSS and calibrated it on Brady’s board. It was working correctly for him, but not for his partners. After much testing to find how exactly calibration on the TSS works, we consulted with a TA in office hours that told us not to worry too much about this type of reliability.

We also wanted to make our project more complex by incorporating an accelerometer by using tilting as an input into our memory game, but we had difficulty getting off the ground with the accelerometer. After we got the SDK demo provided in MCUExpresso, we tried to incorporate the demo logic into our project but ran into hard faults when setting up the GPIO for the accelerometer that we were never able to get past. Ultimately, we reprioritized the game design aspect of our project over this new medium for user input, thus scrapping the accelerometer.

Finally, there was one bug that really tested our mettle. In testing our round logic, we found that the flag used to indicate if the user passed the round called pass would suddenly change values, even when it seemed nothing was being assigned to it. It took many breakpoints and print statements to find where exactly this change was occurring. Ultimately, we found that it happened when values were assigned to the gene_lst[] array. The issue was that we had initialized gene_lst[] without indicating how much space was allocated to the array, so it was overwriting other variables when trying to assign values to any index higher than the zeroth index. Had we paid close attention to the MCUExpresso warning that said how the compiler handles initialization of arrays without specified sizes, we could have caught this error earlier. Nonetheless, this rigmarole sharpened our debugging skills.



Work Distribution & Review

Everyone on our team had an opportunity to play an active role in all the primary tasks of the project. We used pair programming during the project. We worked to ensure that we split the work evenly in an attempt to optimize for the learning benefits of the project. For the implementation, Xinyu was mainly responsible for the game logic and game utilities part. Brady was primarily responsible for the calibration and TSS setup, updates, and finalization of TSS and game logic after TSS connection with the game. Weiyan was responsible for the connection of TSS and game logic and variables update. We all tested the code on each of our boards to ensure its correct functionality.

For the website contents, Xinyu mainly wrote the introduction, system diagram, and system description part; Weiyan mainly wrote the system description, testing, work distribution part, and she will be responsible for the video part; Brady worked part of the system description part and the TSS resources. Brady will record several video clips for Weiyan to cut and polish. At the end, Brady will review the content as a whole and submit it to the website. We read over each other’s work and lightly edited it in an effort to make it complete and polished.

We helped each other whenever any of us got into trouble and talked about our progress and way through timely. We coded together through zoom, and each of us examined our program logic of the functions before actual programming to ensure consistency and accuracy. Whenever we encounter a problem during debugging or coding, we will consult each other through messenger. When encountering problems that we couldn’t solve ourselves, we attended Office Hours to seek help and then met up again.

We followed the good coding convention throughout the entire project. For instance, while programming, we followed the naming convention as well. We kept the length of our identifiers short, used multiple-word identifiers, and underscore “_” to concatenate two meanings together and make meaningful names of the variables. By writing in this way, we not only helped reduce the effort that needed to read and understand our source code but also reduce the possibility of the occurrence of ambiguity which helps us and others who are going to review our code more focus on the syntax and semantics of the code.

Before the GitHub pages were set up, we made our own repository so we could more easily code collaboratively. In addition, whenever we got stuck on a section but were out of time to work together, we would push code to GitHub so that we could all try to figure out the problem before we met again. Through hard work and meticulous attitudes in every aspect, we created a complete, polished final project Introduction

In this final project, our goal is to create a game to test the player's memory based on the classic Simon memory game. The game will display a series of LEDs, and the player has to memorize the sequence of LEDs and input them back into the board via the touch slider in a limited time.




Xinyu Shen (xs248), Brady Volkmann (bvv4), Weiyan Wu (ww333) - May 24th 2021