23 April 2026

Team 2: Temple Run

Our Controller:



Description:

Our controller is a stylish way to play the game Temple Run. The design of the controller mimics the look of the pathways that you run through in the game, and the guy that our controller uses is designed after the guy in the game. We wanted our controller to be highly themed to create a unique experience for each person that uses it. 

The technical aspects of the controller consist of 3 photoresistors, 1 range sensor, and 1 button. The 3 photoresistors are mapped to the inputs for left, right, and slide, while the range sensor is mapped to the jump key. The button is simply used to start the game and activate your power ups when you unlock those. The code for the game simply detects in the range or light level is within a certain threshold and presses the assigned button if it is. You simply hover the guy figure over the photoresistor or range sensor to simulate this input. 

Since you are actively moving the figure around the controller, it feels as though you are physically controlling the character in the game, creating a unique controller experience.

There were certain design intentions that were not able to be translated and/or did not come out as intended. These include: the size of the controller came out smaller than anticipated and the range sensor was intended to detect if the figure was lifted off of the sensor rather than the other way around.

Do you feel that the controller would be better with the ability to lift the figure as it was originally intended, or does it not make too much of a difference?

Schematic:



Project Code:

#include <Keyboard.h>
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_Circuit_Playground.h>

//Initializing Variables
#define startButton 1

#define trigPin 9
#define echoPin 6
int disThreshold = 60;

const int photoLeft = A3;
const int photoRight = A4;
const int photoSlide = A6;
const int debounce = 200;

int lumoLeft;
int lumoRight;
int lumoSlide;
int lumoThreshold = 200;

void setup() {
  //Basic setup for code
  Serial.begin(9600);
  CircuitPlayground.begin();
  Keyboard.begin();
  delay(1000);

  //Initializing pin modes
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(startButton, INPUT);
}

void loop() {
  //Calling functions that store the code
  startButtonInputCheck();
  jumpInputCheck();
  leftInputCheck();
  rightInputCheck();
  slideInputCheck();

  //Delay between checks
  delay(150);
}

//Start button function
void startButtonInputCheck() {
  if(digitalRead(startButton) == HIGH) {
    Keyboard.write(' ');
    Serial.println("START"); //For debug/testing
    delay(debounce);
  }
}

//Left input function
void leftInputCheck() {
  lumoLeft = analogRead(photoLeft);
 
  if(lumoLeft < lumoThreshold) {
    Keyboard.write('A');
    Serial.println("LEFT"); //For debug/testing
    delay(debounce);
  }
}

//Right input function
void rightInputCheck() {
  lumoRight = analogRead(photoRight);
 
  if(lumoRight < lumoThreshold) {
    Keyboard.write('D');
    Serial.println("RIGHT"); //For debug/testing
    delay(debounce);
  }
}

//Slide input function
void slideInputCheck() {
  lumoSlide = analogRead(photoSlide);
 
  if(lumoSlide < lumoThreshold) {
    Keyboard.write('S');
    Serial.println("SLIDE"); //For debug/testing
    delay(debounce);
  }
}

//Jump input function
void jumpInputCheck() {
  //Code for range sensor
  long duration, distance;
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  distance = duration * 0.1715;

  //Printing distance in serial monitor for tracking and debugging
  Serial.println(distance);

  //Distance check for input
  if(distance < disThreshold) {
    Keyboard.write('W');
    Serial.println("JUMP"); //For debug/testing
    delay(debounce);
  }
}

Video Demonstration:



14 April 2026

Jasmine Darman Final Project Progress/ Prototype

 The main goal of this project is to recreate a wooden board game to give better visual indicates for player piece movement. It will have 3 parts: an RFID reader to get the piece, an array of neo pixels, and an array of push buttons over top a layer of copper tape. when a user scans their piece, the RFID reader saves their piece data. The player then places their piece down on the board and holds down. This sets registers a location within the push button array. This is then given to the LED array to signal how they should display. 
Here is the Schematic: 


The two arrays are visually separated, but they are meant to be on top of one another. The red is representative of the individual tiles. 
For the starting progress, I worked on feasibility and modeling to try to get an effective push down button. It was 3 iterations and still counting.
At first, I tried to make it so you close a circuit when you get a press down. 
 This came with an Led inside and a program that checked for a completed circuit before turning on the LED. This iteration used a lot of copper tape and soldering. The finished result was having to place the upper part of the button directly onto a section to complete a circuit. 


Video for Button 1

The second iteration I tried to use TPU plastic that is flexible to make the moving part. This was too flexible and resulted in the top of the button not having any bounce. However, The top of the button was now surrounding parts of the bottom of it. The top this time was thinner, but too thin to where it could easily break. 

The third iteration used Transparent PETG for the spring effect and the top of the button was glued to the bottom as a strictly bouncing button prototype. It was very effective. 

