├── FeatherChorder ├── BluefruitConfig.h ├── ChordMappings.h ├── FeatherChorder.ino └── KeyCodes.h ├── LICENSE.txt └── README.md /FeatherChorder/BluefruitConfig.h: -------------------------------------------------------------------------------- 1 | // COMMON SETTINGS 2 | // ---------------------------------------------------------------------------------------------- 3 | // These settings are used in both SW UART, HW UART and SPI mode 4 | // ---------------------------------------------------------------------------------------------- 5 | #define BUFSIZE 128 // Size of the read buffer for incoming data 6 | 7 | // SOFTWARE UART SETTINGS 8 | // ---------------------------------------------------------------------------------------------- 9 | // The following macros declare the pins that will be used for 'SW' serial. 10 | // You should use this option if you are connecting the UART Friend to an UNO 11 | // ---------------------------------------------------------------------------------------------- 12 | #define BLUEFRUIT_SWUART_RXD_PIN 9 // Required for software serial! 13 | #define BLUEFRUIT_SWUART_TXD_PIN 10 // Required for software serial! 14 | #define BLUEFRUIT_UART_CTS_PIN 11 // Required for software serial! 15 | #define BLUEFRUIT_UART_RTS_PIN -1 // Optional, set to -1 if unused 16 | 17 | 18 | // HARDWARE UART SETTINGS 19 | // ---------------------------------------------------------------------------------------------- 20 | // The following macros declare the HW serial port you are using. Uncomment 21 | // this line if you are connecting the BLE to Leonardo/Micro or Flora 22 | // ---------------------------------------------------------------------------------------------- 23 | //#ifdef Serial1 // this makes it not complain on compilation if there's no Serial1 24 | //#define BLUEFRUIT_HWSERIAL_NAME Serial1 25 | //#endif 26 | 27 | 28 | // SHARED UART SETTINGS 29 | // ---------------------------------------------------------------------------------------------- 30 | // The following sets the optional Mode pin, its recommended but not required 31 | // ---------------------------------------------------------------------------------------------- 32 | #define BLUEFRUIT_UART_MODE_PIN 12 // Set to -1 if unused 33 | 34 | 35 | // HARDWARE SPI SETTINGS 36 | // ---------------------------------------------------------------------------------------------- 37 | // The following macros declare the pins to use for HW SPI communication. 38 | // SCK, MISO and MOSI should be connected to the HW SPI pins on the Uno, etc. 39 | // This should be used with nRF51822 based Bluefruit LE modules that use SPI. 40 | // ---------------------------------------------------------------------------------------------- 41 | #define BLUEFRUIT_SPI_CS 8 42 | #define BLUEFRUIT_SPI_IRQ 7 43 | #define BLUEFRUIT_SPI_RST 4 // Optional but recommended, set to -1 if unused 44 | -------------------------------------------------------------------------------- /FeatherChorder/ChordMappings.h: -------------------------------------------------------------------------------- 1 | // ChordMappings.h 2 | // Chordmappings for 7 button chorder, split out from FeatherChorder.ino 3 | // Mappings moved here so they can be changed without risk 4 | // of modifying the rest of the code. 5 | // - Greg 6 | 7 | typedef uint8_t keymap_t; 8 | 9 | 10 | /********************************************************** 11 | * order is Far Thumb, Center Thumb, Near Thumb button * 12 | * Index Finger, Middle Finger, Ring Finger, Pinky * 13 | * FCN IMRP * 14 | **********************************************************/ 15 | const keymap_t keymap_default[128] = { 16 | ENUMKEY__, // --- ---- 0x00 no keys pressed 17 | ENUMKEY_W, // --- ---P 0x01 18 | ENUMKEY_Y, // --- --R- 0x02 19 | ENUMKEY_U, // --- --RP 0x03 20 | ENUMKEY_R, // --- -M-- 0x04 21 | MACRO_1, // --- -M-P 0x05 22 | ENUMKEY_H, // --- -MR- 0x06 23 | ENUMKEY_S, // --- -MRP 0x07 24 | 25 | ENUMKEY_I, // --- I--- 0x08 26 | ENUMKEY_B, // --- I--P 0x09 27 | ENUMKEY_K, // --- I-R- 0x0A 28 | ENUMKEY_Z, // --- I-RP 0x0B 29 | ENUMKEY_D, // --- IM-- 0x0C 30 | MACRO_2, // --- IM-P 0x0D 31 | ENUMKEY_E, // --- IMR- 0x0E 32 | ENUMKEY_T, // --- IMRP 0x0F 33 | 34 | MODE_NUM, // --N ---- 0x10 35 | 36 | MODE_FUNC, // --N ---P 0x11 37 | ENUMKEY_esc, // --N --R- 0x12 38 | ENUMKEY_smcol, // --N --RP 0x13 39 | ENUMKEY_comma, // --N -M-- 0x14 40 | MACRO_closecurly, // --N -M-P 0x15 41 | ENUMKEY_dot, // --N -MR- 0x16 42 | MOD_LALT, // --N -MRP 0x17 43 | 44 | RAW_LGUI, // --N I--- 0x18 45 | ENUMKEY_ins, // --N I--P 0x19 46 | MOD_LGUI, // --N I-R- 0x1A 47 | MOD_LCTRL, // --N I-RP 0x1B 48 | LATCH, // --N IM-- 0x1C 49 | MACRO_opencurly, // --N IM-P 0x1D 50 | ENUMKEY_ping, // --N IMR- 0x1E 51 | MODE_NUMLCK, // --N IMRP 0x1F 52 | 53 | ENUMKEY_spc, // -C- ---- 0x20 54 | ENUMKEY_F, // -C- ---P 0x21 55 | ENUMKEY_G, // -C- --R- 0x22 56 | ENUMKEY_V, // -C- --RP 0x23 57 | ENUMKEY_C, // -C- -M-- 0x24 58 | MACRO_3, // -C- -M-P 0x25 59 | ENUMKEY_P, // -C- -MR- 0x26 60 | ENUMKEY_N, // -C- -MRP 0x27 61 | 62 | ENUMKEY_L, // -C- I--- 0x28 63 | ENUMKEY_X, // -C- I--P 0x29 64 | ENUMKEY_J, // -C- I-R- 0x2A 65 | ENUMKEY_Q, // -C- I-RP 0x2B 66 | ENUMKEY_M, // -C- IM-- 0x2C 67 | MACRO_4, // -C- IM-P 0x2D 68 | ENUMKEY_A, // -C- IMR- 0x2E 69 | ENUMKEY_O, // -C- IMRP 0x2F 70 | 71 | MULTI_NumShift, // -CN ---- 0x30 72 | ENUMKEY__, // -CN ---P 0x31 73 | ENUMKEY__, // -CN --R- 0x32 74 | ENUMKEY__, // -CN --RP 0x33 75 | ANDROID_dpadcenter, // -CN -M-- 0x34 76 | ENUMKEY__, // -CN -M-P 0x35 77 | ANDROID_home, // -CN -MR- 0x36 78 | MOD_RALT, // -CN -MRP 0x37 79 | 80 | ENUMKEY__, // -CN I--- 0x38 81 | ANDROID_back, // -CN I--P 0x39 82 | MOD_RGUI, // -CN I-R- 0x3A 83 | MOD_RCTRL, // -CN I-RP 0x3B 84 | ANDROID_menu, // -CN IM-- 0x3C 85 | ENUMKEY__, // -CN IM-P 0x3D 86 | ANDROID_search, // -CN IMR- 0x3E 87 | ENUMKEY_numlock, // -CN IMRP 0x3F 88 | 89 | MOD_LSHIFT, // F-- ---- 0x40 90 | ENUMKEY_enter, // F-- ---P 0x41 91 | ENUMKEY_rarr, // F-- --R- 0x42 92 | ENUMKEY_darr, // F-- --RP 0x43 93 | ENUMKEY_bckspc, // F-- -M-- 0x44 94 | ENUMKEY_PrtScr, // F-- -M-P 0x45 95 | ENUMKEY_del, // F-- -MR- 0x46 96 | ENUMKEY_pgdn, // F-- -MRP 0x47 97 | 98 | ENUMKEY_larr, // F-- I--- 0x48 99 | ENUMKEY_end, // F-- I--P 0x49 100 | ENUMKEY_tab, // F-- I-R- 0x4A 101 | ENUMKEY_home, // F-- I-RP 0x4B 102 | ENUMKEY_uarr, // F-- IM-- 0x4C 103 | ENUMKEY_scrlck, // F-- IM-P 0x4D 104 | ENUMKEY_pgup, // F-- IMR- 0x4E 105 | ENUMKEY_cpslck, // F-- IMRP 0x4F 106 | 107 | ENUMKEY_break, // F-N ---- 0x50 108 | MACRO_SHIFTDN, // F-N ---P 0x51 109 | ENUMKEY__, // F-N --R- 0x52 110 | ENUMKEY__, // F-N --RP 0x53 111 | ENUMKEY__, // F-N -M-- 0x54 112 | ENUMKEY__, // F-N -M-P 0x55 113 | ENUMKEY__, // F-N -MR- 0x56 114 | ENUMKEY__, // F-N -MRP 0x57 115 | 116 | ENUMKEY__, // F-N I--- 0x58 117 | ENUMKEY__, // F-N I--P 0x59 118 | ENUMKEY__, // F-N I-R- 0x5A 119 | ENUMKEY__, // F-N I-RP 0x5B 120 | ENUMKEY__, // F-N IM-- 0x5C 121 | ENUMKEY__, // F-N IM-P 0x5D 122 | ENUMKEY__, // F-N IMR- 0x5E 123 | ENUMKEY__, // F-N IMRP 0x5F 124 | 125 | MOD_RSHIFT, // FC- ---- 0x60 126 | ENUMKEY_KPenter, // FC- ---P 0x61 127 | ENUMKEY_KP6, // FC- --R- 0x62 128 | MEDIA_volup, // was ENUMKEY_KP2, // FC- --RP 0x63 129 | MEDIA_stop, // was ENUMKEY_KP5, // FC- -M-- 0x64 130 | ENUMKEY_KPast, // FC- -M-P 0x65 131 | MEDIA_playpause, // was ENUMKEY_KPcomma, // FC- -MR- 0x66 132 | MEDIA_next, // was ENUMKEY_KP3, // FC- -MRP 0x67 133 | 134 | ENUMKEY_KP4, // FC- I--- 0x68 135 | MEDIA_previous, // was ENUMKEY_KP1, // FC- I--P 0x69 136 | ENUMKEY_KPminus, // FC- I-R- 0x6A 137 | ENUMKEY_KP7, // FC- I-RP 0x6B 138 | MEDIA_voldn, // was ENUMKEY_KP8, // FC- IM-- 0x6C 139 | ENUMKEY_KPslash, // FC- IM-P 0x6D 140 | MEDIA_previous, // was ENUMKEY_KP9, // FC- IMR- 0x6E 141 | ENUMKEY_KP0, // FC- IMRP 0x6F 142 | 143 | MODE_RESET, // FCN ---- 0x70 144 | ENUMKEY__, // FCN ---P 0x71 145 | ENUMKEY__, // FCN --R- 0x72 146 | ENUMKEY__, // FCN --RP 0x73 147 | ENUMKEY__, // FCN -M-- 0x74 148 | ENUMKEY__, // FCN -M-P 0x75 149 | ENUMKEY__, // FCN -MR- 0x76 150 | ENUMKEY__, // FCN -MRP 0x77 151 | 152 | MODE_FRESET, // FCN I--- 0x78 153 | ENUMKEY__, // FCN I--P 0x79 154 | ENUMKEY__, // FCN I-R- 0x7A 155 | ENUMKEY__, // FCN I-RP 0x7B 156 | ENUMKEY__, // FCN IM-- 0x7C 157 | ENUMKEY__, // FCN IM-P 0x7D 158 | ENUMKEY__, // FCN IMR- 0x7E 159 | ENUMKEY__ // FCN IMRP 0x7F 160 | }; 161 | 162 | /************************************** 163 | * number/symbols mode * 164 | **************************************/ 165 | const keymap_t keymap_numsym[128] = { 166 | ENUMKEY__, // --- ---- 0x00 167 | ENUMKEY_5, // --- ---P 0x01 168 | ENUMKEY_4, // --- --R- 0x02 169 | MACRO_quotes, // --- --RP 0x03 "" and a back arrow 170 | ENUMKEY_3, // --- -M-- 0x04 171 | ENUMKEY__, // --- -M-P 0x05 172 | MACRO_00, // --- -MR- 0x06 00 173 | ENUMKEY_minus, // --- -MRP 0x07 174 | 175 | ENUMKEY_2, // --- I--- 0x08 176 | ENUMKEY_bckslsh, // --- I--P 0x09 177 | MACRO_dollar, // --- I-R- 0x0A $ 178 | ENUMKEY_grave, // --- I-RP 0x0B 179 | ENUMKEY_slash, // --- IM-- 0x0C 180 | ENUMKEY__, // --- IM-P 0x0D 181 | ENUMKEY_equal, // --- IMR- 0x0E 182 | MACRO_000, // --- IMRP 0x0F 000 183 | 184 | ENUMKEY_spc, // --N ---- 0x10 185 | MODE_FUNC, // --N ---P 0x11 186 | ENUMKEY_esc, // --N --R- 0x12 187 | ENUMKEY_smcol, // --N --RP 0x13 188 | ENUMKEY_comma, // --N -M-- 0x14 189 | ENUMKEY__, // --N -M-P 0x15 190 | ENUMKEY_dot, // --N -MR- 0x16 191 | MOD_LALT, // --N -MRP 0x17 192 | 193 | ENUMKEY__, // --N I--- 0x18 194 | ENUMKEY_ins, // --N I--P 0x19 195 | MOD_LGUI, // --N I-R- 0x1A 196 | MOD_LCTRL, // --N I-RP 0x1B 197 | LATCH, // --N IM-- 0x1C 198 | ENUMKEY__, // --N IM-P 0x1D 199 | ENUMKEY_ping, // --N IMR- 0x1E 200 | MODE_RESET, // --N IMRP 0x1F 201 | 202 | ENUMKEY_1, // -C- ---- 0x20 203 | ENUMKEY_9, // -C- ---P 0x21 204 | ENUMKEY_8, // -C- --R- 0x22 205 | ENUMKEY_rbr, // -C- --RP 0x23 206 | ENUMKEY_7, // -C- -M-- 0x24 207 | ENUMKEY_rbr, // -C- -M-P 0x25 208 | MACRO_percent, // -C- -MR- 0x26 % 209 | ENUMKEY_lbr, // -C- -MRP 0x27 210 | 211 | ENUMKEY_6, // -C- I--- 0x28 212 | MACRO_ampersand, // -C- I--P 0x29 & 213 | MACRO_parens, // -C- I-R- 0x2A () and a back arrow 214 | MACRO_question, // -C- I-RP 0x2B ? 215 | MACRO_asterisk, // -C- IM-- 0x2C 216 | ENUMKEY_lbr, // -C- IM-P 0x2D 217 | MACRO_plus, // -C- IMR- 0x2E 218 | ENUMKEY_0, // -C- IMRP 0x2F 219 | 220 | MULTI_NumShift, // -CN ---- 0x30 221 | ENUMKEY__, // -CN ---P 0x31 222 | ENUMKEY__, // -CN --R- 0x32 223 | ENUMKEY__, // -CN --RP 0x33 224 | ANDROID_dpadcenter, // -CN -M-- 0x34 225 | ENUMKEY__, // -CN -M-P 0x35 226 | ANDROID_home, // -CN -MR- 0x36 227 | MOD_RALT, // -CN -MRP 0x37 228 | 229 | ENUMKEY__, // -CN I--- 0x38 230 | ANDROID_back, // -CN I--P 0x39 231 | MOD_RGUI, // -CN I-R- 0x3A 232 | MOD_RCTRL, // -CN I-RP 0x3B 233 | ANDROID_menu, // -CN IM-- 0x3C 234 | ENUMKEY__, // -CN IM-P 0x3D 235 | ANDROID_search, // -CN IMR- 0x3E 236 | ENUMKEY_numlock, // -CN IMRP 0x3F 237 | 238 | MOD_LSHIFT, // F-- ---- 0x40 239 | ENUMKEY_enter, // F-- ---P 0x41 240 | ENUMKEY_rarr, // F-- --R- 0x42 241 | ENUMKEY_darr, // F-- --RP 0x43 242 | ENUMKEY_bckspc, // F-- -M-- 0x44 243 | ENUMKEY_PrtScr, // F-- -M-P 0x45 244 | ENUMKEY_del, // F-- -MR- 0x46 245 | ENUMKEY_pgdn, // F-- -MRP 0x47 246 | 247 | ENUMKEY_larr, // F-- I--- 0x48 248 | ENUMKEY_end, // F-- I--P 0x49 249 | ENUMKEY_tab, // F-- I-R- 0x4A 250 | ENUMKEY_home, // F-- I-RP 0x4B 251 | ENUMKEY_uarr, // F-- IM-- 0x4C 252 | ENUMKEY_scrlck, // F-- IM-P 0x4D 253 | ENUMKEY_pgup, // F-- IMR- 0x4E 254 | ENUMKEY_cpslck, // F-- IMRP 0x4F 255 | 256 | ENUMKEY_break, // F-N ---- 0x50 257 | ENUMKEY__, // F-N ---P 0x51 258 | ENUMKEY__, // F-N --R- 0x52 259 | ENUMKEY__, // F-N --RP 0x53 260 | ENUMKEY__, // F-N -M-- 0x54 261 | ENUMKEY__, // F-N -M-P 0x55 262 | ENUMKEY__, // F-N -MR- 0x56 263 | ENUMKEY__, // F-N -MRP 0x57 264 | 265 | ENUMKEY__, // F-N I--- 0x58 266 | ENUMKEY__, // F-N I--P 0x59 267 | ENUMKEY__, // F-N I-R- 0x5A 268 | ENUMKEY__, // F-N I-RP 0x5B 269 | ENUMKEY__, // F-N IM-- 0x5C 270 | ENUMKEY__, // F-N IM-P 0x5D 271 | ENUMKEY__, // F-N IMR- 0x5E 272 | ENUMKEY__, // F-N IMRP 0x5F 273 | 274 | MOD_RSHIFT, // FC- ---- 0x60 275 | ENUMKEY_KPenter, // FC- ---P 0x61 276 | ENUMKEY_KP6, // FC- --R- 0x62 277 | ENUMKEY_KP2, // FC- --RP 0x63 278 | ENUMKEY_KP5, // FC- -M-- 0x64 279 | ENUMKEY_KPast, // FC- -M-P 0x65 280 | ENUMKEY_KPcomma, // FC- -MR- 0x66 281 | ENUMKEY_KP3, // FC- -MRP 0x67 282 | 283 | ENUMKEY_KP4, // FC- I--- 0x68 284 | ENUMKEY_KP1, // FC- I--P 0x69 285 | ENUMKEY_KPminus, // FC- I-R- 0x6A 286 | ENUMKEY_KP7, // FC- I-RP 0x6B 287 | ENUMKEY_KP8, // FC- IM-- 0x6C 288 | ENUMKEY_KPslash, // FC- IM-P 0x6D 289 | ENUMKEY_KP9, // FC- IMR- 0x6E 290 | ENUMKEY_KP0, // FC- IMRP 0x6F 291 | 292 | MODE_RESET, // FCN ---- 0x70 293 | ENUMKEY__, // FCN ---P 0x71 294 | ENUMKEY__, // FCN --R- 0x72 295 | ENUMKEY__, // FCN --RP 0x73 296 | ENUMKEY__, // FCN -M-- 0x74 297 | ENUMKEY__, // FCN -M-P 0x75 298 | ENUMKEY__, // FCN -MR- 0x76 299 | ENUMKEY__, // FCN -MRP 0x77 300 | 301 | ENUMKEY__, // FCN I--- 0x78 302 | ENUMKEY__, // FCN I--P 0x79 303 | ENUMKEY__, // FCN I-R- 0x7A 304 | ENUMKEY__, // FCN I-RP 0x7B 305 | ENUMKEY__, // FCN IM-- 0x7C 306 | ENUMKEY__, // FCN IM-P 0x7D 307 | ENUMKEY__, // FCN IMR- 0x7E 308 | ENUMKEY__ // FCN IMRP 0x7F 309 | }; 310 | 311 | /************************************** 312 | * function key mode * 313 | **************************************/ 314 | const keymap_t keymap_function[128] = { 315 | ENUMKEY__, // --- ---- 0x00 316 | ENUMKEY_F5, // --- ---P 0x01 317 | ENUMKEY_F4, // --- --R- 0x02 318 | MEDIA_volup, // --- --RP 0x03 319 | ENUMKEY_F3, // --- -M-- 0x04 320 | ENUMKEY__, // --- -M-P 0x05 321 | MACRO_TEST, // --- -MR- 0x06 322 | MEDIA_stop, // --- -MRP 0x07 323 | 324 | ENUMKEY_F2, // --- I--- 0x08 325 | MEDIA_previous, // --- I--P 0x09 326 | ENUMKEY__, // --- I-R- 0x0A 327 | ENUMKEY__, // --- I-RP 0x0B 328 | MEDIA_voldn, // --- IM-- 0x0C 329 | ENUMKEY__, // --- IM-P 0x0D 330 | BAT_LVL, // --- IMR- 0x0E 331 | ENUMKEY__, // --- IMRP 0x0F 332 | 333 | ENUMKEY__, // --N ---- 0x10 334 | MODE_RESET, // --N ---P 0x11 335 | ENUMKEY__, // --N --R- 0x12 336 | ENUMKEY__, // --N --RP 0x13 337 | ENUMKEY__, // --N -M-- 0x14 338 | ENUMKEY__, // --N -M-P 0x15 339 | ENUMKEY__, // --N -MR- 0x16 340 | MOD_LALT, // --N -MRP 0x17 341 | 342 | ENUMKEY__, // --N I--- 0x18 343 | ENUMKEY__, // --N I--P 0x19 344 | MOD_LGUI, // --N I-R- 0x1A 345 | MOD_LCTRL, // --N I-RP 0x1B 346 | LATCH, // --N IM-- 0x1C 347 | ENUMKEY__, // --N IM-P 0x1D 348 | ENUMKEY__, // --N IMR- 0x1E 349 | ENUMKEY__, // --N IMRP 0x1F 350 | 351 | ENUMKEY_F1, // -C- ---- 0x20 352 | ENUMKEY_F9, // -C- ---P 0x21 353 | ENUMKEY_F8, // -C- --R- 0x22 354 | ENUMKEY_F12, // -C- --RP 0x23 355 | ENUMKEY_F7, // -C- -M-- 0x24 356 | ENUMKEY__, // -C- -M-P 0x25 357 | ENUMKEY_F11, // -C- -MR- 0x26 358 | ENUMKEY__, // -C- -MRP 0x27 359 | 360 | ENUMKEY_F6, // -C- I--- 0x28 361 | ENUMKEY__, // -C- I--P 0x29 362 | ENUMKEY__, // -C- I-R- 0x2A 363 | ENUMKEY__, // -C- I-RP 0x2B 364 | ENUMKEY_F10, // -C- IM-- 0x2C 365 | ENUMKEY__, // -C- IM-P 0x2D 366 | ENUMKEY__, // -C- IMR- 0x2E 367 | ENUMKEY__, // -C- IMRP 0x2F 368 | 369 | MULTI_NumShift, // -CN ---- 0x30 370 | ENUMKEY__, // -CN ---P 0x31 371 | ENUMKEY__, // -CN --R- 0x32 372 | ENUMKEY__, // -CN --RP 0x33 373 | ANDROID_dpadcenter, // -CN -M-- 0x34 374 | ENUMKEY__, // -CN -M-P 0x35 375 | ANDROID_home, // -CN -MR- 0x36 376 | MOD_RALT, // -CN -MRP 0x37 377 | 378 | ENUMKEY__, // -CN I--- 0x38 379 | ANDROID_back, // -CN I--P 0x39 380 | MOD_RGUI, // -CN I-R- 0x3A 381 | MOD_RCTRL, // -CN I-RP 0x3B 382 | ANDROID_menu, // -CN IM-- 0x3C 383 | ENUMKEY__, // -CN IM-P 0x3D 384 | ANDROID_search, // -CN IMR- 0x3E 385 | ENUMKEY__, // -CN IMRP 0x3F 386 | 387 | MOD_LSHIFT, // F-- ---- 0x40 388 | ENUMKEY__, // F-- ---P 0x41 389 | ENUMKEY__, // F-- --R- 0x42 390 | ENUMKEY__, // F-- --RP 0x43 391 | ENUMKEY__, // F-- -M-- 0x44 392 | ENUMKEY__, // F-- -M-P 0x45 393 | MEDIA_playpause, // F-- -MR- 0x46 394 | MEDIA_next, // F-- -MRP 0x47 395 | 396 | MEDIA_previous, // F-- I--- 0x48 397 | ENUMKEY__, // F-- I--P 0x49 398 | ENUMKEY__, // F-- I-R- 0x4A 399 | ENUMKEY__, // F-- I-RP 0x4B 400 | ENUMKEY__, // F-- IM-- 0x4C 401 | ENUMKEY__, // F-- IM-P 0x4D 402 | ENUMKEY__, // F-- IMR- 0x4E 403 | ENUMKEY__, // F-- IMRP 0x4F 404 | 405 | ENUMKEY__, // F-N ---- 0x50 406 | ENUMKEY__, // F-N ---P 0x51 407 | ENUMKEY__, // F-N --R- 0x52 408 | ENUMKEY__, // F-N --RP 0x53 409 | ENUMKEY__, // F-N -M-- 0x54 410 | ENUMKEY__, // F-N -M-P 0x55 411 | ENUMKEY__, // F-N -MR- 0x56 412 | ENUMKEY__, // F-N -MRP 0x57 413 | 414 | ENUMKEY__, // F-N I--- 0x58 415 | ENUMKEY__, // F-N I--P 0x59 416 | ENUMKEY__, // F-N I-R- 0x5A 417 | ENUMKEY__, // F-N I-RP 0x5B 418 | ENUMKEY__, // F-N IM-- 0x5C 419 | ENUMKEY__, // F-N IM-P 0x5D 420 | ENUMKEY__, // F-N IMR- 0x5E 421 | ENUMKEY__, // F-N IMRP 0x5F 422 | 423 | MOD_RSHIFT, // FC- ---- 0x60 424 | ENUMKEY__, // FC- ---P 0x61 425 | ENUMKEY__, // FC- --R- 0x62 426 | ENUMKEY__, // FC- --RP 0x63 427 | ENUMKEY__, // FC- -M-- 0x64 428 | ENUMKEY__, // FC- -M-P 0x65 429 | ENUMKEY__, // FC- -MR- 0x66 430 | ENUMKEY__, // FC- -MRP 0x67 431 | 432 | ENUMKEY__, // FC- I--- 0x68 433 | ENUMKEY__, // FC- I--P 0x69 434 | ENUMKEY__, // FC- I-R- 0x6A 435 | ENUMKEY__, // FC- I-RP 0x6B 436 | ENUMKEY__, // FC- IM-- 0x6C 437 | ENUMKEY__, // FC- IM-P 0x6D 438 | ENUMKEY__, // FC- IMR- 0x6E 439 | ENUMKEY__, // FC- IMRP 0x6F 440 | 441 | MODE_RESET, // FCN ---- 0x70 442 | ENUMKEY__, // FCN ---P 0x71 443 | ENUMKEY__, // FCN --R- 0x72 444 | ENUMKEY__, // FCN --RP 0x73 445 | ENUMKEY__, // FCN -M-- 0x74 446 | ENUMKEY__, // FCN -M-P 0x75 447 | ENUMKEY__, // FCN -MR- 0x76 448 | ENUMKEY__, // FCN -MRP 0x77 449 | 450 | ENUMKEY__, // FCN I--- 0x78 451 | ENUMKEY__, // FCN I--P 0x79 452 | ENUMKEY__, // FCN I-R- 0x7A 453 | ENUMKEY__, // FCN I-RP 0x7B 454 | ENUMKEY__, // FCN IM-- 0x7C 455 | ENUMKEY__, // FCN IM-P 0x7D 456 | ENUMKEY__, // FCN IMR- 0x7E 457 | ENUMKEY__ // FCN IMRP 0x7F 458 | }; 459 | 460 | // end ChordMappings.h 461 | -------------------------------------------------------------------------------- /FeatherChorder/FeatherChorder.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Arduino code for a Bluetooth version of the Chorder. 3 | * @author: clc@clcworld.net 4 | * additional code by: priestdo@budgardr.org 5 | * This version tested on/with the Adafruit Feather 32u4 Bluefruit LE 6 | * 7 | * This is new arduino code based on/inspired by the SpiffChorder 8 | * which can be found at http://symlink.dk/projects/spiffchorder/ 9 | * 10 | * changes Feb. 2025 11 | * - Added many comments to remind me of what the code is doing. 12 | * - Work to add proper key down/key up so key repeat works started 2025-02 13 | * - Fixed key send, it needed Hexidecimal digits padded to be 2 char. - existing 14 | * code regularly send one digit when a leading zero seems to be required by 15 | * BLE firmware greater than 0.6.7, It also partially fixed need to forget and 16 | * re-pair every time. However, this still creeps back in now and then. SO... 17 | * - Added factory reset keyboard macro bound to Function modifyer then all 3 thumbs. 18 | * - Added ability to suppress all output to the serial console with VERBOSE_MODE 19 | * set to false, moved this setting from BluefruitConfig.h 20 | * - Added Battery Voltage macro that returns the current voltage of the LiPo by 21 | * printing that as keyboard output. (Not to serial console) 22 | * - Have not impimented key repeat yet. 23 | * - Added printed comment to device when doing a factory reset. 24 | * - Added 4 macros for the 4 unassigned chords in the default keyset 25 | * - Changed caps lock to be handled on the host rather than by the keyboard 26 | * - Added mod key latching. If LATCH is pressed, any current modKeys are sent 27 | * with each key until LATCH is pressed again. 28 | * - Added / changed the 4 default keys not part of 29 | * the default key map. --- -M-P, --- IM-P, 30 | * -C- -M-P, -C- IM-P. as, er, th, an, in. 31 | * The behavior for these is that latched mods and 32 | * caps lock will affect both letters, but other modifyers 33 | * like shift, will only affect the first letter. 34 | * - Corrected latch mod so it no longer sends the after it is toggled off with 35 | * the latched mods. 36 | * - Corrected the way the shiftdn macro worked. 37 | * 38 | * Last mucked with on: 2025/03/26 39 | */ 40 | 41 | #include 42 | #include 43 | 44 | // slight change to below to match adafruit example GPD 2025-02-09 45 | // #if not defined (_VARIANT_ARDUINO_DUE_X_) 46 | // #include 47 | // #endif 48 | 49 | #include "Adafruit_BLE.h" 50 | #include "Adafruit_BluefruitLE_SPI.h" 51 | #include "Adafruit_BluefruitLE_UART.h" 52 | 53 | #include "BluefruitConfig.h" 54 | 55 | #if SOFTWARE_SERIAL_AVAILABLE 56 | #include 57 | #endif 58 | 59 | 60 | #include "KeyCodes.h" 61 | #include "ChordMappings.h" 62 | 63 | /*============================================================= 64 | APPLICATION SETTINGS 65 | 66 | FACTORYRESET_ENABLE    Perform a factory reset when running this sketch 67 | 68 | Enabling this will put your Bluefruit LE module 69 | in a 'known good' state and clear any config 70 | data set in previous sketches or projects, so 71 | running this at least once is a good idea. 72 | 73 | When deploying your project, however, you will 74 | want to disable factory reset by setting this 75 | value to 0.  If you are making changes to your 76 | Bluefruit LE device via AT commands, and those 77 | changes aren't persisting across resets, this 78 | is the reason why.  Factory reset will erase 79 | the non-volatile memory where config data is 80 | stored, setting it back to factory default 81 | values. 82 | 83 | Some sketches that require you to bond to a 84 | central device (HID mouse, keyboard, etc.) 85 | won't work at all with this feature enabled 86 | since the factory reset will clear all of the 87 | bonding data stored on the chip, meaning the 88 | central device won't be able to reconnect. 89 | MINIMUM_FIRMWARE_VERSION Minimum firmware version to have some new features 90 | VERBOSE_MODE If set to 'true' enables debug output, 'false' 91 | attempts to suppresses most serial output. 92 | -----------------------------------------------------------------------*/ 93 | #define FACTORYRESET_ENABLE 0 94 | #define MINIMUM_FIRMWARE_VERSION "0.6.6" 95 | #define VERBOSE_MODE true 96 | 97 | #define DEVICENAME "FeatherChorder+" 98 | //============================================================= 99 | #define VBATPIN A9 // used by gAsBattLvl() 100 | //============================================================= 101 | 102 | // Create the bluefruit object, either software serial...uncomment these lines 103 | /* 104 | SoftwareSerial bluefruitSS = SoftwareSerial(BLUEFRUIT_SWUART_TXD_PIN, BLUEFRUIT_SWUART_RXD_PIN); 105 | 106 | Adafruit_BluefruitLE_UART ble(bluefruitSS, BLUEFRUIT_UART_MODE_PIN, 107 | BLUEFRUIT_UART_CTS_PIN, BLUEFRUIT_UART_RTS_PIN); 108 | */ 109 | 110 | /* ...or hardware serial, which does not need the RTS/CTS pins. Uncomment this line */ 111 | // Adafruit_BluefruitLE_UART ble(BLUEFRUIT_HWSERIAL_NAME, BLUEFRUIT_UART_MODE_PIN); 112 | 113 | /* ...hardware SPI, using SCK/MOSI/MISO hardware SPI pins and then user selected CS/IRQ/RST */ 114 | Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST); 115 | 116 | /* ...software SPI, using SCK/MOSI/MISO user-defined SPI pins and then user selected CS/IRQ/RST */ 117 | //Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_SCK, BLUEFRUIT_SPI_MISO, 118 | // BLUEFRUIT_SPI_MOSI, BLUEFRUIT_SPI_CS, 119 | // BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST); 120 | 121 | //============================================================= 122 | // a small helper used in setup() 123 | // ctb 124 | void error(const __FlashStringHelper*err) { 125 | if ( VERBOSE_MODE ) Serial.println(err); 126 | while (1); 127 | } 128 | //============================================================= 129 | class Button { 130 | byte _pin; // The button's I/O pin, as an Arduino pin number. 131 | 132 | public: 133 | Button(byte pin) : _pin(pin) { 134 | pinMode(pin, INPUT_PULLUP); // Make pin an input and activate pullup. 135 | } 136 | 137 | bool isDown() const { 138 | // TODO: this assumes we're using analog pins! 139 | //return analogRead(_pin) < 0x100; 140 | return (digitalRead(_pin) == LOW); 141 | } 142 | }; 143 | //============================================================= 144 | // board and connectivity specific 145 | // power regulator enable control pin 146 | 147 | const int EnPin = 5; 148 | 149 | 150 | // Pin numbers for the chording keyboard switches, using the Arduino numbering. 151 | static const Button switch_pins[7] = { 152 | Button(6), // Pinky 153 | Button(A5), // Ring 154 | Button(A4), // Middle 155 | Button(A3), // Index 156 | Button(A2), // Near Thumb 157 | Button(A1), // Center Thumb 158 | Button(A0), // Far Thumb 159 | }; 160 | 161 | //================================================== 162 | // ctb 163 | // A few timing constants 164 | const int HalfSec = 500; // for a half second delay 165 | 166 | // It seems it can happen that there are times when sending 3 raw 167 | // keys in a macro, the keys can arrive in the wrong order. So this was added 168 | // 2025-02-23 to see if it prevents that. Adjust as needed. 169 | // A long name for a short thing. The time between raw key sends in a macro . 170 | const int InterstitialDelay = 50; // for a 5/100 sec. delay (4/100 was not enough) 171 | 172 | // note - key debounce timing constants are not in this section. 173 | 174 | //====END=CONSTANTS=====================END=CONSTANTS============= 175 | 176 | //=====SETUP============================SETUP==================== 177 | // board specific messages 178 | void setup(void) 179 | { 180 | // Ensure software power reset pin in high 181 | pinMode(EnPin, OUTPUT); // Make pin an output, 182 | digitalWrite(EnPin, HIGH); // and activate pullup. 183 | // while (!Serial); // Required for Flora & Micro (and usb output) 184 | delay(HalfSec); 185 | 186 | if ( VERBOSE_MODE ) Serial.begin(115200); 187 | if ( VERBOSE_MODE ) Serial.println(F("Adafruit Bluefruit HID Chorder")); 188 | if ( VERBOSE_MODE ) Serial.println(F("---------------------------------------")); 189 | 190 | /* Initialise the module */ 191 | if ( VERBOSE_MODE ) Serial.print(F("Initialising the Bluefruit LE module: ")); 192 | 193 | if ( !ble.begin(VERBOSE_MODE) ) 194 | { 195 | error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?")); 196 | } 197 | if ( VERBOSE_MODE ) Serial.println( F("OK!") ); 198 | 199 | if ( FACTORYRESET_ENABLE ) 200 | { 201 | 202 | /* Perform a factory reset to make sure everything is in a known state */ 203 | if ( VERBOSE_MODE ) Serial.println(F("Performing a factory reset: ")); 204 | if ( ! ble.factoryReset() ){ 205 | error(F("Factory reset failed!")); 206 | } 207 | } 208 | 209 | /* Disable command echo from Bluefruit */ 210 | ble.echo(false); 211 | 212 | if ( VERBOSE_MODE ) Serial.println("Requesting Bluefruit info:"); 213 | /* Print Bluefruit information */ 214 | if ( VERBOSE_MODE ) ble.info(); 215 | 216 | /* Change the device name to make it easier to find */ 217 | if ( VERBOSE_MODE ) Serial.println(F("Setting device name to " DEVICENAME ": ")); 218 | if (! ble.sendCommandCheckOK(F( "AT+GAPDEVNAME="DEVICENAME )) ) { 219 | error(F("Could not set device name?")); 220 | } 221 | 222 | // GPD 2025-02-09 replaced this section with section from latest hid keyboiard example 223 | 224 | /* Enable HID Service */ 225 | if ( VERBOSE_MODE ) Serial.println(F("Enable HID Service (including Keyboard): ")); 226 | if ( ble.isVersionAtLeast(MINIMUM_FIRMWARE_VERSION) ) 227 | { 228 | if ( !ble.sendCommandCheckOK(F( "AT+BleHIDEn=On" ))) { 229 | error(F("Could not enable Keyboard")); 230 | } 231 | }else 232 | { 233 | if (! ble.sendCommandCheckOK(F( "AT+BleKeyboardEn=On" ))) { 234 | error(F("Could not enable Keyboard")); 235 | } 236 | } 237 | 238 | /* Add or remove service requires a reset */ 239 | if ( VERBOSE_MODE ) Serial.println(F("Performing a SW reset (service changes require a reset): ")); 240 | if (! ble.reset() ) { 241 | error(F("Couldn't reset??")); 242 | } 243 | 244 | // GPD end of replaced section 245 | 246 | 247 | String stringOne = String(0x45, HEX); 248 | 249 | if ( VERBOSE_MODE ) Serial.println(stringOne); 250 | } 251 | 252 | // used by processReading() 253 | // ctb 254 | enum State { 255 | PRESSING, 256 | RELEASING, 257 | }; 258 | 259 | State state = RELEASING; 260 | byte lastKeyState = 0; 261 | 262 | // used by sendKey() 263 | // ctb 264 | enum Mode { 265 | ALPHA, 266 | NUMSYM, 267 | FUNCTION 268 | }; 269 | 270 | bool isNumsymLocked = false; 271 | keymap_t latchMods = 0x00; // currently latched modKeys 272 | keymap_t modKeys = 0x00; // current modifyers ( L/Rshift,L/Ralt, L/Rctrl, L/Rgui ) 273 | 274 | Mode mode = ALPHA; 275 | 276 | // used by processREADING and loop 277 | byte previousStableReading = 0; 278 | byte currentStableReading = 0; 279 | long lastDebounceTime = 0; // the last time the output pin was toggled 280 | long debounceDelay = 10; // the debounce time; increase if the output flickers 281 | //=====RESET=====================RESET========================== 282 | void reset(){ 283 | mode = ALPHA; 284 | latchMods=0x00; 285 | modKeys = 0x00; 286 | isNumsymLocked = false; 287 | sendRawKeyUp(); 288 | } 289 | //=====SEND KEY====================SEND KEY======================== 290 | // used by processReading() 291 | // ctb 292 | void sendKey(byte keyState){ 293 | keymap_t theKey; 294 | // Determine the key based on the current mode's keymap 295 | if (mode == ALPHA) { 296 | theKey = keymap_default[keyState]; 297 | } else if (mode == NUMSYM) { 298 | theKey = keymap_numsym[keyState]; 299 | } else { 300 | theKey = keymap_function[keyState]; 301 | } 302 | 303 | switch (theKey) { 304 | // Handle mode switching - return immediately after the mode has changed 305 | // Handle basic mode switching 306 | case LATCH: 307 | if ( latchMods == 0x00 ) { // latch was not set, so set it current modKeys 308 | latchMods = modKeys; 309 | } else { 310 | latchMods = 0x00; // latch was set, so clear it. 311 | } 312 | break; 313 | case MODE_NUM: 314 | if (mode == NUMSYM) { 315 | mode = ALPHA; 316 | } else { 317 | mode = NUMSYM; 318 | } 319 | return; 320 | case MODE_FUNC: 321 | if (mode == FUNCTION) { 322 | mode = ALPHA; 323 | } else { 324 | mode = FUNCTION; 325 | } 326 | return; 327 | case MODE_RESET: 328 | reset(); 329 | return; 330 | case MODE_MRESET: 331 | reset(); 332 | digitalWrite(EnPin, LOW); // turn off 3.3v regulator enable. 333 | return; 334 | // something with a battery only 335 | case BAT_LVL: 336 | // get and send the battedy level, then 337 | // do a mode_reset 338 | gAsBattLvl(); 339 | reset(); 340 | return; 341 | case MODE_FRESET: 342 | sendFactoryReset(); 343 | return; 344 | // back to common code 345 | // Handle mode locks 346 | case MODE_NUMLCK: 347 | if (isNumsymLocked){ 348 | isNumsymLocked = false; 349 | mode = ALPHA; 350 | } else { 351 | isNumsymLocked = true; 352 | mode = NUMSYM; 353 | } 354 | return; 355 | // Handle modifier keys toggling 356 | case MOD_LCTRL: 357 | modKeys = modKeys ^ 0x01; 358 | return; 359 | case MOD_LSHIFT: 360 | modKeys = modKeys ^ 0x02; 361 | return; 362 | case MOD_LALT: 363 | modKeys = modKeys ^ 0x04; 364 | return; 365 | case MOD_LGUI: 366 | modKeys = modKeys ^ 0x08; 367 | return; 368 | case MOD_RCTRL: 369 | modKeys = modKeys ^ 0x10; 370 | return; 371 | case MOD_RSHIFT: 372 | modKeys = modKeys ^ 0x20; 373 | return; 374 | case MOD_RALT: 375 | modKeys = modKeys ^ 0x40; 376 | return; 377 | case MOD_RGUI: 378 | modKeys = modKeys ^ 0x80; 379 | return; 380 | // Handle special keys 381 | case MULTI_NumShift: 382 | if (mode == NUMSYM) { 383 | mode = ALPHA; 384 | } else { 385 | mode = NUMSYM; 386 | } 387 | modKeys = modKeys ^ 0x02; 388 | return; 389 | case MULTI_CtlAlt: 390 | modKeys = modKeys ^ 0x01; 391 | modKeys = modKeys ^ 0x04; 392 | return; 393 | /* Everything after this sends actual keys to the system; break rather than 394 | return since we want to reset the modifiers after these keys are sent. */ 395 | case MACRO_000: 396 | sendRawKey(0x00, ENUMKEY_0); 397 | sendRawKey(0x00, ENUMKEY_0); 398 | sendRawKey(0x00, ENUMKEY_0); 399 | break; 400 | case MACRO_00: 401 | sendRawKey(0x00, ENUMKEY_0); 402 | sendRawKey(0x00, ENUMKEY_0); 403 | break; 404 | case MACRO_quotes: 405 | sendRawKey(0x02, 0x34); 406 | delay(InterstitialDelay); 407 | sendRawKey(0x02, 0x34); 408 | delay(InterstitialDelay); 409 | sendRawKey(0x00, 0x50); 410 | break; 411 | case MACRO_parens: 412 | sendRawKey(0x02, 0x26); 413 | delay(InterstitialDelay); 414 | sendRawKey(0x02, 0x27); 415 | delay(InterstitialDelay); 416 | sendRawKey(0x00, 0x50); 417 | break; 418 | case MACRO_dollar: 419 | sendRawKey(0x02, 0x21); 420 | break; 421 | case MACRO_percent: 422 | sendRawKey(0x02, 0x22); 423 | break; 424 | case MACRO_ampersand: 425 | sendRawKey(0x02, 0x24); 426 | break; 427 | case MACRO_asterisk: 428 | sendRawKey(0x02, 0x25); 429 | break; 430 | case MACRO_question: 431 | sendRawKey(0x02, 0x38); 432 | break; 433 | case MACRO_plus: 434 | sendRawKey(0x02, 0x2E); 435 | break; 436 | case MACRO_openparen: 437 | sendRawKey(0x02, 0x26); 438 | break; 439 | case MACRO_closeparen: 440 | sendRawKey(0x02, 0x27); 441 | break; 442 | case MACRO_opencurly: 443 | sendRawKey(0x02, 0x2F); 444 | break; 445 | case MACRO_closecurly: 446 | sendRawKey(0x02, 0x30); 447 | break; 448 | case MACRO_1 : 449 | // er 450 | sendRawKey(modKeys, ENUMKEY_E); 451 | delay(InterstitialDelay); 452 | sendRawKey(latchMods, ENUMKEY_R); 453 | break; 454 | case MACRO_2: 455 | // th 456 | sendRawKey(modKeys, ENUMKEY_T); 457 | delay(InterstitialDelay); 458 | sendRawKey(latchMods, ENUMKEY_H); 459 | break; 460 | case MACRO_3: 461 | // an 462 | sendRawKey(modKeys, ENUMKEY_A); 463 | delay(InterstitialDelay); 464 | sendRawKey(latchMods, ENUMKEY_N); 465 | break; 466 | case MACRO_4: 467 | // in 468 | sendRawKey(modKeys, ENUMKEY_I); 469 | delay(InterstitialDelay); 470 | sendRawKey(latchMods, ENUMKEY_N); 471 | break; 472 | // macro test is a long string to confirm length of interstitial delay 473 | case MACRO_TEST: 474 | sendRawKey (modKeys, ENUMKEY_A); 475 | delay(InterstitialDelay); 476 | sendRawKey (modKeys, ENUMKEY_B); 477 | delay(InterstitialDelay); 478 | sendRawKey (modKeys, ENUMKEY_C); 479 | delay(InterstitialDelay); 480 | sendRawKey (modKeys, ENUMKEY_D); 481 | delay(InterstitialDelay); 482 | sendRawKey (modKeys, ENUMKEY_E); 483 | delay(InterstitialDelay); 484 | sendRawKey (modKeys, ENUMKEY_F); 485 | delay(InterstitialDelay); 486 | sendRawKey (modKeys, ENUMKEY_G); 487 | delay(InterstitialDelay); 488 | sendRawKey (modKeys, ENUMKEY_H); 489 | break; 490 | case MACRO_SHIFTDN: 491 | modKeys = MOD_LSHIFT; 492 | sendRawKeyDn (0x02, 0x00); 493 | break; 494 | // Handle Android specific keys 495 | case ANDROID_search: 496 | sendRawKey(0x04, 0x2C); 497 | break; 498 | case ANDROID_home: 499 | sendRawKey(0x04, 0x29); 500 | break; 501 | case ANDROID_menu: 502 | sendRawKey(0x10, 0x29); 503 | break; 504 | case ANDROID_back: 505 | sendRawKey(0x00, 0x29); 506 | break; 507 | case ANDROID_dpadcenter: 508 | sendRawKey(0x00, 0x5D); 509 | break; 510 | case MEDIA_playpause: 511 | sendControlKey("PLAYPAUSE"); 512 | break; 513 | case MEDIA_stop: 514 | sendControlKey("MEDIASTOP"); 515 | break; 516 | case MEDIA_next: 517 | sendControlKey("MEDIANEXT"); 518 | break; 519 | case MEDIA_previous: 520 | sendControlKey("MEDIAPREVIOUS"); 521 | break; 522 | case MEDIA_volup: 523 | sendControlKey("VOLUME+,500"); 524 | break; 525 | case MEDIA_voldn: 526 | sendControlKey("VOLUME-,500"); 527 | break; 528 | // Send the key 529 | default: 530 | sendRawKey(modKeys, theKey); 531 | break; 532 | } 533 | 534 | modKeys = latchMods; //sets modKeys to any currently latched mods, or 0x00 if none 535 | mode = ALPHA; 536 | // Reset the modKeys and mode based on 537 | if (isNumsymLocked){ 538 | mode = NUMSYM; 539 | } 540 | } 541 | 542 | //======SEND RAW KEY====================SEND RAW KEY================ 543 | // ctb 544 | // used in sendKey() 545 | // 546 | // new sendRawKey to make sure all is working as expected 547 | // before trying to impliment seporate watching for key down and key up 548 | // to allow host side key repeat 549 | // 550 | 551 | 552 | void sendRawKey(char modKey, char rawKey){ 553 | sendRawKeyDn(modKey, rawKey); 554 | sendRawKeyUp(); 555 | } 556 | 557 | //======SEND RAW KEY DOWN===============SEND RAW KEY DOWN============ 558 | // connectivity specific - This is for BT/BLE 559 | // used in sendRawKey() 560 | // 561 | 562 | void sendRawKeyDn(char modKey, char rawKey){ 563 | // Format for Bluefruit Feather is MOD-00-KEY. 564 | // Plan: use print to only print the last 2 ch so that we get 2 char 565 | // String keys = String(modKey, HEX) + "-00-" + String(rawKey, HEX); 566 | 567 | 568 | // pad & trim modKey to ensure 2 digit hexidecimal is sent 569 | String tmpModKey = "00" + String(modKey, HEX); 570 | int modKeyLen = tmpModKey.length() - 2; 571 | // pad & trim rawKey to ensure 2 digit hexidecimal is sent 572 | String tmpRawKey = "00" + String(rawKey, HEX); 573 | int rawKeyLen = tmpRawKey.length() - 2; 574 | 575 | ble.print("AT+BLEKEYBOARDCODE="); 576 | ble.println(String(&tmpModKey[modKeyLen]) + "-00-" + String(&tmpRawKey[rawKeyLen])); 577 | 578 | 579 | } 580 | 581 | //======SEND RAW KEY UP==============SEND RAW KEY UP================== 582 | // connectivity specific - This is for BT/BLE 583 | // used in sendRawKey() and sendString() 584 | // 585 | void sendRawKeyUp(){ 586 | ble.println("AT+BLEKEYBOARDCODE=00-00"); 587 | } 588 | //======SEND STRING============SEND STRING========================== 589 | // connectivity specific - This is for BT/BLE 590 | // Currently this is only for testing, it was temporarily added to MRESET 591 | // 592 | void sendString(String StringOut){ 593 | // 594 | ble.print("AT+BleKeyboard="); 595 | ble.println(StringOut); 596 | sendRawKeyUp(); // just in case as there have been some odd key repeats happening. 597 | } 598 | 599 | //======SEND MOUSE KEY=====SEND MOUSE KEY=========================== 600 | // connectivity specific - This is for BT/BLE 601 | // 602 | void sendMouseKey(String MouseKey){ 603 | ble.print("AT+BleHidMouseButton="); 604 | ble.println(MouseKey); 605 | delay(HalfSec); 606 | ble.println("AT+BleHidMouseButton=0"); 607 | } 608 | //======SEND CONTROL KEY============SEND CONTROL KEY================== 609 | // connectivity specific - This is for BT/BLE 610 | // used in sendKey() 611 | // 612 | void sendControlKey(String cntrlName){ 613 | // note: for Volume +/- and the few other keys that take a time to hold, simply add it into the string 614 | // for example: 615 | // sendControlKey("VOLUME+,500") 616 | // will send Volume up and hold it for half a second 617 | ble.print("AT+BleHidControlKey="); 618 | ble.println(cntrlName); 619 | } 620 | //======GET AND SEND BATTERY LEVEL================================== 621 | // board specific this is for BLE feather 622 | // note needs #define VBATPIN A9 which is up top with other defines. 623 | void gAsBattLvl() { 624 | float measuredvbat = analogRead(VBATPIN); 625 | measuredvbat *= 2; // we divided by 2, so multiply back 626 | measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage 627 | measuredvbat /= 1024; // convert to voltage 628 | sendString( " Kbd Batt: " ); 629 | sendString( String(measuredvbat) ); 630 | sendString( "volts. " ); 631 | } 632 | //======SEND FACTORY RESET============SEND FACTORY RESET=============== 633 | // board specific - This is for BLE feather 634 | // Factory Reset will clear the Bluetooth known hosts table 635 | void sendFactoryReset(){ 636 | // Perform a factory reset to make sure everything is in a known state 637 | if ( VERBOSE_MODE ) Serial.println(F("Performing a factory reset: ")); 638 | sendString( " Performing Factory Reset, You will need to pair the device after..."); 639 | if ( ! ble.factoryReset() ){ 640 | error(F("Factory reset failed!")); 641 | } 642 | } 643 | //=====PROCESS READING==================PROCESS READING=============== 644 | // ctb 645 | // used in loop() 646 | // 647 | // check if was pressing chord and now releasing, change to releasing, 648 | // then send the key 649 | // if chord was not pressing and now is, change to pressing 650 | // 651 | void processReading(){ 652 | switch (state) { 653 | case PRESSING: 654 | if (previousStableReading & ~currentStableReading) { 655 | state = RELEASING; 656 | sendKey(previousStableReading); 657 | } 658 | break; 659 | 660 | case RELEASING: 661 | if (currentStableReading & ~previousStableReading) { 662 | state = PRESSING; 663 | } 664 | break; 665 | } 666 | } 667 | 668 | //========LOOP=========================LOOP================== 669 | // ctb 670 | void loop() { 671 | // Build the current key state. 672 | byte keyState = 0, mask = 1; 673 | for (int i = 0; i < 7; i++) { 674 | if (switch_pins[i].isDown()) keyState |= mask; 675 | mask <<= 1; 676 | } 677 | 678 | if (lastKeyState != keyState) { 679 | lastDebounceTime = millis(); 680 | } 681 | 682 | if ((millis() - lastDebounceTime) > debounceDelay) { 683 | // whatever the reading is at, it's been there for longer 684 | // than the debounce delay, so take it as the actual current state: 685 | currentStableReading = keyState; 686 | } 687 | 688 | if (previousStableReading != currentStableReading) { 689 | processReading(); 690 | previousStableReading = currentStableReading; 691 | } 692 | 693 | lastKeyState = keyState; 694 | } 695 | -------------------------------------------------------------------------------- /FeatherChorder/KeyCodes.h: -------------------------------------------------------------------------------- 1 | // KeyCodes.h 2 | // Split out from main code for FeatherChorder.ino 3 | // If using the feather 32u4 bluefruit then see 4 | // https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-services 5 | // for all posible codes (in case something has been added). 6 | // - Greg 7 | 8 | /***************************************** 9 | * Possible keycodes in the chord tables * 10 | *****************************************/ 11 | enum keycodes { 12 | /* first part is simply an enumeration of the allowed scan-codes 13 | used for USB HID devices */ 14 | ENUMKEY__, // 0x00 Unused key-binding 15 | ENUMKEY_errorRollOver, 16 | ENUMKEY_POSTfail, 17 | ENUMKEY_errorUndefined, 18 | ENUMKEY_A, // 0x04 19 | ENUMKEY_B, 20 | ENUMKEY_C, 21 | ENUMKEY_D, 22 | ENUMKEY_E, 23 | ENUMKEY_F, 24 | ENUMKEY_G, 25 | ENUMKEY_H, 26 | ENUMKEY_I, 27 | ENUMKEY_J, 28 | ENUMKEY_K, 29 | ENUMKEY_L, 30 | ENUMKEY_M, // 0x10 31 | ENUMKEY_N, 32 | ENUMKEY_O, 33 | ENUMKEY_P, 34 | ENUMKEY_Q, 35 | ENUMKEY_R, 36 | ENUMKEY_S, 37 | ENUMKEY_T, 38 | ENUMKEY_U, 39 | ENUMKEY_V, 40 | ENUMKEY_W, 41 | ENUMKEY_X, 42 | ENUMKEY_Y, 43 | ENUMKEY_Z, 44 | ENUMKEY_1, 45 | ENUMKEY_2, 46 | ENUMKEY_3, // 0x20 47 | ENUMKEY_4, 48 | ENUMKEY_5, 49 | ENUMKEY_6, 50 | ENUMKEY_7, 51 | ENUMKEY_8, 52 | ENUMKEY_9, 53 | ENUMKEY_0, // 0x27 54 | ENUMKEY_enter, // 0x28 55 | ENUMKEY_esc, // 0x29 56 | ENUMKEY_bckspc, // 0x2A backspace 57 | ENUMKEY_tab, // 0x2B 58 | ENUMKEY_spc, // 0x2C space 59 | ENUMKEY_minus, // 0x2D - (and _) 60 | ENUMKEY_equal, // 0x2E = (and +) 61 | ENUMKEY_lbr, // 0x2F [ 62 | ENUMKEY_rbr, // 0x30 ] 63 | ENUMKEY_bckslsh, // 0x31 \ (and |) 64 | ENUMKEY_hash, // 0x32 Non-US # and ~ 65 | ENUMKEY_smcol, // 0x33 ; (and :) 66 | ENUMKEY_ping, // 0x34 ' and " 67 | ENUMKEY_grave, // 0x35 Grave accent and tilde 68 | ENUMKEY_comma, // 0x36 , (and <) 69 | ENUMKEY_dot, // 0x37 . (and >) 70 | ENUMKEY_slash, // 0x38 / (and ?) 71 | ENUMKEY_cpslck, // 0x39 capslock 72 | ENUMKEY_F1, 73 | ENUMKEY_F2, 74 | ENUMKEY_F3, 75 | ENUMKEY_F4, 76 | ENUMKEY_F5, 77 | ENUMKEY_F6, 78 | ENUMKEY_F7, // 0x40 79 | ENUMKEY_F8, 80 | ENUMKEY_F9, 81 | ENUMKEY_F10, 82 | ENUMKEY_F11, 83 | ENUMKEY_F12, 84 | ENUMKEY_PrtScr, 85 | ENUMKEY_scrlck, 86 | ENUMKEY_break, 87 | ENUMKEY_ins, 88 | ENUMKEY_home, 89 | ENUMKEY_pgup, 90 | ENUMKEY_del, 91 | ENUMKEY_end, 92 | ENUMKEY_pgdn, 93 | ENUMKEY_rarr, 94 | ENUMKEY_larr, // 0x50 95 | ENUMKEY_darr, 96 | ENUMKEY_uarr, 97 | ENUMKEY_numlock, 98 | ENUMKEY_KPslash, 99 | ENUMKEY_KPast, 100 | ENUMKEY_KPminus, 101 | ENUMKEY_KPplus, 102 | ENUMKEY_KPenter, 103 | ENUMKEY_KP1, 104 | ENUMKEY_KP2, 105 | ENUMKEY_KP3, 106 | ENUMKEY_KP4, 107 | ENUMKEY_KP5, // 0x5E 108 | ENUMKEY_KP6, 109 | ENUMKEY_KP7, 110 | ENUMKEY_KP8, // 0x60 111 | ENUMKEY_KP9, 112 | ENUMKEY_KP0, 113 | ENUMKEY_KPcomma, 114 | ENUMKEY_Euro2, 115 | 116 | /* The following are not standard USB HID, but are the modifier keys, 117 | handeled specially in decoding and mapped to the modifier byte in 118 | the USB report */ 119 | DIV_Mods, 120 | MOD_LCTRL, // 0x01 ? removed '=DIV_Mods' that I think was in error 121 | MOD_LSHIFT, // 0x02 122 | MOD_LALT, // 0x04 123 | MOD_LGUI, // 0x08 124 | MOD_RCTRL, // 0x10 125 | MOD_RSHIFT, // 0x20 126 | MOD_RALT, // 0x40 127 | MOD_RGUI, // 0x80 128 | 129 | /* Next comes the mode change codes */ 130 | DIV_Modes, 131 | MODE_RESET, // Reset (Default mode) ? removed '=DIV_Mods' 132 | MODE_MRESET, // Master Reset and ID 133 | MODE_FRESET, // do a factory reset - For BLE Feather this clears 134 | // all BT paring info). Should be on something hard 135 | // to reach. 136 | MODE_NUM, // Number/symbols mode 137 | MODE_NUMLCK, // Number/symbols lock 138 | MODE_FUNC, // Function key mode 139 | MODE_FUNCLCK, // Function key lock 140 | 141 | /* Then a special mode for both num/sym and shift */ 142 | DIV_Multi, 143 | MULTI_NumShift=DIV_Multi, 144 | MULTI_CtlAlt, 145 | 146 | /* media keys for testing */ 147 | MEDIA_playpause, // PLAYPAUSE 148 | MEDIA_next, // MEDIANEXT 149 | MEDIA_previous, // MEDIAPREVIOUS 150 | MEDIA_stop, // MEDIASTOP 151 | MEDIA_volup, // Volume up 152 | MEDIA_voldn, // Volume dn 153 | 154 | /* And finally macros, that generate multiple key presses */ 155 | DIV_Macro, 156 | MACRO_000, // 000 157 | MACRO_00, // 00 158 | MACRO_quotes, // "" and left arrow 159 | MACRO_parens, // () and left arrow 160 | MACRO_dollar, // aka, FORCE_LSHIFT|KEY_4 161 | MACRO_percent, // aka, FORCE_LSHIFT|KEY_5 162 | MACRO_ampersand, // aka, FORCE_LSHIFT|KEY_7 163 | MACRO_asterisk, // aka, FORCE_LSHIFT|KEY_8 164 | MACRO_question, // aka, FORCE_LSHIFT|KEY_slash 165 | MACRO_plus, // aka, FORCE_LSHIFT|KEY_equal 166 | MACRO_openparen, // aka, FORCE_LSHIFT|KEY_9 167 | MACRO_closeparen, // aka, FORCE_LSHIFT|KEY_0 168 | MACRO_opencurly, // aka, FORCE_LSHIFT|lbr 169 | MACRO_closecurly, // aka, FORCE_LSHIFT|rbr 170 | ANDROID_search, // aka, ALT|KEY_spc 171 | ANDROID_home, // aka, ALT|KEY_esc 172 | ANDROID_menu, // aka, CTRL|KEY_esc 173 | ANDROID_back, // aka, KEY_esc with NO MODS 174 | ANDROID_dpadcenter, // aka, KEY_KP5 with NO MODS 175 | 176 | /* Trying some multi-key macros on the 4 unused keys */ 177 | MACRO_1, 178 | MACRO_2, 179 | MACRO_3, 180 | MACRO_4, 181 | MACRO_TEST, // a - h to test interstitial delay 182 | MACRO_SHIFTDN, // try for shift down 183 | /* Some new macros for a few BT functions */ 184 | BAT_LVL, // print the batter level of the LiPo 185 | /* and last of all latch (I can't bring myself to call it "latchkey") */ 186 | LATCH, 187 | DIV_Last 188 | }; 189 | 190 | const int RAW_LGUI = 0xE3; 191 | // end of KeyCodes.h 192 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 Charles Chen and Greg Priest-Dorman 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chorder input device. See: https://www.chorder.org/wiki/doku.php/featherchorder 2 | # This is a version built using the Feather 32u4 Bluefruit (https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/overview). 3 | # Please note: The adafruit software will offer firmware beyond 0.7.7, but those introduce issues and are not listed on the page for this board. Don't flash higher than 0.7.7. 4 | # Chording charts and some basic lessons are also up at the above URL 5 | 6 | --------------------------------------------------------------------------------