Project 1 – Intro to Physical Computing: Student Work Spring 2023 https://courses.ideate.cmu.edu/60-223/s2023/work Intro to Physical Computing: Student Work Fri, 03 Mar 2023 02:00:47 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.6 Double Transducer: Water level to Rotational Speed https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-water-level-to-rotational-speed/ Wed, 15 Feb 2023 13:17:43 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17458 Peiran Ge, Quinten Staples.

 

Top View of Peiran’s double transducer. Water level (bottom left), rotational speed (top right)

“Image of Quinten's double transducer”

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.

Image of breadboard with L293D Motor Driver and cables attaching to Arduino and dc motor

(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.

IMG_9351

 

Process:

 

Experimenting with how to best exclude outside lighting for the photo-resistors.

TinkerCad Model of Double Transducer- Water Level to Rotational Speed

TinkerCad Model of Double Transducer- Water Level to Rotational Speed

Process Image of Quinten's double transducer.

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

Block Diagram of Quinten's Double Transducer from draw.io

(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);  
  } 
}

 

]]>
Double Transducer: Rotational Speed to Heat https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-rotational-speed-to-heat/ Wed, 15 Feb 2023 07:50:36 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17189 Team members:
Lily Hummel & Caleb Sun

Caleb’s Double Transducer

A roughly A4 sized piece of chipboard with Arduino components on it. There is a small LCD screen, a servo motor with a piece of yellow paper attached, as well as a few mini breadboards and a 9 volt battery. The wires are organized with pipe cleaners and the pieces are held to the chipboard with zip ties

Caleb’s Double Transducer

Detail Pictures

A closeup shot of the transducer, with emphasis on the yellow paper arm that is attached to the servo motor. It has a big cartoonish hand drawn on it with a cat in the background.

Closeup of the arm attached to Caleb’s servo motor.

 

A closeup shot of the soldered portion of transducer

Closeup of Caleb’s soldering for the middle and end stage of the transducer.

Process Pictures

A mess of wires and breadboards, with an Arduino and ultrasonic ranger in the middle.

Caleb’s Process 1: Early stage process where the pieces are still in stages.

 

A mess of breadboards, wires, and components on a chipboard. There are a few transistors scattered and a lot of the wires are unplugged.

Caleb’s Process 2: Image taken shortly after deciding to solder a portion of the transducer together, causing mass destruction to the original transducer.

Lily’s Double Transducer

Image of an arduino set-up taped to a piece of chipboard. The set-up starts by connecting the arduino to a breadboard that has multiple wires and a rotary encoder plugged in. The wires connect a LCD display, servo motor, ultrasonic ranger, transistor, and battery to the breadboard. The transistor is also connected to an incandescent bulb.

This is the overall setup of the double transducer.

Detail Pictures

Image of a breadboard holding an ultrasonic ranger that is positioned in front of a piece of paper attached to a servo motor arm made of a popsicle stick.

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.

 

Image of a breadboard holding an ultrasonic ranger that is positioned in front of a piece of paper attached to a servo motor arm made of a popsicle stick.

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.

 

Image of red breadboard connecting the arduino to a transistor that then connects to an incandescent bulb. The transistor uses a BLANK ohm resistor to connect pin power to ground.

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.

 

Image of red breadboard connecting the arduino to a transistor that then connects to an incandescent bulb. The transistor uses a BLANK ohm resistor to connect pin power to ground.

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.

Process Photos

Image of a breadboard connecting an arduino to a servo motor.

Progress photo showing testing of the servo motor.

 

 

Video

Narrative Description

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.

Discussion

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.

Block Diagram

Block diagram of rotational speed to heat double transducer

Semantic Diagram

Schematic Diagram for the Double Transducer

Code

//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--;
  }  
}

 

]]>
Double Transducer – Temperature to Fan Speed. https://courses.ideate.cmu.edu/60-223/s2023/work/project-1-double-transducer-temperature-to-fan-speed/ Tue, 14 Feb 2023 22:19:37 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17171 Angie Wang, Stanley Ip, Michelle Liu

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:

A hand holding the adafruit sensor next to a color wheel, propped up on a stepper motor.

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;
}

 

 

]]>
Double Transducer: Light Color to Sound Pitch https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-light-color-to-sound-pitch/ Tue, 14 Feb 2023 20:13:11 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17111