Video for button 3
While creating buttons, I was also getting a list of materials for the final project. I've bought all of it except for PETG. 
 Additionally, I had shifted the way I was making the bttons from having an open curcuit to having two lines of conductive material meeting. 
Each intersection where there is a push button will have 3 layers of paper: 
Layer 1 at the bottom will have lines of copper tape going in one of the hexagonal diagonals. 
Layer 2 will have an opening small enough to where layer 1 and 3 would touch when pressed together. 
Layer 3 at the top will have lines of copper tape going in one of the other hexagonal diagonals. 
The current code for the project is mainly what was taught in class. This was modified for an 11 by 11 grid and pin numbers are changed to match the schematic above. 
#include <Shifter.h>



//pins  for the mux 4 // muxes are 11
int muxPin1 = 4; //s0
int muxPin2 = 5;//s1
int muxPin3 = 7; //s2
int muxPin4 = 8; //s3
//power and ground as well


//3 pins for the shifter
int pin1 = 2; // ser pin
int pin2 = 1; //L clock
int pin3 =0; // clock
int registerNumber = 2;

//ser in to ser out, l clock to l clock, clock to clock,
//ground ground, vcc to vcc


//A5
int muxPin5 = A5; //sig

//ser pin, l clock, clock
Shifter shifter(pin1, pin2, pin3, registerNumber); //shifters are 11
int array[121];


void setup() {
  // set all muxs to output
  pinMode(muxPin1, OUTPUT);
  pinMode(muxPin2, OUTPUT);
  pinMode(muxPin3, OUTPUT);
  pinMode(muxPin4, OUTPUT);
  pinMode(muxPin5, INPUT);// you need a pull down resisteor on the muxes





  //loop through the array set it all to 0


  //start a serial


}

void loop() {
  // put your main code here, to run repeatedly:

  //iterating through the shifters by 11
 for( int i = 0; i < 11; i++){
  shifter.clear(); // ground it all
  // set an i to high
  shifter.setPin(i, HIGH);
  shifter.write(); // set pin value to the device

  //for loop with a
  for(int x = 0; x < 11; x++){
    array[i*11+x] = readMux(x);
  }


 }


}

int readMux(int channel){
  int controlPin[] = {muxPin1, muxPin2, muxPin3, muxPin4};

  int muxChannel[16][4]={
    {0,0,0,0}, //channel 0
    {1,0,0,0}, //channel 1
    {0,1,0,0}, //channel 2
    {1,1,0,0}, //channel 3
    {0,0,1,0}, //channel 4
    {1,0,1,0}, //channel 5
    {0,1,1,0}, //channel 6
    {1,1,1,0}, //channel 7
    {0,0,0,1}, //channel 8
    {1,0,0,1}, //channel 9
    {0,1,0,1}, //channel 10
    {1,1,0,1}, //channel 11
    {0,0,1,1}, //channel 12
    {1,0,1,1}, //channel 13
    {0,1,1,1}, //channel 14
    {1,1,1,1}  //channel 15
  };
 
  //loop through the 4 sig
  for(int i = 0; i < 4; i ++){
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }

  //read the value at the SIG pin
  int val = analogRead(muxPin5);

  //return the value
  return val;
}









12 April 2026

IVergara - Final project prototype

 Productivity pal

This project is a small productivity device that uses RFID tags to trigger different modes on a screen. The idea is to make the interaction feel more physical and intentional: instead of opening features through regular buttons or menus, the user taps a specific tag to launch a mode. One mode is a focus timer for work sessions, and another is an active break mode where the user selects a short exercise and follows a countdown on screen.

I originally started the project with an ESP32, but I ran into technical issues while testing the display, which made development less reliable. Another factor was speed: every change on the ESP32 took noticeably longer to compile and upload, which slowed down testing and experimentation. Because of that, I moved the project to an Arduino Uno. The Uno felt easier to work with for this prototype because uploads were much faster and the overall setup was simpler to debug.

The device combines a display, an RFID reader, and a rotary encoder. The RFID reader and the screen share some communication pins, which keeps the wiring compact, while separate control pins let each part work correctly in the same system. The encoder is used to move through options and confirm selections, so once a mode is opened, the user can interact with it in a simple and clear way.

 Video



 

 

 

 Photos








 Schematic

 

 

Code 

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <MFRC522.h>

#define TFT_CS    10
#define TFT_DC    8
#define TFT_RST   9

#define RFID_SS   7
#define RFID_RST  2

const int pinA = 3;
const int pinB = 4;
const int pushPin = 5;
const int k0Pin = 6;

int currentMode = 0;  

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
MFRC522 rfid(RFID_SS, RFID_RST);

String lastUID = "";


