Created
June 1, 2022 05:43
-
-
Save nibral/3772bce4b32e31dd59afcf2a09472ee5 to your computer and use it in GitHub Desktop.
Firmware for Sunrise70 keyboard
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
#include <bluefruit.h> | |
/* | |
キーコード | |
*/ | |
#define FN_KEY 0x1000 | |
#define FN_BLE_1 0x1001 | |
#define FN_BLE_2 0x1002 | |
#define FN_BLE_INFO 0x1003 | |
#define FN_BLE_ADV 0x1004 | |
#define FN_BLE_CLR 0x1005 | |
/* | |
キーマップ | |
*/ | |
const uint8_t rows[] = {2, 3, 4, 5, 6}; | |
const uint8_t cols[] = {A3, A2, A1, A0, 15, 14, 16, 7}; | |
const uint8_t row_size = sizeof(rows) / sizeof(rows[0]); | |
const uint8_t col_size = sizeof(cols) / sizeof(cols[0]); | |
const uint8_t duplex_left_col_size = 8; | |
const uint8_t duplex_right_col_size = 6; | |
const uint8_t col_map_size = duplex_left_col_size + duplex_right_col_size; | |
const uint16_t hid_key_codes[row_size][col_map_size] = { | |
// SW1 SW6 SW11 SW16 SW21 SW26 SW31 SW36 SW41 SW46 SW51 SW56 SW61 SW66 | |
{ HID_KEY_ESCAPE, HID_KEY_1, HID_KEY_2, HID_KEY_3, HID_KEY_4, HID_KEY_5, HID_KEY_6, HID_KEY_U, HID_KEY_K, HID_KEY_L, HID_KEY_SEMICOLON, HID_KEY_APOSTROPHE, HID_KEY_KANJI3 , HID_KEY_ARROW_DOWN }, | |
{ HID_KEY_TAB, HID_KEY_Q, HID_KEY_W, HID_KEY_E, HID_KEY_R, HID_KEY_T, HID_KEY_Y, HID_KEY_J, HID_KEY_COMMA, HID_KEY_PERIOD, HID_KEY_SLASH, HID_KEY_KANJI1, HID_KEY_ARROW_LEFT, HID_KEY_HOME }, | |
{ HID_KEY_CAPS_LOCK, HID_KEY_A, HID_KEY_S, HID_KEY_D, HID_KEY_F, HID_KEY_G, HID_KEY_H, HID_KEY_M, HID_KEY_LANG1, FN_KEY, HID_KEY_CONTROL_RIGHT, HID_KEY_EQUAL, HID_KEY_ENTER, HID_KEY_DELETE }, | |
{ HID_KEY_SHIFT_LEFT, HID_KEY_Z, HID_KEY_X, HID_KEY_C, HID_KEY_V, HID_KEY_B, HID_KEY_N, HID_KEY_8, HID_KEY_9, HID_KEY_0, HID_KEY_MINUS, HID_KEY_BRACKET_RIGHT, HID_KEY_BACKSPACE, HID_KEY_END }, | |
{ HID_KEY_CONTROL_LEFT, HID_KEY_GUI_LEFT, HID_KEY_ALT_LEFT, HID_KEY_LANG2, HID_KEY_SPACE, HID_KEY_SPACE, HID_KEY_7, HID_KEY_I, HID_KEY_O, HID_KEY_P, HID_KEY_BRACKET_LEFT, HID_KEY_EUROPE_1, HID_KEY_ARROW_UP, HID_KEY_ARROW_RIGHT } | |
}; | |
const uint16_t fn_key_codes[row_size][col_map_size] = { | |
// SW1 SW6 SW11 SW16 SW21 SW26 SW31 SW36 SW41 SW46 SW51 SW56 SW61 SW66 | |
{ HID_KEY_NONE, HID_KEY_F1, HID_KEY_F2, HID_KEY_F3, HID_KEY_F4, HID_KEY_F5, HID_KEY_F6, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE }, | |
{ HID_KEY_NONE, FN_BLE_1, FN_BLE_2, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_VOLUME_UP }, | |
{ HID_KEY_NONE, FN_BLE_ADV, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, FN_KEY, HID_KEY_NONE, HID_KEY_F12, HID_KEY_NONE, HID_KEY_NONE }, | |
{ HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_F8, HID_KEY_F9, HID_KEY_F10, HID_KEY_F11, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_VOLUME_DOWN }, | |
{ HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_F7, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE } | |
}; | |
/* | |
Bluefruit | |
*/ | |
#define MAX_PRPH_CONNECTION 2 // 最大接続数 | |
uint8_t connection_count = 0; | |
uint8_t selected_device = 0; | |
uint8_t central_addr[2][6] = { | |
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // BT1のアドレス | |
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // BT2のアドレス | |
}; | |
uint16_t central_conn_handles[2] = { 10, 10 }; // 10は未接続 | |
BLEDis bledis; | |
BLEHidAdafruit blehid; | |
bool ble_adv_initialized = false; | |
bool compare_addr(uint8_t* addr1, uint8_t* addr2) { | |
for (uint8_t i = 0; i < 6; i++) { | |
if (addr1[i] != addr2[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
void print_conn_status() { | |
Serial.println(""); | |
Serial.print("BT1: "); | |
if (central_conn_handles[0] < 10) { | |
Serial.print("connected (handle "); | |
Serial.print(central_conn_handles[0]); | |
Serial.println(")"); | |
} else { | |
Serial.println("disconnected"); | |
} | |
Serial.print("BT2: "); | |
if (central_conn_handles[1] < 10) { | |
Serial.print("connected (handle "); | |
Serial.print(central_conn_handles[1]); | |
Serial.println(")"); | |
} else { | |
Serial.println("disconnected"); | |
} | |
} | |
void startAdvertising() { | |
if (connection_count >= MAX_PRPH_CONNECTION || Bluefruit.Advertising.isRunning()) { | |
return; | |
} | |
if (!ble_adv_initialized) { | |
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); | |
Bluefruit.Advertising.addTxPower(); | |
Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD); | |
Bluefruit.Advertising.addService(blehid); | |
Bluefruit.Advertising.addName(); | |
Bluefruit.Advertising.restartOnDisconnect(true); | |
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms | |
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode | |
ble_adv_initialized = true; | |
} | |
Bluefruit.Advertising.start(60); // 0 = Don't stop advertising after n seconds | |
Serial.println("BLE advertising..."); | |
} | |
void ble_connect_callback(uint16_t conn_handle) { | |
BLEConnection* connection = Bluefruit.Connection(conn_handle); | |
ble_gap_addr_t conn_central_addr = connection->getPeerAddr(); | |
Serial.print("central addr: "); | |
for (uint8_t i = 0; i < 6; i++) { | |
Serial.print(conn_central_addr.addr[i], HEX); | |
} | |
if (compare_addr(conn_central_addr.addr, central_addr[0])) { | |
central_conn_handles[0] = conn_handle; | |
Serial.println(" -> BT1"); | |
} else if (compare_addr(conn_central_addr.addr, central_addr[1])) { | |
central_conn_handles[1] = conn_handle; | |
Serial.println(" -> BT2"); | |
} else { | |
Serial.println(" -> not registered"); | |
} | |
print_conn_status(); | |
// 最大接続数に達していなければアドバタイズ継続 | |
connection_count++; | |
if (connection_count < MAX_PRPH_CONNECTION) | |
{ | |
Bluefruit.Advertising.start(60); | |
Serial.println("Keep advertising..."); | |
} | |
} | |
void ble_disconnect_callback(uint16_t conn_handle, uint8_t reason) { | |
Serial.print("disconnect "); | |
Serial.println(conn_handle); | |
if (central_conn_handles[0] == conn_handle) { | |
central_conn_handles[0] = 10; | |
connection_count--; | |
} else if (central_conn_handles[1] == conn_handle) { | |
central_conn_handles[1] = 10; | |
connection_count--; | |
} | |
print_conn_status(); | |
} | |
/* | |
Fnキー | |
*/ | |
bool fn_enable = false; | |
uint8_t fn_row; | |
uint8_t fn_col; | |
void searchFnKey() { | |
for (int col = 0; col < col_map_size; col++) { | |
for (int row = 0; row < row_size; row++) { | |
if (hid_key_codes[row][col] == FN_KEY) { | |
fn_row = row; | |
fn_col = col; | |
fn_enable = true; | |
return; | |
} | |
} | |
} | |
} | |
void processFnKeyCode(uint16_t k) { | |
switch (k) { | |
case FN_BLE_1: | |
Bluefruit.Advertising.stop(); | |
selected_device = 0; | |
Serial.println("BT1 selected."); | |
break; | |
case FN_BLE_2: | |
Bluefruit.Advertising.stop(); | |
selected_device = 1; | |
Serial.println("BT2 selected."); | |
break; | |
case FN_BLE_INFO: | |
Bluefruit.printInfo(); | |
break; | |
case FN_BLE_ADV: | |
startAdvertising(); | |
break; | |
case FN_BLE_CLR: | |
Bluefruit.Periph.clearBonds(); | |
Serial.println("BLE bonding cleared."); | |
break; | |
} | |
} | |
uint8_t scanKeys(uint16_t *keys) { | |
uint8_t count = 0; | |
// Fnキーの状態を読み込み | |
bool fn_pressed = false; | |
if (fn_enable) { | |
if (fn_col < duplex_left_col_size) { | |
// Fnキーが左手側 | |
pinMode(rows[fn_row], OUTPUT); | |
digitalWrite(rows[fn_row], LOW); | |
fn_pressed = digitalRead(cols[fn_col]) == LOW; | |
pinMode(rows[fn_row], INPUT_PULLUP); | |
} else { | |
uint8_t fn_col_index = fn_col - duplex_left_col_size; | |
// Fnキーが右手側 | |
pinMode(cols[fn_col_index], OUTPUT); | |
digitalWrite(cols[fn_col_index], LOW); | |
fn_pressed = digitalRead(rows[fn_row]) == LOW; | |
pinMode(cols[fn_col_index], INPUT_PULLUP); | |
} | |
} | |
// 左手側読み込み(COL2ROW) | |
for (int row = 0; row < row_size; row++) { | |
// 選択する行だけLOWにする | |
pinMode(rows[row], OUTPUT); | |
digitalWrite(rows[row], LOW); | |
// 1列ずつ読み取り | |
for (int col = 0; col < duplex_left_col_size; col++) { | |
// Fnキーは飛ばす | |
if (fn_enable && row == fn_row && col == fn_col) { | |
continue; | |
} | |
// 押されてなければ次 | |
if (digitalRead(cols[col]) != LOW) { | |
continue; | |
} | |
Serial.println(fn_pressed); | |
Serial.println(digitalRead(cols[col])); | |
// キーコード格納 | |
keys[count++] = fn_pressed ? fn_key_codes[row][col] : hid_key_codes[row][col]; | |
// 同時押しは6つまで | |
if (count == 6) { | |
pinMode(rows[row], INPUT_PULLUP); | |
return count; | |
} | |
} | |
// 行選択解除 | |
pinMode(rows[row], INPUT_PULLUP); | |
} | |
// 右手側読み込み(ROW2COL) | |
for (int col = 0; col < duplex_right_col_size; col++) { | |
// 選択する列だけLOWにする | |
pinMode(cols[col], OUTPUT); | |
digitalWrite(cols[col], LOW); | |
// 1行ずつ読み取り | |
for (int row = 0; row < row_size; row++) { | |
if (fn_enable && row == fn_row && col == fn_col) { | |
continue; | |
} | |
if (digitalRead(rows[row]) != LOW) { | |
continue; | |
} | |
keys[count++] = fn_pressed ? fn_key_codes[row][col + duplex_left_col_size] : hid_key_codes[row][col + duplex_left_col_size]; | |
if (count == 6) { | |
pinMode(cols[col], INPUT_PULLUP); | |
return count; | |
} | |
} | |
// 列選択解除 | |
pinMode(cols[col], INPUT_PULLUP); | |
} | |
return count; | |
} | |
void setup() { | |
// Serial.begin(115200); | |
// while ( !Serial ) delay(10); | |
// Fnキーの位置を特定 | |
searchFnKey(); | |
if (fn_enable) { | |
Serial.print("Fn key enabled at row "); | |
Serial.print(fn_row); | |
Serial.print(" col "); | |
Serial.print(fn_col); | |
Serial.println("."); | |
} else { | |
Serial.println("Fn key disabled."); | |
} | |
// キースイッチ初期化 (すべてプルアップ) | |
for (int col = 0; col < col_size; col++) { | |
pinMode(cols[col], INPUT_PULLUP); | |
} | |
for (int row = 0; row < row_size; row++) { | |
pinMode(rows[row], INPUT_PULLUP); | |
} | |
Serial.println("Key matrix initialized."); | |
// BLE初期化 | |
Bluefruit.autoConnLed(false); | |
Bluefruit.begin(MAX_PRPH_CONNECTION, 0); | |
Bluefruit.setTxPower(4); | |
Bluefruit.Periph.setConnectCallback(ble_connect_callback); | |
Bluefruit.Periph.setDisconnectCallback(ble_disconnect_callback); | |
bledis.setManufacturer("Manifacturer"); | |
bledis.setModel("Sunrise70 keyboard"); | |
bledis.begin(); | |
blehid.begin(); | |
Serial.println("BLE initialized."); | |
// アドバタイズ開始 | |
startAdvertising(); | |
} | |
void loop() { | |
delay(10); | |
// 押されているキーと送るキー | |
uint16_t pressed_keys[6] = {0}; | |
uint8_t report_keys[6] = {0}; | |
uint8_t report_modifiers = 0; | |
static uint16_t last_sent_device = 10; | |
static bool prev_key_pressed = false; | |
uint8_t count = scanKeys(pressed_keys); | |
// 内部用のキーコードを処理して残りのキーコードを送る | |
for (uint8_t i = 0; i < 6; i++) { | |
uint16_t k = pressed_keys[i]; | |
if (k > FN_KEY) { | |
processFnKeyCode(k); | |
} else { | |
report_keys[i] = k; | |
} | |
} | |
// BLEの接続先を変えるときに空のキーコードを送る | |
uint16_t send_conn = central_conn_handles[selected_device]; | |
if (selected_device != last_sent_device) { | |
if (central_conn_handles[last_sent_device] != 10) { | |
blehid.keyRelease(central_conn_handles[last_sent_device]); | |
} | |
last_sent_device = selected_device; | |
Serial.println("BLE conn changed."); | |
} | |
// コネクションがなければ何もしない | |
if (send_conn == 10) { | |
// 再接続できるかもしれないのでアドバタイズ開始 | |
startAdvertising(); | |
return; | |
} | |
// 1つ以上送るキーがあれば送る | |
if (count > 0) { | |
prev_key_pressed = true; | |
blehid.keyboardReport(send_conn, report_modifiers, report_keys); | |
} else { | |
// キーが全部離されたタイミングで1度だけ空のキーコードを送る | |
if (prev_key_pressed) { | |
prev_key_pressed = false; | |
blehid.keyRelease(send_conn); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment