Advertisement
Blogs
Advertisement

Turn your ProMicro into a USB keyboard-mouse

Tue, 02/28/2012 - 10:59am
Jim Lindblom, Engineer, SparkFun Electronics
SparkFun Pro MicroHow cool would it be if you turn any button, joystick, sensor, or other electronic gizmo into a USB keyboard and/or mouse? You could make just about any input device you want into a controller for your computer programs or games. What if you could do it with one line of code inside the comfy confines of Arduino? Interested? Well, say hello to my micro friend! The SparkFun Pro Micro!

The Pro Micro is a tiny, Arduino-compatible microcontroller centered around an ATmega32U4 - an 8-bit AVR very similar to the ATmega328 with one huge caveat; the 32U4 comes equipped with a full-speed USB transceiver. So now, when you connect the Arduino to your computer, they're connected directly over the USB bus (excuse my RAS). The Pro Micro can be programmed to emulate any USB device you could imagine. You can even program it to act just like a mouse, keyboard, or other HID-class USB device.

What is HID you might ask? It's one of the many defined USB device classes. Every USB device is assigned a device class, which defines what exactly its general purpose is. There are loads of classes - printers, hubs, speakers, webcams to mention a few - but this tutorial is going to specialize in HID. Human interface device. You might be holding an HID device right now, all while your other hand is hovering over another.

So, the ATmega32U4 takes care of the USB-hardware hurdle, but we've still got to clear the firmware one. Fortunately for us, Arduino exists, and with the release of 1.0 it comes with built-in support for the ATmega32U4 and its USB core.That USB support includes HID functionality. Unfortunately, that HID stuff is somewhat tucked (even locked) away from us. The goal of this tutorial is to explain how to use the Arduino HID library, so you can get the most out of your Pro Micro. So lets pop the hood!

Part 1: A simple HID Keyboard
I stress "simple" at the header of this section, because that's what it is. And that's a great thing! There are essentially two functions you'll need to turn your Pro Micro into a USB keyboard:

Keyboard.write(char) - This function will send a single character over USB. The character passed can be any standard, printable, ASCII-defined character - 0-9, a-z, A-Z, space, symbols, etc. Here's an example line of code: 
        · Keyboard.write('z'); - This will send a single 'z' character to your computer. Note those are single-quote's around the character. You could also replace 'z' with a pre-defined char variable.
Keyboard.print(string) - If you need to perform a series a Keyboard.write()'s, consider using just a single Keyboard.print(). This works similar to Serial.print() - give it a string of characters and it'll send that stream of characters over USB. An example of that: 
        · Keyboard.print("Hello, world"); - The "Hello, world" of the Arduino HID class. This'll send your computer an 'H', followed by 'e', followed by an 'l', followed by...you get the picture. You could also replace the "Hello, world" with either a String variable, or an array of char's.
Keyboard.println(string) - Just like Keyboard.print() except this adds an [Enter] key to the end of the string.

That's it. You don't need to include any libraries or anything. Just pull out either of those two functions.

After a Keyboard.write() or Keyboard.print() function has been performed by the Pro Micro, your computer will have to decide what to do with it. What your computer does with that character, or string of characters, is entirely dependent on what program it's running at the time. If you have a text editor open and active, it'll print it out there.

The most straightforward example I can think of is tying a single button to a single key-press. Give this example a quick try (copy/paste from below, or download the sketch here). If you have a button handy, tie one end to pin 9, and the other to ground. Or, if you don't have a button, just use a wire to short pin 9 to ground. 

Code 1

When pin 9 is grounded your computer should receive a 'z' character. When you do activate the keypress, make sure you have an application open that will be able to do something with a 'z'. If you want, just leave the Arduino sketch active, and it should type a 'z' into the sketch editor.

I'd also like to point out that the delay(1000) is an important part of that sketch. The 1000 is arbitrary, but the delay() is not. Try taking the delay out, your computer will try to catch some z's (zzzzzzzzzzz). Needless to say, you've got to watch out what you're sending to the computer, as well as how fast and how often.

