Skip to content

Final Project

Summary

For my final project, I created an interactive writing activity where users can display a certain character on an LED screen and then when they draw the character, only the correct LEDs will light up.

Video

Slide

Project Sketch

I’m designing an interactive learning activity where LEDs briefly light up in white to form a specific shape—such as a number or letter—and then turn off. The user’s task is to replicate that shape by touching the correct points, each equipped with a touch sensor. As the user touches each correct point, its LED turns green to indicate progress. If the user taps an incorrect point, the corresponding LED blinks red, and a small screen displays “Incorrect.”

The process is similar to an interactive “Connect the Dots” game, encouraging users to recall and recreate the shape from memory. When the correct sequence of points is touched, the screen will display a “Correct” message. If not, the shape will be shown again, and the user gets another chance. This cycle repeats until the shape is completed successfully.

The goal of this project is to help young children learn how to write numbers and letters through engaging, hands-on practice that combines memory, coordination, and feedback-based learning.

Who has done what beforehand?

I asked ChatGPT if anyone has done something similar before, and here are some of the projects it told me about:

  1. LightUp – Augmented Learning Blocks

Description:

LightUp is an educational toolkit combining magnetic blocks and LEDs to teach kids about circuits. While it’s focused on STEM education, it includes interactive lighting feedback to guide users. The kit encourages step-by-step problem solving by lighting up components when connected correctly, similar to your concept of lighting points and validating interaction.

  1. Touch the Dot Game (Makey Makey/Instructables Project)

Description:

A game built with Makey Makey or capacitive touch sensors, where dots (connected to foil or conductive pads) light up in a sequence. The user has to touch the same points in the same order. If the user fails, the pattern repeats until correct.

  1. Simon Says LED Touch Game (ESP32/Arduino Projects)

Description:

An advanced version of Simon Says, this is a game using an LED matrix and capacitive touch sensors (often with ESP32). LEDs light up in a pattern, and the player must tap the corresponding touch zones in the same order. If the pattern is incorrect, it restarts. Often featured in maker contests.

Final Project Plan

For Midterm Review, I was instructed to create a Bill of Materials and a Gantt chart to create a plan for my final project.

Materials

Material Quantity Description Cost In Lab Link
ESP32S3 1 ESP32S3 microcontroller $15.00 Yes Amazon
LEDs 36 6x6 RGB LED matrix Yes
Jumper Wires 72 Male-to-female jumper wires $7.49 Yes Amazon
Solid-Core Wire N/A Yes
LCD1602 Display 1 16x2 character I2C LCD display for simple text output using GPIO $9.99 Yes Amazon
220Ω Resistors 6 220Ω resistors for LED matrix Yes
MPR121 Sensors 3 12-pin capacitive touch sensors $23.85 No Digikey
Acrylic 1 8 in. x 10 in. x 0.050 (1/20) in. Clear Non-Glare Acrylic Sheet $4.58 No Home Depot
Copper BBs 36 $5.99 No Amazon

Tentative Gantt Chart

This is definitely subject to change, but here is my tentative gantt chart:

Weeks

I worked on my final project these weeks:

Computer-Aided Design Week

Input Week

Output Week

Application and Interface Programming Week

System Integration Week

Application and Implications Week

Invention, Intellectual Property, and Income Week

Designing Project in Fusion 360

I chose to design my final project in Fusion 360 because I have some prior experience from previous engineering classes and from watching and doing around most of Kevin Kennedy’s Learn Fusion 360 in 30 Days YouTube tutorials. They were very helpful, and I was very grateful to have done them.

First, I needed to design the inner part of my project that houses the LEDs, touch, sensors, and the main area where the user writes. I started by making the outisde of this inner housing. I made a sketch on the bottom plane in the shape of a square, extruded it, made a sketch on top of it, and then extruded a hole into to make it hollow. Using parameters was very, very important, and I used parameters in most, if not all, of my dimensions. Parameters allow dimensions to be stored in essentially variables, so if you change the value, it changes the value in all the places that variable is used. For example, I used the parameter board_height to set the height of the board. Later, when I finished the project, I was able to go back and change the height of the board easily by changing the value of the parameter in just one place. For each part of the project, I made sure to make a new component. Making new components was helpful when changing the appearance and keeping the design organized.

