diff options
author | brickbots <rich@brickbots.com> | 2020-03-22 06:06:16 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-23 00:06:16 +1100 |
commit | bfb2f8e0a8f809374fdec102eb02c3bce46a14ee (patch) | |
tree | 2bab982f9b1cc9184b58ca9764d1ba09e133335c | |
parent | d8f3c28a3786e7888fe3157c173845107c3ccc95 (diff) | |
download | qmk_firmware-bfb2f8e0a8f809374fdec102eb02c3bce46a14ee.tar.gz qmk_firmware-bfb2f8e0a8f809374fdec102eb02c3bce46a14ee.zip |
Add Word Per Minute calculation feature (#8054)
* Add Word Per Minute calculation feature
* Fix copyright info
* Remove header from quantum.c, setup overloadable keycode inclusion for WPM, update docs
* Simplify logic for keycode filtering
* Adding link from summary to wpm_feature info
* Update docs/feature_wpm.md
Typo in function prototype example in docs
Co-Authored-By: James Young <18669334+noroadsleft@users.noreply.github.com>
* Add WPM transport via i2c
Co-authored-by: James Young <18669334+noroadsleft@users.noreply.github.com>
-rw-r--r-- | common_features.mk | 5 | ||||
-rw-r--r-- | docs/_summary.md | 1 | ||||
-rw-r--r-- | docs/feature_wpm.md | 25 | ||||
-rw-r--r-- | quantum/quantum.c | 10 | ||||
-rw-r--r-- | quantum/quantum.h | 4 | ||||
-rw-r--r-- | quantum/split_common/transport.c | 27 | ||||
-rw-r--r-- | quantum/wpm.c | 67 | ||||
-rw-r--r-- | quantum/wpm.h | 30 |
8 files changed, 169 insertions, 0 deletions
diff --git a/common_features.mk b/common_features.mk index 269ca2b13..50b1127dc 100644 --- a/common_features.mk +++ b/common_features.mk | |||
@@ -322,6 +322,11 @@ ifeq ($(strip $(USB_HID_ENABLE)), yes) | |||
322 | include $(TMK_DIR)/protocol/usb_hid.mk | 322 | include $(TMK_DIR)/protocol/usb_hid.mk |
323 | endif | 323 | endif |
324 | 324 | ||
325 | ifeq ($(strip $(WPM_ENABLE)), yes) | ||
326 | SRC += $(QUANTUM_DIR)/wpm.c | ||
327 | OPT_DEFS += -DWPM_ENABLE | ||
328 | endif | ||
329 | |||
325 | ifeq ($(strip $(ENCODER_ENABLE)), yes) | 330 | ifeq ($(strip $(ENCODER_ENABLE)), yes) |
326 | SRC += $(QUANTUM_DIR)/encoder.c | 331 | SRC += $(QUANTUM_DIR)/encoder.c |
327 | OPT_DEFS += -DENCODER_ENABLE | 332 | OPT_DEFS += -DENCODER_ENABLE |
diff --git a/docs/_summary.md b/docs/_summary.md index d6186bbf9..4a6e6996e 100644 --- a/docs/_summary.md +++ b/docs/_summary.md | |||
@@ -80,6 +80,7 @@ | |||
80 | * [Terminal](feature_terminal.md) | 80 | * [Terminal](feature_terminal.md) |
81 | * [Unicode](feature_unicode.md) | 81 | * [Unicode](feature_unicode.md) |
82 | * [Userspace](feature_userspace.md) | 82 | * [Userspace](feature_userspace.md) |
83 | * [WPM Calculation](feature_wpm.md) | ||
83 | 84 | ||
84 | * Hardware Features | 85 | * Hardware Features |
85 | * Displays | 86 | * Displays |
diff --git a/docs/feature_wpm.md b/docs/feature_wpm.md new file mode 100644 index 000000000..12dd08057 --- /dev/null +++ b/docs/feature_wpm.md | |||
@@ -0,0 +1,25 @@ | |||
1 | # Word Per Minute (WPM) Calculcation | ||
2 | |||
3 | The WPM feature uses time between keystrokes to compute a rolling average words | ||
4 | per minute rate and makes this available for various uses. | ||
5 | |||
6 | Enable the WPM system by adding this to your `rules.mk`: | ||
7 | |||
8 | WPM_ENABLE = yes | ||
9 | |||
10 | For split keyboards using soft serial, the computed WPM | ||
11 | score will be available on the master AND slave half. | ||
12 | |||
13 | ## Public Functions | ||
14 | |||
15 | `uint8_t get_current_wpm(void);` | ||
16 | This function returns the current WPM as an unsigned integer. | ||
17 | |||
18 | |||
19 | ## Customized keys for WPM calc | ||
20 | |||
21 | By default, the WPM score only includes letters, numbers, space and some | ||
22 | punctuation. If you want to change the set of characters considered as part of | ||
23 | the WPM calculation, you can implement `wpm_keycode_user(uint16_t keycode)` | ||
24 | and return true for any characters you would like included in the calculation, | ||
25 | or false to not count that particular keycode. | ||
diff --git a/quantum/quantum.c b/quantum/quantum.c index 749a08eea..49767819d 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c | |||
@@ -192,6 +192,12 @@ bool process_record_quantum(keyrecord_t *record) { | |||
192 | } | 192 | } |
193 | #endif | 193 | #endif |
194 | 194 | ||
195 | #ifdef WPM_ENABLE | ||
196 | if (record->event.pressed) { | ||
197 | update_wpm(keycode); | ||
198 | } | ||
199 | #endif | ||
200 | |||
195 | #ifdef TAP_DANCE_ENABLE | 201 | #ifdef TAP_DANCE_ENABLE |
196 | preprocess_tap_dance(keycode, record); | 202 | preprocess_tap_dance(keycode, record); |
197 | #endif | 203 | #endif |
@@ -645,6 +651,10 @@ void matrix_scan_quantum() { | |||
645 | encoder_read(); | 651 | encoder_read(); |
646 | #endif | 652 | #endif |
647 | 653 | ||
654 | #ifdef WPM_ENABLE | ||
655 | decay_wpm(); | ||
656 | #endif | ||
657 | |||
648 | #ifdef HAPTIC_ENABLE | 658 | #ifdef HAPTIC_ENABLE |
649 | haptic_task(); | 659 | haptic_task(); |
650 | #endif | 660 | #endif |
diff --git a/quantum/quantum.h b/quantum/quantum.h index d03ba5942..191407fab 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h | |||
@@ -178,6 +178,10 @@ extern layer_state_t layer_state; | |||
178 | # include "via.h" | 178 | # include "via.h" |
179 | #endif | 179 | #endif |
180 | 180 | ||
181 | #ifdef WPM_ENABLE | ||
182 | # include "wpm.h" | ||
183 | #endif | ||
184 | |||
181 | // Function substitutions to ease GPIO manipulation | 185 | // Function substitutions to ease GPIO manipulation |
182 | #if defined(__AVR__) | 186 | #if defined(__AVR__) |
183 | typedef uint8_t pin_t; | 187 | typedef uint8_t pin_t; |
diff --git a/quantum/split_common/transport.c b/quantum/split_common/transport.c index ab421adc4..3234a3ef5 100644 --- a/quantum/split_common/transport.c +++ b/quantum/split_common/transport.c | |||
@@ -35,6 +35,9 @@ typedef struct _I2C_slave_buffer_t { | |||
35 | # ifdef ENCODER_ENABLE | 35 | # ifdef ENCODER_ENABLE |
36 | uint8_t encoder_state[NUMBER_OF_ENCODERS]; | 36 | uint8_t encoder_state[NUMBER_OF_ENCODERS]; |
37 | # endif | 37 | # endif |
38 | # ifdef WPM_ENABLE | ||
39 | uint8_t current_wpm; | ||
40 | # endif | ||
38 | } I2C_slave_buffer_t; | 41 | } I2C_slave_buffer_t; |
39 | 42 | ||
40 | static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; | 43 | static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; |
@@ -43,6 +46,7 @@ static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_re | |||
43 | # define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync) | 46 | # define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync) |
44 | # define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix) | 47 | # define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix) |
45 | # define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) | 48 | # define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) |
49 | # define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm) | ||
46 | 50 | ||
47 | # define TIMEOUT 100 | 51 | # define TIMEOUT 100 |
48 | 52 | ||
@@ -79,6 +83,14 @@ bool transport_master(matrix_row_t matrix[]) { | |||
79 | encoder_update_raw(i2c_buffer->encoder_state); | 83 | encoder_update_raw(i2c_buffer->encoder_state); |
80 | # endif | 84 | # endif |
81 | 85 | ||
86 | # ifdef WPM_ENABLE | ||
87 | uint8_t current_wpm = get_current_wpm(); | ||
88 | if(current_wpm != i2c_buffer->current_wpm) { | ||
89 | if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)¤t_wpm, sizeof(current_wpm), TIMEOUT) >= 0) { | ||
90 | i2c_buffer->current_wpm = current_wpm; | ||
91 | } | ||
92 | } | ||
93 | # endif | ||
82 | return true; | 94 | return true; |
83 | } | 95 | } |
84 | 96 | ||
@@ -102,6 +114,10 @@ void transport_slave(matrix_row_t matrix[]) { | |||
102 | # ifdef ENCODER_ENABLE | 114 | # ifdef ENCODER_ENABLE |
103 | encoder_state_raw(i2c_buffer->encoder_state); | 115 | encoder_state_raw(i2c_buffer->encoder_state); |
104 | # endif | 116 | # endif |
117 | |||
118 | # ifdef WPM_ENABLE | ||
119 | set_current_wpm(i2c_buffer->current_wpm); | ||
120 | # endif | ||
105 | } | 121 | } |
106 | 122 | ||
107 | void transport_master_init(void) { i2c_init(); } | 123 | void transport_master_init(void) { i2c_init(); } |
@@ -126,6 +142,9 @@ typedef struct _Serial_m2s_buffer_t { | |||
126 | # ifdef BACKLIGHT_ENABLE | 142 | # ifdef BACKLIGHT_ENABLE |
127 | uint8_t backlight_level; | 143 | uint8_t backlight_level; |
128 | # endif | 144 | # endif |
145 | # ifdef WPM_ENABLE | ||
146 | uint8_t current_wpm; | ||
147 | # endif | ||
129 | } Serial_m2s_buffer_t; | 148 | } Serial_m2s_buffer_t; |
130 | 149 | ||
131 | # if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) | 150 | # if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) |
@@ -228,6 +247,10 @@ bool transport_master(matrix_row_t matrix[]) { | |||
228 | encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state); | 247 | encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state); |
229 | # endif | 248 | # endif |
230 | 249 | ||
250 | # ifdef WPM_ENABLE | ||
251 | // Write wpm to slave | ||
252 | serial_m2s_buffer.current_wpm = get_current_wpm(); | ||
253 | # endif | ||
231 | return true; | 254 | return true; |
232 | } | 255 | } |
233 | 256 | ||
@@ -244,6 +267,10 @@ void transport_slave(matrix_row_t matrix[]) { | |||
244 | # ifdef ENCODER_ENABLE | 267 | # ifdef ENCODER_ENABLE |
245 | encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state); | 268 | encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state); |
246 | # endif | 269 | # endif |
270 | |||
271 | # ifdef WPM_ENABLE | ||
272 | set_current_wpm(serial_m2s_buffer.current_wpm); | ||
273 | # endif | ||
247 | } | 274 | } |
248 | 275 | ||
249 | #endif | 276 | #endif |
diff --git a/quantum/wpm.c b/quantum/wpm.c new file mode 100644 index 000000000..d4c971f31 --- /dev/null +++ b/quantum/wpm.c | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | * Copyright 2020 Richard Sutherland (rich@brickbots.com) | ||
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 2 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 | |||
18 | #include "wpm.h" | ||
19 | |||
20 | //WPM Stuff | ||
21 | static uint8_t current_wpm = 0; | ||
22 | static uint8_t latest_wpm = 0; | ||
23 | static uint16_t wpm_timer = 0; | ||
24 | |||
25 | //This smoothing is 40 keystrokes | ||
26 | static const float wpm_smoothing = 0.0487; | ||
27 | |||
28 | void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; } | ||
29 | |||
30 | uint8_t get_current_wpm(void) { return current_wpm; } | ||
31 | |||
32 | bool wpm_keycode(uint16_t keycode) { return wpm_keycode_kb(keycode); } | ||
33 | |||
34 | __attribute__((weak)) bool wpm_keycode_kb(uint16_t keycode) { return wpm_keycode_user(keycode); } | ||
35 | |||
36 | __attribute__((weak)) bool wpm_keycode_user(uint16_t keycode) { | ||
37 | |||
38 | if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX) || (keycode >= QK_MODS && keycode <= QK_MODS_MAX)) { | ||
39 | keycode = keycode & 0xFF; | ||
40 | } else if (keycode > 0xFF) { | ||
41 | keycode = 0; | ||
42 | } | ||
43 | if((keycode >= KC_A && keycode <= KC_0) || (keycode >= KC_TAB && keycode <= KC_SLASH)) { | ||
44 | return true; | ||
45 | } | ||
46 | |||
47 | return false; | ||
48 | } | ||
49 | |||
50 | |||
51 | void update_wpm(uint16_t keycode) { | ||
52 | if(wpm_keycode(keycode)) { | ||
53 | if(wpm_timer > 0) { | ||
54 | latest_wpm = 60000 / timer_elapsed(wpm_timer) / 5; | ||
55 | current_wpm = (latest_wpm - current_wpm) * wpm_smoothing + current_wpm; | ||
56 | } | ||
57 | wpm_timer = timer_read(); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | void decay_wpm(void) { | ||
62 | if (timer_elapsed(wpm_timer) > 1000) { | ||
63 | current_wpm = (0 - current_wpm) * wpm_smoothing + | ||
64 | current_wpm; | ||
65 | wpm_timer = timer_read(); | ||
66 | } | ||
67 | } | ||
diff --git a/quantum/wpm.h b/quantum/wpm.h new file mode 100644 index 000000000..fa0b6d128 --- /dev/null +++ b/quantum/wpm.h | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * Copyright 2020 Richard Sutherland (rich@brickbots.com) | ||
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 2 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 | |||
18 | #pragma once | ||
19 | |||
20 | #include "quantum.h" | ||
21 | |||
22 | bool wpm_keycode(uint16_t keycode); | ||
23 | bool wpm_keycode_kb(uint16_t keycode); | ||
24 | bool wpm_keycode_user(uint16_t keycode); | ||
25 | |||
26 | void set_current_wpm(uint8_t); | ||
27 | uint8_t get_current_wpm(void); | ||
28 | void update_wpm(uint16_t); | ||
29 | |||
30 | void decay_wpm(void); | ||