Overall view of Andreas' double transducer. Each component is labelled with a breif description.
Close up view of the middle step. A laser pointer shines a light of a specified brightness on a photoresistor.
Close up view of the Arduino and the breadboard's wiring.
Close-up view of the motor driver, potentiometer, and the skip-middle button and the wiring
Close-up view of the arduino and the breadboard wiring of Patrick's project
Andreas documentation video. Features the double transducer interpreting a range of inputs and then showing a demonstration of the skip-middle-step button.
Patrick's documentation video shows the working project with testing the skip-the-middle button.
The user turns knob on the left side, which controls how bright a light glows. Then the machine reads how bright the light is and extends an arm different distances depending how bright it is. The arm has a spring on it that gets squished when the arm goes out, which makes force.
When we first started working on our ideation, we initially considered using sound as the intermediate signal. However, after discussing it with Professor Zeglin and our TA, Ella, we realized that using sound would be more challenging. The issue lies in the fact that sound-related devices that we can find during that class time can only vary by frequency and not by volume, making it difficult to detect changes during testing. Because of this limitation, we decided to use light as the intermediate signal instead.
The most challenging part of our project involves the linear actuator, which is responsible for generating the force output. The issue we ran into is that our power source provides only 5V, while the linear actuator requires 12V to function properly. As a result, during testing, when we applied pressure by placing a finger at the end of the actuator, it would stop moving. This poses a problem for generating the desired force, especially when attaching a spring at the end while using only a 5V power supply. After consulting professor Zach, we resolved this issue by wiring a motor driver as the connector between the arduino/power supply and the linear actuator.
The other parts of this project were not too difficult, and we were able to overcome the minor challenges we faced. One small adjustment that made a significant difference was the decision to use a laser pointer instead of a light bulb or LED. Since our project required detecting changes in light levels using a photoresistor, an LED or light bulb would not have worked as well because their emitted light is not focused. Ambient light would have interfered, reducing accuracy. The laser pointer, with its concentrated beam, effectively solved this problem.
/*
Double Transducer (Rotation to Force)
This code converts a rotational angle from a potentiometer
into the brightness of a light. This brightness is then read
by a photoresistor and converted into linear distance through
a linear actuator. The linear actuator is assumed to have a
spring attached to its tip which is compressed upon extension,
resulting in the generation of force.
Pin mapping:
Arduino pin | role | details
------------------------------
5 output laser
6 output motor (a)
7 output motor (b)
10 input button
A0 input potentiometer
A3 input photoresistor
Released to the public domain by the author, September 2024
Andreas Wieslander, awieslan@andrew.cmu.edu
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <NewPing.h>
const int PHOTOPIN = A3; //assigns the pohotoresistor pin
const int LIGHTPIN = 5; //assigns the laser pin
const int POTENPIN = A0; //assigns the potentiometer pin
const int BUTTONPIN = 10; //assigns the button pin
const int MOTORPINA = 6; //assigns the first motor pin
const int MOTORPINB = 7; //assigns the second motor pin
int motorPos = 0; //initializes the internal tracker for the motor position
LiquidCrystal_I2C screen(0x27, 16, 2); //creates a screen object
const int DELAY = 250; //sets the delay for the LCD screen refresh rate
unsigned long timer = 0; //sets the time for the LCD screen refresh rate
void setup() {
// put your setup code here, to run once:
pinMode(PHOTOPIN, INPUT); //declares the pohotoresistor pin to be an input
pinMode(LIGHTPIN, OUTPUT); //declares the laser pin to be an output
pinMode(POTENPIN, INPUT); //declares the potentiometer pin to be an input
pinMode(BUTTONPIN, INPUT); //declares the button pin to be an input
pinMode(MOTORPINA, OUTPUT); //declares the first motor pin to be an output
pinMode(MOTORPINB, OUTPUT); //declares the second motor pin to be an output
screen.init(); //initialize the screen
screen.backlight(); //turn on the backlight
screen.home(); //set cursor to home position, i.e. the upper left corner
Serial.begin(9600); //this block of code resets the linear actuator to a fully retracted position before the loop begins
Serial.println();
Serial.println("Resetting...");
screen.print("Resetting..."); // print the words Hello, world! onto the screen starting at the above cursor position
digitalWrite(MOTORPINA, LOW);
digitalWrite(MOTORPINB, HIGH);
delay(3000);
digitalWrite(MOTORPINA, LOW);
digitalWrite(MOTORPINB, LOW);
Serial.println("Starting");
screen.setCursor(0, 1); // move cursor to column 0, row 1
screen.print("Starting");
delay(1000);
screen.clear();
}
void loop() {
// put your main code here, to run repeatedly:
//-----------------------------------------------------------------------------------------------
//GATHER DATA FROM THE WORLD (READ INPUTS)
//-----------------------------------------------------------------------------------------------
int buttonVal = digitalRead(BUTTONPIN); //reads if the skip step button is pressed or not
int potenVal = map(analogRead(POTENPIN),0,1023,0,30); //reads the value on the potentiometer and maps it onto a scale of 0 to 30
int photoVal = 30 - map(analogRead(PHOTOPIN),100,400,0,30); //reads the photoresistor and maps the value onto a scale of 0 to 30
if(photoVal < 0) photoVal = 0; //enforces a minimum value for the photoresistor
if(photoVal > 30) photoVal = 30; //enforces a maximum value for the photoresistor
//-----------------------------------------------------------------------------------------------
//COMPUTE AND/OR MAKE DECISIONS
//-----------------------------------------------------------------------------------------------
int lightVal = map(potenVal, 0, 30, 0, 255); //maps the value of the potentiometer onto the PWM scale for use with the laser pointer
if(buttonVal == 1){ //if button pressed
analogWrite(LIGHTPIN, LOW); //turn off laser
if(motorPos == potenVal){ //if moter is in correct position relative to potentiometer
stopMotor(); //stop motor
} else if (motorPos < potenVal){ //else if motor is not extended enough relative to potentiometer
extendMotor(); //extend motor
} else retractMotor(); //else retract motor
} else { //if button is not pressed
analogWrite(LIGHTPIN, lightVal); //writes mapped potentiometer value to the laser
if(motorPos == photoVal){ //if motor is in correct position relative to photoresistor
stopMotor(); //stop motor
} else if (motorPos < photoVal){ //else if motor is not extended enough relative to photoresistor
extendMotor(); //extend motor
} else retractMotor(); //else retract motor
}
bool display = false; //creates boolean to determine if screen will be refreshed
if(millis() >= timer){ //if current time is equal to or after target time
display = true; //set display to true
timer = millis() + DELAY; //set new target time
}
//-----------------------------------------------------------------------------------------------
//ACTUATE ACTUATORS (DRIVE OUTPUTS)
//-----------------------------------------------------------------------------------------------
if(display){ //if the screen should be refreshed
screen.clear();
screen.setCursor(0,0); //print the initial input (potentiometer) value
screen.print("i:");
screen.print(map(potenVal,0,30,0,99));
screen.setCursor(6,0); //print the middle step actuator (laser) value
screen.print("m:");
screen.print(map(lightVal,0,255,0,99));
screen.setCursor(8,1); //print the middle step sensor (photoresistor) value
screen.print(map(photoVal,0,30,0,99));
screen.setCursor(11,1); //print the final output (motor) value
screen.print("o:");
screen.print(map(motorPos,0,30,0,99));
//screen.setCursor
}
//-----------------------------------------------------------------------------------------------
//REPORT STATUS BACK TO THE USER (VIA SERIAL MONITOR AND/OR OTHER MEANS)
//-----------------------------------------------------------------------------------------------
//Serial.println(buttonVal); //print various values to the serial monitor for debugging
//Serial.println(potenVal);
//Serial.println(lightVal);
//Serial.println(photoVal);
Serial.println(motorPos);
delay(100); //delay the entire loop for the linear actuator to function properly
}
void extendMotor(){ //extends the motor and keeps track of its position on a scale of 0 to 30
if(motorPos <30){
digitalWrite(MOTORPINA, HIGH);
digitalWrite(MOTORPINB, LOW);
motorPos++;
}
else stopMotor();
}
void retractMotor(){ //retracts the motor and keeps track of its position on a scale of 0 to 30
if(motorPos >0){
digitalWrite(MOTORPINA, LOW);
digitalWrite(MOTORPINB, HIGH);
motorPos--;
}
else stopMotor();
}
void stopMotor(){ //stops the motor
digitalWrite(MOTORPINA, LOW);
digitalWrite(MOTORPINB, LOW);
}