Shivani's Final Double Transducer Setup
Brian's Final Double Transducer Setup
Rongrong's Final Double Transducer Setup
Wiring of light color sensor (input)
Wiring of middle transduction step (buzzer to sound sensor)
Wiring of push button
Color box displaying servo motor orientation (output)
Video Documentation of Shivani's Double Transducer
Video Documentation of Brian's Double Transducer
Video Documentation of Rongrong's Double Transducer
A color sensor reads the color of an LED that the user shines onto the device. The detected color then drives a buzzer that emits a series of beeps at different speeds (for example, red light would make the buzzer beep quickly, while blue would make it beep slowly). A sound sensor then counts how quickly the buzzer beeps, and tells a servo motor to orient accordingly. Ideally, the arm of the motor should move to the position on our physical rainbow box matching the color inputted by the LED.
To begin building, we first tested each component individually using solderless breadboards.
Slowly, we began integrating the parts together by combining the scripts, which took a lot of trial and error.
Eventually, all of the components were combined into a functional system, save for the servo motor which continued to output nonsense signals for seemingly no reason.
After a lot of testing and debugging of code, we realized that the servo motor was using too much power and interfering with communication. To address this, we added an external power supply.
To finalize our project, we rearranged the system by soldering parts onto a protoboard.
Final touches included labeling the wires and implementing a color box that was created in Adobe llustrator.
Overall, this project taught us a lot. It didn’t work very well on demo day, but it was still a fruitful experience that taught us about Arduino and wiring principles. Some parts of the project were relatively straightforward, especially during the ideation stage. When the concepts were purely ideas on paper, they felt achievable and almost too simple– we quickly realized though, that even the simplest plans contain unexpected bugs and challenges.
In terms of our strengths, our team was very well-organized. We worked collaboratively and all members were consistently timely and hard-working. Our effective teamwork served as a good foundation for progress because we were able to maximize efficiency and communicate issues. Some of the other executive tasks were also easy to accomplish; for example, laying out the circuit was intuitive. Soldering was also relatively seamless, although there was a learning curve for members who were completely new to the practice. Additionally, getting individual electronic parts to work didn’t present too many challenges, as it was simply a matter of finding online source code and wiring according to internet schematics.
The biggest initial challenge that we ran into was integrating the individual pieces, particularly within the buzzer-to-sound-sensor pipeline. Not only were we dealing with noisy readings from ambient sound, but we also couldn’t test more than one project at a time because the beeps would interfere with one another. (Plus, after a certain number of hours of hearing the buzzer scream, the sounds started to mildly get to us.) Sharing code between three team members also posed a slight challenge purely for logistical reasons. Everyone had different variable naming conventions, and was hard to successfully integrate two existing scripts; additionally, debugging other peoples’ code was difficult when we weren’t present for each step along the way.
We eventually ran into a major problem: the motor would behave erratically despite our seemingly unproblematic code. We spent many hours testing, googling, and debugging, only to some amount of avail. After meeting with the professor, we eventually realized that the issue wasn’t necessarily within the code– it was innate to the servo motor, which was sucking in too much power and interfering with the Arduino. Trying to solve that issue was hard– we tried adding capacitors and external power sources as well as changing the read rate of the servo motor, only to a mediocre level of success. Eventually, we decided that our system was working well enough, as the motor would move correctly most of the time with occasional fluctuations. On demo day, however, two of our motors didn’t behave; furthermore, the LCD started displaying strange values that we hadn’t seen before. It was definitely disappointing to see our projects fall short on game day, but at least we had some moments of success beforehand.
If we were to redo this project, we would meet with the professor earlier in the process (before trying for so long to debug the servo motor). Simply learning about the presence of electronic noise changed our approach largely, and it led us to realize that we had wasted a lot of time searching for code errors that didn’t necessarily exist. We would also change our transducer chain altogether, perhaps opting for a middle step that was more mechanical in nature. The combination of the sound sensor and servo gave us a bit more grief than expected, so if given a do-over, we would experiment with more compatible transducers for a better final performance.
/*
Double Transducer - Light Color to Orientation
Rongrong Wang, Brian Kim, and Shivani Kannan
Includes code snippets from Adafruit tutorials concerning the color sensor
This code takes in readings from a color sensor, sends them to a passive buzzer to make
it beep faster/slower, transmits the beeping speed to a sound sensor, and
uses that speed to determine the orientation of a servo motor.
Pin mapping:
Arduino pin | role | details
-----------------------------
A0 input sound sensor/microphone
4 input button
6 output servo motor
8 output passive buzzer
*/
// import libraries
#include "Adafruit_APDS9960.h"
#include <Adafruit_NeoPixel.h>
#include <Servo.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// define global variables
const int BUZZERPIN = 8;
const int BEEPFREQUENCY = 1000;
const int BEEPDURATION = 25;
const int MICPIN = A0;
const int THRESHOLD = 550;
const int SERVOPIN = 6;
const int BUTTONPIN = 4;
// for beep() and readMic()
unsigned long lastUpdated = 0;
unsigned long oldTime = 0;
unsigned long difference = 0;
unsigned long beepInterval;
// for changeColor()
unsigned long colorTime = 0;
unsigned long colorTimeInterval = 5000;
unsigned long counter = 0;
// for colorRead()
float hue = 0.0;
// for updateLCD()
unsigned long LCDUpdateTime = 0;
const unsigned long LCDInterval = 250;
// initialize and define servo motor
Servo armMotor;
unsigned long motorReadInterval = 500;
unsigned long motorTime = 0;
int motorDiff;
int outputPos;
// initialize color sensor and LCD
Adafruit_APDS9960 apds;
LiquidCrystal_I2C screen(0x27, 16, 2);
void setup() {
Serial.begin(9600);
pinMode(BUZZERPIN, OUTPUT); // define pins
pinMode(MICPIN, INPUT);
pinMode(BUTTONPIN, INPUT);
if (!apds.begin()) { // check for initialization of color sensor
Serial.println("Failed to initialize device! Please check your wiring.");
} else Serial.println("Device initialized!");
apds.enableColor(true); // enable color sensing mode
armMotor.attach(SERVOPIN); // attach motor
armMotor.write(0); // start at 0 degrees
screen.init(); // initialize LCD
screen.backlight(); // turn on backlight
screen.home(); // set cursor
screen.print("Initializing...");
delay(800);
screen.clear();
screen.print("Welcome to Screaming in Color!");
for (int i = 0; i < 15; i++) {
screen.scrollDisplayLeft(); // scroll screen
delay(300);
}
delay(2000); // wait to begin running program
screen.clear();
}
void loop() {
if (digitalRead(BUTTONPIN) == HIGH) { // if button pressed skip middle stepss
colorRead();
moveMotorFromColor();
}
else {
colorRead();
beep();
readMic();
moveMotor();
}
if (millis() - LCDUpdateTime >= LCDInterval) {
updateLCD();
LCDUpdateTime = millis();
}
}
// detect color input
void colorRead() { // source:
uint16_t r, g, b, c;
unsigned long colorStartTime = millis();
if (apds.colorDataReady()) {
apds.getColorData(&r, &g, &b, &c);
r = constrain(map(r, 0, 4000, 0, 255), 0, 255); // get rgb values
g = constrain(map(g, 0, 4000, 0, 255), 0, 255);
b = constrain(map(b, 0, 4000, 0, 255), 0, 255);
hue = rgb_to_hue(r, g, b);
// Serial.println(millis() - colorStartTime);
}
}
// emit series of beeps
void beep() {
beepInterval = map(hue, 0, 360, 200, 1700); // tie beeping speed to hue
if (millis() - lastUpdated >= beepInterval) {
tone(BUZZERPIN, BEEPFREQUENCY, BEEPDURATION); // beep
lastUpdated = millis();
}
}
// detect and determine difference between beeps
void readMic() {
int micVal = analogRead(MICPIN);
if (micVal > THRESHOLD) { // if beep detected
difference = millis() - oldTime;
oldTime = millis();
if (difference > 150) { // account for noise
Serial.print("Difference between beeps:");
Serial.println(difference);
motorDiff = difference;
}
}
}
void moveMotor() {
if (millis() - motorTime >= motorReadInterval) {
outputPos = map(motorDiff, 150, 1750, 0, 180);
outputPos = constrain(outputPos, 0, 180); // ensure motor stays within valid range
armMotor.write(outputPos);
motorTime = millis();
}
}
// control motor directly from color sense
void moveMotorFromColor() {
if (millis() - motorTime >= motorReadInterval) {
outputPos = map(hue, 0, 360, 0, 180);
outputPos = constrain(outputPos, 0, 180); // ensure motor stays within valid range
armMotor.write(outputPos);
motorTime = millis();
}
}
// display numbers on LCD
void updateLCD() {
int i = map(hue, 0, 360, 0, 99);
int m1 = map(beepInterval, 200, 1700, 0, 99);
int m2 = map(motorDiff, 150, 1750, 0, 99);
int o = map(outputPos, 0, 180, 0, 99);
screen.clear();
screen.setCursor(0, 0);
screen.print("i:");
screen.print(i);
screen.setCursor(6, 0);
screen.print("m:");
screen.print(m1);
screen.setCursor(8, 1);
screen.print(m2);
screen.setCursor(12, 1);
screen.print("o:");
screen.print(o);
}
// convert RGB to hue value
float rgb_to_hue(int r, int g, int b) {
// convert RGB values to range [0,1]
float R = r / 255.0;
float G = g / 255.0;
float B = b / 255.0;
// find minimum and maximum values
float min = (R < G) ? ((R < B) ? R : B) : ((G < B) ? G : B);
float max = (R > G) ? ((R > B) ? R : B) : ((G > B) ? G : B);
float delta = max - min;
float hue = 0.0;
// compute hue
if (delta > 0) {
if (max == R) {
hue = (G - B) / delta;
} else if (max == G) {
hue = 2.0 + (B - R) / delta;
} else if (max == B) {
hue = 4.0 + (R - G) / delta;
}
// convert to degrees
hue *= 60;
if (hue < 0) {
hue += 360;
}
}
return hue;
}