int lastAState;
bool lastButtonState = HIGH;

// Work mode options
int timerOptions[3] = {5, 10, 20};
int selectedIndex = 0;

bool workTimerStarted = false;
unsigned long workStartTime = 0;
unsigned long workDuration = 0;
int lastWorkSecond = -1;

// Break Mode options
String breakOptions[3] = {"Stretch", "Jumping Jacks", "Pushups"};
int breakSelectedIndex = 0;

bool breakTimerStarted = false;
unsigned long breakStartTime = 0;
const unsigned long breakDuration = 2UL * 60UL * 1000UL;
int lastBreakSecond = -1;

// Home screen
void runHomeMode() {
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextSize(2);
  tft.setTextColor(ST77XX_GREEN);
  tft.setCursor(20, 20);
  tft.println("Home");

  tft.setTextSize(1);
  tft.setCursor(20, 60);
  tft.println("Tap an RFID tag");
  tft.setCursor(20, 80);
  tft.println("Blue tag -> Work");
  tft.setCursor(20, 95);
  tft.println("Card -> Break");
}

// Work screen
void drawWorkSelectionScreen() {
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextSize(2);
  tft.setTextColor(ST77XX_YELLOW);
  tft.setCursor(20, 20);
  tft.println("Work Mode");

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(20, 50);
  tft.println("Choose timer:");

  for (int i = 0; i < 3; i++) {
    int y = 80 + i * 25;

    if (i == selectedIndex) {
      tft.fillRect(20, y - 2, 140, 18, ST77XX_YELLOW);
      tft.setTextColor(ST77XX_BLACK);
    } else {
      tft.fillRect(20, y - 2, 140, 18, ST77XX_BLACK);
      tft.setTextColor(ST77XX_WHITE);
    }

    tft.setTextSize(2);
    tft.setCursor(30, y);
    tft.print(timerOptions[i]);
    tft.print(" min");
  }

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_CYAN);
  tft.setCursor(20, 170);
  tft.println("Press knob to start");
}

void enterWorkMode() {
  workTimerStarted = false;
  selectedIndex = 0;
  lastWorkSecond = -1;
  drawWorkSelectionScreen();
}

void drawWorkTimerScreen() {
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextSize(2);
  tft.setTextColor(ST77XX_YELLOW);
  tft.setCursor(20, 20);
  tft.println("Work Mode");

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(20, 50);
  tft.print("Pomodoro ");
  tft.print(timerOptions[selectedIndex]);
  tft.println(" min");
}

void startWorkTimer() {
  workTimerStarted = true;
  workStartTime = millis();
  workDuration = (unsigned long)timerOptions[selectedIndex] * 60UL * 1000UL;
  lastWorkSecond = -1;
  drawWorkTimerScreen();
}

void runWorkMode() {
  if (!workTimerStarted) {
    int currentAState = digitalRead(pinA);

    if (currentAState != lastAState) {
      if (digitalRead(pinB) != currentAState) {
        selectedIndex++;
      } else {
        selectedIndex--;
      }

      if (selectedIndex > 2) selectedIndex = 0;
      if (selectedIndex < 0) selectedIndex = 2;

      drawWorkSelectionScreen();
      delay(5);
    }

    lastAState = currentAState;

    bool currentButtonState = digitalRead(pushPin);

    if (currentButtonState == LOW && lastButtonState == HIGH) {
      startWorkTimer();
      delay(200);
    }

    lastButtonState = currentButtonState;
  } else {
    unsigned long elapsed = millis() - workStartTime;
    if (elapsed > workDuration) {
      elapsed = workDuration;
    }

    int remainingSeconds = (workDuration - elapsed) / 1000;
    int minutes = remainingSeconds / 60;
    int seconds = remainingSeconds % 60;

    if (remainingSeconds != lastWorkSecond) {
      lastWorkSecond = remainingSeconds;

      char timeText[6];
      sprintf(timeText, "%02d:%02d", minutes, seconds);

      tft.fillRect(20, 80, 160, 40, ST77XX_BLACK);
      tft.setTextSize(3);
      tft.setTextColor(ST77XX_RED);
      tft.setCursor(20, 80);
      tft.print(timeText);

      if (remainingSeconds == 0) {
        tft.fillRect(20, 140, 140, 30, ST77XX_BLACK);
        tft.setTextSize(2);
        tft.setTextColor(ST77XX_GREEN);
        tft.setCursor(20, 140);
        tft.println("Done");
      }
    }
  }
}