This is the finished outer part of my project with the appearance changed.

Next, I had to create the holder for my LED matrix. I made another new component to do this, but it was nested under the inner housing component. When adjusting the height of the LED holder, I learned that you need to select move bodies or it will just pull the face up. I struggled for a little bit trying to figure out why it kept stretching my LED holder up instead of moving the whole thing. I then asked ChatGPT what size LED it would recommend, and it said 5 mm, so my next goal was to create a matrix of 5 mm circles evenly spaced out on the LED holder. This task turned out to be a lot harder than I initially expected because I could not figure out how to evenly space the circles throughout the square. The first thing I tried was creating a circle in the top right corner of the square and then using a rectangular pattern to spread them out the distance of the square. I also had to figure out how to change the direction of the patetrn so that the circles appeared in the square. I had to select the top edge and the left edge of the square I believe to make this happen.

I realized I needed some sort of groove so that the LED holder would be able to fit in the box and not be all the way at the bottom. I made a sketch on one of the inner walls of the box and made a rectangle, extruded it, and then mirrored that to the other three sides. This is the groove on one side of the box before I mirrored it:

There are both grooves:

Next, I had to design the holder for my touch sensors. I copied the LED component I had and used Paste New under my Housing component to duplicate the LED holder for my Touch Sensor Holder. I wanted to keep the LED holes in my Touch sensor holder, so the LEDs light does not get blocked, but I needed to add another hole next to each LED hole for the wiring of the touch sensors. I repeated the Rectangular Pattern steps, Construction Lines, and Equal Constraints that I had to do for the LED holes and quickly added another set of holes for the wiring.

The last thing I needed to do was add the semi-transparent top to my project design. I initiall tried to create a new component to do this, but I could not get the constraints to be completed in the new sketch, so I made the top in the Housing component instead. I got the sketch to work first try and then extruded my rectangle by -1 mm and changed the appearance to a mostly clear glass.

Final without LED holder, touch sensor holder, or top:

Final with only LED holder:

Final:

All Parameters Used:

To see my full documentation of designing my final project in Fusion 360, you can visit my Computer-Aided Design page.

Choosing a Touch Sensor

While brainstorming touch sensors, I came across this video, which I found very interesting because they made touch sensors out of common materials. I do not know if this would be the most practical for my final project though, but it could be a potential option if I can figure out how to make that work.

Another option I explored was this touch sensor that I found on Amazon for a relatively inexpensive price. If I chose this option, it would make it so that I would have a different touch sensor for every spot in the grid, which is a possibility.

During input week, I began to look into more options. Mr. Dubick recommended that I try using step response. I also was curious about capacitive touch sensors.

Step Response

The first sensor I tried to use was step response. Step response is Neil’s favorite form of input and Mr. Dubick recommended giving it a try, so I did. I was very confused by this sensor and spent hours trying to understand it, but I eventually decided that it would be worth looking for another sensor for my final project since step response code is so complicated. Because I already have prior coding knowledge, I chose a software-heavy final project, but the step response would be difficult to integrate into the project as a whole. To see my full PCB designing process, visit my Electronics Production page.

HW-139 Touch Sensor

I decided to try the HW-139 touch sensor next just to get a better idea of how touch sensors work. This sensor worked very well, but it seems unpractical for my final project because I am looking to make a 6x6 matrix, so I did not end up designing and milling a PCB for this sensor. However, I did several tests with breadboards and ended up using this sensor for machine week, so ultimately, I was glad that I learned how to use this sensor this week.

One thing that was cool about this sensor is that it is able to take input even through a material as thick as cardboard.

Here is a video of me testing this:

Standalone 5-Pad Capacitive Touch Sensor Breakout - AT42QT1070

