ECE 3140 Final Project - Interactive LED Canvas!
Gerardo Montemayor (gm547), Krishna Patel (khp42), Srinithi Krishnamoorthy (sk2693)
Overview
For our final project, we designed and built an interactive LED board controlled through a custom Pygame application. Users can draw on the LED grid in real time using their computer, selecting from a palette of colors to create dynamic patterns and visuals. The system allows intuitive point-and-click drawing, showing users’ creations on the LED display!
Demo Video
Click the thumbnail below to watch our video!
Technical Approach
The main components of the project are the FRDM-KL64Z development board, LED strips, and the pygame application. We needed to purchase LED strips, so we ordered a 300-LED strip online.
The first step in this project was to build the LED grid. Our initial plan was to use 289 LEDs to make a 17x17 square grid. We cut individual strips of 17 LEDs, soldered the power, data, and ground lines to the next strip, and taped them onto a cardboard. As we were soldering LED strips, we discovered that the FRDM could only power 14 rows, so only 238 LEDs. Therefore, we could not make a 17x17 grid and went with a 14x17 grid. The final result is shown below.
After building the board, the next step was to interact with the LEDs. At this point, we had not set up a UART connection. In previous labs, we worked with a small external serial LED board, as shown in the diagram above. Our LED canvas was soldered onto this small board, so lighting up the 238 LEDs was as simple as sending a 240-size array through the serial LEDs. We only had to slightly modify prior lab code to do so, and were able to light up the board.
Sending RGB data serially to the LEDs means we can’t individually address the grid using a 2D matrix. Rather, we need to send a 1D array of RGB values. We can consider the LED grid as a singular 238-long array. The grid is formed with a snake-like pattern as shown below. This required extra logic to ensure that the specific color for a specific LED showed up correctly.
After ensuring that interfacing with the LED matrix FRDM-side was done, the next step was to establish the UART connection. This proved to be the most challenging part of the project since we had not done this before.
We were provided code for being able to send messages from the FRDM to a laptop, but not code for actually receiving external messages on the FRDM, which is what we wanted. Our goal was for the FRDM to receive streams of characters from the pygame – and the FRDM would process that stream and send it to the LED matrix. The streams of characters consisted of ‘r’, ‘o’, ‘y’, ‘g’, ‘b’, ‘p’, ‘w’, corresponding to the colors of the rainbow and white.
On the pygame end of things, setting up UART was simple and just required a few lines to set up a serial connection with the correct port (FRDM USB port). Every 2 seconds, the pygame takes the current draw image (a 14x17 array of characters), flattens it, then sends the stream of data serially through the port. On the pygame, users could draw and erase, and erasing basically cleared the value of the LED, turning it off.
Setting up UART on the FRDM was a lot trickier. It involved setting up the correct registers and baud rate to receive. The biggest issue we ran into involved the frequency of the clock of the FRDM, which made setting up UART a lot harder, but we will get into that in the Testing section. On the FRDM, we have a buffer array of size 238. As the FRDM reads incoming characters from the pygame, it fills the buffer array. Once the array is full, the board knows that it has all the color data it needs, and it loops through the buffer and populates the 1D LED array to send to the matrix.
And finally, users could draw on the LED canvas via the pygame application, as shown below!
Testing and Debugging
Testing occurred at every stage of the project. While we were building the board, we would periodically make sure that we could light up all the soldered LEDs. This is how we discovered that only 238 LEDs could be powered. After that, we tested that individual LEDs could be turned on specific colors we wanted. This was more tedious, and we actually made drawings with a 14x17 array, compressed it into a 1D array, and sent it to the board. Finally, as we developed the UART connection portion, we wanted to confirm that the board could receive data, and we did this by lighting up an on-board LED if a specific character was received.
UART Connection
Finally, UART deserves its own small section, since it gave us the hardest time. In previous labs, we had to set up the internal clock frequency of the board to 15MHz, which was crucial for making the serial LEDs work. We had written assembly code that had precise timing measurements for sending 1s and 0s to the LEDs, and this was tuned only for 15MHz. What we discovered during testing was that the board could not receive over UART when the internal clock was set to 15 MHz. We tried changing the baud rate of UART to adjust for 15 MHz, but this was trickier and didn’t end up working. UART only worked when we used the standard clock frequency of 20.9, but doing so made the LEDs stop working. The solution to this was to go into the assembly code where we implemented the precise timing delays.
Since we had written timing delays for 15MHz and wanted timings for 20.9MHz, it meant we had to multiply the number of NOPs by 20.9 / 15, or 1.4. After doing so and some finetuning, we finally got the UART connection to work as well as the LEDs turning on.
Team Work
All of us worked together to build the board. We would meet up often to debug and keep the project moving forward. We also met up at the end to record clips for a video and build the project demo video at the top of the page!
Outside Resources
We had to go into lab office hours to receive help for a lot of the project! Thanks to the TAs for helping us build the board, and find solutions to the UART connection problems. We also consulted GenAI to help us make a simple and interactive pygame. It helped us ensure that the drawing application was intuitive and easy to use. We also used previous lab code: the assembly file from lab 2 that set up precise GPIO pin timings, and led.c.
