ToeKeys is an 8 button interface that applies transformations to
button state before passing the state to a function to send USB
keystrokes.
Requirements:
- Arduino Micro. May work on other atmega32u4 based products
- 8x double throw switches
- 16 IO pins connected as shown in toekeys_schematic.png
Features: - Fast, reliable and trivial debounce code made possible
by the use of double throw switches. - Fast button handling. Polling all buttons and applying all
transformations happens within 4µs. - Supported button state transformations: reverse, toggle,
repeat, strike/hold. - Send up to 6 keys per button.
/* ToeKeys v0.05: USB macro keyboard : For Arduino Micro */
#include <Arduino.h>
void setup()
{
pinSetup();
Keyboard.begin();
}
void loop()
{
word button_state = pinModify(pinRead());
runMacros(button_state);
}
/* ==============Abstract===============
ToeKeys is an 8 button interface that applies transformations to
button state before passing the state to a function to send USB
keystrokes.
Requirements:
- Arduino Micro. May work on other atmega32u4 based products
- 8x double throw switches
- 16 IO pins connected as shown in toekeys_schematic.png
Features:
- Fast, reliable and trivial debounce code made possible
by the use of double throw switches.
- Fast button handling. Polling all buttons and applying all
transformations happens within 4µs.
- Supported button state transformations: reverse, toggle,
repeat, strike/hold.
- Send up to 6 keys per button.
*/
// ================Macros==================
const word repeat_rate = 200; //milliseconds
const byte key_hold = B00000100;
/* key_hold is standard keyboard behaviour with keydown
and keyup coinciding with button press and release.
If not key_hold both key reports will be sent on button press*/
/* A maximum of 6 keys can be held simultaneously (not including
shift, ctrl, alt). Support for sending much longer key
sequences is on the wishlist but not by increasing the size of
this array. */
const char key_code[8][7] = {{'a'},
{' '},
{KEY_LEFT_CTRL},
{'f'},
{'g'},
{'h'},
{"groovy"},
{"kool\n"}
};
void runMacros(word pin_state) {
if (!(pin_state >> 8)) return;
//quick exit if nothing changed
for (byte i=0;i<8;i++){
if ((pin_state >> (8+i)) & 1){
if (!((key_hold >> i) & 1)){
if ((pin_state >> i) & 1)
macroStrike(i);
}
else {
macroHold(i, (pin_state >> i) & 1);
}
}
}
}
void macroStrike(byte id) {
Keyboard.print(key_code[id]);
}
void macroHold(byte id, byte pressed) {
for (byte i=0;i<6;i++){
if (pressed)
Keyboard.press(key_code[id][i]);
else
Keyboard.release(key_code[id][i]);
}
}
// ===============Pins===================
/* Port masks for reading IO ports directly .. low byte is
primary state data. High byte is opposite throw of switches
for debounce code */
#define PB_MASK B11111100 // MO,MI,D11-D8 .. high byte
#define PF_MASK B11110011 // A5-A0 .. low byte
#define PD_MASK B11000011 // D3,D2,D12,D6 .. shifted << 2
const byte mod_reverse = B00000001;
const byte mod_toggle = B00000010;
const byte mod_repeat = B10000010;
void pinSetup()
{
DDRB &= ~PB_MASK;
DDRD &= ~PD_MASK;
DDRF &= ~PF_MASK;
/* using 3 ports to rally 16 input pins */
PORTB |= PB_MASK;
PORTD |= PD_MASK;
PORTF |= PF_MASK;
/* The combination of INPUT_PULLUP and reading normally
closed switches means 1 = "on". Yay. */
}
word pinRead()
{
word pin_read = (((PINB & PB_MASK) << 8)
| ((PIND & PD_MASK) << 2)
| (PINF & PF_MASK));
byte bounce_mask = (pin_read >> 8) ^ pin_read;
/* If nether side of a double throw switch shows a closed
circuit it's read is masked and we treat that button
as if it hasn't changed since last read. */
static byte last_read = 0;
byte pin_changed = (pin_read ^ last_read) & bounce_mask;
last_read = (pin_read & bounce_mask)
| (last_read & ~bounce_mask);
return last_read | (pin_changed << 8);
/* Button state is passed around this sketch in this format.
High byte is change flags. Low byte is current status. */
}
word pinModify(word mod_state)
{
mod_state ^= mod_reverse;
// up is down
mod_state = modToggle(mod_state);
// on state is toggled at button press
mod_state = modRepeat(mod_state);
/* a shared timer periodically asserts changed flag while
button is down */
return mod_state;
}
word modToggle(word mod_state)
{
static byte tglon_state = 0;
tglon_state ^= mod_state & (mod_state >> 8);
mod_state &= (mod_state | ~mod_toggle) << 8 | 0xFF;
mod_state = (mod_state & ~mod_toggle)
| (tglon_state & mod_toggle);
return mod_state;
}
word modRepeat(word mod_state)
{
static unsigned long timer = 0;
unsigned long now = millis();
if ((timer + repeat_rate) < now) {
timer = now;
mod_state |= (mod_state & mod_repeat) << 8;
}
return mod_state;
}