The goal of Elevate is to create a soothing and calming experience for the user, while also giving the user an indirect sense of companionship. Since the beginning of the pandemic, many individuals have remained quarantined from social interaction. Elevate aims to address the lack of interaction by providing a soft, subtle presence in everyday life. Whether it is your morning coffee, or an afternoon book, Elevate acknowledges your actions and highlights your daily routines.

Due to limited access to on-campus resources, we continued the development of Elevate virtually. The next iteration of Elevate includes state-driven movement, to allow for simultaneous motion of multiple panels. Additionally, we looked to improve the mannerisms of the table, along with the user’s experience. As a result, we decreased the speed of the motors to provide the users a subtle and more relaxed interaction.

Updated Code

The updated code base includes state-machines for each panel. A panel consists of an LED, force-sensitive resistor (FSR), and a stepper motor. Each panel is either loaded or unloaded, based on the FSR reading. Each loop, the Master Arduino runs each panel’s state machine, which maintains the state of the panel, and then checks the state of the panel to see if it is loaded or unloaded. If the panel is loaded and was previously unloaded, the master controller sends the state change to the Stepper Driver Arduino. The Driver Arduino loop iterates through a list of steps needed until a panel is at its state. If a panel has steps remaining, each step is taken incrementally, allowing for simultaneous stepper motor movements. When data is received from the master controller, the steps array is updated, and the motor is moved accordingly.

The updated code is provided below:

Master Controller (Arduino Nano)


#include <Wire.h&gt;


const int aA                 = 0;
const int aX                 = 1;
const int aY                 = 2;
const int aZ                 = 3;

const int ledA               = 2;
const int ledX               = 3;
const int ledY               = 4;
const int ledZ               = 5;


// Array for LEDs in following order [a,x,y,z]
boolean lights[4] = {false, false, false, false};

int val_a = 0;
int state_a = 0;
int state_prev_a = 0;
unsigned long t_a = 0;
unsigned long t0_a = 0;

int val_x = 0;
int state_x = 0;
int state_prev_x = 0;
unsigned long t_x = 0;
unsigned long t0_x = 0;

int val_y = 0;
int state_y = 0;
int state_prev_y = 0;
unsigned long t_y = 0;
unsigned long t0_y = 0;


int val_z = 0;
int state_z = 0;
int state_prev_z = 0;
unsigned long t_z = 0;
unsigned long t0_z = 0;

// Debounce delay to avoid false state changes
unsigned long debounceDelay = 300; 

// Threshhold for Loaded/Unloaded reading of FSR
// Depending on mechanical configuration, this value 
// may vary for each panel

int fsr_a_threshhold = 500;
int fsr_x_threshhold = 400;
int fsr_y_threshhold = 600;
int fsr_z_threshhold = 400;



void setup() {
  Serial.begin(9600);
 
  Wire.begin();
  
  pinMode(ledA, OUTPUT);
  pinMode(ledX, OUTPUT);
  pinMode(ledY, OUTPUT);
  pinMode(ledZ, OUTPUT);
  
 
}

void loop() {
  // Run the State Machine of each Motor/FSR/LED combo
  SM_a();
  SM_x();
  SM_y();
  SM_z();

  //Send updated states if applicable
  transmit_states();
}
// Polling Functions

// Polls the state of the lights in the lights array
void poll_lights() {
  if (lights[0]){
    digitalWrite(ledA, HIGH);
  }else{
    digitalWrite(ledA, LOW);
    }
   if (lights[1]){
    digitalWrite(ledX, HIGH);
  }else{
    digitalWrite(ledX, LOW);
    }
    if (lights[2]){
    digitalWrite(ledY, HIGH);
  }else{
    digitalWrite(ledY, LOW);
    }
    if (lights[3]){
    digitalWrite(ledZ, HIGH);
  }else{
    digitalWrite(ledZ, LOW);
    }
}

