For our Physical Computing class at Carnegie Mellon University, our final project was to prototype and design an assistive device for an older Osher student over a five-week period. Our Osher student was a lovely lady named Lynne who was looking for a way to rest her hands, which hurt due to arthritis in her fingers’ joints. Thus, we designed a large four-key keyboard to be used by her feet and aptly named it the Foot Keyboard. The keyboard can be plugged into a computer as arrow keys input or alternatively used as a standalone Simon Says game. The project began with interviewing Lynne in order to brainstorm about the device (Interview Documentation). We then received feedback on our prototypes of the device (Prototype Documentation). We concluded the project with a final critique from our Osher student, Lynne.
What we built:
Description
Our project is a foot arrow keyboard. There is a 14 in x 10 in platform with four arrow keys arranged in one row. The keys are styled to look and function like a gas pedal in a car. On the back edge of the platform is a hinge with a kickstand, which is adjustable to allow Lynne to set the keyboard at any desired height. There are two functions to the keyboard: a computer input mode and a Simon Says mode. For the computer input mode, Lynne can connect the keyboard to her laptop and browse the internet using the arrow keys, or she can play games that only require the four arrow keys (like Tetris or Snake). For the Simon Says mode, the keyboard is preprogrammed to randomly generate arrow key patterns for Lynne to repeat after. The difficulty of the Simon Says game starts out easy with a short pattern to repeat and gets more difficult as she continues to play.
Images:
CAD Images:
Demonstration of joint movement for Arrow Keys and Kickstand:
TinkerCad Demo:
Narrative Sketch
After a long day at work, Lynne arrives home and considers how to spend the rest of her afternoon. She has spent the majority of the day using her hands and fingers on her work computer and the pain from her arthritis is very uncomfortable. After greeting Mendy and her two cats, she decides to give her hands a rest and occupy herself by using the Foot Keyboard! She powers on the device and plays a couple of rounds of Simon Says, to see if she can beat her record of 6 rounds from the previous day. She enjoys the fast-paced, challenging nature of the game.
A few rounds (and high scores) later, Lynne plugs the Foot Keyboard into her computer and switches the mode to ‘CPU Input’ Mode to play some online keyboard games, like Snake and Pac-man. After she feels like she has played enough games for the afternoon, she opens up her favorite online book and begins reading, using the Foot Keyboard to flip pages. When bedtime eventually rolls around, the pain in her hands has subsided, she unplugs her Foot Keyboard and prepares for bed.
How we got here:
There were multiple steps that we took to get to the final project after the initial design and prototyping such as multiple rewiring in TinkerCad, debugging the Code to work as intended, as well as changing up the design to create a believable, functional device for Lynne.
The TinkerCad wiring and the internal mechanic were made before the 3D Cad Model, which led to some mishaps as the button location didn’t match up with the model shown. Initially the ON/OFF and Mode button was a push button, but with some discussion with the team, we switched to a slide switch. In addition, before the code was added to the TinkerCad, the wiring for the push button was wired to work with a pull up input. We kept the pull up wiring and we decided to change the code to work with a pull up input. Once the design of the 3D Cad model was finished, we went back and changed the wiring to match up the location as close as possible. The Up and Down button location was swapped, as well as the indicated LED, in addition to the ON/OFF and MODE switch location. The wiring for the battery was also changed after creating the schematic, as I noticed that the ON/OFF button was not connected to the battery.
For one section of the code: the computer input, we discovered that type of Arduino matters. While the code was simple with one add on of a library called “Keyboard.h” and input called “Keyboard.press” the code wasn’t able to be read with Arduino Uno. With research it was due to Uno’s different USB communication from other Arduinos. With further research Ardunio Leonardo, which has a certain USB communication that can read Keyboard and mouse inputs when connected to the computer, is the best option to use if we were to make it physically.
Another issue we ran into was integrating the code with the Tinkercad model. To represent variables with different “states”, such as the current Simon Says game mode or the direction of a button or LED, we decided to use enums in our code. This way, we could use a switch-case statement to cleanly execute different blocks of code depending on which state a variable was. We tried to use two enums (game_stage and dir) and this compiled mess-free in the Arduino IDE, but unfortunately caused mysterious bugs when pasting the code into the Tinkercad model. Unable to figure out the issue on our own, with the help of Zach, our professor, and Harshine, our TA, we were able to get the Tinkercad model working by replacing the second enum (dir) with the use of an integer (with values 0,1,2,3).
Conclusions and lessons learned:
We received a lot of helpful feedback and ideas from the final critique. One interesting idea which emerged from discussion was “Have you thought about adding a handle to the device?” We thought this was an excellent suggestion, since: 1) a handle wouldn’t be too hard to add to the design (a wide rectangular hole cut from base on the opposite side of the power/mode switches) and 2) it would make moving the keyboard (perhaps from the ground to the recliner) an easier endeavour. Other helpful suggestions appeared in the written feedback. One critiquer suggested mitigating typing fatigue by using “a nicer (mechanical) keyboard that isn’t as tiring to type on (since you don’t have to bottom the keys out)?” This suggestion ties into another provided during discussion, which was “There are large pre-made keys you can purchase which would allow you to avoid using foam.” Typing fatigue is definitely an important consideration, especially for a keyboard which works the typically unused foot and calf muscles. A feature to fine-tune during actual fabrication would be the thickness of the foam beneath the key-pedals to find a balance between sensitivity and fatigue. If this balance turns out to be difficult to obtain, looking into purchasing manufactured keys would definitely be a slightly more expensive, yet reasonable, suggestion to consider. A fourth suggestion, found in the written feedback, was to add a fifth “spacebar since a lot of games also require that button.” During the design phase, we played around with the idea of adding more keys but concluded that four was an optimal number, in terms of keeping the size fairly small and avoiding typing fatigue as much as possible.
Working remotely has been fairly strange as physical projects are more easily done in person. Overall, this experience was a positive one. Coordinating with each other online wasn’t too difficult. We found most difficulty in not being able to collaborate on unshareable files together, such as the Fusion 360 model and our Arduino code. Other than those challenges, we worked fairly cohesively, and there is not much we would do differently given the chance to work together again. Our client, Lynne, was extremely fun to work with, and she easily kept in contact with us as we worked on our device. We would’ve loved to meet with her in person and show her our work face to face, but chatting with her through Zoom has still been an enjoyable experience.
Our experience of working with an older individual was pleasant, as Lynne was very helpful in each of the steps we took, and how as a whole we were able to keep our conversation light and fun. What was difficult initially was coming up with a personalized device for Lynne from our first meeting, as many of our initial designs could have been used by the public. What helped us get through the initial challenge was, as mentioned before, how Lynne was easy to keep in contact with as we worked on our device. As we narrowed down our idea, we were able to ask additional questions, such as what games she enjoyed and what difficulties she may have with our initial foot keyboard design. Each critique we got from Lynne, we were able to slowly make the keyboard more personalized for her, which was a big help as we finalized our project. Because Lynne was tech-savvy there were no problems while working remotely, but if we were to do this again, during our initial interview, we could have asked multiple follow up questions, so we do not assume things, for example, when we initially designed it to be used on the floor rather than a recliner.
Technical details:
Design Files:
Final Project Lynne Laser Cut DXFs
TinkerCad:
TinkerCad Link: https://www.tinkercad.com/things/7FbfX3RFi6Y
Schematic:
Code:
/* Project Title: Foot Keyboard - Team Lynne Names: Sue Lee, Hojung Kim, Achilles Lin Description: This code controls the two modes of the Foot Keyboard. The Simon Says mode randomly generates sequences and asks the user to repeat the sequences in increments of 3 moves. The code receives user input from the 4 momentary pushbuttons and delivers output through 4 LEDs and the LCD screen. The Computer Input mode allows the user to use the 4 momentary pushbuttons to send arrow key controls to a USB connected computer. The code continually loops to detect changes in the mode switch, allowing the user to switch between modes at any point in either mode. Pin mapping: pin | mode | description ------|--------|------------ 10 input Left Key Momentary Pushbutton 9 input Right Key Momentary Pushbutton 8 input Up Key Momentary Pushbutton 7 input Down Key Momentary Pushbutton A0 output Left Key LED A1 output Right Key LED A2 output Up Key LED A3 output Down Key LED A4 output Mode Switch Credit: Randomized numbers code from https://www.arduino.cc/reference/en/language/functions/random-numbers/random/ License Notice: Copyright 2020 Sue Lee, Hojung Kim, Achilles Lin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include <LiquidCrystal.h> #include <Keyboard.h> LiquidCrystal lcd(12, 11, 5, 4, 3, 2); #define LBTN_PIN 10 #define RBTN_PIN 9 #define UBTN_PIN 8 #define DBTN_PIN 7 #define LLED_PIN A0 #define RLED_PIN A1 #define ULED_PIN A2 #define DLED_PIN A3 #define MODE_PIN A4 bool prev_mode; // Stages enum game_stage { OFF, MODE_DISPLAY, PRE_START, ROUND_DISPLAY, PATTERN_DISPLAY, PATTERN_INPUT, CORRECT_FEEDBACK, GAMEOVER_FEEDBACK }; /* tried to use enum but would crash tinkerCad enum dir { UP, DOWN, LEFT, RIGHT // {0,1,2,3} -> {UP,DOWN,LEFT,RIGHT} respectively }; */ // must be multiple of 3 #define MAX_PTN_LEN 30 enum game_stage curr_stage = OFF; enum game_stage prev_stage = OFF; int round_num = 1; int pattern[MAX_PTN_LEN] = { }; // initialize as zeros int pattern_index = 0; unsigned long stage_tmr = 0; // instructions constants #define MODE_DISPLAY_INTVL 2000 #define ROUND_DISPLAY_INTVL 2000 #define CORRECT_DISPLAY_INTVL 3000 #define GAMEOVER_DISPLAY_INTVL 3000 // in game constants #define PTRN_DISPLAY_INTVL 2000 #define PTRN_DISPLAY_PADDING 500 #define PTRN_INPUT_INTVL 5000 /* HELPER FUNCTIONS */ // returns the amount of time that has elapsed since stage_tmr was last set unsigned long elapsed() { return millis() - stage_tmr; } // reset the stage_tmr with millis() void reset_timer() { stage_tmr = millis(); } // returns true if the current game stage doesn't match the previous game stage bool new_stage() { return curr_stage != prev_stage; } // returns true if the current keyboard mode doesn't match the previous keyboard mode bool new_mode() { return digitalRead(MODE_PIN) != prev_mode; } // clears the LCD screen and prints str onto the LCD void clearPrintLCD(String str) { //insert LCD print code here lcd.clear(); lcd.print(str); } // return the negated button state the desired key where // {0,1,2,3} -> {UP,DOWN,LEFT,RIGHT} respectively bool readBtn(int key) { switch (key) { case 0: return !digitalRead(UBTN_PIN); case 1: return !digitalRead(DBTN_PIN); case 2: return !digitalRead(LBTN_PIN); case 3: return !digitalRead(RBTN_PIN); } return false; } // sets the LED output value of the desired key's LED where // {0,1,2,3} -> {UP,DOWN,LEFT,RIGHT} respectively void setLED(int key, bool output) { switch (key) { case 0: digitalWrite(ULED_PIN, output); break; case 1: digitalWrite(DLED_PIN, output); break; case 2: digitalWrite(LLED_PIN, output); break; case 3: digitalWrite(RLED_PIN, output); break; } } // sets the LED output values of all the key's LEDs void setAllLEDs(bool output) { for (int i = 0; i < 4; i++) { setLED(i, output); } } // initializes the simon says game round number and pattern sequence void init_game() { // reset round_num round_num = 1; // randomize pattern sequence Serial.println("Generating sequence: "); Serial.print("<"); for (int i = 0; i < MAX_PTN_LEN; i++) { pattern[i] = random(4); Serial.print(pattern[i]); if (i < MAX_PTN_LEN - 1) { Serial.print(","); } } Serial.println(">"); } void setup() { // initialize inputs/outputs Serial.begin(9600); pinMode(UBTN_PIN, INPUT_PULLUP); pinMode(DBTN_PIN, INPUT_PULLUP); pinMode(LBTN_PIN, INPUT_PULLUP); pinMode(RBTN_PIN, INPUT_PULLUP); pinMode(ULED_PIN, OUTPUT); pinMode(DLED_PIN, OUTPUT); pinMode(LLED_PIN, OUTPUT); pinMode(RLED_PIN, OUTPUT); pinMode(MODE_PIN, INPUT); lcd.begin(16, 2); // if analog input pin 0 is unconnected, random analog // noise will cause the call to randomSeed() to generate // different seed numbers each time the sketch runs. // randomSeed() will then shuffle the random function. // Note: tinkerCad generates the same "random" sequence each time randomSeed(analogRead(0)); // reads the keyboard mode and initliazes global mode variables prev_mode = digitalRead(MODE_PIN); } void loop() { // read keyboard mode bool mode_switch_state = digitalRead(MODE_PIN); if (new_mode()) { if (mode_switch_state) { // Just entered Simon Says game mode // set game stage to MODE_DISPLAY curr_stage = MODE_DISPLAY; } else { // Just entered Computer Input mode prev_stage = OFF; clearPrintLCD("CPU INPUT MODE"); Serial.println("Computer Input Mode"); } } bool ubtn_state; bool dbtn_state; bool lbtn_state; bool rbtn_state; // Check if keyboard is in Simon Says Game Mode if (mode_switch_state) { switch (curr_stage) { // MODE_DISPLAY: print "SIMON SAYS MODE" to LCD for MODE_DISPLAY_INTVL case MODE_DISPLAY: if (new_stage()) { // When entering MODE_DISPLAY stage, print "SIMON SAYS MODE" to LCD prev_stage = curr_stage; reset_timer(); clearPrintLCD("SIMON SAYS MODE"); Serial.println("MODE DISPLAY STAGE"); } else if (elapsed() > MODE_DISPLAY_INTVL) { curr_stage = PRE_START; } break; // PRE_START: print "PRESS -> TO START" until right button press (light up right LED with right button press) case PRE_START: if (new_stage()) { // When entering PRE_START stage, print "PRESS -> TO START" to LCD prev_stage = curr_stage; reset_timer(); clearPrintLCD("PRESS -> TO START"); Serial.println("PRE-START STAGE"); } // upon right key button press: light up right LED, intialize game rbtn_state = readBtn(3); if (rbtn_state) { setLED(3, HIGH); while (rbtn_state) { rbtn_state = readBtn(3); } setLED(3, LOW); curr_stage = ROUND_DISPLAY; init_game(); } break; // ROUND_DISPLAY: print "ROUND <round_num>" for ROUND_DISPLAY_INTVL case ROUND_DISPLAY: if (new_stage()) { // When entering ROUND_DISPLAY stage, print "ROUND <round_num>" to LCD prev_stage = curr_stage; reset_timer(); clearPrintLCD("ROUND "); lcd.print(round_num); Serial.println((String)"ROUND " + round_num); } else if (elapsed() > ROUND_DISPLAY_INTVL) { curr_stage = PATTERN_DISPLAY; } break; // PATTERN_DISPLAY: print "SIMON SAYS..." to LCD and display <3x round_num> moves with LEDS: PTRN_DISPLAY_INTVL millis LED ON, PTRN_DISPLAY_PADDING millis LED OFF case PATTERN_DISPLAY: if (new_stage()) { // When entering PATTERN_DISPLAY stage: print "SIMON SAYS..." to LCD, reset pattern_index prev_stage = curr_stage; reset_timer(); clearPrintLCD("SIMON SAYS..."); pattern_index = 1; Serial.println("PATTERN_DISPLAY MODE"); } // light up each LED corresponding to sequence if (pattern_index > (round_num * 3)) { // once all moves have been displayed for current round, move to next stage curr_stage = PATTERN_INPUT; } else if (elapsed() < PTRN_DISPLAY_INTVL) { // turn on LED setLED(pattern[pattern_index - 1], HIGH); Serial.println((String)"Pattern index: " + pattern_index + ", Key: " + pattern[pattern_index - 1]); delay(PTRN_DISPLAY_INTVL); } else if (elapsed() > PTRN_DISPLAY_INTVL) { // turn off LED Serial.println("Turn LED OFF"); setLED(pattern[pattern_index - 1], LOW); pattern_index++; delay(PTRN_DISPLAY_PADDING); reset_timer(); } break; // PATTERN_INPUT: print "YOUR TURN!" to LCD and accept user input of pattern, light up LEDs with button presses case PATTERN_INPUT: if (new_stage()) { // When entering PATTERN_DISPLAY stage: print "SIMON SAYS..." to LCD, reset pattern_index prev_stage = curr_stage; reset_timer(); clearPrintLCD("YOUR TURN"); pattern_index = 1; } if (pattern_index > (round_num * 3)) { // once all moves have been inputted for current round, move to next stage curr_stage = CORRECT_FEEDBACK; } else if (elapsed() > PTRN_INPUT_INTVL) { // game over if time runs out curr_stage = GAMEOVER_FEEDBACK; } else { ubtn_state = readBtn(0); dbtn_state = readBtn(1); lbtn_state = readBtn(2); rbtn_state = readBtn(3); int curr_dir = pattern[pattern_index - 1]; int input_dir = 0; // check if any button has been pressed if (ubtn_state || dbtn_state || lbtn_state || rbtn_state) { if (ubtn_state) { Serial.println("UP"); input_dir = 0; } else if (dbtn_state) { Serial.println("DOWN"); input_dir = 1; } else if (lbtn_state) { Serial.println("LEFT"); input_dir = 2; } else if (rbtn_state) { Serial.println("RIGHT"); input_dir = 3; } // turn on corresponding key LED until button unpressed setLED(input_dir, HIGH); int pressed_btn_state = readBtn(input_dir); while (pressed_btn_state) { pressed_btn_state = readBtn(input_dir); } // turn off LED setLED(input_dir, LOW); // check if correct key was pressed if (curr_dir == input_dir) { // correct key pressed pattern_index++; reset_timer(); } else { // incorrect key pressed curr_stage = GAMEOVER_FEEDBACK; } } } break; // CORRECT_FEEDBACK: print "NICE JOB!" for CORRECT_DISPLAY_INTVL case CORRECT_FEEDBACK: // print "NICE JOB!" for CORRECT_DISPLAY_INTVL if (new_stage()) { prev_stage = curr_stage; reset_timer(); clearPrintLCD("NICE JOB!"); Serial.println("CORRECT FEEDBACK STAGE"); } else if (elapsed() > CORRECT_DISPLAY_INTVL) { curr_stage = ROUND_DISPLAY; round_num++; } break; // GAMEOVER_FEEDBACK: print "GAME OVER :(" and flash all LEDs on for GAMEOVER_DISPLAY_INTVL case GAMEOVER_FEEDBACK: if (new_stage()) { prev_stage = curr_stage; reset_timer(); clearPrintLCD("GAME OVER :("); setAllLEDs(HIGH); Serial.println("GAMEOVER FEEDBACK STAGE"); } else if (elapsed() > GAMEOVER_DISPLAY_INTVL) { // turn off all LEDs curr_stage = PRE_START; setAllLEDs(LOW); } break; // default handles OFF stage; curr_stage should never be OFF default: clearPrintLCD("ERROR"); Serial.println("ERROR"); delay(500); } } else { // computer input mode Keyboard.begin(); //begin keyboard //Up Button if (readBtn(0)) { Keyboard.press(KEY_UP_ARROW); Serial.println("KEY_UP_ARROW PRESSED"); } else { Keyboard.releaseAll(); } //Down Button if (readBtn(1)) { Keyboard.press(KEY_DOWN_ARROW); Serial.println("KEY_DOWN_ARROW PRESSED"); } else { Keyboard.releaseAll(); } //Left Button if (readBtn(2)) { Keyboard.press(KEY_LEFT_ARROW); Serial.println("KEY_LEFT_ARROW PRESSED"); } else { Keyboard.releaseAll(); } //Right Button if (readBtn(3)) { Keyboard.press(KEY_RIGHT_ARROW); Serial.println("KEY_RIGHT_ARROW PRESSED"); } else { Keyboard.releaseAll(); } delay(200); Keyboard.end(); //stops keybord } prev_mode = mode_switch_state; delay(50); }
Comments are closed.