Next, I found a Standalone 5-Pad Capacitive Touch Sensor Breakout - AT42QT1070 in the lab and decided to use that. I really enjoyed this sensor and will definitely be doing more work with it in the future for my final project. Milling the board for this was definitely tedious because I kept ripping traces and running into other issues. If I end up using this sensor, I will have to remill the PCB I designed, and I will probably make it double sided.

Without any code connected to it, the touch sensor lights up each of its built-in LEDs when the corresponding pad is touched. All it needs is a connection to power and ground. I used a Xiao RP2040 to program it.

I started out trying to program the touch sensor to just be able to recieve signal from one of the pads. My first big mistake was that I connected the wires on the wrong side of the sensor. I tried to connect the touch pads of the sensor to the Xiao RP2040 instead of the side with the built-in LEDs so one pad was just always being touched, which is not helpful.

Once I figured out this issue, the wiring came very easily.

This is the wiring I used:

This is my PCB design that I made slight alterations to every time I milled it:

Here is the PCB soldered and working:

ESP32-S3 Built-In Touch Sensor

During machine week, my group decided to use the built-in touch sensors on the Xiao ESP32-S3 which I did not even know existed, but I decided to try them for my final project because that would be easier than using multiple of the other types. It works very similarly to the Standalone 5-Pad Capacitive Touch Sensor Breakout - AT42QT1070, but it is able to be done using just one microcontroller, which is more efficient.

The ESP32-S3 ended up being a really good candidate for my final project because it has so many pins. I did the calculations, and using this microcontroller, I would have enough space to use 6 pins for my screen, 12 pins for my LED matrix, and 12 pins for my touch matrix. The ESP32-S3 also has web server capabilities, and I plan on incorporating a web server into my project, so that is another pro.

MPR121

I still plan on using the ESP32-S3 for the microcontroller in my project but the MPR121 will be more efficient for the touch capabilities since it has 12 touch pads.

Testing Screen

I decided to use a LCD1602 screen for my final project because it can display 2 rows of text, and all I plan on my screen displaying is the character that is lit up in the LED matrix and “Correct!” or “Incorrect.” depending on the user’s response. I made sure the ESP32-S3 had enough pins for this, and once I was sure that it did, I began testing it. I found a very helpful YouTube video explaining how it works. I began programming the screen with a Xiao ESP32-S3 and then moved on to programming it with an ESP32-S3. I started out using a breadboard to program the screen, but I will be making a PCB for the final product.

Even with no code uploaded to it, the screen lit up, which is helpful because it helped me double check my wiring.

Here is the screen displaying a simple message:

This is the code I used:

#include <LiquidCrystal.h>

// Create an LCD object with the pin connections:
// LiquidCrystal(rs, enable, d4, d5, d6, d7)
LiquidCrystal lcd(2, 3, 6, 7, 8, 9);

void setup() {
  // Set up the LCD's number of columns and rows:
  lcd.begin(16, 2);

  // Print a message to the LCD
  lcd.setCursor(0, 0);  // Column 0, Row 0
  lcd.print("Hello, World!");

  lcd.setCursor(0, 1);  // Column 0, Row 1
  lcd.print("XIAO ESP32-S3");
}

void loop() {
  // Nothing to do in loop for static text
}

ChatGPT helped me make this test code, but I went on to test various messages and change the pinout of the screen to help ensure my knowledge of the code.

I also tried to create a simple web interface to display different numbers on the screen, which is one of the things I want my final screen to do. This was very successful as well. I will be improving this interface as I progress on my project to be more personalized and achieve the purpose of my project more accurately.

This is the simple test interface:

Figuring Out LED Matrix

I started out trying to understand how LED matrices work in Tinkercad and the Wokwi. This was very helpful and I gained a much better understanding of how LED matrices work after doing this. I started out using an Arduino in Tinkercad and the moved to using an ESP32-S3 in Wokwi and creating a test code. Ultimately, I want to create a 6x6 LED Matrix, but as of now, I have only tested a 3x3 matrix on a breadboard, but I will make my PCB for a 6x6 matrix.

The diagram and code on this article was a very helpful start for me.

Tinkercad 3x3 Matrix:

