Top View of Peiran’s double transducer. Water level (bottom left), rotational speed (top right)
Top View of Quinten’s double transducer – Water Level (left), rotational speed (right)
Detail on the structure that separates each photoresistor
Combinations of different LED representations.
(Quinten) Image of breadboard with L293D Motor Driver and cables attaching to Arduino and DC Motor
Final Video:
Peiran
The ultrasonic ranger detects the water level, or distance to the object, which changes how 4 LEDs light up. Photoresistors then sense these light changes, and the Arduino Uno alters the motor’s rotational speed based on what the photoresistors read.
Quinten
The ultrasonic ranger works to detect distance and would probably read water levels somewhat well. The ultrasonic ranger is able to drive the 4 LEDs light output. At this point the photoresistors should pick up the light output from the LEDs and drive the DC motor’s speed. The system works up until the photoresistors and the DC motor.
Process:
Experimenting with how to best exclude outside lighting for the photo-resistors.
TinkerCad Model of Double Transducer- Water Level to Rotational Speed
Process Image of Quinten’s double transducer. Laying out physical components to match TinkerCad diagram
Discussion:
Since our task was to only detect water levels, not manipulate them, the first part of this project was not as challenging. Both of us decided to just place the ultrasonic ranger on the board without worrying about actual water levels. We were also able to visually display the LEDs in a way that’s easy to understand and distinguish between various levels.
However, there was a lot of debugging, especially with code, and both of our projects didn’t end up successfully performing the target task. It is a bit disappointing, but overall this has been an extremely eye-opening experience. Getting the middle stage of the double transducer was more difficult than I had expected. Even with the LEDs scaling in brightness to the ultrasonic ranger’s distance readings, it was hard to get the photoresistors to read and control the motor speed the way we intended. Utilizing online documentation of past projects was a huge help in troubleshooting and figuring out our code and setup.
Diagram and Schematic:
Block Diagram
(Quinten) Block Diagram
Code:
#include <Servo.h> #include <NewPing.h> const int trigger = 5; //ultrasonic ranger const int echo = 4; const int maxDist = 30; //in centimeters const int ServoPin = 11; //Motor const int Red = 13; //LED pins by color const int Yellow = 12; const int Blue = 7; const int Green = 8; const int Redresis = A0; //Photoresistors by color of LED const int Yellowresis = A1; const int Greenresis = A2; const int Blueresis = A3; //Global Variables fore pin numbers. //Referencing to code structure guide from class website Servo Motor; NewPing sonar(trigger, echo, maxDist); //Referencing tutorial pages from class website int Dist, Display, Brightness, Speed; //Variables for input/storing information void setup() { Motor.attach(ServoPin); pinMode(Redresis, INPUT); pinMode(Yellowresis, INPUT); pinMode(Greenresis, INPUT); pinMode(Blueresis, INPUT); pinMode(Greenresis, INPUT); pinMode(Red, OUTPUT); pinMode(Yellow, OUTPUT); pinMode(Green, OUTPUT); pinMode(Blue, OUTPUT); pinMode(Speed,OUTPUT); pinMode(ServoPin,OUTPUT); } void loop() { //detect water level delay(50); Dist = sonar.ping_cm(); DisplayLED (); //Photoresistor Brightness = 0; int Redval; Redval = analogRead(Redresis); if (Redval >= 300) { Brightness += 1; } //referencing class website for photoresistor int Yellowval; Yellowval = analogRead(Yellowresis); if (Yellowval >= 300) { Brightness += 2; } int Greenval; Greenval = analogRead(Greenresis); if (Greenval >= 300) { Brightness += 4; } int Blueval; Blueval = analogRead(Blueresis); if (Blueval >= 300) { Brightness += 8; } //motor speed Speed = 200/Brightness; Motor.write(0); delay(Speed); Motor.write(180); delay(Speed); } void DisplayLED() { Display = Dist/2; if (Display%2 == 1){ digitalWrite(Red, HIGH); } if (Display%4 == 2 || Display%4 == 3){ digitalWrite(Yellow, HIGH); } if ((Display-3)%8 <= 4){ digitalWrite(Green, HIGH); } if (Display >= 8){ digitalWrite(Blue, HIGH); } }
]]>
Caleb’s Double Transducer
Closeup of the arm attached to Caleb’s servo motor.
Closeup of Caleb’s soldering for the middle and end stage of the transducer.
Caleb’s Process 1: Early stage process where the pieces are still in stages.
Caleb’s Process 2: Image taken shortly after deciding to solder a portion of the transducer together, causing mass destruction to the original transducer.
This is the overall setup of the double transducer.
The arm of the servo motor is long enough to allow a bigger difference in arm position changes. This allows the ultrasonic ranger to record a bigger difference in distance.
The arm of the servo motor is long enough to allow a bigger difference in arm position changes. This allows the ultrasonic ranger to record a bigger difference in distance.
The 9V battery and lightbulb are connected to a transistor that allows the arduino to communicate with the lightbulb while also supplying enough power to power the lightbulb.
The 9V battery and lightbulb are connected to a transistor that allows the arduino to communicate with the lightbulb while also supplying enough power to power the lightbulb.
Progress photo showing testing of the servo motor.
Our double transducer turns rotational speed to physical heat. When something turns the knob at the left side of the board, a little motor makes a piece of yellow paper move closer to a distance reader. The distance reader makes a lightbulb heat up as the yellow paper gets closer. An LCD display shows the distance and “heat” values.
The beginning stages of the project actually went smoothly, when we were just wiring up individual parts and configuring them. What a lot of the trouble came from was with configuring parts to each other. The ultrasonic ranger for one was extremely finicky and would refuse to work at certain points, and the Servo arm had to be placed at a certain angle and calibrated specifically.
When everything began to work together, things came down to just manual calibration, which introduced a different issue. Since we shared our code, the code was only calibrated to Caleb’s transducer, and caused issues when copied over to Lily’s. This might just call for more robust code that doesn’t have to be calibrated manually, or possible automatic calibration that we didn’t have the scope to figure out.
Something we learned was the importance of compartmentalization in the process of making a machine. Starting off, the task was extremely overwhelming. But by breaking apart the transducer into four portions that we could work on one-by-one, it was much more manageable. The different segments made it easier to conduct research, diagram, and ultimately create.
//Code first records RPM of the rotary encoder and maps value to angle of servo motor //Second, the ultrasonic ranger detects its distance from the servo motor, and the distance is mapped to the intensity of light //Finally, the light turns on //Map values are tuned to Caleb's transducer //Pins: //Rotary encoder: //DT -> 2 //CLT -> 3 //Ultrasonic Ranger: //trig -> 10 //echo -> 13 //Lightbulb: //Light -> 6 //Servo Motor -> 4 //Ultrasonic ranger code from: https://www.circuitbasics.com/how-to-set-up-an-ultrasonic-range-finder-on-an-arduino/ // Timing Code from: https://courses.ideate.cmu.edu/60-223/s2023/tutorials/code-bites#blink-without-blocking #define ENC_COUNT_REV 620 #include <Servo.h> #include <Wire.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #define trigPin 10 #define echoPin 13 #define lightPin 6 extern uint16_t twi_readFrom_timeout; extern uint16_t twi_writeTo_timeout; extern bool twi_readFrom_wait; extern bool twi_writeTo_wait; Servo doorMotor; LiquidCrystal_I2C screen(0x27, 16, 2); volatile long encoder_value = 0; int interval = 1000; int x = 123; int motorPos = 0; long previousMillis = 0; long currentMillis = 0; int lightVal = 0; int rpm = 0; int o = 0; int m2 = 0; int m = 0; int Servo_pin = 4; int DT_pin = 2; int CLK_pin = 3; void setup() { pinMode(DT_pin , INPUT_PULLUP); doorMotor.attach(Servo_pin); pinMode(CLK_pin , INPUT); Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(lightPin,OUTPUT); attachInterrupt(digitalPinToInterrupt(DT_pin), interrupt_on_pulse, RISING); screen.init(); screen.backlight(); screen.clear(); screen.setCursor(12,1); screen.print("o:"); screen.setCursor(6,0); screen.print("m:"); screen.home(); screen.print("i:"); } void loop() { int motorPos = 0; // Record the time currentMillis = millis(); if (currentMillis - previousMillis > interval) { previousMillis = currentMillis; // Calculate revolutions per minute rpm = (int)(encoder_value * 60 / ENC_COUNT_REV); int i = map (abs(rpm),0,20,0,99); if (i >99) { i = 99; } screen.setCursor(2,0); screen.print(abs(i)); int motorPos = map (abs(rpm), 0, 20, 35, 80); // Safeguard against overload from Rotary Encoder, if motorPos is mapped to a value higher than 80 it defaults to 80 if (motorPos > 80) { doorMotor.write(80); } // Regular write, causes motor to move to mapped angle else { doorMotor.write(motorPos); } delay(25); m = map (motorPos, 35,80,0,99); if (m > 99) { m = 99; } screen.setCursor(8, 0); screen.print(m); encoder_value = 0; } int duration, distance; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = (duration / 2) * 0.0344; lightVal = map (distance,4.5,12,255,0); int o = map(lightVal,0,255,0,99); if (distance > 15) { lightVal = 500; o = 99; } screen.setCursor(14,1); screen.print(abs(o)); analogWrite(lightPin,abs(lightVal)); } void interrupt_on_pulse() { int val = digitalRead(CLK_pin); if(val == LOW) { encoder_value++; } else { encoder_value--; } }
]]>
Top view of Stanley’s Double Transducer. Thermistor (left), fan (right)
Top view of Michelle’s Double Transducer. Fan (left), thermistor (right).
Top view of Angie’s Double Transducer. Thermistor(Middle-left, labeled as “INPUT”), Fan(Top-right, labeled as “OUTPUT”)
Adafruit color sensor secured directly above color wheel, which is spun by a stepper motor.
Another angle of the Adafruit color sensor secured above the color wheel and overview of some of the wiring we did to connect the steps.
Close up of the fan and LED setup.
Final Working Model:
Stanley
Michelle
Narrative Description:
The thermistor picks up on the temperature being emitted, and based on how high or low the temperature is, the Arduino Uno will determine how much to rotate a stepper motor. A color wheel is attached to the stepper motor, and an RGB sensor will detect the hue rotated in front of it. This hue then determines the wind speed.
Process:
Testing the Adafruit sensor and stepper motor to make sure these middle step components work reliably. – Stanley
Getting the color sensor to pick up hue from the color wheel. As the color wheel spins, the hue value is recorded and is represented by the graph on screen. – Stanley
Getting the fan to respond to hue. A higher hue value (blues, purples) corresponds to higher fan speed, and lower hue values (red) corresponds to lower fan speed. – Michelle
Putting it all together (stepper motor, color wheel, fan) – Michelle
Discussion:
From the beginning of the project, we didn’t expect that our direction of going from a temperature input, to color, and fan speed would be technically challenging to execute. In particular, there were a few external things we had to learn, from learning how to use a MOSFET to control fan speed, to using a driver for the stepper motor, etc. We worked through this by taking the time to understand each component individually first – their capabilities, how the hardware itself works, how to speak to it through code, etc. and that helped us get a holistic idea of how we can get each component to communicate with each other.
Needless to say, there was a lot of debugging in our process. With so many new components and systems, it was difficult to pinpoint exactly which part was going wrong – whether it was the software, weak connections, or just a single wire plugged incorrectly. Oftentimes, we would mess with the code and wiring a lot just to find that the breadboard was faulty. Towards the end, we learned to use software workarounds for hardware problems. For example, with the RGB sensor, it was difficult to figure out a way to physically set the stepper motor such that the low temperature corresponds to the red part (low hue value) of the color wheel. Instead, we wrote a function to rotate and calibrate the color wheel, so that a lower temperature always corresponds to red, and a higher temperature always corresponds to purple.
In the end, we are glad that we followed through with the idea, using these different dimensions (temperature, hue, fan speed) in a novel yet cohesive way, and it was definitely a very productive experience in learning how to work with hardware and software simultaneously and creatively.
Block Diagram & Schematic:
Code:
/* * TEMPERATURE > COLOR > FAN SPEED DOUBLE TRANSDUCER * Michelle Liu, Stanley Ip, Angie Wang * * The thermistor picks up on the temperature being emitted, and based on how high or * low the temperature is, the Arduino Uno will determine how much to rotate a stepper motor. * A color wheel is attached to the stepper motor, and an RGB sensor will detect the hue * rotated in front of it. This hue then determines the fan speed. * * Pin Map: variable name | mode | pin | description ----------------------------------------------- FAN_PIN | output | 10 | controls fan speed STEP_PIN | output | 2 | stepper motor: sets stepper position DIR_PIN | output | 3 | stepper motor: sets stepper direction THERMO_PIN | output | A0 | thermistor: reads in resistance from the thermistor * * Credits: * RGB sensor code referenced from the Adafruit TCS34725 breakout library example * code: https://learn.adafruit.com/adafruit-color-sensors/arduino-code * * Algorithm for converting RGB values to hue values referenced from * https://www.geeksforgeeks.org/program-change-rgb-color-model-hsv-color-model/ * * Stepper motor code referenced from the course website: * https://courses.ideate.cmu.edu/60-223/s2023/tutorials/stepper * * LCD display code referenced from the course website: * https://courses.ideate.cmu.edu/60-223/s2023/tutorials/I2C-lcd */ #include <Wire.h> #include "Adafruit_TCS34725.h" // ADAFruit TCS34725 driver for ADAFruit TCS34725 RGB Color Sensor by Adafruit #include <AccelStepper.h> // AccelStepper stepper motor driver by Mike McCauley #include <LiquidCrystal_I2C.h> // LiquidCrystal I2C LCD display driver by Frank de Brabander LiquidCrystal_I2C screen(0x27, 16, 2); Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_614MS, TCS34725_GAIN_1X); // WIRING const int FAN_PIN = 3; const int STEP_PIN = 8; const int DIR_PIN = 9; const int THERMO_PIN = A0; const int FAN_START_SPEED = 0; AccelStepper myMotor(1, STEP_PIN, DIR_PIN); int pos = 800; // variable to store motor position instruction int fanSpeed = 0; int dispDelay = 0; void setup(void) { screen.init(); Serial.begin(9600); // LCD DISPLAY SETUP screen.backlight(); screen.home(); // RGB SENSOR SETUP if (tcs.begin()) { Serial.println("Found sensor"); } else { Serial.println("No TCS34725 found ... check your connections"); while (1); } // STEPPER MOTOR SETUP myMotor.setMaxSpeed(1000); // measured in steps per second myMotor.setAcceleration(500); // measured in steps per second squared initializeMotorPos(); pinMode(FAN_PIN, OUTPUT); pinMode(THERMO_PIN, INPUT); analogWrite(FAN_PIN, FAN_START_SPEED); } // HUE ALGORITHM // Algorithm from https://www.geeksforgeeks.org/program-change-rgb-color-model-hsv-color-model/ // Range: 0 (red) - 360 (violet) double calculateHue(uint16_t r, uint16_t g, uint16_t b) { double rDepth = r / 65535.0; double gDepth = g / 65535.0; double bDepth = b / 65535.0; double rgbMax = max(max(gDepth, bDepth), rDepth); // maximum of r, g, b double rgbMin = min(min(gDepth, bDepth), rDepth); // minimum of r, g, b double rgbDiff = rgbMax - rgbMin; // diff of cmax and cmin. if (rgbDiff == 0) { return 0.0; } else if (rgbMax == rDepth) { return fmod(60.0 * ((gDepth - bDepth) / rgbDiff) + 360.0, 360); } else if (rgbMax == gDepth) { return fmod(60 * ((bDepth - rDepth) / rgbDiff) + 120, 360); } else { return fmod(60 * ((rDepth - gDepth) / rgbDiff) + 240, 360); } } // Reads RGB sensor data without introducing a delay like the built-in function does void getRawData_noDelay(uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c) { *c = tcs.read16(TCS34725_CDATAL); *r = tcs.read16(TCS34725_RDATAL); *g = tcs.read16(TCS34725_GDATAL); *b = tcs.read16(TCS34725_BDATAL); } // initialize motor pos to approximately red void initializeMotorPos() { uint16_t r, g, b, c, colorTemp, lux; getRawData_noDelay(&r, &g, &b, &c); int hue = (int)calculateHue(r, g, b); // rotate the stepper motor by 1 degree until the hue reaches a value close to red while (hue > 15) { getRawData_noDelay(&r, &g, &b, &c); hue = (int)calculateHue(r, g, b); myMotor.move(1); myMotor.run(); } Serial.print("RED FOUND: "); Serial.println(myMotor.currentPosition()); myMotor.setCurrentPosition(0); } // debugging helper function for printing values with their labels void p(String label, int val) { Serial.print(label + ": "); Serial.print(val, DEC); Serial.print(" "); Serial.println(" "); } void displayVal(String c, int i) { screen.print(c); screen.print(":"); screen.print(i < 10 ? "0" : ""); screen.print(i < 10 ? "0" : ""); screen.print(i); } /* i = input, iMax = input upper range m = middle-step actuator value, mMax = middle-step actuator value upper range s = middle-step sensor value, mMax = middle-step sensor value upper range o = output, oMax = middle-step actuator value upper range */ // helper function for displaying values on the LCD screen void displayLCD(int i, int iMax, int m, int mMax, int s, int sMax, int o, int oMax) { int iNorm = map(i, 0, iMax, 0, 99); int mNorm = map(m, 0, mMax, 0, 99); int sNorm = map(s, 0, sMax, 0, 99); int oNorm = map(o, 0, oMax, 0, 99); displayVal("i", iNorm); screen.print(" "); displayVal(" m", mNorm); screen.setCursor(1, 6); displayVal("s", sNorm); screen.print(" "); displayVal(" o", oNorm); screen.print(" "); if (dispDelay > 100) { dispDelay = 0; screen.clear(); } } void loop() { int thermoVal = analogRead(THERMO_PIN); pos = map(thermoVal, 450, 600, 0, 200); myMotor.moveTo(-pos); // and tell the motor to go there myMotor.run(); // call this function as often as possible uint16_t r, g, b, c, colorTemp, lux; getRawData_noDelay(&r, &g, &b, &c); int hue = (int)calculateHue(r, g, b); fanSpeed = map(hue, 0, 360, FAN_START_SPEED, 255); analogWrite(FAN_PIN, fanSpeed); displayLCD(thermoVal, 1023, pos, 400, hue, 360, fanSpeed, 255); dispDelay += 1; }
]]>
Juhi’s Double Transducer
Andres’s Double Transducer
The Adafruit TCS34725 RGB color sensor is propped up by an inch to align with the output of the other team and receive the input.
Close up of the wiring we did to connect the Arduino to different sub-systems of the double transducer project.
The linear actuator is responsible for extending and retracting the rubber band (i.e. the lever mechanism) dependent on the color read by the RGB sensor.
Close up of the middle step. The force sensor is supported by a wall and is being pushed by the lever pivoted on a stand, which is moved by the linear actuator.
The output i.e. the speaker that varied the sound pitch (from 200Hz to 2000 Hz) according to the amount of force received by the force sensor. (In the color spectrum red being the lowest to purple being the highest)
The machine sees the color emitted by LED, then uses the color value to expand or compress a motor which moves a lever mechanism. The force exerted by the lever mechanism is detected by a senor and is used to change the pitch of a speaker.
At first, we tested out all our inputs and output individually to see whether they were working or not (Juhi)
Once we had all our elements, we were experimenting with the linear actuator and its position in terms of the range we set in relation to the color spectrum (Juhi)
To connect our parts in relation to the other we had to solder a few things (Andres)
To create our middle step we were experimenting with different types of lever mechanisms that would suit our transducer (Andres)
Our double transducer project, while educational, was filled with unexpected challenges. We faced many throughout the project, but the biggest were converting the RGB input signal form into a usable scale, controlling the linear actuator, and coding in general. To start with the RGB adafruit color sensor, depending on the color it ‘sees’ it outputs a RGB value to the arduino; however, we needed to convert this to a linear scale to control the linear actuator. We did this by transforming from the RGB domain to the ‘HSL’ domain where H or hue can be used to put colors on a linear scale from 0 to 360. But, not knowing anything about colors before this project, we had to do research online, speak to the professor and the TA to not only understand how to do this but understand that this was even an option. Moving on to controlling the linear actuator, as it doesn’t have an integrated control, or position controller, we had to figure out how to not only determine position but control that position using time passed and an experimentally determined rate of speed. As neither of us knew a lot about code, we had to once again conduct outside research and learn what and how to use an open loop controller which was extremely helpful in controlling both the actuator, and updates on the I2C screen. Finally, we made bug fixing our code much more challenging than it had to be. This is because we began putting things together before we had debugged each individual piece. Meaning errors propagated throughout the system that could have been fixed piecemeal if we had done more rigorous testing early on. For example, we connected the color sensor to the actuator early on and when the actuator didn’t output the correct distance, I was sure that it was because I mapped the hue incorrectly when actually it was because I was incorrectly controlling the non-feedback actuator.
Being open to change and adapting original ideas was also, in my opinion, one of the most significant and positive parts of our creation process. For instance, when we started, I was sure that a spring was the only way to go in regards to attaching the actuator to the FSR sensor, but through testing we realized a spring added unneeded complexity that just using a weak rubber band would fix. Another thing we had to be open to changing was how to transmit the force to the FSR sensor. Because it only senses compression, at first we weren’t sure of the best way to switch the tension force we were getting from the actuator to compression. We solved this issue by meeting, talking, and listing out ideas before deciding that a simple lever as seen above was the best solution. Finally on things we would have changed if there was more time, two come to mind. The first is changing from a 4 inch extending actuator to a 2 inch as the extra distance only added error to our conversion that a smaller range would have solved. The second is further research into how the Adafruit color sensor works. Because much of the code was based on examples provided by the downloaded library, we were using features and commands that we weren’t 100% certain of their purpose. Because of this, things that we could have changed or adjusted for more ease of use were taken as set in stone. For instance, the sensor has an optional white LED to make colors clearer to the sensor, but since our input was from a LED itself, for us this was unnecessary. However, we weren’t aware it could be turned off and the glare it caused, created problems we had to work around that had an extremely easy fix.
As a group some of the biggest lessons we learned from this project was to avoid unnecessary stress and take things one step at a time. I think we were worried about succeeding 100% in everything we set out to do, which is good, but not to the point of frustration. Every part had value and even if we didn’t succeed completely the learning is more important than success. The other thing we hope to take away from this into project two is while these electronics combine into one system, they are composed of many different individual components. If there is a problem it is most likely in one of these subsystems and the best practice is to start at the beginning and move up when testing.
/* Double transducer: From light color to sound pitch {Andres Montemayor, Juhi Kedia} The code below processes and translates an input RGB reading from LED lights to a speaker pitch (200Hz to 2000Hz). The middle step for the double transducer is a liner actuator that extends and contracts according to the RGB color sensor reading and pushes along a force sensing resistor according to the color spectrum (red being the lowest and violet being the hightest). Pin Map: pin | mode | description FSR_pin | input | LED MOTORCONT1 | output | potentiometer: sets blink rate MOTORCONT2 | output | pushbutton: affect blink rate SPEAKER | output | tone from 200 Hz to 2000 Hz based on FSR reading SQL | OUTPUT/INPUT | SCREEN and ADAFRUIT go in here SDA | OUTPUT/INPUT | SCREEN and ADAFRUIT go in here RGB sensor code sourced from adafruit Color control Used example code from adafuit library 'color view' and code linked below to write this: https://www.geeksforgeeks.org/program-change-rgb-color-model-hsv-color-model/ Conversion from RGB to HSV values makes use of an algorithm sourced from: https://www.rapidtables.com/convert/color/rgb-to-hsv.html */ /* have Working: adafruit color sensor, force sensor, speaker in progress: done test on board */ #include <Wire.h> #include "Adafruit_TCS34725.h" #include <LiquidCrystal_I2C.h> // set to false if using a common cathode LED #define commonAnode true // our RGB -> eye-recognized gamma color byte gammatable[256]; Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X); LiquidCrystal_I2C screen(0x27, 16, 2); // rename pins const int FSR_pin = A0; const int MOTORCONT1 = 10; const int MOTORCONT2 = 11; const int SPEAKER = 2; // Set variables for use throughout code float posPast; double travelTime; float stopTime; int swtch = 0; float hPast = 0; int swtch2 = 1; unsigned int timer = 250; void setup() { pinMode(FSR_pin, INPUT); pinMode(MOTORCONT1, OUTPUT); pinMode(MOTORCONT2, OUTPUT); pinMode(SPEAKER, OUTPUT); pinMode(A3, INPUT); screen.init(); screen.backlight(); Serial.begin(9600); // Begin color sensing sensor if (tcs.begin()) { Serial.println("Found sensor"); } else { Serial.println("No sensor"); } extend(); delay(3000); stop(); } // extends the linear actuator void extend() { digitalWrite(MOTORCONT2, LOW); digitalWrite(MOTORCONT1, HIGH); } // returns the linear actuator void retract() { digitalWrite(MOTORCONT2, HIGH); digitalWrite(MOTORCONT1, LOW); } //stops the linear actuator void stop() { digitalWrite(MOTORCONT1, LOW); digitalWrite(MOTORCONT2, LOW); } void loop() { /* //read a pot val was used for testing int potVal = 0; //do the read and store the value into potVal potVal = analogRead(A3); //send potVal's value back to computer */ //FSR control int fsrReading = analogRead(FSR_pin); //Serial.println(fsrReading); // this done input of fsrReading tells force on sensor 0 -> 1023... might need to adjust if too much force being put on // can just change spring as well uint16_t r, g, b, c; double h = 0; // Gather various color data from RGB sensor tcs.getRawData(&r, &g, &b, &c); // RGB to HSV float rNew = r / 65535.0; float gNew = g / 65535.0; float bNew = b / 65535.0; // RGB to HSV algorithm float cmax = max(rNew, max(gNew, bNew)); // maximum of r, g, b float cmin = min(rNew, min(gNew, bNew)); // minimum of r, g, b float diff = cmax - cmin; // diff of cmax and cmin. // if cmax and cmax are equal then h = 0 if (cmax == cmin) { h = 0; } // if cmax equal r then compute h else if (cmax == rNew) { h = fmod(60 * ((gNew - bNew) / diff) + 360, 360); } // if cmax equal g then compute h else if (cmax == gNew) { h = fmod(60 * ((bNew - rNew) / diff) + 120, 360); } // if cmax equal b then compute h else if (cmax == bNew) { h = fmod(60 * ((rNew - gNew) / diff) + 240, 360); } Serial.print("Hue: "); Serial.println(h); //end color control h: 0 -> 360 red -> purple //motor control int travelTime = map(h, 0, 360, 0, 3000); int posNew = map(h, 0, 360, 0, 100); //int posNew = potValMap; if (abs(posNew - posPast) > 5) { if (swtch == 0) { float travelProportion = abs(posNew - posPast) / 100; stopTime = millis() + (travelTime * travelProportion); swtch = 1; } //Serial.println("Need to move"); if (stopTime < millis()) { stop(); posPast = posNew; swtch = 0; } if (posNew < posPast) { retract(); Serial.println("retract"); } if (posNew > posPast) { extend(); Serial.println("extend"); } } /* Serial.print("PosNew: "); Serial.println(posNew); Serial.print("posPast: "); Serial.println(posPast); */ //speaker control int pitch = map(fsrReading, 0, 1023, 200, 2000); //dropped from 1023 to 900 because sensor wasn't reaching max //Serial.println("pitch: "); //Serial.println(pitch); tone(SPEAKER, pitch, 0); //(pin, frequency, delay between tones: should be 0) //lcd screen //only want to update every quarter of a second so made swtch2 to do that if (swtch2 == 1) { Serial.println("screen update"); screen.clear(); screen.home(); screen.print("i:"); int hPrint = map(h, 0, 360, 0, 99); screen.print(hPrint); screen.setCursor(6, 0); screen.print("m:"); screen.print(posNew); screen.setCursor(8, 1); int fsrPrint = map(fsrReading, 0, 1023, 0, 99); screen.print(fsrPrint); screen.setCursor(12, 1); screen.print("o:"); int pitchPrint = map(pitch, 200, 2000, 0, 99); screen.print(pitchPrint); swtch2 = 0; } if (millis() >= timer) { swtch2 = 1; timer += 200; } }
]]>
This is the final setup of Evie’s Sunshine machine
Finalized setup of Dongtao’s sunshine machine (without Casey)
The “Sunshine Lovers” is a machine that transduces light input into a linear position through an intermediate step.
here you can see the thermistor taped to the incandescent bulb to read heat (evie).
Here you can see the photoresistor at the end of the board which is our main input. (evie)
This is Job the turtle who’s head it attached to a linear actuator.
Working Demo 1 – The Turtle Job
Working Demo 2 – The Casey Bro
Narrative description
Our double Transducer takes in light and transforms it to a linear position. The photoresistor reads light and the value of light is transformed to a value of heat created by an incandescent bulb. A thermistor reads the heat on the incandescent bulb and then sends the value of heat to a linear actuator. In Evie’s the linear actuator works as a turtle, named Job, who wakes up to the heat of the sun and then begins to swim downstream. In Dongtao’s machine, Casey brings a happy face that moves to indicate joy. The linear actuator should resemble the amount of light from the beginning of the chain.
This is our first attempt at coding the LCD screen you can see how the data is coming out as a string with two spaces between. (evie)
We tested a temperature detector to see if it was more or less accurate to a thermistor, we found that it was much less (evie)
Our specialized LCD screen has a back panel that effectively simplifies the connections needed to operate it with the Arduino. However, it is critical to clarify the differences between the 4pins and double-check their connections; sometimes it is hard to notice a connection mistake when it is behind the panel.
Sometimes it is helpful to have a larger-scale breadboard for managing complex cable connections. However, it needs to be noted that some of the larger breadboards are not connected across the centerline.
//Evie and Dongtoa //light value to linear position //intor to physical computing Winter 2023 #include <LiquidCrystal_I2C_Hangul.h> #include <Wire.h> LiquidCrystal_I2C_Hangul lcd(0x3F, 16, 2); int photoVal; int temP; int heaT; int linearP; int linearOUT; int previouseOUT; const int PHOTO = A3; const int THERM = A2; const int HOTB = 6; const int MOT_0 = 10; const int MOT_1 = 11; void setup() { Serial.begin(9600); //inputs and outputs pinMode(PHOTO, INPUT); pinMode(THERM, INPUT); pinMode(HOTB, OUTPUT); pinMode(MOT_0, OUTPUT); pinMode(MOT_1, OUTPUT); // moters are off digitalWrite(MOT_0, HIGH); digitalWrite(MOT_1, HIGH); //LCD screen setup lcd.begin(16, 2); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); //goto first column (column 0) and first line (Line 0) lcd.print("Light "); //Print at cursor Location lcd.setCursor(0, 1); //goto first column (column 0) and second line (line 1) lcd.print("Temp "); //Print at cursor location lcd.setCursor(10, 0); lcd.print("Pos"); //source for LCD screen :https://www.instructables.com/Display-temperature-on-LCD/ } void loop() { //Input values: photoVal = map(analogRead(PHOTO), 0, 1023, 0, 100); temP = map(analogRead(THERM), 0, 1023, 0, 100); //Out put values: linearOUT = (map(temP, 50, 100, 0, 3000)); heaT = map(photoVal, 30, 100, 0, 200); //to show on the screen linearP = map(linearOUT, 0, 3000, 0, 100); /////////////////set the screen: lcd.setCursor(6, 0); //move the cursor to position 6 on row 1 lcd.print(photoVal); delay(300); lcd.setCursor(6, 1); lcd.print(temP); delay(300); lcd.setCursor(14, 0); lcd.print(linearP); delay(300); ///////////////// heat bulb: if (photoVal > 30) { analogWrite(HOTB, heaT); // 30 is about anbient light, this way the lightbulb wont be on // unless there is a light next to it, giving it time to cool down } if (photoVal < 30) { analogWrite(HOTB, 0); } //////////////////// linear actuator based on time //takes 3000milsecs to reach the the end. 0 is 0% heat and 3000 is 100% heat //if the temperature rises, the linear position increases if (linearOUT > previouseOUT && temP > 50) { digitalWrite(MOT_0, LOW); digitalWrite(MOT_1, HIGH); // how long it goes out: delay(linearOUT - previouseOUT); digitalWrite(MOT_0, HIGH); digitalWrite(MOT_1, HIGH); //how long it stops: delay(300); } //if the temperature decreases the linear position decreases if (linearOUT < previouseOUT && temP > 50) { digitalWrite(MOT_0, HIGH); digitalWrite(MOT_1, LOW); //how long it goes in: delay(previouseOUT - linearOUT); digitalWrite(MOT_0, HIGH); digitalWrite(MOT_1, HIGH); //how long it stops: delay(300); } previouseOUT = linearOUT; Serial.println(previouseOUT); }
]]>
Overall top-level view of project
Yuxi
Overall view of the project
~~~
Transparency station:
Light valve is inserted between LED and photo-resistor
LED (off)
LED (on)
Yuxi
LED OFF
LED ON
~~~
Servo -> Potentiometer:
Servo motor attached to potentiometer
Yuxi
Servo motor physically connected to the potentiometer (by hot glue)
~~~
Video Demo:
Simple Narrative:
A square that can change its transparency called a light valve is placed between the LED and photo-resistor. This triggers the servo motor (blue motor on the right) to twist which in turn twists the potentiometer. This triggers the pump to vary the water level in the cylinders.
Simple Narrative:
The photoresistor receives the light signal from the LED, which triggers the rotation of the blue servo motor, which physically controls the rotation of the potentiometer and eventually triggers the water pump to change the water level.
—
Mid-process Photos:
Custom dark room cover for transparency station in order to get more accurate readings of the light valve.
We chose to attach the servo motor with the potentiometer by inserting a popsicle stick in the potentiometer knob and then wrapping wire around the servo arm and the stick.
Initial basic layout of all components of the double transducer.
Custom slot for light valve for other team to easily insert their light valve into our station.
Yuxi
We try to connect servo motor and potentiometer directly with hot glue by a simpler method.
We solved the problem of how to connect the pump to the Arduino Uno. The key is that we need a motor driver and how we need to connect it.
When trying to get LCD Screen to work, the code would run on the simulator, but not on the actual LCD Screen. Finally it turned out to be a material problem.
~~~
Discussion:
This project had its ups and downs. The best part was getting exposed to such a variety of devices and getting to put together a working gadget. We learned how to combine code with hardware, wire up circuits, write block diagrams and transfer said block diagrams to real circuits.
Surprisingly, the part that was the easiest was getting the rudimentary signal to flow through the gadget (input to output). The hardest part was mapping the input signal to the output signal in a continuous fashion. The physical placing of the parts on the board was also challenging, since there were many wires and moving parts (literally) that hindered the goal of making a clean, intuitive layout.
This project was also a great way to spread one’s wings creatively, since we could pick any intermediate transducer we wanted and create a narrative with our final project. Our narrative was of a sun and moon that would rise and set due to the change in water level in the graduated cylinders.
The next step will be to focus on project 2, which is a great opportunity for us to work independently. Also losing the fixed theme, our creativity becomes more important and can be better reflected. This does not only mean a bigger challenge, but also an opportunity for us to make more progress. We hope to use the experience we have gained to better our next project.
// Double Transducer: Transparency -> Rotation -> Water Level // Contributors: Cameron, Ethan // Date: 2/15/23 // This code facilitates the signal inputted by the photoresistor into an output signal // to the servo motor, which triggers a signal to the potentiometer which signals the // persitaltic pump. // // --------------------------- // | PIN | LABEL | // --------------------------- // | PHOTORESPIN | A0 | // | LEDPIN | 6 | // | PUMPPIN1 | 3 | // | PUMPPIN2 | 4 | // | SERVOPIN | 10 | // | POTENTPIN | A5 | // --------------------------- // LCD Code from https://courses.ideate.cmu.edu/60-223/s2023/tutorials/I2C-lcd // by Robert Zacharias // LCD #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C screen(0x27, 16, 2); int x = 123; const int PHOTORESPIN = A0; const int PHOTOMIN = 5; const int PHOTOMAX = 980; const int LEDPIN = 6; const int LEDMAX = 255; const int LEDMIN = 0; const int PUMPPIN1 = 3; const int PUMPPIN2 = 4; #include <Servo.h> Servo motor; const int SERVOPIN = 10; const int ROTMIN = 0; const int ROTMAX = 150; const int POTENTPIN = A5; int POTENTMAX = 631; int POTENTMIN = 276; // GLOBALS int rot = 0; int new_rot = 0; // MACROS #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) //LCD SCREEN void lcd_loop(int photoVal, int rotVal, int potVal, int ml){ // just count up to show x changing (the rest of the text will remain untouched) screen.setCursor(0, 0); screen.print("i:"); screen.print(photoVal); screen.print(" m:"); screen.print(rotVal); screen.setCursor(1, 9); screen.print(" ->"); screen.print(potVal); screen.print(" o:"); screen.print(ml); delay(1000); } // set ups void potent_setup(){ pinMode(POTENTPIN, INPUT); } void pump_setup(){ pinMode(PUMPPIN1, OUTPUT); pinMode(PUMPPIN2, OUTPUT); } void photo_setup(){ pinMode(PHOTORESPIN,INPUT); } void servo_setup(){ motor.attach(SERVOPIN); } void led_setup(){ pinMode(LEDPIN, OUTPUT); } // led loop void led_loop(int i){ Serial.print("LED AT LEVEL: "); Serial.println(i); analogWrite(LEDPIN, i); } // photo loop int photo_loop(){ int photoVal = analogRead(PHOTORESPIN); Serial.print("Photo lvl: "); Serial.println(photoVal); return photoVal; } // pump loop int pump_loop(){ Serial.println("Turning on pump"); int diff = abs(new_rot - rot); // if(diff < 50){ // return; // } int time = map(POTENTMIN + diff, POTENTMIN, POTENTMAX, 800, 3000); // (we turned CW) if(new_rot > rot){ Serial.print("We are pumping CW for this many ms: "); Serial.println(time); digitalWrite(PUMPPIN1, HIGH); delay(time); digitalWrite(PUMPPIN1, LOW); } // (we turned C'CW) if (new_rot < rot){ Serial.print("We are pumping CCW for this many ms: "); Serial.println(time); digitalWrite(PUMPPIN2, HIGH); delay(time); digitalWrite(PUMPPIN2, LOW); } delay(time); return time; } // servo loop int servo_loop(int photoVal){ int rot = map(photoVal, PHOTOMIN, PHOTOMAX, ROTMIN, ROTMAX); Serial.print("Servo rot: "); Serial.println(rot); motor.write(rot); // return its rotation return rot; } // potentiometer loop int potent_loop(){ int potVal = analogRead(POTENTPIN); Serial.print("Pot val: "); Serial.println(potVal); // return its potential return potVal; } //********* void setup() { Serial.begin(9600); // debug_setup(); // LCD lcd_setup(); // pump pump_setup(); // photo photo_setup(); //servo servo_setup(); //led led_setup(); // potent potent_setup(); } int i = 0; bool reset = 0; void loop() { if(reset){ //fill debug digitalWrite(PUMPPIN2, HIGH); delay(1000); digitalWrite(PUMPPIN1, LOW); delay(1000); } else{ Serial.println(); // // debug_loop(); led_loop(255); // read in from photo resistor int pV = photo_loop(); // trigger server int sV = servo_loop(pV); // servo physically triggers potent // read in potent new_rot = potent_loop(); POTENTMAX = max(POTENTMAX, new_rot); POTENTMIN = min(POTENTMIN, new_rot); // trigger pump int ml = pump_loop(); lcd_loop(pV, sV, new_rot, ml); rot = new_rot; delay(1000); i += 10; if(i >= LEDMAX){ i = LEDMAX; } } } void lcd_setup() { // 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(); // print the words Hello, world! onto the screen starting at the above cursor position screen.print("Hello, world!"); // move cursor to column 0, row 1 screen.setCursor(0, 1); // print text starting in that position screen.print("2nd row text"); screen.display(); screen.clear(); // you can also display a variable on the screen screen.home(); screen.print("x = "); screen.print(x); delay(1000); screen.clear(); } void debug_setup(){ Wire.begin(); Serial.begin(9600); Serial.println("\nI2C Scanner"); } void debug_loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for (address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknown error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for next scan }
]]>
Dunn’s double transducer
(Position -> Sound -> Transparency)
Bhairavi’s Double Transducer (position –> frequency –> transparency)
Part 2. Details
Figuring out how to use a MOSFET for the 12V power needed for the fan. (Mo)
Accidentally plugging the light valve (purple) into a non-PWM pin. (Bhairavi)
Part 3. Demonstration
Dunn’s project in action.
Mo’s project in action.
Bhairavi’s project in action.
Part 4. Narrative
A sensor reads the distance of an object in front of it, then converts that data to transparency through the use of a light valve. Our team had some different approaches to the middle step of the device. Mo used wind speed as the middle step, while Dunn and Bhairavi used sound. Mo’s project uses a fan that blows into a wind sensor for the middle step, while Dunn and Bhairavi’s projects use a buzzer and a microphone.
Part 5. Processes
Dunn’s process:
I wanted to ensure the laser was working correctly before proceeding with the other components. The laser range finder required solder before attachment. This component is a much better alternative to the ultrasonic ranger, which has a shorter range and wider scanning cone.
After resolving the laser issue, all other components came together fairly easily and quickly. However, I wanted to ensure everything worked before anchoring the components to the chipboard to avoid potential hardware issues.
Almost-final iteration. Most components are secured in their locations.
Mo’s Process:
Starting out by individually testing each component. This picture is the laser distance sensor – I found an example sketch on the Adafruit website which I used to learn how the VL530X works. (Bhairavi)
Last thing to change: accidentally plugging the light valve (purple) into a non-PWM pin, meaning that the transparency couldn’t be changed easily. (Bhairavi)
Part 6. Discussion
Overall, this project was an excellent introduction to physical computing: how everything works and comes together. Though our team had different solutions and approaches, they were equally unique and valid. We learned the hard lesson that the product might not work as you intended on the presentation day, and there are always issues to be resolved. It was interesting to see how our teammates approached the project prompt – each of us faced different constraints, leading us to end up with different middle steps for our transducers.
Since there was only one fan present in the classroom, both Dunn and Bhairavi chose to use buzzers generating sound for their middle steps. Dunn chose to control the volume of the buzzer for the middle step, since he found this easier than controlling the frequency. However, Bhairavi had trouble accurately detecting the volume of the buzzer and decided to hone in on the small range of frequencies in which the microphone could detect a linear increase in frequency.
Another difference between our projects was that the sensitivity of the transparency to movement was significantly different. Both Dunn and Bhairavi found it necessary to significantly limit the range in which the laser could detect movement due to range limitations in their middle step, while Mo was able to detect and express a larger range of movement.
Part 7. Functional Block Diagram and Schematic
Part 8. Code Submission
/* File/Sketch Name: transducer_test Version No.: v2.0 Created Feb 14 2023 Bhairavi Chandersekhar Description: This code works to run an Arduino Uno powered double transducer. The transducer converts distance to transparency. The double transducer uses a VL53L0X 'Time of Flight' Laser distance sensor to input distance (must be between 40 and 220 mm). The distance signal is then converted by the Arduino to a frequency between 290 and 335 Hz. which is then sent to a passive piezo buzzer. An electret microphone picks up the signal from the passive buzzer and uses the frequency to control the transparency of a light valve. Input, intermediate, and output values are displayed on an LCD screen. Notes: - all frequencies between 290 and 355 Hz are not able to be used. Frequencies from 300-315 Hz are very frequently not picked up accurately by the microphone. This is why much of the code is split up using if statements; frequencies in two different nonadjacent ranges are generated, and the frequencies picked up are also present in two nonadjacent ranges. Frequencies above, below, or between these ranges are disregarded as noise. - Specific ranges in which the microphone is able to detect the frequency accurately depend on the specific choice of buzzer and microphone, as well as the ambient noise in the room. Pin Mappings: 5 one lead of the piezo buzzer (other lead to ground) 10 one lead of the light valve (other lead to ground) A0 electret microphone input 3.3 V runs to electret microphone and laser distance sensor 5V runs to LCD display. SCL/SDA connects to both the LCD display and to the laser distance sensor FFT code based on AudioFrequencyDetector code written by Clyde A. Lettsome, PhD, PE, MEM For more information visit https://clydelettsome.com/blog/2019/12/18/my-weekend-project-audio-frequency-detector-using-an-arduino/ */ #include "arduinoFFT.h" #include <Wire.h> #include <VL53L0X.h> #include <LiquidCrystal_I2C.h> #define SAMPLES 128 //SAMPLES-pt FFT. Must be a base 2 number. Max 128 for Arduino Uno. #define SAMPLING_FREQUENCY 3000 //Ts = Based on Nyquist, must be 2 times the highest expected frequency. #define PIEZO_PIN 5 // Pin connected to the piezo buzzer. #define LIGHTVALVE 10 // digital pin connected to the light valve VL53L0X sensor; LiquidCrystal_I2C screen(0x27, 16, 2); arduinoFFT FFT = arduinoFFT(); unsigned int samplingPeriod; unsigned long microSeconds; unsigned long previousMillis; double vReal[SAMPLES]; //create vector of size SAMPLES to hold real values double vImag[SAMPLES]; //create vector of size SAMPLES to hold imaginary values int peak; int LCD_peak; void setup() { // FFT setup Serial.begin(115200); //Baud rate for the Serial Monitor samplingPeriod = round(1000000*(1.0/SAMPLING_FREQUENCY)); //Period in microseconds // TOF distance sensor setup delay(100); Wire.begin(); screen.init(); screen.backlight(); screen.home(); sensor.setTimeout(500); if (!sensor.init()) { Serial.println("Failed to detect and initialize sensor!"); while (1) {} } // Start continuous back-to-back mode (take readings as // fast as possible). To use continuous timed mode // instead, provide a desired inter-measurement period in // ms (e.g. sensor.startContinuous(100)). sensor.startContinuous(); previousMillis = millis(); peak = 0; LCD_peak = 0; } double fft() { /*Sample SAMPLES times*/ for(int i=0; i<SAMPLES; i++) { microSeconds = micros(); //Returns the number of microseconds since the Arduino board began running the current script. vReal[i] = analogRead(0); //Reads the value from analog pin 0 (A0), quantize it and save it as a real term. vImag[i] = 0; //Makes imaginary term 0 always /*remaining wait time between samples if necessary*/ while(micros() < (microSeconds + samplingPeriod)) { //do nothing } } /*Perform FFT on samples*/ FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD); FFT.ComplexToMagnitude(vReal, vImag, SAMPLES); /*Find peak frequency and print peak*/ double new_peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY); long transparency; // print detected frequency to LCD screen. if (new_peak < 1000) { peak = new_peak; LCD_peak = map(peak, 865, 955, 0, 17); LCD_peak = constrain(LCD_peak, 0, 17); transparency = map(peak, 865, 955, 0, 17); //transparency = constrain(transparency, 0, 17); } else if (new_peak > 1190 && new_peak < 1410) { peak = new_peak; LCD_peak = map(peak, 1190, 1410, 17, 99); LCD_peak = constrain(LCD_peak, 17, 99); transparency = map(peak, 1190, 1410, 17, 99); //transparency = constrain(transparency, 17, 99); } /* int LCD_peak = map(peak, 850, 1500, 0, 99); LCD_peak = constrain(LCD_peak, 0, 99); */ screen.setCursor(6, 1); screen.print(LCD_peak); if (transparency < 0) { transparency = 0; } analogWrite(LIGHTVALVE, transparency); int LCD_transparency = map(transparency, 0, 120, 0, 99); LCD_transparency = constrain(LCD_transparency, 0, 99); screen.setCursor(12, 1); screen.print(LCD_peak); Serial.print("peak frequency: "); Serial.println(peak); //Print out the most dominant frequency. Serial.print("transparency: "); Serial.println(transparency); } void buzz() { // detect distance int distance = sensor.readRangeContinuousMillimeters(); // print to LCD screen screen.home(); screen.print("i:"); // map distance from 0-100 long LCD_distance = map(distance, 40, 210, 0, 99); LCD_distance = constrain(LCD_distance, 0, 99); screen.print(LCD_distance); //calculate piezo freq. based on distance int frequency; if (distance < 85) { frequency = map(distance, 40, 85, 290, 295); frequency = constrain(frequency, 290, 295); } else { frequency = map(distance, 85, 220, 315, 335); frequency = constrain(frequency, 315, 335); } tone(PIEZO_PIN, frequency); //print to LCD screen, column 6 row 0 screen.setCursor(6, 0); screen.print("m:"); // map frequency from 0 - 99; int LCD_frequency; if (frequency <= 295) { LCD_frequency = map(frequency, 290, 295, 0, 17); LCD_frequency = constrain(LCD_frequency, 0, 17); } else { LCD_frequency = map(frequency, 315, 335, 17, 99); LCD_frequency = constrain(LCD_frequency, 17, 99); } screen.print(LCD_frequency); if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); } } void loop() { // first generate a sound buzz(); // then read in the frequency fft(); // refresh the screen every half a second. unsigned long currentMillis = millis(); if (currentMillis - previousMillis > 500) { screen.clear(); previousMillis = currentMillis; } }
]]>
Overall project photo
Team: Sapna Tayal, Harry Ren
Our project took the input of wind speed through a wind sensor and converted that to linear motion through a rack and pinion mechanism. This linear movement is detected by an ultrasonic ranger and transmitted into an RGB value to a Neopixel-led ring.
Video of the transducers working:
Sapna:
Harry:
Sapna’s Images:
testing the light and the servo motor with a potentiometer
doing tests with wind sensor (the broken one so it wasn’t working)
Assembly process! Adjusting the distance of the rack and pinion for an effective mapping of the ultrasonic sensor
Harry’s Images:
Final Transducer: Wind sensor inputs from the left, Color light outputs from the right.
Progress photo: Potentiometer LED setup which will soon be converted with a NEOPIXEL LED to test color code
Progress photo: Completed wiring of the tranducer prior to assembly on the chip board.
Part Highlight: ultrasonic ranger and the wall object that is being tracked. (The distance will change a light color)
Part Highlight: Servo motor driving a laser printed rack & pinion.
Discussion:
Our process for making the double transducers was one with a lot of unexpected pitfalls— often issues that weren’t really in our control. We started by ideating different transducer pairs for the middle step of the project and realizing the different strengths we brought with our differing experiences and backgrounds (Harry with more experience working with code, and Sapna with designing and building physical things), decided to go with a linear motion and ultrasonic sensor pair one that seemed exciting. Borrowing a wind sensor from Ideate lending, and playing around with example code on some neopixel led lights, we headed into what would be the horrors of putting all the pieces together. The lights and mapping of the range of the ultrasonic sensor to RGB values came first. Not finding too much help with online resources, Harry figured out a series of if statements that would cycle the RGB values from the (255, 0, 0) to (255, 255, 255) and range through the red to yellow to green to blue to purple range. Once we got that working, we focused on mapping a simple potentiometer (for testing purposes) to a servo motor that would eventually hold the gear that would eventually move the rack closer or further away to the ultrasonic sensor. This worked just fine. But the issues really started when we tried to connect the wind sensor to the motor. It was a frustrating tug of war between running the example code for the wind sensor, having it work mostly well, and then running our code to map it to the servo, and having it give up on us. The data would just be too noisy, and extremely inconsistent. Turns out it was a bunch of things out to get us: the wind sensor we were testing with was (99% sure) broken, and through reverse checking by removing big chunks of code, testing, and then adding stuff back in, we found that there was probably a library clash between the wind sensor and the neopixel precoded libraries we were using. So, with a lot of overthinking and analysing of little pieces of code, we managed to find that expanding a loop for the 12 led bulbs in the neopixel seemed to mostly solve our bugs! The last thing was fine-tuning the ranges and maps for the different pieces, adding the LCD screen and putting it all together!
With all of the electronics working, we proceeded with assembly. The process was mostly smooth. We did however, have some issues with the servo/ rack and pinion assembly. Our solution was to use modeling clay to elevate the servo to the necessary height, and use balsa strips to create a rail for the rack and pinion. After the physical assembly, we completed a final tuning of the ranges and maps for each portion. Our final product works mostly as expected and testing was limited to the consistencies of our breath as we had no other way to produce constant wind for our input.
This project served as an introduction to Arduino and electronics for the both of us and we are very happy with the final outcome. The process of successfully troubleshooting both a hardware problem, and a hidden software bug allowed us to experience both tracing physical electronic wiring and long code loops to find issues. This skill will no doubt be useful as we move on to more complex projects. Additionally, through our project and the class presentations, we learned about many useful and unique input and outputs that we could use in the future.
Arduino Code :
/* Project Name: Double Transducer: Wind speed to Light Color Team Members: Harry Ren, Sapna Tayal - reads a wind speed input and converts it to a servo motor output - reads an ultrasonic sensor input and converts it to a light color - normalizes all input and output data and displays it on an LCD screen Pin Mapping: A0: Wind sensor OUT A2: Wind sensor TMP SDA: LCD SDA SCL: LCD SCL 12: Ultrasonic TRIG 11: ULtrasonic ECHO 6: LED Data IN 5: Servo Data Portions of code are adapted from the following sources - Ideate 99-355: https://courses.ideate.cmu.edu/99-355/s2016a4/text/ex/Arduino/event-loop-programming/event-loop-programming.html - NEOPIXEL by ADAFRUIT example: strandtest relevant functions are credited code by Harry Ren and Sapna Tayal at Carnegie Mellon University, (hjren@andrew.cmu.edu, sapnat@andrew.cmu.edu) released by the authors to the public domain, Feb 2023 */ #include <Adafruit_NeoPixel.h> #include <Servo.h> #include <NewPing.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #ifdef __AVR__ #include <avr/power.h> // Required for 16 MHz Adafruit Trinket #endif // Neopixel connection Pin? #define LED_PIN 6 // How many NeoPixels are attached to the Arduino? #define LED_COUNT 60 // Declare our NeoPixel strip object: Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); // Declare servo object Servo doorMotor; //Set Pin constants const int DOORMOTORPIN = 5; //Servo Pin const int POTPIN = A1; // Potentiometer input for testing const int OutPin = A0; // wind sensor analog pin hooked up to Wind P sensor "OUT" pin const int TempPin = A2; // temp sesnsor analog pin hooked up to Wind P sensor "TMP" pin const int trigPin = 12; //ultrasonic trig pin const int echoPin = 11; //ultrasonic echo pin #define Max_D 20 // ultrasonic max distance //Declare ultrasonic NewPing sonar(trigPin,echoPin,Max_D); //Declare LCD screen LiquidCrystal_I2C screen(0x27, 16, 2); //Declare Variables for Event loop programming //Event loop function from https://courses.ideate.cmu.edu/99-355/s2016a4/text/ex/Arduino/event-loop-programming/event-loop-programming.html long next_output_time_1 = 0; // timestamp in microseconds for when next to update output 1 long next_output_time_2 = 0; // timestamp in microseconds for when next to update output 2 long output_interval_1 = 250000; // interval in microseconds between output 1 updates long output_interval_2 = 700; // interval in microseconds between output 2 updates int output_state_1 = LOW; // current state of output 1 int output_state_2 = LOW; // current state of output 2 //setup function void setup() { Serial.begin(115200); pinMode(POTPIN,INPUT); pinMode(OutPin,INPUT); doorMotor.attach(DOORMOTORPIN); strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) strip.show(); // Turn OFF all pixels ASAP strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255) screen.init(); //INITIALIZE LCD Screen screen.backlight(); screen.home(); screen.clear(); } // loop() function void loop() { //Wind speed input code int windADunits = analogRead(OutPin); float windMPH = pow((((float)windADunits - 264.0) / 85.6814), 3.36814); //Servo output code int servoval= map(windMPH,0,5,0,180); doorMotor.write(servoval); delay(100); //ultrasonic input code int ultra = sonar.ping_cm(); Serial.println(ultra); int light_inp = map(ultra,12,6,0,1023); //set colors, no looping implemented as looping during testing resulted in library conflicts with Servo.h strip.setPixelColor(0, get_col(light_inp)); strip.setPixelColor(1, get_col(light_inp)); strip.setPixelColor(2, get_col(light_inp)); strip.setPixelColor(3, get_col(light_inp)); strip.setPixelColor(4, get_col(light_inp)); strip.setPixelColor(5, get_col(light_inp)); strip.setPixelColor(6, get_col(light_inp)); strip.setPixelColor(7, get_col(light_inp)); strip.setPixelColor(8, get_col(light_inp)); strip.setPixelColor(9, get_col(light_inp)); strip.setPixelColor(10, get_col(light_inp)); strip.setPixelColor(11, get_col(light_inp)); strip.setPixelColor(12, get_col(light_inp)); strip.setPixelColor(13, get_col(light_inp)); strip.setPixelColor(14, get_col(light_inp)); strip.setPixelColor(15, get_col(light_inp)); strip.setPixelColor(16, get_col(light_inp)); strip.show(); // https://courses.ideate.cmu.edu/99-355/s2016a4/text/ex/Arduino/event-loop-programming/event-loop-programming.html // read the current time in microseconds long now = micros(); // Polled task 1 for output 1. Check if the next_output_time_1 timestamp has // been reached; if so then update the output 1 state. if (now > next_output_time_1) { // reset the timer for the next polling point next_output_time_1 = now + output_interval_1; // toggle the output_state_1 variable output_state_1 = !output_state_1; //Screen instructions: Prints normalized values for all sensor readings screen.clear(); screen.home(); screen.print("i:"); int normwind = map(windMPH,0,20,0,100); screen.print(normwind); screen.setCursor(6,0); screen.print("m:"); int normServo = map(servoval,0,180,0,100); screen.print(normServo); screen.setCursor(8,1); int normultra = map(ultra,12,6,0,100); screen.print(normultra); screen.setCursor(11,1); screen.print("o:"); int normlight = map(light_inp,0,1023,0,100); screen.print(normlight); } } // Color mapping function //Input: int from 0 to 1023 //Output: RBG Tuple /* RGB Values for reference red: (255, 0, 0) green: (0, 255, 0) blue: (0, 0, 255) cyan: (0, 255, 255) purple: (255, 0, 255) yellow: (255, 255, 0) */ uint32_t get_col(int i){ //red to yellow if(i<170){ i=map(i,0,170,0,255); return strip.Color(255,i,0); } //yellow to green else if (i<340){ i=map(i,170,340,0,255); return strip.Color(255-i,255,0); } //green to cyan else if (i<510){ i=map(i,340,510,0,255); return strip.Color(0,255,i); } else if (i<680){ i=map(i,510,680,0,255); return strip.Color(0,255-i,255); } else if (i<850){ i=map(i,680,850,0,255); return strip.Color(i,0,255); } else{ i=map(i,850,1023,0,255); return strip.Color(255,i,255); } }
Block Diagram and Schematics:
Block Diagram and Schematics for transducer.