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.

StenoFW.ino 12KB

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