M common_features.mk => common_features.mk +5 -0
@@ 322,6 322,11 @@ ifeq ($(strip $(USB_HID_ENABLE)), yes)
include $(TMK_DIR)/protocol/usb_hid.mk
endif
+ifeq ($(strip $(WPM_ENABLE)), yes)
+ SRC += $(QUANTUM_DIR)/wpm.c
+ OPT_DEFS += -DWPM_ENABLE
+endif
+
ifeq ($(strip $(ENCODER_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/encoder.c
OPT_DEFS += -DENCODER_ENABLE
M docs/_summary.md => docs/_summary.md +1 -0
@@ 80,6 80,7 @@
* [Terminal](feature_terminal.md)
* [Unicode](feature_unicode.md)
* [Userspace](feature_userspace.md)
+ * [WPM Calculation](feature_wpm.md)
* Hardware Features
* Displays
A docs/feature_wpm.md => docs/feature_wpm.md +25 -0
@@ 0,0 1,25 @@
+# Word Per Minute (WPM) Calculcation
+
+The WPM feature uses time between keystrokes to compute a rolling average words
+per minute rate and makes this available for various uses.
+
+Enable the WPM system by adding this to your `rules.mk`:
+
+ WPM_ENABLE = yes
+
+For split keyboards using soft serial, the computed WPM
+score will be available on the master AND slave half.
+
+## Public Functions
+
+`uint8_t get_current_wpm(void);`
+This function returns the current WPM as an unsigned integer.
+
+
+## Customized keys for WPM calc
+
+By default, the WPM score only includes letters, numbers, space and some
+punctuation. If you want to change the set of characters considered as part of
+the WPM calculation, you can implement `wpm_keycode_user(uint16_t keycode)`
+and return true for any characters you would like included in the calculation,
+or false to not count that particular keycode.
M quantum/quantum.c => quantum/quantum.c +10 -0
@@ 192,6 192,12 @@ bool process_record_quantum(keyrecord_t *record) {
}
#endif
+#ifdef WPM_ENABLE
+ if (record->event.pressed) {
+ update_wpm(keycode);
+ }
+#endif
+
#ifdef TAP_DANCE_ENABLE
preprocess_tap_dance(keycode, record);
#endif
@@ 645,6 651,10 @@ void matrix_scan_quantum() {
encoder_read();
#endif
+#ifdef WPM_ENABLE
+ decay_wpm();
+#endif
+
#ifdef HAPTIC_ENABLE
haptic_task();
#endif
M quantum/quantum.h => quantum/quantum.h +4 -0
@@ 178,6 178,10 @@ extern layer_state_t layer_state;
# include "via.h"
#endif
+#ifdef WPM_ENABLE
+# include "wpm.h"
+#endif
+
// Function substitutions to ease GPIO manipulation
#if defined(__AVR__)
typedef uint8_t pin_t;
M quantum/split_common/transport.c => quantum/split_common/transport.c +27 -0
@@ 35,6 35,9 @@ typedef struct _I2C_slave_buffer_t {
# ifdef ENCODER_ENABLE
uint8_t encoder_state[NUMBER_OF_ENCODERS];
# endif
+# ifdef WPM_ENABLE
+ uint8_t current_wpm;
+# endif
} I2C_slave_buffer_t;
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
# define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync)
# define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix)
# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state)
+# define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm)
# define TIMEOUT 100
@@ 79,6 83,14 @@ bool transport_master(matrix_row_t matrix[]) {
encoder_update_raw(i2c_buffer->encoder_state);
# endif
+# ifdef WPM_ENABLE
+ uint8_t current_wpm = get_current_wpm();
+ if(current_wpm != i2c_buffer->current_wpm) {
+ if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)¤t_wpm, sizeof(current_wpm), TIMEOUT) >= 0) {
+ i2c_buffer->current_wpm = current_wpm;
+ }
+ }
+# endif
return true;
}
@@ 102,6 114,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE
encoder_state_raw(i2c_buffer->encoder_state);
# endif
+
+# ifdef WPM_ENABLE
+ set_current_wpm(i2c_buffer->current_wpm);
+# endif
}
void transport_master_init(void) { i2c_init(); }
@@ 126,6 142,9 @@ typedef struct _Serial_m2s_buffer_t {
# ifdef BACKLIGHT_ENABLE
uint8_t backlight_level;
# endif
+# ifdef WPM_ENABLE
+ uint8_t current_wpm;
+# endif
} Serial_m2s_buffer_t;
# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
@@ 228,6 247,10 @@ bool transport_master(matrix_row_t matrix[]) {
encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif
+# ifdef WPM_ENABLE
+ // Write wpm to slave
+ serial_m2s_buffer.current_wpm = get_current_wpm();
+# endif
return true;
}
@@ 244,6 267,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE
encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif
+
+# ifdef WPM_ENABLE
+ set_current_wpm(serial_m2s_buffer.current_wpm);
+# endif
}
#endif
A quantum/wpm.c => quantum/wpm.c +67 -0
@@ 0,0 1,67 @@
+/*
+ * Copyright 2020 Richard Sutherland (rich@brickbots.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "wpm.h"
+
+//WPM Stuff
+static uint8_t current_wpm = 0;
+static uint8_t latest_wpm = 0;
+static uint16_t wpm_timer = 0;
+
+//This smoothing is 40 keystrokes
+static const float wpm_smoothing = 0.0487;
+
+void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; }
+
+uint8_t get_current_wpm(void) { return current_wpm; }
+
+bool wpm_keycode(uint16_t keycode) { return wpm_keycode_kb(keycode); }
+
+__attribute__((weak)) bool wpm_keycode_kb(uint16_t keycode) { return wpm_keycode_user(keycode); }
+
+__attribute__((weak)) bool wpm_keycode_user(uint16_t keycode) {
+
+ 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)) {
+ keycode = keycode & 0xFF;
+ } else if (keycode > 0xFF) {
+ keycode = 0;
+ }
+ if((keycode >= KC_A && keycode <= KC_0) || (keycode >= KC_TAB && keycode <= KC_SLASH)) {
+ return true;
+ }
+
+ return false;
+}
+
+
+void update_wpm(uint16_t keycode) {
+ if(wpm_keycode(keycode)) {
+ if(wpm_timer > 0) {
+ latest_wpm = 60000 / timer_elapsed(wpm_timer) / 5;
+ current_wpm = (latest_wpm - current_wpm) * wpm_smoothing + current_wpm;
+ }
+ wpm_timer = timer_read();
+ }
+}
+
+void decay_wpm(void) {
+ if (timer_elapsed(wpm_timer) > 1000) {
+ current_wpm = (0 - current_wpm) * wpm_smoothing +
+ current_wpm;
+ wpm_timer = timer_read();
+ }
+}
A quantum/wpm.h => quantum/wpm.h +30 -0
@@ 0,0 1,30 @@
+/*
+ * Copyright 2020 Richard Sutherland (rich@brickbots.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "quantum.h"
+
+bool wpm_keycode(uint16_t keycode);
+bool wpm_keycode_kb(uint16_t keycode);
+bool wpm_keycode_user(uint16_t keycode);
+
+void set_current_wpm(uint8_t);
+uint8_t get_current_wpm(void);
+void update_wpm(uint16_t);
+
+void decay_wpm(void);