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

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
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. }