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>
This commit is contained in:
brickbots 2020-03-22 06:06:16 -07:00 committed by GitHub
parent d8f3c28a37
commit bfb2f8e0a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 0 deletions

View file

@ -322,6 +322,11 @@ ifeq ($(strip $(USB_HID_ENABLE)), yes)
include $(TMK_DIR)/protocol/usb_hid.mk include $(TMK_DIR)/protocol/usb_hid.mk
endif endif
ifeq ($(strip $(WPM_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/wpm.c
OPT_DEFS += -DWPM_ENABLE
endif
ifeq ($(strip $(ENCODER_ENABLE)), yes) ifeq ($(strip $(ENCODER_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/encoder.c SRC += $(QUANTUM_DIR)/encoder.c
OPT_DEFS += -DENCODER_ENABLE OPT_DEFS += -DENCODER_ENABLE

View file

@ -80,6 +80,7 @@
* [Terminal](feature_terminal.md) * [Terminal](feature_terminal.md)
* [Unicode](feature_unicode.md) * [Unicode](feature_unicode.md)
* [Userspace](feature_userspace.md) * [Userspace](feature_userspace.md)
* [WPM Calculation](feature_wpm.md)
* Hardware Features * Hardware Features
* Displays * Displays

25
docs/feature_wpm.md Normal file
View file

@ -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.

View file

@ -192,6 +192,12 @@ bool process_record_quantum(keyrecord_t *record) {
} }
#endif #endif
#ifdef WPM_ENABLE
if (record->event.pressed) {
update_wpm(keycode);
}
#endif
#ifdef TAP_DANCE_ENABLE #ifdef TAP_DANCE_ENABLE
preprocess_tap_dance(keycode, record); preprocess_tap_dance(keycode, record);
#endif #endif
@ -645,6 +651,10 @@ void matrix_scan_quantum() {
encoder_read(); encoder_read();
#endif #endif
#ifdef WPM_ENABLE
decay_wpm();
#endif
#ifdef HAPTIC_ENABLE #ifdef HAPTIC_ENABLE
haptic_task(); haptic_task();
#endif #endif

View file

@ -178,6 +178,10 @@ extern layer_state_t layer_state;
# include "via.h" # include "via.h"
#endif #endif
#ifdef WPM_ENABLE
# include "wpm.h"
#endif
// Function substitutions to ease GPIO manipulation // Function substitutions to ease GPIO manipulation
#if defined(__AVR__) #if defined(__AVR__)
typedef uint8_t pin_t; typedef uint8_t pin_t;

View file

@ -35,6 +35,9 @@ typedef struct _I2C_slave_buffer_t {
# ifdef ENCODER_ENABLE # ifdef ENCODER_ENABLE
uint8_t encoder_state[NUMBER_OF_ENCODERS]; uint8_t encoder_state[NUMBER_OF_ENCODERS];
# endif # endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
} I2C_slave_buffer_t; } I2C_slave_buffer_t;
static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; 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_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync)
# define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix) # define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix)
# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) # 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 # define TIMEOUT 100
@ -79,6 +83,14 @@ bool transport_master(matrix_row_t matrix[]) {
encoder_update_raw(i2c_buffer->encoder_state); encoder_update_raw(i2c_buffer->encoder_state);
# endif # 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 *)&current_wpm, sizeof(current_wpm), TIMEOUT) >= 0) {
i2c_buffer->current_wpm = current_wpm;
}
}
# endif
return true; return true;
} }
@ -102,6 +114,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE # ifdef ENCODER_ENABLE
encoder_state_raw(i2c_buffer->encoder_state); encoder_state_raw(i2c_buffer->encoder_state);
# endif # endif
# ifdef WPM_ENABLE
set_current_wpm(i2c_buffer->current_wpm);
# endif
} }
void transport_master_init(void) { i2c_init(); } void transport_master_init(void) { i2c_init(); }
@ -126,6 +142,9 @@ typedef struct _Serial_m2s_buffer_t {
# ifdef BACKLIGHT_ENABLE # ifdef BACKLIGHT_ENABLE
uint8_t backlight_level; uint8_t backlight_level;
# endif # endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
} Serial_m2s_buffer_t; } Serial_m2s_buffer_t;
# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) # 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); encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif # endif
# ifdef WPM_ENABLE
// Write wpm to slave
serial_m2s_buffer.current_wpm = get_current_wpm();
# endif
return true; return true;
} }
@ -244,6 +267,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE # ifdef ENCODER_ENABLE
encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state); encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif # endif
# ifdef WPM_ENABLE
set_current_wpm(serial_m2s_buffer.current_wpm);
# endif
} }
#endif #endif

67
quantum/wpm.c Normal file
View file

@ -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();
}
}

30
quantum/wpm.h Normal file
View file

@ -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);