-
-
Save Billiam/ec4ccf967088edef36e67010839f3dd1 to your computer and use it in GitHub Desktop.
Teensy LC sketch for Sherbet one-handed keypad (https://www.billiam.org/2019/05/29/designing-an-ergonomic-keypad)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Optional to control name of device. Place in same directory as sherbet.ino. | |
#include "usb_names.h" | |
#define PRODUCT_NAME {'s', 'h', 'e', 'r', 'b', 'e', 't'} | |
#define PRODUCT_NAME_LEN 7 | |
struct usb_string_descriptor_struct usb_string_product_name = { | |
2 + PRODUCT_NAME_LEN * 2, | |
3, | |
PRODUCT_NAME | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Original programming by Stefan Jakobsson, 2019 | |
Released to public domain | |
https://forum.pjrc.com/threads/55395-Keyboard-simple-firmware | |
*/ | |
/* | |
Copyright 2019 Colin Fein | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
// Use USB Type: Keybord+Mouse+Joystick | |
#include <Bounce2.h> | |
const int ROW_COUNT = 4; //Number of rows in the keyboard matrix | |
const int COL_COUNT = 6; //Number of columns in the keyboard matrix | |
const int DEBOUNCE = 5; //Adjust as needed: increase if bouncing problem, decrease if not all keypresses register; not less than 2 | |
const int SCAN_DELAY = 5; //Delay between scan cycles in ms | |
const int JOYSTICK_X_PIN = 14; // Analog pin used for the X axis | |
const int JOYSTICK_Y_PIN = 15; // Analog pin used for the Y axis | |
const bool REVERSE_X = true; // Reverses X axis input | |
const bool REVERSE_Y = true; // Reverses Y axis input | |
const int MIN_X = 215; // Minimum range for the X axis | |
const int MAX_X = 780; // Maxixmum range for the X axis | |
const int MIN_Y = 280; // Minimum range for the Y axis | |
const int MAX_Y = 815; // Maximum range for the Y axis | |
const int BUTTON_COUNT = 1; // Number of joystick buttons | |
const int JOY_MIN = 0; | |
const int JOY_MAX = 1023; | |
Bounce buttons[BUTTON_COUNT]; | |
Bounce switches[ROW_COUNT * COL_COUNT]; | |
boolean buttonStatus[ROW_COUNT * COL_COUNT + BUTTON_COUNT]; //store button status so that inputs can be released | |
boolean keyStatus[ROW_COUNT * COL_COUNT]; //store keyboard status so that keys can be released | |
const int rowPins[] = {3, 2, 1, 0}; //Teensy pins attached to matrix rows | |
const int colPins[] = {11, 10, 9, 8, 7, 6}; //Teensy pins attached to matrix columns | |
const int buttonPins[] = {12}; //Teensy pins attached directly to switches | |
int axes[] = {512, 512}; | |
int keyMode = true; // Whether to begin in standard qwerty mode or joystick button mode | |
// Keycodes for qwerty input | |
const int layer_rows[] = { | |
KEY_ESC, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, | |
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, | |
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, | |
MODIFIERKEY_SHIFT, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B | |
}; | |
// keystroke to use (counting from top left to top right of keypad) to switch between standard qwerty input and joystick buttons | |
// default uses B+5 | |
const int mode_swap_keystroke[2] = {23, 5}; | |
int pivoted_keystroke[2]; //rows to columns | |
boolean keystrokeModifier = false; //whether beginning of keystroke is active | |
// rows to columns | |
int layer[ROW_COUNT * COL_COUNT]; | |
void setup() { | |
int i; | |
//pivot key array for row-to-column diodes | |
for (i = 0; i < ROW_COUNT * COL_COUNT; i++) { | |
layer[rotateIndex(i)] = layer_rows[i]; | |
// create debouncers for row pins | |
Bounce debouncer = Bounce(); | |
debouncer.attach(rowPins[i % ROW_COUNT]); | |
debouncer.interval(DEBOUNCE); | |
switches[i] = debouncer; | |
} | |
//convert keystroke to (pivoted) indexes | |
for (i = 0; i < 2; i++) { | |
pivoted_keystroke[i] = rotateIndex(mode_swap_keystroke[i]); | |
} | |
// create debouncers for non-matrix input pins | |
for (i = 0; i < BUTTON_COUNT; i++) { | |
Bounce debouncer = Bounce(); | |
debouncer.attach(buttonPins[i], INPUT_PULLUP); | |
debouncer.interval(DEBOUNCE); | |
buttons[i] = debouncer; | |
} | |
// Ground first column pin | |
pinMode(colPins[0], OUTPUT); | |
digitalWrite(colPins[0], LOW); | |
for (i = 1; i < COL_COUNT; i++) { | |
pinMode(colPins[i], INPUT); | |
} | |
//Row pins | |
for (i = 0; i < ROW_COUNT; i++) { | |
pinMode(rowPins[i], INPUT_PULLUP); | |
} | |
} | |
void loop() { | |
scanMatrix(); | |
scanJoy(); | |
delay(SCAN_DELAY); | |
} | |
/* | |
Scan keyboard matrix, triggering press and release events | |
*/ | |
void scanMatrix() { | |
int i; | |
for (i = 0; i < ROW_COUNT * COL_COUNT; i++) { | |
prepareMatrixRead(i); | |
switches[i].update(); | |
if (switches[i].fell()) { | |
matrixPress(i); | |
} else if (switches[i].rose()) { | |
matrixRelease(i); | |
} | |
} | |
} | |
/* | |
Scan physical, non-matrix joystick buttons | |
*/ | |
void scanJoy() { | |
int i; | |
boolean anyChange = false; | |
for (i=0; i < BUTTON_COUNT; i++) { | |
buttons[i].update(); | |
if (buttons[i].fell()) { | |
buttonPress(i); | |
anyChange = true; | |
} else if (buttons[i].rose()) { | |
buttonRelease(i); | |
anyChange = true; | |
} | |
} | |
int x = getJoyDeflection(JOYSTICK_X_PIN, REVERSE_X, MIN_X, MAX_X); | |
int y = getJoyDeflection(JOYSTICK_Y_PIN, REVERSE_Y, MIN_Y, MAX_Y); | |
Joystick.X(x); | |
Joystick.Y(y); | |
if (x != axes[0] || y != axes[y]) { | |
anyChange = true; | |
axes[0] = x; | |
axes[1] = y; | |
} | |
if (anyChange) { | |
Joystick.send_now(); | |
} | |
} | |
/* | |
Return a remapped and clamped analog value | |
*/ | |
int getJoyDeflection(int pin, boolean reverse, int min, int max) { | |
int input = analogRead(pin); | |
if (reverse) { | |
input = JOY_MAX - input; | |
} | |
return map(constrain(input, min, max), min, max, JOY_MIN, JOY_MAX); | |
} | |
/* | |
Returns input pin to be read by keyScan method | |
Param key is the keyboard matrix scan code (col * ROW_COUNT + row) | |
*/ | |
void prepareMatrixRead(int key) { | |
static int currentCol = 0; | |
int p = key / ROW_COUNT; | |
if (p != currentCol) { | |
pinMode(colPins[currentCol], INPUT); | |
pinMode(colPins[p], OUTPUT); | |
digitalWrite(colPins[p], LOW); | |
currentCol = p; | |
} | |
} | |
/* | |
Sends key press event | |
Param keyCode is the keyboard matrix scan code (col * ROW_COUNT + row) | |
*/ | |
void matrixPress(int keyCode) { | |
if (keyMode) { | |
keyPress(keyCode); | |
} else { | |
buttonPress(BUTTON_COUNT + keyCode); | |
} | |
keystrokePress(keyCode); | |
} | |
/* | |
Sends key release event | |
Param keyCode is the keyboard matrix scan code (col * ROW_COUNT + row) | |
*/ | |
void matrixRelease(int keyCode) { | |
//TODO: Possibly do not trigger keyboard.release if key not already pressed (due to changing modes) | |
if (keyMode) { | |
keyRelease(keyCode); | |
} else { | |
buttonRelease(BUTTON_COUNT + keyCode); | |
} | |
keystrokeRelease(keyCode); | |
} | |
/* | |
Send key press event | |
*/ | |
void keyPress(int keyCode) { | |
Keyboard.press(layer[keyCode]); | |
keyStatus[keyCode]=true; | |
} | |
/* | |
Send key release event | |
*/ | |
void keyRelease(int keyCode) { | |
Keyboard.release(layer[keyCode]); | |
keyStatus[keyCode]=false; | |
} | |
/* | |
Send joystick button press event | |
Param buttonId 0-indexed button ID | |
*/ | |
void buttonPress(int buttonId) { | |
Joystick.button(buttonId + 1, 1); | |
buttonStatus[buttonId] = true; | |
} | |
/* | |
Send joystick button release event | |
Param buttonId 0-indexed button ID | |
*/ | |
void buttonRelease(int buttonId) { | |
Joystick.button(buttonId + 1, 0); | |
buttonStatus[buttonId] = false; | |
} | |
/* | |
Listen for keystroke keys, and change keyboard mode when condition is met | |
*/ | |
void keystrokePress(int keyCode) { | |
if (keyCode == pivoted_keystroke[0]) { | |
keystrokeModifier = true; | |
} else if (keystrokeModifier && keyCode == pivoted_keystroke[1]) { | |
releaseLayer(); | |
keyMode = !keyMode; | |
} | |
} | |
/* | |
Listen for keystroke key release, unsetting keystroke flag | |
*/ | |
void keystrokeRelease(int keyCode) { | |
if (keyCode == pivoted_keystroke[0]) { | |
keystrokeModifier = false; | |
} | |
} | |
/* | |
Releases all matrix and non-matrix keys; called upon change of key mode | |
*/ | |
void releaseLayer() { | |
int i; | |
for (i = 0; i < ROW_COUNT * COL_COUNT; i++) { | |
matrixRelease(i); | |
} | |
for (i=0; i < BUTTON_COUNT; i++) { | |
if (buttonStatus[i]) { | |
buttonRelease(i); | |
} | |
} | |
} | |
/* | |
Converts an index in a row-first sequence to column-first | |
[1, 2, 3] [1, 4, 7] | |
[4, 5, 6] => [2, 5, 8] | |
[7, 8, 9] [3, 6, 9] | |
*/ | |
int rotateIndex(int index) { | |
return index % COL_COUNT * ROW_COUNT + index / COL_COUNT; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, I like your project. Show the wiring diagram on the board.