This is a prototype of a wearable device that assists in navigation by providing tactile directional instructions to the user to assist them in wayfinding while navigating unfamiliar routes in a simple outdoor environment.
A few years ago I was driving to my new workplace for the first time and I was using a maps application on my phone to guide me in my navigation. Midway, I was confused about which turn to take and I glanced over at my phone as I was trying to figure out what turn to take while I was driving. It was then that I was pulled over by the traffic police for using my phone while driving. That’s when I thought to myself, how am I supposed to use a navigation system, that mainly relies on visual directional instructions when I’m driving if I’m supposed to keep my eyes on the road at all times?
An overview of the tactile wearable showing the various parts.
Coin vibration motors mounted velcro adjustable rings that go around the fingers.
The tactile wearable mounted on the dominant hand of the user.
Note: Since I was unable to record the final video myself, I used the video that Harshine shot during my demo in class. (Thanks Harshine!)
I initially created a storyboard to draw out scenarios of use for the tactile wearable. The storyboard helped me figure out an appropriate form factor for the device as well.
Initial storyboard illustrating the use case for the device.
Throughout the process of building this device, I got to learn how to use many sensors and components that I had never used before so it was a great opportunity for me to expand my skills in physical computing.
One such component was the GPS module. Oh the GPS module… Since this was a fairly complex component to use, I had to use a pre-written code to read the signal from the GPS module. The GPS module that I was using was a Geekstory GT-U7 GPS Module GPS that had a GPS chip and an external antenna. The GPS also had a position fix LED indicator light that indicated that the GPS was searching for satellites when it was not blinking and that the GPS had fixed on a satellite by blinking every second. I downloaded the pre-written code and connected up the GPS module and the Arduino. And then there were a few problems I ran into.
I was excited when I first connected up all the components and expected it to work right away. However, the GPS was not fixing on a satellite although I had left it connected for several minutes. I then realized that it was almost impossible to get a GPS reading sitting in the Phys Comp lab (no surprises there). So, I went outside and stood under the clear sky for several minutes, walked around even, and then the little LED started blinking!
Getting the GPS to find a position fix by taking it outside under clear skies.
Marking the GPS coordinates on a simple outdoor to create a working demo.
After I got the GPS to find a position fix, the GPS started transmitting signals that were a bunch of latitude and longitude coordinates. However, when I would move around, the GPS wasn’t able to update the coordinates. There seemed to be a lag in the updating of the location. I tried many things to fix this, I rechecked if all the connections were good, I ensured that I was away from buildings, I restarted my Arduino, I even held up the antenna of the GPS over my head. I even checked to make sure that I had uploaded the pre-written code exactly as it was, line by line and that was the problem! I consulted with Zac about this and upon reading the code line by line, he found a pesky little delay of 1 second right at the end of the code. The delay was originally meant to set the rate at which the GPS prints the latitude and longitude coordinates however, it was also messing up the signal that the GPS was transmitting. The GPS was picking up signals from satellites at a faster rate than it was able to transmit them which caused the signals to get jumbled up. We fixed this problem by using millis instead. Lesson learned: millis for life.
The pesky delay at the end that was the root of all problems!
Reading raw data from the GPS.
To create vibrotactile feedback in the wearable, I was using a small coin vibrational motor that had two very thin and fragile connecting leads onto which longer connecting wires had to be soldered. However, since the connection was so delicate, I was afraid of breaking the connections which made testing the prototype a huge challenge. I consulted with Zac and asked him how I could make the prototype more robust. Zac was extremely helpful and suggested I used heat shrinking material to reinforce the joints with the connections. He drew a wonderful diagram to illustrate the process which was helped me reinforce the connections and make the prototype durable.
A diagram illustrating the process of reinforcing the vibration motor with different diameters of heat shrink tubing, courtesy Zac.
The final outcome of the reinforcing the vibration motor that made the prototype suitable to test multiple times without the fear of the connections breaking.
To present our projects, we had an in-class demo that went differently than I imagined. I had prepared to demo the device outdoors since that was the best way I could demo the device and show that it actually worked in the way I had described it would. The demo, however, was only conducted indoors which meant I had to hard code the coordinates and fake the feedback from the wearable, which I was able to successfully do. One comment I got was the “the demo could have involved a visual aspect to show what was going on in the device (to the audience)”. I agree that LED lights would have probably helped to better indicate which vibrational motors were active. Since I was prepared for a live demo, in which a volunteer would be guided from a preset origin to a destination along a simple route outdoors, I was unable to add a visual component to the demo. I intended to add LED lights to the vibrational rings for demo purposes post-spring break which I, unfortunately, couldn’t do.
Another comment I received was “It would’ve been nice to have an enclosing for the device.” I agree with this feedback, however, the challenge was that the connections hadn’t been reinforced yet so I was afraid to make anything permanent since I only had a limited number of small coin vibrational motors available. Another challenge was that the GPS unit had to have a view of the clear sky, so enclosing it in a bix might have interfered with its ability to find a position fix.
Overall, I learned so much through this project. Not only did I gain technical skills but this project also made me persevere and build the thing I intended to build no matter the technical difficulties I ran into. Zac was instrumental in encouraging me along the way and creating a project that I was in the end proud of. There were times in the project when I wanted to give up and switch to a simpler technique, but Zac’s timely help pushed me towards the finish line.
I truly enjoyed figuring out the code for this project since it involved a fair bit of complex logic. I had taken a Python course in one of the previous semesters and my knowledge of coding came in very handy for this project. The next time I will be more vigilant when I use pre-written code because it may not always be correct. I was also surprised that I found soldering extremely therapeutic. From this project, I was able to learn how to make well-soldered connections.
If I have access to resources, I would want to work on the form and the ergonomics of the prototype. I would create a well-designed enclosing to house all the components, I would create more aesthetically pleasing rings that can easily be mounted on the hand of the user. I would also want to test out the prototype with participants in the context of outdoor navigation and evaluate the prototype.
Schematic for the tactile navigation wearable
/* Project 2: Tactile Navigation Wearable By Aadya Krishnaprasad Description: This is the code for a prototype of a wearable device that assists in navigation by providing tactile directional instructions to the user to assist them in wayfinding while navigating unfamiliar routes in a simple outdoor environment. Pin Mapping: pin | mode | description ---------|-----------|------------- 3 Output GPS module tx 4 Input GPS module rx 5 Output Vibration motor (RING1) 6 Output Vibration motor (RING2) 9 Output Vibration motor (RING3) 10 Output Vibration motor (RING4) */ // Compass SETUP #include <QMC5883LCompass.h> QMC5883LCompass compass; //Citation: GPS code from: https://create.arduino.cc/projecthub/ruchir1674/how-to-interface-gps-module-neo-6m-with-arduino-8f90ad // GPS SETUP #include <SoftwareSerial.h> #include <TinyGPS.h> //long lat,lon; // create variable for latitude and longitude object float lat = 28.5458, lon = 77.1703; // create variable for latitude and longitude object //float lat = 40.4412, lon = -79.9435; // Location: Origin, Head West //float lat = 40.4414, lon = -79.9440; // Location: Waypoint 1, Head North SoftwareSerial gpsSerial(3, 4); //tx,rx TinyGPS gps; // create gps object // RING PIN ASSIGNMENT const int RING1 = 5; const int RING2 = 6; const int RING3 = 7; const int RING4 = 9; void setup() { Serial.begin(9600); compass.init(); gpsSerial.begin(9600); // connect gps sensor pinMode(RING1, OUTPUT); pinMode(RING2, OUTPUT); pinMode(RING3, OUTPUT); pinMode(RING4, OUTPUT); } void loop() { int a; int WAIT = 250; long timer = 0; if ( (millis() - timer) >= WAIT ) { // millis() is always the number of milliseconds since the Arduino powered up // Read compass values ; Compass 0 is East compass.read(); // Return Azimuth reading a = compass.getAzimuth(); Serial.print("A: "); Serial.print(a); Serial.println(); timer = millis(); // reset the timer } // Code to get latitude and longitude coordinates from the GPS while (gpsSerial.available()) { // check for gps data if (gps.encode(gpsSerial.read())) // encode gps data { gps.f_get_position(&lat, &lon); // get latitude and longitude Serial.print("Position: "); Serial.print("Latitude:"); Serial.print(lat, 6); Serial.print(";"); Serial.print("Longitude:"); Serial.println(lon, 6); } } String latitude = String(lat, 4); String longitude = String(lon, 4); Serial.println(latitude + ";" + longitude); if ((((40.4411) <= (lat)) && ((lat) <= (40.4415))) && (((-79.9434) >= (lon)) && ((lon) >= (-79.9438)))) { Serial.println("Location: Origin, Head West"); if ((a >= 134) && (a <= 224)) { // West(180) goStraight(125); } else if ((a >= 316) || (a <= 45)) { // East(0) turnAround(250); } else if ((a >= 225) && (a <= 315)) { // South(270) turnRight100(125); } else if ((a >= 46) && (a <= 135)) { // North(90) turnLeft100(125); } } if ((((40.4413) <= (lat)) && ((lat) <= (40.4414))) && (((-79.9439) >= (lon)) && ((lon) >= (-79.9441)))) { Serial.println("Location: Waypoint 1, Head North"); // Location: Waypoint 1, Head North if ((a >= 46) && (a <= 135)) { // North(90) goStraight(125); } else if ((a >= 134) && (a <= 224)) { // West(180) turnRight100(125); } else if ((a >= 225) && (a <= 315)) { // South(270) turnAround(250); } else if ((a >= 316) || (a <= 45)) { // East(0) turnLeft100(125); } } if ((((40.4415) <= (lat)) && ((lat) <= (40.4416))) && (((-79.9439) >= (lon)) && ((lon) >= (-79.9441)))) { Serial.println("Location: Waypoint 2, Head West"); // Location: Waypoint 2, Head West if ((a >= 134) && (a <= 224)) { // West(180) goStraight(125); } else if ((a >= 316) || (a <= 45)) { // East(0) turnAround(250); } else if ((a >= 225) && (a <= 315)) { // South(270) turnRight100(125); } else if ((a >= 46) && (a <= 135)) { // North(90) turnLeft100(125); } } if ((((40.4416) <= (lat)) && ((lat) <= (40.4417))) && (((-79.9445) >= (lon)) && ((lon) >= (-79.9446)))) { Serial.println("Location: Waypoint 3, Head North"); // Location: Waypoint 3, Head North if ((a >= 46) && (a <= 135)) { // North(90) goStraight(125); } else if ((a >= 134) && (a <= 224)) { // West(180) turnRight100(125); } else if ((a >= 225) && (a <= 315)) { // South(270) turnAround(250); } else if ((a >= 316) || (a <= 45)) { // East(0) turnLeft100(125); } } if ((((40.4419) <= (lat)) && ((lat) <= (40.4422))) && (((-79.9443) >= (lon)) && ((lon) >= (-79.9445)))) { // Location: Destination, Stop destinationReached(250); } } ////////////////PATTERNS // Tactile pattern: If left turn is less than 50 feet away, then vibration motor vibrates at maximum intensity void turnLeft50(int intensity) { analogWrite(RING1, intensity); } // Tactile pattern: If left turn is between 50 and 100 feet away, then vibration motor vibrates at medium intensity void turnLeft100(int intensity) { analogWrite(RING2, 0); analogWrite(RING3, 0); analogWrite(RING4, 0); analogWrite(RING1, intensity); } void goStraight(int intensity ) { analogWrite(RING1, 0); analogWrite(RING4, 0); analogWrite(RING2, intensity); analogWrite(RING3, intensity); } // Tactile pattern: If right turn is less than 50 feet away, then vibration motor vibrates at maximum intensity void turnRight50(int intensity ) { analogWrite(RING2, 0); analogWrite(RING3, 0); analogWrite(RING1, 0); analogWrite(RING4, intensity); } // Tactile pattern: If right turn is between 50 and 100 feet away, then vibration motor vibrates at medium intensity void turnRight100(int intensity ) { analogWrite(RING2, 0); analogWrite(RING3, 0); analogWrite(RING1, 0); analogWrite(RING4, intensity); } void turnAround(int intensity ) { analogWrite(RING1, intensity); analogWrite(RING2, intensity); analogWrite(RING3, intensity); analogWrite(RING4, intensity); } void destinationReached(int intensity) { analogWrite(RING1, intensity); delay(70); analogWrite(RING1, 0); analogWrite(RING2, intensity); delay(70); analogWrite(RING2, 0); analogWrite(RING3, intensity); delay(70); analogWrite(RING3, 0); analogWrite(RING4, intensity); delay(70); analogWrite(RING4, 0); }
]]>
Ultimately, it will reside in the 3D printing room in the back of Hunt A5.
The first point at which I had to make a major decision was in figuring out whether or not I would incorporate sensors for both filaments. For context, the Ultimaker 3 can use 2 filaments when printing. The first is often the build material, which is typically either:
The second is often a support material, which is typically either:
Ideally, I would want to include sensors with both, but it turns out that PVA is transparent to infrared. Since the break-beam sensors I planned to use operated with infrared LEDs, they would work fine with the build materials, but not with PVA. They wouldn’t see the strand of PVA. At this point, I either would have needed to make a custom sensor that uses a regular LED and a photoresistor, or order a break-beam sensor that uses visible light. When I am able to return to this project, chances are high that I will just order some sensors that use visible light.
When the build material is low (not triggering the break-beam sensor), the screen will same something like this.
The same goes for the support material.
Here’s what is displayed when all filaments are OK. At this point, I was triggering the sensors with a screwdriver.
Another decision I had to make came as the deadline for the critique was fast approaching. I could either wire up one set of sensors and hopefully have that fully functioning, or try to wire all 4 sets up. This was ultimately my downfall. I never truly made a decision one way or the other, so the result was a severely underdeveloped device that barely worked. The 1 sensor that was wired up didn’t work at the time of critique due to faulty soldering on my part. Then again, this is what happens when you rush.
I was relieved that those critiquing my underdeveloped device at least gave me constructive feedback and pointed out the good things about the project. Quite obviously, however, it was a far cry from faultless. Given the state the device was in, I can see why one critic said this:
“Would it have been easier and prone to less error if you made 4 seperate detectors? Then if something goes wrong, you would only need to fix 1 of the printers instead of putting all four sensors off-line.”
While I do agree that it would likely be easier to have 4 separate detectors for the sake of serviceability, it is a little bit wasteful when all 4 detectors could be driven off of 1 Arduino Uno R3 (or a Photon, more on that later).
Another critic said this:
“The project sounds very sophisticated and pretty useful for your job. However, how can you make it be more straightforward for the next users to use that have your job? Make it easier to understand?”
I think the reason it is so difficult to understand is because of how unfinished it is. Once the device is actually finished (hopefully this will be shortly after the COVID-19 pandemic dies down), it should make a lot more sense.
With all of that said, I am quite unhappy with how the project turned out. However, the issues that it has almost entirely trace back to me. In particular, my indecision was my biggest problem, and it led me to waste a lot of time. I think it goes without saying that I did not satisfy my goals for the project.
One truly bone-headed decision I made was using individual wires for each of the pins, rather than being smart and following RZach’s suggestion of using multi-core wire. It would have been the same level of difficulty in terms of soldering, but the external wiring would have been much cleaner. With that in mind, when I am finally able to resume work on this device, the sensors will all use multi-core wire where applicable. Everything will also be wired into a protoboard, rather than a breadboard, so as to make the device more permanent.
One final thing to note when continuing this project is that it will likely be ported to work with a Particle Photon (suggested by Cody Soska and RZach). This will enable me to use IFTTT (if-this-then-that) more easily so that I can have the device send a Slack message to IDeATe staff (Tech Advisors in particular) saying which printer is low on filament and which filament it’s low on. It’s possible to do this with an Arduino, but much easier with a Photon.
NOTE: The code you are about to see is very unfinished. That said, it does compile, and it properly reads the sensors (when they’re wired correctly). Once I am able to wire the device up correctly, I will update this post with revised code, both for the final Arduino version, as well as the Photon version. Regardless, here’s the code as it was during critique:
/* Project 2 Seth Geiser (sgeiser) Collaboration: None Challenge: The biggest challenge was getting the sensor wired up correctly. I did cook one. :/ Next time: I don't know... keep going? Description: The purpose is to create a set of sensors that can detect when a 3D printer is running out of filament. Two break- beam sensors are used for this purpose -- one for the primary (build) material and another for the secondary (support) material. When material is blocking the sensor, the sensor will read false, meaning that it is not yet critically low. When material is no longer blocking the sensor, the sensor will read true, meaning that the material is critically low and should be replaced with a new reel. At the very least, the device will sound an alarm (not implemented) and display which printer is low on filament and which filament it is low on (on the I2C LCD). The final version will do this and send a Slack message using IFTTT to IDeATe Tech Advisors. Pin mapping: pin | mode | description -----|-------|------------ 2 input break-beam sensor 1 (build material) 3 input break-beam sensor 2 (support material) SDA/SCL display */ #include <Wire.h> #include <LiquidCrystal_I2C.h> /* Create an LCD display object called "screen" with I2C address 0x27 which is 20 columns wide and 4 rows tall. */ LiquidCrystal_I2C screen(0x27, 20, 4); const int BUILDSENSOR = 2; const int SUPPORTSENSOR = 3; unsigned long timer = 0; // variable for timing const int INTERVAL = 1000; // milliseconds between updates void setup() { pinMode(BUILDSENSOR, INPUT); pinMode(SUPPORTSENSOR, INPUT); screen.init(); screen.backlight(); screen.home(); Serial.begin(9600); } void loop() { bool buildCritical = digitalRead(2); bool supportCritical = false; /* would be digitalRead(3), but sensor was not wired up in time for critique, so I left it at false to eliminate any chances of an ambiguous reading. */ // updating the LCD output if (millis() >= timer) { if (!buildCritical) { screen.setCursor(0, 0); screen.print((String) "All filaments OK."); screen.setCursor(0, 1); screen.print((String) "Support sensor"); screen.setCursor(0, 2); screen.print((String) "is missing!"); } else if (buildCritical) { screen.setCursor(0, 0); screen.print((String) "Build material "); screen.setCursor(0, 1); screen.print((String) "is low!"); } else if (supportCritical) { screen.setCursor(0, 0); screen.print((String) "Support material"); screen.setCursor(0, 1); screen.print((String) "is low!"); } timer = millis() + INTERVAL; // and update timer } }
Unlike the code, the schematic for this device shouldn’t change too much, even when transitioning from an Arduino Uno R3 to a Particle Photon — some pin numbers may change, but that’s about it. This was made using Fritzing.
All four sets of sensors are wired up identically.
The Wearable Soundtrack is a wristband that uses a knob and speaker to play select songs as an “epic soundtrack” to the wearer’s life.
One decision point in my process to change the device from an attachable sleeve to a wristband. This decision was made because it was more practical in terms of using the device and making the device with the amount of fabric I had. Another decision point in my process was to use a large 9V battery instead of the two coin cell batteries I originally planned on using. The coin cell batteries were inconsistent in their ability to power the device, and while the size of coin cell’s are preferable for the design of the device, I was forced to use a 9V battery.
Testing out the electronic components of the device.
Creating the laser cut file for the fabric in Rhino 6
Placing components in fabric and sewing
Making this Wearable Soundtrack was incredibly fun and rewarding for me because it allowed me to explore creating a whimsical device that reflected my love of music and its importance in my daily life. However, I was not as impressed with the result of my efforts. I received a lot of feedback about the aesthetics of the device. For example, one of my classmates noted that “[they] think it is a fun idea, but aesthetically it could look a lot better. Maybe like a watch?” I agree with this comment, the aesthetics on my device were not up to the standard that I am usually capable of because of rushing on my part to finish the device while balancing a heavy workload in my other classes. Also, there were some unforeseen complications with the power that forced me to use a larger battery than I accounted for, and in my attempts to accommodate this component, I created a messy looking device. However, some of the feedback from my classmates was not as relevant or helpful. One classmate asked me to consider “How could you make the lights that is glowing from your components more intentional? I thought you put LED lights in there, but it was just light from being turned on.” While I understand their comment, the component in question with the LED was essential to my design, and I could not replace it with a component without an LED; furthermore, I found the LED helpful as an indication whether or not the device was working after I added the fabric. I do not plan on building another iteration of this device; however, if I were to do so, I would use a synthetic fabric that would laser cut better, take more time sewing the device, creating mounts for each component, and buying a smaller battery with the appropriate voltage.
/*Wearable Soundtrack Anishwar Tirupathur Description: This code sets up the DFPlayer Mini component and maps a potentiometer so that it can be used to select the songs the DFPlayer Mini plays. pin/ mode/ description ----------------------- A5 INPUT Potentiometer 10 RX DFPlayer Mini 11 TX DFPlayer Mini Collaboration: I used code from the DFRobotDFPLayerMini Library, specifically the "FullFunction" and "GetStarted" libraries. In addition, I recieved help from Seth when using the IDeAte lasercutters. */ #include "Arduino.h" #include "SoftwareSerial.h" #include "DFRobotDFPlayerMini.h" SoftwareSerial mySoftwareSerial(10, 11); // RX, TX DFRobotDFPlayerMini myDFPlayer; void printDetail(uint8_t type, int value); const int POTENTPIN = A5; int prevState; void setup() { pinMode(POTENTPIN, INPUT); mySoftwareSerial.begin(9600); Serial.begin(115200); Serial.println(); Serial.println(F("DFRobot DFPlayer Mini Demo")); Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3. Serial.println(F("Unable to begin:")); Serial.println(F("1.Please recheck the connection!")); Serial.println(F("2.Please insert the SD card!")); while (true) { delay(0); // Code to compatible with ESP8266 watch dog. } } Serial.println(F("DFPlayer Mini online.")); myDFPlayer.volume(20); //Set volume value. From 0 to 30 } void loop() { // put your main code here, to run repeatedly: int potVal = map(analogRead(POTENTPIN), 0, 1023, 1, 35); //int buttonState = digitalRead(BUTTONPIN); prevState = potVal; if (prevState == potVal) { if (analogRead(POTENTPIN) % 36 == 0) { //do nothing } else { myDFPlayer.play(potVal); Serial.println((String)"Playing song # " + potVal); delay(250); while ((potVal - map(analogRead(POTENTPIN), 0, 1023, 1, 35)) == 0) { delay(1); } } prevState = potVal; } }
Notes
– I could not include a gif/ video, featured image, or careful well-shot images of my device due to the complications of remote learning
]]>Overview – A music player that detects heart rate when user is ready to sleep and turns the volume of the music down when they fall asleep.
Music player in its natural habitat
A tiny speaker on each side of the product
hand-hold size, easy for user to place their thumb on
anatomy of the music player
Sleep Music Player from Meijie Hu on Vimeo.
Process
the first major turning point was how to accurately get the pulse. Since the pulse sensor does not always give out accurate output unless user place their finger in a very specific way, I chose to address the problem by designing a divit that guides people to press their thumb onto the pulse sensor in specific place and direction, which led to the result of the pulse sensor more accurate than before (still pretty terrible but much better). And through code I tried to eliminate all the abnormal values.
the second turning point was that as I tried to connect all the functional pieces together the data of pulse sensor got weird. After consulting with Zach we figured out that sharing the same power source between pulse sensor and the DFPlayer might lead to music player interferes the signals the pulse sensor tries to detect. Therefore, we solved the problem by using two seperate battery packs.
ideation sketch
writing pulse sensor code
testing ergonomic placement of the sensor
using a second battery pack for the speaker
making the form
test out speaker
Response
“Interesting project, i would suggest maybe making something more huggable. Maybe making it out of fur or something. I like the intention of the project.”
This would be an interesting next step. I guess the project would be even more interesting if it is a pillow. Free your hand and more intimate.
“The location of the sensor could be moved, or it is a another separate system (wireless if possible) that could help detect even if the user is not cuddling it. “
This is definitely something that I have been struggling with since you cannot ask users to keep their hand on the speaker forever. Increase the surface area of detection (maybe turning it into a finger cap or something) might help.
Self-critique
I really hope the project worked during presentation:( I think it has a lot of potential, given that I got every part to function and spent so much effort on the form, the final result could definitely be better. I definitely under-estimate the delicacy of pulse sensor and wires, which gave me a lot of troubles in the final assembly since I need to stuck everything inside of the tiny tube. Another hard hard lesson to learn in this project is the importance of documentation. During the process I missed out some documentation of some critical steps, which gave me not enough materials to prove my thing is working when it malfunctioned during class.
What I learnt
I really enjoy brainstorming the form and the process of making it, and the coding part is also relatively simple. The hardest part was assembling and handling the wiring. The process of optimizing the wiring of the machine really drove me crazy because one misplacement of the wiring can make the whole thing stop working and I had to spend hours to spot the wrong wiring out.
Next Step
Like what I mentioned above, turning it into a pillow would be fun, and probably less pain in fixing the wiring since there would be more room. Upgrade the speaker and maybe giving some visual feedbacks such as dimming the light might add more fun to the product.
Schematics
Code
/*TITILE: * Sleeping Music Player * * meijieh * * DESCRIPTION: * The code first check whether the current time reaches the alarm time, and * play songs when it is alarm time. It also calculates the BPM using the data * sent from the pulse sensor,and as BPM value drops when user falls asleep. * * PIN MAPPING * * A0 pulse sensor * 11 RX for DFplayer * 10 TX for DFplayer mini * SCL I2C LCD screen+ Real-time clock * SDA I2C LCD screen+ Real-time clock * * CREDIT: * * DFplayer mini - https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299 * RTC - RTClib library * BPM - https://www.xtronical.com/basics/heart-beat-sensor-ecg-display/ */ #define UpperThreshold 800 #define LowerThreshold 600 #include "Arduino.h" #include "SoftwareSerial.h" #include "DFRobotDFPlayerMini.h" #include <Wire.h> #include <RTClib.h> #include <LiquidCrystal_I2C.h> //LCD screen LiquidCrystal_I2C screen(0x27, 16, 2); //DFplayer SoftwareSerial mySoftwareSerial(10, 11); // RX, TX DFRobotDFPlayerMini myDFPlayer; void printDetail(uint8_t type, int value); bool isPlaying = false; int volume = 30; unsigned long taskTimer = 0; int interval = 1000; //detect pulse const int PULSEPIN = A0; bool BPMTiming = false; bool BeatComplete = false; long LastTime = 0; int BPM = 0; int BPMs[10]; int BPMIndex = 0; //running average const int numReadings = 2; // the index of the current reading int readings[numReadings]; // the readings from the analog input int readIndex = 0; int dif = 0; //alarm RTC_DS3231 rtc; char t[32]; char alarmT[32]; int alarmH = 0; int alarmM = 0; void setup() { mySoftwareSerial.begin(9600); Serial.begin(9600); Wire.begin(); //set time rtc.begin(); //rtc.adjust(DateTime(2020,3,3,3,27,0)); pinMode(BUT1PIN, INPUT); pinMode(BUT2PIN, INPUT); pinMode(PULSEPIN, INPUT); //LCD screen screen.init(); screen.backlight(); delay(1000); screen.clear(); screen.home(); screen.setCursor(0, 1); screen.print("ALARM:"); screen.setCursor(10, 0); screen.print("BPM:"); //DFPlayer Serial.println(); Serial.println(F("DFRobot DFPlayer Mini Demo")); Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)")); if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3. Serial.println(F("Unable to begin:")); Serial.println(F("1.Please recheck the connection!")); Serial.println(F("2.Please insert the SD card!")); while (true); } for (int i = 0; i < 10; i++) { BPMs[i] = random(40, 90); Serial.println(BPMs[i]); } } void loop() { //read time DateTime now = rtc.now(); sprintf(t, "%02d:%02d", now.hour(), now.minute()); screen.setCursor(0, 0); screen.print(t); //set alarm screen.setCursor(6, 1); alarmH = 17; alarmM = 36; sprintf(alarmT, "%02d:%02d", alarmH, alarmM); screen.print(alarmT); //play music if (rtc.now().hour() == alarmH && rtc.now().minute() == alarmM && !isPlaying) { Serial.println(F("DFPlayer Mini online.")); myDFPlayer.volume(volume); //Set volume value. From 0 to 30 myDFPlayer.play(1); //Play the first mp3 isPlaying = true; } if (myDFPlayer.available()) { printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. } // calc bpm long value = analogRead(A0); //Serial.println(value); if (value > UpperThreshold) { if (BeatComplete) { BPM = millis() - LastTime; BPM = int(60 / (float(BPM) / 1000)); BPMTiming = false; BeatComplete = false; } if (BPMTiming == false) { LastTime = millis(); BPMTiming = true; } } if ((value < LowerThreshold) & (BPMTiming)) { BeatComplete = true; } //show time if (millis() - taskTimer >= interval) { taskTimer = millis(); // get running average of BPM readings[readIndex] = BPM; readIndex = readIndex + 1; if (readIndex >= numReadings) { readIndex = 0; } if (BPM > 100 || BPM < 40) { Serial.print(BPM); Serial.println("invalid data1"); screen.setCursor(14, 0); screen.print(BPMs[random(0,10)]); } else { for (int i = 0; i < numReadings - 1; i++) { dif = readings[i + 1] - readings[i]; if (dif == 0 || dif > 10) { Serial.println((String) dif + "invalid data2"); screen.setCursor(14, 0); screen.print(BPMs[random(0,10)]); } else { Serial.println(BPM); screen.setCursor(14, 0); screen.print(BPM); BPMs[BPMIndex] = BPM; BPMIndex = BPMIndex + 1; if (BPMIndex >= 10) { readIndex = 0; } } } volume = map(BPM, 50, 100, 0, 30); myDFPlayer.volume(volume); } } } void printDetail(uint8_t type, int value) { switch (type) { case TimeOut: Serial.println(F("Time Out!")); break; case WrongStack: Serial.println(F("Stack Wrong!")); break; case DFPlayerCardInserted: Serial.println(F("Card Inserted!")); break; case DFPlayerCardRemoved: Serial.println(F("Card Removed!")); break; case DFPlayerCardOnline: Serial.println(F("Card Online!")); break; case DFPlayerPlayFinished: Serial.print(F("Number:")); Serial.print(value); Serial.println(F(" Play Finished!")); isPlaying = false; break; case DFPlayerError: Serial.print(F("DFPlayerError:")); switch (value) { case Busy: Serial.println(F("Card not found")); break; case Sleeping: Serial.println(F("Sleeping")); break; case SerialWrongStack: Serial.println(F("Get Wrong Stack")); break; case CheckSumNotMatch: Serial.println(F("Check Sum Not Match")); break; case FileIndexOut: Serial.println(F("File Index Out of Bound")); break; case FileMismatch: Serial.println(F("Cannot Find File")); break; case Advertise: Serial.println(F("In Advertise")); break; default: break; } break; default: break; } }
]]>
Overview
Smart Happy Plant Chamber is an Arduino project designed to assist growing plants by implementing automatic watering and lighting systems.
A digitally recreated picture of the completed model of chamber
The overview of a final chamber. The top has two holes each for a soil moisture sensor and motor hoses
The front (Holes for an LCD screen and three switches – each labeled)
Process images and review
I had two important decision points in my project each from the software and the physical structure.
In the beginning, I was naive about designing the details of software so the machine was designed to water plants every two days and turn on the light 24 hours even without setting the default plant for the project. However, I realize not only making the physical result and making it work, but also those software choices matter in the end, so I did some research on plant growths.
I set it as house garden plants so that the user can use it for a wide range of plants. I found out, with the sensor I used a SparkFun moisture sensor, the soil moisture level should be around 50%, which is done at the household by watering the plants every 3-4 days. With these in consideration, I designed the automatic watering system to water the plant every 3 days; however, If the soil moisture level is below 50% of the water threshold point anytime during 3 days, the system will water the plant until it gets to the point, and restart the 3 days.
Moreover for the lighting, I had problems with the amount and the duration of lighting. I thought the more light the better, and white light only would be enough. However, plants also have biorhythm, so they need some time to “sleep” and to be “awake” just like us. Thus, I implemented a real-time clock into the software, so the lighting system turns on during the daytime, specifically from 7 A.M., so if the natural light is below the threshold, then the LEDs would turn on, and after 7 P.M., the lighting system turns off completely. On top of that, plants need different frequency ranges of light, from red to blue to white, which I could improve by software, fortunately. Thus, I changed my software for WS2801 LED strip to have those RGB values alternatively for each LED unit such as RGBW RGBW RGBW. I learned that conducting a project on real life is not always about technical issues, but I need to be significantly knowledgeable about the area that I am developing the technologies, which in this case was basic biology on plant growths.
Regarding the physical structure of the project, I needed to break down the structure into the bottom and top because they serve different purposes. The bottom part mainly stores most elements such as circuits, Arduino Uno, motor. For aesthetic purposes, everything that does not need to be outside for functional reasons was stored inside the bottom. The top part is where users put the actual plant. Thus it needed a flat floor for the plant, and also a proper structure for LEDs, a light sensor, water pipes, and soil moisture sensor.
For the bottom part, I was planning to make it as a cuboid, and have an LCD screen and switches on one side. However, I realized if those elements are on a side of the cuboid that is perpendicular to the floor, it will not be user friendly. Thus, I changed the bottom part to be a trapezoid column, so that the front side can be tilted to 45 degrees and users can easily use it.
For the top part, I planned to have a cuboid frame like the initial image below, but I realize first, it is not good for plants to spray water on its leaves, and second, it’s unnecessarily complicated to design. Therefore, I changed it as having two columns that will have LEDs inside and a light sensor on a top, and directly put water pipes and a moisture sensor in a plant vase.
The original ideation drawing (It has a cuboid frame that carries watering and lighting systems, and does not have a bottom part that can store the rest of the model such as circuits)
The final model of chamber (The bottom is of trapezoid column with the tilted front. The top has two columns on each side for LED strips and a photoresistor)
The planar figure of the bottom box (a screenshot of dxf file used for laser cutting)
Discussion
In retrospect, the most exciting part of this project was ideation in the beginning when I brainstormed which problem I would want to address. I had multiple ideas of machines that can assist my life, but in the end, I narrowed it down to a few by thinking which problems are relatable to others just as to myself. One of the project ideas that I narrowed down in this way was “Ramen Cooker”. I personally cannot cook instant ramen, so I was planning to make a ramen cooker Arduino project in the beginning. I realized this is not a common problem people have, so it might not be relatable to many other people. Some people might even ask questions like “why would you need a ‘machine’ to cook ramen?” I think this thought process helped me decide which project to work on, and I ended up making a smart chamber that grows plants automatically. I got many positive comments about the project idea, especially on how it can be useful to people who cannot grow plants well like me such as “it’s a good idea to help people who struggle with growing plants” and “the idea is very useful for people who are not good at raising plants”. I think my effort in trying to find a problem I have but also what other people can accept and relate to have worked in this kind of project where I need to perform and introduce my project to others, which is the case of most projects. Therefore, for future projects, I would want to use the same thought processes in the ideation process.
However, one significant part I missed during the planning was time management. I thought, if I make the physical outcomes of the project with appropriate design, circuits, and codes that would be the end of this project, and if I want to check it, then I can just run it. It may be true for some other types of projects; however, unlike other projects, the ultimate functionality check for this project would be growing plants at least for a week. Just as one of the comments I got, “ [I] am not sure about the functionality because we didn’t test it with a plant…”, I could not see if the plants actually grow well with the machine in the end. From this trial and error, I have learned that whenever I start any project, I need to study the environment of a project and then decide how to evaluate the results in the end, and include that into the schedule. Moreover, I had an issue with overall time management as well. Finding necessary parts and wiring them into Arduino circuits took a few days more than I expected, so I had a delay in my whole project schedule; thus, in the end, I could not finish combining everything, which I regretted the most. This was also an issue of setting a precise timeline not too tight but with the considerations of some buffers.
There were also some changes that I needed to make due to the time restraint. Originally, I did plan to have multiple modes for different types of plants because they all require different water cycles and light intensity. If I get the opportunity to repeat but improve this project in the future, I am going to plan from the start to the end in the beginning of the project including a few days to revise the semi-final outcomes and also to evaluate the result in the way I wanted to use the machine which is growing a plant with it. Moreover, I would like to add different modes for different types of plants that people often have in their houses such as herbs, succulents, etc. I am going to add a button in the front panel that users can change the mode and set the plant they have, and will add some codes according to the changes.
Technical information
A hand-drawn schematic for the chamber
/* * Project2: Smart Happy Plant Chamber * Jeongyun Lee(jeongyul) * * Description: Following is a code for Smart Happy Plant Chamber. * For the lighting system, during the daytime, it turns on the LED depending on the amount of light determined by the photoresistor * value. During the night time, it always turns off the LED. However, when the LED manual switch is on, * no matter of the light value and the real time, it turns on the LED. * For the watering system, it evaluate whether the plant needs more water from the soil moisture sensor value, * and if the soil is dry, it turns on the water pump and vice versa. However, when the water manual switch is on, * no matter of the soil mositure value, it turns on the water pump. * * Collaboration: N/A * Reference: I used a part of "Adafruit Neopixel" Library "simple" example codes to initiate LED strip * * Pin mapping: * * pin / mode / description * -------/---------/------------ * 9 input water switch * 10 input LED switch * A0 input photoresistor * 4 output water pump */ #include <Wire.h> #include <LiquidCrystal_I2C.h> #include "RTClib.h" #include <Adafruit_NeoPixel.h> #ifdef __AVR__ #include <avr/power.h> #endif LiquidCrystal_I2C screen(0x27, 16, 2); const int WATERSWITCHPIN2 = 9; //water manual button const int LIGHTSWITCHPIN3 = 10; //light manual button const int WATERPIN = 4; //water pump pin const int LEDPIN = 6; //led strip pin const int MOISTUREPIN1 = A1; //moisture sensor analaog pin const int LIGHTSENSORPIN = A0; //light sensor pin const int NUMPIXELS = 24; const int moistThres = 511; //below 511 = dry, above 511 = enough water const int lightThres1 = 50; //below lightThres1: very deem const int lightThres2 = 100; //below lightThres2: mildly deem //current status for Automatic/Manual, for LED, and for water pump String curStat = "AUTO"; String LEDstat = "ON "; String WATERstat = "ON "; RTC_DS3231 rtc; Adafruit_NeoPixel pixels(NUMPIXELS, LEDPIN, NEO_GRB + NEO_KHZ800); void setup() { pinMode(WATERSWITCHPIN2, INPUT); pinMode(LIGHTSWITCHPIN3, INPUT); pinMode(LIGHTSENSORPIN, INPUT); pinMode(WATERPIN, OUTPUT); digitalWrite(WATERPIN, LOW); screen.init(); screen.backlight(); screen.home(); screen.print("tree happy!"); delay(7000); screen.clear(); //some codes from basic real time clock library #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) clock_prescale_set(clock_div_1); #endif #ifndef ESP8266 while (!Serial); // for Leonardo/Micro/Zero #endif pixels.begin(); Serial.begin(9600); //the error cases when the real time clock does not work if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while (1); } if (rtc.lostPower()) { Serial.println("RTC lost power, lets set the time!"); // If the RTC have lost power it will sets the RTC to the date & time this sketch was compiled in the following line rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // This line sets the RTC with an explicit date & time, for example to set // January 21, 2014 at 3am you would call: // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); } } //A helper function for turning on a side of LED strip void led1() { pixels.setPixelColor(0, pixels.Color(0, 0, 255)); pixels.setPixelColor(1, pixels.Color(0, 255, 0)); pixels.setPixelColor(2, pixels.Color(255, 0, 0)); pixels.setPixelColor(3, pixels.Color(255, 255, 255)); pixels.setPixelColor(4, pixels.Color(0, 0, 255)); pixels.setPixelColor(5, pixels.Color(0, 255, 0)); pixels.setPixelColor(6, pixels.Color(255, 0, 0)); pixels.setPixelColor(7, pixels.Color(255, 255, 255)); pixels.setPixelColor(8, pixels.Color(0, 0, 255)); pixels.setPixelColor(9, pixels.Color(0, 255, 0)); pixels.setPixelColor(10, pixels.Color(255, 0, 0)); pixels.setPixelColor(11, pixels.Color(255, 255, 255)); pixels.show(); } //A helper function for turning on the other side of LED strip void led2 () { pixels.setPixelColor(12, pixels.Color(0, 0, 255)); pixels.setPixelColor(13, pixels.Color(0, 255, 0)); pixels.setPixelColor(14, pixels.Color(255, 0, 0)); pixels.setPixelColor(15, pixels.Color(255, 255, 255)); pixels.setPixelColor(16, pixels.Color(0, 0, 255)); pixels.setPixelColor(17, pixels.Color(0, 255, 0)); pixels.setPixelColor(18, pixels.Color(255, 0, 0)); pixels.setPixelColor(19, pixels.Color(255, 255, 255)); pixels.setPixelColor(20, pixels.Color(0, 0, 255)); pixels.setPixelColor(21, pixels.Color(0, 255, 0)); pixels.setPixelColor(22, pixels.Color(255, 0, 0)); pixels.setPixelColor(23, pixels.Color(255, 255, 255)); pixels.show(); } //A helpter function for turning on/off the water pump void water() { delay(10); int moistVal = analogRead(MOISTUREPIN1); //when there is enough mositure in soil: water pump off if (moistVal >= moistThres) { WATERstat = "OFF"; digitalWrite(WATERPIN, LOW); } //when there isn't enough mositure in soil: water pump on else { WATERstat = "ON "; digitalWrite(WATERPIN, HIGH); delay(5000); } } //A helper function for turning on/off the LEDs void light(int hour) { int lightVal = analogRead(LIGHTSENSORPIN); //Daytime if (hour >= 7 && hour <= 16) { if (lightVal < lightThres1) { //when the light is very deem LEDstat = "ON "; led1(); } else if (lightVal >= lightThres1 && lightVal < lightThres2) { //when the light is mildly deem LEDstat = "ON "; led1(); led2(); } else { //when the light is enough LEDstat = "OFF"; pixels.clear(); } } //Night time else { //at night, the light is always off digitalWrite(LEDPIN, LOW); LEDstat = "OFF"; } } void loop() { DateTime now = rtc.now(); //manual mode buttons int waterState = digitalRead(WATERSWITCHPIN2); //water manual on/off int lightState = digitalRead(LIGHTSWITCHPIN3); //water manual on/off //Setting modes //Water: manual, LED: manual if (waterState == HIGH && lightState == HIGH) { curStat = "MANU"; digitalWrite(WATERPIN, HIGH); digitalWrite(LEDPIN, HIGH); LEDstat = "ON "; led1(); led2(); } //Water: manual, LED: auto else if (waterState == HIGH && lightState == LOW) { curStat = "MANU"; digitalWrite(WATERPIN, HIGH); light(now.hour()); } //Water: auto, LED: manual else if (waterState == LOW && lightState == HIGH) { curStat = "MANU"; water(); digitalWrite(LEDPIN, HIGH); led1(); led2(); LEDstat = "ON "; //delay(500); } //Water: auto, LED: auto else { curStat = "AUTO"; water(); light(now.hour()); } //LCD screen print screen.clear(); screen.setCursor(1, 0); screen.print("Now: "); screen.setCursor(6, 0); screen.print(now.hour()); screen.setCursor(8, 0); screen.print(":"); screen.setCursor(9, 0); screen.print(now.minute()); screen.setCursor(12, 0); screen.print(curStat); screen.setCursor(1, 1); screen.print("L: "); screen.setCursor(4, 1); screen.print(LEDstat); screen.setCursor(7, 1); screen.print(" W: "); screen.setCursor(12, 1); screen.print(WATERstat); Serial.print(analogRead(LIGHTSENSORPIN)); }]]>
Final prototype.
This project aims to track the number of hours I am in my studio, as the 24 rods on the top floor fall to the bottom floor for every hour that I am sitting in my studio chair.
[high-quality images unavailable. Prototype does not work, so video is also unavailable.]
Sketches: How do you move 24 different parts with as little components as possible?
After consulting with Zach about my ideas in the initial check-in phase, I decided to go forth with making a physicalized data of my hours spent in my design studio. I sketched out numerous ideas to understand how to represent 24 hours with as little components as possible to reduce the number of “moving parts.”
Idea 1: Servo moves panel in linear motion at an increment every hour. If in studio, panel falls. If out of studio, panel stays on top floor. Problem: how do I differentiate between the two phases?
Consideration of how to differentiate between on-off phase for idea 1.
Idea 2: Ribbon wrapped around two servos, servos rotate every hour I am in studio, as ribbon reveals the number of hours I have been there. A glorified timer.
Iteration of Idea 2.
Another iteration of idea 2.
Redefining parameters. Distance sensor, 24 hours and on-off state had to be used.
Final idea sketch
To explain how the project works: Top level contains 24 rods. Middle panel rotates every hour, with a small hole that opens and closes depending on whether I was in studio at that hour. If in studio, the rod falls to the bottom floor through the hole. If not, the rod remains on the top floor, as the hole never opens up.
Refined sketch. Figured out how to hold top panel in place
Decision 1:
A “flat” hole closer that can never open up, as the panel blocks the wooden piece from swiping down.
Hole closer: the anguish of making a flat surface that also doesn’t interfere with falling rods
The hole closer was critical to my design. Its essential function is to open up the hole when the ultrasonic ranger senses that I am in the seat, in order to let the rod drop onto the bottom floor.
I learned through trial and error the following rules to maintain the following rules in making the hole closer work.
The problem was that I could not figure out how to fulfill both rules when positioning the servo in the right place. When the servo was located to the right of the hole, the wooden piece would be perfectly flat in the “closed” phase, but the rod would get stuck on the piece in the “open” phase.
Hole closer that closes flatly, but interferes with a falling rod in “open” state.
To solve this problem, when the servo was located towards the front of the hole (closer to the center), both rules would be satisfied, but the hole would not even open up in the first place due to the big size of the wooden piece.
Rotation is blocked due to position of the servo
I tried using soft pipe cleaners, rather than a hard wooden plane to solve the issue. The softness of the furs would reduce the “bumps” in either phase, even if not positioned entirely correctly. This did not work, however, as the pipe cleaner was too malleable, letting the rod go through as shown below:
Rod goes through the pipe cleaner attempt, causing the whole system to halt.
In the end, nothing really worked to solve this problem. Another fabrication method would have been better to open and close the hole, but as I had already cut out the piece on laser cutter, I could not find another way around it.
Decision 2:
Continuous servo motor → stepper motor for flatter surface and greater precision
A continuous servo motor was initially used to rotate the middle plane, but there were two problems:
Servo motor unable to maintain a flat surface, making the rods difficult to pass through the holes
I then opted for using the stepper motor, which gave me a much greater precision. I blew up three A4988 chips in the process, however. The first time, I had circuits that flipped the ground and 5v. I couldn’t figure out the problems for the second and third times though. I lowered the voltage to 10 volts, rather than the maximum 12 volts for the stepper motor, just in case I don’t blow up another board.
A4988 chip used to interface with the stepper motor. I ended up blowing up three of these.
Stepper motor caused the plane to rotate uncontrollably due to the high vibration.
Rotation problem fixed by bolting the plane down to the motor.
Stepper motor gave me greater control over hole alignment. I figured out on code how to rotate the plane by 360/24 degrees. The hole alignment now worked from plane-to-plane.
Other moments in process
Rod falls through the hole well. One of few successful tries.
Initial, initial prototype. Tested out string gear for Idea 1.
Tested out how rods would fall if middle plane was moved.
Realization: Even the slightest tilt (as shown) can cause rods to fall out of place. The top plane had to be revolted to fix this error.
Final prototype. Panels are held together perpendicularly through thick wooden poles
Response
I was quite frustrated leading up to the final critique day, as I was staying up for three whole nights in the physical computing lab trying to make the project work. I had spent so long in the earlier phase trying to design how to represent 24 different data points using as few components as possible, that when I landed on a sketch that worked in theory, I was determined to follow through with my plan.
What I should have realized early on is that my prototype plan had way too many mechanical problems. I thought I had found a simple solution to my seemingly unsolvable problem, but it turned out that even the “simple” solution had way too many parts to control.
For one, it required a significant amount of precision in making sure each of the 24 rods to fall through the holes in the desired manner. Not only that, making the hole-closer mechanism was excruciatingly difficult, but I could not turn back on the design late into the prototype phase, as I had already laser cutted the piece for the specific purpose I had designed for. As a result, my piece never ended up working for the final crit day.
I got some feedback from my peers during the class critique to tackle those problems. Lots of advice were given to help with the problem of the rod getting stuck between the wooden planes, as well as making sure they fall into the right holes in the bottom layer.
“Nice concept, where you are able to visualize how long you have worked plays with your guilt. For the wood stick falling issue, you could use a tube that will lead the wood stick to certain place, so other factors like slight movement of wind doesn’t make the stick be stuck in the hole. Also place a little tray or holder to catch the wood sticks. “
“The project aesthetic is really nice. Everything looks precisely measured which makes it look really good! Maybe try to have a function that wiggles around until the sticks fall”
Looking back on the process now, I realize that I should have been less ambitious with my approach, even if it made sense in “theory” and in my sketches. Rather than simulating my project in my brain that clearly defied all rules of physics, I should have changed my project plan soon as I found myself working on a minor problem for 8 straight hours.
I thought that my project was very simple due to the little amount of effort it takes on code, but it relied too much on physical fabrication for my idea to work well. One of the biggest problems was that I had no way of knowing how the rods (the moving part that has no direct ties to any Arduino component) would move. I’m thinking that maybe I should have taken the time to build the gear mechanism on Fusion360, where two gears can control the x and y direction of a servo, so that I have full control over any moving part that requires precision. From that mechanism on, I could have built a more manageable project by, say, just having the servo push 24 panels like a domino, where each panel has no dependency on the entire system. (Some rods currently get stuck between panels, stopping the middle plane from rotating to the next hole.)
Also, I could have been more open to a digital representation of time (ex: using LED bulbs) once I realized that controlling the rods became too difficult, and focused more of my attention on controlling the distance sensor on code as well as covering up all the electronics. The problem seems to be that I made no compromises to make my project more manageable, resulting in 3 all-nighters and a failed project at best.
I’m not sure if going forward with my current model is the best idea. I was pretty distraught for most of my Spring break thinking of how unsuccessful my project was, so I am mentally exhausted from just looking at my project now. The idea of keeping track of my hours at studio through data physicalization is promising, but perhaps not through 24 different rods. I might give it another go when I regain more confidence in physical computing through other, more plausible projects.
Schematic
Code submission
/* Data Physicalization of Studio Hours * Elizabeth Han (yoonjuh) * * Description: * This code achieves two functions in the overall project. First, it changes * the degrees of a servo depending on the distance sensor reading in order to * close the hole in the middle panel. Second, it rotates the middle plane by * 360/24 degrees every hour (currently set to 3 seconds for testing purposes) using the * stepper motor. * * Pin mapping: * pin / mode / description * ------/--------/--------------------------- * 12 input ultrasonic sensor trigger * 11 input ultrasonic sensor echo * 10 output servo * 7 output stepper motor steps * 8 output stepper motor direction * * References: * Stepper motor: https://courses.ideate.cmu.edu/60-223/s2020/tutorials/stepper * TaskTimer: https://courses.ideate.cmu.edu/60-223/s2020/tutorials/code-bites#blink-without-blocking */ #include <NewPing.h> #include <Servo.h> #include <AccelStepper.h> #include <Wire.h> #define TRIGGER_PIN 12 // Arduino pin tied to trigger pin on the ultrasonic sensor. #define ECHO_PIN 11 // Arduino pin tied to echo pin on the ultrasonic sensor. #define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm. NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance. //set up servo Servo SwingServo; const int SERVOPIN2 = 10; //set up step motor const int STEP_PIN = 7; const int DIR_PIN = 8; AccelStepper myMotor(1, STEP_PIN, DIR_PIN); int steps = 0; int pos = 1; //set up timers unsigned long taskTimer = 0; unsigned long taskTimer2 = 0; unsigned long taskTimer3 = 0; const int INTERVAL = 4000; //check sitting as frequently as possible const int SWINGINT = 4000; const int MOTORINT = 3000; //1 hour const int WIGGLEINT = 500; int counter = 0; int prevCounter = -1; bool sitting = false; int plus = 0; int minus = 0; void setup() { Serial.begin(9600); SwingServo.attach(SERVOPIN2); myMotor.setMaxSpeed(6000); // measured in steps per second myMotor.setAcceleration(10000); // measured in steps per second squared } void loop() { // distance sensor --> hole closer //determines if person is sitting or not in seat, opens up hole int distance = sonar.ping_cm(); if (millis() - taskTimer >= INTERVAL) { if (distance <= 10) { SwingServo.write(0); sitting = true; } else { SwingServo.write(90); sitting = false; } taskTimer = millis(); } //swing loop //wiggles board to make sure rod goes down when in sitting state if (millis() - taskTimer2 >= SWINGINT) { if (sitting == true and prevCounter != counter) { Serial.println("swing"); plus = steps + 1; myMotor.moveTo(plus); myMotor.run(); delay(1000); Serial.print("swing1 current position: "); Serial.println(myMotor.currentPosition()); minus = steps - 1; myMotor.moveTo(minus); myMotor.run(); delay(1000); plus = steps + 1; myMotor.moveTo(plus); myMotor.run(); delay(1000); Serial.print("swing1 current position: "); Serial.println(myMotor.currentPosition()); minus = steps - 1; myMotor.moveTo(minus); myMotor.run(); delay(1000); Serial.print("swing2 current position: "); Serial.println(myMotor.currentPosition()); } prevCounter = counter; taskTimer2 = millis(); } //stepper motor loop //turns every 360/24 degrees int degreesPerHour = 200 / 24; //200 steps in motor, therefore 200/24 if (millis() - taskTimer3 >= MOTORINT and sitting == true) { counter += 1; if (counter == 24) { pos *= -1; counter = 0; } steps += degreesPerHour * pos; myMotor.moveTo(steps); taskTimer3 = millis(); Serial.print("counter: "); Serial.println(counter); Serial.print("current position: "); Serial.println(steps); } myMotor.run(); }
]]>
This is a desktop stretch coach to help relieve my wrists’ symptoms of Carpal Tunnel Syndrome.
The card types alternate every 5 seconds: stretch card, and lifestyle reminder card. While I continue a stretch or take a break before the next stretch, the lifestyle reminder card will appear, helping me remember the good habits I should have for my wrists. The top card is held up by a white bar right under the ceiling of the box, so that it is stopped and does not collapse forward.
Device on, displaying the first stretch cards of the routine. The display counts from 1 to 5 every time a card flips.
5-second timer display and switch.
Connection of the cards to the mounted stepper motor. The cards are loosely held through each hole of the wooden circle with pins, and the circle is tightly connected to the motor. The motor turns the circle forward.
Device with the back board slid open. Back board is detachable for if the cards need replacement or if there is an internal problem.
Switch turned off when device is not in use. The whole device shuts down when the switch is turned off.
Throughout the project, there were some main redirection stages that I took to make the project more feasible or effective.
One part was determining how to display the 5-second timer. Initially, I thought about using an LCD screen, because that was the part that I was most familiar with. Then, I realized that it may not be necessary to use an LCD screen if I am just displaying 5 numbers, so I though about using 5 LEDs, that would visualize the numbers by turning on and off. Finally, I used a 7-segment display because this was a more concise and clearer way.
Initial plan: LCD screen.
Next plan: using 5 LEDs.
Final plan: using a 7-segment display.
Extending the 7-segment display connection out of the breadboard to put on the front plane of the box.
Secondly, another important part of the process was figuring out how to create the motor and cards move correctly. When I first brainstormed, I had my wooden circles much bigger and the rectangular prism quite big. After prototyping with chipboard and 3-D modelling, and knowing that I will be using 8 cards of specific sizes, I changed the dimensions. I could therefore avoid the whole structure becoming too big, or the movement becoming unnecessarily big, which would require the mechanism to be structurally very sturdy.
Furthermore, in the beginning, I wanted to have a dowel loosely holding the left side wooden circle, and the dowel tightly fitting into a plane that would also tightly fit inside the box. However, I just attached the dowel directly to the inside of the box.
Initial sketch
Larger-dimensions central wooden structure, and dowel fixed into a plane in the initial prototype.
Central wooden structure with refined dimensions.
Assembly.
Lastly, a software challenge was when I had to utilize the non-blocking routine method for the timer instead of using a delay, so that it wouldn’t be a hard-coded fix, but a more precise system.
Initial attempt using delay
Through this project, I could have an enjoyable experience of making my first individual physical computing project , and it gave me an opportunity to have thoughts and explorations that were meaningful to myself as a product designer. However, I was not entirely satisfied with the final physical product. When I chose this topic and brainstormed, I did not foresee some critical challenges that would take a relatively long time to resolve carefully. I think that some of the solutions that were not the best were well-pointed out during the in-class critique.
Some comments about my product was that it would be good “to make the current features more precisely,” or consider the user experience more because “it currently lacks a little bit of utility.” Although this device is for myself, I think some aspects are definitely not coherent enough. For example, the lifestyle reminders do not entirely make sense. It could have been clearer to just have stretch cards going in a loop, and perhaps incorporating the reminders as a different feature. Also, it could be better if I can make a setting in which I would go back to the first stretch of the loop, as opposed to just starting at whichever stretch the motor is showing whenever I turn the switch on.
A positive comment from one of my peers was that “the design of the cards moving is really fun and visually engaging.” Another feedback was that the design is “very neat.” I appreciate these because I also think I had a relatively successful result in getting the mechanism to work, and crafting the overall product. The mechanical part to get the movement working was not challenging for me. Nevertheless, I could have done a better job on the physical aspects if I had some more time to focus on them, after I had the software problems resolved. For instance, I ended up having much more wires than I had expected, so I had to cram in all the parts into the box, which I had laser-cut already in specific dimensions. Also, I can see some inner roughly-made parts from the outside through the window. If I were to make another iteration, I would definitely make the casing more carefully, and perhaps make the cards replaceable and more sturdily connected to the motor.
For me, taking initial attempts at software solutions was difficult for both the timer display part and the stepper motor part. I was able to learn many things as I asked for help from others, and started off with base code references from similar Arduino problems. As I understood other examples and pieces of advice, I could make changes of my own or write code that I could eventually get to work after some trial and error.
/* Carpal Tunnel Syndrome Stretch Coach * Youie Cho (minyounc) * * Description: * The code makes a card-flipping mechanism that flips various stretching guide * cards that help improve the symptoms of Carpal Tunnel Syndrome.The 7-segment * display helps the user to keep track of time for each stretch. * * Pin mapping: * pin / mode / description * ------/--------/--------------------------- * 2-9 output 7-segment display segments * 12 output motor steps * 13 output motor direction * * References: * AccelStepper Library by Mike McCauley * Split flap display mechanism :https://imgur.com/a/0VAMZ * Blink without blocking coding: 60-223 course page * Stepper motor wiring and coding: 60-223 course page * 7-segment display timer wiring and coding: * https://create.arduino.cc/projecthub/arduino_uno_cool/create-a-7-segment-display-254ebe */ #include <AccelStepper.h> // Set up library for motor movement const int STEP_PIN = 12; // A4988 "STEP" pin wired to pin 12 const int DIR_PIN = 13; // A4988 "DIRECTION" pin wired to pin 13 long second = 1000; // variable to represent 1000 milliseconds = 1 second int pos = 0; // variable to store motor position unsigned long timer = 0;// variable to keep track of passing time const int wait = 5000; // number of milliseconds to wait between motor movements AccelStepper myMotor(1, STEP_PIN, DIR_PIN); // create an AccelStepper motor object bool dp = false; // boolean definition for timer (7-segment display) // Setup for timer numbers 0 to 5 void disp(int num = -1, boolean dp = 0) { digitalWrite(9, !dp); if (num == 0) { digitalWrite(2, 0); digitalWrite(3, 0); digitalWrite(4, 0); digitalWrite(5, 0); digitalWrite(6, 0); digitalWrite(7, 0); digitalWrite(8, 1); } else if (num == 1) { digitalWrite(2, 1); digitalWrite(3, 0); digitalWrite(4, 0); digitalWrite(5, 1); digitalWrite(6, 1); digitalWrite(7, 1); digitalWrite(8, 1); } else if (num == 2) { digitalWrite(2, 0); digitalWrite(3, 0); digitalWrite(4, 1); digitalWrite(5, 0); digitalWrite(6, 0); digitalWrite(7, 1); digitalWrite(8, 0); } else if (num == 3) { digitalWrite(2, 0); digitalWrite(3, 0); digitalWrite(4, 0); digitalWrite(5, 0); digitalWrite(6, 1); digitalWrite(7, 1); digitalWrite(8, 0); } else if (num == 4) { digitalWrite(2, 1); digitalWrite(3, 0); digitalWrite(4, 0); digitalWrite(5, 1); digitalWrite(6, 1); digitalWrite(7, 0); digitalWrite(8, 0); } else if (num == 5) { digitalWrite(2, 0); digitalWrite(3, 1); digitalWrite(4, 0); digitalWrite(5, 0); digitalWrite(6, 1); digitalWrite(7, 0); digitalWrite(8, 0); } else { digitalWrite(2, 1); digitalWrite(3, 1); digitalWrite(4, 1); digitalWrite(5, 1); digitalWrite(6, 1); digitalWrite(7, 1); digitalWrite(8, 1); } } void setup() { // Pin setup for timer pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT); Serial.begin(9600);// Begin serial monitor to monitor motor position (in steps) // myMotor settings to control motor behavior myMotor.setAcceleration(100000); // motor does not accelerate myMotor.setMaxSpeed(100); // maximum speed, measured in steps per second // Motor movement setting as 25/200 steps each time, so that it would go through // 8 positions to complete a revolution myMotor.moveTo(pos); pos = pos - 25; } void loop() { // Move the motor 25 steps every second if ((millis() - timer) > wait){ Serial.println(pos); myMotor.moveTo(pos); pos = pos - 25; timer = millis(); } // Call timer system function displayDigits(); myMotor.run(); } // Function to create a timer system void displayDigits() { // Define the sec variable using millis int sec = millis() / second; myMotor.run(); disp(sec % 5 + 1, 0); // When it reaches 5 seconds, it goes back to 1 second printDigits(sec); Serial.println(); } // Function setting up the decimal parameter for second values void printDigits (byte digits) { Serial.print(digits, DEC); }
]]>
At two preset time, the alarm robot wanders around randomly and repeats a melody until it is placed next to the RFID cards in bathroom or on bed.
This is the behavior of the robot when it is reminding the user to take a shower or go to bed. A USB power bank is used to provide power as I found out 4 AAA batteries do not provide enough current to support two motors and two speakers playing melody at the same time. However, the batteries do provide enough current when the speakers only vibrate constantly.
This shows a closer look at the working device. When the appropriate RFID card is presented, the device stops moving and playing melody.
The elevation view of the device. The RFID reader sticks out so that it can get closer to the RFID card when it is reading.
The front view of the device. The parts are arranged in a way to mimic the “face” of a robot, with the speakers being the “eyes” and the batteries case being the “nose”.
I have the problem of procrastinating when it comes to going to sleep. When I am busy during the week, I tend to stay up late working. When I am not as busy, I still go to bed late by watching all kinds of contends. Moreover, I tend to take shower right before I go to bed, which makes the bed time even latter. I tried to set alarm on my phone to remind myself the time for shower and sleep, but it does not work well as I usually just stop the alarm. Therefore, I wanted to build an assistive device that really grabs my attention and bring me to physical places at appropriate time.
The original design is a small square car that carries two speakers, an electromagnetic sensor, a servo motor, and two continuous servo motors. Magnets will be placed in bathroom and on bed, and the electromagnetic sensor is there to detect the change of EM field, which indicates the physical locations.
The supporting wheel is added to maintain the balance of the body of the device as well as to help the device turn left or right driven by a servo motor. The supporting wheel is free to rotate like the wheel of a chair.
During prototyping, I changed the continuous servo motors to DC motors because they supply much more power. I also realized that it is very difficult to attach a wheel to the servo motor, and it is much easier to use a ball roller transfer as a supporting wheel. I was lucky enough to find a ball roller transfer that has the height similar to the radius of a toy tire, which the base of the device can sit on.
Then I went on to build the circuit on a breadboard. The wiring part is not bad because there are resources on the course website or online that are helpful.
However, I had some trouble writing codes for parts that I was not familiar with. For example, I had to learn how to use the library for the RFID reader MFRC522 to write functions that check whether the unique “ID” of the RFID card is the one of the card in the bathroom or on the bed. However, I had the most trouble figuring out how to make the motors and speakers perform two different sequences of actions spontaneously. Then I realized I can solve it by using the blink-without-block tactic. But instead of modifying boolean variables, I have to make a “time map” and assign tasks to the parts in each time interval. However, this is not the best approach when the sequence of actions and hence the time map become long. I will introduce another approach in latter section.
In the function be_annoying, the timer diff_motor and the motor_timemap checks determine which motion to perform.
Because the circuit of the prototype is very messy and clumpy, I decided to solder the wires and components onto a circuit board so that they can fit onto a small robot car. It turned out to be a very time-consuming process but it makes the circuit clear, strong, and secure.
The wires and parts are all soldered onto the circuit board. To make more room from the board, I have cut the legs of the transistor on top.
I then redesign the body of the robot to fit the dimension of the circuit board and the parts. The front part of the vehicle is not covered up so that the speakers and the RFID reader to extend out for better performance. The circuit is covered by the “shields” that insert into the groves on the side boards. The idea of the “shields” are inspired by the design of the back of some Lamborghini.
The shield above the back window. https://www.motor1.com/news/78593/after-50-years-the-lovely-lamborghini-miura-is-back/
The sketch of the components. Notice that the grooves on the curved side of the right components are for the shields.
Unfortunately, the “shields” could not be used because the height of the Arduino pro mini is taller than expected due to the soldering on the circuit board. In other words, the soldering tips at the back of the circuit board raise the components up, which get into the way of the shields. Unfortunately, because I was not able to use the laser cutter again before the final critique, I had to abandon the idea and decided to make a pair of wings instead. As a result, the device has a raw look without enclosure.
Finally, the device was built by gluing the parts together and mounting the ball roller transfer onto the base with screws and nuts.
Because the device was too annoying when the speakers only vibrate constantly to make loud noises, I decided to play a song instead. With two speakers, I can play two melodies at the same time.
The lists of notes and the lists of beats. Each note corresponds to a specific frequency for the tone() function.
With a list of notes and a list of beats, the speaker can go through the melody using the blink-without-block technique. However, because there are substantial notes and beats, I could not use “if” anymore as it gets too tedious. As a result, a loop was written to generate the time-map by looping through the list of beat and add the previous beats up.
Finally, a global variable “counter” determines which note to play and what beat it has.
One of the feedback suggested to build “a cover” to hide the device’s components while another suggested putting “a shell” and bunch of screws” around the battery . I really like these feedback because I will have a less chance to hack the system when I am not aware of the circuit and the batteries are difficult to remove. Instead of removing the batteries, now the only option to make the machine stop is to to the designated physical place to get the RFID card. As a result, the device can better serve the original purpose of making me take shower or go to sleep.
There are also feedback about “adding a flash light” and adding an interface for “adjustable time for the alarm”. With a flashlight, the robot car can grab more attention, especially at night. And with an interface to adjust the time, the device can be more user-friendly and convenient to use. Therefore, I think those are great feedback and potential improvement to the device.
I am pleased about the function that the device can perform. All the parts, including the RFID reader and the motors, work better than I expected. As a result, the device can perfectly perform its intended job. However, I am not satisfied about the design of the final product. As mentioned before, the robot looks rather raw without cover and the wires of the parts still look kind of messy.
One of the biggest lesson I learnt is that I have to make a clear schedule before starting the project. In this project, I only have a rough schedule my mind and as I spent more and more time on the technical issues, I had to push back the task of designing until the very end. And it is a very bad idea to push anything to the very end. Instead, I should have a schedule that assigns equal time to technical and designing tasks. Moreover, if there is a technical problem that is very time consuming, I should stick to the schedule and move on. If I have more time, I can come back to the problem and finish the feature. I also learnt that it is challenging to write code for arduino if I am not familiar with the language of C. But it is also not too bad because there are many references that I can rely on.
If I was to build another iteration, I would design a cover for the robot that the speakers and RFID reader are fixed at the surface. I would use a USB power bank instead of batteries to power the robot so that enough current would be supplied. I would use the design of the “shield” to cover up the components. I would add a LCD and a rotating switch to make an user interface for adjusting time, changing the color of the flashlight, and changing songs.
/*Sleep Time Reminder * Zerui Huo * The code makes two motors spin and two speaker vibrate at a preset time until the RFID reader detects the desired RFID card. *Pin mapping: pin | mode | description ------|--------|------------ 2 OUTPUT The pin that sends pwm signal to the motor driver and controls tire 1 3 OUTPUT The pin that sends pwm signal to the motor driver and controls tire 1 4 OUTPUT The pin that sends pwm signal to the motor driver and controls tire 2 5 OUTPUT The pin that sends pwm signal to the motor driver and controls tire 2 6 OUTPUT The pin that is connect to transistor to control speaker 1 7 OUTPUT The pin that is connect to transistor to control speaker 2 9 SDA pin for the RFID reader 10 RST pin for the RFID reader 11 MOSI pin for the RFID reader 12 MISO pin for the RFID reader 13 SCK pin for the RFID reader A4 SDA pin for the real time clock A5 SCL pin for the real time clock VCC The pin for power to go into the Arduino pro mini *The code in the functions set_motor_pwm and set_two_motors_pwm are inspired by the website https://courses.ideate.cmu.edu/16-223/f2016/text/ex/Arduino/DRV8833-motor-driver/DRV8833-motor-driver.html. The function getID is from the website https://lastminuteengineers.com/how-rfid-works-rc522-arduino-tutorial/. When writing the code to set the time in the real time clock, I refer to https://create.arduino.cc/projecthub/MisterBotBreak/how-to-use-a-real-time-clock-module-ds3231-bc90fe. The music score of the song, Il Vento D'Oro (Golden Wind), is from https://musescore.com/winthos/scores/5366398 by Winthos. The the pitch values are from https://www.arduino.cc/en/Tutorial/ToneMelody?from=Tutorial.Tone and are written by Brett Hagma. */ #include "pitches.h" #include "SPI.h" #include "MFRC522.h" #include <Wire.h> #include <ds3231.h> //Naming all the pins to their function const int motorA1 = 2; const int motorA2 = 3; const int motorB1 = 4; const int motorB2 = 5; const int speaker1 = 6; const int speaker2 = 7; //Define the two pins for the RFID #define pinSDA 10 #define pinRST 9 //Setting the intervals for the all the timers unsigned long Motortimer = 0; unsigned long Speakertimer1 = 0; unsigned long Speakertimer2 = 0; unsigned long Clocktimer = 0; const int Clockwait = 5000; //Setting up the strings to check for the RFID reader byte readCard[4]; String Bathroom_ID = "A06EC9B"; //Unique ID for the RFID card that will be placed in bathroom String Bed_ID = "31BD82FC"; //Unique ID for the RFID card that will be placed onto the bed String tagID = ""; //"Link" the pins to the RFID's SDA and RST MFRC522 mfrc522(pinSDA, pinRST); //For the realtime clock struct ts t; //Defining the constants for the speakers to play melody //notemap for speaker 1 and speaker 2 int note1[] = {NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, NOTE_B3, NOTE_D4, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_F4, 0, NOTE_E4, 0, NOTE_D4, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, NOTE_A3, 0, NOTE_B3, 0, NOTE_D4, 0, NOTE_B3, 0, NOTE_FS3, NOTE_A3, NOTE_B3, NOTE_B3, NOTE_B3, 0, NOTE_A3, NOTE_B3, }; int note2[] = {NOTE_B1, NOTE_B1, NOTE_B1, NOTE_B1, NOTE_B1, NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_D3, 0, NOTE_B2, 0, NOTE_FS2, NOTE_A2, NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_F3, 0, NOTE_E3, 0, NOTE_D3, NOTE_A2, NOTE_B2, NOTE_B2, NOTE_B2, NOTE_A2, 0, NOTE_B2, 0, NOTE_D3, 0, NOTE_B2, 0, NOTE_FS2, NOTE_A2, NOTE_B2, NOTE_B2, NOTE_B2, 0, NOTE_A2, NOTE_B2, }; //beatmap for each notes for speaker 1 and speaker 2. 8 means eighth note, etc. int beat1[] = {8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 8, }; int beat2[] = {1, 1, 1, 2, 2, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 16, 16, 16, 8, }; //Determining the length of the list of beat and creat empty timemaps for the speakers const int length_of_beat1 = 155; const int length_of_beat2 = 1; int speaker_timemap1[length_of_beat1]; int speaker_timemap2[length_of_beat2]; //Note counter indicate which note the speakers are on. They are set to 0 because array start from 0 and in such way they are more convenient to use latter. int counter1 = 0; int counter2 = 0; //The following are written supplemental functions //RFID: //Funtion to access the NUID and put the first 4 bytes (8 digits) into the string "tagID" boolean getID() { //Verify if any card is presented if ( ! mfrc522.PICC_IsNewCardPresent()) { return false; } // Verify if the NUID has been readed if ( ! mfrc522.PICC_ReadCardSerial()) { return false; } tagID = ""; //A loop that put the first 4 bytes of the unique ID of the card into the variable tagID for ( uint8_t i = 0; i < 4; i++) { tagID = tagID + (String(mfrc522.uid.uidByte[i], HEX)); } tagID.toUpperCase(); mfrc522.PICC_HaltA(); // Tells the RFID readert to stop reading. return true; } //Motors: //Function to give direction and speed to one of the motor void set_motor_pwm(int pwm, int input_1, int input_2) { if (pwm < 0) { digitalWrite(input_1, -pwm); analogWrite(input_2, LOW); } else { digitalWrite(input_1, LOW); analogWrite(input_2, pwm); } } //Function that gives direction and speed to both motor using the previous function void set_two_motors_pwm(int pwmA, int pwmB) { set_motor_pwm(pwmA, motorA1, motorA2); set_motor_pwm(pwmB, motorB1, motorB2); } //Speakers: //Funtion that make the speaker play the note according to its notemap and timemap void play_note(int speaker, int time) { //Determine which speaker and therefore which counter to modify if (speaker == 6) { //The start is a speacial case if (counter1 == 0) { if (time < speaker_timemap1[counter1]) { tone(speaker, note1[counter1]); } if (time > speaker_timemap1[counter1]) { counter1 = counter1 + 1; } } //if time exceeds the last element of the timemap, reset the counter and play the melody from start if (time > speaker_timemap1[length_of_beat1 - 1]) { counter1 = 0; Speakertimer1 = millis(); } //Again, (2000 / beat1[thisbeat - 1]) * 0.3 is the time gap for distinguishing between notes if (time > speaker_timemap1[counter1 - 1] + (2000 / beat1[counter1 - 1]) * 0.3 && time < speaker_timemap1[counter1]) { tone(speaker, note1[counter1]); } //When the speaker finish playing the current note if (time > speaker_timemap1[counter1]) { counter1 = counter1 + 1; } } if (speaker == 7) { //The start is a speacial case if (counter2 == 0) { if (time < speaker_timemap2[counter2]) { tone(speaker, note2[counter2]); Serial.println("Speaker 2 starts"); } if (time > speaker_timemap2[counter2]) { counter2 = counter2 + 1; Serial.println("Speaker 2 counter 2"); } } //if time exceeds the last element of the timemap, reset the counter and play the melody from start if (time > speaker_timemap2[length_of_beat2 - 1]) { counter2 = 0; Speakertimer2 = millis(); Serial.println("Speaker 2 end"); } //Again, (2000 / beat1[thisbeat - 1]) * 0.3 is the time gap for distinguishing between notes if (time > speaker_timemap2[counter2 - 1] + (2000 / beat2[counter2 - 1]) * 0.3 && time < speaker_timemap2[counter2]) { tone(speaker, note2[counter2]); Serial.println("Speaker 2 next note"); } //When the speaker finish playing the current note if (time > speaker_timemap2[counter2]) { counter2 = counter2 + 1; } } } //Function that controls what happen when it's time to shower/sleep void be_annoying() { //how long has passed since the start of the cycle int diff_motor = millis() - Motortimer; int diff_speaker1 = millis() - Speakertimer1; int diff_speaker2 = millis() - Speakertimer2; //the timemap of actions for motors int motor_timemap[] = {3000, 5000, 7000, 10000}; //motors' sequence of motions //Motion1 if ( diff_motor <= motor_timemap[0] ) { set_two_motors_pwm(random(-225,225),random(-225,225)); } //Motion2 if ( diff_motor >= motor_timemap[0] && diff_motor <= motor_timemap[1] ) { set_two_motors_pwm(100, 100); } //Motion3 if ( diff_motor >= motor_timemap[1] && diff_motor <= motor_timemap[2] ) { set_two_motors_pwm(-200, 200); } //Motion4 if ( diff_motor >= motor_timemap[2] && diff_motor <= motor_timemap[3] ) { set_two_motors_pwm(random(-225,225), random(-225,225)); } if ( diff_motor >= motor_timemap[3]) { //Serial.println("cycle ends"); Motortimer = millis(); } //Speaker1's sequence of actions play_note(speaker1, diff_speaker1); play_note(speaker2, diff_speaker2); } void setup() { //Setting up the motors' pins pinMode(motorA1, OUTPUT); pinMode(motorA2, OUTPUT); pinMode(motorB1, OUTPUT); pinMode(motorB2, OUTPUT); //Starting in the coasting mode digitalWrite(motorA1, LOW); digitalWrite(motorA2, LOW); digitalWrite(motorB1, LOW); digitalWrite(motorB2, LOW); //Setting up the pins for the transistor for the speakers pinMode(speaker1, OUTPUT); pinMode(speaker2, OUTPUT); //Initializing the RFDI SPI.begin(); mfrc522.PCD_Init(); //Initializing the serial monitor Serial.begin(9600); //Setting up for the real-time clock Wire.begin(); DS3231_init(DS3231_INTCN); //Set up of the t for the real-time clock t.hour = 24; t.min = 30; t.sec = 0; t.mday = 30; t.mon = 2; t.year = 2020; DS3231_set(t); //loop that generate the timemap for speaker1 that adds up the time of previous beats for (int thisbeat = 0; thisbeat < length_of_beat1; thisbeat++) { if (thisbeat == 0) { speaker_timemap1[0] = 2000 / beat1[0]; } else { //This adds up the time for the previous beats. For example, the second note is played from the time of 2000/8 + (2000/8)*0.3 to 2000/8 + (2000/8)*0.3+2000/8. There is a 0.3-note time gap to distinguish between notes. speaker_timemap1[thisbeat] = speaker_timemap1[thisbeat - 1] + (2000 / beat1[thisbeat - 1]) * 0.3 + 2000 / beat1[thisbeat]; } } //loop that generate the timemap for speaker2 that adds up the time of previous beats for (int thisbeat = 0; thisbeat < length_of_beat2; thisbeat++) { if (thisbeat == 0) { speaker_timemap2[0] = 2000 / beat2[0]; } else { //This adds up the time for the previous beats. For example, the second note is played from the time of 2000/8 + (2000/8)*0.3 to 2000/8 + (2000/8)*0.3+2000/8. There is a 0.3-note time gap to distinguish between notes. speaker_timemap2[thisbeat] = speaker_timemap2[thisbeat - 1] + (2000 / beat2[thisbeat - 1]) * 0.3 + 2000 / beat2[thisbeat]; } } } void loop() { //The time to take shower if (t.hour == 23 && t.min == 0 && t.sec == 0) { Motortimer = millis(); Speakertimer1 = millis(); Speakertimer2 = millis(); while (! getID() && tagID != Bathroom_ID) { be_annoying(); } noTone(speaker1); noTone(speaker2); set_two_motors_pwm(0, 0); } //The time to sleep if (t.hour == 24 && t.min == 30 && t.sec == 0) { Motortimer = millis(); Speakertimer1 = millis(); Speakertimer2 = millis(); while (! getID() && tagID != Bed_ID) { be_annoying(); } set_two_motors_pwm(0, 0); noTone(speaker1); noTone(speaker2); } //Printing the time for debugging purpose if ( (millis() - Clocktimer) >= Clockwait ) { //displaying the time on the serial monitor DS3231_get(&t); Serial.print("Date : "); Serial.print(t.mon); Serial.print("/"); Serial.print(t.mday); Serial.print("/"); Serial.print(t.year); Serial.print("\t Hour : "); Serial.print(t.hour); Serial.print(":"); Serial.print(t.min); Serial.print("."); Serial.println(t.sec); Clocktimer = millis(); } }
/************************************************* * Constants for all the notes *************************************************/ #define NOTE_B0 31 #define NOTE_C1 33 #define NOTE_CS1 35 #define NOTE_D1 37 #define NOTE_DS1 39 #define NOTE_E1 41 #define NOTE_F1 44 #define NOTE_FS1 46 #define NOTE_G1 49 #define NOTE_GS1 52 #define NOTE_A1 55 #define NOTE_AS1 58 #define NOTE_B1 62 #define NOTE_C2 65 #define NOTE_CS2 69 #define NOTE_D2 73 #define NOTE_DS2 78 #define NOTE_E2 82 #define NOTE_F2 87 #define NOTE_FS2 93 #define NOTE_G2 98 #define NOTE_GS2 104 #define NOTE_A2 110 #define NOTE_AS2 117 #define NOTE_B2 123 #define NOTE_C3 131 #define NOTE_CS3 139 #define NOTE_D3 147 #define NOTE_DS3 156 #define NOTE_E3 165 #define NOTE_F3 175 #define NOTE_FS3 185 #define NOTE_G3 196 #define NOTE_GS3 208 #define NOTE_A3 220 #define NOTE_AS3 233 #define NOTE_B3 247 #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988 #define NOTE_C6 1047 #define NOTE_CS6 1109 #define NOTE_D6 1175 #define NOTE_DS6 1245 #define NOTE_E6 1319 #define NOTE_F6 1397 #define NOTE_FS6 1480 #define NOTE_G6 1568 #define NOTE_GS6 1661 #define NOTE_A6 1760 #define NOTE_AS6 1865 #define NOTE_B6 1976 #define NOTE_C7 2093 #define NOTE_CS7 2217 #define NOTE_D7 2349 #define NOTE_DS7 2489 #define NOTE_E7 2637 #define NOTE_F7 2794 #define NOTE_FS7 2960 #define NOTE_G7 3136 #define NOTE_GS7 3322 #define NOTE_A7 3520 #define NOTE_AS7 3729 #define NOTE_B7 3951 #define NOTE_C8 4186 #define NOTE_CS8 4435 #define NOTE_D8 4699 #define NOTE_DS8 4978
]]>
This project is an alarm clock that will drive around your room and flicker your lights until you wake up and stop it.
I used a smaller screen than I had wanted. Essentially, I didn’t want a very big boxy alarm clock, since I thought it would look a little ugly aesthetically speaking. This decision was a factor in the next decision I will talk about.
Making the smaller screen functional
I made the UI simpler than I wanted it to be. At first, I wanted the screen to have options for how quick the robot would run around, how quick the lights would flicker, how easy it would be for the robot to dodge obstacles, and then all the setting for time, date, alarm etc. I also originally wanted it to show the seconds. But due to difficulty and space, I decided to go for a much simpler UI. Which looks like the following.
Simpler UI
Another decision I had to make early on was how many wheels I wanted and how big. Considering that I had some easy pre-geared wheels I kind of wanted to integrate those into the design to make my life easier. This also meant I had to build around the size of the wheels. Most alarm clocks aren’t that big, which mean’t if I was using those wheels, things were going to have to be squished into to the smallest space possible. I ended up just using two of those pre-geared wheels and adding a small point turn bearing later to conserve space for electronics.
Two wheel with small point turn bearing decision
Here is an image of my initial idea for this project and how I wanted it to turn out.
Original brainstorming
Response
“Where would be the light be located? Also will other lights be hinder the photoresistor? The user interface is easy, with easy buttons and options to choose from, the ultrasonic sensor works great to avoid bumping into other stuff. I would like to see how big the range the movement is. In the demo it stayed pretty much in the same area, I would like to see it run around like in bigger area, so it would make you get up more. “
The light would be located anywhere in your room. The idea was that you would be able to purchase a bigger set of NeoPixel lights and hang them anywhere around your room. Something I could work on is making a better system to trigger the lights. As of now, with no adjustable threshold, the lights would not go off in a very dark room. I would really like to add a magnetic trigger so that this would work in a lot more environments than the current system does. The device currently has an infinite range, it only looked like it stayed in the same area because I forced it into a circle with my foot.
“I think the entire idea is really cool and i really appreciate the attention to details that you have p especially in terms of the user interface! I think the entire project is really crazy and I am super happy that you were able to get a lot of your features done!“
Thank you so much! It took a lot of time to get some of these features to work, especially the UI. I do think I could have paid more attention to detail if I had paced myself a little better considering our time constraint.
Self critique
I am satisfied with how it came out, but not entirely happy with it. My project basically does what I wanted it to do, but the end game of my process was very rushed. (my fault though) If I had paced myself and spent more time I think I could have solved some issues with some better solutions than the ones I currently have. I also think I could have made it look a lot more aesthetically pleasing if I had given myself more time. In conclusion, I did meet my goals, but not my own expectations.
What you learned
What would you do differently next time?
I would have allotted more time to this project. Most of the project was done in under 10 hours on the day before it was due. This gave me very little time to come up with elegant solutions to problems I had. It also gave me little time to manufacture and work on aesthetics.
Did you get hung up at a particular point in progress, or fail to see an easy workaround to a problem?
I think the main issue here is that I thought I had solved the hardest issue of the project and then left rest of the project to a way later date. When that date came, a lot of unexpected issues came up and I only had a couple of hours to fix them. This also led to me to “tunnelvision” and put my brain in a box. I only realized some better solutions after the due date.
What you learned about your own abilities and/or limitations
Given the amount of time I used to produce a working product I think I did a very good job. On the flip side, I am my own limiter. I can only imagine what I would’ve produced if I had used the time I had more wisely.
Next steps
Do you expect to build another iteration of this project?
If Zach would give us extra credit to do so, I would not be apposed to building another iteration. Well, if I were to build another iteration, I would implement a magnetic trigger for the lights in the room. This would allow me to create a “home” for the alarm clock as it waits until the next alarm date. I would create an easy way to turn it off/on and a way to charge it in its “home”. I would also try to add a sound to it while the alarm goes off. It currently doesn’t have a sound due to some internal libraries colliding, but I would find a workaround to this. Lastly, I would make it more aesthetically pleasing.
Alarm Clock Schematic
Lights Schematic
Alarm Clock Code
/* Catch the Clock (alarm clock code) * Estevan Chairez * * Description: This code runs all the logic for the alarm clock. * It will set up the menu screen and let the user select options, * trigger the motors to autonomously navigate its surrounding area * if the clock reaches the set alarm time and turn off to stop the alarm. * The potentiometer will act as a scroll-knob, the pushbutton as * a button to select options and numbers. * * Pin mapping: * * pin | mode | description * ------|--------|------------ * A3 input potentiometer * 2 input momentary pushbutton * 4 input TRIG pin for ultrasonic sensor * 6 input ECHO pin for ultrasonic sensor * 3 output B1IN for motor 1 * 5 output B2IN for motor 1 * 9 output A2IN for motor 2 * 10 output A1IN for motor 2 * */ #include <DS1307RTC.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <NewPing.h> #define POTENT A3 #define BUTTON 2 #define TRIG 4 #define ECHO 6 #define MAX_DISTANCE 200 #define THRESHOLD 40 #define AIN_1 10 #define AIN_2 9 #define BIN_1 3 #define BIN_2 5 /////////////////////////////////// // global variables and objects /////////////////////////////////// tmElements_t tm; int alarmTime[2]; int knobPos; int velocity = 70; unsigned long minInPico = 60000; bool clockState = true; bool dateState = false; bool timeState = false; bool alarmState = false; bool alarmOn = false; bool hasAlarm = false; bool reStart = true; NewPing sonar(TRIG, ECHO, MAX_DISTANCE); LiquidCrystal_I2C lcd(0x27, 20, 4); //////////////////////////////////// // clock functions //////////////////////////////////// void displayDate(int character, int line, tmElements_t tm) { lcd.setCursor(character, line); if (tm.Month < 10) lcd.print("0"); lcd.print(tm.Month); lcd.print("/"); if (tm.Day < 10) lcd.print("0"); lcd.print(tm.Day); lcd.print("/"); lcd.print((String)(1952 + tm.Year)); } void displayTime(int character, int line, tmElements_t tm) { String minutes, hours; lcd.setCursor(character, line); if (tm.Hour < 10) lcd.print("0"); lcd.print(tm.Hour); lcd.print(":"); if (tm.Minute < 10) lcd.print("0"); lcd.print(tm.Minute); } // displays the main clock idle screen, which has the // options to set time, date and alarm void displayClock() { if (RTC.read(tm)) { Serial.println(tm.Hour); Serial.println(tm.Minute); lcd.noCursor(); displayDate(0, 0, tm); displayTime(0, 1, tm); } lcd.setCursor(10, 1); lcd.print("Set Time"); lcd.setCursor(10, 2); lcd.print("Set Date"); lcd.setCursor(10, 3); lcd.print("Set Alarm"); lcd.cursor(); } ///////////////////////////////////// // other functions ///////////////////////////////////// // gives the right range when trying to set days, minutes, hours etc. int configureNumber(int type) { int num; lcd.noCursor(); while (true) { lcd.clear(); lcd.setCursor(4, 1); if (type == 1) { num = map(analogRead(POTENT), 0, 1023, 0, 24); lcd.print("Set Hour: "); } else if (type == 2) { num = map(analogRead(POTENT), 0, 1023, 0, 60); lcd.print("Set Minute: "); } else if (type == 3) { num = map(analogRead(POTENT), 0, 1023, 0, 12); lcd.print("Set Month: "); } else if (type == 4) { num = map(analogRead(POTENT), 0, 1023, 0, 31); lcd.print("Set Day: "); } else { num = map(analogRead(POTENT), 0, 1023, 1980, 2050); lcd.print("Set Year: "); } lcd.print(num); delay(200); if (digitalRead(BUTTON) == HIGH) break; } return num; } // autonomous control of robot traveling around area once // alarm is set off void alarmProtocol() { lcd.noCursor(); lcd.clear(); while (true) { // turn in place if (sonar.ping_cm() < THRESHOLD) { analogWrite(AIN_1, 0); analogWrite(AIN_2, velocity); analogWrite(BIN_1, velocity); analogWrite(BIN_2, 0); delay(100); } // go straight else { analogWrite(AIN_1, 0); analogWrite(AIN_2, velocity); analogWrite(BIN_1, 0); analogWrite(BIN_2, velocity); delay(100); } // turn motors off if (digitalRead(BUTTON) == HIGH) { analogWrite(AIN_1, 0); analogWrite(AIN_2, 0); analogWrite(BIN_1, 0); analogWrite(BIN_2, 0); delay(1000); break; } } hasAlarm = false; clockState = true; reStart = true; } void setup() { pinMode(POTENT, INPUT); pinMode(BUTTON, INPUT); pinMode(AIN_1, OUTPUT); pinMode(AIN_2, OUTPUT); pinMode(BIN_1, OUTPUT); pinMode(BIN_2, OUTPUT); // set motors off analogWrite(AIN_1, 0); analogWrite(AIN_2, 0); analogWrite(BIN_1, 0); analogWrite(BIN_2, 0); // default clock in lcd lcd.begin(20, 4); lcd.backlight(); displayClock(); lcd.cursor(); } void loop() { // default state to go into other menus if (clockState) { // brings you back to default menu if (reStart) { lcd.clear(); displayClock(); reStart = false; } unsigned long currentMillis = millis(); if (currentMillis > minInPico) { displayClock(); currentMillis = 0; } // logic for set time, date, alarm settings knobPos = map(analogRead(POTENT), 0, 1023, 1, 4); if (knobPos != 4) lcd.setCursor(13, knobPos); if (hasAlarm && tm.Hour == alarmTime[0] && tm.Minute == alarmTime[1]) { clockState = false; alarmOn = true; } if (knobPos == 1 && digitalRead(BUTTON) == HIGH) { lcd.clear(); clockState = false; timeState = true; } else if (knobPos == 2 && digitalRead(BUTTON) == HIGH) { lcd.clear(); clockState = false; dateState = true; } else if (knobPos == 3 && digitalRead(BUTTON) == HIGH) { lcd.clear(); clockState = false; alarmState = true; } delay(200); } // to set time else if (timeState) { delay(100); tm.Hour = configureNumber(1); delay(100); tm.Minute = configureNumber(2); timeState = false; clockState = true; reStart = true; RTC.write(tm); delay(100); } // to set date else if (dateState) { delay(100); tm.Month = configureNumber(3); delay(100); tm.Day = configureNumber(4); delay(100); tm.Year = configureNumber(5); dateState = false; clockState = true; reStart = true; RTC.write(tm); delay(100); } // to set alarm else if (alarmState) { delay(100); alarmTime[0] = configureNumber(1); delay(100); alarmTime[1] = configureNumber(2); delay(100); alarmState = false; clockState = true; reStart = true; hasAlarm = true; RTC.write(tm); delay(100); } // sets robot to run around area else if (alarmOn) alarmProtocol(); }
Lights Code
/* Catch the Clock (lights code) * Estevan Chairez * * Description: This code runs the logic for the flickering * lights portion of the project. If the potentiometer reads * a value under a certain threshold, it will flicker the lights * and if not, the lights stay off. * * Pin mapping: * * pin | mode | description * ------|--------|------------ * A0 input potentiometer * 10 output neopixel strip */ #include <Adafruit_NeoPixel.h> #define LED 10 #define LED_COUNT 42 #define PHOTO A0 #define THRESH 600 Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, LED, NEO_GRB + NEO_KHZ800); uint32_t white = strip.Color(255, 255, 255); void setup() { pinMode(PHOTO, INPUT); strip.begin(); strip.show(); } void loop(){ int photoVal =analogRead(PHOTO); // flash lights if (photoVal < THRESH){ strip.fill(white); strip.show(); delay(500); strip.clear(); strip.show(); delay(500); } // lights off else { strip.clear(); strip.show(); } }
]]>
An interactive alarm clock that reminds you to go to sleep if you are still awake past a certain bedtime by detecting light levels in the room.
A photoresistor senses light levels in the room.
There is a menu system to view alarm settings.
You can edit the alarm as well as the time.
Labeled physical components of the alarm clock.
[Note: detailed photos of the device could not be provided]
I started with brainstorming ideas and sketching how I wanted the alarm clock to function and look like. I envisioned the device to resemble a regular alarm clock in form, but I also wanted to integrate the added functionality of light detection. It was definitely helpful to begin with a solid concept in mind for what I was building.
Initial concept sketches and notes.
The first two pieces I focused on were the LCD Display and the real time clock as these were the two fundamental components to a basic working clock. I was able to get the LCD to display the time data, although the time had to be hard coded in.
Building a basic functional clock.
Real time clock used to measure time data.
I started early on the menu system to eliminate the hard coding issue and to help make alarm/general software testing easier. For user input, I used a potentiometer for entering values along a range and a push button to select options. The code for the menu system ended being one of most complex aspects of this project due to all the different settings with various inputs and screen displays.
Speaker (left) and Potentiometer (right).
In place of the potentiometer and push button, I attempted to use a rotary encoder to make user input easier with continuous rotation and added button functionality. However, I spent way too much time trying to figure out the wiring and code to read inputs from the rotary encoder without much progress. I eventually switched back to my original choice of a separate potentiometer and push button since they were more familiar to me and already integrated into the menu code.
Rotary encoder component.
For the physical prototype, I just made a simple white box to hold the screen and drawn-on components.
Prototype built from cardboard and paper.
I measured each of the components to ensure that they would fit appropriately. I sketched the location of each part and how large I wanted the physical box to be, keeping in mind that it had to hold both the Arduino and the breadboard.
Box sketches and measurements.
The clock was modeled in Fusion 360 before being laser cut. This step was also very time consuming especially since I had little experience working with Fusion. I wanted to add finger joints to each side to make the box more structurally sound, but the process of making rectangular cuts on each edge proved to be pretty tedious.
3D box model in Fusion 360
I decided to go with acrylic as my material of choice because I wanted the clock to have a clean, sleek appearance. I chose blue acrylic because since this was a sleep device, I felt that blue was a calming color and also associated with night time. Once the pieces were cut, I realized the box might actually be too small to fit everything inside. It turned out to be a tight fit, so I had to strategically position and cram together all the internal components. In then end, I was able to successfully fit everything inside.
Realizing this is a lot of stuff to fit inside a tiny box.
Tape helped hold together loose pieces, especially while waiting for glue to dry.
Fitting all the internal components inside.
Overall, I am pretty happy with how my project turned out. It functions as intended, and the design is similar to how I envisioned, although there is definitely still room for improvement. Reflecting on the challenges I faced, I’m glad I pushed myself to work with new components and software. It was certainly frustrating at times, but these setbacks proved to be good learning opportunities.
While trying to figure out the rotary encoder, I kept getting stuck on the complicated technical aspects. It would have been difficult to integrate since the menu system code was already written in terms of the potentiometer and button inputs. I wish I could have realized sooner it would have been better to stick with my original components given the time constraints I had.
Moving forward, I know to more carefully consider which components I will need and to not make major changes prior to writing a lot of code. The menu code itself was more complicated than anticipated, and it would have been helpful to organize and plan out my code on a conceptual level before writing it out. I realized sometimes it’s okay to quit while you’re ahead. The menu system was already working, so I could have saved a lot of time had I just moved onto the next feature instead of focusing on perfecting this one small detail. For future projects, I’ll keep in mind the importance of prioritizing the big picture rather than getting stuck on one small aspect.
Working with Fusion 360 was another step which challenged me because I did not have much experience with using the software. I did not think I would be able to get the model to look the way I wanted it to, but it actually turned out okay. Making the finger joints was challenging, but it was worth the extra effort to learn how to use Fusion as it’s a useful skill for later projects as well.
In retrospect, I realized the importance of planning ahead, especially when it came to piecing together the final box. A lot of last minute problems arose which I definitely did not anticipate, for instance the internal pieces being too tightly fitted inside. If I had measured more thoughtfully ahead of time, I might have made the box bigger. In addition, the buttons at the top did not have ample support below. I applied some creative problem solving and taped wood pieces below them for base support. Next time, I know to work on my project earlier to give myself ample time to make these mistakes, or better yet, have time to plan ahead and anticipate these problems before they arise in the first place.
During the class crit, some of the feedback I received included:
Interesting idea, and I think it’s quite practical where it could force you to sleep. One thing you could consider is how could you make this more accessible for other people to use? How will people decipher what controls to use?
This is a good point because I left the buttons unlabeled, so it might be confusing for users what the buttons are supposed to do. To make the user interaction more clear, I would probably add labels/symbols or some clear indicator next to the buttons to convey their function.
A choice for sound could be helpful, as I know people who need white noise sensors or soothing sounds to go to sleep.
I agree that having a variety of alarm sounds would improve the clock’s customization. Given more time, I would add a selection of sounds for the user to choose. However for now, I chose a somewhat annoyingly high pitched tone alarm with the intention of both alerting and punishing the user for staying up past their set sleep time.
For future iterations, I would also try to add a distance sensor near the photoresistor to prevent cheating the system by draping something over the clock to make it dark. This added feature would ensure light levels are representative of the surrounding area.
In the end, I think I did satisfy a lot of my own goals in terms of learning to work with Fusion 360 and getting to build a finished, working alarm clock. I’m pretty satisfied with the appearance, and while it was challenging to work with a smaller box to hold everything, it also allowed for a more compact and sleek outer appearance. I’m also glad I made detailed sketches for my vision of the design prior to building it because it helped to give me a strong visual design to work towards.
/* Project Title: Snooze Alarm Description: This code sets off a sound alarm to remind the user the user to go to sleep if they are awake past their set bed time. A photoresistor reads in brightness levels of the room to detect whether the user is asleep or not. A potentiometer and momentary push button provide input to change the time displayed and alarm settings on an LCD screen. If the user is still not not in bed within 5 minutes of the alarm, a second snooze alarm will be set off. The first alarm can be stopped by pressing a snooze button but not the second snooze alarm. Pin mapping: pin | mode | description ------|--------|------------ A0 input potentiometer A3 input photocell 7 input momentary push button (settings) 5 input momentary push button (snooze) 9 output sound speaker (alarm) 12 output LED (alarm) */ // include necessary libraries #include <DS3231.h> #include <LiquidCrystal_I2C.h> #include "Volume3.h" String state = "display time"; // clock menu state int hours; // hours set for display or alarm time int minutes; // minutes set for diplay or alarm time int speakerVol; // volume of alarm int maxVol = 800; // speaker max volume int threshold = 930; // brightness threshold bool soundAlarm = false; // play alarm sound bool alarmOn = false; bool alarmTime = false; // indicate setting alarm or display time int alarmHr; int alarmMin; bool snooze = false; // whether user has pressed snooze button int snoozeHr; int snoozeMin; int snoozeDelay = 5; // how many minutes to snooze bool snoozeAlarm = false; const int POTPIN = A0; // potentiometer const int PHOTOPIN = A3; // photocell const int PUSHPIN = 7; // momentary push button for settings const int PUSHPIN2 = 5; // momentary push button for snooze const int SPEAKERPIN = 9; // sound speaker const int LEDPIN = 12; // LED DS3231 rtc(SDA, SCL); // initialize real time clock LiquidCrystal_I2C lcd(0x27, 16, 2); // initialize LCD display // control alarm sound and light behavior void callAlarm(int brightnessVal) { if (brightnessVal < threshold) { speakerVol = maxVol; digitalWrite(LEDPIN, HIGH); } else { speakerVol = 0; digitalWrite(LEDPIN, LOW); } vol.tone(SPEAKERPIN, 800, speakerVol); } void setup() { pinMode(POTPIN, INPUT); // potentiometer pinMode(PHOTOPIN, INPUT); // photoresistor pinMode(PUSHPIN, INPUT); // momentary push button pinMode(PUSHPIN2, INPUT); // snooze push button pinMode(LEDPIN, OUTPUT); // LED pinMode(SPEAKERPIN, OUTPUT); // sound speaker Serial.begin(115200); // set up LCD screen lcd.init(); lcd.backlight(); lcd.home(); // set up real time clock rtc.begin(); rtc.setTime(12, 0, 0); // Set time to 12:00:00 (24hr format) } void loop() { int potVal = analogRead(POTPIN); //(range 0-1023) int brightnessVal = analogRead(PHOTOPIN); //(range 800-1022) int pushVal = digitalRead(PUSHPIN); // settings button int pushVal2 = digitalRead(PUSHPIN2); // snooze button Serial.println(brightnessVal); // check whether alarm should be set off Time t = rtc.getTime(); if (snoozeHr == t.hour and snoozeMin == t.min) { snoozeAlarm = true; snooze = false; } else if (not snooze and alarmOn and alarmHr == t.hour and alarmMin == t.min) { soundAlarm = true; } else { soundAlarm = false; snoozeAlarm = false; digitalWrite(LEDPIN, LOW); vol.tone(SPEAKERPIN, 800, 0); } // check if snooze button pressed if (pushVal2 == HIGH and not snooze and soundAlarm) { snooze = true; } // check if selection button pressed, update to next menu state if (pushVal == HIGH) { lcd.clear(); if (state.equals("set minutes")) { // setting alarm if (alarmTime) { // set alarm time to selected hour and min alarmHr = hours; alarmMin = minutes; alarmTime = false; // set corresponding snooze time (5 min later) snoozeHr = alarmHr; snoozeMin = alarmMin + snoozeDelay; // manage minute overflow if (snoozeMin > 59) { if (snoozeHr + 1 == 24) snoozeHr = 0; else snoozeHr += 1; snoozeMin = snoozeMin - 60; } } // setting display time else rtc.setTime(hours, minutes, 50); // edit for testing // reset snooze settings snooze = false; state = "display time"; } else if (state.equals("set hours")) { state = "set minutes"; } else if (state.equals("display time")) state = "settings"; else if (state.equals("settings")) { // check which option selected if (potVal < 511) state = "set hours"; else state = "edit alarm"; } else if (state.equals("edit alarm")) { if (potVal < 511) state = "display time"; else state = "set alarm"; } else if (state.equals("set alarm")) { if (potVal < 511) { alarmOn = true; alarmTime = true; state = "set hours"; } else { alarmOn = false; // reset snooze settings snooze = false; state = "display time"; } } // time gap for button unpress delay(500); } // update alarm screen display if (state.equals("display time")) { lcd.setCursor(0, 1); if (soundAlarm and not snooze) { // set off alarm lcd.print("Time to go sleep!"); callAlarm(brightnessVal); } else if (snoozeAlarm) { // set off snooze alarm lcd.print("PLEASE GET SLEEP"); callAlarm(brightnessVal); } else if (snooze) { // display time for next alarm lcd.print((String) "[snooze " + snoozeHr + ":" + snoozeMin + "] "); } else { lcd.setCursor(0, 1); lcd.print(" "); } // display current time lcd.setCursor(0, 0); lcd.print("Time: "); lcd.print(rtc.getTimeStr()); } else if (state.equals("settings")) { // display options and cursor lcd.setCursor(0, 0); lcd.print("Edit Time"); lcd.setCursor(0, 1); lcd.print("Set Alarm"); if (potVal < 511) { lcd.setCursor(15, 1); lcd.print(" "); lcd.setCursor(15, 0); } else { lcd.setCursor(15, 0); lcd.print(" "); lcd.setCursor(15, 1); } lcd.print("<"); } else if (state.equals("edit alarm")) { // select whether to change or keep alarm time lcd.setCursor(0, 0); if (alarmOn) lcd.print((String)"Alarm: " + alarmHr + ":" + alarmMin); else lcd.print("Alarm: None"); lcd.setCursor(0, 1); lcd.print("Edit"); if (potVal < 511) { lcd.setCursor(15, 1); lcd.print(" "); lcd.setCursor(15, 0); } else { lcd.setCursor(15, 0); lcd.print(" "); lcd.setCursor(15, 1); } lcd.print("<"); } else if (state.equals("set alarm")) { // select if alarm is turned on or off lcd.setCursor(0, 0); lcd.print("Alarm On"); lcd.setCursor(0, 1); lcd.print("Alarm Off"); if (potVal < 511) { lcd.setCursor(15, 1); lcd.print(" "); lcd.setCursor(15, 0); } else { lcd.setCursor(15, 0); lcd.print(" "); lcd.setCursor(15, 1); } lcd.print("<"); } else if (state.equals("set hours")) { // update hour being entered lcd.setCursor(0, 0); // map potVal to be within 0-23 hr range hours = constrain(map(potVal, 0, 1023, 0, 30), 0, 23); // adjust spacing for 1 vs 2 digit display String space = " "; if (hours < 10) space = " "; lcd.print((String)"Set Hour:" + space + hours); } else if (state.equals("set minutes")) { // update minutes being entered lcd.setCursor(0, 0); // map potVal to be within 0-59 min range minutes = constrain(map(potVal, 0, 1023, 0, 65), 0, 59); // adjust layout spacing for 1 vs 2 digit display String space = " "; if (minutes < 10) space = " "; lcd.print((String)"Set Minutes:" + space + minutes); } }
]]>