Introduction: Chording Keyboard BLE and USB

About: I specialize in electronics, but I can operate a band saw, ride a skateboard, and brew a tasty cuppa. I blog incessantly.

Skip to step two if you already know who I am and what a chording keyboard is. They are more interesting than me.

First things first, this is a computer keyboard and it types every letter you can find on a standard keyboard.

A chording keyboard is a device which relies on pressing multiple keys at once, similar to playing a chord on a guitar. Since combinations are possible chording keyboards have fewer keys than a standard keyboard. Some people even argue that because you don't have to move your fingers for each keystroke you can type faster. My argument is that fewer keys make this highly portable. The ergonomics of this device have been abandoned in favor of a highly portable keyboard. This device, the size of a computer mouse, is a full keyboard AND mouse. Not only that but it's Bluetooth 4.0 so it pairs up nicely with a mobile phone. BLE sound expensive? The Bluetooth module can be left out and it will work on USB with no changes to the code. That way you can try the keyboard with your regular computer to see if the Bluetooth is valuable to you. Heck, just use the programming and build your own keys, I'd love to see your desktop version of this keyboard.

Much of the credit for this project go to Greg Priest-Dorman who did it all first. His lessons on typing with the chording keyboard are better than I could write so I recommend them. I wrote the program for this project in Arduino's IDE but the charts I used for the key combinations were made by Greg. He developed it, I copied it, make yourself a copy from our work.

This project involves pretty ordinary materials but a few things, like liquid tape and a 3D printer might not be in your garage. Schematics are included so if you don't have a 3D printer it is possible to build your own, in fact, Greg's site has a few ideas on how to make a comfortable keyboard.

This project started very differently than it ended. The original design was going to be solely wrist-mounted, complex, lots of moving parts, servo motors, and a cabling system. That was dumb so I didn't do it. Despite my best efforts at being dumb. Check out my blog if you want to see the day-by-day trials and mishaps of this project. It took me three months to arrive at this design. And a lot of spent printer filament.

This keyboard is part of a cyberpunk costume I'm assembling. I wanted to have a futuristic/cinematic feel to the costume parts but I also want to have functional props. I could just hold some buttons glued to block and say it's a keyboard but I'd rather be able to type text messages with it.

Stay tuned for more costume parts including a lock pick holder that straps to your forearm for easy access. You'll have to source your own lock picks though.

Step 1: ​Materials and Parts

I've created a neat little package with all the 3D printer files needed for this project as well as a part list with links to anything you can't find at a hardware store. In addition to the printer files I've included the source files for anyone who wants to change something with OpenSCAD.

Alternatively the files can be downloaded right from this step.

The parts is in the package with links but here is the list to give you an idea if you want to start the project.

  • 1@ Arduino Micro
  • 1@ Adafruit EZ-Key Bluefruit
  • 1@ ADXL335 breakout board
  • 1@ Protoboard or strip board
  • 7@ 12mm tactile switches
  • 1@ 23A battery holder and battery
  • 1@ USB micro breakout board
  • 1@ USB micro male end
  • 4@ #6 black screws

Step 2: The Enclosure

If you're printing the included files, an enclosure and a lid, there's nothing more to say. Go to the next step.

If you want to make your own enclosure I'd like to share some thoughts before you start. Each of your fingers has a single switch and the thumb has three switches. Arranging seven switches isn't difficult but making it ergonomic is tricky. If, like me, you want portability over comfort just arrange the switches so that you can operate them without it falling out of your hand. Maybe put them across from one another so you pinch it as you type. Stick your switches into an eraser for prototyping, that's what I did.

Don't confine yourself to square corners, it would be feasible to put all the electronics inside a tennis ball or a blue racket ball and have a spherical keyboard. In the future I hope to build one of these. This is a good time to think outside the box. See what I did there?

If you're not building a portable keyboard there are even more options. The switches I scoped out aren't important so pick ones you like. Cherry makes awesome switches but beware of breaking the leads on the back of them. Mechanical limit switches can be turned into keyboard keys. Heck, you could use infrared proximity switches and have a keyboard with no moving parts.

Hopefully some of these ideas get wheels turning, experiment and don't be afraid to take your electronics out of a failed keyboard and put them into a design you like better. My desk is filled with failed attempts in pursuit of a design I liked.

Step 3: Circuitry