//// sends State change to Stepper Driver controller and updates LED states
void transmit_states(){

  //Checks to see if state is loaded and was not previously loaded
  
    if (state_a == 5 &amp;&amp; state_prev_a != 5){
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(0);        
    Wire.endTransmission();
    lights[0] = true;
  }
  //Checks to see if state is unloaded
  if (state_a == 4) {
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(1);        
    Wire.endTransmission();
    lights[0] = false;
  }
  if (state_x == 5 &amp;&amp; state_prev_x != 5){
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(2);        
    Wire.endTransmission();
    lights[1] = true;
  }
  if (state_x == 4) {
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(3);        
    Wire.endTransmission();
    lights[1] = false;
  }
    if (state_y == 5 &amp;&amp; state_prev_y != 5){
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(4);        
    Wire.endTransmission();
    lights[2] = true;
  }
  if (state_y == 4) {
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(5);        
    Wire.endTransmission();
    lights[2] = false;
  }
    if (state_z == 5 &amp;&amp; state_prev_z != 5){
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(6);        
    Wire.endTransmission();
    lights[3] = true;
  }
  if (state_z == 4) {
    Wire.beginTransmission(4); // transmit to device #4
    Wire.write(7);        
    Wire.endTransmission();
    lights[3] = false;
  }
  poll_lights();
}

// STATE MACHINES
// Manages the state of the Motor/FSR/LED combo and updates according to 
// FSR reading

void SM_z (){
  state_prev_z = state_z;
  
  switch (state_z){
    case 0: //RESET
      state_z = 1;
      break;
      
    case 1: //START
      val_z = analogRead(aZ);

      if (val_z &gt;= fsr_z_threshhold) {state_z = 2;}
      
      break;
      
     case 2: // SET TIME
      t0_z = millis();
      state_z = 3;
      break;
      
     case 3:// WAIT AND CHECK
      val_z = analogRead(aZ);
      t_z = millis();

      if (val_z < fsr_z_threshhold) {state_z = 0;}

      if (t_z - t0_z &gt; debounceDelay){
        state_z = 5;
      }
      break;
      
     case 4: // UNLOADED
      state_z = 0;
      break;
      
     case 5: // LOADED
      val_z = analogRead(aZ);
      if (val_z < fsr_z_threshhold) {state_z = 4;}
      break; 
  }
}

void SM_y (){
  state_prev_y = state_y;
  
  switch (state_y){
    case 0: //RESET
      state_y = 1;
      break;
      
    case 1: //START
      val_y = analogRead(aY);

      if (val_y &gt;= fsr_y_threshhold) {state_y = 2;}
      
      break;
      
     case 2: // SET TIME
      t0_y = millis();
      state_y = 3;
      break;
      
     case 3:// WAIT AND CHECK
      val_y = analogRead(aY);
      t_y = millis();

      if (val_y < fsr_y_threshhold) {state_y = 0;}

      if (t_y - t0_y &gt; debounceDelay){
        state_y = 5;
      }
      break;
      
     case 4: // UNLOADED
      state_y = 0;
      break;
      
     case 5: // LOADED
      val_y = analogRead(aY);
      if (val_y < fsr_y_threshhold) {state_y = 4;}
      break;    
  }
}

void SM_x (){
  state_prev_x = state_x;
  
  switch (state_x){
    case 0: //RESET
      state_x = 1;
      break;
      
    case 1: //START
      val_x = analogRead(aX);

      if (val_x &gt;= fsr_x_threshhold) {state_x = 2;}
      
      break;
      
     case 2: // SET TIME
      t0_x = millis();
      state_x = 3;
      break;
      
     case 3:// WAIT AND CHECK
      val_x = analogRead(aX);
      t_x = millis();

      if (val_x < fsr_x_threshhold) {state_x = 0;}

      if (t_x - t0_x &gt; debounceDelay){
        state_x = 5;
      }
      break;
      
     case 4: // UNLOADED
      state_x = 0;
      break;
      
     case 5: // LOADED
      val_x = analogRead(aX);
      if (val_x < fsr_x_threshhold) {state_x = 4;}
      break;
     
    
  }
  

}

void SM_a (){
  state_prev_a = state_a;
  
  switch (state_a){
    case 0: //RESET
      state_a = 1;
      break;
      
    case 1: //START
      val_a = analogRead(aA);

      if (val_a &gt;= fsr_a_threshhold) {state_a = 2;}
      
      break;
      
     case 2: // SET TIME
      t0_a = millis();
      state_a = 3;
      break;
      
     case 3:// WAIT AND CHECK
      val_a = analogRead(aA);
      t_a = millis();

      if (val_a < fsr_a_threshhold) {state_a = 0;}

      if (t_a - t0_a &gt; debounceDelay){
        state_a = 5;
      }
      break;
      
     case 4: // UNLOADED
      state_a = 0;
      break;
      
     case 5: // LOADED
      val_a = analogRead(aA);
      if (val_a < fsr_a_threshhold) {state_a = 4;}
      break;
  }
}


