Double Transducer: Transparency to Water Level
by Josh and Emma
Overall images of our project:
Functioning videos of our project:
Narrative:
In Project 1, we're creating a device that can turn light into a way to control the water level in a container. To make this happen, we're using two important parts: a light sensor and a mechanical system. First, the light sensor, which is like an electronic eye, will measure how transparent a surface is by measuring the light that goes through the surfaces. We'll use this information to figure out how much water needs to be added or removed in our container. Next, we have a mechanical system that can move a lever up and down. This lever will physically press buttons for us by positioning its wings at a certain angle. One button will make the water level go down (like draining water from a sink), and the other button will make it go up (like filling a glass with water). Now, here's how it all works together: When the light sensor sees more light, it will make the lever move in a certain way. This lever, in turn, will press one of the buttons. The number of times this button gets pressed will tell us how much water we should add or remove. Finally, we have a pipe connected to our container. Depending on the number of times the button is pressed, the pipe will either let water out or add more water in, just like turning a tap on and off. This way, we can control the water level in our container with the help of the light and the mechanical system. In conclusion, our device uses light to tell us when to add or remove water, and a mechanical system does the actual work by pressing buttons and controlling a pipe.
Progress photos (Emma):
Progress photos (Josh):
Discussion:
Initially, our team had a list of creative ideas on how to implement transparency to the water level measurement project, so the planning phase felt relatively straightforward. We brainstormed various approaches, considering different technologies and methodologies. However, as we delved deeper into the project, we realized that we lacked in focus in details. A moment came when we decided to incorporate buttons into our design. At this juncture, we were faced with the task of selecting the most appropriate materials, equipment, and input methods to make our idea a reality.
The real challenge emerged when we attempted to choose the right equipment for the job. We received a recommendation from Zach suggesting the use of solenoids, but this led to unforeseen issues. The solenoids heated up quickly, rendering them impractical for our purposes. This tweak forced us to regroup during the middle of the project, making the tough decision to unplug everything and shift our focus to servo motors. Despite the setbacks and adjustments, our determination to create a transparent water level measurement system remained unwavering, demonstrating the inherent complexity and problem-solving nature of engineering projects.
Thus, in the future, we decided to promptly plan more carefully in advance, giving us seamless procedure with our project.
Functional block diagram / schematic / sketch:
Block diagram of the transducer
Electronic Schematic Diagram for our transducer
Code Submission
// Double transducer : changing trasparency to water level
// by Josh and Emma
// The following code executes a way to change transparency to water level, using servo motors and buttons.
// Pins configuration, LCD display initialization, and servo motor initialization are executed in the
// setup() function. After proper declaration of variable, the code calculates target volume by mapping
// light value to some value, and difference between current volume and target volume. This is done so that
// we can set a target range for the servo motor to properly orientate itself. The orientation of servo
// motor is calculated so that it physically presses one of two buttons (fill / drain) pump, or press neither
// at all at a default angle. Then we make the pump to do fill / drain / do nothing in those situations.
// Pin mapping:
// pin 13: backLight
// pin 9: pumpFill (pump for filling)
// pin 10: pumpDrain (pump for Draining)
// pin 6: btnFill (button for filling)
// pin 7: btnDrain (button for draining)
// pin 3: servo motor
// pin A0: photoresistor input for analogWrite
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
// pins
#define backLight 13
#define pumpFill 9
#define pumpDrain 10
#define btnFill 6
#define btnDrain 7
#define SERVO 3
#define photoRes A0 //0-1023, actual 720- 980 with 5V power supplies
//consts
const bool debug = false;
const float volError = 1.5;
const float photoHigh = 1023;
const float photoLow = 650;
const float waterHigh = 30;
const float waterLow = 0;
Servo servo;
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x3F for a 16 chars and 2 line display
int photoVal = 0;
float currentVol = 0;
int state = 0;
int prevState = 0;
long fillTime = 0;
long drainTime = 0;
long lastLoopTime = 0;
long lastLCDtime = 0;
void setup() {
servo.attach(SERVO);
pinMode(backLight, OUTPUT);
pinMode(photoRes, INPUT);
pinMode(btnFill, INPUT);
pinMode(btnDrain, INPUT);
pinMode(pumpFill, OUTPUT);
pinMode(pumpDrain, OUTPUT);
lcd.init();
lcd.clear();
lcd.backlight(); // Make sure backlight is on
Serial.begin(9600);
servo.write(50);
delay(500); // wait for servo to reset
}
void loop() {
delay(100);
photoVal = analogRead(photoRes);
// calcualate target volume by mapping light value to some volume
float targetVol = mapfloat(photoVal, photoLow, photoHigh, waterLow, waterHigh);
// calcualte the difference between current volume and target volume
float volDiff = targetVol - currentVol;
if (abs(volDiff) < volError) { // volumes are close enough, no pumping
// do nothing
if (debug) {
Serial.print(photoVal);
Serial.print(" stop at ");
Serial.println(targetVol);
}
servo.write(50);
} else if (volDiff > 0) { // current below target, fill
// fill
if (debug) {
Serial.print(photoVal);
Serial.print(" fill to ");
Serial.println(targetVol);
}
servo.write(78);
} else if (volDiff < 0) { // current above target, drain
// drain
if (debug) {
Serial.print(photoVal);
Serial.print(" drain to ");
Serial.println(targetVol);
}
servo.write(22);
} else {
Serial.println("Unexpected Error");
}
// set which state the pump should be in based on buttons
if (digitalRead(btnFill)) {
state = 1; // fill
} else if (digitalRead(btnDrain)) {
state = 2; // drain
} else {
state = 0; // do nothing
}
// based on which state the pump is in, get total time pumped in each state
switch (state) {
case 1: // fill
// TODO make pump fill
digitalWrite(pumpFill, HIGH);
digitalWrite(pumpDrain, LOW);
fillTime += millis() - lastLoopTime; // essentially add up time each time loop() happens
break;
case 2: //drain
// TODO make pump drain
digitalWrite(pumpFill, LOW);
digitalWrite(pumpDrain, HIGH);
drainTime += millis() - lastLoopTime;
break;
default:
digitalWrite(pumpFill, LOW);
digitalWrite(pumpDrain, LOW);
break; // nothing happens
}
// calculate current volume as sum of filled and drained volume, assume begin at 0
currentVol = (float)(fillTime - drainTime) / 1000;
displayVolume(targetVol, currentVol);
if (debug || true) {
Serial.print(targetVol);
Serial.print(" : ");
Serial.println(currentVol);
}
// record the time when this loop ends so we can get duration between this loop and last loop
lastLoopTime = millis();
}
void displayVolume(float targetVol, float currentVol) {
if (millis() > lastLCDtime + 300) {
lcd.setCursor(0,0); //Set cursor to character 2 on line 0
lcd.print("Target : Current");
lcd.setCursor(0,1); //Move cursor to character 2 on line 1
lcd.print(String(targetVol) + " : " + String(currentVol));
lastLCDtime = millis();
}
}
// same map function except this one returns floats
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}