Tinkercad 6x6 Matrix:

In Tinkercad, out of curiosity, I also started exploring making a number in the matrix. I tried the number 3 and was pleased with the results.

Wokwi 3x3 Matrix with ESP32-S3:

After working in Wokwi, I moved on to make a 3x3 matrix using the ESP32-S3 on a breadboard. The breadboard is just a temporary test to try and connect several components of my project together before creating my final PCB.

Before connecting my screen, the matrix, and the web interface, I wanted to see if the matrix on its own would work. I set it up on a breadboard for testing purposes, and it was not very visually appealing but achieved its purpose. I initially ran into a wiring issue that led two LEDs to stay on all the time, but that was because one of the wires was supposed to be connected to the other leg of the LED.

This is my 3x3 matrix displaying a series of patterns that ChatGPT helped me come up with:

This is my 3x3 matrix turning all the LEDs on and then off:

Once I got the matrix working, I asked ChatGPT to help me come up with a code for a web server that has 3 different pattern option buttons. When a button is pressed the pattern name will display on the screen, and the LED matrix will show the pattern.

This is the code I used:

#include <WiFi.h>
#include <WebServer.h>
#include <LiquidCrystal.h>

// ==== LCD with NEW pinout ====
LiquidCrystal lcd(39, 38, 37, 36, 35, 48);  // rs, e, d4, d5, d6, d7

// ==== LED Matrix ====
const int ROW_PINS[] = {16, 17, 18};  // Rows: anodes
const int COL_PINS[] = {42, 41, 40};  // Columns: cathodes
const int ROWS = 3;
const int COLS = 3;
const int REFRESH_DELAY = 1;

// ==== WiFi ====
const char* ssid = "SSID";
const char* password = "password";
WebServer server(80);

// ==== Patterns ====
byte patterns[][3] = {
  // Diagonal
  {B100, B010, B001},
  // Border
  {B111, B101, B111},
  // All On
  {B111, B111, B111}
};

const char* patternNames[] = {"Diagonal", "Border", "All On"};
const int NUM_PATTERNS = 3;
int currentPattern = 0;

void allOff() {
  for (int r = 0; r < ROWS; r++) digitalWrite(ROW_PINS[r], LOW);
  for (int c = 0; c < COLS; c++) digitalWrite(COL_PINS[c], HIGH);
}

void displayPattern(int index) {
  for (int row = 0; row < ROWS; row++) {
    allOff();
    digitalWrite(ROW_PINS[row], HIGH);
    for (int col = 0; col < COLS; col++) {
      if (bitRead(patterns[index][row], COLS - 1 - col)) {
        digitalWrite(COL_PINS[col], LOW);  // Turn LED on
      } else {
        digitalWrite(COL_PINS[col], HIGH); // Turn LED off
      }
    }
    delay(REFRESH_DELAY);
  }
}

// ==== Web Interface ====
String htmlPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>LED Matrix</title>
  <style>
    body { font-family: sans-serif; text-align: center; padding-top: 40px; }
    button {
      font-size: 24px; padding: 15px 30px;
      margin: 10px; border-radius: 8px; background-color: #007BFF;
      color: white; border: none;
    }
    button:hover { background-color: #0056b3; }
  </style>
</head>
<body>
  <h2>Select LED Pattern</h2>
  <a href="/pattern?index=0"><button>Diagonal</button></a>
  <a href="/pattern?index=1"><button>Border</button></a>
  <a href="/pattern?index=2"><button>All On</button></a>
</body>
</html>
)rawliteral";

// ==== Web Handlers ====
void handleRoot() {
  server.send(200, "text/html", htmlPage);
}

void handlePattern() {
  if (server.hasArg("index")) {
    int index = server.arg("index").toInt();
    if (index >= 0 && index < NUM_PATTERNS) {
      currentPattern = index;

      // Update LCD
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Pattern:");
      lcd.setCursor(0, 1);
      lcd.print(patternNames[index]);

      Serial.println("Pattern set to: " + String(patternNames[index]));
    }
  }
  server.sendHeader("Location", "/", true);
  server.send(302, "text/plain", "");
}

