Co-authored-by: Drashna Jaelre <drashna@live.com>pull/17535/head
@ -0,0 +1,130 @@ | |||
/* | |||
Copyright 2020 Kan-Ru Chen <kanru@kanru.info> | |||
Copyright 2012 Jun Wako <wakojun@gmail.com> | |||
This program is free software: you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation, either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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. | |||
You should have received a copy of the GNU General Public License | |||
along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#pragma once | |||
#include "config_common.h" | |||
/* USB Device descriptor parameter */ | |||
#define VENDOR_ID 0x4848 // HH = happy hacking | |||
#define PRODUCT_ID 0x0001 // ANSI HHKB | |||
#define DEVICE_VER 0x0104 | |||
#define MANUFACTURER YANG | |||
#define PRODUCT HHKB BLE Keyboard | |||
/* key matrix size */ | |||
#define MATRIX_ROWS 8 | |||
#define MATRIX_COLS 8 | |||
/* matrix power saving */ | |||
#define MATRIX_POWER_SAVE_TIMEOUT_MS 10000 | |||
#define MATRIX_POWER_SAVE_TIMEOUT_L2_MS 1800000 | |||
#define MATRIX_POWER_SAVE_TIMEOUT_L3_MS 7200000 | |||
#define LED_CAPS_LOCK_PIN F4 | |||
#ifdef BLUETOOTH_ENABLE | |||
# define OUTPUT_DEFAULT OUTPUT_AUTO | |||
# undef SERIAL_UART_BAUD | |||
# undef SERIAL_UART_DATA | |||
# undef SERIAL_UART_UBRR | |||
# undef SERIAL_UART_RXD_VECT | |||
# undef SERIAL_UART_TXD_READY | |||
# undef SERIAL_UART_INIT | |||
# define SERIAL_UART_BAUD 76800 | |||
# define SERIAL_UART_DATA UDR1 | |||
# define SERIAL_UART_UBRR (F_CPU / (8UL * SERIAL_UART_BAUD) - 1) | |||
# define SERIAL_UART_RXD_VECT USART1_RX_vect | |||
# define SERIAL_UART_TXD_READY (UCSR1A & _BV(UDRE1)) | |||
# define SERIAL_UART_INIT() \ | |||
do { \ | |||
cli(); \ | |||
/* baud rate */ \ | |||
UBRR1L = SERIAL_UART_UBRR; \ | |||
/* baud rate */ \ | |||
UBRR1H = SERIAL_UART_UBRR >> 8; \ | |||
/* enable TX */ \ | |||
UCSR1B |= (0 << TXCIE1) | (1 << TXEN1); \ | |||
/* enable RX */ \ | |||
UCSR1B |= (1 << RXCIE1) | (1 << RXEN1); \ | |||
/* parity: none(00), even(01), odd(11) */ \ | |||
UCSR1C |= (0 << UPM11) | (0 << UPM10); \ | |||
/* 2x speed (error = 0.2%) */ \ | |||
UCSR1A |= (1 << U2X1); \ | |||
sei(); \ | |||
} while (0) | |||
#endif | |||
/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ | |||
//#define LOCKING_SUPPORT_ENABLE | |||
/* Locking resynchronize hack */ | |||
//#define LOCKING_RESYNC_ENABLE | |||
/* If defined, GRAVE_ESC will always act as ESC when CTRL is held. | |||
* This is useful for the Windows task manager shortcut (ctrl+shift+esc). | |||
*/ | |||
//#define GRAVE_ESC_CTRL_OVERRIDE | |||
/* | |||
* Force NKRO | |||
* | |||
* Force NKRO (nKey Rollover) to be enabled by default, regardless of the saved | |||
* state in the bootmagic EEPROM settings. (Note that NKRO must be enabled in the | |||
* makefile for this to work.) | |||
* | |||
* If forced on, NKRO can be disabled via magic key (default = LShift+RShift+N) | |||
* until the next keyboard reset. | |||
* | |||
* NKRO may prevent your keystrokes from being detected in the BIOS, but it is | |||
* fully operational during normal computer usage. | |||
* | |||
* For a less heavy-handed approach, enable NKRO via magic key (LShift+RShift+N) | |||
* or via bootmagic (hold SPACE+N while plugging in the keyboard). Once set by | |||
* bootmagic, NKRO mode will always be enabled until it is toggled again during a | |||
* power-up. | |||
* | |||
*/ | |||
//#define FORCE_NKRO | |||
/* | |||
* Feature disable options | |||
* These options are also useful to firmware size reduction. | |||
*/ | |||
/* disable debug print */ | |||
//#define NO_DEBUG | |||
/* disable print */ | |||
//#define NO_PRINT | |||
/* disable action features */ | |||
//#define NO_ACTION_LAYER | |||
//#define NO_ACTION_TAPPING | |||
//#define NO_ACTION_ONESHOT | |||
/* Bootmagic Lite key configuration */ | |||
//#define BOOTMAGIC_LITE_ROW 0 | |||
//#define BOOTMAGIC_LITE_COLUMN 0 | |||
//#define DEBUG_MATRIX_SCAN_RATE | |||
// Disable debounce | |||
#define DEBOUNCE 0 |
@ -0,0 +1,71 @@ | |||
{ | |||
"keyboard_name": "YANG HHKB BLE", | |||
"url": "", | |||
"maintainer": "qmk", | |||
"layouts": { | |||
"LAYOUT_60_hhkb": { | |||
"layout": [ | |||
{ "label": "Esc", "x": 0, "y": 0 }, | |||
{ "label": "!", "x": 1, "y": 0 }, | |||
{ "label": "@", "x": 2, "y": 0 }, | |||
{ "label": "#", "x": 3, "y": 0 }, | |||
{ "label": "$", "x": 4, "y": 0 }, | |||
{ "label": "%", "x": 5, "y": 0 }, | |||
{ "label": "^", "x": 6, "y": 0 }, | |||
{ "label": "&", "x": 7, "y": 0 }, | |||
{ "label": "*", "x": 8, "y": 0 }, | |||
{ "label": "(", "x": 9, "y": 0 }, | |||
{ "label": ")", "x": 10, "y": 0 }, | |||
{ "label": "_", "x": 11, "y": 0 }, | |||
{ "label": "+", "x": 12, "y": 0 }, | |||
{ "label": "|", "x": 13, "y": 0 }, | |||
{ "label": "~", "x": 14, "y": 0 }, | |||
{ "label": "Tab", "x": 0, "y": 1, "w": 1.5 }, | |||
{ "label": "Q", "x": 1.5, "y": 1 }, | |||
{ "label": "W", "x": 2.5, "y": 1 }, | |||
{ "label": "E", "x": 3.5, "y": 1 }, | |||
{ "label": "R", "x": 4.5, "y": 1 }, | |||
{ "label": "T", "x": 5.5, "y": 1 }, | |||
{ "label": "Y", "x": 6.5, "y": 1 }, | |||
{ "label": "U", "x": 7.5, "y": 1 }, | |||
{ "label": "I", "x": 8.5, "y": 1 }, | |||
{ "label": "O", "x": 9.5, "y": 1 }, | |||
{ "label": "P", "x": 10.5, "y": 1 }, | |||
{ "label": "{", "x": 11.5, "y": 1 }, | |||
{ "label": "}", "x": 12.5, "y": 1 }, | |||
{ "label": "Delete", "x": 13.5, "y": 1, "w": 1.5 }, | |||
{ "label": "Control", "x": 0, "y": 2, "w": 1.75 }, | |||
{ "label": "A", "x": 1.75, "y": 2 }, | |||
{ "label": "S", "x": 2.75, "y": 2 }, | |||
{ "label": "D", "x": 3.75, "y": 2 }, | |||
{ "label": "F", "x": 4.75, "y": 2 }, | |||
{ "label": "G", "x": 5.75, "y": 2 }, | |||
{ "label": "H", "x": 6.75, "y": 2 }, | |||
{ "label": "J", "x": 7.75, "y": 2 }, | |||
{ "label": "K", "x": 8.75, "y": 2 }, | |||
{ "label": "L", "x": 9.75, "y": 2 }, | |||
{ "label": ":", "x": 10.75, "y": 2 }, | |||
{ "label": "\"", "x": 11.75, "y": 2 }, | |||
{ "label": "Return", "x": 12.75, "y": 2, "w": 2.25 }, | |||
{ "label": "Shift", "x": 0, "y": 3, "w": 2.25 }, | |||
{ "label": "Z", "x": 2.25, "y": 3 }, | |||
{ "label": "X", "x": 3.25, "y": 3 }, | |||
{ "label": "C", "x": 4.25, "y": 3 }, | |||
{ "label": "V", "x": 5.25, "y": 3 }, | |||
{ "label": "B", "x": 6.25, "y": 3 }, | |||
{ "label": "N", "x": 7.25, "y": 3 }, | |||
{ "label": "M", "x": 8.25, "y": 3 }, | |||
{ "label": "<", "x": 9.25, "y": 3 }, | |||
{ "label": ">", "x": 10.25, "y": 3 }, | |||
{ "label": "?", "x": 11.25, "y": 3 }, | |||
{ "label": "Shift", "x": 12.25, "y": 3, "w": 1.75 }, | |||
{ "label": "Fn", "x": 14, "y": 3 }, | |||
{ "label": "", "x": 1.5, "y": 4 }, | |||
{ "label": "", "x": 2.5, "y": 4, "w": 1.5 }, | |||
{ "x": 4, "y": 4, "w": 6 }, | |||
{ "label": "", "x": 10, "y": 4, "w": 1.5 }, | |||
{ "label": "", "x": 11.5, "y": 4 } | |||
] | |||
} | |||
} | |||
} |
@ -0,0 +1,73 @@ | |||
/* -*- eval: (turn-on-orgtbl); -*- | |||
* default HHKB Layout | |||
* | |||
* Copyright 2021 Kan-Ru Chen <kanru@kanru.info> | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation, either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
enum custom_layers { | |||
BASE, | |||
HHKB, | |||
}; | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
/* BASE Level: Default Layer | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | = | \ | ` | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Tab | Q | W | E | R | T | Y | U | I | O | P | [ | ] | Backs | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Cont | A | S | D | F | G | H | J | K | L | ; | ' | Ent | | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Shift | Z | X | C | V | B | N | M | , | . | / | Shift | Fn0 | | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
|------+------+-----------------------+------+------| | |||
| LAlt | LGUI | ******* Space ******* | RGUI | RAlt | | |||
|------+------+-----------------------+------+------| | |||
*/ | |||
[BASE] = LAYOUT_60_hhkb( // default layer | |||
KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSLS, KC_GRV, | |||
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSPC, | |||
KC_LCTL, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, | |||
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, MO(HHKB), | |||
KC_LALT, KC_LGUI, /* */ KC_SPC, KC_RGUI, KC_RALT), | |||
/* Layer HHKB: HHKB mode (HHKB Fn) | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| Pwr | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | Ins | Del | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| Caps | | | | | | | | Psc | Slk | Pus | Up | | Backs | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| | VoD | VoU | Mut | | | * | / | Hom | PgU | Lef | Rig | Enter | | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| | | | | | | + | - | End | PgD | Dow | | | | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
|------+------+----------------------+------+------+ | |||
| **** | **** | ******************** | **** | **** | | |||
|------+------+----------------------+------+------+ | |||
*/ | |||
[HHKB] = LAYOUT_60_hhkb( | |||
KC_PWR, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_INS, KC_DEL, | |||
KC_CAPS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PSCR, KC_SLCK, KC_PAUS, KC_UP, KC_TRNS, KC_BSPC, | |||
KC_TRNS, KC_VOLD, KC_VOLU, KC_MUTE, KC_TRNS, KC_TRNS, KC_PAST, KC_PSLS, KC_HOME, KC_PGUP, KC_LEFT, KC_RGHT, KC_PENT, | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PPLS, KC_PMNS, KC_END, KC_PGDN, KC_DOWN, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS)}; |
@ -0,0 +1,21 @@ | |||
/* Copyright 2021 Kan-Ru Chen <kanru@kanru.info> | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation, either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#pragma once | |||
#define MOUSEKEY_DELAY 90 | |||
#define MOUSEKEY_INTERVAL 16 | |||
#define MOUSEKEY_MAX_SPEED 4 | |||
#define MOUSEKEY_WHEEL_INTERVAL 50 |
@ -0,0 +1,126 @@ | |||
/* -*- eval: (turn-on-orgtbl); -*- | |||
* kanru's HHKB Layout | |||
* | |||
* Copyright 2021 Kan-Ru Chen <kanru@kanru.info> | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation, either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
#include <stdio.h> | |||
enum custom_layers { | |||
BASE, | |||
HHKB, | |||
MOUSE, | |||
}; | |||
#define BATTERY_FULL 550 | |||
#define BATTERY_EMPTY 326 | |||
enum my_keycodes { KC_VBAT = SAFE_RANGE }; | |||
uint32_t adafruit_ble_read_battery_voltage(void); | |||
bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
switch (keycode) { | |||
#ifdef BLUETOOTH_ENABLE | |||
case KC_VBAT: | |||
if (record->event.pressed) { | |||
char vbat[8]; | |||
uint8_t level = (adafruit_ble_read_battery_voltage() - BATTERY_EMPTY) / (float)(BATTERY_FULL - BATTERY_EMPTY) * 100; | |||
snprintf(vbat, sizeof(vbat), "%d", level); | |||
send_string(vbat); | |||
} | |||
return false; | |||
#endif | |||
default: | |||
return true; | |||
} | |||
} | |||
// clang-format off | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
/* BASE Level: Default Layer | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Esc | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | = | \ | ` | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Tab | Q | W | E | R | T | Y | U | I | O | P | [ | ] | Backs | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Cont | A | S | D | F | G | H | J | K | L | ; | ' | Ent | | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
| Shift | Z | X | C | V | B | N | M | , | . | / | Shift | Fn0 | | | | |||
|-------+---+---+---+---+---+---+---+---+---+---+-------+-----+-------+---| | |||
|------+------+-----------------------+------+------| | |||
| LAlt | LGUI | ******* Space ******* | RGUI | RAlt | | |||
|------+------+-----------------------+------+------| | |||
*/ | |||
[BASE] = LAYOUT_60_hhkb( // default layer | |||
LT(MOUSE, KC_ESC), KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSLS, KC_GRV, | |||
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSPC, | |||
KC_LCTL, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, | |||
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, MO(HHKB), | |||
KC_LALT, KC_LGUI, /* */ KC_SPC, KC_RGUI, KC_RALT), | |||
/* Layer HHKB: HHKB mode (HHKB Fn) | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| Pwr | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | Ins | Del | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| Caps | | | BAT | | | | | Psc | Slk | Pus | Up | | Backs | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| | VoD | VoU | Mut | | | * | / | Hom | PgU | Lef | Rig | Enter | | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
| | | | | | | + | - | End | PgD | Dow | | | | | | |||
|------+-----+-----+-----+----+----+----+----+-----+-----+-----+-----+-------+-------+-----| | |||
|------+------+----------------------+------+------+ | |||
| **** | **** | ******************** | **** | **** | | |||
|------+------+----------------------+------+------+ | |||
*/ | |||
[HHKB] = LAYOUT_60_hhkb( | |||
KC_PWR, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_INS, KC_DEL, | |||
KC_CAPS, KC_TRNS, KC_TRNS, KC_VBAT, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PSCR, KC_SLCK, KC_PAUS, KC_UP, KC_TRNS, KC_BSPC, | |||
KC_TRNS, KC_VOLD, KC_VOLU, KC_MUTE, KC_TRNS, KC_TRNS, KC_PAST, KC_PSLS, KC_HOME, KC_PGUP, KC_LEFT, KC_RGHT, KC_PENT, | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PPLS, KC_PMNS, KC_END, KC_PGDN, KC_DOWN, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS), | |||
/* Layer MOUSE: Mouse Key mode (ESC) | |||
|------+------+------+-----+----+----+----+----+----+----+-----+-----+-------+-------+----- | | |||
| | | | | | | | | | | | | | | | | |||
|------+------+------+-----+----+----+----+----+----+----+-----+-----+-------+-------+-----| | |||
| | BTN1 | WH_U | | | | | | | | | | | | | | |||
|------+------+------+-----+----+----+----+----+----+----+-----+-----+-------+-------+-----| | |||
| | BTN2 | WH_D | | | |MS_L|MS_D|MS_U|MS_R| | | | | | | |||
|------+------+------+-----+----+----+----+----+----+----+-----+-----+-------+-------+-----| | |||
| | BTN3 | | | | | | | | | | | | | | | |||
|------+------+------+-----+----+----+----+----+----+----+-----+-----+-------+-------+-----| | |||
|------+------+----------------------+------+------+ | |||
| **** | **** | ******************** | **** | **** | | |||
|------+------+----------------------+------+------+ | |||
*/ | |||
[MOUSE] = LAYOUT_60_hhkb( | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_BTN1, KC_WH_U, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_BTN2, KC_WH_D, KC_TRNS, KC_TRNS, KC_TRNS, KC_MS_L, KC_MS_D, KC_MS_U, KC_MS_R, KC_TRNS, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_BTN3, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, | |||
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS) | |||
}; | |||
// clang-format on |
@ -0,0 +1,173 @@ | |||
/* | |||
Copyright 2011 Jun Wako <wakojun@gmail.com> | |||
Copyright 2020 Kan-Ru Chen <kanru@kanru.info> | |||
This program is free software: you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation, either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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. | |||
You should have received a copy of the GNU General Public License | |||
along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "quantum.h" | |||
#ifdef BLUETOOTH_ENABLE | |||
# include "adafruit_ble.h" | |||
#endif | |||
#define RELAX_TIME_US 5 | |||
#define ADC_READ_TIME_US 5 | |||
uint8_t power_save_level; | |||
static uint32_t matrix_last_modified = 0; | |||
static inline void key_strobe_high(void) { writePinLow(B6); } | |||
static inline void key_strobe_low(void) { writePinHigh(B6); } | |||
static inline bool key_state(void) { return readPin(D7); } | |||
static inline void key_prev_on(void) { writePinHigh(B7); } | |||
static inline void key_prev_off(void) { writePinLow(B7); } | |||
static inline bool key_power_state(void) { return !readPin(D6); } | |||
static inline void suspend_power_down_longer(void) { | |||
uint8_t times = 60; | |||
while (--times) suspend_power_down(); | |||
} | |||
void matrix_power_up(void) { | |||
dprint("[matrix_on]\n"); | |||
// change pins output | |||
DDRB = 0xFF; | |||
PORTB = 0x40; | |||
// switch MOS FET on | |||
setPinOutput(D6); | |||
writePinLow(D6); | |||
} | |||
void matrix_power_down(void) { | |||
dprint("[matrix_off]\n"); | |||
// input with pull-up consumes less than without it when pin is open | |||
DDRB = 0x00; | |||
PORTB = 0xFF; | |||
// switch MOS FET off | |||
setPinOutput(D6); | |||
writePinHigh(D6); | |||
} | |||
static inline void key_select_row(uint8_t row) { PORTB = (PORTB & 0b11111000) | ((row)&0b111); } | |||
static inline void key_select_col(uint8_t col) { PORTB = (PORTB & 0b11000111) | (((col)&0b111) << 3); } | |||
static inline bool key_prev_was_on(matrix_row_t matrix[], uint8_t row, uint8_t col) { return matrix[row] & (1 << col); } | |||
void matrix_init_custom(void) { power_save_level = 0; } | |||
bool matrix_scan_custom(matrix_row_t current_matrix[]) { | |||
bool matrix_has_changed = false; | |||
// power on | |||
if (!key_power_state()) { | |||
matrix_power_up(); | |||
} | |||
for (uint8_t row = 0; row < MATRIX_ROWS; row++) { | |||
matrix_row_t last_row_value = current_matrix[row]; | |||
key_select_row(row); | |||
wait_us(RELAX_TIME_US); | |||
for (uint8_t col = 0; col < MATRIX_COLS; col++) { | |||
// Hysteresis control: assert(1) when previous key state is on | |||
if (key_prev_was_on(current_matrix, row, col)) { | |||
key_prev_on(); | |||
} else { | |||
key_prev_off(); | |||
} | |||
// Disable interrupts to encure the ADC timing is correct | |||
cli(); | |||
// strobe | |||
key_select_col(col); | |||
key_strobe_high(); | |||
// Wait for ADC to outputs its value. | |||
// 1us was ok on one HHKB, but not worked on another. | |||
// no wait doesn't work on Teensy++ with pro(1us works) | |||
// no wait does work on tmk PCB(8MHz) with pro2 | |||
// 1us wait does work on both of above | |||
// 1us wait doesn't work on tmk(16MHz) | |||
// 5us wait does work on tmk(16MHz) | |||
// 5us wait does work on tmk(16MHz/2) | |||
// 5us wait does work on tmk(8MHz) | |||
// 10us wait does work on Teensy++ with pro | |||
// 10us wait does work on 328p+iwrap with pro | |||
// 10us wait doesn't work on tmk PCB(8MHz) with pro2(very lagged scan) | |||
wait_us(ADC_READ_TIME_US); | |||
if (key_state()) { | |||
current_matrix[row] &= ~(1 << col); | |||
} else { | |||
current_matrix[row] |= (1 << col); | |||
} | |||
key_strobe_low(); | |||
sei(); | |||
// Make sure enough time has elapsed since the last call | |||
// This is to ensure the matrix voltages have relaxed | |||
wait_us(RELAX_TIME_US); | |||
} | |||
if (current_matrix[row] ^ last_row_value) { | |||
matrix_has_changed = true; | |||
matrix_last_modified = timer_read32(); | |||
} | |||
} | |||
// Power saving | |||
uint32_t time_diff = timer_elapsed32(matrix_last_modified); | |||
if (time_diff > MATRIX_POWER_SAVE_TIMEOUT_L3_MS) { | |||
power_save_level = 3; | |||
suspend_power_down_longer(); | |||
} else if (time_diff > MATRIX_POWER_SAVE_TIMEOUT_L2_MS) { | |||
power_save_level = 2; | |||
#ifdef BLUETOOTH_ENABLE | |||
if (!adafruit_ble_is_connected()) { | |||
power_save_level = 3; | |||
} | |||
#endif | |||
suspend_power_down_longer(); | |||
} else if (time_diff > MATRIX_POWER_SAVE_TIMEOUT_MS) { | |||
power_save_level = 1; | |||
suspend_power_down(); | |||
} else { | |||
if (power_save_level != 0) { | |||
power_save_level = 0; | |||
suspend_wakeup_init(); | |||
} | |||
} | |||
return matrix_has_changed; | |||
} | |||
bool adafruit_ble_delbonds(void); | |||
bool adafruit_ble_reconnect(void); | |||
bool command_extra(uint8_t code) { | |||
switch (code) { | |||
#ifdef BLUETOOTH_ENABLE | |||
case KC_R: | |||
adafruit_ble_delbonds(); | |||
return true; | |||
case KC_S: | |||
adafruit_ble_reconnect(); | |||
return true; | |||
#endif | |||
default: | |||
return false; | |||
} | |||
} |
@ -0,0 +1,135 @@ | |||
## Hardware Information | |||
The YANG HHKB BLE controller design is similiar to hasu's | |||
controller. Most pins are compatiable. | |||
**MCU**: ATmega32U4 | |||
**Bluetooth**: MDBT40 (nRF51822-based), with Adafruit Bluefruit LE UART Friend firmware. | |||
**Power**: 3.3V | |||
**CPU Frequency**: 8MHz | |||
**Bootloader**: Lufa MassStorage | |||
## Pin usage | |||
| Description | HASU pin usage | YANG mod changed | | |||
|:------------------------------------ | ---------------------- | -------------------------- | | |||
| ~KEY: Lo(0) when key is pressed | PD7 input(with pullup) | | | |||
| Hysteresis: Hi(1) if key was pressed | PB7 output | | | |||
| Row selector bit0 | PB0 output | | | |||
| Row selector bit1 | PB1 output | | | |||
| Row selector bit2 | PB2 output | | | |||
| Col selector bit0 | PB3 output | | | |||
| Col selector bit1 | PB4 output | | | |||
| Col selector bit2 | PB5 output | | | |||
| Key unable | PB6 output | | | |||
| Switch power | PD4 output | PD6 output (PMOS FET) | | |||
| Bluetooth UART Rx | PC4 input | PD2 | | |||
| Bluetooth UART Tx | PC5 output | PD3 | | |||
| Bluetooth power | | PD5 output (low: power on) | | |||
| LED 0 | | PF4 | | |||
| LED 2 | | PF1 | | |||
| LED 4 | | PF0 | | |||
| Unused for PRO2 | PC6 | | | |||
| Unused for PRO2 | PC7 | | | |||
| Inner USB power | | PF7 | | |||
## How to flash LUFA MassStorage bootloader on Linux | |||
The FAT filesystem on Linux very often cannot flush the write cache, | |||
leading to broken firmware in the flash. | |||
We can use `dd` to write to the virtual block storage directly to | |||
bypass the vfs layer. | |||
``` | |||
dd if=FLASH.bin of=<path of virtual block device> seek=4 | |||
``` | |||
Skip 4 sectors because the default sector size of the virtual device | |||
and dd is 512 bytes and the emulated flash file starts at 5th sector. | |||
## How to find the path of the virtual block device | |||
After the keyboard boots into flash mode, on Linux system you should | |||
be able to find the block device in `dmesg` logs. | |||
For exmaple if you type | |||
``` | |||
sudo dmesg | |||
``` | |||
You should find something like | |||
``` | |||
[357885.143593] usb 1-1.4: USB disconnect, device number 24 | |||
[357885.627740] usb 1-1.4: new full-speed USB device number 25 using xhci_hcd | |||
[357885.729486] usb 1-1.4: New USB device found, idVendor=03eb, idProduct=1962, bcdDevice= 0.01 | |||
[357885.729492] usb 1-1.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0 | |||
[357885.745620] SCSI subsystem initialized | |||
[357885.746712] usb-storage 1-1.4:1.0: USB Mass Storage device detected | |||
[357885.746818] scsi host0: usb-storage 1-1.4:1.0 | |||
[357885.746919] usbcore: registered new interface driver usb-storage | |||
[357885.747689] usbcore: registered new interface driver uas | |||
[357886.766755] scsi 0:0:0:0: Direct-Access LUFA Bootloader 0.00 PQ: 0 ANSI: 0 | |||
[357886.773216] scsi 0:0:0:0: Attached scsi generic sg0 type 0 | |||
[357886.777474] sd 0:0:0:0: [sdx] 134 512-byte logical blocks: (68.6 kB/67.0 KiB) | |||
[357886.780300] sd 0:0:0:0: [sdx] Write Protect is off | |||
[357886.780302] sd 0:0:0:0: [sdx] Mode Sense: 00 00 00 00 | |||
[357886.783113] sd 0:0:0:0: [sdx] Asking for cache data failed | |||
[357886.783114] sd 0:0:0:0: [sdx] Assuming drive cache: write through | |||
[357886.842676] sdx: | |||
[357886.859528] sd 0:0:0:0: [sdx] Attached SCSI removable disk | |||
``` | |||
The `sdx` is the block device name and the full path is at `/dev/sdx` | |||
The above flash command will become | |||
``` | |||
dd if=FLASH.bin of=/dev/sdx seek=4 | |||
``` | |||
## Adafruit Bluefruit LE UART configuraton | |||
The default baud rate used by the firmware is 76800 although adafruit | |||
do not recommend using higher baudrates than 9600 because the nRF51 | |||
UART can drop characters. | |||
Double speed mode to get more accurate async reading because the F_CPU | |||
speed is 8MHz. | |||
## Power saving mode design | |||
Power saving is only enabled when USB is detached and using battery | |||
power. Here we define several levels of power saving mode, each saves | |||
more power but takes longer to resume operation. | |||
1. Level 1: idle mode is activated after a short configurable time | |||
(MATRIX_POWER_SAVE_TIMEOUT_MS) MCU is put into sleep mode and only | |||
scan the matrix per 15ms. PORTB pins are set to input with pull-up | |||
to save power. Sensing PCB is powered down between scans. | |||
2. Level 2: after idling for longer (MATRIX_POWER_SAVE_TIMEOUT_L2_MS) | |||
we entry this state. Matrix scan is skipped until the time lapses | |||
900ms. | |||
2. Level 3: sleep mode is activated after a longer timeout | |||
(MATRIX_POWER_SAVE_TIMEOUT_L3_MS) Bluetooth module is powered down. | |||
## Battery reading | |||
VBAT is connected to AIN6 pin on the MDBT40 module and the AREF pin is | |||
the reference voltage. Doing a ADC with AT+HWDAC=6 will return the | |||
difference between VBAT and VREF. | |||
It seems when fully charged the ADC read is 550. Likely VREF is 3311mV | |||
and the fully charged VBAT is thus 3861mV. | |||
Enable battery service with AT+BLEBATTEN=1 first then we can update the | |||
battery level by using AT+BLEBATTVAL=%d | |||
## References | |||
* https://github.com/joric/qmk/wiki/hhkb_ble | |||
* https://github.com/tomsmalley/custom-topre-guide | |||
* https://github.com/abcminiuser/lufa/blob/master/Bootloaders/MassStorage/Lib/VirtualFAT.h |
@ -0,0 +1,118 @@ | |||
# HHKB Alternate Controller (YANG HHKB BLE Mod) | |||
![YANG HHKB BLE Mod](https://i.imgur.com/aZP1GYc.jpeg) | |||
An alternative controler for the HHKB designed by YANG (yangdigi) | |||
based on the hasu controller. | |||
* Keyboard Maintainer: [Kan-Ru Chen](https://github.com/kanru) | |||
* Hardware Supported: YANG HHKB BLE Controller | |||
* Hardware Availability: https://kbdfans.com/products/hhkb-ble-mod-upgrade-module | |||
Make example for this keyboard (after setting up your build environment): | |||
make hhkb/yang:default | |||
To flash, first boot your keyboard into bootloader (hold ESC and attach usb cable) | |||
then a virtual USB storage should appear. You can copy the `hhkb_yang_default.bin` | |||
file to the virtual USB storage and override the `HHKB_BLE.BIN` file in there. | |||
Make sure to unmount and eject the virtual USB storage. | |||
## Features: | |||
- [x] QMK (via USB) | |||
- [x] Bluetooth (BLE) | |||
- [x] Power saving mode | |||
- [x] Idle mode | |||
- [x] Deep sleep mode | |||
- [x] LEDs | |||
- [x] Battery service | |||
- [x] Special commands | |||
- [x] Switch BT peer | |||
## Entering flash mode | |||
Different ways to enter flash mode: | |||
* Press and hold the ESC key. Insert the USB cable to enter the flash | |||
mode. When the OS shows the drive disk, you can release the key. | |||
* Use the magic command LSHIFT+RSHIFT+B to reboot to bootloader then | |||
quickly hold the ESC key. | |||
If you reflash the wrong firmware or did not reflash successfully, you | |||
can no longer enter the flash mode, especially the wireless keyboard | |||
with battery. You need to turn off the keyboard's power switch, and | |||
re-enter the flash mode, reflash the correct firmware. | |||
After entering the bootloader(flash mode), three indicators on the top | |||
right of the HHKB BLE controller will flash. LED3(green) will flash | |||
quickly when writing firmware to the controller. | |||
If these three leds are not soldered or your hhkb case is black, you | |||
can't know their status, but you can still see LED3 under the right | |||
USB port. | |||
## How to reliably flash LUFA MassStorage bootloader on Linux | |||
The FAT filesystem on Linux very often cannot flush the write cache, | |||
leading to broken firmware in the flash. | |||
We can use `dd` to write to the virtual block storage directly to | |||
bypass the vfs layer. | |||
``` | |||
dd if=FLASH.bin of=<path of virtual block device> seek=4 | |||
``` | |||
Skip 4 sectors because the default sector size of the virtual device | |||
and dd is 512 bytes and the emulated flash file starts at 5th sector. | |||
## How to find the path of the virtual block device | |||
After the keyboard boots into flash mode, on Linux system you should | |||
be able to find the block device in `dmesg` logs. | |||
For exmaple if you type | |||
``` | |||
sudo dmesg | |||
``` | |||
You should find something like | |||
``` | |||
[357885.143593] usb 1-1.4: USB disconnect, device number 24 | |||
[357885.627740] usb 1-1.4: new full-speed USB device number 25 using xhci_hcd | |||
[357885.729486] usb 1-1.4: New USB device found, idVendor=03eb, idProduct=1962, bcdDevice= 0.01 | |||
[357885.729492] usb 1-1.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0 | |||
[357885.745620] SCSI subsystem initialized | |||
[357885.746712] usb-storage 1-1.4:1.0: USB Mass Storage device detected | |||
[357885.746818] scsi host0: usb-storage 1-1.4:1.0 | |||
[357885.746919] usbcore: registered new interface driver usb-storage | |||
[357885.747689] usbcore: registered new interface driver uas | |||
[357886.766755] scsi 0:0:0:0: Direct-Access LUFA Bootloader 0.00 PQ: 0 ANSI: 0 | |||
[357886.773216] scsi 0:0:0:0: Attached scsi generic sg0 type 0 | |||
[357886.777474] sd 0:0:0:0: [sdx] 134 512-byte logical blocks: (68.6 kB/67.0 KiB) | |||
[357886.780300] sd 0:0:0:0: [sdx] Write Protect is off | |||
[357886.780302] sd 0:0:0:0: [sdx] Mode Sense: 00 00 00 00 | |||
[357886.783113] sd 0:0:0:0: [sdx] Asking for cache data failed | |||
[357886.783114] sd 0:0:0:0: [sdx] Assuming drive cache: write through | |||
[357886.842676] sdx: | |||
[357886.859528] sd 0:0:0:0: [sdx] Attached SCSI removable disk | |||
``` | |||
The `sdx` is the block device name and the full path is at `/dev/sdx` | |||
The above flash command will become | |||
``` | |||
dd if=FLASH.bin of=/dev/sdx seek=4 | |||
``` | |||
**Caution**: if set to incorrect device it may wipe out | |||
your actual disk. | |||
## Help page of original firmware | |||
http://help.ydkb.io/doku.php?id=en:kb-mods:hhkb-ble |
@ -0,0 +1,27 @@ | |||
# MCU name | |||
MCU = atmega32u4 | |||
# MCU frequency | |||
F_CPU = 8000000 | |||
# Bootloader selection | |||
BOOTLOADER = lufa-ms | |||
# Build Options | |||
# change yes to no to disable | |||
# | |||
BOOTMAGIC_ENABLE = yes # Enable Bootmagic Lite | |||
MOUSEKEY_ENABLE = yes # Mouse keys | |||
EXTRAKEY_ENABLE = yes # Audio control and System control | |||
CONSOLE_ENABLE = no # Console for debug | |||
COMMAND_ENABLE = yes # Commands for debug and configuration | |||
NKRO_ENABLE = no # USB Nkey Rollover | |||
LAYOUTS = 60_hhkb | |||
# Disable bluetooth until the UART code is merged | |||
BLUETOOTH_DRIVER = BluefruitLE | |||
# Custom matrix file for the HHKB | |||
CUSTOM_MATRIX = lite | |||
SRC += matrix.c |
@ -0,0 +1,118 @@ | |||
/* Copyright 2021 Kan-Ru Chen <kanru@kanru.info> | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation, either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "yang.h" | |||
extern uint8_t power_save_level; | |||
void hhkb_led_on(uint8_t led) { | |||
switch (led) { | |||
case 1: | |||
writePinHigh(F4); | |||
break; | |||
case 2: | |||
writePinHigh(F2); | |||
break; | |||
case 3: | |||
writePinHigh(F0); | |||
break; | |||
} | |||
} | |||
void hhkb_led_off(uint8_t led) { | |||
switch (led) { | |||
case 1: | |||
writePinLow(F4); | |||
break; | |||
case 2: | |||
writePinLow(F2); | |||
break; | |||
case 3: | |||
writePinLow(F0); | |||
break; | |||
} | |||
} | |||
void keyboard_pre_init_kb(void) { | |||
// BT power up | |||
setPinOutput(D5); | |||
writePinLow(D5); | |||
// Row selectors | |||
setPinOutput(B0); | |||
setPinOutput(B1); | |||
setPinOutput(B2); | |||
// Col selectors | |||
setPinOutput(B3); | |||
setPinOutput(B4); | |||
setPinOutput(B5); | |||
// Key strobe | |||
setPinOutput(B6); | |||
writePinHigh(B6); | |||
// Key: input with pull-up | |||
setPinInputHigh(D7); | |||
// Unused pins on Pro2 ANSI | |||
// Input with pull up to save power | |||
setPinInputHigh(C6); | |||
setPinInputHigh(C7); | |||
// LED pin configuration | |||
setPinOutput(F0); | |||
setPinOutput(F1); | |||
setPinOutput(F4); | |||
writePinLow(F0); | |||
writePinLow(F1); | |||
writePinLow(F4); | |||
// Turn on switch PCB | |||
setPinOutput(D6); | |||
writePinLow(D6); | |||
keyboard_pre_init_user(); | |||
} | |||
void suspend_power_down_kb(void) { | |||
if (power_save_level > 2) { | |||
// Disable UART TX to avoid current leakage | |||
UCSR1B &= ~_BV(TXEN1); | |||
// Power down BLE module | |||
writePinHigh(D5); | |||
} | |||
suspend_power_down_user(); | |||
} | |||
void suspend_wakeup_init_kb(void) { | |||
// Power up BLE module | |||
writePinLow(D5); | |||
// Enable UART TX | |||
UCSR1B |= _BV(TXEN1); | |||
suspend_wakeup_init_user(); | |||
} | |||
layer_state_t layer_state_set_kb(layer_state_t state) { | |||
state = layer_state_set_user(state); | |||
writePin(F1, IS_LAYER_ON_STATE(state, 1)); | |||
writePin(F0, IS_LAYER_ON_STATE(state, 2)); | |||
return state; | |||
} |
@ -0,0 +1,36 @@ | |||
/* Copyright 2021 Kan-Ru Chen <kanru@kanru.info> | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation, either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program 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. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#pragma once | |||
#include "quantum.h" | |||
#define LAYOUT_60_hhkb( \ | |||
K31, K30, K00, K10, K11, K20, K21, K40, K41, K60, K61, K70, K71, K50, K51, \ | |||
K32, K01, K02, K13, K12, K23, K22, K42, K43, K62, K63, K73, K72, K52, \ | |||
K33, K04, K03, K14, K15, K24, K25, K45, K44, K65, K64, K74, K53, \ | |||
K34, K05, K06, K07, K16, K17, K26, K46, K66, K76, K75, K55, K54, \ | |||
K35, K36, K37, K57, K56) \ | |||
\ | |||
{ \ | |||
{ K00, K01, K02, K03, K04, K05, K06, K07 }, \ | |||
{ K10, K11, K12, K13, K14, K15, K16, K17 }, \ | |||
{ K20, K21, K22, K23, K24, K25, K26, KC_NO }, \ | |||
{ K30, K31, K32, K33, K34, K35, K36, K37 }, \ | |||
{ K40, K41, K42, K43, K44, K45, K46, KC_NO }, \ | |||
{ K50, K51, K52, K53, K54, K55, K56, K57 }, \ | |||
{ K60, K61, K62, K63, K64, K65, K66, KC_NO }, \ | |||
{ K70, K71, K72, K73, K74, K75, K76, KC_NO } \ | |||
} |