This class teaches students how to design and fabricate mechanisms that utilize Arduino/small electrical components. This project was intended to allow students to interview a client with a disability and then with their help find an opportunity to improve their day-to-day life with a product. You can see how our interview went here.
What We Built:
Our project is designed to allow our client Allie to type a price into a keypad and then have the amount of money they need to be dispensed to them as well as the amount of change left over displayed. This cash dispenser can be attached to our client’s wheelchair. There is also a reload mechanism that allows our client to reload the machine with money. Our specific device was designed to dispense $10 and $20 bills.
A video that demonstrates the entire process of dispensing money from the view of the user.
A video that demonstrates the entire process of dispensing money from the view of the shop clerks.
Narrative Sketch:
When Allie goes out shopping and is about to pay for her groceries, she would turn to the Money Dispenser sitting on her desk on top of the wheelchair. She types in the price of the item using the keypad and the motors run and dispense the correct number of 10- and 20-dollar bills. The display screen reads the change Allie would get back. When Allie gets home, she and her helper would unlock the cabinet in the front, and then press the red button which says reload 10s and reload the 20s to reload the machine for purchases in the future.
How we got here:
Prototype:
This prototype was designed to help answer the question: how to most effectively dispense only one bill at a time?
Our first prototype was based on how a printer dispenses paper and utilized multiple roller mechanisms that would dispense a bill from a stack. Basically a spring would push up a stack of bills to a certain height and then a rolling cylinder with grippy material would push the bill forward. We then moved on to our second prototype which is the design that we ended up using with two rollers and fabric pulled between them. This design is essentially a piece of fabric taped to two roller and as its winded, bills are inserted between the folds of the fabric. The fabric is then unwound to dispense the bills.
Our first prototype of the bill dispensing mechanism was not able to consistently roll out a single bill at a time.
Most of our prototyping process was spent figuring out if it was possible for us to create a money dispenser, specifically one that would only dispense one bill at a time. We first modeled our mechanism after a printer that utilized a set of rollers that would only allow one bill to pass through at a time. However, we found that this mechanism required high precision tolerance between the rollers which was difficult for us to consistently achieve. We then switched to our final mechanism which instead of storing the bills in a stack where they could stick together, layered the bills separately inside a roll of fabric.
We received feedback that our original mechanism was flawed because we did not have a way to detect if the correct number of bills had been dispensed, which we solved by integrating IR sensors into the design. However, we weren’t able to react to the feedback to make the mechanism smaller due to how much testing that would require and the time limit we were under. The biggest (good) surprise we encountered was how smoothly our second mechanism worked after having so much difficulty with tweaking the first one.
Process:
The biggest change in our design moving forward from our prototype presentation was switching our bill dispensing mechanism to a simpler two-roller setup. We found a mechanism online that used two rollers driven by motors to reload and dispense bills from a spool of fabric. This mechanism gave us more control over how many bills were released at once. It was also more appropriate for Allie’s needs since she told us that she gets about 60 dollars each week in the form of 3 twenty-dollar bills, so she didn’t necessarily need a device that could fit a large stack of bills.
We also had to make a decision about how our device could track the number of dispensed bills from each compartment. Initially, we planned to time how long it would take to dispense a single bill, but that would require the bills to be evenly spaced out when loaded into the rollers. Instead, we chose to use an IR transmitter and receiver that detects a decrease in the IR signal as a bill exits the money rollers. Similarly, we had to find a method to determine when a given compartment was out of bills. At first we planned to use an RGB color sensor to detect a color change when a roller reached a white piece of tape at the end of the fabric. However, we ended up using an IR sensor so that we would only have to work with one signal value rather than the three values that an RGB sensor outputs.
Our biggest breakthrough was figuring out how to prevent fabric slack from bunching up between the two rollers when running the motors. We found the best way to maintain good tension in the fabric roll was to drive the bottom roller faster than the top roller while dispensing and drive the top roller faster while reloading. This allowed us to smoothly and consistently dispense/reload bills.
One mistake we made was waiting too long to start arranging our pieces of hardware into the outer shell of the box. We spent a lot of time fine-tuning the IR sensor thresholds outside the final cash box. This took up a lot of time, and we had to redo the IR signal thresholds in our code anyway because the box provided a much darker environment. If we had put our entire device into the box earlier on, we would have still been able to fine-tune the performance of our hardware and would have had more time for wire management inside the final form.
Gantt Chart Schedule:
Our team followed the sequence of tasks on the Gantt chart pretty closely. However, we underestimated how long it would take us to complete certain tasks. There ended up being a lot more hardware than initially expected, so the process of wiring, writing code, and introducing each component into the circuit took longer than expected. Rather than finishing the “implementing circuit” phase by 4/24, we didn’t finish integrating all the electronics and the roller mechanisms until 4/29. This only gave us 3 days to put all of our hardware into the outer box.
Conclusions and Lessons Learned:
This project has been a difficult but rewarding experience. We have run into a lot of problems during every stage of the process, including revamping the entire mechanism after the prototype review, but we were able to put together the mechanism in the end. The box itself during the final review still had a lot of room for improvement. As pointed out by comments such as “the fabrication is flimsy with a less-than-ideal UI”, “a bit big”, and “[Allie] can’t see what comes out”, the fabrication was a last-minute process and had flaws such as its size and fragility. We also planned on making a locked cabinet in the front but couldn’t get around to installing it. Additionally, the box orients itself away from Allie which prevents her from seeing the money being dispensed. A great solution that our critic brought up was adding a “swivel” to the bottom of the device so that it can “turn towards Allie” first, then when Allie makes sure it is the right amount, she can turn the machine towards the shop staff. Moreover, the problem of size was also discussed heavily in the critique. One critic suggested using “bill recognition” and some forms of servo motor that directs the money out or back into the loop so that we can use one module for all kinds of bills instead of two modules for two types of bills. This will not only increase the utility of the machine but also downsize it to a reasonable and usable form. Another critic pointed out is the messy “inner components” that should be organized which might reduce the size of the box further. However, despite the shortcomings, our project still got some praise for tackling such a difficult problem, dealing with single sheets of paper. Some critics said that the “motor system works well” and contains “clever paper handling” which we are happy to see.
Working with Allie has been a phenomenal experience as she was extremely helpful during our creation process. Allie shared us some hobbies with us and even some of the posters she made in her free time. We were able to learn to incorporate ergonomic designs into the project that caters to Allie’s specific needs, and through that, we learned more about disability beyond the project as we realized that disability is not just simply a medical definition but also a social problem. Some items and facilities are simply not designed for those with different needs and people are handicapped by the products and that is an external problem instead of an internal one. In the future, we will ask more questions working with people with disabilities and have more meetings with them to get a better sense of the user’s experience.
Working on Zoom certainly brought up some difficulties during the interview as Allie had some issues with her microphone, but overall, we as a team were able to get together in person for the most part and work in the Physical Computing lab together without any issues.
Technical Details:
Code:
/* Money Box Code - Andy Jiang, Cassie Rausch, Lynn Rushkin * * How it works: * Use the keypad to enter the price you would like to pay: '#' = enter (E) '*' = delete (D) type in your price as dollars and cents (i.e. 1600 --> $16.00, 4567 --> $45.67) if you mess up a digit then click '*' to delete the last digit press # when you have your price entered The code calculates how many 10 and 20 dollar bills need to be dispensed. As the device dispenses the bills, "dispensing..." will show up on LCD display. The IR transducer/receiver pair on the $10 and $20 compartments will detect how many bills have been dispensed and once the correct amount has been paid the motors of the compartments will stop running. The change amount is calculated based on how much was paid and the entered price. This amount will pop up on the LCD display. If one bill compartment is empty(detected by IR sensor)then the number of bills needed will be recalculated and dispensed from the compartment that still has bills. Sources: 1. Keypad code from Terrance Ou's "Math Buddy" code (https://courses.ideate.cmu.edu/60-223/s2022/work/math-buddy/) 2. LCD display code from https://courses.ideate.cmu.edu/60-223/s2022/tutorials/I2C-lcd //----------------pin mapping ---------------------- Pin mapping: Arduino Mega pin | type | description -----------------|--------|------------- A1 input IR receiver (detect dispensed $20 bills) A2 input IR receiver (detect dispensed $10 bills) A4 input IR sensor (detects empty $10 bill compartment) A5 input IR sensor (detects empty $20 bill compartment) 2 output toy gear box motor 1 (top roller of $10 bill compartment) 3 output toy gear box motor 1 (top roller of $10 bill compartment) 5 output toy gear box motor 2 (bottom roller of $10 bill compartment) 6 output toy gear box motor 2 (bottom roller of $10 bill compartment) 9 output toy gear box motor 3 (top roller of $20 bill compartment) 10 output toy gear box motor 3 (top roller of $20 bill compartment) 11 output toy gear box motor 4 (bottom roller of $20 bill compartment) 12 output toy gear box motor 4 (bottom roller of $20 bill compartment) 25 input button 1 (for reloading $20 bills) 27 input button 2(for reloading $10 bills) 39 input keypad (COL 2) 41 input keypad (ROW 4) 43 input keypad (COL 3) 45 input keypad (ROW 1) 47 input keypad (COL 1) 49 input keypad (ROW 2) 51 input keypad (ROW 3) SDA LCD display SCL LCD diaplay Note: toy gear box motors are connected to Arduino Mega through a dual motor driver */ //--------------initialize LCD---------------------- #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C screen(0x27, 16, 2); //---------------------initialize keypad--------------------- #include <Keypad.h> const byte ROWS = 4; // four rows const byte COLS = 3; // three columns char keys[ROWS][COLS] = { {'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'}, {'D', '0', 'E'} }; byte rowPins[ROWS] = {45, 49, 51, 41}; // connect to the row pinouts of the keypad byte colPins[COLS] = {47, 39, 43}; // connect to the column pinouts of the keypad Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); //----------- initialize motors ---------------------------------- //pins for 20s top int motorControlPin1 = 12; int motorControlPin2 = 11; //pins for 20s bottom int motorControlPin3 = 2; int motorControlPin4 = 3; //pins for 10s top int motorControlPin5 = 5; int motorControlPin6 = 6; //pins for 10s bottom int motorControlPin7 = 9; int motorControlPin8 = 10; //---------------initialize IR receivers for bill detection ------------------ const int IRPin_20s = A1; const int IRPin_10s = A2; unsigned long LastIRTime; int IRVal_20s; //reading of the sensor (0 to 1023) bool currentIR_20s = 0; //is the IR sensor covered currently? (1 = yes, 0 = no) bool prevIR_20s = 0; //was the IR sensor covered previously? (1 = yes, 0 = no) int IRVal_10s; bool currentIR_10s = 0; bool prevIR_10s = 0; int dispensedTwenties = 0; //counts how many bills have been dispensed from 20s compartment int dispensedTens = 0; //-------------initialize IR sensor variables for empty compartment detection ------- const int tenIR_empty = A4; const int twenIR_empty = A5; int tenIR, twenIR; bool EmptyModule_20s = 0; //is the $20 compartment empty? (1 = yes, 0 = no) bool EmptyModule_10s = 0; // -----------initialize reload buttons ------------------------ const int tens_button = 27; const int twens_button = 25; int twens_buttonVal; //on or off values of the button reading int tens_buttonVal; //----------- initialize bill calculating variables -------------- char key; //keypad reading String price = ""; float finalPrice = 0; int billsTwenties = 0; //number of $20 bills we need to dispense int billsTens = 0; //number of $10 bills we need to dispense float change = 0; // -----------calibrate these values ----------------------------- //motor speed dispense const int twenTopSpeed_dispense = 100; const int twenBotSpeed_dispense = 140; const int tenTopSpeed_dispense = 100; const int tenBotSpeed_dispense = 140; //motor speed reloading const int twenTopSpeed_reload = 150; const int twenBotSpeed_reload = 80; const int tenTopSpeed_reload = 150; const int tenBotSpeed_reload = 60; /*IR bill dispense threshold - any IR reading below these values means a bill is passing */ const int twenBills_threshold = 50; const int tenBills_threshold = 60; /*IR empty module threshold - any IR reading above these values means the bill compartment has been emptied.*/ const int empty_threshold20s = 200; const int empty_threshold10s = 180; //------------setup and main loop--------------------------------- void setup() { Serial.begin(9600); // setup LCD screen.init(); screen.backlight(); screen.home(); pinMode(motorControlPin1, OUTPUT); pinMode(motorControlPin2, OUTPUT); pinMode(motorControlPin3, OUTPUT); pinMode(motorControlPin4, OUTPUT); pinMode(motorControlPin5, OUTPUT); pinMode(motorControlPin6, OUTPUT); pinMode(motorControlPin7, OUTPUT); pinMode(motorControlPin8, OUTPUT); pinMode(tenIR_empty, INPUT); pinMode(twenIR_empty, INPUT); pinMode(IRPin_20s, INPUT); pinMode(IRPin_10s, INPUT); pinMode(twens_button, INPUT); pinMode(tens_button, INPUT); } void loop() { enteredPrice(); if (key == 'E') { delay(1000); calculateBills(); dispenseBills(); stop10s_motor(); stop20s_motor(); delay(1000); calculateChange(); resetVariables(); } checkReload(); } //------------functions ------------------------------------- void clearLastInput() { int lastIdx = price.length() - 1; screen.setCursor(lastIdx + 8, 0); screen.print(" "); } void enteredPrice() { //displays the price typed into the keypad on LCD screen.home(); screen.print("price: $"); key = keypad.getKey(); if (key != NO_KEY && key != 'D' && key != 'E' && price.length() < 20) { price += key; screen.setCursor(8, 0); screen.print(price); } if (key == 'D') { clearLastInput(); price.remove(price.length() - 1); //removes the last digit from the price string } else if (key == 'E') { finalPrice = price.toFloat() / 100; screen.setCursor(8, 0); screen.print(finalPrice); price = ""; } } void calculateBills() { //calculates the number of 10 and 20 dollar bills to ideally dispense billsTwenties = finalPrice / 20; //integer divide to get how many twenties we need to dispense float remainder = finalPrice - 20 * billsTwenties; if (remainder > 10) { billsTwenties += 1; } else if (remainder > 0 ) { billsTens = 1; } /*Serial.print("bills twenties:"); Serial.println(billsTwenties); Serial.print("bills Tens:"); Serial.println(billsTens);*/ } void run20s_motor() { //drives the top and bottom motors of the $20 compartment to dispense //do an initial acceleration (at highest speed) to overcome internal gear resistance, //and then drive the motors at a reasonable speed if (digitalRead(motorControlPin1) == 0 && digitalRead(motorControlPin4) == 0) { analogWrite(motorControlPin1, 255); analogWrite(motorControlPin4, 255); delay(300); } analogWrite(motorControlPin1, twenTopSpeed_dispense); analogWrite(motorControlPin2, 0); analogWrite(motorControlPin3, 0); analogWrite(motorControlPin4, twenBotSpeed_dispense); } void stop20s_motor() { analogWrite(motorControlPin1, 0); analogWrite(motorControlPin2, 0); analogWrite(motorControlPin3, 0); analogWrite(motorControlPin4, 0); } void run10s_motor() { if (digitalRead(motorControlPin6) == 0 && digitalRead(motorControlPin8) == 0) { analogWrite(motorControlPin6, 255); analogWrite(motorControlPin8, 255); delay(300); } analogWrite(motorControlPin5, 0); analogWrite(motorControlPin6, tenTopSpeed_dispense); analogWrite(motorControlPin7, 0); analogWrite(motorControlPin8, tenBotSpeed_dispense); } void stop10s_motor() { analogWrite(motorControlPin5, 0); analogWrite(motorControlPin6, 0); analogWrite(motorControlPin7, 0); analogWrite(motorControlPin8, 0); } void checkIR() { //updates boolean values of whether a bill is passing //check IR readings in $20 compartment IRVal_20s = analogRead(IRPin_20s); if (IRVal_20s < twenBills_threshold) { currentIR_20s = 1; } else { currentIR_20s = 0; } //check IR in $10 compartment IRVal_10s = analogRead(IRPin_10s); if (IRVal_10s < tenBills_threshold) { currentIR_10s = 1; } else { currentIR_10s = 0; } } void count_billsdispensed() { /*uses the change in currentIR_10s value from 1 --> 0 to determine that a bill has passed*/ if (millis() - LastIRTime >= 400) { LastIRTime = millis(); checkIR(); if (currentIR_20s == 0 && prevIR_20s == 1) { dispensedTwenties += 1; } prevIR_20s = currentIR_20s; if (currentIR_10s == 0 && prevIR_10s == 1) { dispensedTens += 1; } prevIR_10s = currentIR_10s; /* Serial.print("dispensed tens: "); Serial.println(dispensedTens); */ } } void moduleStatus() { //check if either bill compartment has been emptied tenIR = analogRead(tenIR_empty); twenIR = analogRead(twenIR_empty); //Serial.println(tenIR); // push the most recent value to the computer if (tenIR >= empty_threshold10s) { EmptyModule_10s = 1; } if (twenIR >= empty_threshold20s) { EmptyModule_20s = 1; } } void dispenseBills() { screen.clear(); screen.home(); screen.print("dispensing..."); //the following while loop will continue to run as long as there are bills needed to be dispensed to pay the entered price and at least one bill compartment hasn't been emptied while ((billsTwenties > dispensedTwenties or billsTens > dispensedTens) && (EmptyModule_20s == 0 or EmptyModule_10s == 0)) { if (billsTwenties > dispensedTwenties and EmptyModule_20s == 0) { run20s_motor(); } if (billsTwenties > dispensedTwenties and EmptyModule_20s == 1) { stop20s_motor(); billsTens += 2 * (billsTwenties - dispensedTwenties); billsTwenties = dispensedTwenties; } if (billsTwenties == dispensedTwenties) { stop20s_motor(); } if (billsTens > dispensedTens and EmptyModule_10s == 0) { run10s_motor(); } if (billsTens > dispensedTens and EmptyModule_10s == 1) { stop10s_motor(); billsTwenties += ((billsTens - dispensedTens) / 2) + 1 ; billsTens = dispensedTens; } if (billsTens == dispensedTens) { stop10s_motor(); } //check whether the 10s or 20s modules are empty moduleStatus(); //check how many 10s and 20s have been dispensed count_billsdispensed(); } } void calculateChange() { change = (dispensedTwenties * 20 + dispensedTens * 10) - finalPrice; screen.clear(); screen.setCursor(0, 0); screen.print("change: $"); screen.setCursor(9, 0); screen.print(change); delay(3000); screen.clear(); } void checkReload() { //checks if buttons are being pressed. If so, then motors of the bill compartment that correspond to the button will be driven in the opposite direction to reload money into the rollers. twens_buttonVal = digitalRead(twens_button); tens_buttonVal = digitalRead(tens_button); if (twens_buttonVal == 1) { if (digitalRead(motorControlPin2) == 0 && digitalRead(motorControlPin3) == 0) { analogWrite(motorControlPin2, 255); analogWrite(motorControlPin3, 255); delay(300); } analogWrite(motorControlPin1, 0); analogWrite(motorControlPin2, twenTopSpeed_reload); analogWrite(motorControlPin3, twenBotSpeed_reload); analogWrite(motorControlPin4, 0); } if (twens_buttonVal == 0) { stop20s_motor(); } if (tens_buttonVal == 1) { //10 if (digitalRead(motorControlPin5) == 0 && digitalRead(motorControlPin7) == 0) { analogWrite(motorControlPin5, 255); analogWrite(motorControlPin7, 200); delay(300); } analogWrite(motorControlPin5, tenTopSpeed_reload); analogWrite(motorControlPin6, 0); analogWrite(motorControlPin7, tenBotSpeed_reload); analogWrite(motorControlPin8, 0); } if (tens_buttonVal == 0) { stop10s_motor(); } } void resetVariables() { key; price = ""; finalPrice = 0; billsTwenties = 0; billsTens = 0; change = 0; dispensedTwenties = 0; //counts how many bills have been dispensed from 20s compartment dispensedTens = 0; EmptyModule_20s = 0; EmptyModule_10s = 0; }