// Break screen
void drawBreakSelectionScreen() {
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextSize(2);
  tft.setTextColor(ST77XX_CYAN);
  tft.setCursor(20, 20);
  tft.println("Break Mode");

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(20, 50);
  tft.println("Choose exercise:");

  for (int i = 0; i < 3; i++) {
    int y = 80 + i * 25;

    if (i == breakSelectedIndex) {
      tft.fillRect(20, y - 2, 180, 18, ST77XX_CYAN);
      tft.setTextColor(ST77XX_BLACK);
    } else {
      tft.fillRect(20, y - 2, 180, 18, ST77XX_BLACK);
      tft.setTextColor(ST77XX_WHITE);
    }

    tft.setTextSize(1);
    tft.setCursor(30, y);
    tft.print(breakOptions[i]);
  }

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_YELLOW);
  tft.setCursor(20, 170);
  tft.println("Press knob to start");
}

void enterBreakMode() {
  breakTimerStarted = false;
  breakSelectedIndex = 0;
  lastBreakSecond = -1;
  drawBreakSelectionScreen();
}

void drawBreakTimerScreen() {
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextSize(2);
  tft.setTextColor(ST77XX_CYAN);
  tft.setCursor(20, 20);
  tft.println("Break Mode");

  tft.setTextSize(1);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(20, 50);
  tft.print("Exercise: ");
  tft.println(breakOptions[breakSelectedIndex]);
}

void startBreakTimer() {
  breakTimerStarted = true;
  breakStartTime = millis();
  lastBreakSecond = -1;
  drawBreakTimerScreen();
}

void runBreakMode() {
  if (!breakTimerStarted) {
    int currentAState = digitalRead(pinA);

    if (currentAState != lastAState) {
      if (digitalRead(pinB) != currentAState) {
        breakSelectedIndex++;
      } else {
        breakSelectedIndex--;
      }

      if (breakSelectedIndex > 2) breakSelectedIndex = 0;
      if (breakSelectedIndex < 0) breakSelectedIndex = 2;

      drawBreakSelectionScreen();
      delay(5);
    }

    lastAState = currentAState;

    bool currentButtonState = digitalRead(pushPin);

    if (currentButtonState == LOW && lastButtonState == HIGH) {
      startBreakTimer();
      delay(200);
    }

    lastButtonState = currentButtonState;
  } else {
    unsigned long elapsed = millis() - breakStartTime;
    if (elapsed > breakDuration) {
      elapsed = breakDuration;
    }

    int remainingSeconds = (breakDuration - elapsed) / 1000;
    int minutes = remainingSeconds / 60;
    int seconds = remainingSeconds % 60;

    if (remainingSeconds != lastBreakSecond) {
      lastBreakSecond = remainingSeconds;

      char timeText[6];
      sprintf(timeText, "%02d:%02d", minutes, seconds);

      tft.fillRect(20, 85, 160, 40, ST77XX_BLACK);
      tft.setTextSize(3);
      tft.setTextColor(ST77XX_GREEN);
      tft.setCursor(20, 85);
      tft.print(timeText);

      if (remainingSeconds == 0) {
        tft.fillRect(20, 140, 220, 50, ST77XX_BLACK);
        tft.setTextSize(2);
        tft.setTextColor(ST77XX_YELLOW);
        tft.setCursor(20, 140);
        tft.println("Congrats on");
        tft.setCursor(20, 165);
        tft.println("staying active");
      }
    }
  }
}

void setup() {
  Serial.begin(9600);
  delay(2000);

  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);
  pinMode(pushPin, INPUT_PULLUP);
  pinMode(k0Pin, INPUT_PULLUP);

  SPI.begin();

  tft.init(240, 320);
  tft.invertDisplay(false);
  tft.setRotation(1);

  rfid.PCD_Init();

  lastAState = digitalRead(pinA);
  runHomeMode();
}

void loop() {
  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
    String uidString = "";

    for (byte i = 0; i < rfid.uid.size; i++) {
      if (rfid.uid.uidByte[i] < 0x10) {
        uidString += "0";
      }
      uidString += String(rfid.uid.uidByte[i], HEX);
      if (i < rfid.uid.size - 1) {
        uidString += " ";
      }
    }

    uidString.toUpperCase();
    lastUID = uidString;

    Serial.print("UID: ");
    Serial.println(lastUID);

    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();

    if (lastUID == "13 EA 00 2D") {
      currentMode = 1;
      lastAState = digitalRead(pinA);
      lastButtonState = HIGH;
      enterWorkMode();
    }
    else if (lastUID == "8C 50 FA 03") {
      currentMode = 2;
      lastAState = digitalRead(pinA);
      lastButtonState = HIGH;
      enterBreakMode();
    }
    else {
      currentMode = 0;
      runHomeMode();
    }
  }

  if (digitalRead(k0Pin) == LOW) {
    currentMode = 0;
    runHomeMode();
    delay(200);
  }

  if (currentMode == 1) {
    runWorkMode();
  }
  else if (currentMode == 2) {
    runBreakMode();
  }

  delay(50);
}