Skip to content

Instantly share code, notes, and snippets.

@nibral
Created June 1, 2022 05:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nibral/3772bce4b32e31dd59afcf2a09472ee5 to your computer and use it in GitHub Desktop.
Save nibral/3772bce4b32e31dd59afcf2a09472ee5 to your computer and use it in GitHub Desktop.
Firmware for Sunrise70 keyboard
#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