void setup() {
  Serial.begin(115200);
  lcd.begin(16, 2);
  lcd.print("Connecting WiFi");

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(".");
  }
  Serial.println("\nWiFi connected!");
  Serial.println(WiFi.localIP());

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("IP:");
  lcd.setCursor(0, 1);
  lcd.print(WiFi.localIP());

  // Init matrix pins
  for (int r = 0; r < ROWS; r++) {
    pinMode(ROW_PINS[r], OUTPUT);
    digitalWrite(ROW_PINS[r], LOW);
  }
  for (int c = 0; c < COLS; c++) {
    pinMode(COL_PINS[c], OUTPUT);
    digitalWrite(COL_PINS[c], HIGH);
  }

  server.on("/", handleRoot);
  server.on("/pattern", handlePattern);
  server.begin();
}

void loop() {
  server.handleClient();
  displayPattern(currentPattern);
}

This was the result:

The screen is not very visible in that video, so this is an example of a message it was displaying:

This is what the web server was displaying:

This program was a form of proof of concept for me because my end product will be similar in that a web server displays several options and when the user selects an option, the option will appear on the screen and on the LED matrix.

Currently, I am working on a PCB for the 6x6 LED matrix and the LCD1602 screen using the ESP32-S3 microcontroller, which should be pretty simple since I already have it working on a breadboard. My next step will be figure out how to make the LED matrix a form of touch screen, preferably using the built-in touch pins on the ESP32-S3. I am also still working on understanding the code for web interfaces, and I have a pretty good understand the screen code and the LED matrix code.

PCB

Now that I had decided to use the MPR121 touch sensors, LCD I2C screen, and figured out the LED matrix, it was time to make the PCB. Andrew Puky had already made a PCB for an LED matrix for his project, so he wanted to help me design mine. However, instead of placing the LEDs directly onto the PCB, I wanted pinheaders because the PCB would not be able to be the size I wanted my matrix to be due to the capabilities of the milling machines in out lab. He helped me design the first rendition of the PCB and taught me how to make a double-sided PCB, which I had not done before.

The first PCB’s pin headers were too small:

That was a pretty simple fix though. We just selected different pin headers that were larger before milling it again.

This was the updated design and PCB:

When the PCB first came out, the holes were not all the way through, so Andrew recommended that I poke through them with the 1/64th bit. That worked.

This was just a test PCB to see if I could get the LED matrix and one touch sensor working. I was not able to get the LED matrix working, and Andrew suggested that it may have been because of my poor soldering job, so I just went ahead and remilled instead of trying to find all the issues with my soldering.

I updated my PCB to make the full thing with spots for all three touch sensors. The amount of rivets I placed in this PCB was INSANE. After all the PCBs I ended up making, I probably placed around 300 rivets, if not more. Luckily, I got much faster as time went on. The first few rivets took me like 10 minutes a rivet, but by the end, I could do around 20 rivets in 10 minutes.

My updated PCB without rivets:

My updated PCB with rivets:

My soldered PCB with some components on it:

When I tested this LED matrix and got an LED to light up, I was so so happy. I had asked for help and done essentially everything I could to get it to work, so when I finally did, I was so proud and grateful.

However, I made the silly mistake of getting the pinout of my MPR121 sensors backwards, so the traces did not line up correctly. I did not want to have to do all the rivets again for the LED matrix or solder the whole thing again and risk messing it up, so Mr. Dubick said I could use the bandsaw to cut my PCB in half. My PCB design was thoughtfully designed so that half of the microcontroller controlled the LED matrix, and the other half of the microcontroller controlled the sensors, so cutting it in half would not disturb any traces. I drew a line with a dry erase marker showing me where to cut, and I tested with several old PCBs first.

My cut PCB:

Here are some videos of me testing the matrix half of the PCB with the MPR121 sensor on a breadboard:

This was my new touch half PCB design:

This was my new touch half PCB:

It did not work, so I decided to back up and go back to one sensor.

This worked:

