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 encoder abstraction. #21548

Merged
merged 24 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ba26242
Add encoder abstraction.
tzarc Jul 16, 2023
fd99f3f
Lower bound on number of queued events.
tzarc Jul 17, 2023
ec9f3d8
Add changes for other ploopy boards (#22)
drashna Jul 17, 2023
aa667ab
Fix tests
tzarc Jul 17, 2023
78f3470
Cleanup.
tzarc Jul 19, 2023
8f72a97
Function naming.
tzarc Jul 19, 2023
3766715
planck/rev7?
tzarc Jul 19, 2023
9b29b46
Fixup mechwild/sugarglider
tzarc Nov 11, 2023
69ec540
Merge remote-tracking branch 'upstream/develop' into encoder-abstraction
tzarc Nov 11, 2023
699a74c
Merge branch 'develop' into encoder-abstraction
tzarc Nov 27, 2023
d78625f
Update keyboards/planck/rev7/info.json
tzarc Dec 13, 2023
f5827bd
Don't implement pin operations unless A/B pads are specified.
tzarc Dec 15, 2023
880c39b
Add support for `encoder.driver` to info.json
tzarc Dec 16, 2023
9858cc4
Merge remote-tracking branch 'upstream/develop' into encoder-abstraction
tzarc Dec 16, 2023
10e98c7
License year updates.
tzarc Dec 16, 2023
ab351c4
Merge remote-tracking branch 'upstream/develop' into encoder-abstraction
tzarc Feb 18, 2024
6fc132e
Review comments.
tzarc Feb 18, 2024
b6bd9c8
Move ploopy encoder configs to `info.json` where possible.
tzarc Feb 18, 2024
d82d430
Remove unnecessary testing changes.
tzarc Feb 18, 2024
35d39e3
Review comments.
tzarc Feb 18, 2024
6bc955f
Apply suggestions from code review
tzarc Feb 18, 2024
244b1a6
Change `graycode` => `quadrature`.
tzarc Feb 18, 2024
ce94ba8
Merge remote-tracking branch 'origin/encoder-abstraction' into encode…
tzarc Feb 18, 2024
81908cf
Merge remote-tracking branch 'upstream/develop' into encoder-abstraction
tzarc Feb 18, 2024
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
15 changes: 15 additions & 0 deletions builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -924,9 +924,24 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
endif
endif

ENCODER_ENABLE ?= no
ENCODER_DRIVER ?= graycode
VALID_ENCODER_DRIVER_TYPES := graycode custom
ifeq ($(strip $(ENCODER_ENABLE)), yes)
ifeq ($(filter $(ENCODER_DRIVER),$(VALID_ENCODER_DRIVER_TYPES)),)
$(call CATASTROPHIC_ERROR,Invalid ENCODER_DRIVER,ENCODER_DRIVER="$(ENCODER_DRIVER)" is not a valid encoder driver)
endif
SRC += $(QUANTUM_DIR)/encoder.c
OPT_DEFS += -DENCODER_ENABLE
OPT_DEFS += -DENCODER_DRIVER_$(strip $(shell echo $(ENCODER_DRIVER) | tr '[:lower:]' '[:upper:]'))

COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/encoder
COMMON_VPATH += $(DRIVER_PATH)/encoder

ifneq ($(strip $(ENCODER_DRIVER)), custom)
SRC += encoder_$(strip $(ENCODER_DRIVER)).c
drashna marked this conversation as resolved.
Show resolved Hide resolved
endif

ifeq ($(strip $(ENCODER_MAP_ENABLE)), yes)
OPT_DEFS += -DENCODER_MAP_ENABLE
endif
Expand Down
213 changes: 213 additions & 0 deletions drivers/encoder/encoder_graycode.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright 2018 Jack Humbert <jack.humb@gmail.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 <stdint.h>
#include "encoder.h"
#include "gpio.h"
#include "keyboard.h"
#include "action.h"
#include "keycodes.h"
#include "wait.h"

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

// for memcpy
#include <string.h>

#if !defined(ENCODER_RESOLUTIONS) && !defined(ENCODER_RESOLUTION)
# 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"
#endif

extern volatile bool isLeftHand;

static pin_t encoders_pad_a[NUM_ENCODERS_MAX_PER_SIDE] = ENCODERS_PAD_A;
static pin_t encoders_pad_b[NUM_ENCODERS_MAX_PER_SIDE] = ENCODERS_PAD_B;

#ifdef ENCODER_RESOLUTIONS
static uint8_t encoder_resolutions[NUM_ENCODERS] = ENCODER_RESOLUTIONS;
#endif

#ifndef ENCODER_DIRECTION_FLIP
# define ENCODER_CLOCKWISE true
# define ENCODER_COUNTER_CLOCKWISE false
#else
# define ENCODER_CLOCKWISE false
# define ENCODER_COUNTER_CLOCKWISE true
#endif
static int8_t encoder_LUT[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};

static uint8_t encoder_state[NUM_ENCODERS] = {0};
static int8_t encoder_pulses[NUM_ENCODERS] = {0};

// encoder counts
static uint8_t thisCount;
#ifdef SPLIT_KEYBOARD
// encoder offsets for each hand
static uint8_t thisHand, thatHand;
// encoder counts for each hand
static uint8_t thatCount;
#endif

__attribute__((weak)) void encoder_wait_pullup_charge(void) {
wait_us(100);
}

__attribute__((weak)) void encoder_graycode_init_pin(uint8_t index, bool pad_b) {
pin_t pin = pad_b ? encoders_pad_b[index] : encoders_pad_a[index];
if (pin != NO_PIN) {
setPinInputHigh(pin);
}
}

__attribute__((weak)) uint8_t encoder_graycode_read_pin(uint8_t index, bool pad_b) {
pin_t pin = pad_b ? encoders_pad_b[index] : encoders_pad_a[index];
if (pin != NO_PIN) {
return readPin(pin) ? 1 : 0;
}
return 0;
}

__attribute__((weak)) void encoder_graycode_post_init_kb(void) {
extern void encoder_graycode_handle_read(uint8_t index, uint8_t pin_a_state, uint8_t pin_b_state);
// Unused normally, but can be used for things like setting up pin-change interrupts in keyboard code.
// During the interrupt, read the pins then call `encoder_handle_read()` with the pin states and it'll queue up an encoder event if needed.
}

void encoder_graycode_post_init(void) {
for (uint8_t i = 0; i < thisCount; i++) {
encoder_graycode_init_pin(i, false);
encoder_graycode_init_pin(i, true);
}
encoder_wait_pullup_charge();
for (uint8_t i = 0; i < thisCount; i++) {
encoder_state[i] = (encoder_graycode_read_pin(i, false) << 0) | (encoder_graycode_read_pin(i, true) << 1);
}

encoder_graycode_post_init_kb();
}

void encoder_driver_init(void) {
#ifdef SPLIT_KEYBOARD
thisHand = isLeftHand ? 0 : NUM_ENCODERS_LEFT;
thatHand = NUM_ENCODERS_LEFT - thisHand;
thisCount = isLeftHand ? NUM_ENCODERS_LEFT : NUM_ENCODERS_RIGHT;
thatCount = isLeftHand ? NUM_ENCODERS_RIGHT : NUM_ENCODERS_LEFT;
#else // SPLIT_KEYBOARD
thisCount = NUM_ENCODERS;
#endif

#ifdef ENCODER_TESTS
// Annoying that we have to clear out values during initialisation here, but
// because all the arrays are static locals, rerunning tests in the same
// executable doesn't reset any of these. Kinda crappy having test-only code
// here, but it's the simplest solution.
memset(encoder_state, 0, sizeof(encoder_state));
memset(encoder_pulses, 0, sizeof(encoder_pulses));
static const pin_t encoders_pad_a_left[] = ENCODERS_PAD_A;
static const pin_t encoders_pad_b_left[] = ENCODERS_PAD_B;
for (uint8_t i = 0; i < thisCount; i++) {
encoders_pad_a[i] = encoders_pad_a_left[i];
encoders_pad_b[i] = encoders_pad_b_left[i];
}
#endif

#if defined(SPLIT_KEYBOARD) && defined(ENCODERS_PAD_A_RIGHT) && defined(ENCODERS_PAD_B_RIGHT)
// Re-initialise the pads if it's the right-hand side
if (!isLeftHand) {
static const pin_t encoders_pad_a_right[] = ENCODERS_PAD_A_RIGHT;
static const pin_t encoders_pad_b_right[] = ENCODERS_PAD_B_RIGHT;
for (uint8_t i = 0; i < thisCount; i++) {
encoders_pad_a[i] = encoders_pad_a_right[i];
encoders_pad_b[i] = encoders_pad_b_right[i];
}
}
#endif // defined(SPLIT_KEYBOARD) && defined(ENCODERS_PAD_A_RIGHT) && defined(ENCODERS_PAD_B_RIGHT)

// Encoder resolutions is defined differently in config.h, so concatenate
#if defined(SPLIT_KEYBOARD) && defined(ENCODER_RESOLUTIONS)
# if defined(ENCODER_RESOLUTIONS_RIGHT)
static const uint8_t encoder_resolutions_right[NUM_ENCODERS_RIGHT] = ENCODER_RESOLUTIONS_RIGHT;
# else // defined(ENCODER_RESOLUTIONS_RIGHT)
static const uint8_t encoder_resolutions_right[NUM_ENCODERS_RIGHT] = ENCODER_RESOLUTIONS;
# endif // defined(ENCODER_RESOLUTIONS_RIGHT)
for (uint8_t i = 0; i < NUM_ENCODERS_RIGHT; i++) {
encoder_resolutions[NUM_ENCODERS_LEFT + i] = encoder_resolutions_right[i];
}
#endif // defined(SPLIT_KEYBOARD) && defined(ENCODER_RESOLUTIONS)

encoder_graycode_post_init();
}

static void encoder_handle_state_change(uint8_t index, uint8_t state) {
uint8_t i = index;

#ifdef SPLIT_KEYBOARD
index += thisHand;
#endif

#ifdef ENCODER_RESOLUTIONS
const uint8_t resolution = encoder_resolutions[index];
#else
const uint8_t resolution = ENCODER_RESOLUTION;
#endif

encoder_pulses[i] += encoder_LUT[state & 0xF];

#ifdef ENCODER_DEFAULT_POS
if ((encoder_pulses[i] >= resolution) || (encoder_pulses[i] <= -resolution) || ((state & 0x3) == ENCODER_DEFAULT_POS)) {
if (encoder_pulses[i] >= 1) {
#else
if (encoder_pulses[i] >= resolution) {
#endif

encoder_queue_event(index, ENCODER_COUNTER_CLOCKWISE);
}

#ifdef ENCODER_DEFAULT_POS
if (encoder_pulses[i] <= -1) {
#else
if (encoder_pulses[i] <= -resolution) { // direction is arbitrary here, but this clockwise
#endif
encoder_queue_event(index, ENCODER_CLOCKWISE);
}
encoder_pulses[i] %= resolution;
#ifdef ENCODER_DEFAULT_POS
encoder_pulses[i] = 0;
}
#endif
}

void encoder_graycode_handle_read(uint8_t index, uint8_t pin_a_state, uint8_t pin_b_state) {
uint8_t state = pin_a_state | (pin_b_state << 1);
if ((encoder_state[index] & 0x3) != state) {
encoder_state[index] <<= 2;
encoder_state[index] |= state;
encoder_handle_state_change(index, encoder_state[index]);
}
}

__attribute__((weak)) void encoder_driver_task(void) {
for (uint8_t i = 0; i < thisCount; i++) {
encoder_graycode_handle_read(i, encoder_graycode_read_pin(i, false), encoder_graycode_read_pin(i, true));
}
}
5 changes: 1 addition & 4 deletions keyboards/mechwild/sugarglider/matrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static void select_row(uint8_t row) {
//wait_us(100);
return;
}

if (row > 1) {
mcp23018_errors += !mcp23018_set_config(I2C_ADDR, mcp23018_PORTB, ALL_INPUT);
mcp23018_errors += !mcp23018_set_config(I2C_ADDR, mcp23018_PORTA, ~(row_pos[row]));
Expand Down Expand Up @@ -87,9 +87,6 @@ bool matrix_scan_custom(matrix_row_t current_matrix[]) {
bool changed = false;
for (uint8_t current_row = 0; current_row < MATRIX_ROWS; current_row++) {
changed |= read_cols_on_row(current_matrix, current_row);
#ifdef ENCODER_ENABLE
encoder_read();
#endif
tzarc marked this conversation as resolved.
Show resolved Hide resolved
}
return changed;
}
8 changes: 0 additions & 8 deletions keyboards/pica40/rev2/post_rules.mk

This file was deleted.

98 changes: 17 additions & 81 deletions keyboards/pica40/rev2/rev2.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "rev2.h"
#include "gpio.h"

#ifdef ENCODER_ENABLE // code based on encoder.c

static const pin_t encoders_pad_a[] = ENCODERS_PAD_A;
static const pin_t encoders_pad_b[] = ENCODERS_PAD_B;

static int8_t encoder_LUT[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
static uint8_t encoder_state = 3;
static int8_t encoder_pulses = 0;
static uint8_t encoder_value = 0;

typedef struct encoder_sync_data {
int value;
} encoder_sync_data;
#define ENCODER_PIN_A (((pin_t[])ENCODERS_PAD_A)[0])
#define ENCODER_PIN_B (((pin_t[])ENCODERS_PAD_B)[0])

// custom handler that returns encoder B pin status from slave side
void encoder_sync_slave_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) {
encoder_sync_data *data = (encoder_sync_data *)out_data;
data->value = readPin(encoders_pad_b[0]);
}

__attribute__((weak)) bool encoder_update_user(uint8_t index, bool clockwise) {
return true;
*(uint8_t *)out_data = readPin(ENCODER_PIN_B) ? 1 : 0;
}

bool encoder_update_kb(uint8_t index, bool clockwise) {
if (!encoder_update_user(index, clockwise)) return false;

tap_code(clockwise ? KC_VOLU : KC_VOLD);
void encoder_graycode_init_pin(uint8_t index, bool pad_b) {}

return false;
}

#ifdef ENCODER_MAP_ENABLE
static void encoder_exec_mapping(uint8_t index, bool clockwise) {
action_exec(clockwise ? ENCODER_CW_EVENT(index, true) : ENCODER_CCW_EVENT(index, true));
wait_ms(ENCODER_MAP_KEY_DELAY);
action_exec(clockwise ? ENCODER_CW_EVENT(index, false) : ENCODER_CCW_EVENT(index, false));
wait_ms(ENCODER_MAP_KEY_DELAY);
}
#endif // ENCODER_MAP_ENABLE

void encoder_init(void) {
setPinInputHigh(encoders_pad_a[0]);
setPinInputHigh(encoders_pad_b[0]);
wait_us(100);
transaction_register_rpc(ENCODER_SYNC, encoder_sync_slave_handler);
}

bool encoder_read(void) {
// ignore if running on slave side
if (!is_keyboard_master()) return false;

bool changed = false;
encoder_sync_data data = {0};
// request pin B status from slave side
if (transaction_rpc_recv(ENCODER_SYNC, sizeof(data), &data)) {
uint8_t new_status = (readPin(encoders_pad_a[0]) << 0) | (data.value << 1);
if ((encoder_state & 0x3) != new_status) {
encoder_state <<= 2;
encoder_state |= new_status;
encoder_pulses += encoder_LUT[encoder_state & 0xF];

if (encoder_pulses >= ENCODER_RESOLUTION) {
encoder_value++;
changed = true;
#ifdef ENCODER_MAP_ENABLE
encoder_exec_mapping(0, false);
#else // ENCODER_MAP_ENABLE
encoder_update_kb(0, false);
#endif // ENCODER_MAP_ENABLE
}

if (encoder_pulses <= -ENCODER_RESOLUTION) {
encoder_value--;
changed = true;
#ifdef ENCODER_MAP_ENABLE
encoder_exec_mapping(0, true);
#else // ENCODER_MAP_ENABLE
encoder_update_kb(0, true);
#endif // ENCODER_MAP_ENABLE
}

encoder_pulses %= ENCODER_RESOLUTION;
}
uint8_t encoder_graycode_read_pin(uint8_t index, bool pad_b) {
if(pad_b) {
uint8_t data = 0;
transaction_rpc_recv(ENCODER_SYNC, sizeof(data), &data);
return data;
}
return changed;
return readPin(ENCODER_PIN_A) ? 1 : 0;
}

// do not use standard split encoder transactions
void encoder_state_raw(uint8_t *slave_state) {}
void encoder_update_raw(uint8_t *slave_state) {}

#endif // ENCODER_ENABLE

#ifdef PICA40_RGBLIGHT_TIMEOUT
Expand Down Expand Up @@ -125,6 +55,12 @@ bool should_set_rgblight = false;
void keyboard_post_init_kb(void) {
setPinOutput(PICA40_RGB_POWER_PIN);

#ifdef ENCODER_ENABLE
setPinInputHigh(ENCODER_PIN_A);
setPinInputHigh(ENCODER_PIN_B);
transaction_register_rpc(ENCODER_SYNC, encoder_sync_slave_handler);
#endif // ENCODER_ENABLE

#ifdef PICA40_RGBLIGHT_TIMEOUT
idle_timer = timer_read();
check_rgblight_timer = timer_read();
Expand Down
Loading