Migrate from Bazel
This commit is contained in:
commit
016dbd0814
59 changed files with 7044 additions and 0 deletions
192
content/posts/arduino-leonardo-fully-featured-keyboard.md
Normal file
192
content/posts/arduino-leonardo-fully-featured-keyboard.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
+++
|
||||
template = "article.html"
|
||||
title = "Arduino Leonardo fully-featured keyboard"
|
||||
date = 2014-05-04T23:03:16+02:00
|
||||
description = "Building a fully-featured keyboard emulator with Arduino Leonardo, including support for modifier keys and special characters."
|
||||
|
||||
[taxonomies]
|
||||
tags = ["arduino"]
|
||||
+++
|
||||
|
||||
The Leonardo has a simple [keyboard API](http://arduino.cc/en/Reference/MouseKeyboard).
|
||||
I needed a way to emulate a keyboard (from a joystick and arcade buttons - you
|
||||
see where I'm going now). Here's how I did it.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## First try
|
||||
|
||||
Starting with an [Arduino sample](http://arduino.cc/en/Tutorial/KeyboardAndMouseControl),
|
||||
we can make a first attempt. The circuit is the same as the sample - simply
|
||||
adjust the pins to your needs. It won't need any change until the end of this
|
||||
post.
|
||||
|
||||
_Basic keyboard_
|
||||
|
||||
```cpp
|
||||
const int upButton = 2;
|
||||
const int downButton = 3;
|
||||
const int leftButton = 4;
|
||||
const int rightButton = 5;
|
||||
|
||||
void setup() {
|
||||
pinMode(upButton, INPUT);
|
||||
pinMode(downButton, INPUT);
|
||||
pinMode(leftButton, INPUT);
|
||||
pinMode(rightButton, INPUT);
|
||||
pinMode(mouseButton, INPUT);
|
||||
|
||||
Keyboard.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (digitalRead(upButton) == HIGH) {
|
||||
Keyboard.write(KEY_UP_ARROW);
|
||||
}
|
||||
if (digitalRead(downButton) == HIGH) {
|
||||
Keyboard.write(KEY_DOWN_ARROW);
|
||||
}
|
||||
if (digitalRead(leftButton) == HIGH) {
|
||||
Keyboard.write(KEY_LEFT_ARROW);
|
||||
}
|
||||
if (digitalRead(rightButton) == HIGH) {
|
||||
Keyboard.write(KEY_RIGHT_ARROW);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This however has a major issue. Each `Keyboard.write()` call generates a
|
||||
press/release cycle. If you keep a button pushed, instead of a single, long key
|
||||
press, the computer will receive a ton of press/release events. We need to keep
|
||||
the buttons states between `loop()` calls.
|
||||
|
||||
## Adding memory to the keyboard
|
||||
|
||||
Here's a second attempt, with two modifications. First, to ease the
|
||||
addition/removal of a button, the code uses arrays instead of doing all steps
|
||||
four times. Second thing changed: each button now remember its state.
|
||||
|
||||
_Stateful keyboard_
|
||||
|
||||
```cpp
|
||||
// Number of buttons to handle
|
||||
const int buttonsCount = 4;
|
||||
|
||||
// Arduino PINs to use
|
||||
const int pins[buttonsCount] = {
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
};
|
||||
|
||||
// Keys to send (order has to match the pins array)
|
||||
const byte keys[buttonsCount] = {
|
||||
KEY_UP_ARROW,
|
||||
KEY_DOWN_ARROW,
|
||||
KEY_LEFT_ARROW,
|
||||
KEY_RIGHT_ARROW
|
||||
};
|
||||
|
||||
bool status[buttonsCount] = {LOW};
|
||||
|
||||
void setup() {
|
||||
for (int i = 0; i < buttonsCount; ++i) {
|
||||
pinMode(pins[i], INPUT);
|
||||
}
|
||||
|
||||
Keyboard.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int i = 0; i < buttonsCount; ++i) {
|
||||
const int pinStatus = digitalRead(pins[i]);
|
||||
if (pinStatus != status[i]) {
|
||||
status[i] = pinStatus;
|
||||
if (pinStatus == HIGH) {
|
||||
Keyboard.press(keys[i]);
|
||||
} else {
|
||||
Keyboard.release(keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
So… the keyboard now remembers which buttons are pressed, and should generate
|
||||
a single couple of events for each button press/release. _Should_. There's still
|
||||
an issue: mechanical buttons are not perfect. Many events are still generated.
|
||||
This is due to a phenomenon called [bounce](http://en.wikipedia.org/wiki/Switch#Contact_bounce).
|
||||
|
||||
## Debouncing the keyboard
|
||||
|
||||
A simple way to debounce a button is, well, really simple: ignore all changes to
|
||||
the state of the button during a short delay after an initial change. While it's
|
||||
not the most precise way and could be problematic in a more complex scenario,
|
||||
it's perfectly fine to do this for a keyboard, given we keep this delay short
|
||||
enough.
|
||||
|
||||
Let's throw in an array to remember the last event acknowledged by the keyboard:
|
||||
|
||||
_Debounced keyboard_
|
||||
|
||||
```cpp
|
||||
// Number of buttons to handle
|
||||
const int buttonsCount = 4;
|
||||
|
||||
// Arduino PINs to use
|
||||
const int pins[buttonsCount] = {
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
};
|
||||
|
||||
// Keys to send (order has to match the pins array)
|
||||
const byte keys[buttonsCount] = {
|
||||
KEY_UP_ARROW,
|
||||
KEY_DOWN_ARROW,
|
||||
KEY_LEFT_ARROW,
|
||||
KEY_RIGHT_ARROW
|
||||
};
|
||||
|
||||
// Debounce delay
|
||||
const long debounceDelay = 50;
|
||||
|
||||
bool status[buttonsCount] = {LOW};
|
||||
long lastDebounces[buttonsCount] = {0};
|
||||
|
||||
void setup() {
|
||||
for (int i = 0; i < buttonsCount; ++i) {
|
||||
pinMode(pins[i], INPUT);
|
||||
}
|
||||
|
||||
Keyboard.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
for (int i = 0; i < buttonsCount; ++i) {
|
||||
const int pinStatus = digitalRead(pins[i]);
|
||||
if (pinStatus != status[i] && millis() - debounceDelay > lastDebounces[i]) {
|
||||
status[i] = pinStatus;
|
||||
if (pinStatus == HIGH) {
|
||||
Keyboard.press(keys[i]);
|
||||
} else {
|
||||
Keyboard.release(keys[i]);
|
||||
}
|
||||
lastDebounces[buttonNumber] = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You'll maybe need to adjust the debounce delay according to your buttons. Try to
|
||||
keep it as short as possible.
|
||||
|
||||
## Conclusion
|
||||
|
||||
And _voilà_! We now have a fully functional keyboard, to which it's easy to
|
||||
add/remove/change buttons. There's still room for improvement: it would be easy
|
||||
to allow it to send key sequences instead of single key presses, for example.
|
||||
|
||||
You can find the full code on [GitHub](https://github.com/Kernald/gameduino).
|
||||
Loading…
Add table
Add a link
Reference in a new issue