When I tried to add more sensors, it did not work, but this is where I figured out the issue with the sensor, which you can read about below.

When I got the new MPR121, I milled the PCB again:

As soon as I milled it, I realized that the new MPR had 7 pins and a different pinout than the old one, so I had to fix the design of my PCB again. I was in a hurry, so when I milled it, I only used the 1/32nd bit. It turned out to look awful and did not work because of all the dust in the traces.

I was feeling really discouraged and stressed out at this point in my project, so I asked Mrs. Morrow for help, and she came in to help me, which I really appreciated.

First, she pointed out that my sensor was not fully soldered onto its pin headers:

I also had a solder bridge:

We ultimately just remilled it, and she showed me how to increase the trace clearance, which was really helpful, and I wish I had known about that feature sooner. It helped avoid so many solder bridges and made soldering so much easier.

New PCB:

I cut this PCB in half with the bandsaw to use the LED matrix, which Andrew Puky was so kind to help me solder:

I had to go through and fix these rows because there were solder bridges or something interfering with them:

Through this project, I learned that a multimeter is the most beneficial tool ever. I did not know how to use a multimeter before Fab Academy, but now I cannot deal with PCBs or electronics without one.

Final PCB

Here is the combined PCB that I ended up using:

Troubleshooting Touch Sensors

I used this code to try and make sure both sensors were being recognized:

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Change pins if you’re using different SDA/SCL
  Wire.begin(10, 9);  // SDA = GPIO21, SCL = GPIO22

  Serial.println("\nI2C Scanner Starting...");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  Serial.println("Scanning I2C bus...");

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("✅ I2C device found at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
  }

  if (nDevices == 0) {
    Serial.println("❌ No I2C devices found. Check wiring!");
  }

  delay(3000);  // Wait 3 seconds before scanning again
}

It did not work for several hours before I finally realized the issue was in the sensor that I ordered. The ADD pin was purposefully bridged and that made it not work for anything other than the default GND connection. Essentially how the MPR121 sensor works is that if you want to use multiple sensors, which I did, all the sensors share the SDA, SCL, GND, and VCC but have different ADDR. The ADDR pin can be connected to GND (default), power, SDA, or SCL, and then the code just has to recognize which is which. If you do not know which is which, running an I2C scanner is very helpful. To cut the bridge in the ADD pin, I used an exacto knife.

Before:

After:

This code was able to read touch signals from two MPR121 sensors:

#include <Wire.h>
#include "Adafruit_MPR121.h"

Adafruit_MPR121 cap1 = Adafruit_MPR121();  // 0x5A
Adafruit_MPR121 cap2 = Adafruit_MPR121();  // 0x5C

uint16_t last1 = 0;
uint16_t last2 = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin(10, 9);  // SDA, SCL

  if (!cap1.begin(0x5A)) {
    Serial.println("MPR121 0x5A not found");
    while (1);
  }

  if (!cap2.begin(0x5C)) {
    Serial.println("MPR121 0x5C not found");
    while (1);
  }

  Serial.println("MPR121s ready");
}

void loop() {
  uint16_t now1 = cap1.touched();
  uint16_t now2 = cap2.touched();

  for (int i = 0; i < 12; i++) {
    if ((now1 & (1 << i)) && !(last1 & (1 << i))) {
      Serial.print("0x5A pin "); Serial.print(i); Serial.println(" touched");
    }
    if (!(now1 & (1 << i)) && (last1 & (1 << i))) {
      Serial.print("0x5A pin "); Serial.print(i); Serial.println(" released");
    }

    if ((now2 & (1 << i)) && !(last2 & (1 << i))) {
      Serial.print("0x5C pin "); Serial.print(i); Serial.println(" touched");
    }
    if (!(now2 & (1 << i)) && (last2 & (1 << i))) {
      Serial.print("0x5C pin "); Serial.print(i); Serial.println(" released");
    }
  }

  last1 = now1;
  last2 = now2;
  delay(100);
}