This is the hardest step. Even if you are competent at soldering this will be the most difficult step. Unless you're hand forging an enclosure from iron ore this step will be where most of the work is done. Despite being the most difficult this is straight forward. The schematics included in the package detail which pins to attach to the switches. The rest of the components are optional. If they are not used there is no need for reprogramming. If you don't attach the Bluetooth adapter you simply won't have Bluetooth capability. If you don't attach the accelerometer you simply won't have mouse movement. If you don't attach the battery holder you will need to run off USB power. If you don't attach an external USB port you will have to rely on battery and Bluetooth. It won't hurt my feelings if you don't build this with all the features.

The most important thing to remember is that the switches attach to the outside of the enclosure so I recommend wiring each switch with two wires then feeding those wires through the enclosure holes before soldering them to the Arduino Micro. Keep your wires tidy since the keyboard enclosure was meant to be snug. Excessive wire length or sloppy wiring will make this more difficult than necessary.

Step 4: Programming

Arduino Micros are super easy to program. If you're familiar with Arduino IDE then you already know what to do. If not, please explore Instructables for programming an Arduino Micro. Fortunately, all you need to do is upload the program I've written, no changes are necessary. Although, if you want to make changes you certainly may.

If you are familiar with Codebender.cc then you can upload the code right from here! Codebender is a site which allows people like us to host Arduino code for free and share it with others. It is even possible to send your code to and Arduino right from a browser. I highly recommend them and support them with my donations.

The program for the Arduino can be downloaded from this step, taken from the bundled package, found directly on Codebender or copied right out of the text in this step. Formatting gets removed when pasted into Instructables but it should run just fine.

