Generating F13 .. F18 function key pressed

Hi -

I have a sketch that turns my Arduino into a USB HID game controller so that I can intercept Joystick key presses in Windows. This all works fine to some degree, but to get more flexibility I would need to use keyboard commands.

Sending keyboard commands was easy with the tutorials that I found, unfortunately I do not want the keyboard to interfere with other keyboard devices and I would like to differenciate between keyboards when intercepting the presses in Windows, makes sense right?

To be able to do that I came up with the idea to use the F13 … F24 keys instead of the Joystick reports. I created this sketch:

// SparkFun Pro Micro 5v/16mhz 
// Board: "SparkFun Pro Micro 5v/16mhz"
// Programmer: AVRISP MkII

// Change this line in HID.cpp
// 0x29, 0x73, //USAGE_MAXIMUM (Keyboard Application)

#define KEY_F13	0x68
#define KEY_F14	0x69
#define KEY_F15	0x6A
#define KEY_F16	0x6B
#define KEY_F17	0x6C
#define KEY_F18	0x6D

const int buttonPins[] = {3, 4, 15, 14, 16, 10};
const int buttonKeys[] = {KEY_F13,KEY_F14,KEY_F15,KEY_F16,KEY_F17,KEY_F18};
bool buttonStates[] {false, false, false, false, false, false};

const int size = sizeof(buttonPins) / sizeof(int);
const int pollRate = 50;
long m = millis();

void setup() { 
  for (int i = 0; i < size; i++)
    pinMode(buttonPins[i], INPUT_PULLUP);    

  m = millis();
}

void loop() {
  if (millis() - m > pollRate)
  {
    m = millis();

    for (int i = 0; i < size; i++) 
    {
       int state = digitalRead(buttonPins[i]); 
       
       if (state == LOW && !buttonStates[i]) 
       {
         buttonStates[i] = true;     
         Keyboard.press(buttonKeys[i]);
       } 
       else if (state == HIGH && buttonStates[i]) 
       {
         buttonStates[i] = false;
         Keyboard.release(buttonKeys[i]);
       }
    }   
  }
}

The problem was that the Arduino keyboard code falsely translates the keycodes that are sent via Keyboard.press(). It all works of course, but not for these F13 … F18 keys. I fixed it by creating two overloads for when you want to send USB HID key commands in RAW format. (not chars, not ascii, not VK_code).

The result

// Added to HID.cpp

size_t Keyboard_::rawpress(uint8_t k) {
	uint8_t i;
	// Add k to the key report only if it's not already present
	// and if there is an empty slot.
	if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && 
		_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
		_keyReport.keys[4] != k && _keyReport.keys[5] != k) {
		
		for (i=0; i<6; i++) {
			if (_keyReport.keys[i] == 0x00) {
				_keyReport.keys[i] = k;

				Serial.println(k);

				break;
			}
		}
		if (i == 6) {
			setWriteError();
			return 0;
		}	
	}

	sendReport(&_keyReport);
	return 1;
}

size_t Keyboard_::rawrelease(uint8_t k) {
	uint8_t i;
	// Test the key report to see if k is present.  Clear it if it exists.
	// Check all positions in case the key is present more than once (which it shouldn't be)
	for (i=0; i<6; i++) {
		if (0 != k && _keyReport.keys[i] == k) {
			_keyReport.keys[i] = 0x00;
		}
	}

	sendReport(&_keyReport);
	return 1;
}

When you inspect the code that this replaces you’ll find that it is the same implementation with the exception of some code that translates the input code into whatever USB HID wants to have. You’ll need to change the release and press calls in the sketch above.

Unfortunately one more thing was missing because the keys still weren’t registered by windows. It was missing a proper HID descriptor:

This was changed:

0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)

To this:

0x29, 0x73,                    //   USAGE_MAXIMUM (Keyboard Application)

And now it all works. Sometimes I do searches in these forums and not find anything, I hope this might be useful to someone. Please do post back.

If you look at the code you replaced you will see that adding 136 to the scan code will cause the scan code to be sent raw.