Stepper Driver (Arduino Uno)

const int enable =  8;
const int xDir =    5;
const int yDir  =   6;
const int zDir =    7;
const int aDir =    13;
const int xStep =   2;
const int yStep =   3;
const int zStep =   4;
const int aStep =   12;


#include <Wire.h&gt;



// An array to keep track of number of steps needed until state completed
// in the order of [a,x,y,z]
int steps[4] = {0,0,0,0};

//Nema 17 and CNC Shield has 3200 steps per rotation
// 800 steps is equal to 90 degrees of rotation
int steps_revolution = 3200;

//Sets the speed of the Stepper Motor
int stepDelay = 800;


void setup() {
  
  Serial.begin(9600);
  pinMode(xDir,OUTPUT);
  pinMode(xStep,OUTPUT);  
  pinMode(yDir,OUTPUT);
  pinMode(yStep,OUTPUT); 
  pinMode(zDir,OUTPUT);
  pinMode(zStep,OUTPUT);
  pinMode(aDir,OUTPUT);
  pinMode(aStep,OUTPUT);  
  pinMode(enable,OUTPUT);
  digitalWrite(enable,LOW);

  Wire.begin(4);                // join i2c bus with address #4

  //Calls the receiveEvent function when data is received from Master Controller
  Wire.onReceive(receiveEvent);
  

}

void loop() {
  
  step_motors();

}

//Steps a given motor one step in the given direction
// True is clockwise, False is counterclockwise
void step (bool dir, int dirPin, int stepperPin) {
  digitalWrite(dirPin,dir);
  digitalWrite(stepperPin, HIGH);
  digitalWrite(stepperPin, LOW);
  delayMicroseconds(stepDelay);

}


// Iterates through the steps array and steps each motor the required number of steps
// This allows for simultaneous movement of stepper motors
void step_motors() {
  
  for (int i = 0; i < 4; i++){
    //Rotate clockwise to loaded state
    if (steps[i]&gt;0){
      //Case for A motor because A pins are not sequential on CNC Shield
      if (i == 0) {
        step (true, aDir,aStep);
        steps[i] -= 1;
        }else{
          step (true, (4+i),(1+i) );
          steps[i] -= 1;
        }
      //Rotate counterclockwise to unloaded state
    } else if (steps[i] < 0){
      if (i == 0) {
        step (false, aDir,aStep);
        steps[i] += 1;
        }else{
          step (false, (4+i),(1+i));
          steps[i] += 1;
        }
      
    }
    
  }
  
}


void receiveEvent(int howMany)
{
  // Reads value sent from Master Controller which reads the FSR's and updates LEDs
  int x = Wire.read();

  int remaining = 0;
  
  //Updates the amount of steps remaining for each Stepper Motor based 
  //on the state of the panel (loaded or unloaded)
  //Panel A : 0 is loaded, 1 is unloaded
  //Panel X : 2 is loaded, 3 is unloaded
  //Panel Y : 4 is loaded, 5 is unloaded
  //Panel Z : 6 is loaded, 7 is unloaded
  
    switch (x) {
      
      case 0:
        remaining = steps[0];
        steps[0] = remaining + 800;
        break;   
      case 1:
        remaining = steps[0];
        steps[0] = (remaining - 800);
        
        break;
      case 2:
        remaining = steps[1];
        steps[1] = remaining + 800;
        break;
      case 3:
        remaining = steps[1];
        steps[1] = (remaining - 800);
        break;
      case 4:
        remaining = steps[2];
        steps[2] = remaining + 800;
        break;
      case 5:
        remaining = steps[2];
        steps[2] = (remaining - 800);
        break;
      case 6:
        remaining = steps[3];
        steps[3] = remaining + 800;
        break;
      case 7:
        remaining = steps[3];
        steps[3] = (remaining - 800);
        break;
    }
}