HID USB Keypad
When I'm using my laptop, I often find myself lamenting the absence of a keypad. But with the Pro Micro, I could make a keypad!

If you pair the Pro Micro with our 12-Button Keypad, you're just a few wires away from your very own DIY USB keypad. Here's an example schematic for my DIY USB Keypad.

12-Button Keypad

Feel free to connect any of the keypad pins to any digital pin of the Pro Micro. That was the easiest way for me to wire it all on a breadboard.

Feel free to connect any of the keypad pins to any digital pin of the Pro Micro.

Click here to download the keypadHID code or copy and paste from below.

/* keyPadHiduino Example Code
   by: Jim Lindblom
   date: January 5, 2012
   license: MIT license. If you find this code useful, please
   feel free to use this code however you'd like, commercially 
   or otherwise. Just keep this license on there whatever you do.

   This code implements a 12-key USB keypad. You can type 0-9,
   * is the + sign and the # key is enter. I'm using SparkFun's
   12-button keypad, your pinouts may vary. Multi-touch is
   not supported.

   SparkFun Keypad Pinout:
   Rows and columns are connected as such:
   -------------
   | 1 | 2 | 3 | - 3
   | 4 | 5 | 6 | - 7
   | 7 | 8 | 9 | - 6
   | * | 0 | # | - 1
   -------------
     |   |   |
     2   4   5
*/
// Pins 1-7 of the keypad connected to the Arduino respectively:
int keypadPins[7] = {2, 3, 4, 5, 10, 16, 14};
int keypadStatus;  // Used to monitor which buttons are pressed.
int timeout;  // timeout variable used in loop

void setup()
{
  for (int i=0; i<7; i++)
  {
    pinMode(keypadPins[i], INPUT);  // Set all keypad pins as inputs
    digitalWrite(keypadPins[i], HIGH);  // pull all keypad pins high
  }
}

void loop()
{
  keypadStatus = getKeypadStatus();  // read which buttons are pressed
  if (keypadStatus != 0)  // If a button is pressed go into here
  {
    sendKeyPress(keypadStatus);  // send the button over USB
    timeout = 2000;  // top of the repeat delay
    while ((getKeypadStatus() == keypadStatus) && (--timeout))  // Decrement timeout and check if key is being held down
      delayMicroseconds(1);
    while (getKeypadStatus() == keypadStatus)  // while the same button is held down
    {
      sendKeyPress(keypadStatus);  // continue to send the button over USB
      delay(50);  // 50ms repeat rate
    }
  }
}

/* sendKeyPress(int key): This function sends a single key over USB
   It requires an int, of which the 12 LSbs are used. Each bit in
   key represents a single button on the keypad.
   This function will only send a key press if a single button
   is being pressed */
void sendKeyPress(int key)
{
  switch(key)
  {
    case 1:  // 0x001
      Keyboard.write('1');  // Sends a keyboard '1'
      break;
    case 2:  // 0x002
      Keyboard.write('2');
      break;
    case 4:  // 0x004
      Keyboard.write('3');
      break;
    case 8:  // 0x008
      Keyboard.write('4');
      break;
    case 16:  // 0x010
      Keyboard.write('5');
      break;
    case 32:  // 0x020
      Keyboard.write('6');
      break;
    case 64:  // 0x040
      Keyboard.write('7');
      break;
    case 128:  // 0x080
      Keyboard.write('8');
      break;
    case 256:  // 0x100
      Keyboard.write('9');
      break;
    case 512:  // 0x200
      Keyboard.write('+');
      break;
    case 1024:  // 0x400
      Keyboard.write('0');  // Sends a keyboard '0'
      break;
    case 2048:  // 0x800
      Keyboard.write('\n');  // Sends the 'ENTER' key
      break;
  }
}