Hi John,

Thanks for the response. I did notice that but I wasn't sure why that was happening so I decided to do it this way. After your response I have changed the code like this:

#define KEY_F13	0xF0 // 0x68 + 0x88
#define KEY_F14	0xF1 // 0x69 + 0x88
#define KEY_F15	0xF2 // 0x6A + 0x88
#define KEY_F16	0xF3 // 0x6B + 0x88
#define KEY_F17	0xF4 // 0x6C + 0x88
#define KEY_F18	0xF5 // 0x6D + 0x88

Now I don't need the overloads. I'm still unclear about why this is happening, do you know more about that? It looks like the write function wants this for ascii translation from chars, but I am not sending a char in this case. Why can't the implementation overload char and subtract 136 for the ascii translation and have the constants contain the actual HID usage table instead of having to add 136 to those.

Doesn't this mean that the write function has to subtract 136 in every call, even if it doesn't require ascii translation?

Also, I feel that it would make more sense to have those the USB usage constants in the source code.

What do you think John?

#define KEYCODE_F13 0x68
#define KEYCODE_F14 0x69
#define KEYCODE_F15 0x6A
#define KEYCODE_F16 0x6B
#define KEYCODE_F17 0x6C
#define KEYCODE_F18 0x6D
#define KEYCODE_F19 0x6E
#define KEYCODE_F20 0x6F
#define KEYCODE_F21 0x70
#define KEYCODE_F22 0x71
#define KEYCODE_F23 0x72
#define KEYCODE_F24 0x73

Seems to do the trick, thanks. Just wondering why these codes are different to the Virtual Key codes specified by Microsoft. F13 for example, is 0x7C and NOT 0x68. Why is there a difference? See also this page:

Thanks for the answer.

Thank you for sharing this. I am going to build a button box for a simulator very soon, and this might just be of help. FOr now I decided to use the Big Joystick though, and I think that will work for me. I does in the small test I performance.

What made you go the keyboard way?

Also does the sketch turn it into a keyboard without having to firmware flash the USB on the Arduino?

The reason I am asking is that I would like to use both input and output from my computer, and I figured that if I flash the Arduino to be detected as a joystick, I will have to use another Arduino for the stuff going the other way.

@bfkmnemonic: I don't know if you are talking to me but I use these codes to trigger a self made Windows debugger to debug a Attiny85 as USB keyboard and Mouse. The reason I use this is because with this Sparkfun board, it not possible to use a serial connection, you need an cable construction or Arduino for this (which I don't have). I don't want the debugger to interfere with existing hotkeys, so I came up with the same idea Fexduino had, to use keys that are not very common on keyboards (the F13..F24 for example).

The Windows debugger with a text-field catch, for example, the F13 hotkey (triggered by the Attiny85) as part of a 'handshake' method. The Windows debugger sets the focus on the text-field and switch on the CAPS, NUM and SCROLL lock as answer. Now the Attiny85 knows it's save to 'type' some output, for example: "HELLO WORLD FROM ATTINY!"

With this simple method you can output some debug info without extra needs.

Your question about your Arduino is something I can't answer because I don't have one.

bfkmnemonic:
What made you go the keyboard way?

Also does the sketch turn it into a keyboard without having to firmware flash the USB on the Arduino?

The reason I am asking is that I would like to use both input and output from my computer, and I figured that if I flash the Arduino to be detected as a joystick, I will have to use another Arduino for the stuff going the other way.

Great! Good luck with your project! I am using this as a true Windows keyboard like a separate numpad. The reason I wanted it to be a keyboard is for two main reasons:

  • It’s a keyboard, with Cherry MX switches
  • As a keyboard it is easy to hotkey in other applications.

You could use this keyboard like any other keyboard and map your favorite application’s functions to it. My personal preference for hotkeys is AutoHotKey. This allows me to assign different functions in different applications.

You mention that you need two way communication, I have no idea how to achieve that using USB HID reports. Is that what you meant? Do Joysticks allow you to send data? I guess they do … You might be able to use the Serial for that. It works together with the Keyboard class, that way you need one Arduino. Not sure if you need that.

