Final Images
Estee's Double Transducer
Cole's Double Transducer
Detail Photos
IR sensor circuit on breadboard with wires for inputs and outputs. Entire board is mounted sideways in Lego stand.
LED strip is taped to a lego stand approximately 1" off the ground.
Final Project Video
Narrative Description
First, a sensor detects how close an object is. Then, a motor with a straw attachment pushes down onto a sensor that detects force. Lastly, the light color changes depending on the previous inputs.
Progress Images
Force values read from the load cell were far more accurate and offered a wider range of values. However, the code was much more complicated and difficult to control, so we went with the force sensitive resistor.
Shows when LCD display screen worked as tested
Discussion
Both of us enjoyed creating little Lego "homes" for our parts, but figuring out how to use certain components (i.e. Servo to FSR connection) weren't so enjoyable. It was quite frustrating to figure out how to get the servo motor to properly push on the FSR to get a range of force values, as the FSR was extremely sensitive and jumped from 0-100. We somewhat resolved it by limiting the range of the servo.
Another difficulty we faced was getting the right IR proximity sensor. The first one we used required us to be too far from the sensor, and we had trouble getting it to read values properly. Once we changed it to the smaller sensor, this was fixed.
Lastly, the LED strip took a while to work properly, since getting the code to translate the force values to RGB values took a lot of thinking... We learned a lot through this frustrating process nevertheless.
/*
Project 1: Double Transducer
60-223 Intro to Physical Computing
An IR sensor reads in the proximity of an object in front of it, and that's used
to drive a servo motor. The servo's output is physically
coupled to a flexible arm. When the servo rotates the arm will touch down on a
force sensitive resistor. When this resistor is pressed the resistance value is then
used to determine the color of the LED light strip.
A middle button is included as well to skip the middle step,
bypassing the servo motor and force sensitive resistor, and directly changing
the color of the LED light strip based on the linear proximity range.
Pin mapping:
Arduino pin | role | description
-------------------------------------
A0 input IR sensor
A3 input force sensitive resistor
4 input pushbutton
8 output servo motor
6 output LED strip
Code by Cole Franklin, davidfra@andrew.cmu.edu
September 2025
*/
// library for servo motor, LED, and LCD display
#include <Servo.h>
#include <PololuLedStrip.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// pin assignments
const int IRPIN = A0,
FORCEPIN = A3,
BUTTONPIN = 4,
SERVOPIN = 8,
LEDPIN = 6,
NUMLEDS = 7;
// input data variables
int irVal, forceVal, buttonVal;
// counter variable for updating LCD screen
int quarterTimer = 0;
// internal mapping values
int mappedIRVal, mappedForceVal;
// output variables
int servoPos;
rgb_color LEDColor;
// make servo motor variable "servoMotor"
Servo servoMotor;
// create LCD screen variable with sizing
LiquidCrystal_I2C screen(0x27, 16, 2);
// Create an ledStrip object and specify the pin it will use.
PololuLedStrip<LEDPIN> ledStrip;
// Create an array for holding the colors (3 bytes per color).
rgb_color colors[NUMLEDS];
// Each rainbow color is either one LED fully on and others
// off (that's red, green, and blue) or some mixture of them
// at varying strengths.
rgb_color red = rgb_color(255, 0, 0);
rgb_color orange = rgb_color(255, 75, 0);
rgb_color yellow = rgb_color(255, 255, 0);
rgb_color green = rgb_color(0, 255, 0);
rgb_color blue = rgb_color(0, 0, 255);
rgb_color indigo = rgb_color(75, 0, 130);
void setup() {
// initialize servo motor and set position to 90 degrees
servoMotor.attach(SERVOPIN);
servoMotor.write(90);
// set up pin modes
pinMode(IRPIN, INPUT);
pinMode(FORCEPIN, INPUT);
pinMode(BUTTONPIN, INPUT);
pinMode(LEDPIN, OUTPUT);
// start serial connection at 9,600 baud
Serial.begin(9600);
// initialize the screen (only need to do this once)
screen.init();
// turn on the backlight to start
screen.backlight();
// set cursor to home position, i.e. the upper left corner
screen.home();
}
void loop() {
// step 1: read all of the sensors
readInputs();
// step 2: make decisions about what to do
updateInternalState();
// step 3: drive all of the outputs
driveOutputs();
// step 4: report data back to the user
reportBack();
lcdDisplay();
// a brief delay at the bottom of the loop is usually a good idea
delay(5);
}
// function to read inputs from the physical board
void readInputs() {
// read IR sensor
irVal = (float)analogRead(IRPIN);
// read Force sensitive resistor
forceVal = analogRead(FORCEPIN);
// read button
buttonVal = digitalRead(BUTTONPIN);
}
// function to map values to a useable range based on button state
void updateInternalState() {
// map the IR value value to a range of 0 to 99
mappedIRVal = map(irVal, 25, 280, 0, 99);
// make a decision about driving the buzzer output based on the button state
if (buttonVal == LOW) {
// if the button is not pressed, move the servo motor based on IR sensor
// value
// map the force value to a range of 0 to 99
mappedForceVal = map(forceVal, 0, 1000, 0, 99);
// use the mapped ir value to figure out where the servo should go,
servoPos = map(mappedIRVal, 0, 99, 75, 100);
// write the color of the LED strip based on the force value
find_LEDColor(mappedForceVal);
}
else {
// if the button is pressed, then use the mapped IR value to directly
// control the output color on the LED strip
find_LEDColor(mappedIRVal);
}
}
//function to drive outputs based on button state
void driveOutputs() {
//if button is pressed simply drive the LED outputs
if (buttonVal == HIGH) {
drive_LEDs();
}
//if button is not pressed drive the LED outputs and update servo motor
// position
if (buttonVal == LOW) {
servoMotor.write(servoPos);
drive_LEDs();
}
}
// helper function to determine LED color based on mappedVal from (0, 99)
void find_LEDColor(int mappedVal){
if (mappedVal < 17){
LEDColor = red;
}
if ((17 <= mappedVal) && (mappedVal < 34)) {
LEDColor = orange;
}
if ((34 <= mappedVal) && (mappedVal < 51)) {
LEDColor = yellow;
}
if ((51 <= mappedVal) && (mappedVal < 68)){
LEDColor = green;
}
if ((68 <= mappedVal) && (mappedVal < 85)){
LEDColor = blue;
}
if ((85 <= mappedVal) && (mappedVal < 100)){
LEDColor = indigo;
}
}
//function to update color output on LED strip
void drive_LEDs() {
// Run through the "colors[]" array and set every element to LEDColor.
for (int i = 0; i < NUMLEDS; i++){
colors[i] = LEDColor;
}
// Instruct the LED strip to light up according to "colors[]".
ledStrip.write(colors, NUMLEDS);
// Wait a second to see the color before changing.
delay(1000);
}
void reportBack() {
Serial.print("irVal = ");
Serial.print(irVal);
Serial.print(", forceVal = ");
Serial.print(forceVal);
Serial.print(", buttonVal = ");
Serial.println(buttonVal);
Serial.print(", Color = ");
Serial.println(mappedIRVal);
}
// function to update the LCD screen with values
void lcdDisplay(){
// only update screen every 0.25s
if((millis()-quarterTimer) >= 250){
screen.home();
// input value
screen.print("i:");
screen.print(mappedIRVal);
// middle #1
screen.setCursor(6, 0);
screen.print("m:");
screen.print(mappedIRVal);
// middle #2
screen.setCursor(8, 1);
screen.print(mappedForceVal);
// output
screen.setCursor(12, 1);
screen.print("o:");
screen.print(mappedForceVal);
//update quarter timer to global clock timer
quarterTimer = millis();
}
}