/* getKeypadStatus(): This function returns an int that represents
the status of the 12-button keypad. Only the 12 LSb's of the return
value hold any significange. Each bit represents the status of a single
key on the button pad. '1' is bit 0, '2' is bit 1, '3' is bit 2, ..., 
'#' is bit 11.

This function doesn't work for multitouch.
*/
int getKeypadStatus()
{
  int rowPins[4] = {keypadPins[2], keypadPins[6], keypadPins[5], keypadPins[0]};  // row pins are 2, 7, 6, and 1 of the keypad
  int columnPins[3] = {keypadPins[1], keypadPins[3], keypadPins[4]};  // column pins are pins 2, 4, and 5 of the keypad
  int keypadStatus = 0;  // this will be what's returned
  
  /* initialize all pins, inputs w/ pull-ups */
  for (int i=0; i<7; i++)
  {
    pinMode(keypadPins[i], INPUT);
    digitalWrite(keypadPins[i], HIGH);
  }
  
  for (int row=0; row<4; row++)
  {  // initial for loop to check all 4 rows
    pinMode(rowPins[row], OUTPUT);  // set the row pin as an output
    digitalWrite(rowPins[row], LOW);  // pull the row pins low
    for (int col=0; col<3; col++)
    {  // embedded for loop to check all 3 columns of each row
      if (!digitalRead(columnPins[col]))
      {
        keypadStatus |= 1 << ((row+1)*3 + (col+1) - 4);  // set the status bit of the keypad return value
      }
    }
    pinMode(rowPins[row], INPUT);  // reset the row pin as an input
    digitalWrite(rowPins[row], HIGH);  // pull the row pin high
  }
  
  return keypadStatus;
}

Now just open up your computer's calculator and start adding. The * button is configured as the '+' sign, and the # button is configured as the [Enter] key.

There are three functions at work here. The getKeypadStatus() function reads each button of the keypad and returns an int. Only 12-bits of the value returned by getKeypadStatus() are used, each representing the status of each key – 1 for pressed, 0 for not pressed.

The sendKeyPress(int) function is where the HID magic happens. It'll interpret the value returned by getKeypadStatus() and send a keystroke to the computer based upon that value.

In the loop() we continuously check for a keypress. If a keypress is detected, we send the corresponding character to the computer. There is a bit of funkiness in the loop(), as we're trying to emulate a repeat delay, and then a repeat rate. The repeat delay is that short, one-time delay where the key must be held down before it starts repeating endlessly. The repeat rate is a short delay (50ms) to slow the spamming of characters.

Want to get some use out of Keyboard.print()? Try replacing Keyboard.write('1'), in the above example, with Keyboard.println("Password1234"). Now, when I press 1 on the keypad, it prints out "Password1234 [ENTER]", and I can log into my computer after pressing just one button! If you're as bad at remembering passwords as me (especially when you're forced to change it every few months *eyes SparkFun IT*), perhaps you might benefit from this password-typer. It's not all that secure, but it's probably more-so than writing it down on a slip of paper that you hide under your keyboard (don't look under my keyboard!).

Part 2: HID USB Mouse
That covers about half of USB HID library, how about we add a mouse to the mix now? Implementing a USB HID mouse requires a few more functions, but it's still crazy simple. There are five functions provided by Arduino's HID class that can be used to implement a mouse:

Mouse.move(x, y, wheel) - This function tells the computer to move the mouse a certain number of pixels along either the x, y and/or wheel axis. Each variable can be any value between -128 and +127, negative numbers moving the cursor down/left, positive numbers move the right/up.
Mouse.press(b) - This function sends a down-click on button(s) (one or more) b. The button(s) will remain "pressed" until you call Mouse.release(b). The b variable is a single byte, each bit of which represents a different button. You can set it equal to any of the following, or OR them together to click multiple buttons at once: 
        · MOUSE_LEFT - Left Mouse button 
        · MOUSE_RIGHT - Right Mouse button 
        · MOUSE_MIDDLE - Middle mouse button 
        · MOUSE_ALL - All three mouse buttons
Mouse.release(b) - Similar to Mouse.press(b), this function sends an up-click signal to the computer to tell it that a button has been released.
Mouse.click(b) - This function sends a down-click (press) followed immediately by an up-click (release) acting on button(s) b.

Ever want to control your computer's cursor with a joystick? Well, you may not want to after this example (it's not exactly precise...) but at least it'll help show how to use the Mouse class. For those playing along, I'm using our Thumb Joystick, planted into its breakout, and connected to the Pro Micro.

