Macro Keyboard
Do you know Elgato Stream Deck? It is a fantastic macro keyboard consisting of programmable keys displaying custom images. It allows you to automate numerous things including entering key sequences, running programs or websites, controlling specific software and more. Moreover you can extend its capabilities by writing your own plugins. And don’t get fooled by the “stream” in its name. Even though it was initially created for content creators, it serves fantastically as a productivity tool.
There’s only one downside of this fantastic keyboard: the price. The most reasonable, 15-key version costs around 150€, which sometimes may be a deal breaker. So I decided to create such macro keyboard on my own.
Designing buttons, which display an image underneath is not simple (Stream Deck actually does it with only one screen in quite an ingenious way). I saw one DIY approach named FreeDeck, but ultimately I decided that this is not the way I would like to go. Firstly, it has only 6 keys (because of screen board sizes), secondly it treats goldpins as springs – every screen press causes stress on the pins, which may eventually start cracking. Also, LED/OLED screens tend to wear off in time – especially if they display the same image all the time. Stream Deck counters that by allowing you to set a delay, after which the screen is turned off. You can turn it back on by pressing any of the keys.
Finally, I decided to take two separate approaches.
One is using an e-paper screen, which displays labels for keys. However, they are not displayed underneath the keys themselves, but instead I show them above them, in the same layout as the keys. So effectively, top-left icon describes top-left key and so on. Surprisingly, this works quite well, you quickly learn, which button represents which feature.
Another approach is a screen-less keyboard. Apart from an obvious lack of the e-paper screen, the design is exactly the same.
I figured, that the device without some form of displaying labels for buttons represents a fraction of its capabilities. The sole purpose of using such keyboard is so that you don’t have to remember things (eg. shortcuts), so now having to remember which key does what hinders its usability. Moreover it is hard to use more than one layout, because you have no way of knowing, which layout is currently active. So to counter this problem I modified the software for the keyboard and also wrote a desktop application, which acts as a satellite. It connects with the keyboard with a COM port and displays on screen contents of what otherwise would be shown on the e-paper display. Moreover, if you hold any button for more than half a second, screen is temporarily displayed for you, so that you can quickly check, which button you need.
The Stream Deck requires a satellite application to be running at all times. Moreover, it stores layouts of buttons on the computer, so if you connect the device to another PC, you will have a completely different set of keys there. Obviously, you can synchronize layouts between devices, but this requires some more effort.
On the other hand, in my case, the layout is stored on the SD card, which you put in the keyboard’s reader. Thanks to that, you can use the same layout regardless of which machine you connect the keyboard to.
I made my keyboard opensource (both software and hardware). You can build your own unit by following the tutorial.
Bill of material
Components required to assemble the keyboard are the following.
- 1 x Macro Keyboard 1.1 PCB – approximately 8 USD + shipment (I ordered mine from JLCPCB)
- 1 x Teensy 3.5, 3.6 or 4.1 – approximately 25 USD
- 12 x Cherry MX switches – 1 USD per switch
- 12 x Keycaps for keys – can be 3D-printed or purchased separately
- 2 x 24-pin female goldpin strips – < 1 USD
- 1 x encoder with button (I used 30 steps per rotation, 6mm diameter, 15mm height) – < 1 USD
- 1 x encoder cap (up to 30mm of diameter) – < 1 USD
- 5 x 18mm (incl. head) M3 screw + nut for the case
- 1 x MicroSD card of as small size as you can buy (128 Mb will do just fine)
If you want to assemble version with e-Paper screen, you need as well:
- 1 x angled 8-pin male goldpin strip
- 1 x Waveshare 2.9″ 296×128 black & white e-Paper display (code: 12956). DO NOT buy 3-colored versions! – 20 USD
- 3 x 15mm (incl. head) M3 screw + nut for the screen
Note that if you use my designs, you may add the screen at any point in the future. Just buy missing parts, solder pins for the screen, connect it, assemble everything again and burn different version of the firmware.
If you want to use my enclosure, you also have to either 3D print it or order a print.
Pre-requisites
Clone my repository on Gitlab.
Board
In the cloned repository, in Board folder you can find a couple of files describing the PCB. You can either open them using Autodesk Eagle or simply use provided Gerber files to order a board.
Assembly
You have to solder the following components:
- Female goldpin strips for Teensy
- Cherry MX keys
- Angled male goldpin strip for e-paper screen
- The encoder
Fully soldered board should look like the following:
If you are assembling version with screen, screw the screen into its case with screws, which are provided with the screen itself (M2s). Remember to connect the screen’s plug before screwing it. Afterwards, screw screen’s case.
Then connect the screen to the board. Use the descriptions on the board to connect proper pins (colors of wires may differ from the version I used).
Screen case provides enough space to push the wires inside, so they don’t clutter the main board.
Afterwards, place the main board on the bottom part of the casing and afterwards place the top part of the casing on the board. Case is designed in such way, that screws go through the top part, then through dedicated hole in the board and then through the bottom part. Remember to insert nuts into their slots before screwing the casing.
When everything is screwed on, put Teensy into its socket, put on the keycaps and encoder cap. You’re done with the assembly!
Programming
MacroKeyboard folder contains application for the keyboard itself. Use Arduino Studio (along with Teensyduino loader) to burn the software into the board.
Software is designed for both screen and screen-less version. However depending on which version you want to use, you have to set one thing in the code.
Version with e-paper screen
Beginning of MacroKeyboard.ino file should look like following:
// *** Configuration start ***
// Define to send screens via serial port
// Undefine to send to e-paper display
#undef DISPLAY_SCREEN_SERIAL
// *** Configuration end ***
Version without screen
Beginning of MacroKeyboard.ino file should look like following:
// *** Configuration start ***
// Define to send screens via serial port
// Undefine to send to e-paper display
#define DISPLAY_SCREEN_SERIAL
// *** Configuration end ***
Keyboard definition
Macro keyboard loads screen layouts from a binary Keyboard.def file. To simplify creation of this file, you may define layouts in XML format and then use the MacroKeyboardGen console tool to convert XML to binary.
The sample XML layout definition may look like following:
<?xml version="1.0" encoding="utf-8"?>
<Definition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Screens>
<Screen Id="0">
<Items>
<Item Header="Copy" Icon="Copy.png">
<Actions>
<ModifierDown Modifier="Ctrl" />
<KeyPress Key="C" />
<ModifierUp Modifier="Ctrl" />
</Actions>
</Item>
</Items>
</Screen>
</Screens>
</Definition>
The definition consists of the following tags:
Definition |
|
---|---|
Description | Root XML tag containing the whole definition |
Children | Screens |
Screens |
|
---|---|
Description | Contains a list of Screen entries describing screens, which may be displayed either on the e-paper display or on screen of the PC (by the satellite application) |
Children | Screen (multiple) |
Screen |
|
---|---|
Description | Describes a single screen |
Attributes | Id (required) should be a unique identifier for a screen. You must supply screen with Id 0, it will be treated as a main one, displayed after booting up the device. You can assign identifiers to other screens as you wish, as long as their Ids are unique. |
Children | Items |
Items |
|||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Description |
Contains a list of items, from which every one relates to a button on the keyboard. Keys are described row-first, in the following order:
Each item contains information about its look and actions, which will be executed when button is pressed. |
||||||||||||
Children | Item and EmptyItem , in total up to 12 |
Item |
|
---|---|
Description | Describes a single item (its appearance and behavior) |
Attributes |
|
Children | Actions |
EmptyItem |
|
---|---|
Description | An empty placeholder. Use if you want to omit some item definitions to eg. position an item in specific place. Pressing button, which is represented by EmptyItem has no effect. |
Actions |
|
---|---|
Description | Contains a list of actions, which will be executed, when a key is pressed. Actions are always executed in order in which they are defined. |
Children | ModifierDown , ModifierUp , KeyPress , SwitchToScreen , ToggleEncoderMode , Sleep , SwitchToEncoderMode , Execute (multiple) |
ModifierDown, ModifierUp |
|
---|---|
Description | Causes the modifier key to be pressed or released. Use to define keyboard shortcuts, like Ctrl+C. |
Attributes | Modifier (required) Modifier key to be pressed. See list of valid modifiers below. |
KeyPress |
|
---|---|
Description | Causes the key to be pressed and released. |
Attributes | Key (required) Key to be pressed. See list of valid keys below. |
SwitchToScreen |
|
---|---|
Description | Switches the current screen to one with matching Id. This allows you to create complex hierarchies of screens with pages and folders. There is no built-in way of returning to previous screen, so always remember to provide some kind of "back" or other navigation key, or you will be stuck on a screen. |
Attributes | Id (required) Id of the target screen. Screen with such Id must exist. |
ToggleEncoderMode |
|
---|---|
Description | Cycles encoder behavior between arrow keys, mouse movement and mouse scroll. Press the encoder to switch the submode (eg. vertical/horizontal keys, mouse movement and scroll) |
SwitchEncoderMode |
|
---|---|
Description | Switches to a specific encoder mode. |
Attributes | Mode (required) Target mode. 0 – Arrow keys, 1 – Mouse, 2 – Mouse scroll. |
Sleep |
|
---|---|
Description | Clears the screen. Useful if you want to turn the device off, to reduce e-paper scren wear. You can exit sleep mode by pressing any key. |
Execute |
|
---|---|
Description | Executes given command (eg. starts a program or opens a webpage) |
Requirements | This action requires the satellite application to be running on the PC. |
Attributes | Command (required) Command to be executed. |
Valid modifiers
ctrl | shift | alt | gui | lctrl |
lshift | lalt | lgui | rctrl | rshift |
ralt | rgui |
Valid keys
system_power_down | system_sleep | system_wake_up | media_play |
media_pause | media_record | media_fast_forward | media_rewind |
media_next_track | media_prev_track | media_stop | media_eject |
media_random_play | media_play_pause | media_play_skip | media_mute |
media_volume_inc | media_volume_dec | a | b |
c | d | e | f |
g | h | i | j |
k | l | m | n |
o | p | q | r |
s | t | u | v |
w | x | y | z |
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 0 | enter | esc |
backspace | tab | space | minus |
equal | left_brace | right_brace | backslash |
non_us_num | semicolon | quote | tilde |
comma | period | slash | caps_lock |
f1 | f2 | f3 | f4 |
f5 | f6 | f7 | f8 |
f9 | f10 | f11 | f12 |
printscreen | scroll_lock | pause | insert |
home | page_up | delete | end |
page_down | right | left | down |
up | num_lock | keypad_slash | keypad_asterix |
keypad_minus | keypad_plus | keypad_enter | keypad_1 |
keypad_2 | keypad_3 | keypad_4 | keypad_5 |
keypad_6 | keypad_7 | keypad_8 | keypad_9 |
keypad_0 | keypad_period | non_us_bs | menu |
f13 | f14 | f15 | f16 |
f17 | f18 | f19 | f20 |
f21 | f22 | f23 | f24 |
Version without screen
If you want to use version without screen, compile and run application in the folder MacroKeyboard.Satellite. Note: it requires .NET 6 to run (you can download and install it freely though). After starting, right-click its icon in the System Tray and choose proper COM port, under which your Teensy is available. If you are not sure, which one is it, simply try all one by one until you find the proper one.
To check if port is valid, press and hold any key on the macro keyboard. If you chose the proper port, keyboard’s screen should be displayed just above the System Tray.
Done!
If you reached this step, congratulations! You have just assembled your own macro keyboard. If you did, make sure to send me a message with photo, I will gladly show a gallery of successful builds. Also, if you have trouble building your keyboard, leave note in the comments, I will try to improve the description or answer any questions you may have.