Skip to content

Instantly share code, notes, and snippets.

@Billiam
Last active May 6, 2023 03:59
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Billiam/ec4ccf967088edef36e67010839f3dd1 to your computer and use it in GitHub Desktop.
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)
// 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
};
/*
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;
}
@naivand
Copy link

naivand commented Jan 16, 2022

Hello, I like your project. Show the wiring diagram on the board.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment