Mechtronics Projects

Fall 2021 - Fall 2022

Robotic Kinetic Sand Art

See Bottom of Page for Arduino Code

Project Overview: Robotic Kinetic art piece that utilizes motors and magnets to autonomously drag a ball in a layer of sand and draw abstract circular patterns. The speed at which the robot draws is based on how close the user holds their hand to an distance sensor on the outside of the device. 

Skills: Arduino Uno, motor, and sensor integration, PDI motor control (external motor controller), DC driver usage, electrical engineering, wringing diagrams, timer interrupts, PWM, mechanical modeling, high fidelity prototyping. 

Major Project Components: 

-Servo and stepper motors 

-Arduino Uno

-Dc driver 

-Distance Sensor embedded on the side of the round base

-Two rotating motors with mechanical arms to hold and move the internal magnet

-Sand tray with metal ball that is pulled by the magnets underneath

-3D printed housing and laser cut acrylic top plate

"Simon Says" Gaming Module

Goal: Create a low cost gaming module to play "Simon Says" the call and repeat game.

Major Components:

-ESP32-CAM microprocessor to power and control  --

-Arduino Programming to interface the EPS32 with the LED lights, Adafruit speaker. 

-3D Design and Modeling in Solidworks to create the shell and buttons. 

My Role: 
Served at the 3D printing and CAD lead on the team. Designed the outer box to house all electrical components, buttons, and lights. Created an innovative solution to create tactile buttons using precise tolerancing of 3D printed parts.

Supported electrical lead with troubleshooting sensor and wiring errors.  Contributed to coding of the lights and sounds module. 

See Bottom of Page for Arduino Code

ESP-32, adafruit LED display, and Pulse oximeter integration Project

Utilized the ESP-32 and Arduino IDE to create a pulse oximeter display device that accurately reads and displays the user's heart rate, blood oxygen levels, and time of data collection. 

Ardunio Code

Kinetic Art Arduino Code
Maria Tagliaferri 2023
#include <avr/interrupt.h>
float w_d=20; //Desired motor speed
float e, IA=0, Kp=6, kp=1, Ki=.5,w_encA, uA,countA=0; //PI System Variables
void setup() {
  pinMode(5,OUTPUT);	//analog Output Motor A
  pinMode(12,OUTPUT);	//digital Ground Motor A
//Hypothetical Motor B pins
  pinMode(9,OUTPUT);
  pinMode(13,OUTPUT);
//Set all speeds to zero to start
  analogWrite(5,0);
  digitalWrite(12,0);
  digitalWrite(13,0);
  analogWrite(9,0);
cli(); // Disables all interrupts
// External Interrupt set up
//Encoder Pin 2 - Encoder A
EIMSK |= (1 << INT0); // Enable/Disable external interrupt INT0
EICRA |= (1 << ISC00); // Trigger Selection for INT0
EICRA |= (1 << ISC01);
//timer1 interrupt setup for 50Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 312;// = (16*10^6) / (50*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 2048 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
sei(); // Enables all interrupts
pinMode(2, INPUT);
pinMode(3,INPUT);
Serial.begin(9600);
}


// External Interrupt for pin 2 - Encoder A
ISR(INT0_vect){
  countA++;
}
ISR(TIMER1_COMPA_vect) {
 w_encA = countA*2.08;
 countA=0;
 e=w_d-w_encA;
 Kp=kp+Ki*0.02; 
 uA=e*Kp+Ki*IA;
 IA=IA+e;
}


void loop() {
  //motor A
  if(uA>255){ //if reaches max speed
    uA=255;
  }
  else if(uA<0){ //if below minimum speed
    uA=0;
  }
  analogWrite(5,uA);
  }




///Servo Code
#include <Servo.h>
Servo servo1;
int servoPin = 4;
//sonar variables
const int pingPin = 11; // Trigger Pin of Ultrasonic Sensor
const int echoPin = 10; // Echo Pin of Ultrasonic Sensor
long duration, cm;
int dist;
//Logic variables
int angle = 12;
bool move_pos=1;
bool ultra_new=1;
int countTIME=0;
void setup(){
Serial.begin(9600);
//Servo initialization
servo1.attach(servoPin);
//Sonar Pins
pinMode(pingPin,OUTPUT);
pinMode(echoPin,INPUT);
cli();//stop interrupts
//set timer2 interrupt at 8kHz
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  // set compare match register for 8khz increments
  OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for 8 prescaler
  TCCR2B |= (1 << CS21);   
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  sei(); // Enables all interrupts
}
long microsecondsToCentimeters(long microseconds) {
   return microseconds / 29 / 2;
}


ISR(TIMER2_COMPA_vect){

if(countTIME<8000){ //idle for 7999 cycles
    countTIME++;
  }
  else{	//on 8000th cycle, executes longer sonar sensor loop
ultra_new=1;
countTIME=0;
}
countTIME++;
}



