Handheld Shortcut Controller (for Photoshop + More) [Arduino]
by NikeC2 in Circuits > Arduino
1658 Views, 8 Favorites, 0 Comments
Handheld Shortcut Controller (for Photoshop + More) [Arduino]
Last time I created a tiny control pad to use in Photoshop. It worked wonders, and I still use it! But it's also rather limited, with just five buttons and the useful size and opacity dials. I still found myself reaching for the keyboard a lot...
So I started working on the next iteration of the control pad, one with way more buttons and functionality. One control pad to rule them all.
This is not that control pad. BUT in a way it might be better.
What if you could have a ton of shortcuts, but in a super snug and lightweight package you can hold with your free hand while you draw uninterrupted? ...ok, enough with the infomercial.
This controller is programmed in a way that, with just 4 buttons, it can be mapped to up to 32 possible shortcuts! The additional 5th button is there to allow me to use modifier keys in any combination, which is useful for many programmes (did you ever try the Alt-RMB combo in PS? If you haven't, please do. It's a lifesaver). I explain the system later on.
To make all of this you will need:
- 1 Microcontroller (I used an Adafruit ItsyBitsy 32u4 but any should do as long as it has the atmega32u4 chip)
- 1 micro-USB adapter (data, not power-only)
- 5 push buttons (I used soft ones, like these)
- 10k Ohm resistors (1 per button)
- Wires, breadboard, solder material, etc.
- Something to make a casing with (3D printer, etc.)
This is an intermediate-level Arduino project, and I suggest looking at my past tutorial to better understand what is going on, as much of this is a repeat of the things I explained there.
Ok, let's get started!
Planning
This is a basic schematic I drew of the controller. The circuit is really simple when you compare it to my previous project! But we will be able to do much more with the few buttons it has, with the power of combined presses!
The idea behind the control scheme is that each button can either be free, pressed and released, or pressed and held. Pressing and releasing is what will actually activate the shortcut, while holding down buttons will give us access to different shortcuts. So if you just press button A, you will activate shortcut A, but if you hold B when pressing A, you will get a different shortcut. You can hold up to 3 buttons at once while pressing, so when you do apply some basic combinatorics, you will see just how many combinations are possible with this system!
The extra fifth button felt like a natural addition, given the shape of the handheld I came up with. I decided to use it to access modifier keys in photoshop. The way it works is slightly different from the other buttons: whenever the thumb button is held, only modifiers will be used. These will activate when they are held and multiple can be pressed. So if button A is Shift, and button B is Ctrl, when you hold down A and B it will be like pressing both Shift and Ctrl, but only for as long as the thumb button is held!
The shell is designed to be both ergonomic and ambidextrous. I took great care to make it fit snugly so that using the little finger isn't too tiresome, and it should work for those with hands bigger than mine too.
Prototype + Code
It's good practice to test the buttons on a breadboard. It is pretty simple, just connect the buttons and resistors as shown. You can test it with the code here (pastebin link alternative):
#include <Keyboard.h>
// use vthisv option for MacOS:
//char ctrlKey = KEY_LEFT_GUI;
// use vthisv option for Windows and Linux: char ctrlKey = KEY_LEFT_CTRL; char shiftKey = KEY_LEFT_SHIFT; char altKey = KEY_LEFT_ALT;
//Function Keys here char Fn1Key = KEY_F2; char Fn2Key = KEY_F3; char Fn3Key = KEY_F4; char Fn4Key = KEY_F5;
const int pins[] = {9,10,11,12,13}; // array of all button pins
//Sensibility const int THRESH_0 = 10; const int THRESH_1 = 20; const int THRESH_2 = 25; const int THRESH_3 = 50; const int THRESH_4 = 100; const int THRESH_5 = 200;
const int BUTTON_NUM = 5;
//Freeze frames const int DELAY = 0;
enum States { freed, pressed, held, released };
struct button { int pin; States state; int timeHeld; }; //thumb, index, mid, ring, little;
button buttons[BUTTON_NUM] = {};
button initButton(int p) { button b; pinMode(p, INPUT); b.pin = p; b.state = States::freed; b.timeHeld = 0; return b; }
void setup() { // put your setup code here, to run once: Serial.begin(9600); Keyboard.begin();
while(!Serial){}; //Buttons for(int i = 0; i < (BUTTON_NUM); ++i) { Serial.print("set button "); Serial.print(i); Serial.print(" at pin: "); Serial.println(pins[i]); //buttons[i].pin = 1; buttons[i]=initButton(pins[i]); Serial.println(buttons[i].pin); }
}
bool readButton(int pin) { // check and debounce buttons if (digitalRead(pin) == HIGH) { delay(10); if (digitalRead(pin) == HIGH) { return true; } } return false; }
int pintobin(int pin) { if (pin==pins[0]) return 1; if (pin== pins[1]) return 10; if (pin== pins[2]) return 100; if (pin== pins[3]) return 1000; if (pin== pins[4]) return 10000; } button buttonStateUpdate(button b) {
bool press = readButton(b.pin);
switch (b.state) { case States::freed: b.timeHeld=0; if (press) b.state=States::pressed; break; case pressed: b.timeHeld+=1; if (press) { if (b.timeHeld>(THRESH_1/(1+DELAY))) { b.state=States::held; } } else { //if (b.timeHeld
int getButtonStateCode(button b) { return b.state*pintobin(b.pin); }
int getCodeByButton(int code, int index) { int r1,r2,r3,r4,r5; int opStep = BUTTON_NUM - (1+index);
//first operation if (opStep==0) return code/10000; r1 = code%10000;
if (opStep==1) return r1/1000; r2 = r1%1000; if (opStep==2) return r2/100; r3 = r2%100; if (opStep==3) return r3/10; r4 = r3%10; if (opStep==4) return r4/1; r5 = r4%1; }
void completePress(int pin) { // Serial.print ("input"); // Serial.println(pin); delay(THRESH_3); Keyboard.releaseAll(); }
void doAction(int code) { //Modifiers if (getCodeByButton(code, 0)==2) { // Serial.println("---modifiers----"); if (getCodeByButton(code,1)>0) { Keyboard.press(altKey); // Serial.println("-------alt---------"); } else Keyboard.release(altKey); if (getCodeByButton(code,2)>0) { Keyboard.press(ctrlKey); // Serial.println("--------ctrl----------"); } else Keyboard.release(ctrlKey); if (getCodeByButton(code,3)>0) { Keyboard.press(' '); } else Keyboard.release(' '); if (getCodeByButton(code,4)>0) { Keyboard.press(shiftKey); // Serial.println("------shift------"); } else Keyboard.release(shiftKey); } else {
// perform tasks switch (code) { case 30: //---| Brush Keyboard.press(shiftKey); Keyboard.print('b'); completePress(code); break; case 300: //---| Eraser Keyboard.press(shiftKey); Keyboard.print('e'); completePress(code); break; case 3000: //---| Bucket Keyboard.press(shiftKey); Keyboard.print('g'); completePress(code); break; case 30000: //---| Lasso Keyboard.press(shiftKey); Keyboard.print('l'); completePress(code); break; case 320: //--|o Undo Keyboard.press(ctrlKey); Keyboard.print('z'); completePress(code); break; case 3020: //-|-o Redo Keyboard.press(ctrlKey); Keyboard.print('y'); completePress(code); break; case 30020: //|--o History Keyboard.press(shiftKey); Keyboard.print('y'); completePress(code); break; case 230: //--o| Save Keyboard.press(ctrlKey); Keyboard.print('s'); completePress(code); break; case 3200: //-|o- Quick PNG Keyboard.press(shiftKey); Keyboard.press(ctrlKey); Keyboard.print('\''); completePress(code); break; case 22230: //ooo| Fn1 Keyboard.press(Fn1Key); completePress(code); break; case 22320: //oo|o Fn2 Keyboard.press(Fn2Key); completePress(code); break; case 23220: //0|00 Fn3 Keyboard.press(Fn3Key); completePress(code); break; case 32220: //|ooo Fn4 Keyboard.press(Fn4Key); completePress(code); break; } } } int f = 0; //------------------MAIN LOOP------------------------- void loop() {
int buttonCode=0; for(int i = 0; i < BUTTON_NUM; ++i) { buttons[i]=buttonStateUpdate(buttons[i]); buttonCode+=getButtonStateCode(buttons[i]); }
if(buttonCode!=0) { Serial.print("button code: "); Serial.println(buttonCode); }
doAction(buttonCode); // put your main code here, to run repeatedly: // for(int i = buttons[0]; i < sizeof(buttons)/sizeof(buttons[0])+buttons[0]; ++i) { // // if (readButton(i)) { // doAction(i); // } // }
if (getCodeByButton(buttonCode,0)!=2) Keyboard.releaseAll();
delay(DELAY); }
There isn't much to say about the logic as it's similar to that of my last controller, with two notable differences:
- The buttons are structs with their own state machines
- The states are summed together to make a code that determines the action
The principle is similar to bit-shifting, but because the buttons have multiple states and can't simply be represented with a binary, they are multiplied by powers of ten instead. I then add up all the button states into a single number, and pass it onto the doAction() switch statement, where I put all the shortcuts code.
As you can see, I didn't map every possible combination. I only added a few of my favourite shortcuts, I leave it up to you to fill in the rest how you best see fit ;)
The Casing
I used a 3D printer for the casing. As you can see, the design has some flaws and I had to MacGyver a way to just close it. So I won't be posting the model file just yet.
The buttons are hot-glued onto "benches" so that they keep the caps in place. The soft pushbuttons are especially good at that, so be sure to get some of those if you plan on making a case similar to mine.
Also, I suggest adding a little weight inside the case, as it is very light. The extra grams will make holding it feel more natural.
Solder everything as shown and connect the usb cable, and everything should fit into place (with the help of some glue)!
Result and Possible Improvements
There you have it! A handheld controller you can use to access all your important shortcuts with just one hand!
It takes some muscle-memory to use, but it's really versatile!
Of course it's not perfect, and right now I'm thinking about some ways to improve it. Aside from improving the casing and adding the shortcuts, I think I'd be interesting to support multiple applications with different shortcuts. I'm thinking of having a button combination to switch between control schemes, like pressing 4 buttons at the same time to switch between a Photoshop shortcut library to one-tailor made for Maya.
Just some ideas.
Thanks for reading, until next time!