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);
}

 

30 March 2026

30 March 2026 Prototype Review - Team 6

Our prototype is a two-handed flat-panel cardboard controller designed for Star Wars: Squadrons, built to put the player in the feeling of actually piloting a starfighter. The CPE sits at the center of the controller, and its built-in accelerometer handles left and right steering by tilting the whole controller side to side, no joystick needed. Thrust is handled by a thumbstick that controls forward and backward speed. We have four external buttons. Two handle primary fire, secondary fire, and the ones in the circuit playground buttons handle toggle targeting, and ship repair, while a switch on the right side activates sublight boost for a quick speed burst. During prototyping we learned that the potentiometers we originally planned for laser power and shield routing couldn't map cleanly to the in-game scroll wheel inputs the CPE can't replicate, so we cut them. The front/back shield transfer switch was also dropped for the same reason. In the final version we want to revisit those inputs with a different approach.
Peer Review Questions: What other controller styles or inputs do you think would work well for a space combat simulation like this? What do you think is the coolest part of the design so far?