Introduction: Mini Control Pad for Photoshop (Arduino)

Here're I'll be showing you how to make a little tool to help you work in Photoshop faster!

Keyboards specifically made for PS aren't new, but they don't exactly offer what I need. As a painter, much of my time in Photoshop is spent adjusting the brush setting, and I think that simple shortcut buttons don't give me the control to match my workflow. So I decided to make my own keyboard, one which is small, unobtrusive, and has dials to give me that analog interaction that I always wanted.

The way it works is simple: to make the microcontroller interact with Photoshop, we take advantage of the default shortcuts. With a board that the computer can read as a keyboard/mouse, all we have to do is use some simple lines of code to tell the computer to read each input as a combination of key presses. Now the undo button is just a button press away!

Let's get started! For this project you'll need:

  • 1 Sparkfun ProMicro (or an Arduino Leonardo, not recommended)
  • 1 micro-USB adapter
  • 6 pushbuttons (or any number you like)
  • 10k Ohm resistors (1 for each button)
  • 1 potentiometer
  • 1 rotary encoder
  • wires, breadboard, perfboard, solder, header pins, etc.

You can use an Arduino Leonardo for this project, but the ProMicro is a much cheaper alternative that uses the same atmega32u4 chip, has more pins and comes in a much smaller form, making it perfect for a keyboard.

To program the ProMicro in the Arduino IDE you might need to set some things up first. You can read more about it in SparkFun's guide: https://learn.sparkfun.com/tutorials/pro-micro--fi...

If your computer has trouble finding the device, make sure the micro-USB you are using is not power-only and supports data transfer.

This is my first Arduino project, and is fit for beginners.

Step 1: Prototyping the Control Pad

I recommend you test your program on a breadboard first before you start soldering.

Here you can see my schematic.

Buttons 1 and 2 will be Undo and Redo, 3 to 5 are for the Brush, Eraser and Lasso tools, button 6 is a quick Save button. The encoder and the potmeter control Size and Opacity respectively.

Note that I am left-handed and designed the layout in the way that is most comfortable for me to use. See the moment you use your breadboard as an opportunity to think about what functions you'd like your controller to have, what works best for you and eventually if you'll need additional parts to make it.

Step 2: Push Buttons

The buttons are the simplest to implement. Let's take a look at the code:

#include <Keyboard.h>

const int buttons[] = {2,3,4,5,6,7,8,9}; // array of all button pins

char ctrlKey = KEY_LEFT_GUI;
// use this option for Windows and Linux:
//char ctrlKey = KEY_LEFT_CTRL;
char shiftKey = KEY_LEFT_SHIFT;
char altKey = KEY_LEFT_ALT;

void setup() { // put your setup code here, to run once:
  Serial.begin(9600);
  Keyboard.begin();  
  //Buttons -- loop through the array and check for presses
  for(int i = buttons[0]; i < (sizeof(buttons)/sizeof(buttons[0]))+buttons[0]; ++i) {
    pinMode(i, INPUT);
  }
}

boolean readButton(int pin) {
 // check and debounce buttons
 if (digitalRead(pin) == HIGH) {
   delay(10);
   if (digitalRead(pin) == HIGH) {
   return true;
   }
 }
 return false;
}
void doAction(int pin) { // perform tasks
 switch (pin) {
  // ----Shortcuts----
  //Undo
  case 4:
    Keyboard.press(ctrlKey);
    Keyboard.print('z');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  //Redo
  case 5:
    Keyboard.press(ctrlKey);
    Keyboard.print('y');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  //Brush
  case 6:
    Keyboard.press('b');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  //Eraser
  case 7:
    Keyboard.press('e');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  //Lasso
  case 8:
    Keyboard.press('l');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  //Save
  case 9:
    Keyboard.press(ctrlKey);
    Keyboard.print('s');
    Serial.print ("input");
    Serial.println(pin);
    delay(200);
    Keyboard.releaseAll();
    break;
  default:
    Keyboard.releaseAll();
    break;
 }
}

void loop() {

// 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); } } //Reset modifiers Keyboard.releaseAll();

}

They're fairly straightforward. To get the computer to recognise a button press as a key press we simply use the Keyboard.press() function. So to activate the Undo shortcut(ctrl+z), we simply use Keyboard.press(ctrlKey) and then Keyboard.press('z'). Remember you will need to include Keyboard.h, and the initialise the keyboard to access these functions.

The input pins are stored in an array, so you can easily loop through all of them in the loop() function. One easy way to access and array's length in c++ by dividing the size of whole the array by the array element, plus one element. We loop through all buttons to check if one has been pressed.

To keep things organised, I stored all of my button's actions in the switch statement of a function that takes the pin number as argument.

If you want your buttons to do different things, or want to add more buttons, simply edit the contents of the doAction function!

Because of how physical buttons work, we will need to debounce them. This is to prevent the program to read any unwanted presses caused by the springiness of the pushbuttons. There are many ways to do this, but I added a simple readButton() function that takes care of that.

Just wire up your buttons with some 10k resistors, and you should be golden!

Step 3: The Potentiometer

Now onto the potmeter:

#include <Keyboard.h>
int dial0 = 0;

void setup() {

 // put your setup code here, to run once:
  Serial.begin(9600);
  Keyboard.begin();

  //Dials
  dial0= analogRead(0);
  dial0= map(dial0, 0, 1023, 1, 20);

}