For those playing along, I'm using our Thumb Joystick, planted into its breakout, and connected to the Pro Micro.

The joystick is powered by the Pro Micro's VCC and GND pins. The horizontal analog output is connected to A0, vertical to A1, and the select switch is connected to D9. As always, feel free to hook it up however you want, just make sure to change the pin definitions near the top of the code.

Here's the code: 

  
/* HID Joystick Mouse Example
   by: Jim Lindblom
   date: 1/12/2012
   license: MIT License - Feel free to use this code for any purpose.
   No restrictions. Just keep this license if you go on to use this
   code in your future endeavors! Reuse and share.
 
   This is very simplistic code that allows you to turn the 
   SparkFun Thumb Joystick (http://www.sparkfun.com/products/9032)
   into an HID Mouse. The select button on the joystick is set up
   as the mouse left click. 
 */
int horzPin = A0;  // Analog output of horizontal joystick pin
int vertPin = A1;  // Analog output of vertical joystick pin
int selPin = 9;  // select button pin of joystick

int vertZero, horzZero;  // Stores the initial value of each axis, usually around 512
int vertValue, horzValue;  // Stores current analog output of each axis
const int sensitivity = 200;  // Higher sensitivity value = slower mouse, should be <= about 500
int mouseClickFlag = 0;

void setup()
{
  pinMode(horzPin, INPUT);  // Set both analog pins as inputs
  pinMode(vertPin, INPUT);
  pinMode(selPin, INPUT);  // set button select pin as input
  digitalWrite(selPin, HIGH);  // Pull button select pin high
  delay(1000);  // short delay to let outputs settle
  vertZero = analogRead(vertPin);  // get the initial values
  horzZero = analogRead(horzPin);  // Joystick should be in neutral position when reading these

}

void loop()
{
  vertValue = analogRead(vertPin) - vertZero;  // read vertical offset
  horzValue = analogRead(horzPin) - horzZero;  // read horizontal offset
  
  if (vertValue != 0)
    Mouse.move(0, vertValue/sensitivity, 0);  // move mouse on y axis
  if (horzValue != 0)
    Mouse.move(horzValue/sensitivity, 0, 0);  // move mouse on x axis
    
  if ((digitalRead(selPin) == 0) && (!mouseClickFlag))  // if the joystick button is pressed
  {
    mouseClickFlag = 1;
    Mouse.press(MOUSE_LEFT);  // click the left button down
  }
  else if ((digitalRead(selPin))&&(mouseClickFlag)) // if the joystick button is not pressed
  {
    mouseClickFlag = 0;
    Mouse.release(MOUSE_LEFT);  // release the left button
  }
}

The loop() of this code continuously monitors the horizontal and vertical analog values of the joystick, and sends the Mouse.move() command based on what it reads. It'll move the mouse in steps, depending on what the sensitivity variable is set to. With sensitivity set to 2, the cursor will move in either 1 or 2 pixel steps.

It's important to note that the variables used by Mouse.move() - vertValue and horzValue - can be either negative or positive; their sign determines whether the mouse moves up/down or left/right.

The select switch on the joystick is used to control the mouse left click. Notice this code is using Mouse.press() and Mouse.release(), rather than just calling a single Mouse.click(). This requires a bit more coding, but it allows you to do things like drag-and-drop, double click, etc.

