422 lines
18 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* =======================================================================================================
* -------------------------------------------------------------------------------------------------------
* ---####################-----###########-------###########-----############--############-############--
* --######################---#############-----#############---- -- - ---
* --###### ##---##### ###-----### #####---------##-------#######------#-------------
* -- -------------- --- ----- --- ----- ---------##-------#------------#-------------
* --#####--------------------#####------####-####------#####---------##-------###########--############--
* -- -------------------- ------ ------ --------- ------- -- --
* --#####--------------------#####--------#####--------#####---------------------------------------------
* -- -------------------- -------- -------- ---------------------------------------------
* --######--------------##---#####---------------------#####---------- CMtec CMDR Keyboard 42 -----------
* --##################### ---#####---------------------#####---------------------------------------------
* ---################### ----#####---------------------#####---------------------------------------------
* --- ----- --------------------- ---------------------------------------------
* -------------------------------------------------------------------------------------------------------
* =======================================================================================================
*
* Copyright 2022 Christoffer Martinsson <cm@cmtec.se>
*
* CMtec CMDR Keyboard 42 can be redistributed and/or modified under the terms of the GNU General
* Public License (Version 2), as published by the Free Software Foundation.
* A copy of the license can be found online at www.gnu.o urg/licenses.
*
* CMtec CMDR Keyboard 42 is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* Keyboard/Mouse based on standard teensy "Keypad" library for button scanning, standard teensy
* "usb_keyboard" library for HID keyboard/mouse usb data communication.
*
* Layer 0
* --------------------------------------- ---------------------------------------
* | Esc | Q | W | E | R | T | | Y | U | I | O | P | Å |
* | LCtrl | A | S | D | F | G | | H | J | K | L | Ö | Ä |
* | Shift | Z | X | C | V | B | | N | M | , | . | - | Shift |
* --------------------| Alt | Fn1 | Spc | | Spc | Fn1 | Win |--------------------
* ------------------- -------------------
* Layer 1 (Fn1)
* --------------------------------------- ---------------------------------------
* | Tab | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | BSpc |
* | LCtrl | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | Enter |
* | Shift | 6 | 7 | 8 | 9 | 0 | | < | ´ | ' | ¨ | + | Shift |
* --------------------| Alt | Fn1 | Spc | | BSpc| Fn1 |AlrGr|--------------------
* ------------------- -------------------
* Layer 2 (Fn1 + Fn1)
* --------------------------------------- ---------------------------------------
* | F11 | F12 | F13 | F14 | F15 | F16 | | § | | | | CpLk| BSpc |
* | LCtrl | Play| Next| F17 | F18 | F19 | | Left| Down| Up |Right| Del | Enter |
* | Shift | F20 | F21 | F22 | F23 | F24 | | Home| PgD | PgU | End | Ins | Shift |
* --------------------| Alt | Fn1 | Win | | BSpc| Fn1 | Win |--------------------
* ------------------- -------------------
* Features:
*
* * 42 keys "Split" keyboard layout. 36 finger buttons and 6 thumb buttons.
* * Extreme low profile (only one pcb).
* * Cost efficient solution with one pcb and one 3D printed cover.
* * Function keys with total of three layer support (Primary + 2fn layers).
* * Mouse movement, wheel up, wheel down, left button, right button and middle button support
* * Status indication -
* - LED off = Normal mode
* - LED flashing = Caps Lock activated
*/
#include <Arduino.h>
#include <Keypad.h>
#define USB_LED_NUM_LOCK 0
#define USB_LED_CAPS_LOCK 1
#define USB_LED_SCROLL_LOCK 2
#define KEY_OFFSET 0xAA00 // Offset to apply for not interfere with already defined keyboard keys
#define KEY_FN1 10 + KEY_OFFSET // Function layer 1 button
#define KEY_FN2 11 + KEY_OFFSET // Function layer 2 button
#define KEY_WIN_LATCH 12 + KEY_OFFSET // Function layer 2 button
#define TAP_TIMEOUT 150 // Key tap timeout (ms)
#define NBR_OF_BUTTONS 42 // Number of buttons used (42 in this case)
struct Button
{
int keypad_kchar = 0;
uint16_t keycode = NO_KEY;
uint16_t fn1_keycode = NO_KEY;
uint16_t fn2_keycode = NO_KEY;
int kstate = IDLE;
uint16_t last_keycode = NO_KEY;
bool run_keycode = false;
};
const byte KP_ROWS = 4;
const byte KP_COLS = 12;
byte kp_rowPins[KP_ROWS] = {0, 1, 2, 3};
byte kp_colPins[KP_COLS] = {9, 8, 7, 6, 5, 4, 19, 18, 17, 16, 15, 14};
char kp_keys[KP_ROWS][KP_COLS] = {
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
{13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24},
{25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36},
{0, 0, 0, 37, 38, 39, 40, 41, 42, 0, 0, 0}};
Keypad kp_keypad = Keypad(makeKeymap(kp_keys), kp_rowPins, kp_colPins, KP_ROWS, KP_COLS);
/*
* "Button ID" corresponding with the physical design of the actual keyboard. DO NOT CHANGE BTN ID!
------------------------------------- -------------------------------------
| 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 1O | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | | 31 | 32 | 33 | 34 | 35 | 36 |
------------------| 37 | 38 | 39 | | 40 | 41 | 42 |------------------
------------------- -------------------
* "Fn0 key" is the layer 0 key to use.
* "Fn1 key" is the layer 1 key to use. Don NOT add KEY_FN1 or KEY_FN2 to this layer.
* "Fn2 key" is the layer 2 key to use. Don NOT add KEY_FN1 or KEY_FN2 to this layer.
/* Keymap config ----------------------------------------------------------------------------------------------------------------------------------- */
// clang-format off
Button buttons[NBR_OF_BUTTONS] =
{
/* Btn ID Fn0 (hold) key Fn1 key Fn2 key */
{1, KEY_ESC, KEY_TAB, KEY_F11, IDLE, NO_KEY, false},
{2, KEY_Q, KEY_F1, KEY_F12, IDLE, NO_KEY, false},
{3, KEY_W, KEY_F2, KEY_F13, IDLE, NO_KEY, false},
{4, KEY_E, KEY_F3, KEY_F14, IDLE, NO_KEY, false},
{5, KEY_R, KEY_F4, KEY_F15, IDLE, NO_KEY, false},
{6, KEY_T, KEY_F5, KEY_F16, IDLE, NO_KEY, false},
{7, KEY_Y, KEY_F6, '§', IDLE, NO_KEY, false},
{8, KEY_U, KEY_F7, KEY_WIN_LATCH, IDLE, NO_KEY, false},
{9, KEY_I, KEY_F8, KEY_LEFT_GUI, IDLE, NO_KEY, false},
{10, KEY_O, KEY_F9, NO_KEY, IDLE, NO_KEY, false},
{11, KEY_P, KEY_F10, KEY_CAPS_LOCK, IDLE, NO_KEY, false},
{12, 'å', KEY_BACKSPACE, KEY_BACKSPACE, IDLE, NO_KEY, false},
{13, KEY_LEFT_CTRL, KEY_LEFT_CTRL, KEY_LEFT_CTRL, IDLE, NO_KEY, false},
{14, KEY_A, KEY_1, KEY_MEDIA_PLAY_PAUSE, IDLE, NO_KEY, false},
{15, KEY_S, KEY_2, KEY_MEDIA_NEXT_TRACK, IDLE, NO_KEY, false},
{16, KEY_D, KEY_3, KEY_F17, IDLE, NO_KEY, false},
{17, KEY_F, KEY_4, KEY_F18, IDLE, NO_KEY, false},
{18, KEY_G, KEY_5, KEY_F19, IDLE, NO_KEY, false},
{19, KEY_H, KEY_6, KEY_LEFT_ARROW, IDLE, NO_KEY, false},
{20, KEY_J, KEY_7, KEY_DOWN_ARROW, IDLE, NO_KEY, false},
{21, KEY_K, KEY_8, KEY_UP_ARROW, IDLE, NO_KEY, false},
{22, KEY_L, KEY_9, KEY_RIGHT_ARROW, IDLE, NO_KEY, false},
{23, 'ö', KEY_0, KEY_DELETE, IDLE, NO_KEY, false},
{24, 'ä', KEY_ENTER, KEY_ENTER, IDLE, NO_KEY, false},
{25, KEY_LEFT_SHIFT, KEY_LEFT_SHIFT, KEY_LEFT_SHIFT, IDLE, NO_KEY, false},
{26, KEY_Z, KEY_6, KEY_F20, IDLE, NO_KEY, false},
{27, KEY_X, KEY_7, KEY_F21, IDLE, NO_KEY, false},
{28, KEY_C, KEY_8, KEY_F22, IDLE, NO_KEY, false},
{29, KEY_V, KEY_9, KEY_F23, IDLE, NO_KEY, false},
{30, KEY_B, KEY_0, KEY_F24, IDLE, NO_KEY, false},
{31, KEY_N, '<', KEY_HOME, IDLE, NO_KEY, false},
{32, KEY_M, KEY_EQUAL, KEY_PAGE_DOWN, IDLE, NO_KEY, false},
{33, KEY_COMMA, KEY_BACKSLASH, KEY_PAGE_UP, IDLE, NO_KEY, false},
{34, KEY_PERIOD, KEY_RIGHT_BRACE, KEY_END, IDLE, NO_KEY, false},
{35, KEY_SLASH, KEY_MINUS, KEY_INSERT, IDLE, NO_KEY, false},
{36, KEY_RIGHT_SHIFT, KEY_RIGHT_SHIFT, KEY_RIGHT_SHIFT, IDLE, NO_KEY, false},
{37, KEY_LEFT_ALT, KEY_LEFT_ALT, KEY_LEFT_ALT, IDLE, NO_KEY, false},
{38, KEY_FN1, KEY_FN1, KEY_FN1, IDLE, NO_KEY, false},
{39, KEY_SPACE, KEY_SPACE, KEY_LEFT_GUI, IDLE, NO_KEY, false},
{40, KEY_SPACE, KEY_BACKSPACE, KEY_BACKSPACE, IDLE, NO_KEY, false},
{41, KEY_FN1, KEY_FN1, KEY_FN1, IDLE, NO_KEY, false},
{42, KEY_FN1, KEY_RIGHT_ALT, KEY_RIGHT_ALT, IDLE, NO_KEY, false}};
// clang-format on
/* End of keymap config ----------------------------------------------------------------------------------------------------------------------------- */
const int STATUS_LED = 13;
bool status_led_on = false;
int status_led_mode = 0;
unsigned long current_timestamp = 0;
unsigned long button_timestamp = 0;
unsigned long indicator_timestamp = 0;
bool key_pressed = false;
bool win_latched = false;
/**
Perform key action.
@param keycode code to apply action.
@param kstate PRESSED or RELEASED.
@return Action applied.
*/
bool set_key(uint16_t keycode, uint8_t kstate)
{
/* Abort if keycode is invalid */
if (keycode == NO_KEY || keycode == KEY_FN1 || keycode == KEY_FN2)
{
return false;
}
else if (keycode == KEY_WIN_LATCH)
{
win_latched = true;
}
/* Normal keyboard keys (HID keyboard) */
else
{
if (kstate == RELEASED)
{
if (win_latched == true)
{
Keyboard.release(keycode);
Keyboard.release(KEY_LEFT_GUI);
win_latched = false;
}
else
{
Keyboard.release(keycode);
}
}
else if (kstate == PRESSED)
{
if (win_latched == true)
{
Keyboard.press(KEY_LEFT_GUI);
Keyboard.press(keycode);
}
else
{
Keyboard.press(keycode);
}
}
}
return true;
}
/**
Scan key matrix and perform processing for each key.
@return void.
*/
void scan_buttons()
{
/* Scan keypad */
if (kp_keypad.getKeys())
{
/* Enter bootloader if all four corner-buttons is pressed together */
int reboot = 0;
for (int i = 0; i < LIST_MAX; i++)
{
if ((kp_keypad.key[i].kchar == 1) && (kp_keypad.key[i].kstate == PRESSED || kp_keypad.key[i].kstate == HOLD))
{
reboot += 1;
}
if ((kp_keypad.key[i].kchar == 25) && (kp_keypad.key[i].kstate == PRESSED || kp_keypad.key[i].kstate == HOLD))
{
reboot += 1;
}
if ((kp_keypad.key[i].kchar == 12) && (kp_keypad.key[i].kstate == PRESSED || kp_keypad.key[i].kstate == HOLD))
{
reboot += 1;
}
if ((kp_keypad.key[i].kchar == 36) && (kp_keypad.key[i].kstate == PRESSED || kp_keypad.key[i].kstate == HOLD))
{
reboot += 1;
}
}
if (reboot == 4)
{
_reboot_Teensyduino_();
}
/* Check for Fn1 mode */
int fn_mode = 0;
for (int i = 0; i < LIST_MAX; i++)
{
if (kp_keypad.key[i].kstate == PRESSED || kp_keypad.key[i].kstate == HOLD)
{
for (int j = 0; j < NBR_OF_BUTTONS; j++)
{
if (buttons[j].keypad_kchar == kp_keypad.key[i].kchar)
{
/* Check if FN1 key are defined to this button (Layer 0 and first position in combo array)*/
if (buttons[j].keycode == KEY_FN1)
{
fn_mode++;
}
break;
}
}
}
}
/* Process key press/release */
for (int i = 0; i < LIST_MAX; i++)
{
if (kp_keypad.key[i].kstate == PRESSED)
{
for (int j = 0; j < NBR_OF_BUTTONS; j++)
{
if (buttons[j].keypad_kchar == kp_keypad.key[i].kchar && kp_keypad.key[i].stateChanged == true)
{
buttons[j].run_keycode = true;
buttons[j].kstate = PRESSED;
break;
}
}
}
else if (kp_keypad.key[i].kstate == RELEASED)
{
for (int j = 0; j < NBR_OF_BUTTONS; j++)
{
if (buttons[j].keypad_kchar == kp_keypad.key[i].kchar && kp_keypad.key[i].stateChanged == true)
{
buttons[j].run_keycode = true;
buttons[j].kstate = RELEASED;
break;
}
}
}
}
/* Execute key commands */
for (int i = 0; i < NBR_OF_BUTTONS; i++)
{
/* Check if key should be processed */
if (buttons[i].run_keycode == true)
{
/* Check if key pressed or released */
if (buttons[i].kstate == PRESSED)
{
if (fn_mode == 0)
{
set_key(buttons[i].keycode, PRESSED);
buttons[i].last_keycode = buttons[i].keycode;
}
else if (fn_mode == 1)
{
set_key(buttons[i].fn1_keycode, PRESSED);
buttons[i].last_keycode = buttons[i].fn1_keycode;
}
else if (fn_mode == 2)
{
set_key(buttons[i].fn2_keycode, PRESSED);
buttons[i].last_keycode = buttons[i].fn2_keycode;
}
}
else if (buttons[i].kstate == RELEASED)
{
/* Sending release command for last keycode related to this button */
set_key(buttons[i].last_keycode, RELEASED);
}
/* Reset run_keycode flag */
buttons[i].run_keycode = false;
}
}
}
/* Set status indication */
if (keyboard_leds & (1 << USB_LED_CAPS_LOCK))
{
/* 2 = blinking (CapsLock) */
status_led_mode = 2;
}
else if (win_latched)
{
status_led_mode = 1;
}
else
{
/* 0 = off (normal) */
status_led_mode = 0;
}
}
void setup()
{
/* Init HW */
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, LOW);
}
void loop()
{
/* Update current time (ms) */
current_timestamp = millis();
/* Scan buttons 1ms */
if (current_timestamp >= button_timestamp)
{
button_timestamp = current_timestamp + 1;
scan_buttons();
}
/* Update indicator 200ms */
if (current_timestamp >= indicator_timestamp)
{
if (status_led_mode == 2 && status_led_on == false)
{
digitalWrite(STATUS_LED, HIGH);
status_led_on = true;
}
else if (status_led_mode == 2 && status_led_on == true)
{
digitalWrite(STATUS_LED, LOW);
status_led_on = false;
}
else if (status_led_mode == 1)
{
digitalWrite(STATUS_LED, HIGH);
status_led_on = true;
}
else
{
digitalWrite(STATUS_LED, LOW);
status_led_on = false;
}
indicator_timestamp = current_timestamp + 200;
}
}