void dialAction(int dial, int newVal, int lastVal) {

  switch (dial) {
    //Opacity
    case 0:
      delay(200);
      if (newVal!=lastVal) {
        int decim = ((newVal*5)/10);
        int unit = ((newVal *5)% 10);
        if (newVal==20) {
          Keyboard.write(48+0);
          Keyboard.write(48+0);
          Serial.println("max dial 1");
        } else {
          decim=constrain(decim,0,9);
          unit=constrain(unit,0,9);
          Serial.println(newVal*2);
          Keyboard.write(48+decim);
          Keyboard.write(48+unit);
        }
      }
      dial0=newVal;
      break;
    default:
      break;
  }
}

//------------------MAIN LOOP-------------------------
void loop() {

  // put your main code here, to run repeatedly:
  //Opacity
  //delay(500);
  int val0 = analogRead(0);
  val0 = map(val0, 0, 1023, 1, 20);
  //Serial.print ("dial0: ");
  //Serial.println(val0);
  if (val0!=dial0) {
    //Do something
    dialAction(0,val0,dial0);
  }

}

The potmeter follows the same logic, but it's a little trickier.

First let's look at how we want it to work: Photoshop has some handy shortcuts to change a brush's opacity. If you press any num key, the opacity will equal that number*10. But if you press two numbers, it will read the second number as a unit, giving you more precise control.

So we want our potmeter to map it's rotation to a percentage, but we don't want to do it all the time as that would be silly. We only want to change the opacity when the potmeter is being turned. So we store an additional value which we compare to the analogRead() value and only run the action script when there is a difference.

Another issue we will run into is how we turn the analogRead's return int as an input. As there is no easy way to turn an int into a string, we'll have to use the int itself. However, if you simply write Keyboard.press(int) you will notice that the input will not be what you wanted, and instead another key will get pressed.

This is because your keyboard's keys are all coded as integers, each key having its own index. To use the num key correctly, you will have to look up their index in the ASCII table: http://www.asciitable.com/

As you can see, the num keys start at index 48. So to press the correct key, all we will have to do is add the dial's value to 48. The decimal and unit values are separate presses.

Finally, we need a way to keep the value from jumping back and forth. Because if you try using the dial with map(val0, 0, 1023, 0, 100), you'll find the results to be very jittery. Similarly to how we debounced the buttons, we'll fix this by sacrificing some of the accuracy. I found that mapping it to 1-20 and then multiplying the arguments' value by 5 to be an acceptable compromise.

To connect the potentiometer, just connect a 5V wire, a ground wire and an analog input wire and there shouldn't be any problems.

Fun fact: if you use this shortcut while a tool like the Lasso is selected, it will change the Layer's opacity instead. Something to take note of.

Step 4: The Rotary Encoder

Rotary encoders are a little like potentiometers, but without a limit to how much they can turn. Instead of an analog value, we'll be looking at the encoder's turning direction digitally. I won't go into much detail of how these work, but what you need to know is that it uses two input pins on the arduino to tell in which direction it's being turned. The rotary encoder can be trickier to work with, different encoders might require different setups. To make things easier, I bought one with PCB, which are ready to be hooked with female pins. Now, the code:

#include <Keyboard.h>

//Rotary encoder
#define outputA 15
#define outputB 14
int counter = 0; 
int aState;
int aLastState;  

void setup() {
 // put your setup code here, to run once:

  //Rotary

  pinMode (outputA,INPUT);
  pinMode (outputB,INPUT);
  // Reads the initial state of the outputA
  aLastState = digitalRead(outputA);   
}

void rotaryAction(int dir) {
  if (dir>0) {
    Keyboard.press(']');
  }
  else {
    Keyboard.press('[');
  }
  Keyboard.releaseAll();
}

//------------------MAIN LOOP-------------------------
void loop() {
 // put your main code here, to run repeatedly:  
 //Size
 aState = digitalRead(outputA);
 if (aState != aLastState){ 
    if (digitalRead(outputB) != aState) { 
      //counter ++;
      rotaryAction(1);
    } else {
      //counter --;
      rotaryAction(-1);
    }
  //Serial.print("Position: ");
  //Serial.println(counter);
  } 
 aLastState = aState; 
}


By default, Photoshop's ] and [ shortcuts increase and decrease the brush size. Just like before, we want to input those as key presses. The encoder sends a number of inputs per turn (which depends on the model), and we want to increase/decrease the brush size for each of these inputs, so you can turn the dial up or down really fast, but also be able to control it slowly with great precision.

Just like with the potmeter, we only want to run the action when the dial is being turned. Unlike the potmeter, as I explained before, the rotary encoder has two alternating inputs. We look at which of these has changed to establish the direction in which the dial is being turned.

Then depending on the direction, we press the correct key.

As long as you don't have contact issues, it should work.

Step 5: Putting It All Together

Now onto the soldering. First, we drill two holes into the perfboard to fit the two dials. the we solder the buttons and their respective resistors. I drilled two extra small holes to let the input wires pass on top to save space underneath, but this isn't necessary. There aren't many input wire so the GND and 5V wires run in parallel, but if you are feeling crafty you might want to make a matrix. I soldered the microcontroller to another, smaller perfboard, that fit underneath alongside the encoder and the potmeter. Now I solder all the wires to the ProMicro. There's no need to be creative, I just had to follow the same schematic as the one on the breadboard, but soldering in such a small place can understandably be tedious. Don't be like me, use a wire stripper and a good solder!

Finally, you might want to make a nice case for your new Photoshop buddy. One better than mine, at least!

But if you're eager to try it, use some cardboard and tape and plug in your micro-USB.

Step 6: Code + Demonstration

Be sure to test the control pad's program as you move along in the project to avoid surprises!

Here's the complete code: https://pastebin.com/ULWag0Eb

Thank you so much for reading!

First Time Author Contest 2018

Participated in the
First Time Author Contest 2018

Arduino Contest 2017

Participated in the
Arduino Contest 2017