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

Rework encoders to enable asymmetric split keyboards #12090

Merged
merged 19 commits into from
Nov 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions build_test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ include $(BUILDDEFS_PATH)/generic_features.mk
include $(PLATFORM_PATH)/common.mk
include $(TMK_PATH)/protocol.mk
include $(QUANTUM_PATH)/debounce/tests/rules.mk
include $(QUANTUM_PATH)/encoder/tests/rules.mk
include $(QUANTUM_PATH)/sequencer/tests/rules.mk
include $(PLATFORM_PATH)/test/rules.mk
ifneq ($(filter $(FULL_TESTS),$(TEST)),)
Expand Down
15 changes: 14 additions & 1 deletion docs/feature_encoders.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,27 @@ For 4× encoders you also can assign default position if encoder skips pulses wh

## Split Keyboards

If you are using different pinouts for the encoders on each half of a split keyboard, you can define the pinout (and optionally, resolutions) for the right half like this:
The above is enough for split keyboards that are symmetrical, i.e. the halves have the same number of encoders and they are on the same pins.
If the halves are not symmetrical, you can define the pinout (and optionally, resolutions) of the right half separately.
The left half will use the definitions above.

```c
#define ENCODERS_PAD_A_RIGHT { encoder1a, encoder2a }
#define ENCODERS_PAD_B_RIGHT { encoder1b, encoder2b }
#define ENCODER_RESOLUTIONS_RIGHT { 2, 4 }
```

If only the right half has encoders, you must still define an empty array for the left pads (and resolutions, if you define `ENCODER_RESOLUTIONS_RIGHT`).

```c
#define ENCODERS_PAD_A { }
#define ENCODERS_PAD_B { }
#define ENCODER_RESOLUTIONS { }
Comment on lines +62 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth removing this.

In the encoder.c file, you could add:

#ifndef ENCODERS_PAD_A
#    define ENCODERS_PAD_A {  }
#endif

Or the like.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit worried about making things confusing. Leaving out the left side would have very different effect from leaving out the right side. Leave out the _RIGHT pads, the pads get mirrored and you get encoders on both sides. But leave out the "normal" (aka. left) pads and you only get encoders on right.

It would also mean that a board with encoders on only left would look different from a board with encoders only on right:

// only right
#define ENCODERS_PAD_A_RIGHT { X }
#define ENCODERS_PAD_A_RIGHT { Y }
// only left
#define ENCODERS_PAD_A { X }
#define ENCODERS_PAD_A { Y }
#define ENCODERS_PAD_A_RIGHT {  }
#define ENCODERS_PAD_A_RIGHT {  }

Waddaya think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'd prefer to explicitly require both sides in definitions, if an asymmetrical encoder setup is defined.
So you could do the #ifdef, but I'm not sure it's the most readable/understandable/maintainable.

So yeah, my vote would be both:

// only right
#define ENCODERS_PAD_A { }
#define ENCODERS_PAD_A { }
#define ENCODERS_PAD_A_RIGHT { X }
#define ENCODERS_PAD_A_RIGHT { Y }
// only left
#define ENCODERS_PAD_A { X }
#define ENCODERS_PAD_A { Y }
#define ENCODERS_PAD_A_RIGHT { }
#define ENCODERS_PAD_A_RIGHT { }

#define ENCODERS_PAD_A_RIGHT { encoder1a, encoder2a }
#define ENCODERS_PAD_B_RIGHT { encoder1b, encoder2b }
#define ENCODER_RESOLUTIONS_RIGHT { 2, 4 }
```

## Callbacks

The callback functions can be inserted into your `<keyboard>.c`:
Expand Down
146 changes: 91 additions & 55 deletions quantum/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@
*/

#include "encoder.h"
#ifdef SPLIT_KEYBOARD
# include "split_util.h"

// this is for unit testing
#if defined(ENCODER_MOCK_SINGLE)
# include "encoder/tests/mock.h"
#elif defined(ENCODER_MOCK_SPLIT)
# include "encoder/tests/mock_split.h"
#else
# include <gpio.h>
# ifdef SPLIT_KEYBOARD
# include "split_util.h"
# endif
#endif

// for memcpy
Expand All @@ -27,17 +36,41 @@
# define ENCODER_RESOLUTION 4
#endif

#if !defined(ENCODERS_PAD_A) || !defined(ENCODERS_PAD_B)
# error "No encoder pads defined by ENCODERS_PAD_A and ENCODERS_PAD_B"
#if (!defined(ENCODERS_PAD_A) || !defined(ENCODERS_PAD_B)) && (!defined(ENCODERS_PAD_A) || !defined(ENCODERS_PAD_B))
# error "No encoder pads defined by ENCODERS_PAD_A and ENCODERS_PAD_B or ENCODERS_PAD_A_RIGHT and ENCODERS_PAD_B_RIGHT"
#endif