void loop(){
 
  if(ultra_new){ //commanded by timer, new speed check
   pinMode(pingPin, OUTPUT);
      digitalWrite(pingPin, LOW);
      delayMicroseconds(2);
      digitalWrite(pingPin, HIGH);
      cli();//stop interrupts
      delayMicroseconds(10);
      sei(); // Enables all interrupts
      digitalWrite(pingPin, LOW);
      pinMode(echoPin, INPUT);
      duration = pulseIn(echoPin, HIGH);
      cm = microsecondsToCentimeters(duration); //measuring how far away something is with ping/echo
      //Serial.print(cm);
      //Serial.print("cm");
      Serial.println();
 
      //Serial.print(ultra_new);
    ultra_new=0;
  }
if(angle>=180){ //reached maximum position
  move_pos=0; //start moving negative
}
else if(angle<=10){ //reached minimum position
  move_pos=1; //start moving positive
}
/*
//speed boundary 1
if(cm<5){
      if(move_pos==1){
        angle = angle + 1;
      }
      else if(move_pos==0){
        angle = angle - 1;
      }
      servo1.write(angle);
      Serial.println(angle);
   }
//speed boundary 2
 else if(cm>5 || cm<10){
    if(move_pos == 1){
        angle = angle + 5;
      }
      else if(move_pos==0){
         angle = angle - 5;
      }
    servo1.write(angle);
    Serial.println(angle);
  }
//speed boundary 3
 else{
    if(move_pos == 1){
        angle = angle + 10;
      }
      else if(move_pos==0){
        angle = angle - 10;
      }
    servo1.write(angle);
    Serial.println(angle);
  }
*/
if(cm<5){
        m=1;
   }
 else if(cm>5 || cm<10){
    m=5;
  }
 else{
    m=10;
  }
if(move_pos == 1){
        angle = angle + m;
}
else if(move_pos==0){
      angle = angle - m;
}
servo1.write(angle);
Serial.println(angle);
delay(100);
}

Simon Says Arduino Code
//Maria Tagliaferri and Timothy Nicholson October 2022
#define R 13
#define B 1
#define Y 2
#define G 15
#define ST 3
#define SP 14
#define A 12


void setup() {
// put your setup code here, to run once:
pinMode(R,INPUT_PULLUP);
pinMode(B,INPUT_PULLUP);
pinMode(Y,INPUT_PULLUP);
pinMode(G,INPUT_PULLUP);
pinMode(ST,INPUT_PULLUP);
pinMode(SP,OUTPUT); digitalWrite(SP,LOW);
pinMode(A,OUTPUT); digitalWrite(A,LOW);

randomSeed(analogRead(0));
}

bool game_start = false;
bool simon = true;
bool done = false;
bool made = false;
int order[100]; // Stores pattern
int says_count = 0; // Tracks number of lights to play in pattern
int do_count = 0; // Tracks number of inputs read
int choice;
int color;
int game_time = 0; // Timer for inputs
int speaker_time = 0; // Timer for speaker to play

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