That sensor also did not have built-in pull up resistors, which took me so long to figure out, so I had to get a different one off Digikey. Ultimately, I learned from that experience that sometimes it is better to have good quality even if there is a slight price difference because I ended up wasting weeks troubleshooting a sensor that was just missing components.

The new sensor was also nice because it had a power light, so that definitely helped with troubleshooting. My default process for troubleshooting the touch sensors was to first ensure they were recieving power, run an I2C scanner to see if the microcontroller was picking up all three, and then run my touch sensor code. This was a pretty good process and by the end when I had to do a lot of troubleshooting, I was very glad to have discovered this process.

This was my I2C scanner code:

#include <Wire.h>

#define SDA_PIN 5
#define SCL_PIN 4

void setup() {
  Serial.begin(115200);
  delay(1000);
  Wire.begin(SDA_PIN, SCL_PIN);
  Serial.println("🔍 Starting I2C scan...");
}

void loop() {
  byte error, address;
  int nDevices = 0;

  Serial.println("📡 Scanning I2C devices...");

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("✅ Found device at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
  }

  if (nDevices == 0)
    Serial.println("❌ No I2C devices found.");
  else
    Serial.println("✅ Scan complete.");

  delay(2000);
}

This was my go-to sensor code:

#include <Wire.h>
#include <Adafruit_MPR121.h>


#define SDA_PIN 3   // your wiring SDA pin
#define SCL_PIN 8   // your wiring SCL pin

Adafruit_MPR121 cap1 = Adafruit_MPR121(); // sensor at 0x5A
Adafruit_MPR121 cap2 = Adafruit_MPR121(); // sensor at 0x5B
Adafruit_MPR121 cap3 = Adafruit_MPR121(); // sensor at 0x5C

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(10);

  Wire.begin(SDA_PIN, SCL_PIN);

  if (!cap1.begin(0x5A)) {
    Serial.println("❌ MPR121 not found at 0x5A, check wiring!");
  } else {
    Serial.println("✅ MPR121 found at 0x5A");
  }

  if (!cap2.begin(0x5B)) {
    Serial.println("❌ MPR121 not found at 0x5B, check wiring!");
  } else {
    Serial.println("✅ MPR121 found at 0x5B");
  }

  if (!cap3.begin(0x5C)) {
    Serial.println("❌ MPR121 not found at 0x5C, check wiring!");
  } else {
    Serial.println("✅ MPR121 found at 0x5C");
  }

  delay(5000);  // optional: wait for calibration
}

void loop() {
  uint16_t touched1 = cap1.touched();
  uint16_t touched2 = cap2.touched();
  uint16_t touched3 = cap3.touched();

  // Sensor 0x5A
  for (uint8_t i = 0; i < 12; i++) {
    if (touched1 & (1 << i)) {
      Serial.print("[0x5A] Pin ");
      Serial.print(i);
      Serial.println(" is touched.");
    }
  }

  // Sensor 0x5B
  for (uint8_t i = 0; i < 12; i++) {
    if (touched2 & (1 << i)) {
      Serial.print("[0x5B] Pin ");
      Serial.print(i);
      Serial.println(" is touched.");
    }
  }

  // Sensor 0x5C
  for (uint8_t i = 0; i < 12; i++) {
    if (touched3 & (1 << i)) {
      Serial.print("[0x5C] Pin ");
      Serial.print(i);
      Serial.println(" is touched.");
    }
  }

  delay(100);
}

I wanted to see if the touch sensor was attached to any size copper if it would still work, and I found out that any size worked. However, I needed to lower the threshold too much for 1/8th inch acrylic, so I decided a thinner material would be better.

I also wanted to test to make sure the LEDs and sensor would be able to work together. It was successful.

Case

For the case, I was initially inspired by one of my prior engineering projects: an infinity mirror. That case was made out of wood and had grooves in it. I wanted to recreate that vision but make it deeper so that I would have enough room for the wires and everything.

I tested the acrylic covered by mirror film to see if the light would shine through it, and it did.

I first made a finger joint acrylic box, but then when my peer Angel Fang presented, he advised against using finger joint boxes.

Laser cutting:

