351 lines
11 KiB
C++
351 lines
11 KiB
C++
#include <Wire.h>
|
|
|
|
#define TRACKBALL_ADDR 0x0A
|
|
#define TRACKBALL_REG_LEFT 0x04
|
|
|
|
const byte PIN_A = 33;
|
|
const byte PIN_B = 32;
|
|
const byte PIN_C = 31;
|
|
const byte PIN_D = 24;
|
|
const byte PIN_E = 25;
|
|
const byte PIN_F = 26;
|
|
|
|
const byte PIN_MOUSE_UL = 27;
|
|
const byte PIN_MOUSE_UR = 30;
|
|
const byte PIN_MOUSE_DL = 28;
|
|
const byte PIN_MOUSE_DR = 29;
|
|
|
|
const byte PIN_LED1 = 6;
|
|
const byte PIN_LED2 = 1;
|
|
const byte PIN_LED3 = 3;
|
|
|
|
const byte PIN_BALL_SDA = 18;
|
|
const byte PIN_BALL_SCL = 19;
|
|
const byte PIN_BALL_INT = 11;
|
|
|
|
enum Button { A, B, C, D, E, F, MouseUL, MouseUR, MouseDL, MouseDR };
|
|
const int button_pins[10] = {
|
|
PIN_A, PIN_B, PIN_C, PIN_D, PIN_E, PIN_F,
|
|
PIN_MOUSE_UL, PIN_MOUSE_UR, PIN_MOUSE_DL, PIN_MOUSE_DR
|
|
};
|
|
const int debounce_time = 400;
|
|
int button_timers[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
// different actions that the keyboard can take
|
|
// - print a single character
|
|
// - press (and release) a key
|
|
// - toggle a modifier
|
|
enum ActionType { PrintCharacter, PressKey, ToggleModifier };
|
|
|
|
const char button_characters[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
const char button_characters_shifted[6] = { 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
const char button_characters_symbol[6] = { '1', '2', '3', '4', '5', '6' };
|
|
|
|
/*
|
|
backspace, space (keep those two first since they overlap with the rest )
|
|
g, k, o, s, w, native
|
|
*/
|
|
const byte chords[8] = { 7, 56, 24, 48, 3, 6, 40, 5 };
|
|
// buttons that can be combined with a chord for a keypress
|
|
const byte chord_buttons[24] = {
|
|
Button::D, Button::E, Button::F,
|
|
Button::A, Button::B, Button::C,
|
|
Button::A, Button::B, Button::C,
|
|
Button::A, Button::B, Button::C,
|
|
Button::D, Button::E, Button::F,
|
|
Button::D, Button::E, Button::F,
|
|
Button::A, Button::B, Button::C,
|
|
Button::D, Button::E, Button::F,
|
|
};
|
|
// keypresses for chords in sets of 4
|
|
// index 0 is the chord on its own, 1-3 correspond to chord_buttons
|
|
const int chord_targets[32] = {
|
|
KEY_BACKSPACE, KEY_LEFT, KEY_LEFT, KEY_HOME,
|
|
KEY_SPACE, KEY_RIGHT, KEY_RIGHT, KEY_END,
|
|
'g', 'h', 'i', 'j',
|
|
'k', 'l', 'm', 'n',
|
|
'o', 'p', 'q', 'r',
|
|
's', 't', 'u', 'v',
|
|
'w', 'x', 'y', 'z',
|
|
0, 0, 0, 0
|
|
};
|
|
const int chord_targets_shifted[32] = {
|
|
KEY_BACKSPACE, KEY_LEFT, KEY_LEFT, KEY_HOME,
|
|
KEY_SPACE, KEY_RIGHT, KEY_RIGHT, KEY_END,
|
|
'G', 'H', 'I', 'J',
|
|
'K', 'L', 'M', 'N',
|
|
'O', 'P', 'Q', 'R',
|
|
'S', 'T', 'U', 'V',
|
|
'W', 'X', 'Y', 'Z',
|
|
0, 0, 0, 0
|
|
};
|
|
// TODO: check that all the symbols work
|
|
// TODO: match symbols to keypresses in the keyboard layout
|
|
const int chord_targets_symbol[32] = {
|
|
KEY_BACKSPACE, KEY_LEFT, KEY_LEFT, KEY_HOME,
|
|
KEY_SPACE, KEY_RIGHT, KEY_RIGHT, KEY_END,
|
|
'0', '7', '8', '9',
|
|
'#', '@', '½', '&',
|
|
'+', '%', '=', '^',
|
|
'*', '$', '€', '£',
|
|
'(', '[', '<', '{',
|
|
')', ']', '>', '}'
|
|
};
|
|
const byte chord_target_types[32] = {
|
|
1, 1, 1, 1,
|
|
1, 1, 1, 1,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0
|
|
};
|
|
|
|
/*
|
|
dash, backslash, slash, apostrophe, comma, exclamation point, question mark,
|
|
period, up, down, page up, page down, shift, symbol shift, switch keyset,
|
|
escape, control, alt, delete, insert, tab, enter
|
|
*/
|
|
const byte specials[22] = {
|
|
17, 51, 30, 10, 20, 12, 33, 34, 9, 36, 27,
|
|
54, 18, 45, 63, 31, 47, 55, 62, 43, 61, 59
|
|
};
|
|
enum Modifier { Shift, SymbolShift, Keyset, Control, Alt };
|
|
const int special_action_targets[22] = {
|
|
'-', '\\', '/', '\'', ',', '!', '?', '/', KEY_UP, KEY_DOWN, KEY_PAGE_UP,
|
|
KEY_PAGE_DOWN, Modifier::Shift, Modifier::SymbolShift, Modifier::Keyset,
|
|
KEY_ESC, Modifier::Control, Modifier::Alt, KEY_DELETE, KEY_INSERT,
|
|
KEY_TAB, KEY_ENTER
|
|
};
|
|
const int special_action_targets_symbol[22] = {
|
|
'_', '\`', 0, '\"', ';', '|', '~', ':', 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0, 0,
|
|
0, 0
|
|
};
|
|
const byte special_action_target_types[22] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1
|
|
};
|
|
|
|
byte key_pressed = 0;
|
|
byte key_pressed_total = 0;
|
|
bool chord_used = false;
|
|
bool chorded_pressed = false;
|
|
|
|
bool mod_shift = false;
|
|
bool mod_shift_lock = false;
|
|
bool mod_symbol = false;
|
|
bool mod_symbol_lock = false;
|
|
// TODO: control and alt modifiers
|
|
bool mod_control = false;
|
|
bool mod_alt = false;
|
|
|
|
void setup() {
|
|
pinMode(PIN_A, INPUT_PULLUP);
|
|
pinMode(PIN_B, INPUT_PULLUP);
|
|
pinMode(PIN_C, INPUT_PULLUP);
|
|
pinMode(PIN_D, INPUT_PULLUP);
|
|
pinMode(PIN_E, INPUT_PULLUP);
|
|
pinMode(PIN_F, INPUT_PULLUP);
|
|
|
|
pinMode(PIN_MOUSE_UL, INPUT_PULLUP);
|
|
pinMode(PIN_MOUSE_UR, INPUT_PULLUP);
|
|
pinMode(PIN_MOUSE_DL, INPUT_PULLUP);
|
|
pinMode(PIN_MOUSE_DR, INPUT_PULLUP);
|
|
|
|
pinMode(PIN_LED1, OUTPUT);
|
|
pinMode(PIN_LED2, OUTPUT);
|
|
pinMode(PIN_LED3, OUTPUT);
|
|
|
|
Serial.begin(115200);
|
|
Serial.println("helo");
|
|
|
|
Wire.begin();
|
|
}
|
|
|
|
void loop() {
|
|
for (byte b = 0; b < 10; b++) {
|
|
// button is pressed
|
|
if (!digitalRead(button_pins[b])) {
|
|
// increment button timer if it's not full
|
|
if (button_timers[b] < debounce_time)
|
|
button_timers[b]++;
|
|
// update pressed gkos keys
|
|
else if (b < 6) {
|
|
key_pressed |= 1 << b;
|
|
key_pressed_total |= 1 << b;
|
|
}
|
|
}
|
|
// button is released
|
|
else {
|
|
// if the button had been debounced, it counts as a keypress
|
|
if (button_timers[b] >= debounce_time) {
|
|
if (b < 6) {
|
|
key_pressed &= ~(1 << b);
|
|
// gkos key was released, check if it completed a chord
|
|
// clear temporary modifiers if a keypress was completed
|
|
if (key_released(b))
|
|
mod_shift = mod_symbol = mod_control = mod_alt = false;
|
|
update_leds();
|
|
if (key_pressed == 0) {
|
|
key_pressed_total = 0;
|
|
// clear flags
|
|
chord_used = false;
|
|
chorded_pressed = false;
|
|
}
|
|
}
|
|
}
|
|
button_timers[b] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns true if a keypress was completed
|
|
bool key_released(byte key) {
|
|
int target = 0;
|
|
|
|
/*
|
|
SPECIALS
|
|
*/
|
|
bool is_special = false;
|
|
byte special = 0;
|
|
for (byte b = 0; b < 22; b++) {
|
|
if (key_pressed_total == specials[b]) {
|
|
is_special = true;
|
|
special = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_special && !chord_used) {
|
|
// only register a special keypress when all keys have been released
|
|
if (key_pressed != 0)
|
|
return false;
|
|
|
|
target = special_action_targets[special];
|
|
if (mod_shift || mod_shift_lock || mod_symbol || mod_symbol_lock) {
|
|
if (special_action_targets_symbol[special] != 0)
|
|
target = special_action_targets_symbol[special];
|
|
}
|
|
|
|
switch (special_action_target_types[special]) {
|
|
case ActionType::PrintCharacter:
|
|
Keyboard.print((char)target);
|
|
return true;
|
|
case ActionType::PressKey:
|
|
Keyboard.press(target);
|
|
Keyboard.release(target);
|
|
return true;
|
|
case ActionType::ToggleModifier:
|
|
switch (target) {
|
|
case Modifier::Shift:
|
|
mod_symbol = mod_symbol_lock = false;
|
|
if (mod_shift_lock) {
|
|
mod_shift_lock = false;
|
|
} else if (mod_shift) {
|
|
mod_shift = false;
|
|
mod_shift_lock = true;
|
|
} else {
|
|
mod_shift = true;
|
|
}
|
|
return false;
|
|
case Modifier::SymbolShift:
|
|
mod_shift = mod_shift_lock = mod_symbol_lock = false;
|
|
mod_symbol = !mod_symbol;
|
|
return false;
|
|
case Modifier::Keyset:
|
|
mod_shift = mod_shift_lock = mod_symbol = false;
|
|
mod_symbol_lock = !mod_symbol_lock;
|
|
return false;
|
|
case Modifier::Control:
|
|
mod_control = !mod_control;
|
|
return false;
|
|
case Modifier::Alt:
|
|
mod_alt = !mod_alt;
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
CHORDS
|
|
*/
|
|
bool is_chord = false;
|
|
byte chord = 0;
|
|
for (byte b = 0; b < 8; b++) {
|
|
if ((key_pressed_total & chords[b]) == chords[b]) {
|
|
is_chord = true;
|
|
chord = b;
|
|
chord_used = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_chord) {
|
|
// chord on its own
|
|
if (!chorded_pressed && key_pressed == 0 &&
|
|
key_pressed_total == chords[chord]) {
|
|
target = chord_targets[chord * 4];
|
|
if (mod_shift || mod_shift_lock)
|
|
target = chord_targets_shifted[chord * 4];
|
|
else if (mod_symbol || mod_symbol_lock)
|
|
target = chord_targets_symbol[chord * 4];
|
|
|
|
switch (chord_target_types[chord * 4]) {
|
|
case ActionType::PrintCharacter:
|
|
Keyboard.print((char)target);
|
|
return true;
|
|
case ActionType::PressKey:
|
|
Keyboard.press(target);
|
|
Keyboard.release(target);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// keys that can be chorded with
|
|
for (byte b = 0; b < 3; b++) {
|
|
if (key == chord_buttons[chord * 3 + b]) {
|
|
target = chord_targets[chord * 4 + 1 + b];
|
|
if (mod_shift || mod_shift_lock)
|
|
target = chord_targets_shifted[chord * 4 + 1 + b];
|
|
else if (mod_symbol || mod_symbol_lock)
|
|
target = chord_targets_symbol[chord * 4 + 1 + b];
|
|
|
|
// erase key from total so you can hold the chord down for
|
|
// multiple chorded keypresses
|
|
key_pressed_total &= ~(1 << key);
|
|
chorded_pressed = true;
|
|
switch (chord_target_types[chord * 4 + 1 + b]) {
|
|
case ActionType::PrintCharacter:
|
|
Keyboard.print((char)target);
|
|
return true;
|
|
case ActionType::PressKey:
|
|
Keyboard.press(target);
|
|
Keyboard.release(target);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
REGULAR KEYS
|
|
*/
|
|
if (mod_shift || mod_shift_lock)
|
|
Keyboard.print(button_characters_shifted[key]);
|
|
else if (mod_symbol || mod_symbol_lock)
|
|
Keyboard.print(button_characters_symbol[key]);
|
|
else
|
|
Keyboard.print(button_characters[key]);
|
|
return true;
|
|
}
|
|
|
|
void update_leds() {
|
|
digitalWrite(PIN_LED1, mod_shift || mod_shift_lock);
|
|
digitalWrite(PIN_LED2, mod_symbol || mod_symbol_lock);
|
|
digitalWrite(PIN_LED3, mod_control || mod_alt);
|
|
}
|