#define NUMBER_OF_ENCODERS (sizeof(encoders_pad_a) / sizeof(pin_t))
// on split keyboards, these are the pads and resolutions for the left half
static pin_t encoders_pad_a[] = ENCODERS_PAD_A;
static pin_t encoders_pad_b[] = ENCODERS_PAD_B;
#ifdef ENCODER_RESOLUTIONS
static uint8_t encoder_resolutions[] = ENCODER_RESOLUTIONS;
#endif

#ifndef SPLIT_KEYBOARD
# define NUMBER_OF_ENCODERS (sizeof(encoders_pad_a) / sizeof(pin_t))
#else
// if no pads for right half are defined, we assume the keyboard is symmetric (i.e. same pads)
# ifndef ENCODERS_PAD_A_RIGHT
# define ENCODERS_PAD_A_RIGHT ENCODERS_PAD_A
# endif
# ifndef ENCODERS_PAD_B_RIGHT
# define ENCODERS_PAD_B_RIGHT ENCODERS_PAD_B
# endif
# if defined(ENCODER_RESOLUTIONS) && !defined(ENCODER_RESOLUTIONS_RIGHT)
# define ENCODER_RESOLUTIONS_RIGHT ENCODER_RESOLUTIONS
# endif

# define NUMBER_OF_ENCODERS ((sizeof(encoders_pad_a) + sizeof(encoders_pad_a_right)) / sizeof(pin_t))
# define NUMBER_OF_ENCODERS_LEFT (sizeof(encoders_pad_a) / sizeof(pin_t))
# define NUMBER_OF_ENCODERS_RIGHT (sizeof(encoders_pad_a_right) / sizeof(pin_t))
static pin_t encoders_pad_a_right[] = ENCODERS_PAD_A_RIGHT;
static pin_t encoders_pad_b_right[] = ENCODERS_PAD_B_RIGHT;
# ifdef ENCODER_RESOLUTIONS_RIGHT
static uint8_t encoder_resolutions_right[] = ENCODER_RESOLUTIONS_RIGHT;
# endif
#endif