Part 3: Keyboard Modifiers and Non-ASCII Characters
So, that's the sum of what's provided for in the Arduino 1.0 HID library. Enough to emulate just about every mouse and keyboard action there is. However, if you're like me, you may be left wanting a little bit more. For instance, what if you want to send non-ASCII-standard keys to your computer, like the function keys, arrow keys, or even modifiers like CTRL, ALT and SHIFT? The HID library doesn't really support those by default, but we can fix that...

I'll preface this section by saying we're going to be doing a little code hacking within Arduino's core libraries. It's nothing that should cause any problems to your Arduino install - really just one very minor change - but you will need to be careful about how you use the code in this section.

Using a programming text editor, you'll need to open up USBAPI.h, in the hardware\arduino\cores\arduino directory of your Arduino install. Any text editor will do, but it's best to use a code editor (Notepad++ or Programmer's Notepad are good ones).

Arduino example

The Keyboard class definition starts at line 96, and should look like this: 

The Keyboard class definition starts at line 96, and should look like this

To send special characters and modifiers, we need to use the void sendReport(KeyReport) member function. Unfortunately, that function is currently listed under the private: label, which means we can't access it in our Arduino sketch. To make it usable, though, all you need to do is move it under the public: label. So, cut the entire void sendReport(keyReport* keys) line, and paste it just under the public: line. In the end your Keyboard class prototype should look something like this: 

In the end your Keyboard class prototype should look something like this

Remember to save the header file, and close it. That's all the code hacking you have to do!

To use Keyboard.sendReport() you'll need to give it a KeyReport, which is a data structure (here's a good read for those unfamiliar) defined in USBAPI.h. Here's how it's defined: 

Here's how it's defined

A KeyReport contains information like what keys are pressed, and if there are any keys (SHIFT, CTRL, ALT, GUI) modifying it. Each KeyReport has three member variables: keys[6], modifiers, and reserved. We won't make any use of reserved, but the other two are important.

The keys[6] array can store up to six key presses at any one time. For the most part the first value of the array, keys[0], is all you need. The actual value assigned to this variable is not ASCII though, it's is an HID usage ID. The USB standard assigns specific ID's for each-and-every button on a keyboard; they're all listed in this official HID Usage Table document (the table starts on page 53). Click the image below to see a HUGE listing of all the defined HID Keyboard Usages (your browser should allow you to zoom in on the image): 

Click the image below to see a HUGE listing of all the defined HID Keyboard Usages (your browser should allow you to zoom in on the image)

Basically, what this all means is you can't set keys[0] to the character 'A', because the ASCII value of 'A' (0x41) is not the same as the Usage value of 'A' (0x04).

The modifiers variable controls which, if any, modifiers are acting on a key press. You can set modifiers equal to any of the following macros:

• Shift keys: KEY_MODIFIER_LEFT_SHIFT, KEY_MODIFIER_RIGHT_SHIFT
• Control (CTRL) keys: KEY_MODIFIER_LEFT_CTRL, KEY_MODIFIER_RIGHT_CTRL
• ALT keys: KEY_MODIFIER_LEFT_ALT, KEY_MODIFIER_RIGHT_ALT
• GUI (Windows/Command) keys: KEY_MODIFIER_LEFT_GUI, KEY_MODIFIER_RIGHT_GUI
You can also OR (|) any of those macros together, to specify multiple modifiers at the same time.

Once we have all the values we need set up in a keyReport, we can finally send it over to the sendReport(keyReport) function, which will report all keys and modifiers to the computer.

Clear as mud? Let's just jump into the code.

/* keyPadHiduino Advanced Example Code
   by: Jim Lindblom
   date: January 5, 2012
   license: MIT license - feel free to use this code in any way
   you see fit. If you go on to use it in another project, please
   keep this license on there.
*/

#define KEY_DELETE 0x4C
void sendKey(byte key, byte modifiers = 0);
int buttonPin = 9;  // Set a button to digital pin 9

void setup()
{
  pinMode(buttonPin, INPUT);  // Set the button as an input
  digitalWrite(buttonPin, HIGH);  // Pull the button high
}

