Writing code for websites is all good and well but sometimes you want to do something more, well, tactile. How about a nice ominous red button that you can push and make your PC launch your missiles, self-destruct your secret base, or distract the cat?
Buttons are usually connected through the USB port. Most people have large numbers of these buttons in front of them neatly stuck to a board. It’s called a keyboard. The button we’re going to construct is very similar; however, it’s a keyboard consisting of one key only.
Your PC is going to detect the pressing of this button just like it would a normal key, but we’re going to fake it: it isn’t really a keyboard the PC is talking to. Ha! It’ll be talking to our little device which will mimic a keyboard for us. Sneaky, heh?
There are three distinct areas we’ll have to deal with: the actual button hardware, the software running inside the button and, optionally, the software on your PC to tie it all together. This project is by no means exhaustive and just intended to give you an idea of what you can do and how easy it is to build these things.
USB is pretty versatile, but it is not a patient beast
USB, or the Universal Serial Bus (1), is in effect the only real way to get hardware devices to talk to your PC. It consists of a series of hardware standards and a protocol for the plugs and signaling. Since the signaling is standardized and thus OS independent, it doesn’t matter if you’re running Windows, Linux or Mac: they all understand the devices and, if provided with the correct driver, can use them.
To join the USB playground with your device, you need to communicate over the USB port. It needs to identify itself to the host by letting it know what type of device it is (‘I’m a keyboard device’, ‘I’m a printer device’, etc.), what capabilities it has, as well as other assorted things the PC needs to be aware of. Traffic over the USB port is tied to a very strict timing scheme and is thus rather unforgiving in nature. This means that while it is possible to create your own USB connection software for hardware that doesn’t normally support it (also referred to as bit-banging (2)), this method is still prone to timing errors. This is something I kept in mind while selecting a logic board to power the button.
At the start of this little project, I considered various possible hardware options, ranging from el-cheapo controller boards from AliExpress to a Raspberry Pi 3. I ended up somewhere in the middle with the Trinket M0, created by AdaFruit (3). It brings the hardware you need: the micro-processor has the desired built-in USB support, a sufficient number of input/output pins, and it is programmable, so it can be made to mimic the desired keyboard-like behavior.
You’re also going to need a few hardware bits and bobs other than your programming fingers. At the very least you’ll need:
- Adafruit Trinket M0 (the ATSAMD21R18 version)
- a push button of some sort (non-latching is the best)
- some wiring to connect the button to the Trinket
- soldering iron, solder (the rosin-flux kind)
- USB 2.0 cable, type A Male to Micro B (you can use the cable you use to connect your Android phone to your PC if you have one).
Ideally, you have access to the following to help you experiment (not strictly necessary though):
- breadboard
- jumper cables
- alligator clamps (make connecting the button easy)
- housing to put the trinket and button in when it all works
- medical supplies (depending on how handy you are with the soldering iron).
The Trinket M0 comes with CircuitPython already installed, but if you prefer to overwrite this installation and treat it like an Arduino (4), you can do so. For this project and simplicity’s sake, I’ll use the CircuitPython that comes pre-loaded on the board. CircuitPython (5) is a MicroPython variant, ideal for little hardware projects. MicroPython is, in turn, a Python variant that is specialized for microcontrollers.
Wires and switches and pins, oh my!
We’ll connect the Trinket up like this: we’ll use the pin marked ‘2’ on the board to signal the button being pressed. The pin marked ‘1’ is used to light up the red LED to indicate that the button is being pressed; it is optional. The green LED is wired directly into the USB port’s power to signal that the board is getting power.
If, for some reason, the LED does not light up, connect it the other way around. (Yes, I did it wrong the first time around.)
Finding the snake
Setting up the CircuitPython development environment on your computer is not difficult. All you need is a text editor, preferably one that has syntax highlighting, like Visual Studio Code (6), and an application that can host a serial session with the board, like PuTTy (7). You can optionally install the Python extension (8) for Visual Studio Code if you like.
I’m going to assume you are running Windows 10; that comes with all the necessary drivers for your Trinket M0 board pre-installed (yay!). For older versions of Windows please check the manual (9) to see what you need to do.
Programming is done by editing the file in place on the board; no compilation is needed as the board takes care of that for you.
To see the output (print and error messages) of the Trinket, I’ll use PuTTy, which is a very nice free telnet client. If you prefer to use only Visual Studio Code, you can integrate the Arduino IDE into it as explained by Scott Hanselman here (10).
Go to your Device Manager tab in Windows and open the Ports collection. Plug the Trinket board into your PC; the collection will update, and a new device will appear. The board has now been assigned a COM port. Depending on how fast your PC recognizes it, this can take a few moments. The newly found device will be named something like ‘AdaFruit Circuit Playground Express’, ‘Trinket M0’, or ‘USB Serial Device’. You can find the COM port behind the name; remember this as you’ll need it to connect to it in a moment. In the example below, the COM port assigned is COM5.
Next define the connection:
- Start PuTTy
- Session (top left column) -> Connection type: Serial (to the right)
- Serial line: enter the COM value from the Device Manager above
- Speed: 115200
You can then click ‘Open’ to open a session to your Trinket. Stuff appears in the screen: this is the output from the board. You don’t need to do anything here for now, but you can hit CTRL+C and then any other to enter the REPL environment (Read-Eval-Print-Loop).
If you’re familiar with Python, you’ll recognize the >>> prompt as Python’s immediate mode. Type help() to examine the available modules (such as the available pins in the digitalio module). Use CTRL+D to return to the output view.
Starting the serial session and hitting any button is the easiest way to find the version number of your CircuitPython install; remember this number.
Let’s move on to the software side of things on the button.
Putting the snake on board
After plugging it in and waiting a while (it might take a minute or so), the board will be identified as an USB storage device named CIRCUITPY (a memory stick, if you will) with a tiny storage capacity. Open it and see what it contains. There should be a ‘code.py’ file there, but there might be more too (mine had other stuff like a Windows 7 driver). If there is a ‘lib’ directory already, great—if not, create it.
We’re going to need to download the zipped collection of CircuitPython libraries: we need this to add one external library to the Trinket that is required for sending keystrokes to the PC. Remember the major version number printed in the serial output? For my board this is version 2. Download the corresponding zipped library from here (11). Unpack the zip somewhere and copy the ‘adafruit_hid’ directory into the ‘lib’ directory on your CIRCUITPY.
Replace the content of the ‘code.py’ file with the following:
# Based on the Adafruit CircuitPython example import time import board import digitalio from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS from adafruit_hid.keycode import Keycode keyboard = Keyboard() keyboard_layout = KeyboardLayoutUS(keyboard) # Sleep for a bit to avoid a race condition on some systems time.sleep(1) pin = digitalio.DigitalInOut(board.D2) pin.direction = digitalio.Direction.INPUT pin.pull = digitalio.Pull.UP # Activate the red led on the board when the button # is pressed for some quick feedback internalLed = digitalio.DigitalInOut(board.D13) internalLed.direction = digitalio.Direction.OUTPUT internalLed.value = False externalLed = digitalio.DigitalInOut(board.D1) externalLed.direction = digitalio.Direction.OUTPUT externalLed.value = False print("Waiting for button push...") while True: # Wait for the pin to be grounded if not pin.value: # Sleep to debounce time.sleep(0.05) # Turn on the red LEDs internalLed.value = True externalLed.value = True # Wait for the pin to no longer be while not pin.value: pass # Prepare the keystrokes we want to send and send them keyboard.press(Keycode.SHIFT, Keycode.CONTROL, Keycode.F12) keyboard.release_all() print("Sending keystroke...") # Turn off the red LEDs internalLed.value = False externalLed.value = False time.sleep(0.01)
The program keeps track of pin 2 (which is D0 in the software!), and when the button is pressed and released it will send the programmed keypress combination to the PC over the USB.
Mechanical switches have the annoying habit of making contact a few times before actually being fully pressed; the board is sensitive enough to detect this and thus send multiple keypress events to the PC. To counter this effect a debounce was added to the code. It waits 50 ms after the first keypress detection before actually doing anything; any other signals are ignored during this period.
Save the file and we’re done here; it is immediately picked up by the Trinket board and it should now be running. Check your PuTTy window to see the messages.
Now that the hardware and the software of the button are done, we need to do something to make the result ‘visible’ on the PC it is plugged in to.
Making the PC respond the way we want
The most basic way to see the button work is to open Notepad and make it send a single letter keystroke. That’s not very exciting though; we can do better than that. Perhaps with a global macro application like AutoHotKey (12) that can open a browser for you, and do a large number of more complicated tasks as well. But it’s not ideal for what I want—to play a sound.
I’ve created a simple C# console application that does just that. It waits for a specific key combination to be pressed and then plays a sound file. Grab it from Github (13) and you’ll hit the ground running.
git clone https://github.com/Rarz/KeyboardWatcher.git
After you’ve cloned it to your PC all you need to do is build and run. You might have to enable the Nuget Package Restore if the build fails and throws an error message regarding nAudio. (Tools > Options > Nuget Package Manager > Check the ‘Automatic check for missing packages’ option or use the package restore on the project itself.)
There is very little logic in the application as you can see. It uses nAudio to play a sound, and it has a Windows API keyboard hook to look for a specific keypress event. It should work even when minimized.
- Build and run.
- Test the application by pressing Ctrl+Shift+F12 (make sure you have sound turned on, but not too loud).
- Next, hit your button to see if that generates the same result (it should).
- If it doesn’t work, check the PuTTy window to see if the board threw an error, and if the console app is looking for the right combination—then start debugging.
TLDR. Why should I bother?
Because it’s fun? And educational. And because it’s more than likely you’ll run into IoT devices at some point in the future, so getting your feet wet with actually building something related is never a bad thing. This is a simple example; there is so much more out there you can add to projects like this: LEDs, mesh networks, robotics, cloud-based functionality (14) and so on.
There are many, many different boards for you to pick from. Everyone knows about the Raspberry and Arduino—but those two names are only the tip of the iceberg. Windows is even available on several of them in the form of Windows 10 IoT, but you should expand your toolset to be able to benefit from all the available hardware. The landscape isn’t tied to a single vendor or language anymore.
It was fun and interesting to find out how to wire up the Trinket and program the script to govern the keypresses and output event; before I started this I had only a vague idea of how to go about this. It was easier than expected. The only frustration is the wait for parts to arrive; once you have all the bits, however, you’ll be up and running in no time.
Have fun tinkering!
Further reading:
https://en.wikipedia.org/wiki/USB
https://en.wikipedia.org/wiki/Bit_banging
https://www.adafruit.com/product/3500
https://www.arduino.cc
https://circuitpython.readthedocs.io/en/3.x/
https://code.visualstudio.com/download
https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
https://marketplace.visualstudio.com/items?itemName=ms-python.python
https://cdn-learn.adafruit.com/downloads/pdf/adafruit-trinket-m0-circuitpython-arduino.pdf
https://www.hanselman.com/blog/UsingVisualStudioCodeToProgramCircuitPythonWithAnAdaFruitNeoTrellisM4.aspx
https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases
https://www.autohotkey.com/
https://github.com/Rarz/KeyboardWatcher.git
https://azure.microsoft.com/en-us/product-categories/iot/