StenoFW is a firmware for StenoBoard keyboards.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

kolea.ino 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /**
  2. * StenoFW is a firmware for Stenoboard keyboards.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. * Copyright 2014 Emanuele Caruso. See LICENSE.txt for details.
  18. */
  19. /**
  20. * Matrix modified for the Kolea keyboard.
  21. */
  22. #define ROWS 4
  23. #define COLS 11
  24. /* The following matrix is shown here for reference only.
  25. char keys[ROWS][COLS] = {
  26. {' ', '2', '3', '4', '5', ' ', '7', '8', '9', '0', ' '},
  27. {' ', 'q', 'w', 'e', 'r', 't', 'u', 'i', 'o', 'p', '['},
  28. {' ', 'a', 's', 'd', 'f', 'g', 'j', 'k', 'l', ';', '\''},
  29. {' ', ' ', ' ', 'c', 'v', ' ', 'n', 'm', ' ', ' ', ' '}
  30. };*/
  31. // Configuration variables
  32. int rowPins[ROWS] = {4, 5, 6, 7};
  33. int colPins[COLS] = {8, 9, 10, 11, 12, 14, 15, 16, 18, 19, 20};
  34. int ledPin = 3;
  35. long debounceMillis = 20;
  36. // Keyboard state variables
  37. boolean isStrokeInProgress = false;
  38. boolean currentChord[ROWS][COLS];
  39. boolean currentKeyReadings[ROWS][COLS];
  40. boolean debouncingKeys[ROWS][COLS];
  41. unsigned long debouncingMicros[ROWS][COLS];
  42. // Other state variables
  43. int ledIntensity = 1; // Min 0 - Max 255
  44. // Protocol state
  45. #define GEMINI 0
  46. #define TXBOLT 1
  47. #define NKRO 2
  48. int protocol = GEMINI;
  49. // This is called when the keyboard is connected
  50. void setup() {
  51. Keyboard.begin();
  52. Serial.begin(9600);
  53. for (int i = 0; i < COLS; i++)
  54. pinMode(colPins[i], INPUT_PULLUP);
  55. for (int i = 0; i < ROWS; i++) {
  56. pinMode(rowPins[i], OUTPUT);
  57. digitalWrite(rowPins[i], HIGH);
  58. }
  59. pinMode(ledPin, OUTPUT);
  60. analogWrite(ledPin, ledIntensity);
  61. clearBooleanMatrixes();
  62. }
  63. // Read key states and handle all chord events
  64. void loop() {
  65. readKeys();
  66. boolean isAnyKeyPressed = true;
  67. // If stroke is not in progress, check debouncing keys
  68. if (!isStrokeInProgress) {
  69. checkAlreadyDebouncingKeys();
  70. if (!isStrokeInProgress) checkNewDebouncingKeys();
  71. }
  72. // If any key was pressed, record all pressed keys
  73. if (isStrokeInProgress) {
  74. isAnyKeyPressed = recordCurrentKeys();
  75. }
  76. // If all keys have been released, send the chord and reset global state
  77. if (!isAnyKeyPressed) {
  78. sendChord();
  79. clearBooleanMatrixes();
  80. isStrokeInProgress = false;
  81. }
  82. }
  83. // Record all pressed keys into current chord. Return false if no key is currently pressed
  84. boolean recordCurrentKeys() {
  85. boolean isAnyKeyPressed = false;
  86. for (int i = 0; i < ROWS; i++) {
  87. for (int j = 0; j < COLS; j++) {
  88. if (currentKeyReadings[i][j] == true) {
  89. currentChord[i][j] = true;
  90. isAnyKeyPressed = true;
  91. }
  92. }
  93. }
  94. return isAnyKeyPressed;
  95. }
  96. // If a key is pressed, add it to debouncing keys and record the time
  97. void checkNewDebouncingKeys() {
  98. for (int i = 0; i < ROWS; i++) {
  99. for (int j = 0; j < COLS; j++) {
  100. if (currentKeyReadings[i][j] == true && debouncingKeys[i][j] == false) {
  101. debouncingKeys[i][j] = true;
  102. debouncingMicros[i][j] = micros();
  103. }
  104. }
  105. }
  106. }
  107. // Check already debouncing keys. If a key debounces, start chord recording.
  108. void checkAlreadyDebouncingKeys() {
  109. for (int i = 0; i < ROWS; i++) {
  110. for (int j = 0; j < COLS; j++) {
  111. if (debouncingKeys[i][j] == true && currentKeyReadings[i][j] == false) {
  112. debouncingKeys[i][j] = false;
  113. continue;
  114. }
  115. if (debouncingKeys[i][j] == true && micros() - debouncingMicros[i][j] / 1000 > debounceMillis) {
  116. isStrokeInProgress = true;
  117. currentChord[i][j] = true;
  118. return;
  119. }
  120. }
  121. }
  122. }
  123. // Set all values of all boolean matrixes to false
  124. void clearBooleanMatrixes() {
  125. clearBooleanMatrix(currentChord, false);
  126. clearBooleanMatrix(currentKeyReadings, false);
  127. clearBooleanMatrix(debouncingKeys, false);
  128. }
  129. // Set all values of the passed matrix to the given value
  130. void clearBooleanMatrix(boolean booleanMatrix[][COLS], boolean value) {
  131. for (int i = 0; i < ROWS; i++) {
  132. for (int j = 0; j < COLS; j++) {
  133. booleanMatrix[i][j] = value;
  134. }
  135. }
  136. }
  137. // Read all keys
  138. void readKeys() {
  139. for (int i = 0; i < ROWS; i++) {
  140. digitalWrite(rowPins[i], LOW);
  141. for (int j = 0; j < COLS; j++)
  142. currentKeyReadings[i][j] = digitalRead(colPins[j]) == LOW ? true : false;
  143. digitalWrite(rowPins[i], HIGH);
  144. }
  145. }
  146. // Send current chord using NKRO Keyboard emulation
  147. void sendChordNkro() {
  148. // QWERTY mapping
  149. char qwertyMapping[ROWS][COLS] = {
  150. {' ', '2', '3', '4', '5', ' ', '7', '8', '9', '0', ' '},
  151. {' ', 'q', 'w', 'e', 'r', 't', 'u', 'i', 'o', 'p', '['},
  152. {' ', 'a', 's', 'd', 'f', 'g', 'j', 'k', 'l', ';', '\''},
  153. {' ', ' ', ' ', 'c', 'v', ' ', 'n', 'm', ' ', ' ', ' '}
  154. };
  155. int keyCounter = 0;
  156. char qwertyKeys[ROWS * COLS];
  157. boolean firstKeyPressed = false;
  158. // Calculate qwerty keys array using qwertyMappings[][]
  159. for (int i = 0; i < ROWS; i++)
  160. for (int j = 0; j < COLS; j++)
  161. if (currentChord[i][j]) {
  162. qwertyKeys[keyCounter] = qwertyMapping[i][j];
  163. keyCounter++;
  164. }
  165. // Emulate keyboard key presses
  166. for (int i = 0; i < keyCounter; i++) {
  167. if (qwertyKeys[i] != ' ') {
  168. Keyboard.press(qwertyKeys[i]);
  169. if (!firstKeyPressed) firstKeyPressed = true;
  170. else Keyboard.release(qwertyKeys[i]);
  171. }
  172. }
  173. Keyboard.releaseAll();
  174. }
  175. // Send current chord over serial using the Gemini protocol.
  176. void sendChordGemini() {
  177. // Initialize chord bytes
  178. byte chordBytes[] = {B10000000, B0, B0, B0, B0, B0};
  179. // Byte 0
  180. //#
  181. if (currentChord[0][1] || currentChord[0][2] || currentChord[0][3] || currentChord[0][4] || currentChord[0][6] || currentChord[0][7] || currentChord[0][8] || currentChord[0][9]) {
  182. chordBytes[0] = B10000001;
  183. }
  184. // Byte 1
  185. //S
  186. if (currentChord[1][1] || currentChord[2][1]) {
  187. chordBytes[1] += B01000000;
  188. }
  189. //T
  190. if (currentChord[1][2]) {
  191. chordBytes[1] += B00010000;
  192. }
  193. //K
  194. if (currentChord[2][2]) {
  195. chordBytes[1] += B00001000;
  196. }
  197. //P
  198. if (currentChord[1][3]) {
  199. chordBytes[1] += B00000100;
  200. }
  201. //W
  202. if (currentChord[2][3]) {
  203. chordBytes[1] += B00000010;
  204. }
  205. //H
  206. if (currentChord[1][4]) {
  207. chordBytes[1] += B00000001;
  208. }
  209. // Byte 2
  210. //R
  211. if (currentChord[2][4]) {
  212. chordBytes[2] += B01000000;
  213. }
  214. //W
  215. if (currentChord[3][3]) {
  216. chordBytes[2] += B00100000;
  217. }
  218. //O
  219. if (currentChord[3][4]) {
  220. chordBytes[2] += B00010000;
  221. }
  222. //*
  223. if (currentChord[1][5] || currentChord[2][5]) {
  224. chordBytes[2] += B00001000;
  225. }
  226. // Byte 3
  227. //E
  228. if (currentChord[3][6]) {
  229. chordBytes[3] += B00001000;
  230. }
  231. //U
  232. if (currentChord[3][7]) {
  233. chordBytes[3] += B00000100;
  234. }
  235. //F
  236. if (currentChord[1][6]) {
  237. chordBytes[3] += B00000010;
  238. }
  239. //R
  240. if (currentChord[2][6]) {
  241. chordBytes[3] += B00000001;
  242. }
  243. // Byte 4
  244. //P
  245. if (currentChord[1][7]) {
  246. chordBytes[4] += B01000000;
  247. }
  248. //B
  249. if (currentChord[2][7]) {
  250. chordBytes[4] += B00100000;
  251. }
  252. //L
  253. if (currentChord[1][8]) {
  254. chordBytes[4] += B00010000;
  255. }
  256. //G
  257. if (currentChord[2][8]) {
  258. chordBytes[4] += B00001000;
  259. }
  260. //T
  261. if (currentChord[1][9]) {
  262. chordBytes[4] += B00000100;
  263. }
  264. //S
  265. if (currentChord[2][9]) {
  266. chordBytes[4] += B00000010;
  267. }
  268. //D
  269. if (currentChord[1][10]) {
  270. chordBytes[4] += B00000001;
  271. }
  272. // Byte 5
  273. //Z
  274. if (currentChord[2][10]) {
  275. chordBytes[5] += B00000001;
  276. }
  277. // Send chord bytes over serial
  278. for (int i = 0; i < 6; i++) {
  279. Serial.write(chordBytes[i]);
  280. }
  281. }
  282. void sendChordTxBolt() {
  283. byte chordBytes[] = {B0, B0, B0, B0, B0};
  284. int index = 0;
  285. // TX Bolt uses a variable length packet. Only those bytes that have active
  286. // keys are sent. The header bytes indicate which keys are being sent. They
  287. // must be sent in order. It is a good idea to send a zero after every packet.
  288. // 00XXXXXX 01XXXXXX 10XXXXXX 110XXXXX
  289. // HWPKTS UE*OAR GLBPRF #ZDST
  290. // byte 1
  291. // S-
  292. if (currentChord[1][1] || currentChord[2][1]) chordBytes[index] |= B00000001;
  293. // T-
  294. if (currentChord[1][2]) chordBytes[index] |= B00000010;
  295. // K-
  296. if (currentChord[2][2]) chordBytes[index] |= B00000100;
  297. // P-
  298. if (currentChord[1][3]) chordBytes[index] |= B00001000;
  299. // W-
  300. if (currentChord[2][3]) chordBytes[index] |= B00010000;
  301. // H-
  302. if (currentChord[1][4]) chordBytes[index] |= B00100000;
  303. // Increment the index if the current byte has any keys set.
  304. if (chordBytes[index]) index++;
  305. // byte 2
  306. // R-
  307. if (currentChord[2][4]) chordBytes[index] |= B01000001;
  308. // A
  309. if (currentChord[3][3]) chordBytes[index] |= B01000010;
  310. // O
  311. if (currentChord[3][4]) chordBytes[index] |= B01000100;
  312. // *
  313. if (currentChord[1][5] || currentChord[2][5]) chordBytes[index] |= B01001000;
  314. // E
  315. if (currentChord[3][6]) chordBytes[index] |= B01010000;
  316. // U
  317. if (currentChord[3][7]) chordBytes[index] |= B01100000;
  318. // Increment the index if the current byte has any keys set.
  319. if (chordBytes[index]) index++;
  320. // byte 3
  321. // -F
  322. if (currentChord[1][6]) chordBytes[index] |= B10000001;
  323. // -R
  324. if (currentChord[2][6]) chordBytes[index] |= B10000010;
  325. // -P
  326. if (currentChord[1][7]) chordBytes[index] |= B10000100;
  327. // -B
  328. if (currentChord[2][7]) chordBytes[index] |= B10001000;
  329. // -L
  330. if (currentChord[1][8]) chordBytes[index] |= B10010000;
  331. // -G
  332. if (currentChord[2][8]) chordBytes[index] |= B10100000;
  333. // Increment the index if the current byte has any keys set.
  334. if (chordBytes[index]) index++;
  335. // byte 4
  336. // -T
  337. if (currentChord[1][9]) chordBytes[index] |= B11000001;
  338. // -S
  339. if (currentChord[2][9]) chordBytes[index] |= B11000010;
  340. // -D
  341. if (currentChord[1][10]) chordBytes[index] |= B11000100;
  342. // -Z
  343. if (currentChord[2][10]) chordBytes[index] |= B11001000;
  344. // #
  345. if (currentChord[0][1] || currentChord[0][2] || currentChord[0][3] || currentChord[0][4] || currentChord[0][6] || currentChord[0][7] || currentChord[0][8] || currentChord[0][9]) chordBytes[index] |= B11010000;
  346. // Increment the index if the current byte has any keys set.
  347. if (chordBytes[index]) index++;
  348. // Now we have index bytes followed by a zero byte where 0 < index <= 4.
  349. index++; // Increment index to include the trailing zero byte.
  350. for (int i = 0; i < index; i++) {
  351. Serial.write(chordBytes[i]);
  352. }
  353. }
  354. // Send the chord using the current protocol. If there are fn keys
  355. // pressed, delegate to the corresponding function instead.
  356. // In future versions, there should also be a way to handle fn keys presses before
  357. // they are released, eg. for mouse emulation functionality or custom key presses.
  358. void sendChord() {
  359. // If fn keys have been pressed, delegate to corresponding method and return
  360. if (currentChord[1][0] && currentChord[2][0]) {
  361. fn1fn2();
  362. return;
  363. } else if (currentChord[1][0]) {
  364. fn1();
  365. return;
  366. } else if (currentChord[2][0]) {
  367. fn2();
  368. return;
  369. }
  370. if (protocol == NKRO) {
  371. sendChordNkro();
  372. } else if (protocol == GEMINI) {
  373. sendChordGemini();
  374. } else {
  375. sendChordTxBolt();
  376. }
  377. }
  378. // Fn1 functions
  379. //
  380. // This function is called when "fn1" key has been pressed, but not "fn2".
  381. // Tip: maybe it is better to avoid using "fn1" key alone in order to avoid
  382. // accidental activation?
  383. //
  384. // Current functions:
  385. // PH-PB -> Set NKRO Keyboard emulation mode
  386. // PH-G -> Set Gemini PR protocol mode
  387. // PH-B -> Set TX Bolt protocol mode
  388. void fn1() {
  389. // "PH" -> Set protocol
  390. if (currentChord[1][3] && currentChord[1][4]) {
  391. // "-PB" -> NKRO Keyboard
  392. if (currentChord[1][7] && currentChord[2][7]) {
  393. protocol = NKRO;
  394. }
  395. // "-G" -> Gemini PR
  396. else if (currentChord[2][8]) {
  397. protocol = GEMINI;
  398. }
  399. // "-B" -> TX Bolt
  400. else if (currentChord[2][7]) {
  401. protocol = TXBOLT;
  402. }
  403. }
  404. }
  405. // Fn2 functions
  406. //
  407. // This function is called when "fn2" key has been pressed, but not "fn1".
  408. // Tip: maybe it is better to avoid using "fn2" key alone in order to avoid
  409. // accidental activation?
  410. //
  411. // Current functions: none.
  412. void fn2() {
  413. }
  414. // Fn1-Fn2 functions
  415. //
  416. // This function is called when both "fn1" and "fn1" keys have been pressed.
  417. //
  418. // Current functions:
  419. // HR-P -> LED intensity up
  420. // HR-F -> LED intensity down
  421. void fn1fn2() {
  422. // "HR" -> Change LED intensity
  423. if (currentChord[1][4] && currentChord[2][4]) {
  424. // "-P" -> LED intensity up
  425. if (currentChord[1][7]) {
  426. if (ledIntensity == 0) ledIntensity +=1;
  427. else if(ledIntensity < 50) ledIntensity += 10;
  428. else ledIntensity += 30;
  429. if (ledIntensity > 255) ledIntensity = 0;
  430. analogWrite(ledPin, ledIntensity);
  431. }
  432. // "-F" -> LED intensity down
  433. if (currentChord[1][6]) {
  434. if(ledIntensity == 0) ledIntensity = 255;
  435. else if(ledIntensity < 50) ledIntensity -= 10;
  436. else ledIntensity -= 30;
  437. if (ledIntensity < 1) ledIntensity = 0;
  438. analogWrite(ledPin, ledIntensity);
  439. }
  440. }
  441. }