void loop()
{
  if (digitalRead(buttonPin) == 0)  // if the button goes low
  {
    sendKey(KEY_DELETE, KEY_MODIFIER_LEFT_CTRL | KEY_MODIFIER_LEFT_ALT);  // send a CTRL+ALT+DEL to the computer via Keyboard HID
    delay(1000);  // Delay so as not to spam the computer
  }
}

void sendKey(byte key, byte modifiers)
{
  KeyReport report = {0};  // Create an empty KeyReport
  
  /* First send a report with the keys and modifiers pressed */
  report.keys[0] = key;  // set the KeyReport to key
  report.modifiers = modifiers;  // set the KeyReport's modifiers
  report.reserved = 1;
  Keyboard.sendReport(&report);  // send the KeyReport
  
  /* Now we've got to send a report with nothing pressed */
  for (int i=0; i<6; i++)
    report.keys[i] = 0;  // clear out the keys
  report.modifiers = 0x00;  // clear out the modifires
  report.reserved = 0;
  Keyboard.sendReport(&report);  // send the empty key report
}

This example uses the hardware setup in the very first bit of code - a single button attached to pin 9.

This time, I've added a new function - sendKey(byte key, byte modifiers) - which can be passed both a key (an HID usage ID) and modifiers. It'll take care of setting up the KeyReport variable, and sending all the necessary reports.

CTRL+ALT+DELIt shouldn't take much to guess what's being passed to the sendKey() function in that example. It's the three-finger salute, CTRL+ALT+DEL. Windows users should be familiar with CTRL+ALT+DEL, for them grounding pin 9 should either call the task manager, or open the Windows Security dialog.

Mac/Linux people, I'm not sure if that'll do anything for you. If you'd rather not have a CTRL+ALT+DEL key, you can replace the modifier parameters with KEY_MODIFIER_LEFT_CTRL (or KEY_MODIFIER_LEFT_GUI for Macs), and the key value with 0x06 (the [C] key usage id). Now you've got a button devoted to copying, and it shouldn't take much more to add a paste key.

Look closely at the sendKey() function. Notice that we have to send two different HID reports. The first report sends our key, and any modifiers that may be included. The second report sends a completely blanked out KeyReport, which is necessary to tell the computer that the key has been released. This is probably why Arduino kept sendReport() as a private function, it's got a lot of mess-making potential if used incorrectly. Be careful!

You'll notice that KEY_DELETE is defined as 0x4C, which matches the "Usage ID (Hex)" value of "Keyboard Delete Forward" in that HID Usage Table document I linked above. Use that as an example for any other keys you'd like to use. There's some intriguing keys defined in there...F24 (0x73)? Who knew it went that high? Volume Up/Volume Down (0x80, 0x81)? Media player controller? RightArrow, LeftArrow, DownArrow, and UpArrow (0x4F-0x52) could be very useful!

If you want to spend a bit less time digging through that table though, feel free to adapt this example code, which also includes a header file (HID_Keyboard_Advanced.h) with all of the usage ID's defined.

PacMan Duino

You can now create any type of USB input device you could ever dream of. Want to use an old arcade joystick as you play an online arcade game? Make one! Here's some code to get you started. You can use the joystick hardware setup from the mouse example above. That code will use the advanced keystrokes, replacing mouse control with up, down, left and right arrow keystrokes.

Conclusion
Well, that was a long-winded tutorial, but this subject has me really excited. I can't help but talk about it! I think the simplicity of Arduino's HID classes opens up an immense world of physical computing possibilities. I'm excited to see what kind of crazy USB devices you all can come up with.

I hope this tutorial has you as excited about Arduino USB HID stuff as me. If you've got any questions, or need something clarified please post in the comments below.

Thanks for taking the time to check this tutorial out!
Advertisement

Share this Story

X
You may login with either your assigned username or your e-mail address.
The password field is case sensitive.
Loading