Juhi’s Double Transducer

Andres’s Double Transducer

Detail Photos

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)

Working Videos

 

Narrative Description

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.

Progress Images

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)

Discussion

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.

Block and Schematic Diagram

 

Code

/* 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;
  }
}

 

]]>
Double Transducer : Light to Heat to Linear Position | The Sunshine Lovers https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-light-to-heat-to-linear-position/ Tue, 14 Feb 2023 20:07:07 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17166  

board with Arduino and electronic components, featuring a turtle attached to a linear actuator.

This is the final setup of Evie’s Sunshine machine

 

Finalized setup of Dongtao’s sunshine machine (without Casey)

 

Project Overview

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

Process

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.

  • picture of LCD with data values

    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)

    picture of Arduino to breadboard wired up to a thermometer

    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.

 

Discussion

    • Highlight 1: Heating up the light bulb is relatively easy and quick, but cooling it down can be time-consuming – it would be a good idea to add an additional ventilation fan to the setup to help with it.
    • Highlight 2: Photoresistor output data in combination with artificial light input is highly dependent upon the ambient light condition, the results may vary depending on the location in the studio space; therefore, it is a good idea to preset a parameter ‘ambient lighting baseline’ every time the machine starts to run for the flashlight to work with. 
    • Highlight 3: It was fascinating to learn the difference that H-bridge can help drive high-voltage components without a physical ‘switch’ and works with analog input, whereas the relay only works with digital inputs.
    • Highlight 4: Always test run the machine on different power sources to ensure it is compatible across the board – especially need to test it with the power supply used for the presentation
    • For presentation purposes, it would be great to start thinking about designing a casing for future projects – therefore the interactive feature of the machine is easier to apprehend by the audience. The idea of casing shall be evaluated from the beginning of the prototyping process for coordination of different parts within.

 

Functional block diagram and schematic

 

 

Code submission

//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);
}

 

 

 

]]>
Double Transducer: Transparency to Water Level https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-transparency-to-water-level/ Mon, 13 Feb 2023 21:12:36 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17170 Full View:

Top level view of project showing Arduino, transparency station, servo motor, potentiometer, peristaltic pump, and two graduated cylinders filled with water.

Overall top-level view of project

 

Yuxi

Overall view of the project

~~~

 

Transparency station:

Light valve is inserted between LED and photo-resistor

Transparency station with LED in front of photo-resistor with space in the middle for light valve.

LED (off)

Transparency station with LED in front of photo-resistor with space in the middle for light valve. LED is on.

LED (on)

 

Yuxi

LED OFF

 

LED ON

 

~~~

Servo -> Potentiometer:

Servo motor attached to potentiometer with wires

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:

A black aluminum cover covering the transparency station for more accurate readings.

Custom dark room cover for transparency station in order to get more accurate readings of the light valve.

 

Servo motor attached to potentiometer with wires.

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 showing the Arduino, breadboard, LED, photo-resistor, servo motor, potentiometer, pump, and two empty cylinders.

Initial basic layout of all components of the double transducer.

 

Small bread board with popsicle sticks glued to it to act as a slot for the square light valve for easy insert.

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
}

 

]]>
Double Transducer: Linear Position to Transparency https://courses.ideate.cmu.edu/60-223/s2023/work/double-transducer-linear-position-to-transparency/ Mon, 13 Feb 2023 21:06:03 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17248 Part 1. Overall Images of the Final Product

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;
  }
}

 

]]>
Double Transducers: Wind Speed to Colored Light https://courses.ideate.cmu.edu/60-223/s2023/work/project-1-double-transducers-wind-speed-to-colored-light/ Mon, 13 Feb 2023 15:36:33 +0000 https://courses.ideate.cmu.edu/60-223/s2023/work/?p=17164

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.

An image of a potentiometer and LED in a breadboard with an arduino

Progress photo: Potentiometer LED setup which will soon be converted with a NEOPIXEL LED to test color code

A breadbord with a servo, ultrasonic ranger, LCD, wind sensor, and NEOPIXEL LED wired in. Wires are very tangled

Progress photo: Completed wiring of the tranducer prior to assembly on the chip board.

Ultrasonic ranger across from a balsa sheet to track distance

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.

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.

]]>