Tutorial for Adding a Keypad Mapper for ATC to FSX —
Purpose: Convert a USB Keypad to enter numbers for FSX ATC menu responses
In "FSX - Add a Keypad for ATC (Part 1)" the notion of converting a USB keypad for use with Microsoft Flight Simulator (FSX) was introduced. In this Part 2, the hardware and software for a functional USB key stroke mapper or translator is presented.
The primary credit for this USB re-mapper goes to Oleg Mazurov who developed the original hardware and software (https://www.circuitsathome.com/usb-host-shield-hardware-manual/). Oleg developed his USB Host Mini board (using the 3421E USB chip) which was subsequently cloned and sold on eBay. The 3421E board provided the platform for Oleg to examine USB on a number of devices in detail (https://www.circuitsathome.com/mcu/usb/visualizing-hid-device-reports-and-report-descriptors/). The software for that preliminary work is available in the GitHub version 1 library (https://github.com/felis/USB_Host_Shield). That library has been superseded with version 2 (https://github.com/felis/USB_Host_Shield_2.0) which was used for this project. However, working through the version 1 project development as published by Oleg will help understanding how USB works with these small microprocessor boards.
HARDWARE
3421E USB Shield clone:
Image 1. Modified 3421E USB shield which connects to USB keypad.
Image 2. Circuits@Home USB shield pin labels. Note the VBUS
pin wiring options at the top-left and RAW pin at bottom-right.
The 3421E shield clone (Image 1) was wired using the "USB Host Shield Hardware Manual and Image 2" as a guide. In Image 1, the red wire (lower right) provides 5V to the RAW pin which is also jumped to the MAX3421E USB VBUS pin (connector below the brown wire INT pin connector). Just left of the 2K2 label, the copper trace was cut to separate the 5V VBUS connector from the MAX3421E circuit which requires 3.3V rather than 5V.
A 1700-3302E (250mA) 3-pin 5V to 3.3V converter was soldered to the shield to provide 3.3V for the board: 1700-3302E pin 1 Gnd (black) to shield Gnd, 1700-3302E pin 2 +5V (red) to shield RAW, and 1700-3302E pin 3 (orange) to shield 3.3V which provides the 3.3Vcc for the shield or other boards connected to the shield that require 3.3Vcc. Filter capacitors were added: 1uF from 5V to Gnd, and 10uF from 3.3V to Gnd.
The 3421E shield communicates through SPI. The SPI wires used in this project are shown in Image 1 and labels in Image 2: blue to SCK, green to MISO, green/white to MOSI, and blue/white to SS. A brown wire to INT was not connected. A grey wire to Reset connects to 3.3Vcc or the microprocessor RST pin.
Pro Micro (Sparkfun Pro Micro Arduino-compatible 32U4 clone):
Image 3: Pro Micro shield with 32U4 microprocessor
and 5V to 3.3V voltage converter
Image 4: Sparkfun Pro Micro pins
The Arduino-compatible microprocessor used in this project was a Pro Micro shield clone with 32U4 microprocessor (not the standard 328 or 2560 Arduino chips) and also converted from 5V to 3.3V for compatibility with the 3.3V 3421E USB shield.
Converting the shield to 3.3V followed the steps by Adafruit (https://learn.adafruit.com/arduino-tips-tricks-and-techniques/3-3v-conversion). The Pro Micro 5-pin 5V converter chip was first snipped out. The 5 solder pads for the original 5V converter are shown just to the left of the 32U4 microprocessor in Image 3. A 1700-3302E 3-pin converter was used rather than to try to solder in the much smaller 5-pin 3.3V converter chip to replace the 5V chip removed from the Pro Micro. The 1700-3302E pin 1 (black) Gnd was soldered to the shield GND, 1700-3302E pin 2 (red) 5V was soldered to RAW (which obtains the USB +5V from the host PC), and 1700-3302E pin 3 to VCC which provides 3.3V to the Pro Micro shield. A 5V to 3.3V voltage converter was added to both the Pro Micro shield and 3421E USB shield, however one converter may have provided sufficient power for both shields. The Pro Micro shield has filter capacitors on the board, so no additional capacitors were added.
The wiring between the Pro Micro (PM) shield and the 3421E USB (3421) shield includes:
PM Gnd to 3421 Gnd,
PM RAW 5V to 3421 RAW 5V,
SPI PM SCLK pin 15 to 3421 SCK,
SPI PM MISO pin 14 to 3421 MISO,
SPI PM MOSI pin 16 to 3421 MOSI,
SPI PM (CS) pin 10 to 3421 SS,
PM pin 9 to 3421 INT (not currently used),
PM RST to 3421 Reset (can also be connected to 3.3V).
SOFTWARE
Software for this project used USB Host Shield Version 2 (https://github.com/felis/USB_Host_Shield_2.0) with slight modifications to provide the desired functionality for the FSX Keypad Mapper. The USBHIDBootKbd sketch provided with the Host Shield software was used for the basis of the Arduino sketch used for this project. Arduino IDE 1.8.4 was used to compile and program the Pro Micro computer. The Pro Micro boards obtained from eBay were 5V, 16MHz, and came pre-loaded with the Leonardo boot loader software. The Pro Micro shield converted to 3.3V performed without problems with the Arduino programming software set to use either the Leonardo (a 5V board) setting or the Pro Micro, 5V, 16MHz setting. Using the un-modified 5V Pro Micro board with the 3.3V Pro Micro Arduino programmer setting will brick the Pro Micro. If the Pro Micro gets bricked, the Leonardo or Pro Micro 5V, 16MHz boot loader will need to be reloaded to make the 5V board operational.
USB Scan Codes to ASCII Code Mapper
Following is a table of keypad key Scan Codes and the ASCII codes that are sent to the Pro Micro for transmitting through another USB conversion to the PC, with NumLock ON or OFF. The current configuration is to use the first two sets of codes from the table.
Using the Software
To use this software, connect the Pro Micro to the PC through a micro-USB cable. Connect the 3421E Shield to the keypad through a USB-A cable.
Modify and save the C:ProgramFiles/Arduino/Libraries/USB_Host_Shield_Library_2.0/hidboot.h and hidboot.cpp files as described below.
Run the Arduino IDE and choose the Leonardo device and port.
Open the Arduino Serial Monitor window and set the speed to 115200.
Compile and load the USBHIDBootKbd_Micro_00.ino provided below.
The sketch should print:
Start
USB OSC started OK.
See examples of Serial Monitor output at the bottom of this page.
USBHIDBootKbd sketch
Following is the modified USBHIDBootKbd sketch used for this project:
/*
* USBHIDBootKbd_Micro_00.ino
* Uses "USB Host Library Rev.2.0"
* https://github.com/felis/USB_Host_Shield_2.0
*
* 2017-09-06
* Lowell Bahner
*
*/
#include <hidboot.h>
#include <usbhub.h>
// add the keyboard output functions.
// Be careful, sketch keyboard output will write to
// this sketch if it is active on screen!!
#include <Keyboard.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
//#ifdef dobogusinclude
//#include <spi4teensy3.h>
#include <SPI.h>
//#endif
class KbdRptParser : public KeyboardReportParser
{
void PrintKey(uint8_t mod, uint8_t key);
protected:
void OnControlKeysChanged(uint8_t before, uint8_t after);
void OnKeyDown (uint8_t mod, uint8_t key);
void OnKeyUp (uint8_t mod, uint8_t key);
void OnKeyPressed(uint8_t key);
};
void KbdRptParser::PrintKey(uint8_t m, uint8_t key)
{
MODIFIERKEYS mod;
*((uint8_t*)&mod) = m;
Serial.print((mod.bmLeftCtrl == 1) ? "C" : " ");
Serial.print((mod.bmLeftShift == 1) ? "S" : " ");
Serial.print((mod.bmLeftAlt == 1) ? "A" : " ");
Serial.print((mod.bmLeftGUI == 1) ? "G" : " ");
Serial.print(" >");
PrintHex<uint8_t>(key, 0x80);
Serial.print("< ");
Serial.print((mod.bmRightCtrl == 1) ? "C" : " ");
Serial.print((mod.bmRightShift == 1) ? "S" : " ");
Serial.print((mod.bmRightAlt == 1) ? "A" : " ");
Serial.println((mod.bmRightGUI == 1) ? "G" : " ");
};
void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key)
{
Serial.print("\nDN Keycode");
PrintKey(mod, key);
Serial.print("Keycode Decimal: ");
Serial.println(key, DEC);
uint8_t c = OemToAscii(mod, key);
if (c)
OnKeyPressed(c);
}
void KbdRptParser::OnControlKeysChanged(uint8_t before, uint8_t after) {
MODIFIERKEYS beforeMod;
*((uint8_t*)&beforeMod) = before;
MODIFIERKEYS afterMod;
*((uint8_t*)&afterMod) = after;
if (beforeMod.bmLeftCtrl != afterMod.bmLeftCtrl) {
Serial.println("LeftCtrl changed");
}
if (beforeMod.bmLeftShift != afterMod.bmLeftShift) {
Serial.println("LeftShift changed");
}
if (beforeMod.bmLeftAlt != afterMod.bmLeftAlt) {
Serial.println("LeftAlt changed");
}
if (beforeMod.bmLeftGUI != afterMod.bmLeftGUI) {
Serial.println("LeftGUI changed");
}
if (beforeMod.bmRightCtrl != afterMod.bmRightCtrl) {
Serial.println("RightCtrl changed");
}
if (beforeMod.bmRightShift != afterMod.bmRightShift) {
Serial.println("RightShift changed");
}
if (beforeMod.bmRightAlt != afterMod.bmRightAlt) {
Serial.println("RightAlt changed");
}
if (beforeMod.bmRightGUI != afterMod.bmRightGUI) {
Serial.println("RightGUI changed");
}
}
void KbdRptParser::OnKeyUp(uint8_t mod, uint8_t key)
{
//Serial.print("UP ");
//PrintKey(mod, key);
}
void KbdRptParser::OnKeyPressed(uint8_t key)
{
Serial.print(" >> Send ASCII Key Hex: ");
Serial.print(key, HEX);
Serial.print(", Dec: ");
Serial.print(key, DEC);
Serial.print(", ASCII Char: ");
Serial.println((char)key);
// write key press to host app that is active (such as TextEdit)
Keyboard.write((char)key);
};
USB Usb;
//USBHub Hub(&Usb);
HIDBoot<USB_HID_PROTOCOL_KEYBOARD> HidKeyboard(&Usb);
uint32_t next_time;
int pinState = 0; // current state of the button
int lastPinState = 0; // previous state of the button
const int pinD5 = 5;
KbdRptParser Prs;
void setup()
{
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1) {
Serial.println("USB OSC did not start.");
} else {
Serial.println("USB OSC started OK.");
}
delay( 200 );
next_time = millis() + 5000;
HidKeyboard.SetReportParser(0, &Prs);
// Read Arduino pin d5 for value from 74LVC245 pin 9
// Connect 74LVC245 input pin 11 to gnd or +3.3V and read
// corresponding value on pin 9
// pinMode(pinD5, INPUT);
}
void loop()
{
Usb.Task();
/* do not read pin5 in this keypad sketch
pinState = digitalRead(pinD5);
//Serial.println(pinState);
if (pinState != lastPinState) {
if (pinState == HIGH) {
Serial.println("pinD5 HIGH");
}
else {
Serial.println("pinD5 LOW");
}
delay(50);
lastPinState = pinState;
}
*/
}
The USB Host library code also needs to be modified to make the key strokes map to the new desired codes. Both hidboot.h and hidboot.cpp must be edited as follows:
hidboot.h
In hidboot.h, line 30, add additional keypad key define statements:
...
...
Circuits At Home, LTD
Web : http://www.circuitsathome.com
e-mail : support@circuitsathome.com
*/
#if !defined(__HIDBOOT_H__)
#define __HIDBOOT_H__
#include "usbhid.h"
#define UHS_HID_BOOT_KEY_ZERO 0x27
#define UHS_HID_BOOT_KEY_ENTER 0x28
#define UHS_HID_BOOT_KEY_SPACE 0x2c
#define UHS_HID_BOOT_KEY_CAPS_LOCK 0x39
#define UHS_HID_BOOT_KEY_SCROLL_LOCK 0x47
#define UHS_HID_BOOT_KEY_NUM_LOCK 0x53
#define UHS_HID_BOOT_KEY_ZERO2 0x62
#define UHS_HID_BOOT_KEY_PERIOD 0x63
// Add the following lines to define additional keypad functions
#define UHS_HID_BOOT_KEY_END 0x59
#define UHS_HID_BOOT_KEY_BKSP 0x2A
#define UHS_HID_BOOT_KEY_DOWN 0x5A
#define UHS_HID_BOOT_KEY_PGDN 0x5B
#define UHS_HID_BOOT_KEY_LEFT 0x5C
#define UHS_HID_BOOT_KEY_CEN 0x5D
#define UHS_HID_BOOT_KEY_RIGHT 0x5E
#define UHS_HID_BOOT_KEY_HOME 0x5F
#define UHS_HID_BOOT_KEY_UP 0x60
#define UHS_HID_BOOT_KEY_PGUP 0x61
#define UHS_HID_BOOT_KEY_ENT 0x58
// Duplicate values with ZERO2 and PERIOD; these will be handled in hidboot.cpp
//#define UHS_HID_BOOT_KEY_INS 0x62
//#define UHS_HID_BOOT_KEY_DEL 0x63
... continue with the rest of hidboot.h and save the file.
hidboot.cpp
in hidboot.cpp:
> Fix the Enter key to send 0x0A rather than 0x13
line 163, change:
const uint8_t KeyboardReportParser::padKeys[5] PROGMEM = {'/', '*', '-', '+', 0x13};
to:
const uint8_t KeyboardReportParser::padKeys[5] PROGMEM = {'/', '*', '-', '+', 0x0A};
> if NumLock is ON, then send the numbers 0-9. Make this an 'and' condition
line 184, change:
}// Keypad Numbers
else if(VALUE_WITHIN(key, 0x59, 0x61)) {
if(kbdLockingKeys.kbdLeds.bmNumLock == 1)
return (key - 0x59 + '1');
to:
}// Keypad Numbers 1-9
else if ( (VALUE_WITHIN(key, 0x59, 0x61)) &&
(kbdLockingKeys.kbdLeds.bmNumLock == 1) ) {
return (key - 0x59 + '1');
> add code for the additional keypad keys
lines 193-201:
switch(key) {
case UHS_HID_BOOT_KEY_SPACE: return (0x20);
case UHS_HID_BOOT_KEY_ENTER: return (0x13);
case UHS_HID_BOOT_KEY_ZERO2: return
((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '0': 0);
case UHS_HID_BOOT_KEY_PERIOD: return
((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '.': 0);
}
}
return ( 0);
}
to:
switch(key) {
case UHS_HID_BOOT_KEY_SPACE: return (0x20);
case UHS_HID_BOOT_KEY_ENTER: return (0x0A); // 0x13 change to line feed
case UHS_HID_BOOT_KEY_ZERO2:
{
if (kbdLockingKeys.kbdLeds.bmNumLock == 1) {
return ('0');
} else { // INS key + NumLock OFF send 'space'
return (0x20);
}
}
case UHS_HID_BOOT_KEY_PERIOD:
{
if (kbdLockingKeys.kbdLeds.bmNumLock == 1) {
return ('.');
} else { // DEL key
// works for Text Wrangler send (0xD4); // delete forward
return (0xEA); // keypad 0
}
}
/* the following codes work for sending keypad keys to Text Wrangler with numlock off
cursor controls, such as, left arrow, right arrow, pgdn, home, etc
case UHS_HID_BOOT_KEY_END: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD5: 0);
case UHS_HID_BOOT_KEY_BKSP: return (0x08);
case UHS_HID_BOOT_KEY_DOWN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD9: 0);
case UHS_HID_BOOT_KEY_PGDN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD6: 0);
case UHS_HID_BOOT_KEY_LEFT: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD8: 0);
// send 5 key for num lock off also
case UHS_HID_BOOT_KEY_CEN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0x35: 0);
case UHS_HID_BOOT_KEY_RIGHT: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD7: 0);
case UHS_HID_BOOT_KEY_HOME: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD2: 0);
case UHS_HID_BOOT_KEY_UP: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xDA: 0);
case UHS_HID_BOOT_KEY_PGUP: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xD3: 0);
*/
// FSX: send keyboard numbers with numlock ON
// FSX: send the following keypad numbers for changing views with numlock OFF
case UHS_HID_BOOT_KEY_END: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE1: 0);
case UHS_HID_BOOT_KEY_BKSP: return (0x08);
case UHS_HID_BOOT_KEY_DOWN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE2: 0);
case UHS_HID_BOOT_KEY_PGDN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE3: 0);
case UHS_HID_BOOT_KEY_LEFT: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE4: 0);
// send 5 key for num lock off also
case UHS_HID_BOOT_KEY_CEN: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE5: 0);
case UHS_HID_BOOT_KEY_RIGHT: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE6: 0);
case UHS_HID_BOOT_KEY_HOME: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE7: 0);
case UHS_HID_BOOT_KEY_UP: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE8: 0);
case UHS_HID_BOOT_KEY_PGUP: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0xE9: 0);
//case UHS_HID_BOOT_KEY_ENT: return (0x0A); // Handled above in padKeys
//case UHS_HID_BOOT_KEY_DEL: return ((kbdLockingKeys.kbdLeds.bmNumLock == 0) ? 0x08: 0);
//case UHS_HID_BOOT_KEY_INS: return ((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? 0x01: 0);
}
}
return ( 0);
}
Keypad USB output to Text Wrangler with NumLock ON
0123456789./*-+
enter (new line) (space)
Keypad Sketch output to Serial Monitor with NumLock ON
Start
USB OSC started OK.
DN Keycode >53< (Turn on NumLock)
Keycode Decimal: 83
DN Keycode >62<
Keycode Decimal: 98
>> Send ASCII Key Hex: 30, Dec: 48, ASCII Char: 0
DN Keycode >59<
Keycode Decimal: 89
>> Send ASCII Key Hex: 31, Dec: 49, ASCII Char: 1
DN Keycode >5A<
Keycode Decimal: 90
>> Send ASCII Key Hex: 32, Dec: 50, ASCII Char: 2
DN Keycode >5B<
Keycode Decimal: 91
>> Send ASCII Key Hex: 33, Dec: 51, ASCII Char: 3
DN Keycode >5C<
Keycode Decimal: 92
>> Send ASCII Key Hex: 34, Dec: 52, ASCII Char: 4
DN Keycode >5D<
Keycode Decimal: 93
>> Send ASCII Key Hex: 35, Dec: 53, ASCII Char: 5
DN Keycode >5E<
Keycode Decimal: 94
>> Send ASCII Key Hex: 36, Dec: 54, ASCII Char: 6
DN Keycode >5F<
Keycode Decimal: 95
>> Send ASCII Key Hex: 37, Dec: 55, ASCII Char: 7
DN Keycode >60<
Keycode Decimal: 96
>> Send ASCII Key Hex: 38, Dec: 56, ASCII Char: 8
DN Keycode >61<
Keycode Decimal: 97
>> Send ASCII Key Hex: 39, Dec: 57, ASCII Char: 9
DN Keycode >54<
Keycode Decimal: 84
>> Send ASCII Key Hex: 2F, Dec: 47, ASCII Char: /
DN Keycode >55<
Keycode Decimal: 85
>> Send ASCII Key Hex: 2A, Dec: 42, ASCII Char: *
DN Keycode >56<
Keycode Decimal: 86
>> Send ASCII Key Hex: 2D, Dec: 45, ASCII Char: -
DN Keycode >57<
Keycode Decimal: 87
>> Send ASCII Key Hex: 2B, Dec: 43, ASCII Char: +
DN Keycode >63<
Keycode Decimal: 99
>> Send ASCII Key Hex: 2E, Dec: 46, ASCII Char: .
DN Keycode >58<
Keycode Decimal: 88
>> Send ASCII Key Hex: A, Dec: 10, ASCII Char: (insert enter line feed)
DN Keycode >2A<
Keycode Decimal: 42
>> Send ASCII Key Hex: 8, Dec: 8, ASCII Char: (backspace delete)
>>>>> NumLock OFF
DN Keycode >53< NumLock OFF
Keycode Decimal: 83
DN Keycode >62<
Keycode Decimal: 98
>> Send ASCII Key Hex: 20, Dec: 32, ASCII Char: (ins)
DN Keycode >59<
Keycode Decimal: 89
>> Send ASCII Key Hex: E1, Dec: 225, ASCII Char: (end)
DN Keycode >5A<
Keycode Decimal: 90
>> Send ASCII Key Hex: E2, Dec: 226, ASCII Char: (down arrow)
DN Keycode >5B<
Keycode Decimal: 91
>> Send ASCII Key Hex: E3, Dec: 227, ASCII Char: (pgdn)
DN Keycode >5C<
Keycode Decimal: 92
>> Send ASCII Key Hex: E4, Dec: 228, ASCII Char: (left arrow)
DN Keycode >5D<
Keycode Decimal: 93
>> Send ASCII Key Hex: E5, Dec: 229, ASCII Char: (5)
DN Keycode >5E<
Keycode Decimal: 94
>> Send ASCII Key Hex: E6, Dec: 230, ASCII Char: (right arrow)
DN Keycode >5F<
Keycode Decimal: 95
>> Send ASCII Key Hex: E7, Dec: 231, ASCII Char: (home)
DN Keycode >60<
Keycode Decimal: 96
>> Send ASCII Key Hex: E8, Dec: 232, ASCII Char: (up arrow)
DN Keycode >61<
Keycode Decimal: 97
>> Send ASCII Key Hex: E9, Dec: 233, ASCII Char: (pgup)
DN Keycode >54<
Keycode Decimal: 84
>> Send ASCII Key Hex: 2F, Dec: 47, ASCII Char: /
DN Keycode >55<
Keycode Decimal: 85
>> Send ASCII Key Hex: 2A, Dec: 42, ASCII Char: *
DN Keycode >56<
Keycode Decimal: 86
>> Send ASCII Key Hex: 2D, Dec: 45, ASCII Char: -
DN Keycode >57<
Keycode Decimal: 87
>> Send ASCII Key Hex: 2B, Dec: 43, ASCII Char: +
DN Keycode >58<
Keycode Decimal: 88
>> Send ASCII Key Hex: 0A, Dec: 10, ASCII Char: (line feed)
DN Keycode >2A<
Keycode Decimal: 42
>> Send ASCII Key Hex: 08, Dec: 8, ASCII Char: (backspace delete)
DN Keycode >63<
Keycode Decimal: 99
>> Send ASCII Key Hex: EA, Dec: 234, ASCII Char: (forward delete)
<end>
(September 9, 2017)