GOLEM keyboard project

Rotary encoder programming in QMK

Rotary encoders are popular on keyboards nowadays. While I would question their practical use on a really ergonomic keyboard, they look cool and I have some on my builds too.

After wiring the rotary encoder you have to make some adjustments to your code.

rules.mk

Make sure the encoder feature is enabled in your rules.mk:

ENCODER_ENABLE = yes

config.h

This is something similar you'll have in your config.h:

#define ENCODERS_PAD_A { D3 }
#define ENCODERS_PAD_B { D2 }
#define ENCODER_RESOLUTION 4

Interpretation: Pad A and B in this context mean the two directions (clockwise and anticlockwise) and the associated I/O pins are given (docs).

If you have multiple rotary encoders on your keyboard, simply enter more values. This is what I have in my config.h file for a two-encoder setup:

#define ENCODERS_PAD_A { D3, B6 }
#define ENCODERS_PAD_B { D2, B0 }
#define ENCODER_RESOLUTION 4

keymap.c

Here are some code examples to handle rotary encoders from the most basic example to a more advanced, feature-rich implementation.

Basic rotary encoder example

This basic example is for a single encoder with a single function:

void encoder_update_user(uint8_t index, bool clockwise) {
  if (clockwise) {
    tap_code(KC_UP);
  } else {
    tap_code(KC_DOWN);
  }
}

Interpretation: Turning the encoder knob clockwise/anticlockwise triggers an up/down arrow keypress, respectively. This may be interpreted as increasing/decreasing of numeric values in inputboxes, e.g in browser forms or other software.

Intermediate rotary encoder example

This second example is still for a single encoder but with a modifier added:

void encoder_update_user(uint8_t index, bool clockwise) {
  // ctrl
  if (get_mods() & MOD_BIT(KC_LCTRL)) {
    if (clockwise) {
      tap_code(KC_WH_U);
    } else {
      tap_code(KC_WH_D);
    }
  } else {
    if (clockwise) {
      tap_code(KC_UP);
    } else {
      tap_code(KC_DOWN);
    }
  }
}

Interpretation: Turning the encoder knob clockwise/anticlockwise triggers an up/down arrow keypress, respectively. Holding the Ctrl modifier while turning the knob results in scrolling with the mouse (KC_WH_U and KC_WH_D are the keycodes for mouse wheel scroll).

Advanced rotary encoder example

And this is the actual code from my keymap.c for two encoders with lots of modifier action:

static bool tabbing = false;
static uint16_t tabtimer;
#define TABBING_TIMER 750

void encoder_update_user(uint8_t index, bool clockwise) {

/* Right encoder */ if (index == 0) { // ctrl if (get_mods() & MOD_BIT(KC_LCTRL)) { if (clockwise) { tap_code(KC_WH_U); } else { tap_code(KC_WH_D); } } else { if (clockwise) { tap_code(KC_UP); } else { tap_code(KC_DOWN); } }

/* Left encoder */ } else if (index == 1) { if (IS_LAYER_ON(_FUNNAV)) { /* volume */ if (clockwise) { tap_code(KC_VOLU); } else { tap_code(KC_VOLD); } } else if (IS_LAYER_ON(_NUMSYM)) { /* undo/redo */ if (clockwise) { tap_code16(LCTL(KC_Y)); } else { tap_code16(LCTL(KC_Z)); } } else if (IS_LAYER_ON(_ADJ)) { /* app switch */ if (!clockwise) { tabtimer = timer_read(); if(!tabbing) { register_code(KC_LALT); tabbing = true; } tap_code(KC_TAB); } else { tabtimer = timer_read(); if(!tabbing) { register_code(KC_LALT); tabbing = true; } register_code(KC_LSFT); tap_code(KC_TAB); unregister_code(KC_LSFT); } } else { /* BASE LAYER - character navigation */ if (clockwise) { tap_code(KC_LEFT); } else { tap_code(KC_RIGHT); } } } }

void matrix_scan_user(void) { if(tabbing) { if (timer_elapsed(tabtimer) > TABBING_TIMER) { unregister_code(KC_LALT); tabbing = false; } } }

Interpretation: As you can see, volume setting, undo/redo sequencing and app switching is introduced in this example. The actual function depends on which layer we are on (IS_LAYER_ON).

To fire the Alt+Tab and Alt+Shift+Tab keycodes properly, we need to introduce the variables at the top (tabbing) and the snippet in the matrix_scan_user function.

Conclusions

Rotary encoders may come in handy with certain workflows. After wiring them properly you have to enable the feature in QMK (rules.mk), do some basic configuration (config.h) and define how to handle them (keymap.c).

I hope these examples have helped you to understand the basics. With tweaking these code fragments (changing the keycodes, modifiers and layers) you can now achieve the functionality you need.

Thanks for reading!

If you found this useful, consider buying me a coffee.