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

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