I am using a Arduino Leonardo 32u4 Arduino Pro Micro by the way, it didn’t require any flashing of the bootloader. Mine came post-2012 with the latest Caterina bootloader. All I did was upload the sketch and Windows recognized it immediately (I shows up as a Keyboard in the Devices and Printers control panel window)

Here’s some Arduino 1.6.7 compatible code:

// SparkFun Pro Micro 5v/16mhz
// Board: “SparkFun Pro Micro 5v/16mhz”
// Programmer: AVRISP MkII

// Change this line in Keyboard.cpp
// from
// 0x29, 0x65, //USAGE_MAXIMUM (Keyboard Application)
// to
// 0x29, 0x73, //USAGE_MAXIMUM (Keyboard Application)

#include <Keyboard.h>
#define KEY_F13 0xF0 // 0x68 & 0x88
#define KEY_F14 0xF1 // 0x69 & 0x88
#define KEY_F15 0xF2 // 0x6A & 0x88
#define KEY_F16 0xF3 // 0x6B & 0x88
#define KEY_F17 0xF4 // 0x6C & 0x88
#define KEY_F18 0xF5 // 0x6D & 0x88

const int buttonPins = { 3, 4, 15, 14, 16, 10 };
const int buttonKeys = { KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18 };
bool buttonStates { false, false, false, false, false, false };

const int size = sizeof(buttonPins) / sizeof(int);
const int pollRate = 50;
long m = millis();

void setup() {
for (int i = 0; i < size; i++)
pinMode(buttonPins*, INPUT_PULLUP);*

  • m = millis();*

  • Keyboard.begin();*
    }
    void loop() {

  • if (millis() - m > pollRate)*

  • {*

  • m = millis();*

  • for (int i = 0; i < size; i++)*

  • {*
    _ int state = digitalRead(buttonPins*);_
    _ if (state == LOW && !buttonStates)
    {
    buttonStates = true;
    Keyboard.press(buttonKeys);
    }
    else if (state == HIGH && buttonStates)
    {
    buttonStates = false;
    Keyboard.release(buttonKeys);
    }
    }
    }
    }
    [/quote]*_

I realise this is a super necro, but since this thread was the top google result for using f13-f24 - and more importantly, the <keyboard.h> library was just updated (v1.0.2) to handle those without messing about, I thought I’d share the code I have working, ripped almost entirely from the posts above.

Pins and instructions for Arduino Due and Pro Micro boards, since those are what I have here to test and use.

/* Code for f13-f24 keys working
    Really basic setup for individual pins per button.
    Circuit; push button connected between pins and ground.
    Not using delay. 30 ms debounce
    
    Based on the code by Fexduino from - http://forum.arduino.cc/index.php?topic=324554.0
*/

#include "Keyboard.h"   // v1.0.2 or later of the keyboard library required

/* const int buttonPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; 
  Due pins above, remove comment out if using Due
  pro micro (leonardo to the IDE board list) below, comment out if using a Due 
  */
  const int buttonPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 14, 15}; // - continuing in order 18, 19, 20, 21, should you want to add more.

const int buttonKeys[] = {KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24};
bool buttonStates[] {false, false, false, false, false, false, false, false, false, false, false, false};

const int size = sizeof(buttonPins) / sizeof(int);
const int pollRate = 30; // change this figure if the debounce number needs to be higher or lower
long m = millis();

void setup() {
  for (int i = 0; i < size; i++)
    pinMode(buttonPins[i], INPUT_PULLUP);

  m = millis();
}

void loop() {
  if (millis() - m > pollRate)
  {
    m = millis();

    for (int i = 0; i < size; i++)
    {
      int state = digitalRead(buttonPins[i]);

      if (state == LOW && !buttonStates[i])
      {
        buttonStates[i] = true;
        Keyboard.press(buttonKeys[i]);
      }
      else if (state == HIGH && buttonStates[i])
      {
        buttonStates[i] = false;
        Keyboard.release(buttonKeys[i]);
      }
    }
  }
}