Introduction
We wanted to create an LED display with a customizable color sequence, with which you would also be able to play a memory game. We decide to pursue this idea because we thought it would involve working with an external circuit, and it will be good to combine the circuit and our program in the FRDM-fl25z board. It is also really exciting to have more LED components in the program. The goal is to allow the user to choose a color sequence that suits their needs, as well as the ability to play a memory game with the LEDs that light up their entire room.
The first function takes in color inputs provided by the user through an external circuit and repeatedly flashes those colors at a speed and pattern that can also be set by the user. The user would also be able to save an input sequence for later usage (e.g. a red-green pattern for Christmas time). The implementation of this feature would require a non-real-time process queue implemented by a linked list.
The second function is a memory game that requires the user to repeat the pattern displayed in the first function via corresponding inputs within a certain amount of time. The FRDM-fl25z onboard LED WILL flash to signal if the user has the correct or the wrong inputs. Implementation of this feature would require us to use interrups on the rising edges on PORTA and PORTD GPIO pins. We want to dynamically change the priorities (dynamic priorities) of the interrupt handlers such that multiple GPIO pins can interrupt each other in a preferred manner. We also needed to create a new linked list structure to help us implement this project. More details will be provided in System Overview and System Description.
We learned A LOT in this final project, including how to connect an external circuit to the FRDM-kl25z board, how to work with GPIO pins, how to read and write to GPIO pins, how to trigger interrupts on the rising edges, and so on. We truly appreciate this learning experience. We hope you like our project!
System Overview
Important Note: All the signals are done by the FRDM-kl25z board LEDs. The layer's inputs during the memory game are reflected by the LEDs in the external circuit we built.
In this project, we used an external circuit to help us acheive the goals. We have 5 LEDs of different colors and 5 button switches connected to 5 GPIO pins (circuit diagram attached below). The GPIO pins can write to the LEDs, meanwhile, the switches can also write to the GPIO pins. In display mode, the FRDM-kl25z board can write to the LEDs by outputting logic levels onto the GPIO pins. In the memory game mode, the switches can write to the FRDM-kl25z board by applying a voltage to the GPIO pins.
Circuit Used In the Final Project
System Overview
We have a few (about 5 LED patterns of length 7) saved in the queue in the very beginning.
In display mode (current_mode == 0), the user will display the first LED pattern from the display_queue, and the pattern will be displayed on the LEDs from the external circuit. The FRDM-kl25z board LED will blink red once before the pattern displays to signal the beginning of the process.
In save mode (current_mode == 1), the user will be given the chance to use the LEDs from the external circuit to save their preferred pattern to the tail of display_queue. The FRDM-kl25z board LED will blink blue once before the user gets to save their pattern to signal the beginning of the process. When an input is saved successfully to the back of display_queue, the onboard LED will blink green once.
In the memory game mode (current_mode == 2), the user is asked to repeat the pattern they saw displayed in display mode. They are given a limited amount of time (about 30-40 seconds) to complete their inputs. Every time the user enters the correct input, the onboard LED will blink blue once. If the onboard LED has blinked blue 7 times within the time limit, it suggests the user has put in the correct color inputs for the entire pattern, and the onboard LED will blink 3 times rapidly to signal success. On the other hand, if the user was not able to finish inputting within the time limit, or if the user did not put in the correct inputs, the onboard LED will blink red 3 times to signal that they memorized the pattern incorrectly. The FRDM-kl25z board LED will blink green once before the user gets to send in their inputs to signal the beginning of the process.
To implement the programming part of this project, we need to use linked list structures. We created a new linked list structure called display_s. The structure will have the following fields: next, before, and LED_pattern. next and before are both display_s structures that represent the next and the previous display_s structure of this display_s structure. LED_pattern is a char array of length 7. We decide to have a fixed length to make things easier. The array will hold LED patterns such as {'r', 'b', 'y', 'w', 'g', 'r', 'g'}. We do have a special rule here: the pattern can not contain repeated color patterns. For instance, the user is not allowed to have a pattern looking like this: {"r", "r", "r", ...}. However, they can have a pattern looking like this: {"r", "g", "r", ...}, where the red color patterns are not coming right after each other. We decide to have this rule because buttons have bouncing problems, where one button press can trigger multiple rising and falling edges, and it will be hard to determine how many times the user actually pressed on a button if they pressed on the button repeatedly. It is also recommended that the user put in their inputs not too fast, and that will make it easier for the system to distinguish between multiple button presses.
We used interrupts on the rising edges on the GPIO pins in this project. We have 5 GPIO pins in port D that control the 5 LEDs (white, red, blue, green, and yellow). We also have an additional GPIO pin in port A to help us switch modes (for example, from save mode to memory_game mode). We have to first enable interrupts (on the rising edges) on those GPIO pins so that the user input can be captured and dealt with when they press on a button. The interrupts can be handled in PORTD_IRQHandler() and PORTA_IRQHandler(). We also want to make sure that in different modes, the two handlers will have different priorities (dynamic priorities). In this way, the user will be able to add inputs (PORTD) after switching into a different mode (PORTA), and meanwhile, the user is also able to switch mode (PORTA) after finishing adding inputs (PORTD).
The count-down process in the memory mode is implemented simply using a while loop. The exact time is not strictly reinforced, but the time limit is approximately 30-40 seconds.
To maintain the display_queue when the user is displaying a pattern or saving a pattern, we created 2 helper functions (pop_display_pattern() and push_pattern()) to help us implement this feature. Helper function LED_pattern_create() will help us create a new display_s structure with preferred LED pattern. More details will be explained in System Description.
Here is a flow chart of our system:
System Overview
System Description
First of all, we defined a new structure display_s with 3 fields: next, before, and LED_pattern. next and before are both display_s structures that represent the next and the previous display_s structure of this current display_s structure. LED_pattern is a char array of length 7.
We then initialized a few global variables:
- current_display (display_s): the current LED pattern displayed
- display_queue (display_s): the queue holding all the saved LED patterns
- display_tail (display_s): the tail of the display queue
- current_mode (int): display mode (0), save mode (1), memory game mode (2)
- index (int): the current index of the LED pattern the user is inputting to (used in save mode)
- dex (int): keeps track of how many correct inputs the user has put in (used in memory game mode)
- num_entry (int): keeps the number of times a user has added input in the memory game mode
Before implementing what is going to happen in the display and memory game modes, we will first need to set up the GPIO pins. We make sure all the GPIO pins controlling the LEDs are from port D. We use only one GPIO pin from port A (PTA1) to help us switch modes. Pin PTD5 is connected to the red LED. Pin PTD4 is connected to the white LED. Pin PTD3 is connected to the green LED. Pin PTD2 is connected to the yellow LED. Lastly, pin PTD0 is connected to the blue LED.
We will first need to set up those pins as GPIO pins using a helper function we created (GPIO_Initialize()). The function will first enable the clock on port D and port A by setting bits 12 and 9 in the SCGC5 register. Then we can individually set those pins to be GPIO pins by setting bit eight of the PCR register. Next, we will need to enable interrupts on the rising edge on those GPIO pins. We have another helper function (portD_interrupt_setup()) to help us achieve this task. The helper function also sets up interrupts pin PTA1. To set up interrupts on the rising edge on a GPIO pin, we will need to set bit 19-16 in the PCR register of that GPIO pin to be "1001". Then we will need to officially enable interrupts. In our case, we want to start the program in display mode (user should not send inputs yet), we do not want to enable PORTD_IRQn yet. We will enable PORTD_IRQHandler later. Currently, we only enable PORTA_IRQHandler to allow us to change modes. To do this, we used the command NVIC_EnableIRQ(PORTA_IRQn).
After setting up the GPIO pins and interrupts, we will need to set those GPIO pins as outputs or inputs in another helper function (display_setup()). In display_setup(), we worked with the PDDR register in each pin.
We also defined a few other helper functions to help us test our program:
- read_GPIOn(): can replace 'n' by a pin number. The function will check the PDIR register of a specific GPIO pin. This will check the status of the logic level at a specific GPIO pin. If the logic level at that GPIO pin is 1, then the function will return 1. Else, the function will return 0.
- write_GPIOn(): can replace 'n' by a pin number. The function will set up the logic level at a specific GPIO pin, then clear the setup after a short delay. We mainly used this function to display a LED pattern on the external circuit. It requires us to work with the PSOR and PCOR registers.
- We also included helper functions in 'utils.c' from previous labs. We included LED_initialize(), LED_On() of different colors, LED_Off(), LED_Toggle() of different colors, and delay().
- We also wrote our own short delay function (sh_delay()) using the same logic in delay(), but this time the delay is about 10 times shorter.
To add new patterns onto the display queue we wrote a helper function push_pattern() that would insert a given pattern onto the tail while also ensuring that the circular linked list is still maintained. This function's inputs are the current display queue, the display queue's tail, and the new pattern of type display_s. If the display queue is empty, meaning that the new pattern is the first pattern, the new pattern becomes the head and tail of the queue. If there is only one pattern in the queue, the new pattern becomes the display queue tail, and points to the head.
Patterns that we wish to display on the external LEDs must first be popped from the display queue. This function, pop_display_pattern() takes no inputs and deletes the head of the queue if it is not empty. When the display queue has a single pattern the queue is deleted and when it has two patterns the tail points to itself after deletion of the head. For an empty display queue, we return NULL, otherwise we return the popped pattern.
Whenever a new pattern is added to the display queue we call LED_pattern_create() with a char array. This function creates a display_s pattern using the given char array before passing it into push_pattern() to be appended to the display queue.
Flashing a pattern on the external LEDs is done via the display_pattern() function. We call this function with the char array for the sequence of LED colors and iterate through all 7 indexes, calling write_GPIOn() each time for the pin corresponding to each char.
Function count_down() is used to execute an approximately 30-40s count down. It is used in the PORTA handler in memory game mode to add a time limit to the game. It is implemented using delay() and a while loop. It is a very simple function that can be interrupted. However, we do not want to strictly reinforce the exact duration of the time limit. We just want the time limit to be approximately 30-40 seconds.
Function right_or_wrong() is called in the PORTA Handler to check if the player has all the correct inputs after the count down. If global variable dex is less than 7, that means the player did not input all the characters correctly (dex only increments after one correct input is detected), and it will blink the red onboard LED 3 times. However, if dex is greater than or equal to 7, that means the player was able to input all the correct characters on time, and it will blink the green onboard LED 3 times rapidly.
PORTA_IRQHandler()
The Port A interrupt handler is where all of the mode switching functionality is handled. It begins by incrementing the current_mode variable. If the current mode is already 2, then current_mode is reset to 0
If the mode is now 0, Display Mode, port D interrupts are disabled and a single red flash from the on-board LED lets the user know that they have entered mode 0. We then call display_setup() to set up the LED pins for output capabilities. The current head of the display queue is popped and then used as input for display_pattern(). This flashes the pattern once.
If the mode is now 1, Save Mode, port D interrupts are enabled and a single blue flash from the on-board LED lets the user know that they have entered mode 1. The pins are set up for input, and an initialized char array user_choice[7] is initialized and passed into LED_pattern_create() where user inputs are taken as a new pattern and saved to the tail of the display queue.
If the mode is now 2, Memory Game Mode, port D interrupts are enabled and a single green flash from the on-board LED lets the user know that they have entered mode 2. The pins are set up for input, and we enter count_down(). After the game time has expired, we enter right_or_wrong(). After signaling a win or loss, dex and num_entry are reset. The user must toggle mode to return to game mode 0 where they will be shown the next pattern.
The port A interrupt flag is cleared.
PORTD_IRQHandler()
The Port D interrupt handler is where all of the player inputs are handled.
First, we check which pin triggered the interrupt using if statements
Next, If in Save Mode, write the corresponding color to the pattern char array and flash the FRDM green LED.
Once 7 inputs are recorded, reset the counter and reset interrupt priority.
If instead, we are in Memory Game Mode, compare the input to the current index of the displayed LED pattern. If they match,
increment dex and flash the FRDM Blue LED before incrementing num_entry and resetting interrupt priority.
We finish by clearing the corresponding pin's interrupt flag.
Finally, we put everything together in the main function, where we set up the LED and GPIO first. We will have some char arrays created ahead of time to be added to the display_queue. To add those patterns to the queue, we will need to use LED_pattern_create(). This will create a new display_s structure with a preferred char array, and the function will append the new structure to the end of the display_queue. We have 5 saved LED patterns saved to the display_queue. The program will start in display mode (current_mode == 0), so we will need to call pop_display_pattern() and display_pattern() to display the LED pattern that is in the front of the display_queue. Lastly, we include a forever while loop, so that we allow interrupts on the rising edges to dictate what happens next.
We have a video here for the demonstration:
Testing
The best way to test our project is to visualize it. We intentionally triggered the testing cases mentioned below and watched what happened with the LEDs on the FRDM-fl25z board and the LEDs on the breadboard.
Testing Case (1): User want to switch to display mode
In this case, pin PTA1 will be triggered to change modes When the button is pressed to change to display mode, the onboard LED will blink red once to signal the beginning of display mode. We want to observe if the program starts displaying a pattern that is saved in the display queue, and we want to make sure this pattern is different from the previous pattern (because pop_display_pattern() permanently removes a pattern from the display_queue and assign it to be the current_display, so there should not be the same patterns playing).
Testing Case(2): Player inputs the wrong pattern in memory game mode.
In this case, as soon as the system detects the player is inputting a wrong character, the onboard LED will know not to blink the blue LED for this input (the blue LED only blinks when the player inputs the correct character). The global variable dex should not be incremented for this individual wrong input. After the 30-40s wait, the onboard LED will blink red rapidly 3 times to signal that the player has memorized the pattern wrong (dex < 7).
Testing Case(3): Player did not finish inputting all the characters within the time limit in memory game mode
If the player has no inputs, or did not get to finish inputting all the characters, that means global variable dex will be less than 7 for sure. We should expect to observe the board blue LED blink less than 7 times during the countdown. After the countdown finishes, we should be able to observe the board LED blinks red 3 times rapidly to signal the user did not input the correct characters in time.
Testing Case(4): Player inputs all the correct characters on time.
This means the player is able to complete their task within the time limit. We should observe the blue LED on the FRDM-kl25z board blink exactly 7 times (once after each correct input). We should then observe the onboard LED blinking green 3 times to signal the player inputs all the correct characters on time.
Testing Case(5): User saves a pattern to the display_queue
When the program is in save mode (signaled by the onboard blue LED blinking once before the process start), the user should be able to save a new LED pattern of length 7. Each time the user successfully saves a character, the onboard LED will blink green once. The onboard LED should blink exactly 7 times for this reason. After we exhausted all the previously saved patterns, we should be able to display the pattern the user just saved to the queue (because pop_display_pattern() permanently removes a pattern from the display_queue after displaying it).
Resources
- FRDM-kl25z board
- Circuit Materials:
breadboard, resistors, LEDs of different colors, buttons, wires, and voltage source
- Circuit Lab
- brackets (HTML editing)
- old lab files
Work Distribution
Both members in our group have put in an equal amount of effort in this final project and in this website in general. We completed the coding part of the project where we used a pair programming strategy to write every module in the program. We both have the same set of circuit materials to help us understand what to do next and work more efficiently. Michael is especially helpful with putting the graphics together and building a mock circuit on Circuit Lab. We all also actively sought help from office hours and Campuswire. Both members have downloaded brackets.ino to help each other update the wiki page. Both group members have reviewed and made modifications as necessary to the portions of this wiki page. This final project is a good result of group efforts.