#ifndef ENCODER_DIRECTION_FLIP
# define ENCODER_CLOCKWISE true
# define ENCODER_COUNTER_CLOCKWISE false
Expand All @@ -50,102 +83,105 @@ static int8_t encoder_LUT[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1,
static uint8_t encoder_state[NUMBER_OF_ENCODERS] = {0};
static int8_t encoder_pulses[NUMBER_OF_ENCODERS] = {0};

#ifdef SPLIT_KEYBOARD
// right half encoders come over as second set of encoders
static uint8_t encoder_value[NUMBER_OF_ENCODERS * 2] = {0};
// row offsets for each hand
static uint8_t thisHand, thatHand;
#else
static uint8_t encoder_value[NUMBER_OF_ENCODERS] = {0};
#endif

__attribute__((weak)) bool encoder_update_user(uint8_t index, bool clockwise) { return true; }

__attribute__((weak)) bool encoder_update_kb(uint8_t index, bool clockwise) { return encoder_update_user(index, clockwise); }

// number of encoders connected to this controller
static uint8_t numEncodersHere;
// index of the first encoder connected to this controller (only for right halves, this will be nonzero)
static uint8_t firstEncoderHere;
#ifdef SPLIT_KEYBOARD
// index of the first encoder connected to the other half
static uint8_t firstEncoderThere;
#endif
// the pads for this controller
static pin_t* pad_a;
static pin_t* pad_b;

void encoder_init(void) {
#if defined(SPLIT_KEYBOARD) && defined(ENCODERS_PAD_A_RIGHT) && defined(ENCODERS_PAD_B_RIGHT)
if (!isLeftHand) {
const pin_t encoders_pad_a_right[] = ENCODERS_PAD_A_RIGHT;
const pin_t encoders_pad_b_right[] = ENCODERS_PAD_B_RIGHT;
# if defined(ENCODER_RESOLUTIONS_RIGHT)
const uint8_t encoder_resolutions_right[] = ENCODER_RESOLUTIONS_RIGHT;
# endif
for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) {
encoders_pad_a[i] = encoders_pad_a_right[i];
encoders_pad_b[i] = encoders_pad_b_right[i];
# if defined(ENCODER_RESOLUTIONS_RIGHT)
encoder_resolutions[i] = encoder_resolutions_right[i];
# endif
}
#ifndef SPLIT_KEYBOARD
numEncodersHere = NUMBER_OF_ENCODERS;
pad_a = encoders_pad_a;
pad_b = encoders_pad_b;
firstEncoderHere = 0;
#else
if (isLeftHand) {
numEncodersHere = NUMBER_OF_ENCODERS_LEFT;
pad_a = encoders_pad_a;
pad_b = encoders_pad_b;
firstEncoderHere = 0;
firstEncoderThere = NUMBER_OF_ENCODERS_LEFT;
} else {
numEncodersHere = NUMBER_OF_ENCODERS_RIGHT;
pad_a = encoders_pad_a_right;
pad_b = encoders_pad_b_right;
firstEncoderHere = NUMBER_OF_ENCODERS_LEFT;
firstEncoderThere = 0;
}
#endif

for (int i = 0; i < NUMBER_OF_ENCODERS; i++) {
setPinInputHigh(encoders_pad_a[i]);
setPinInputHigh(encoders_pad_b[i]);
for (int i = 0; i < numEncodersHere; i++) {
setPinInputHigh(pad_a[i]);
setPinInputHigh(pad_b[i]);

encoder_state[i] = (readPin(encoders_pad_a[i]) << 0) | (readPin(encoders_pad_b[i]) << 1);
encoder_state[firstEncoderHere + i] = (readPin(pad_a[i]) << 0) | (readPin(pad_b[i]) << 1);
}

#ifdef SPLIT_KEYBOARD
thisHand = isLeftHand ? 0 : NUMBER_OF_ENCODERS;
thatHand = NUMBER_OF_ENCODERS - thisHand;
#endif
}

static bool encoder_update(uint8_t index, uint8_t state) {
bool changed = false;
uint8_t i = index;
static bool encoder_update(int8_t index, uint8_t state) {
bool changed = false;

#ifdef ENCODER_RESOLUTIONS
uint8_t resolution = encoder_resolutions[i];
# ifndef SPLIT_KEYBOARD
int8_t resolution = encoder_resolutions[index];
# else
int8_t resolution = isLeftHand ? encoder_resolutions[index] : encoder_resolutions_right[index - NUMBER_OF_ENCODERS_LEFT];
# endif
#else
uint8_t resolution = ENCODER_RESOLUTION;
#endif

#ifdef SPLIT_KEYBOARD
index += thisHand;
#endif
encoder_pulses[i] += encoder_LUT[state & 0xF];
if (encoder_pulses[i] >= resolution) {
encoder_pulses[index] += encoder_LUT[state & 0xF];
if (encoder_pulses[index] >= resolution) {
encoder_value[index]++;
changed = true;
encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE);
}
if (encoder_pulses[i] <= -resolution) { // direction is arbitrary here, but this clockwise
if (encoder_pulses[index] <= -resolution) { // direction is arbitrary here, but this clockwise
encoder_value[index]--;
changed = true;
encoder_update_kb(index, ENCODER_CLOCKWISE);
}
encoder_pulses[i] %= resolution;
encoder_pulses[index] %= resolution;
#ifdef ENCODER_DEFAULT_POS
if ((state & 0x3) == ENCODER_DEFAULT_POS) {
encoder_pulses[i] = 0;
encoder_pulses[index] = 0;
}
#endif
return changed;
}

bool encoder_read(void) {
bool changed = false;
for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) {
encoder_state[i] <<= 2;
encoder_state[i] |= (readPin(encoders_pad_a[i]) << 0) | (readPin(encoders_pad_b[i]) << 1);
changed |= encoder_update(i, encoder_state[i]);
for (uint8_t i = 0; i < numEncodersHere; i++) {
encoder_state[firstEncoderHere + i] <<= 2;
encoder_state[firstEncoderHere + i] |= (readPin(pad_a[i]) << 0) | (readPin(pad_b[i]) << 1);
changed |= encoder_update(firstEncoderHere + i, encoder_state[firstEncoderHere + i]);
}
return changed;
}

#ifdef SPLIT_KEYBOARD
void last_encoder_activity_trigger(void);

void encoder_state_raw(uint8_t* slave_state) { memcpy(slave_state, &encoder_value[thisHand], sizeof(uint8_t) * NUMBER_OF_ENCODERS); }
void encoder_state_raw(uint8_t* slave_state) { memcpy(slave_state, &encoder_value[firstEncoderHere], sizeof(uint8_t) * numEncodersHere); }

void encoder_update_raw(uint8_t* slave_state) {
bool changed = false;
for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) {
uint8_t index = i + thatHand;
for (uint8_t i = 0; i < NUMBER_OF_ENCODERS - numEncodersHere; i++) {
uint8_t index = firstEncoderThere + i;
int8_t delta = slave_state[i] - encoder_value[index];
while (delta > 0) {
delta--;
Expand Down
3 changes: 2 additions & 1 deletion quantum/encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

#pragma once

#include "quantum.h"
#include <stdbool.h>
#include <stdint.h>

void encoder_init(void);
bool encoder_read(void);
Expand Down
Loading