// Game not in play
if(!game_start) { // Reset necessary variables and wait for start button
says_count = 0;
simon = true;
digitalWrite(A,LOW);
if(digitalRead(ST)==HIGH) {
delay(20);
while(digitalRead(ST)==HIGH);
game_start = true;
pinMode(ST,OUTPUT); digitalWrite(ST,LOW);
digitalWrite(A,HIGH);
delay(1000);
pinMode(ST,INPUT_PULLUP);
}
}
// Game in play
else {
if(simon) { // Play pattern
digitalWrite(A,HIGH);
says_count++;
color = random(4);
simon_says(says_count, color);
simon = false;
}
else { // Repeat pattern
digitalWrite(A,LOW);
do_count = 0;
done = false;
game_time = esp_timer_get_time() + 10000000; // 10 sec to repeat pattern
while(esp_timer_get_time() < game_time) {
simon_do(says_count);
}
simon = true;
if(!done) { // If wrong button pressed or ran out of time
game_start = false;
digitalWrite(A,HIGH);
pinMode(ST, OUTPUT); digitalWrite(ST, LOW);
speaker_time = esp_timer_get_time() + 1000000; // 1 sec
while(esp_timer_get_time()
Pulse Oximeter Arduino Code
//Maria Tagliaferri December 2022
#include 
#include 
#include 
#include 
#include 
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define disp 19
#define heart 18
#define button 12
Adafruit_SSD1306*display;
volatile bool switchvar = false;
volatile bool switch2 = false; 
void IRAM_ATTR int_func(){
   switch2 = true;
  }
DFRobot_MAX30102*particleSensor;
/*
Macro definition options in sensor configuration
sampleAverage: SAMPLEAVG_1 SAMPLEAVG_2 SAMPLEAVG_4
               SAMPLEAVG_8 SAMPLEAVG_16 SAMPLEAVG_32
ledMode:       MODE_REDONLY  MODE_RED_IR  MODE_MULTILED
sampleRate:    PULSEWIDTH_69 PULSEWIDTH_118 PULSEWIDTH_215 PULSEWIDTH_411
pulseWidth:    SAMPLERATE_50 SAMPLERATE_100 SAMPLERATE_200 SAMPLERATE_400
               SAMPLERATE_800 SAMPLERATE_1000 SAMPLERATE_1600 SAMPLERATE_3200
adcRange:      ADCRANGE_2048 ADCRANGE_4096 ADCRANGE_8192 ADCRANGE_16384
*/
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 };
void setup()
{
  //Init serial
  Serial.begin(115200);
  pinMode(disp,OUTPUT); 
  pinMode(heart, OUTPUT);
  pinMode(button,INPUT_PULLUP); 
  digitalWrite(disp,HIGH);
  digitalWrite(heart,HIGH);
  attachInterrupt(button,int_func ,FALLING);
  display = new Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  Wire.begin(33,16);//14,25
    if(!display->begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  /*!
   *@brief Init sensor 
   *@param pWire IIC bus pointer object and construction device, can both pass or not pass parameters (Wire in default)
   *@param i2cAddr Chip IIC address (0x57 in default)
   *@return true or false
   */
   particleSensor = new DFRobot_MAX30102();
  while (!particleSensor->begin()) {
    Serial.println("MAX30102 was not found");
    delay(1000);
  }
  /*!
   *@brief Use macro definition to configure sensor 
   *@param ledBrightness LED brightness, default value: 0x1F(6.4mA), Range: 0~255(0=Off, 255=50mA)
   *@param sampleAverage Average multiple samples then draw once, reduce data throughput, default 4 samples average
   *@param ledMode LED mode, default to use red light and IR at the same time 
   *@param sampleRate Sampling rate, default 400 samples every second 
   *@param pulseWidth Pulse width: the longer the pulse width, the wider the detection range. Default to be Max range
   *@param adcRange ADC Measurement Range, default 4096 (nA), 15.63(pA) per LSB
   */
  particleSensor->sensorConfiguration(/*ledBrightness=*/50, /*sampleAverage=*/SAMPLEAVG_4, \
                        /*ledMode=*/MODE_MULTILED, /*sampleRate=*/SAMPLERATE_100, \
                        /*pulseWidth=*/PULSEWIDTH_411, /*adcRange=*/ADCRANGE_16384);
}
int32_t SPO2; //SPO2
int8_t SPO2Valid; //Flag to display if SPO2 calculation is valid
int32_t heartRate; //Heart-rate
int8_t heartRateValid; //Flag to display if heart-rate calculation is valid 
void start_up(){
  digitalWrite(disp,HIGH);
  digitalWrite(heart,HIGH);
  display = new Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
   if(!display->begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
     particleSensor = new DFRobot_MAX30102();
  while (!particleSensor->begin()) {
    Serial.println("MAX30102 was not found");
    delay(1000);
  }
    particleSensor->sensorConfiguration(/*ledBrightness=*/50, /*sampleAverage=*/SAMPLEAVG_4, \
                        /*ledMode=*/MODE_MULTILED, /*sampleRate=*/SAMPLERATE_100, \
                        /*pulseWidth=*/PULSEWIDTH_411, /*adcRange=*/ADCRANGE_16384);
  switchvar = !switchvar;
  }
void end_func(){
  display->println(F("Shutting Down :("));
  display->display();
  delete display;
  delete particleSensor; 
  digitalWrite(disp,LOW);
  digitalWrite(heart,LOW);
  switchvar = !switchvar;
  switch2 = !switch2;
  }
void loop()
{
  if(switch2){
    if(switchvar) start_up();
    else end_func();
  }
  if(switch2){
  display->clearDisplay();
  display->setTextSize(1);             // Normal 1:1 pixel scale
  display->setTextColor(SSD1306_WHITE);        // Draw white text
  display->setCursor(0,0);   
  Serial.println(F("Wait about four seconds"));
  particleSensor->heartrateAndOxygenSaturation(/**SPO2=*/&SPO2, /**SPO2Valid=*/&SPO2Valid, /**heartRate=*/&heartRate, /**heartRateValid=*/&heartRateValid);
  //Print result 
  Serial.print(F("heartRate="));
  display->println(heartRate, DEC);
  display->display();
  delay(2000); 
  Serial.print(F(", heartRateValid="));
  Serial.print(heartRateValid, DEC);
  Serial.print(F("; SPO2="));
  display->println(SPO2, DEC);
  display->display();
  Serial.print(F(", SPO2Valid="));
  Serial.println(SPO2Valid, DEC);
  }
}