/*
Programmed by Brian McEvoy. 24 Hour Engineer 24hourengineer.com Program is distributable for personal use. *

/ Inputs. Buttons may be addressed by name but the program expects all buttons after the pinky // to be numbered sequentially. int pinkyButton = 4; int ringButton = 5; int middleButton = 6; int indexButton = 7; int nearTButton = 8; int centerTButton = 9; int farTButton = 10;

// Program integers int prefixChord = 0; // 1 = shift (F). 2 = numlock (N). 3 = special (CN). 4 = function keys int chordValue = 0; int randomNumber01; int randomNumber02; int randomNumber03; int randomNumber04; int debounceDelay = 20; int mouseDelay = 40; int mouseValues[] = {0,0,0}; int startingMouseValues[] = {0,0,0}; int mouseDivisionValues[] = {-15,1,-1};

int sign1 = 0; int sign2 = 0;

// Booleans boolean buttons[7]; // Pinky is [0] and far thumb is [6] boolean latchingButtons[7]; boolean acquiringPresses = LOW; boolean calculateKey = LOW; boolean stickyCapsLock = LOW; boolean stickyNumlock = LOW; boolean stickySpecialLock = LOW;

void setup(){ Serial1.begin(9600); Serial.begin(9600); Serial.println("Up and runnning"); Keyboard.begin(); Mouse.begin(); randomSeed(analogRead(0)); pinMode(pinkyButton, INPUT_PULLUP); pinMode(ringButton, INPUT_PULLUP); pinMode(middleButton, INPUT_PULLUP); pinMode(indexButton, INPUT_PULLUP); pinMode(nearTButton, INPUT_PULLUP); pinMode(centerTButton, INPUT_PULLUP); pinMode(farTButton, INPUT_PULLUP); }

void loop(){ acquiringPresses = checkButtonArray(); if (acquiringPresses && onlyFarThumbPressed(farTButton)){ doMouseSTUFF(); } if (acquiringPresses){ delay(debounceDelay); // Instead of a true software debounce this will wait a moment until the first button press has settled. typingChord(); // Wait and see which keys are touched. When they are all released print the correct letter. updateShiftKeys(); // Change the prefixChord value if any of the 'locks' are set. Example, Num Lock or Caps Lock. sendKeyPress(); // Using the buttons pressed during the typingChord function determine how to handle the chord. delay(debounceDelay); // The other half of the software "debounce" for (int i = 0; i < 7; i++){ // Once a keypress has been sent the booleans should be reset. latchingButtons[i] = LOW; } chordValue = 0; } }

void doMouseSTUFF(){ for (int i = 0; i < 3; i++){ startingMouseValues[i] = analogRead(i); } delay(debounceDelay); while (onlyFarThumbPressed(farTButton)){ delay(mouseDelay); for (int i = 0; i < 3; i++){ int reading = analogRead(i); mouseValues[i] = reading - startingMouseValues[i]; } for (int i = 0; i < 3; i++){ mouseValues[i] = mouseValues[i] / mouseDivisionValues[i]; } Mouse.move(mouseValues[1], mouseValues[2], mouseValues[0]); Serial1.write(0xFD); Serial1.write((byte)0x00); Serial1.write((byte)0x03); Serial1.write((byte)0x00); // Buttons Serial1.write((byte)mouseValues[1]); // X axis Serial1.write((byte)-mouseValues[2]); // Y axis Serial1.write((byte)0x00); Serial1.write((byte)0x00); Serial1.write((byte)0x00); } }

boolean onlyFarThumbPressed(int functionMaxButton){ for (int i = functionMaxButton - 1; i > (functionMaxButton - 7); i--){ if(!digitalRead(i)){ return LOW; } } if (!digitalRead(functionMaxButton)){ return HIGH; }else{ return LOW; } }

void updateShiftKeys(){ if (stickyCapsLock){ prefixChord = 1; } if (stickyNumlock){ prefixChord = 2; } if (stickySpecialLock){ prefixChord = 3; } }

boolean checkButtonArray(){ // Update the buttons[] array with each scan. Set the acquiringPresses bit HIGH if any switch is pressed. for (int i = 0; i < 7; i++){ boolean buttonState = !digitalRead(pinkyButton + i); if (buttonState){ buttons[i] = HIGH; }else{ buttons[i] = LOW; } } for (int i = 0; i < 7; i++){ if (buttons[i]){ return HIGH; } } return LOW; }

void typingChord(){ while (acquiringPresses){ for (int i = 0; i < 7; i++){ if (buttons[i] == HIGH){ latchingButtons[i] = HIGH; } } acquiringPresses = checkButtonArray(); } }

void sendKeyPress(){ for (int i = 0; i < 7; i++){ if (latchingButtons[i] == HIGH){ chordValue = chordValue + customPower(2, i); } } Serial1.write(keySwitch(chordValue)); }

int customPower(int functionBase, int functionExponent){ int functionResult = 1; for (int i = 0; i < functionExponent; i++){ functionResult = functionResult * functionBase; } return functionResult; }

int keySwitch(int functionChordValue){ switch (functionChordValue){ case 0: prefixChord = 0; Keyboard.releaseAll(); stickyNumlock = LOW; stickyCapsLock = LOW; stickySpecialLock = LOW; return 0; // error case 1: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('w'); return 119; // 119 is equivalent to the letter 'w' case 1: prefixChord = 0; prefixChord = 0; Keyboard.print('W'); return 87; case 2: prefixChord = 0; prefixChord = 0; Keyboard.print('5'); return 53; case 3: prefixChord = 0; prefixChord = 0; Keyboard.print('%'); return 37; case 4: prefixChord = 0; Keyboard.write(198); return 198; } case 2: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('y'); return 121; // 121 is equivalent to the letter 'y' case 1: prefixChord = 0; Keyboard.print('Y'); return 89; case 2: prefixChord = 0; Keyboard.print('4'); return 52; case 3: prefixChord = 0; Keyboard.print('$'); return 36; case 4: prefixChord = 0; Keyboard.write(197); return 197; } case 3: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('u'); return 117; // 117 is equivalent to the letter 'u' case 1: prefixChord = 0; Keyboard.print('U'); return 85; case 2: prefixChord = 0; Keyboard.print('"'); Keyboard.print('"'); Keyboard.press(KEY_LEFT_ARROW); Keyboard.releaseAll(); Serial1.print('"'); Serial1.print('"'); return 0x0B; case 3: prefixChord = 0; Keyboard.print('"'); Keyboard.print('"'); Keyboard.press(KEY_LEFT_ARROW); Keyboard.releaseAll(); Serial1.print('"'); Serial1.print('"'); return 0x0B; case 4: prefixChord = 0; return 0; } case 4: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('r'); return 114; // 114 → r case 1: prefixChord = 0; Keyboard.print('R'); return 82; case 2: prefixChord = 0; Keyboard.print('3'); return 45; case 3: prefixChord = 0; Keyboard.print('#'); return 35; case 4: prefixChord = 0; Keyboard.write(196); return 196; } case 5: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print("24Eng"); Serial1.print("24Eng"); return 0; case 1: prefixChord = 0; Keyboard.print("24hourengineer.com"); Serial1.print("24hourengineer.com"); return 0; case 2: prefixChord = 0; Keyboard.print("Brian '24HourEngineer' McEvoy"); Serial1.print("Brian '24HourEngineer' McEvoy"); return 0; case 3: prefixChord = 0; Keyboard.print("Brian McEvoy"); Serial1.print("Brian McEvoy"); return 0; case 4: prefixChord = 0; Keyboard.print("Easter Egg"); Serial1.print("Easter Egg"); return 0; } case 6: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('h'); return 104; // 104 → h case 1: prefixChord = 0; Keyboard.print('H'); return 72; case 2: prefixChord = 0; Keyboard.print('0'); Keyboard.print('0'); Serial.print('0'); return 48; case 3: prefixChord = 0; Keyboard.print('0'); Keyboard.print('0'); Serial.print('0'); return 48; case 4: prefixChord = 0; return 0; } case 7: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('s'); return 115; // 115 → s case 1: prefixChord = 0; Keyboard.print('S'); return 83; case 2: prefixChord = 0; Keyboard.print('-'); return 45; case 3: prefixChord = 0; Keyboard.print('_'); return 95; case 4: prefixChord = 0; return 0; } case 8: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('i'); return 105; // 105 → i case 1: prefixChord = 0; Keyboard.print('I'); return 73; case 2: prefixChord = 0; Keyboard.print('2'); return 50; case 3: prefixChord = 0; Keyboard.print('@'); return 64; case 4: prefixChord = 0; Keyboard.write(195); return 195; } case 9: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('b'); return 98; // 98 → b case 1: prefixChord = 0; Keyboard.print('B'); return 66; case 2: prefixChord = 0; Keyboard.write(92); return 92; case 3: prefixChord = 0; Keyboard.print('|'); return 124; case 4: prefixChord = 0; Keyboard.write(205); return 205; } case 10: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('k'); return 107; // 107 → k case 1: prefixChord = 0; Keyboard.print('K'); return 75; case 2: prefixChord = 0; Keyboard.print('$'); return 36; case 3: prefixChord = 0; Keyboard.print('$'); return 36; case 4: prefixChord = 0; return 0; } case 11: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('z'); return 122; // 122 → z case 1: prefixChord = 0; Keyboard.print('Z'); return 90; case 2: prefixChord = 0; Keyboard.print('`'); return 96; case 3: prefixChord = 0; Keyboard.print('~'); return 126; case 4: prefixChord = 0; return 0; } case 12: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('d'); return 100; // 100 → d case 1: prefixChord = 0; Keyboard.print('D'); return 68; case 2: prefixChord = 0; Keyboard.print('/'); return 47; case 3: prefixChord = 0; Keyboard.print('?'); return 63; case 4: prefixChord = 0; return 0; } case 13: // This chord is open randomNumber01 = random(0, 255); randomNumber02 = random(0, 1000); randomNumber03 = random(0, 4000); switch (prefixChord){ case 0: Mouse.click(MOUSE_LEFT); delay(2); prefixChord = 0; sign1 = random(0,4); sign2 = random(0,4); Keyboard.print(randomNumber01); Serial1.print(randomNumber01); if (sign1 == 0){ Keyboard.print('/'); Serial1.print('/'); } if (sign1 == 1){ Keyboard.print('*'); Serial1.print('*'); } if (sign1 == 2){ Keyboard.print('-'); Serial1.print('-'); } if (sign1 == 3){ Keyboard.print('+'); Serial1.print('+'); } Keyboard.print(randomNumber02); Serial1.print(randomNumber02); if (sign2 == 0){ Keyboard.print('/'); Serial1.print('/'); } if (sign2 == 1){ Keyboard.print('*'); Serial1.print('*'); } if (sign2 == 2){ Keyboard.print('-'); Serial1.print('-'); } if (sign2 == 3){ Keyboard.print('+'); Serial1.print('+'); } Keyboard.print(randomNumber03); Serial1.print(randomNumber03); delay(5); Keyboard.print('\n'); return 10; case 1: prefixChord = 0; Keyboard.print("0."); Serial1.print("0."); randomNumber04 = random(0, 10); Keyboard.print(randomNumber04); Serial1.print(randomNumber04); randomNumber04 = random(0, 10); Keyboard.print(randomNumber04); Serial1.print(randomNumber04); randomNumber04 = random(0, 10); Keyboard.print(randomNumber04); Serial1.print(randomNumber04); randomNumber04 = random(0, 10); Keyboard.print(randomNumber04); Serial1.print(randomNumber04); return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; Keyboard.print(randomNumber01); Serial1.print(randomNumber01); return 0; } case 14: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('e'); return 101; // 101 → e case 1: prefixChord = 0; Keyboard.print('E'); return 69; case 2: prefixChord = 0; Keyboard.print('='); return 61; case 3: prefixChord = 0; Keyboard.print('+'); return 43; case 4: prefixChord = 0; return 0; } case 15: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('t'); return 116; // 116 → t case 1: prefixChord = 0; Keyboard.print('T'); return 84; case 2: prefixChord = 0; Keyboard.print('0'); Keyboard.print('0'); Keyboard.print('0'); Serial1.print('0'); Serial1.print('0'); return 48; case 3: prefixChord = 0; Keyboard.print('0'); Keyboard.print('0'); Keyboard.print('0'); Serial1.print('0'); Serial1.print('0'); return 48; case 4: prefixChord = 0; return 0; } case 16: stickyCapsLock = LOW; stickyNumlock = LOW; switch (prefixChord){ case 0: prefixChord = 2; return 0; // Set the 'numlock' when only the near thumb key is pressed. case 1: prefixChord = 2; return 0; case 2: stickyNumlock = !stickyNumlock; // When this is pressed a second time toggle it. prefixChord = 0; return 0; case 3: prefixChord = 2; return 0; case 4: prefixChord = 0; return 0; } case 17: stickyCapsLock = LOW; stickyNumlock = LOW; stickySpecialLock = LOW; switch (prefixChord){ case 0: prefixChord = 4; return 0; case 1: prefixChord = 4; return 0; case 2: prefixChord = 4; return 0; case 3: prefixChord = 4; return 0; case 4: prefixChord = 0; return 0; } case 18: stickyCapsLock = LOW; stickyNumlock = LOW; stickySpecialLock = LOW; switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(27); return 0x1B; case 1: prefixChord = 0; Keyboard.write(27); return 0x1B; case 2: prefixChord = 0; Keyboard.write(27); return 0x1B; case 3: prefixChord = 0; Keyboard.write(27); return 0x1B; case 4: prefixChord = 0; Keyboard.write(27); return 0x1B; } case 19: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print(';'); return 59; case 1: prefixChord = 0; Keyboard.print(':'); return 58; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 20: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print(','); return 44; case 1: prefixChord = 0; Keyboard.print('<'); return 60; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 21: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 22: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('.'); return 46; case 1: prefixChord = 0; Keyboard.print('>'); return 62; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 23: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 1: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 24: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 25: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_INSERT); return 0x01; case 1: Keyboard.write(KEY_INSERT); return 0x01; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 26: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 27: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_LEFT_CTRL); return 0xE0; case 1: prefixChord = 0; Keyboard.write(KEY_LEFT_CTRL); return 0xE0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 28: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 29: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 30: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(39); return 39; case 1: prefixChord = 0; Keyboard.write(34); return 34; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 31: switch (prefixChord){ case 0: prefixChord = 0; return 0; case 1: prefixChord = 0; return 0; case 2: prefixChord = 0; return 0; case 3: prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 32: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print(' '); return 32; // 32 → 'space' case 1: prefixChord = 0; Keyboard.print(' '); return 32; case 2: prefixChord = 0; Keyboard.print('1'); return 49; case 3: prefixChord = 0; Keyboard.print('!'); return 33; case 4: prefixChord = 0; Keyboard.write(194); return 194; } case 33: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('f'); return 102; // 102 → f case 1: prefixChord = 0; Keyboard.print('F'); return 70; case 2: prefixChord = 0; Keyboard.print('9'); return 57; case 3: prefixChord = 0; Keyboard.print('('); return 40; case 4: prefixChord = 0; Keyboard.write(202); return 202; } case 34: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('g'); return 103; // 103 → g case 1: prefixChord = 0; Keyboard.print('G'); return 71; case 2: prefixChord = 0; Keyboard.print('8'); return 56; case 3: prefixChord = 0; Keyboard.print('*'); return 42; case 4: prefixChord = 0; Keyboard.write(201); return 201; } case 35: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('v'); return 118; // 118 → v case 1: prefixChord = 0; Keyboard.print('V'); return 86; case 2: prefixChord = 0; Keyboard.print(']'); return 91; case 3: prefixChord = 0; Keyboard.print('}'); return 125; case 4: prefixChord = 0; return 0; } case 36: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('c'); return 99; // 99 → c case 1: prefixChord = 0; Keyboard.print('C'); return 67; case 2: prefixChord = 0; Keyboard.print('7'); return 55; case 3: prefixChord = 0; Keyboard.print('&'); return 38; case 4: prefixChord = 0; Keyboard.write(200); return 200; } case 37: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print(']'); return 93; // 93 → ] case 1: prefixChord = 0; Keyboard.print('}'); return 125; case 2: prefixChord = 0; Keyboard.print(']'); return 93; case 3: prefixChord = 0; Keyboard.print('}'); return 125; case 4: prefixChord = 0; return 0; } case 38: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('p'); return 112; // 112 → p case 1: prefixChord = 0; Keyboard.print('P'); return 80; case 2: prefixChord = 0; Keyboard.print('%'); return 37; case 3: prefixChord = 0; Keyboard.print('%'); return 37; case 4: prefixChord = 0; return 0; } case 39: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('n'); return 110; // 110 → n case 1: prefixChord = 0; Keyboard.print('N'); return 78; case 2: prefixChord = 0; Keyboard.print('['); return 91; case 3: prefixChord = 0; Keyboard.print('{'); return 123; case 4: prefixChord = 0; return 0; } case 40: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('l'); return 108; // 108 → l (lowercase L) case 1: prefixChord = 0; Keyboard.print('L'); return 76; case 2: prefixChord = 0; Keyboard.print('6'); return 54; case 3: prefixChord = 0; Keyboard.print('^'); return 94; case 4: prefixChord = 0; Keyboard.write(199); return 199; } case 41: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('x'); return 120; // 120 → x case 1: prefixChord = 0; Keyboard.print('X'); return 88; case 2: prefixChord = 0; Keyboard.print('&'); return 38; case 3: prefixChord = 0; Keyboard.print('&'); return 38; case 4: prefixChord = 0; return 0; } case 42: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('j'); return 106; // 106 → j case 1: prefixChord = 0; Keyboard.print('J'); return 74; case 2: prefixChord = 0; Keyboard.print('('); Keyboard.print(')'); Keyboard.press(KEY_LEFT_ARROW); Keyboard.releaseAll(); Serial1.print('('); Serial1.print(')'); return 0x0B; case 3: prefixChord = 0; Keyboard.print('('); Keyboard.print(')'); Keyboard.press(KEY_LEFT_ARROW); Keyboard.releaseAll(); Serial1.print('('); Serial1.print(')'); return 0x0B; case 4: prefixChord = 0; return 0; } case 43: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('q'); return 113; // 113 → q case 1: prefixChord = 0; Keyboard.print('Q'); return 81; case 2: prefixChord = 0; Keyboard.print('?'); return 63; case 3: prefixChord = 0; Keyboard.print('?'); return 63; case 4: prefixChord = 0; return 0; } case 44: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('m'); return 109; // 109 → m case 1: prefixChord = 0; Keyboard.print('M'); return 77; case 2: prefixChord = 0; Keyboard.print('*'); return 42; case 3: prefixChord = 0; Keyboard.print('*'); return 42; case 4: prefixChord = 0; return 0; } case 45: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('['); return 91; // 91 → [ case 1: prefixChord = 0; Keyboard.print('{'); return 123; case 2: prefixChord = 0; Keyboard.print('['); return 91; case 3: prefixChord = 0; Keyboard.print('{'); return 123; case 4: prefixChord = 0; return 0; } case 46: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('a'); return 97; // 97 → a case 1: prefixChord = 0; Keyboard.print('A'); return 65; case 2: prefixChord = 0; Keyboard.print('+'); return 43; case 3: prefixChord = 0; Keyboard.print('+'); return 43; case 4: prefixChord = 0; Keyboard.write(204); return 204; } case 47: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('o'); return 111; // 111 → o case 1: prefixChord = 0; Keyboard.print('O'); return 79; case 2: prefixChord = 0; Keyboard.print('0'); return 48; case 3: prefixChord = 0; Keyboard.print(')'); return 29; case 4: prefixChord = 0; Keyboard.write(203); return 203; } case 48: stickyCapsLock = LOW; stickyNumlock = LOW; switch (prefixChord){ case 0: prefixChord = 3; return 0; // Set the sticky 'special characters' when only the near thumb key is pressed. case 1: prefixChord = 3; return 0; case 2: prefixChord = 3; return 0; case 3: stickySpecialLock = !stickySpecialLock; prefixChord = 0; return 0; case 4: prefixChord = 0; return 0; } case 64: stickyNumlock = LOW; stickySpecialLock = LOW; switch (prefixChord){ case 0: prefixChord = 1; return 0; // Set the sticky 'shift' when only the near thumb key is pressed. case 1: stickyCapsLock = !stickyCapsLock; prefixChord = 0; return 0; case 2: prefixChord = 1; return 0; case 3: prefixChord = 1; return 0; case 4: prefixChord = 1; return 0; } case 65: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.print('\n'); return 10; // 10 → enter case 1: prefixChord = 0; Keyboard.print('\n'); return 10; // 10 → enter case 2: prefixChord = 0; Keyboard.print('\n'); return 10; // 10 → enter case 3: prefixChord = 0; Keyboard.print('\n'); return 10; // 10 → enter case 4: prefixChord = 0; Keyboard.print('\n'); return 10; // 10 → enter } case 66: switch (prefixChord){ case 0: prefixChord = 0; Mouse.click(MOUSE_RIGHT); return 0; case 1: prefixChord = 0; Mouse.click(MOUSE_RIGHT); delay(2); Mouse.click(MOUSE_RIGHT); return 0; case 2: prefixChord = 0; Mouse.click(MOUSE_RIGHT); return 0; case 3: prefixChord = 0; Mouse.click(MOUSE_RIGHT); return 0; case 4: prefixChord = 0; Mouse.click(MOUSE_RIGHT); return 0; } case 67: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_RIGHT_ARROW); return 0x07; case 1: prefixChord = 0; Keyboard.write(KEY_RIGHT_ARROW); return 0x07; case 2: prefixChord = 0; Keyboard.write(KEY_RIGHT_ARROW); return 0x07; case 3: prefixChord = 0; Keyboard.write(KEY_RIGHT_ARROW); return 0x07; case 4: prefixChord = 0; Keyboard.write(KEY_RIGHT_ARROW); return 0x07; } case 68: switch (prefixChord){ case 0: prefixChord = 0; Mouse.click(MOUSE_MIDDLE); return 0; case 1: prefixChord = 0; Mouse.click(MOUSE_MIDDLE); delay(2); Mouse.click(MOUSE_MIDDLE); return 0; case 2: prefixChord = 0; Mouse.click(MOUSE_MIDDLE); return 0; case 3: prefixChord = 0; Mouse.click(MOUSE_MIDDLE); return 0; case 4: prefixChord = 0; Mouse.click(MOUSE_MIDDLE); return 0; } case 69: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_UP_ARROW); return 0x0E; case 1: prefixChord = 0; Keyboard.write(KEY_UP_ARROW); return 0x0E; case 2: prefixChord = 0; Keyboard.write(KEY_UP_ARROW); return 0x0E; case 3: prefixChord = 0; Keyboard.write(KEY_UP_ARROW); return 0x0E; case 4: prefixChord = 0; Keyboard.write(KEY_UP_ARROW); return 0x0E; } case 70: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_DELETE); return 0x04; case 1: prefixChord = 0; Keyboard.write(KEY_DELETE); return 0x04; case 2: prefixChord = 0; Keyboard.write(KEY_DELETE); return 0x04; case 3: prefixChord = 0; Keyboard.write(KEY_DELETE); return 0x04; case 4: prefixChord = 0; Keyboard.write(KEY_DELETE); return 0x04; } case 71: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_BACKSPACE); return 0x08; case 1: prefixChord = 0; Keyboard.write(KEY_BACKSPACE); return 0x08; case 2: prefixChord = 0; Keyboard.write(KEY_BACKSPACE); return 0x08; case 3: prefixChord = 0; Keyboard.write(KEY_BACKSPACE); return 0x08; case 4: prefixChord = 0; Keyboard.write(KEY_BACKSPACE); return 0x08; } case 72: switch (prefixChord){ case 0: prefixChord = 0; Mouse.click(MOUSE_LEFT); return 0; case 1: prefixChord = 0; Mouse.click(MOUSE_LEFT); delay(2); Mouse.click(MOUSE_LEFT); return 0; case 2: prefixChord = 0; Mouse.click(MOUSE_LEFT); return 0; case 3: prefixChord = 0; Mouse.click(MOUSE_LEFT); return 0; case 4: prefixChord = 0; Mouse.click(MOUSE_LEFT); return 0; } case 73: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_LEFT_ARROW); return 0x0B; case 1: prefixChord = 0; Keyboard.write(KEY_LEFT_ARROW); return 0x0B; case 2: prefixChord = 0; Keyboard.write(KEY_LEFT_ARROW); return 0x0B; case 3: prefixChord = 0; Keyboard.write(KEY_LEFT_ARROW); return 0x0B; case 4: prefixChord = 0; Keyboard.write(KEY_LEFT_ARROW); return 0x0B; } case 74: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 1: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 2: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 3: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; case 4: prefixChord = 0; Keyboard.write(KEY_LEFT_ALT); return 0xE2; } case 75: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_PAGE_DOWN); return 0x06; case 1: prefixChord = 0; Keyboard.write(KEY_PAGE_DOWN); return 0x06; case 2: prefixChord = 0; Keyboard.write(KEY_PAGE_DOWN); return 0x06; case 3: prefixChord = 0; Keyboard.write(KEY_PAGE_DOWN); return 0x06; case 4: prefixChord = 0; Keyboard.write(KEY_PAGE_DOWN); return 0x06; } case 76: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_PAGE_UP); return 0x03; case 1: prefixChord = 0; Keyboard.write(KEY_PAGE_UP); return 0x03; case 2: prefixChord = 0; Keyboard.write(KEY_PAGE_UP); return 0x03; case 3: prefixChord = 0; Keyboard.write(KEY_PAGE_UP); return 0x03; case 4: prefixChord = 0; Keyboard.write(KEY_PAGE_UP); return 0x03; } case 77: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_DOWN_ARROW); return 0x0C; case 1: prefixChord = 0; Keyboard.write(KEY_DOWN_ARROW); return 0x0C; case 2: prefixChord = 0; Keyboard.write(KEY_DOWN_ARROW); return 0x0C; case 3: prefixChord = 0; Keyboard.write(KEY_DOWN_ARROW); return 0x0C; case 4: prefixChord = 0; Keyboard.write(KEY_DOWN_ARROW); return 0x0C; } case 78: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_END); return 0x05; case 1: prefixChord = 0; Keyboard.write(KEY_END); return 0x05; case 2: prefixChord = 0; Keyboard.write(KEY_END); return 0x05; case 3: prefixChord = 0; Keyboard.write(KEY_END); return 0x05; case 4: prefixChord = 0; Keyboard.write(KEY_END); return 0x05; } case 79: switch (prefixChord){ case 0: prefixChord = 0; Keyboard.write(KEY_HOME); return 0x02; case 1: prefixChord = 0; Keyboard.write(KEY_HOME); return 0x02; case 2: prefixChord = 0; Keyboard.write(KEY_HOME); return 0x02; case 3: prefixChord = 0; Keyboard.write(KEY_HOME); return 0x02; case 4: prefixChord = 0; Keyboard.write(KEY_HOME); return 0x02; } default: prefixChord = 0; Keyboard.releaseAll(); stickyNumlock = LOW; stickyCapsLock = LOW; stickySpecialLock = LOW; return 0; // error } }

Step 5: Assembly

The rest is easy. Use some plastic glue, or just super glue, and glue the buttons in place. Grab some #6 screws 1" long and attach the lid. Once your glue dries you should be up and running. The Bluetooth will connect with your phone the way you would connect any Bluetooth keyboard but you can always read the tutorial from Adafruit, the makers of Bluefruit. If you're only using USB then you're already golden.

Step 6:

Thank you for reading all the way to the end. Maybe you have built a keyboard and you are scrolling down by pressing [far thumb], [index], [middle] and [pinkie] or maybe you've got an idea of how you would do this differently. I hope you'll tell me either way.

The design and programming for this device took me about three months. It was a lot of hard work, exhausting at times but ultimately rewarding. At my blog you can read this project from day one and see some mistakes I made while designing. After that check out some other projects, not all of which have been made into Instructables. My cyberpunk costume is coming together and I'll be making more parts for it so come and what I'm building.