Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for encoder mapping. #13286

Merged
merged 4 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Encoder mapping.
  • Loading branch information
tzarc committed Mar 9, 2022
commit 68b2e31b9b19adb61eb357ce37c6a8862cad607d
1 change: 1 addition & 0 deletions builddefs/generic_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ GENERIC_FEATURES = \
DYNAMIC_KEYMAP \
DYNAMIC_MACRO \
ENCODER \
ENCODER_MAP \
GRAVE_ESC \
HAPTIC \
KEY_LOCK \
Expand Down
1 change: 1 addition & 0 deletions builddefs/show_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ OTHER_OPTION_NAMES = \
HELIX ZINC \
AUTOLOG_ENABLE \
DEBUG_ENABLE \
ENCODER_MAP_ENABLE \
ENCODER_ENABLE_CUSTOM \
GERMAN_ENABLE \
HAPTIC_ENABLE \
Expand Down
23 changes: 22 additions & 1 deletion docs/feature_encoders.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,30 @@ Additionally, if one side does not have an encoder, you can specify `{}` for the
#define ENCODER_RESOLUTIONS_RIGHT { 4 }
```
## Encoder map
Encoder mapping may be added to your `keymap.c`, which replicates the normal keyswitch layer handling functionality, but with encoders. Add this to your `rules.mk`:
```make
ENCODER_MAP_ENABLE = yes
```

Your `keymap.c` will then need an encoder mapping defined (for four layers and two encoders):

```c
#if defined(ENCODER_MAP_ENABLE)
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = {
[_BASE] = { ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
[_LOWER] = { ENCODER_CCW_CW(RGB_HUD, RGB_HUI), ENCODER_CCW_CW(RGB_SAD, RGB_SAI) },
[_RAISE] = { ENCODER_CCW_CW(RGB_VAD, RGB_VAI), ENCODER_CCW_CW(RGB_SPD, RGB_SPI) },
[_ADJUST] = { ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_RIGHT, KC_LEFT) },
};
tzarc marked this conversation as resolved.
Show resolved Hide resolved
#endif
```

## Callbacks

The callback functions can be inserted into your `<keyboard>.c`:
When not using `ENCODER_MAP_ENABLE = yes`, the callback functions can be inserted into your `<keyboard>.c`:

```c
bool encoder_update_kb(uint8_t index, bool clockwise) {
Expand Down
1 change: 1 addition & 0 deletions keyboards/hnahkb/vn66/rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ BACKLIGHT_ENABLE = yes # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output
ENCODER_ENABLE = yes
LTO_ENABLE = yes

LAYOUTS = 66_ansi 66_iso
65 changes: 53 additions & 12 deletions quantum/action.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ 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 <limits.h>
#include "host.h"
#include "keycode.h"
#include "keyboard.h"
#include "keymap.h"
#include "mousekey.h"
#include "programmable_button.h"
#include "command.h"
Expand Down Expand Up @@ -89,6 +91,7 @@ void action_exec(keyevent_t event) {
}

#ifdef SWAP_HANDS_ENABLE
// Swap hands handles both keys and encoders, if ENCODER_MAP_ENABLE is defined.
if (!IS_NOEVENT(event)) {
process_hand_swap(&event);
}
Expand Down Expand Up @@ -136,27 +139,65 @@ void action_exec(keyevent_t event) {
}

#ifdef SWAP_HANDS_ENABLE
extern const keypos_t PROGMEM hand_swap_config[MATRIX_ROWS][MATRIX_COLS];
# ifdef ENCODER_MAP_ENABLE
extern const uint8_t PROGMEM encoder_hand_swap_config[NUM_ENCODERS];
# endif // ENCODER_MAP_ENABLE

bool swap_hands = false;
bool swap_held = false;

bool should_swap_hands(size_t index, uint8_t *swap_state, bool pressed) {
size_t array_index = index / (CHAR_BIT);
size_t bit_index = index % (CHAR_BIT);
uint8_t bit_val = 1 << bit_index;
bool do_swap = pressed ? swap_hands : swap_state[array_index] & bit_val;
return do_swap;
}

void set_swap_hands_state(size_t index, uint8_t *swap_state, bool on) {
size_t array_index = index / (CHAR_BIT);
size_t bit_index = index % (CHAR_BIT);
uint8_t bit_val = 1 << bit_index;
if (on) {
swap_state[array_index] |= bit_val;
} else {
swap_state[array_index] &= ~bit_val;
}
}

/** \brief Process Hand Swap
*
* FIXME: Needs documentation.
*/
void process_hand_swap(keyevent_t *event) {
static swap_state_row_t swap_state[MATRIX_ROWS];

keypos_t pos = event->key;
swap_state_row_t col_bit = (swap_state_row_t)1 << pos.col;
bool do_swap = event->pressed ? swap_hands : swap_state[pos.row] & (col_bit);

if (do_swap) {
event->key.row = pgm_read_byte(&hand_swap_config[pos.row][pos.col].row);
event->key.col = pgm_read_byte(&hand_swap_config[pos.row][pos.col].col);
swap_state[pos.row] |= col_bit;
} else {
swap_state[pos.row] &= ~(col_bit);
keypos_t pos = event->key;
if (pos.row < MATRIX_ROWS && pos.col < MATRIX_COLS) {
static uint8_t matrix_swap_state[((MATRIX_ROWS * MATRIX_COLS) + (CHAR_BIT)-1) / (CHAR_BIT)];
size_t index = (size_t)(pos.row * MATRIX_COLS) + pos.col;
bool do_swap = should_swap_hands(index, matrix_swap_state, event->pressed);
if (do_swap) {
event->key.row = pgm_read_byte(&hand_swap_config[pos.row][pos.col].row);
event->key.col = pgm_read_byte(&hand_swap_config[pos.row][pos.col].col);
set_swap_hands_state(index, matrix_swap_state, true);
} else {
set_swap_hands_state(index, matrix_swap_state, false);
}
}
# ifdef ENCODER_MAP_ENABLE
else if (pos.row == KEYLOC_ENCODER_CW || pos.row == KEYLOC_ENCODER_CCW) {
static uint8_t encoder_swap_state[((NUM_ENCODERS) + (CHAR_BIT)-1) / (CHAR_BIT)];
size_t index = pos.col;
bool do_swap = should_swap_hands(index, encoder_swap_state, event->pressed);
if (do_swap) {
event->key.row = pos.row;
event->key.col = pgm_read_byte(&encoder_hand_swap_config[pos.col]);
tzarc marked this conversation as resolved.
Show resolved Hide resolved
set_swap_hands_state(index, encoder_swap_state, true);
} else {
set_swap_hands_state(index, encoder_swap_state, false);
}
}
# endif // ENCODER_MAP_ENABLE
}
#endif

Expand Down
67 changes: 52 additions & 15 deletions quantum/action_layer.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <limits.h>
#include <stdint.h>
#include "keyboard.h"
#include "keymap.h"
#include "action.h"
#include "util.h"
#include "action_layer.h"
Expand Down Expand Up @@ -223,38 +225,73 @@ void layer_debug(void) {
/** \brief source layer cache
*/

uint8_t source_layers_cache[(MATRIX_ROWS * MATRIX_COLS + 7) / 8][MAX_LAYER_BITS] = {{0}};
uint8_t source_layers_cache[((MATRIX_ROWS * MATRIX_COLS) + (CHAR_BIT)-1) / (CHAR_BIT)][MAX_LAYER_BITS] = {{0}};
# ifdef ENCODER_MAP_ENABLE
uint8_t encoder_source_layers_cache[(NUM_ENCODERS + (CHAR_BIT)-1) / (CHAR_BIT)][MAX_LAYER_BITS] = {{0}};
# endif // ENCODER_MAP_ENABLE

/** \brief update source layers cache
/** \brief update source layers cache impl
*
* Updates the cached keys when changing layers
* Updates the supplied cache when changing layers
*/
void update_source_layers_cache(keypos_t key, uint8_t layer) {
const uint8_t key_number = key.col + (key.row * MATRIX_COLS);
const uint8_t storage_row = key_number / 8;
const uint8_t storage_bit = key_number % 8;

void update_source_layers_cache_impl(uint8_t layer, uint16_t entry_number, uint8_t cache[][MAX_LAYER_BITS]) {
const uint16_t storage_idx = entry_number / (CHAR_BIT);
const uint8_t storage_bit = entry_number % (CHAR_BIT);
for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) {
source_layers_cache[storage_row][bit_number] ^= (-((layer & (1U << bit_number)) != 0) ^ source_layers_cache[storage_row][bit_number]) & (1U << storage_bit);
cache[storage_idx][bit_number] ^= (-((layer & (1U << bit_number)) != 0) ^ cache[storage_idx][bit_number]) & (1U << storage_bit);
}
}

/** \brief read source layers cache
*
* reads the cached keys stored when the layer was changed
*/
uint8_t read_source_layers_cache(keypos_t key) {
const uint8_t key_number = key.col + (key.row * MATRIX_COLS);
const uint8_t storage_row = key_number / 8;
const uint8_t storage_bit = key_number % 8;
uint8_t layer = 0;
uint8_t read_source_layers_cache_impl(uint16_t entry_number, uint8_t cache[][MAX_LAYER_BITS]) {
const uint16_t storage_idx = entry_number / (CHAR_BIT);
const uint8_t storage_bit = entry_number % (CHAR_BIT);
uint8_t layer = 0;

for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) {
layer |= ((source_layers_cache[storage_row][bit_number] & (1U << storage_bit)) != 0) << bit_number;
layer |= ((cache[storage_idx][bit_number] & (1U << storage_bit)) != 0) << bit_number;
}

return layer;
}

/** \brief update encoder source layers cache
*
* Updates the cached encoders when changing layers
*/
void update_source_layers_cache(keypos_t key, uint8_t layer) {
if (key.row < MATRIX_ROWS && key.col < MATRIX_COLS) {
const uint16_t entry_number = (uint16_t)(key.row * MATRIX_COLS) + key.col;
update_source_layers_cache_impl(layer, entry_number, source_layers_cache);
}
# ifdef ENCODER_MAP_ENABLE
else if (key.row == KEYLOC_ENCODER_CW || key.row == KEYLOC_ENCODER_CCW) {
const uint16_t entry_number = key.col;
update_source_layers_cache_impl(layer, entry_number, encoder_source_layers_cache);
}
# endif // ENCODER_MAP_ENABLE
}

/** \brief read source layers cache
*
* reads the cached keys stored when the layer was changed
*/
uint8_t read_source_layers_cache(keypos_t key) {
if (key.row < MATRIX_ROWS && key.col < MATRIX_COLS) {
const uint16_t entry_number = (uint16_t)(key.row * MATRIX_COLS) + key.col;
return read_source_layers_cache_impl(entry_number, source_layers_cache);
}
# ifdef ENCODER_MAP_ENABLE
else if (key.row == KEYLOC_ENCODER_CW || key.row == KEYLOC_ENCODER_CCW) {
const uint16_t entry_number = key.col;
return read_source_layers_cache_impl(entry_number, encoder_source_layers_cache);
}
# endif // ENCODER_MAP_ENABLE
return 0;
}
#endif

/** \brief Store or get action (FIXME: Needs better summary)
Expand Down
50 changes: 46 additions & 4 deletions quantum/dynamic_keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,14 @@
# endif
#endif

// Dynamic macro starts after dynamic keymaps
// Dynamic encoders starts after dynamic keymaps
#ifndef DYNAMIC_KEYMAP_ENCODER_EEPROM_ADDR
# define DYNAMIC_KEYMAP_ENCODER_EEPROM_ADDR (DYNAMIC_KEYMAP_EEPROM_ADDR + (DYNAMIC_KEYMAP_LAYER_COUNT * MATRIX_ROWS * MATRIX_COLS * 2))
#endif

// Dynamic macro starts after dynamic encoders
#ifndef DYNAMIC_KEYMAP_MACRO_EEPROM_ADDR
# define DYNAMIC_KEYMAP_MACRO_EEPROM_ADDR (DYNAMIC_KEYMAP_EEPROM_ADDR + (DYNAMIC_KEYMAP_LAYER_COUNT * MATRIX_ROWS * MATRIX_COLS * 2))
# define DYNAMIC_KEYMAP_MACRO_EEPROM_ADDR (DYNAMIC_KEYMAP_ENCODER_EEPROM_ADDR + (DYNAMIC_KEYMAP_LAYER_COUNT * NUM_ENCODERS * 2 * 2))
#endif

// Sanity check that dynamic keymaps fit in available EEPROM
Expand Down Expand Up @@ -89,6 +94,7 @@ void *dynamic_keymap_key_to_eeprom_address(uint8_t layer, uint8_t row, uint8_t c
}

uint16_t dynamic_keymap_get_keycode(uint8_t layer, uint8_t row, uint8_t column) {
if (layer >= DYNAMIC_KEYMAP_LAYER_COUNT || row >= MATRIX_ROWS || column >= MATRIX_COLS) return KC_NO;
void *address = dynamic_keymap_key_to_eeprom_address(layer, row, column);
// Big endian, so we can read/write EEPROM directly from host if we want
uint16_t keycode = eeprom_read_byte(address) << 8;
Expand All @@ -97,12 +103,36 @@ uint16_t dynamic_keymap_get_keycode(uint8_t layer, uint8_t row, uint8_t column)
}

void dynamic_keymap_set_keycode(uint8_t layer, uint8_t row, uint8_t column, uint16_t keycode) {
if (layer >= DYNAMIC_KEYMAP_LAYER_COUNT || row >= MATRIX_ROWS || column >= MATRIX_COLS) return;
void *address = dynamic_keymap_key_to_eeprom_address(layer, row, column);
// Big endian, so we can read/write EEPROM directly from host if we want
eeprom_update_byte(address, (uint8_t)(keycode >> 8));
eeprom_update_byte(address + 1, (uint8_t)(keycode & 0xFF));
}

#ifdef ENCODER_MAP_ENABLE
void *dynamic_keymap_encoder_to_eeprom_address(uint8_t layer, uint8_t encoder_id) {
return ((void *)DYNAMIC_KEYMAP_ENCODER_EEPROM_ADDR) + (layer * NUM_ENCODERS * 2 * 2) + (encoder_id * 2 * 2);
}

uint16_t dynamic_keymap_get_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise) {
if (layer >= DYNAMIC_KEYMAP_LAYER_COUNT || encoder_id >= NUM_ENCODERS) return KC_NO;
void *address = dynamic_keymap_encoder_to_eeprom_address(layer, encoder_id);
// Big endian, so we can read/write EEPROM directly from host if we want
uint16_t keycode = ((uint16_t)eeprom_read_byte(address + (clockwise ? 0 : 2))) << 8;
keycode |= eeprom_read_byte(address + (clockwise ? 0 : 2) + 1);
return keycode;
}

void dynamic_keymap_set_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise, uint16_t keycode) {
if (layer >= DYNAMIC_KEYMAP_LAYER_COUNT || encoder_id >= NUM_ENCODERS) return;
void *address = dynamic_keymap_encoder_to_eeprom_address(layer, encoder_id);
// Big endian, so we can read/write EEPROM directly from host if we want
eeprom_update_byte(address + (clockwise ? 0 : 2), (uint8_t)(keycode >> 8));
eeprom_update_byte(address + (clockwise ? 0 : 2) + 1, (uint8_t)(keycode & 0xFF));
}
#endif // ENCODER_MAP_ENABLE

void dynamic_keymap_reset(void) {
// Reset the keymaps in EEPROM to what is in flash.
// All keyboards using dynamic keymaps should define a layout
Expand All @@ -113,6 +143,12 @@ void dynamic_keymap_reset(void) {
dynamic_keymap_set_keycode(layer, row, column, pgm_read_word(&keymaps[layer][row][column]));
}
}
#ifdef ENCODER_MAP_ENABLE
for (int encoder = 0; encoder < NUM_ENCODERS; encoder++) {
dynamic_keymap_set_encoder(layer, encoder, true, pgm_read_word(&encoder_map[layer][encoder][0]));
dynamic_keymap_set_encoder(layer, encoder, false, pgm_read_word(&encoder_map[layer][encoder][1]));
}
#endif // ENCODER_MAP_ENABLE
}
}

Expand Down Expand Up @@ -148,9 +184,15 @@ void dynamic_keymap_set_buffer(uint16_t offset, uint16_t size, uint8_t *data) {
uint16_t keymap_key_to_keycode(uint8_t layer, keypos_t key) {
if (layer < DYNAMIC_KEYMAP_LAYER_COUNT && key.row < MATRIX_ROWS && key.col < MATRIX_COLS) {
return dynamic_keymap_get_keycode(layer, key.row, key.col);
} else {
return KC_NO;
}
#ifdef ENCODER_MAP_ENABLE
else if (layer < DYNAMIC_KEYMAP_LAYER_COUNT && key.row == KEYLOC_ENCODER_CW && key.col < NUM_ENCODERS) {
return dynamic_keymap_get_encoder(layer, key.col, true);
} else if (layer < DYNAMIC_KEYMAP_LAYER_COUNT && key.row == KEYLOC_ENCODER_CCW && key.col < NUM_ENCODERS) {
return dynamic_keymap_get_encoder(layer, key.col, false);
}
#endif // ENCODER_MAP_ENABLE
return KC_NO;
}

uint8_t dynamic_keymap_macro_get_count(void) {
Expand Down
6 changes: 5 additions & 1 deletion quantum/dynamic_keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ uint8_t dynamic_keymap_get_layer_count(void);
void * dynamic_keymap_key_to_eeprom_address(uint8_t layer, uint8_t row, uint8_t column);
uint16_t dynamic_keymap_get_keycode(uint8_t layer, uint8_t row, uint8_t column);
void dynamic_keymap_set_keycode(uint8_t layer, uint8_t row, uint8_t column, uint16_t keycode);
void dynamic_keymap_reset(void);
#ifdef ENCODER_MAP_ENABLE
uint16_t dynamic_keymap_get_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise);
void dynamic_keymap_set_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise, uint16_t keycode);
#endif // ENCODER_MAP_ENABLE
void dynamic_keymap_reset(void);
// These get/set the keycodes as stored in the EEPROM buffer
// Data is big-endian 16-bit values (the keycodes)
// Order is by layer/row/column
Expand Down
Loading