Then, Mrs. Morrow advised that I just 3D print a case because I was getting low on time. My first 3D printed case had one detatachable wall:

Initial 3D printed case design:

3D printing:

Initial 3D printed case:

Screw kit:

Testing hinges (file found on Thingiverse):

Inside layout:

Outside view:

I accidentally made that case where the holes for the screws were in the same spot as the LED holder shelf, so I decided to reprint it. I also wanted to reprint it because I figured it would be much easier to wire everything if it was more open, so I changed the design so that three of the walls would be detachable. This turned out to be very useful. The three walls were able to slide on and make a perfect snug fit, so I did not end up even needing the hinges.

I ended up revising the design to this:

Three walls separated:

Parameters:

Timeline video:

Led Holder

Design:

Final:

Acrylic Top

Mrs. Morrow and I brainstormed ideas for how to do the touch pads. I knew that if we did it through a material, the material would have to be thin enough and we would have to find a way to attach whatever touch pads to the material, and the LED light would still need to be visible. Mrs. Morrow’s first idea was to use rivets and stick the wire through the bottom. This sounded like a great idea at first, but it ended up not working at all. The acrylic got all cracked and did not look good at all.

Testing rivet sizes:

Rivet version:

Cracked acrylic:

Her next idea was to use BBs as the touch material, make holes in the acrylic and set them on top, and solder wires to the bottom of them. Solder did not stick to the BB very well directly, so we soldered onto copper tape and then taped the bottom of the BB.

Testing BB:

The BBs ended up being the best idea.

This was my updated design:

After laser cutting it, I spraypainted it with Frosted Glass spraypaint:

Here is what it looked like with all the BBs:

Assembling

At this point of the process, I had made my PCB, ensured that my touch sensors worked, and finalized the case. The next step was assembling the LED matrix.

I also had to figure out how to add the switch and battery pack to the PCB.

Testing switch:

I had to choose what size LED I wanted. I ended up choosing the middle size because it was bright enough, and I liked how it looked.

I wired up a little over half the LEDs and then decided to test it before finishing. When I tested it, only two of the rows worked, so I ended up having to take off all the wires and troubleshoot my PCB with a multimeter again. I was confused because all the LEDs would turn on when I turned them all on, but when I controlled rows or columns individually only two rows worked.

Wired half LEDs:

All on:

Rows that worked:

Fixed all rows:

Testing all rows:

I wanted to make sure the light was visible enough through the riveted spraypainted acrylic, and they did.

At this point, I learned that I cannot take good pictures of my LED matrix due to the multiplexing. Multiplexing essentially means that the LEDs are flickering so fast that you cannot really see it, and it gives the illusion that they are all on at the same time. Multiplexing is used so that the microcontroller is able to power the LEDs enough.

This is what the pictures turn out like:

This is what the wiring looked like:

I was initially planning on including the screen, but it overwhelmed the I2C system of the microcontroller, so I decided not to. I kept getting I2C errors when I combined the screen code with the code for the touch matrix but not when they were separate.

Testing:

As I mentioned previously, I cannot take good pictures of my LED matrix due to the multiplexing. Multiplexing essentially means that the LEDs are flickering so fast that you cannot really see it, and it gives the illusion that they are all on at the same time. Multiplexing is used so that the microcontroller is able to power the LEDs enough. For my final video, I wnated to avoid this flickering effect as much as possible, so I did my research. First, I had Claude rework my code to make it as smooth as possible, which was very successful. Then, I used a website to reduce the framerate and messed around with the choices until I found one I was satisfied with.

Original framerate:

Smoothed multiplexing:

Adjusted framerate:

I took off the tape for the finished product.

This is what the outside looked like:

Web server:

Hero Shots

Display Mode:

Interactive Mode:

Files

Acrylic Top

Case

Code

LED Holder

PCB Design

Rough Overall Design

AI Help

Here are some of my ChatGPT searches from this project: PDF

I also used Claude for some tasks. Majority of the ideas of this project were from me, my peers, or my teachers, but ChatGPT and Claude were very helpful for my code.


Last update: August 7, 2025