Add analog matrix common files

This commit is contained in:
lokher
2025-11-07 12:26:26 +08:00
parent 3e5f157003
commit 306e3c2259
31 changed files with 3936 additions and 10 deletions

View File

@@ -0,0 +1,204 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "analog_matrix.h"
#include "profile.h"
#include "sqrt.h"
#include "game_controller_common.h"
#ifdef JOYSTICK_ENABLE
extern point_t curve[CURVE_POINTS_COUNT];
extern float slope[CURVE_POINTS_COUNT - 1];
extern bool regular_trigger_action(analog_key_t *key);
extern matrix_row_t game_controller_matrix[MATRIX_ROWS];
static uint8_t axis_travel[GC_MAX] = {0};
static bool axis_changed[GC_MAX] = {0};
static int8_t axis_dir[GC_MAX / 2] = {0};
static int32_t axis_value[GC_MAX / 2] = {0};
joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {
JOYSTICK_AXIS_VIRTUAL, // x
JOYSTICK_AXIS_VIRTUAL, // y
JOYSTICK_AXIS_VIRTUAL, // z
JOYSTICK_AXIS_VIRTUAL, // rx
JOYSTICK_AXIS_VIRTUAL, // ry
JOYSTICK_AXIS_VIRTUAL, // rz
};
static uint8_t travel_to_joystick_axis(uint8_t axis, uint8_t travel) {
(void)axis;
uint8_t axis_value = 0;
float t = travel / 6;
if (t > curve[3].x) {
axis_value = curve[3].y;
} else if (t > curve[2].x) {
axis_value = curve[2].y + slope[2] * (t - curve[2].x);
} else if (t > curve[1].x) {
axis_value = curve[1].y + slope[1] * (t - curve[1].x);
} else if (t > curve[0].x) {
axis_value = curve[0].y + slope[0] * (t - curve[0].x);
}
if (axis_value > JOYSTICK_MAX_VALUE) axis_value = JOYSTICK_MAX_VALUE;
return axis_value;
}
bool joystick_update(analog_key_t *key) {
if (key->travel < 1 * TRAVEL_SCALE) key->travel = 0;
if (key->js_axis < GC_MAX) {
axis_travel[key->js_axis] = key->travel;
axis_changed[key->js_axis] = true;
}
if (regular_trigger_action(key)) {
if (key->state == AKS_REGULAR_PRESSED) {
if (key->js_axis >= GC_BUTTON_0 && key->js_axis <= GC_BUTTON_31) register_joystick_button(QK_JOYSTICK_BUTTON_0 + (key->js_axis - GC_BUTTON_0));
game_controller_matrix[key->r] |= 0x01 << key->c;
} else {
if (key->js_axis >= GC_BUTTON_0 && key->js_axis <= GC_BUTTON_31) unregister_joystick_button(QK_JOYSTICK_BUTTON_0 + (key->js_axis - GC_BUTTON_0));
game_controller_matrix[key->r] &= ~(0x01 << key->c);
}
if (game_controller_type_enabled()) return true;
}
return false;
}
void joystick_clear(void) {
memset(axis_travel, 0, sizeof(axis_travel));
memset(axis_changed, 0, sizeof(axis_changed));
memset(axis_dir, 0, sizeof(axis_dir));
memset(axis_value, 0, sizeof(axis_value));
for (uint8_t i = 0; i < JOYSTICK_BUTTON_COUNT; i++) {
unregister_joystick_button(i);
}
for (uint8_t i = 0; i < GC_AXIS_MAX; i++) {
joystick_set_axis(i, 0);
}
joystick_flush();
}
static void joystick_action(void) {
# define AXIS_DIRECT_VALUE (2 * TRAVEL_SCALE)
uint8_t axis_neg, axis_pos;
bool changed = false;
for (uint8_t i = 0; i < GC_AXIS_MAX; i++) {
axis_neg = i * 2;
axis_pos = i * 2 + 1;
if (axis_changed[i] || axis_changed[axis_pos]) {
changed = true;
if (axis_travel[axis_neg] > TRAVEL_SCALE && axis_travel[axis_pos] > TRAVEL_SCALE) {
// Both direction key are pressed
if (axis_travel[axis_neg] > axis_travel[axis_pos] + AXIS_DIRECT_VALUE)
axis_dir[i] = -1;
else if (axis_travel[axis_neg] + AXIS_DIRECT_VALUE < axis_travel[axis_pos])
axis_dir[i] = 1;
} else if (axis_travel[axis_neg]) {
// Negative direction
axis_dir[i] = -1;
} else if (axis_travel[axis_pos]) {
// Positive direction
axis_dir[i] = 1;
} else {
axis_dir[i] = 0;
}
if (axis_dir[i] < 0) {
axis_value[i] = -travel_to_joystick_axis(axis_neg, axis_travel[axis_neg]);
} else {
axis_value[i] = travel_to_joystick_axis(axis_pos, axis_travel[axis_pos]);
}
}
}
if (changed) {
uint32_t x = axis_dir[GC_AXIS_X] < 0 ? axis_travel[GC_AXIS_X * 2] : axis_travel[GC_AXIS_X * 2 + 1];
uint32_t y = axis_dir[GC_AXIS_Y] < 0 ? axis_travel[GC_AXIS_Y * 2] : axis_travel[GC_AXIS_Y * 2 + 1];
uint32_t axis_square = x * x + y * y;
uint32_t r_square = ((FULL_TRAVEL_UNIT + 1) * TRAVEL_SCALE) * ((FULL_TRAVEL_UNIT + 1) * TRAVEL_SCALE);
if (axis_square > r_square) {
uint32_t sqrt = sqrt_uint32(axis_square);
float ratio = 276.0f / sqrt;
axis_value[GC_AXIS_X] = axis_value[GC_AXIS_X] * ratio;
axis_value[GC_AXIS_Y] = axis_value[GC_AXIS_Y] * ratio;
if (axis_dir[GC_AXIS_X] < 0) {
if (axis_value[GC_AXIS_X] < -JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_X] = -JOYSTICK_MAX_VALUE;
} else {
if (axis_value[GC_AXIS_X] > JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_X] = JOYSTICK_MAX_VALUE;
}
if (axis_dir[GC_AXIS_Y] < 0) {
if (axis_value[GC_AXIS_Y] < -JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_Y] = -JOYSTICK_MAX_VALUE;
} else {
if (axis_value[GC_AXIS_Y] > JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_Y] = JOYSTICK_MAX_VALUE;
}
}
uint32_t rx = axis_dir[GC_AXIS_RX] < 0 ? axis_travel[GC_AXIS_RX * 2] : axis_travel[GC_AXIS_RX * 2 + 1];
uint32_t ry = axis_dir[GC_AXIS_RY] < 0 ? axis_travel[GC_AXIS_RY * 2] : axis_travel[GC_AXIS_RY * 2 + 1];
axis_square = rx * rx + ry * ry;
if (axis_square > r_square) {
uint32_t sqrt = sqrt_uint32(axis_square);
float ratio = 276.0f / sqrt;
axis_value[GC_AXIS_RX] = axis_value[GC_AXIS_RX] * ratio;
axis_value[GC_AXIS_RY] = axis_value[GC_AXIS_RY] * ratio;
if (axis_dir[GC_AXIS_RX] < 0) {
if (axis_value[GC_AXIS_RX] < -JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_RX] = -JOYSTICK_MAX_VALUE;
} else {
if (axis_value[GC_AXIS_RX] > JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_RX] = JOYSTICK_MAX_VALUE;
}
if (axis_dir[GC_AXIS_RY] < 0) {
if (axis_value[GC_AXIS_RY] < -JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_RY] = -JOYSTICK_MAX_VALUE;
} else {
if (axis_value[GC_AXIS_RY] > JOYSTICK_MAX_VALUE) axis_value[GC_AXIS_RY] = JOYSTICK_MAX_VALUE;
}
}
for (uint8_t i = 0; i < GC_AXIS_MAX; i++) {
/* Reverse Y and RY axis value */
if (i == GC_AXIS_Y || i == GC_AXIS_RY)
joystick_set_axis(i, -axis_value[i]);
else
joystick_set_axis(i, axis_value[i]);
}
joystick_flush();
}
}
void joystick_action_task(void) {
# ifdef XINPUT_ENABLE
if (!game_controller_xinput_enabled())
# endif
joystick_action();
}
#endif

View File

@@ -0,0 +1,222 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "analog_matrix.h"
#include "profile.h"
// OKMC action type
enum {
OKMC_ACTION_RELEASE = 0b001,
OKMC_ACTION_PRESS = 0b010,
OKMC_ACTION_TAP = 0b110,
OKMC_ACTION_RE_PRESS = 0b111,
};
enum {
OKMC_RELEASED = AKS_REGULAR_RELEASED,
OKMC_SHALLOW_ACTUATED,
OKMC_DEEP_ACTUATED,
OKMC_DEEP_DEACT_READY,
OKMC_DEEP_DEACTUATED,
OKMC_MAX,
};
matrix_row_t okmc_matrix[MATRIX_ROWS] = {0};
static void report_action(bool add, uint16_t keycode) {
if (add) {
if (IS_BASIC_KEYCODE(keycode)) {
if (!is_key_pressed(keycode)) {
add_key(keycode);
}
} else if (IS_MODIFIER_KEYCODE(keycode)) {
add_mods(MOD_BIT(keycode));
}
} else {
if (IS_BASIC_KEYCODE(keycode)) {
if (is_key_pressed(keycode)) {
del_key(keycode);
}
} else if (IS_MODIFIER_KEYCODE(keycode)) {
del_mods(MOD_BIT(keycode));
}
}
}
static void release_okmc_keys(okmc_config_t *okmc) {
// Relase all keys in the OKMC settings
for (uint8_t i = 0; i < 4; ++i) {
if (okmc->keycode[i]) {
report_action(0, okmc->keycode[i]);
}
}
send_keyboard_report();
}
static void inline shallow_actuate(okmc_config_t *okmc) {
bool any_action;
for (uint8_t bit = 0; bit < 3; bit++) {
any_action = false;
for (uint8_t i = 0; i < 4; ++i) {
if (okmc->keycode[i] && (okmc->action[i].shallow_act & (0x01 << bit))) {
report_action(bit % 2, okmc->keycode[i]);
any_action = true;
}
}
if (any_action) {
send_keyboard_report();
wait_ms(1);
}
}
}
static void inline shallow_deactuate(okmc_config_t *okmc) {
bool any_action;
for (uint8_t bit = 0; bit < 3; bit++) {
any_action = false;
for (uint8_t i = 0; i < 4; ++i) {
if (okmc->keycode[i] && (okmc->action[i].shallow_deact & (0x01 << bit))) {
report_action(bit % 2, okmc->keycode[i]);
any_action = true;
}
}
if (any_action) {
send_keyboard_report();
wait_ms(1);
}
}
send_keyboard_report();
release_okmc_keys(okmc);
}
static void inline deep_actuate(okmc_config_t *okmc) {
bool any_action;
for (uint8_t bit = 0; bit < 3; bit++) {
any_action = false;
for (uint8_t i = 0; i < 4; ++i) {
if (okmc->keycode[i] && (okmc->action[i].deep_act & (0x01 << bit))) {
report_action(bit % 2, okmc->keycode[i]);
any_action = true;
}
}
if (any_action) {
send_keyboard_report();
wait_ms(1);
}
}
}
static void inline deep_deactuate(okmc_config_t *okmc) {
bool any_action;
for (uint8_t bit = 0; bit < 3; bit++) {
any_action = false;
for (uint8_t i = 0; i < 4; ++i) {
if (okmc->keycode[i] && (okmc->action[i].deep_deact & (0x01 << bit))) {
report_action(bit % 2, okmc->keycode[i]);
any_action = true;
}
}
if (any_action) {
send_keyboard_report();
wait_ms(1);
}
}
}
bool okmc_action(analog_key_t *key) {
bool changed = false;
analog_matrix_profile_t *cur_prof = profile_get_current();
okmc_traval_config_t *travel_cfg = &cur_prof->okmc[key->okmc_idx].travel;
switch (key->state) {
case OKMC_RELEASED:
// Check shallow actuation
if (key->travel >= travel_cfg->shallow_act * TRAVEL_SCALE) {
key->state = OKMC_SHALLOW_ACTUATED;
shallow_actuate(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
break;
case OKMC_SHALLOW_ACTUATED:
// Key releasing
if (key->travel < travel_cfg->shallow_deact * TRAVEL_SCALE && key->travel < (travel_cfg->shallow_act - 1) * TRAVEL_SCALE) {
key->state = OKMC_RELEASED;
release_okmc_keys(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
// Continue pressing
else if (key->travel >= travel_cfg->deep_act * TRAVEL_SCALE) {
key->state = OKMC_DEEP_ACTUATED;
deep_actuate(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
break;
case OKMC_DEEP_ACTUATED:
if (key->travel > travel_cfg->deep_deact * TRAVEL_SCALE) {
key->state = OKMC_DEEP_DEACT_READY; // make su
} else if (key->travel < travel_cfg->shallow_deact * TRAVEL_SCALE && key->travel < (travel_cfg->shallow_act - 1) * TRAVEL_SCALE) {
key->state = OKMC_RELEASED;
release_okmc_keys(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
break;
case OKMC_DEEP_DEACT_READY:
if (key->travel <= travel_cfg->deep_deact * TRAVEL_SCALE) {
key->state = OKMC_DEEP_DEACTUATED;
deep_deactuate(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
break;
case OKMC_DEEP_DEACTUATED:
// If we miss the deep deacuation point
if (key->travel <= travel_cfg->shallow_deact * TRAVEL_SCALE && key->travel < (travel_cfg->shallow_act - 1) * TRAVEL_SCALE) {
key->state = OKMC_RELEASED;
shallow_deactuate(&cur_prof->okmc[key->okmc_idx]);
changed = true;
}
break;
default:
break;
}
if (changed) {
if (key->state >= OKMC_SHALLOW_ACTUATED && key->state <= OKMC_DEEP_DEACT_READY)
okmc_matrix[key->r] |= 0x01 << key->c;
else
okmc_matrix[key->r] &= ~(0x01 << key->c);
}
return changed;
}
void okmc_clear(void) {
memset(okmc_matrix, 0, sizeof(okmc_matrix));
}

View File

@@ -0,0 +1,99 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "analog_matrix.h"
bool rapid_trigger_action(analog_key_t *key) {
bool changed = false;
int8_t update_rapid_pts = 0;
switch (key->state) {
case AKS_REGULAR_RELEASED:
// Chick first actuation
if (key->travel >= key->regular.actn_pt) {
key->state = AKS_REGULAR_PRESSED;
changed = true;
// First update rapid trigger point
update_rapid_pts = 1;
}
break;
case AKS_REGULAR_PRESSED:
// Key releasing
if (key->travel <= key->regular.deactn_pt) {
key->state = AKS_REGULAR_RELEASED;
changed = true;
} else if (key->travel <= key->rapid.deactn_pt && key->travel < BOTTOM_DEAD_ZONE * TRAVEL_SCALE - key->rpd_trig_sen_rls) {
key->state = AKS_RAPID_RELEASED;
changed = true;
update_rapid_pts = -1;
}
// Continue pressing
else if (key->travel > key->rapid.actn_pt) {
update_rapid_pts = 1;
}
break;
case AKS_RAPID_RELEASED:
// Continue releasing
if (key->travel <= key->regular.deactn_pt) {
key->state = AKS_REGULAR_RELEASED;
}
// Press again
else if (key->travel >= key->rapid.actn_pt && key->travel >= key->regular.actn_pt) {
key->state = AKS_RAPID_PRESSED;
changed = true;
update_rapid_pts = 1;
} else if (key->travel < key->rapid.deactn_pt) {
update_rapid_pts = -1;
}
break;
case AKS_RAPID_PRESSED:
// Key releasing
if (key->travel > FULL_TRAVEL_UNIT * TRAVEL_SCALE) {
break;
}
if (key->travel <= key->regular.deactn_pt) {
key->state = AKS_REGULAR_RELEASED;
changed = true;
} else if (key->travel <= key->rapid.deactn_pt && key->travel < BOTTOM_DEAD_ZONE * TRAVEL_SCALE - key->rpd_trig_sen_rls) {
key->state = AKS_RAPID_RELEASED;
changed = true;
update_rapid_pts = -1;
}
// Continue pressing
else if (key->travel > key->rapid.actn_pt) {
update_rapid_pts = 1;
}
break;
default:
break;
}
if (update_rapid_pts) {
if (update_rapid_pts > 0) {
key->rapid.deactn_pt = key->travel - key->rpd_trig_sen_rls > 0 ? key->travel - key->rpd_trig_sen_rls : 0;
key->rapid.actn_pt = key->travel;
} else {
key->rapid.deactn_pt = key->travel;
key->rapid.actn_pt = key->travel + key->rpd_trig_sen;
}
}
return changed;
}

View File

@@ -0,0 +1,29 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "analog_matrix.h"
bool regular_trigger_action(analog_key_t *key) {
if (key->state == AKS_REGULAR_PRESSED && (key->travel == 0 || key->travel < key->regular.deactn_pt)) {
key->state = AKS_REGULAR_RELEASED;
return true;
} else if (key->state == AKS_REGULAR_RELEASED && key->travel >= key->regular.actn_pt) {
key->state = AKS_REGULAR_PRESSED;
return true;
}
return false;
}

View File

@@ -0,0 +1,116 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "analog_matrix.h"
#include "profile.h"
#include "action_socd.h"
enum {
KEY_1_ACTIVE = 0x01,
KEY_2_ACTIVE = 0x02,
BOTH_KEYS_ACTIVE = 0x03,
};
extern matrix_row_t analog_raw_matrix[MATRIX_ROWS];
extern matrix_row_t raw_matrix[MATRIX_ROWS];
extern matrix_row_t changed_matrix[MATRIX_ROWS];
void socd_action(void) {
matrix_row_t socd_mask[MATRIX_ROWS] = {0};
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
socd_mask[row] = ~socd_mask[row];
}
socd_config_t *socd = profile_get_current()->socd;
static uint8_t state[SOCD_COUNT];
bool keep_last_state;
uint8_t row1, row2, col1, col2;
for (uint8_t i = 0; i < SOCD_COUNT; i++) {
if (socd[i].type) {
keep_last_state = false;
row1 = socd[i].key_1_row;
col1 = socd[i].key_1_col;
row2 = socd[i].key_2_row;
col2 = socd[i].key_2_col;
if ((analog_raw_matrix[row1] & (0x01 << col1)) && (analog_raw_matrix[row2] & (0x01 << col2))) {
switch (socd[i].type) {
case SOCD_PRI_DEEPER_TRAVEL:
case SOCD_PRI_DEEPER_TRAVEL_SINGLE:
if (analog_matrix_get_travel(row1, col1) > 230 && analog_matrix_get_travel(row2, col2) > 230) {
if (socd[i].type == SOCD_PRI_DEEPER_TRAVEL_SINGLE) {
keep_last_state = true;
}
} else if (state[i] == KEY_2_ACTIVE && analog_matrix_get_travel(row1, col1) > analog_matrix_get_travel(row2, col2)) {
socd_mask[row2] &= ~(0x01 << col2);
} else if (state[i] == KEY_1_ACTIVE && analog_matrix_get_travel(row1, col1) < analog_matrix_get_travel(row2, col2)) {
socd_mask[row1] &= ~(0x01 << col1);
} else
keep_last_state = true;
break;
case SOCD_PRI_LAST_KEYSTROKE:
if ((raw_matrix[row1] & (0x01 << col1)) && (analog_raw_matrix[row2] & (0x01 << col2)) && (changed_matrix[row2] & (0x01 << col2))) {
socd_mask[row1] &= ~(0x01 << col1);
state[i] = KEY_2_ACTIVE;
} else if ((raw_matrix[row2] & (0x01 << col2)) && (analog_raw_matrix[row1] & (0x01 << col1)) && (changed_matrix[row1] & (0x01 << col1))) {
socd_mask[row2] &= ~(0x01 << col2);
state[i] = KEY_1_ACTIVE;
} else
keep_last_state = true;
break;
case SOCD_PRI_KEY_1:
socd_mask[row2] &= ~(0x01 << col2);
state[i] = KEY_1_ACTIVE;
break;
case SOCD_PRI_KEY_2:
socd_mask[row1] &= ~(0x01 << col1);
state[i] = KEY_2_ACTIVE;
break;
case SOCD_PRI_NEUTRAL:
socd_mask[row1] &= ~(0x01 << col1);
socd_mask[row2] &= ~(0x01 << col2);
state[i] = 0;
break;
default:
break;
}
} else if (analog_raw_matrix[row1] & (0x01 << col1)) {
state[i] = KEY_1_ACTIVE;
} else if (analog_raw_matrix[row2] & (0x01 << col2)) {
state[i] = KEY_2_ACTIVE;
}
if (keep_last_state) {
if (state[i] == KEY_1_ACTIVE)
socd_mask[row2] &= ~(0x01 << col2);
else if (state[i] == KEY_2_ACTIVE)
socd_mask[row1] &= ~(0x01 << col1);
}
}
}
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
raw_matrix[row] = analog_raw_matrix[row] & socd_mask[row];
}
}

View File

@@ -0,0 +1,27 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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/>.
*/
// SOCD Prioritization
enum {
SOCD_PRI_NONE = 0,
SOCD_PRI_DEEPER_TRAVEL,
SOCD_PRI_DEEPER_TRAVEL_SINGLE,
SOCD_PRI_LAST_KEYSTROKE,
SOCD_PRI_KEY_1,
SOCD_PRI_KEY_2,
SOCD_PRI_NEUTRAL,
SOCD_PRI_MAX,
};

View File

@@ -0,0 +1,29 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "analog_matrix.h"
bool toggle_action(analog_key_t *key) {
if (key->state == AKS_REGULAR_PRESSED && (key->travel == 0 || key->travel < key->regular.deactn_pt)) {
key->state = AKS_REGULAR_RELEASED;
} else if (key->state == AKS_REGULAR_RELEASED && key->travel > key->regular.actn_pt) {
key->state = AKS_REGULAR_PRESSED;
key->hold = !key->hold;
return true;
}
return false;
}

View File

@@ -0,0 +1,226 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "analog_matrix.h"
#include "game_controller_common.h"
#include "sqrt.h"
#ifdef XINPUT_ENABLE
extern point_t curve[CURVE_POINTS_COUNT];
extern float slope[CURVE_POINTS_COUNT - 1];
extern bool regular_trigger_action(analog_key_t *key);
extern matrix_row_t game_controller_matrix[MATRIX_ROWS];
static report_xinput_t xinput;
static bool xinput_changed = false;
static uint8_t axis_travel[GC_MAX] = {0};
static bool axis_changed[GC_MAX] = {0};
static int8_t axis_dir[GC_MAX / 2] = {0};
static int32_t axis_value[GC_MAX / 2] = {0};
static uint16_t travel_to_xinput_value(uint8_t axis, uint8_t travel) {
static uint16_t max_values[GC_MAX] = {0x8000, 0x7FFF, 0x8000, 0x7FFF, 0xFF, 0xFF, 0x8000, 0x7FFF, 0x8000, 0x7FFF, 0x8000, 0x7FFF};
uint32_t axis_value = 0;
if (axis == GC_Z_AXIS_P || axis == GC_Z_AXIS_N) {
// Don't apply curve to L/R Trigger
axis_value = travel * 127 / FULL_TRAVEL_UNIT / TRAVEL_SCALE;
} else if (travel == 0) {
axis_value = 0;
} else if (travel >= curve[3].x * TRAVEL_SCALE) {
axis_value = curve[3].y;
} else if (travel >= curve[2].x * TRAVEL_SCALE) {
axis_value = curve[2].y + slope[2] * (travel - curve[2].x * TRAVEL_SCALE) / TRAVEL_SCALE;
} else if (travel >= curve[1].x * TRAVEL_SCALE) {
axis_value = curve[1].y + slope[1] * (travel - curve[1].x * TRAVEL_SCALE) / TRAVEL_SCALE;
} else if (travel >= curve[0].x * TRAVEL_SCALE) {
axis_value = curve[0].y + slope[0] * (travel - curve[0].x * TRAVEL_SCALE) / TRAVEL_SCALE;
}
uint16_t range = max_values[axis];
axis_value = axis_value * range / 127;
if (axis_value > range) axis_value = range;
return axis_value & 0xFFFF;
}
bool xinput_update(analog_key_t *key) {
if (key->travel < 1 * TRAVEL_SCALE) key->travel = 0;
if (key->js_axis < GC_MAX) {
axis_travel[key->js_axis] = key->travel;
axis_changed[key->js_axis] = true;
}
if (regular_trigger_action(key)) {
static uint8_t xinput_buttons_map[16] = {12, 13, 14, 15, 8, 9, 5, 4, 6, 7, 0, 1, 2, 3, 10, 11};
uint8_t idx = key->js_axis - GC_BUTTON_0;
if (key->state == AKS_REGULAR_PRESSED) {
if (key->js_axis >= GC_BUTTON_0 && key->js_axis < GC_BUTTON_0 + 16) {
xinput.buttons |= (0x01 << xinput_buttons_map[idx]);
xinput_changed = true;
}
game_controller_matrix[key->r] |= 0x01 << key->c;
} else {
if (key->js_axis >= GC_BUTTON_0 && key->js_axis < GC_BUTTON_0 + 16) {
xinput.buttons &= (~(0x01 << xinput_buttons_map[idx]));
xinput_changed = true;
}
game_controller_matrix[key->r] &= ~(0x01 << key->c);
}
if (game_controller_type_enabled()) return true;
}
return false;
}
void xinput_flush(void) {
if (xinput_changed) {
static report_xinput_t last_report;
if (memcmp(&xinput, &last_report, sizeof(report_xinput_t)) != 0) {
memcpy(&last_report, &xinput, sizeof(report_xinput_t));
host_xinput_send(&xinput);
}
xinput_changed = false;
}
}
void xinput_action(void) {
# define AXIS_DIRECT_VALUE (2 * TRAVEL_SCALE)
uint8_t axis_neg, axis_pos;
for (uint8_t i = 0; i < GC_AXIS_MAX; i++) {
axis_neg = i * 2;
axis_pos = i * 2 + 1;
if (i == GC_AXIS_Z) {
/* Handle z axis as left trigger and right trigger */
xinput.left_trigger = travel_to_xinput_value(axis_neg, axis_travel[axis_neg]);
xinput.right_trigger = travel_to_xinput_value(axis_pos, axis_travel[axis_pos]);
xinput_changed = true;
} else if (axis_changed[i] || axis_changed[axis_pos]) {
if (axis_travel[axis_neg] > TRAVEL_SCALE && axis_travel[axis_pos] > TRAVEL_SCALE) {
// Both direction key are pressed
if (axis_travel[axis_neg] > axis_travel[axis_pos] + AXIS_DIRECT_VALUE)
axis_dir[i] = -1;
else if (axis_travel[axis_neg] + AXIS_DIRECT_VALUE < axis_travel[axis_pos])
axis_dir[i] = 1;
} else if (axis_travel[axis_neg]) {
// Negative direction
axis_dir[i] = -1;
} else if (axis_travel[axis_pos]) {
// Positive direction
axis_dir[i] = 1;
} else {
axis_dir[i] = 0;
}
if (axis_dir[i] < 0) {
axis_value[i] = -travel_to_xinput_value(axis_neg, axis_travel[axis_neg]);
} else {
axis_value[i] = travel_to_xinput_value(axis_pos, axis_travel[axis_pos]);
}
xinput_changed = true;
}
}
if (xinput_changed) {
uint32_t x = axis_value[GC_AXIS_X] >> 8;
uint32_t y = axis_value[GC_AXIS_Y] >> 8;
uint32_t axis_square = x * x + y * y;
uint32_t r_square = 0x80 * 0x80;
if (axis_square > r_square) {
uint32_t sqrt = sqrt_uint32(axis_square);
float ratio = 140.0f / sqrt;
axis_value[GC_AXIS_X] = axis_value[GC_AXIS_X] * ratio;
axis_value[GC_AXIS_Y] = axis_value[GC_AXIS_Y] * ratio;
if (axis_dir[GC_AXIS_X] < 0) {
if (axis_value[GC_AXIS_X] < -0x8000) axis_value[GC_AXIS_X] = -0x8000;
} else {
if (axis_value[GC_AXIS_X] > 0x7FFF) axis_value[GC_AXIS_X] = 0x7FFF;
}
if (axis_dir[GC_AXIS_Y] < 0) {
if (axis_value[GC_AXIS_Y] < -0x8000) axis_value[GC_AXIS_Y] = -0x8000;
} else {
if (axis_value[GC_AXIS_Y] > 0x7FFF) axis_value[GC_AXIS_Y] = 0x7FFF;
}
}
x = axis_value[GC_AXIS_RX] >> 8;
y = axis_value[GC_AXIS_RY] >> 8;
axis_square = x * x + y * y;
if (axis_square > r_square) {
uint32_t sqrt = sqrt_uint32(axis_square);
float ratio = 140.0f / sqrt;
axis_value[GC_AXIS_RX] = axis_value[GC_AXIS_RX] * ratio;
axis_value[GC_AXIS_RY] = axis_value[GC_AXIS_RY] * ratio;
if (axis_dir[GC_AXIS_RX] < 0) {
if (axis_value[GC_AXIS_RX] < -0x8000) axis_value[GC_AXIS_RX] = -0x8000;
} else {
if (axis_value[GC_AXIS_RX] > 0x7FFF) axis_value[GC_AXIS_RX] = 0x7FFF;
}
if (axis_dir[GC_AXIS_RY] < 0) {
if (axis_value[GC_AXIS_RY] < -0x8000) axis_value[GC_AXIS_RY] = -0x8000;
} else {
if (axis_value[GC_AXIS_RY] > 0x7FFF) axis_value[GC_AXIS_RY] = 0x7FFF;
}
}
xinput.x = axis_value[GC_AXIS_X];
xinput.y = axis_value[GC_AXIS_Y];
xinput.rx = axis_value[GC_AXIS_RX];
xinput.ry = axis_value[GC_AXIS_RY];
}
}
void xinput_clear(void) {
memset(axis_travel, 0, sizeof(axis_travel));
memset(axis_changed, 0, sizeof(axis_changed));
memset(axis_dir, 0, sizeof(axis_dir));
memset(axis_value, 0, sizeof(axis_value));
memset(&xinput, 0, sizeof(report_xinput_t));
xinput_changed = true;
xinput_flush();
}
void xinput_task(void) {
# ifdef JOYSTICK_ENABLE
if (game_controller_xinput_enabled())
# endif
{
xinput_action();
xinput_flush();
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 <stdint.h>
#include <stdbool.h>
#include "keycodes.h"
#include "matrix.h"
#include "analog_matrix_eeconfig.h"
#include "analog_matrix_type.h"
#define FULL_TRAVEL_UNIT 40
#ifndef DEFAULT_ACTUATION_POINT
# define DEFAULT_ACTUATION_POINT 20
#endif
#ifndef DEFAULT_RAPID_TRIGGER_SENSITIVITY
# define DEFAULT_RAPID_TRIGGER_SENSITIVITY 4
#endif
#ifndef DEFAULT_ZERO_TRAVEL_VALUE
# define DEFAULT_ZERO_TRAVEL_VALUE 3000
#endif
#ifndef DEFAULT_FULL_RANGE
# define DEFAULT_FULL_RANGE 900
#endif
#define DEFAULT_FULL_TRAVEL_VALUE (DEFAULT_ZERO_TRAVEL_VALUE - DEFAULT_FULL_RANGE)
#ifndef VALID_ANALOG_RAW_VALUE_MIN
# define VALID_ANALOG_RAW_VALUE_MIN 1200
#endif
#ifndef VALID_ANALOG_RAW_VALUE_MAX
# define VALID_ANALOG_RAW_VALUE_MAX 3500
#endif
#ifndef STATIC_HYSTERESIS
# define STATIC_HYSTERESIS 5
#endif
#ifndef RAPID_TRIGGER_TICK
# define RAPID_TRIGGER_TICK 10
#endif
#ifndef MIN_ACTUATION
# define MIN_ACTUATION 5
#endif
#ifndef ZERO_TRAVEL_DEAD_ZONE
# define ZERO_TRAVEL_DEAD_ZONE 20
#endif
#ifndef BOTTOM_DEAD_ZONE
# define BOTTOM_DEAD_ZONE 38
#endif
#ifndef BOTTOM_JITTER
# define BOTTOM_JITTER 80
#endif
#define TRAVEL_SCALE 6
#ifndef ANALOG_DEBOUCE_TIME
# define ANALOG_DEBOUCE_TIME 3
#endif
// Threshold value when the magnet switch is not installed
#ifndef ABNORMAL_ANALOG_RAW_THRESHOLD_VALUE
# define ABNORMAL_ANALOG_RAW_THRESHOLD_VALUE 3250
#endif
//
#ifndef AUTO_CALIB_FULL_TRAVEL_THRESHOLD_VALUE
# define AUTO_CALIB_FULL_TRAVEL_THRESHOLD_VALUE (DEFAULT_ZERO_TRAVEL_VALUE - DEFAULT_FULL_RANGE + 100)
#endif
#ifndef AUTO_CALIB_ZERO_TRAVEL_JITTER_VALUE
# define AUTO_CALIB_ZERO_TRAVEL_JITTER_VALUE 50
#endif
#ifndef AUTO_CALIB_FULL_TRAVEL_JITTER_VALUE
# define AUTO_CALIB_FULL_TRAVEL_JITTER_VALUE 100
#endif
#ifndef AUTO_CALIB_ZERO_TRAVEL_THRESHOLD_VALUE
# define AUTO_CALIB_ZERO_TRAVEL_THRESHOLD_VALUE (DEFAULT_FULL_RANGE - AUTO_CALIB_FULL_TRAVEL_JITTER_VALUE)
#endif
#ifndef AUTO_CALIB_VALID_RELASING_TIME
# define AUTO_CALIB_VALID_RELASING_TIME 1000
#endif
void analog_matrix_init(void);
void analog_matrix_eeconfig_init(void);
bool update_raw_value(uint8_t row, uint8_t col, uint16_t value);
void update_travel_configs(void);
void update_key_config(uint8_t row, uint8_t col);
void analog_matrix_eeprom_update(const void *buf, void *addr, size_t len);
void analog_matrix_set_mins(uint16_t *min);
void analog_matrix_set_maxs(uint16_t *max);
uint8_t analog_matrix_get_travel(uint8_t row, uint8_t col);
uint8_t analog_matrix_get_key_mode(uint8_t row, uint8_t col);
bool analog_matrix_get_key_state(uint8_t row, uint8_t col);
bool analog_matrix_calibrating(void);
matrix_row_t analog_matrix_get_row(uint8_t row);
void analog_matrix_rx(uint8_t *data, uint8_t length);
void analog_matrix_task(void);
void analog_matrix_indicator(void);
void analog_matrix_clear(void);
void analog_matrix_clear_advance_keys(void);

View File

@@ -0,0 +1,24 @@
USE_FPU = yes
OPT_DEFS += -DANANLOG_MATRIX
ANALOG_MATRX_DIR = common/analog_matrix
SRC += \
i2c_master.c \
$(ANALOG_MATRX_DIR)/eeprom_he.c \
$(ANALOG_MATRX_DIR)/analog_matrix_scan.c \
$(ANALOG_MATRX_DIR)/profile.c \
$(ANALOG_MATRX_DIR)/usb_descriptor_override.c \
$(ANALOG_MATRX_DIR)/action_regular_trigger.c \
$(ANALOG_MATRX_DIR)/action_rapid_trigger.c \
$(ANALOG_MATRX_DIR)/action_okmc.c \
$(ANALOG_MATRX_DIR)/action_toggle.c \
$(ANALOG_MATRX_DIR)/action_joystick.c \
$(ANALOG_MATRX_DIR)/action_xinput.c \
$(ANALOG_MATRX_DIR)/action_socd.c \
$(ANALOG_MATRX_DIR)/sqrt.c \
$(ANALOG_MATRX_DIR)/game_controller_common.c \
$(ANALOG_MATRX_DIR)/analog_matrix.c
VPATH += $(TOP_DIR)/keyboards/keychron/$(ANALOG_MATRX_DIR)

View File

@@ -0,0 +1,71 @@
/* Copyright 2024 ~ 2025 @ Keychron (https://www.keychron.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
#ifndef PROFILE_COUNT
# define PROFILE_COUNT 3
#endif
#ifndef OKMC_COUNT
# define OKMC_COUNT 20
#endif
#ifndef SOCD_COUNT
# define SOCD_COUNT 20
#endif
#define CURVE_POINTS_COUNT 4
#define KC_ANALOG_MATRIX_VERSION 0x34340004
#define SIZE_OF_CALIB_VALUE_T 3 // Size of calibrated_value_t
#define SIZE_OF_ANALOG_KEY_CONFIG_T 4 // Size of analog_key_config_t
#define SIZE_OF_OKMC_CONFIG_T 19 // Size of okmc_config_t
#define SIZE_OF_SOCD_CONFIG_T 3 // Size of socd_config_t
#define SIZE_OF_POINT_T 2 // Size of point_t
#define PROFILE_NAME_LEN 30
// clang-format off
#define PROFILE_SIZE ( \
2 + \
(SIZE_OF_ANALOG_KEY_CONFIG_T) * (MATRIX_ROWS * MATRIX_COLS + 1) + \
(PROFILE_NAME_LEN) + \
(SIZE_OF_OKMC_CONFIG_T) * (OKMC_COUNT) + \
(SIZE_OF_SOCD_CONFIG_T) * (SOCD_COUNT) \
)
// clang-format on
#define OFFSET_CALIBRATION 0
#define OFFSET_CURRENT_PROFILE (OFFSET_CALIBRATION + 1)
#define OFFSET_CALIBRATED_DATA_START (OFFSET_CURRENT_PROFILE)
#define OFFSET_CALIBRATED_DATA_END (OFFSET_CALIBRATED_DATA_START + MATRIX_ROWS * MATRIX_COLS * SIZE_OF_CALIB_VALUE_T)
#define OFFSET_PROFILES_START (OFFSET_CALIBRATED_DATA_END)
#define OFFSET_PROFILES_END (OFFSET_PROFILES_START + PROFILE_SIZE * PROFILE_COUNT)
#define OFFSET_CURVE_PTS_START (OFFSET_PROFILES_END )
#define OFFSET_CURVE_PTS_END (OFFSET_CURVE_PTS_START + CURVE_POINTS_COUNT * SIZE_OF_POINT_T)
#define OFFSET_GAME_CONTROLLER_MODE_START OFFSET_CURVE_PTS_END
#define OFFSET_GAME_CONTROLLER_MODE_END (OFFSET_GAME_CONTROLLER_MODE_START + 1)
/* Size of analog matrix eeconfig */
#define EECONFIG_SIZE_ANALOG_MATRIX OFFSET_GAME_CONTROLLER_MODE_END
/* EE config data version */
#define EECONFIG_KB_DATA_VERSION KC_ANALOG_MATRIX_VERSION
#define EXTERNAL_EEPROM_OFFSET 4

View File

@@ -0,0 +1,369 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "hal.h"
#include "gpio.h"
#include "quantum.h"
#include "analog_matrix.h"
#include "debounce.h"
#ifdef LK_WIRELESS_ENABLE
# include "lpm.h"
#endif
#ifndef HC164_DS
# define HC164_DS B3
#endif
#ifndef HC164_CP
# define HC164_CP B5
#endif
#ifndef HC164_MR
# define HC164_MR D2
#endif
#define ADC_GRP_NUM_CHANNELS MATRIX_ROWS
#define ADC_GRP_BUF_DEPTH 1
#define UNUSED_DEPTH 0
extern matrix_row_t raw_matrix[MATRIX_ROWS];
extern matrix_row_t matrix[MATRIX_ROWS];
extern matrix_row_t game_controller_matrix[MATRIX_ROWS];
extern matrix_row_t okmc_matrix[MATRIX_ROWS];
matrix_row_t analog_raw_matrix[MATRIX_ROWS];
matrix_row_t changed_matrix[MATRIX_ROWS];
pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
matrix_row_t matrix_mask[MATRIX_ROWS];
matrix_row_t virtual_matrix[MATRIX_ROWS] = {0};
static bool matrix_changed;
static adcsample_t samples[ADC_GRP_NUM_CHANNELS * ADC_GRP_BUF_DEPTH];
static void adcerrorcallback(ADCDriver *adcp, adcerror_t err) {
dprintf("err\r\n");
(void)adcp;
(void)err;
}
// clang-format off
ADCConversionGroup adcgrpcfg = {
FALSE,
6,
NULL,
adcerrorcallback,
0, /* CR1 */
ADC_CR2_SWSTART, /* CR2 */
0, /* SMPR1 */
0, /* SMPR2 */
0, /* HTR */
0, /* LTR */
0, /* SQR1 */
0, /* SQR2 */
0 /* SQR3 */
};
// clang-format on
uint8_t pinToAdcChn(pin_t pin) {
switch (pin) {
case A0:
return ADC_CHANNEL_IN0;
case A1:
return ADC_CHANNEL_IN1;
case A2:
return ADC_CHANNEL_IN2;
case A3:
return ADC_CHANNEL_IN3;
case A4:
return ADC_CHANNEL_IN4;
case A5:
return ADC_CHANNEL_IN5;
case A6:
return ADC_CHANNEL_IN6;
case A7:
return ADC_CHANNEL_IN7;
case B0:
return ADC_CHANNEL_IN8;
case B1:
return ADC_CHANNEL_IN9;
case C0:
return ADC_CHANNEL_IN10;
case C1:
return ADC_CHANNEL_IN11;
case C2:
return ADC_CHANNEL_IN12;
case C3:
return ADC_CHANNEL_IN13;
case C4:
return ADC_CHANNEL_IN14;
case C5:
return ADC_CHANNEL_IN15;
}
return 0xFF;
}
static inline void shifter_delay(uint16_t n) {
while (n-- > 0) {
asm volatile("nop" ::: "memory");
}
}
static void HC164_output(uint16_t data, bool bit_flag) {
uint8_t n = 50;
ATOMIC_BLOCK_FORCEON {
for (uint8_t i = 0; i < 15; i++) {
if (data & 0x1) {
writePinHigh(HC164_DS);
} else {
writePinLow(HC164_DS);
}
shifter_delay(n);
writePinHigh(HC164_CP);
shifter_delay(n);
writePinLow(HC164_CP);
shifter_delay(n);
if (bit_flag) {
break;
} else {
data = data >> 1;
}
}
}
}
static bool select_col(uint8_t col) {
if (col == 0) {
writePinLow(HC164_MR);
shifter_delay(20);
writePinHigh(HC164_MR);
shifter_delay(20);
HC164_output(0x01, true);
}
return true;
}
static void unselect_col(uint8_t col) {
HC164_output(0x00, true);
return;
}
void select_all_cols(void) {}
static void unselect_cols(void) {}
void matrix_read_rows_on_col(uint8_t current_col, matrix_row_t row_shifter) {
// Select col
if (!select_col(current_col)) {
return; // skip NO_PIN col
}
wait_us(40);
uint8_t debouce_times = ANALOG_DEBOUCE_TIME;
uint8_t row_value = 0;
bool changed = false;
do {
adcConvert(&ADCD1, &adcgrpcfg, samples, ADC_GRP_BUF_DEPTH);
uint8_t row_value_recheck = 0;
for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) {
matrix_row_t row_mask = 0x01 << current_col;
update_raw_value(row_index, current_col, samples[row_index]);
bool pressed = analog_matrix_get_key_state(row_index, current_col);
if (pressed) {
if ((analog_raw_matrix[row_index] & row_mask) == 0) changed = true;
if (debouce_times == ANALOG_DEBOUCE_TIME) {
row_value |= (0x01 << row_index);
} else {
row_value_recheck |= (0x01 << row_index);
}
} else if (analog_raw_matrix[row_index] & row_mask) {
changed = true;
}
}
if (debouce_times != ANALOG_DEBOUCE_TIME && row_value != row_value_recheck) {
// Clear state when bounce occurs
changed = false;
}
} while (--debouce_times && changed);
if (changed) {
matrix_changed = true;
for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) {
if (row_value & (0x01 << row_index)) {
if ((analog_raw_matrix[row_index] & row_shifter) == 0) changed_matrix[row_index] |= row_shifter; // Mark changed matrix position
analog_raw_matrix[row_index] |= row_shifter; // Update matrix
} else {
if ((analog_raw_matrix[row_index] & row_shifter)) changed_matrix[row_index] |= row_shifter;
analog_raw_matrix[row_index] &= ~row_shifter;
}
}
}
// Unselect col
unselect_col(current_col);
}
void matrix_init_custom(void) {
uint32_t smpr[2] = {0, 0};
uint32_t sqr[3] = {0, 0, 0};
uint8_t chn;
uint8_t chn_cnt = 0;
#ifdef ANALOG_MATRIX_POWER_PIN
setPinOutput(ANALOG_MATRIX_POWER_PIN);
writePin(ANALOG_MATRIX_POWER_PIN, ANALOG_MATRIX_POWER_ENABLE_LEVEL);
#endif
#ifdef ANALOG_MATRIX_WAKEUP_PIN
setPinInputHigh(ANALOG_MATRIX_WAKEUP_PIN);
#endif
#ifdef ENCODER_SWITCH_PIN
setPinInputHigh(ENCODER_SWITCH_PIN);
#endif
// Init shift register control pins
setPinOutput(HC164_DS);
setPinOutput(HC164_CP);
setPinOutput(HC164_MR);
writePinLow(HC164_MR);
for (uint8_t x = 0; x < MATRIX_ROWS; x++) {
if (row_pins[x] != NO_PIN) {
palSetLineMode(row_pins[x], PAL_MODE_INPUT_ANALOG);
palWriteLine(row_pins[x], 0);
}
chn = pinToAdcChn(row_pins[x]);
if (chn < 0xFF) {
if (chn > 9)
smpr[0] |= ADC_SAMPLE_56 << ((chn - 10) * 3);
else
smpr[1] |= ADC_SAMPLE_56 << (chn * 3);
sqr[chn_cnt / 6] |= chn << ((chn_cnt % 6) * 5);
chn_cnt++;
}
}
adcgrpcfg.smpr1 = smpr[0];
adcgrpcfg.smpr2 = smpr[1];
adcgrpcfg.sqr3 = sqr[0];
adcgrpcfg.sqr2 = sqr[1];
adcgrpcfg.sqr1 = sqr[2];
unselect_cols();
adcStart(&ADCD1, NULL);
// Refer to STM32 AN4073 Option 2
SYSCFG->PMC |= SYSCFG_PMC_ADC1DC2;
// Value of initial ADC seems abnormal, scan to skip/drop i
for (uint8_t i = 0; i < 5; i++)
for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) {
matrix_read_rows_on_col(current_col, 0);
}
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
analog_raw_matrix[i] = 0;
changed_matrix[i] = 0;
}
analog_matrix_init();
}
bool matrix_scan_custom(matrix_row_t current_matrix[]) {
matrix_row_t last_raw_matrix[MATRIX_ROWS];
memcpy(last_raw_matrix, raw_matrix, sizeof(raw_matrix));
memcpy(virtual_matrix, matrix, sizeof(matrix));
memset(changed_matrix, 0, sizeof(changed_matrix));
matrix_changed = false;
// Set col, read rows
matrix_row_t row_shifter = MATRIX_ROW_SHIFTER;
for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++, row_shifter <<= 1) {
matrix_read_rows_on_col(current_col, row_shifter);
}
analog_matrix_task();
extern matrix_row_t analog_matrix_mask[];
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
raw_matrix[row] &= analog_matrix_mask[row];
}
#if defined(ENCODER_MATRIX_ROW) && defined(ENCODER_MATRIX_ROW)
if (readPin(ENCODER_SWITCH_PIN) == 0) {
if ((raw_matrix[ENCODER_MATRIX_ROW] & (1 << ENCODER_MATROX_COL)) == 0) {
matrix_changed = true;
raw_matrix[ENCODER_MATRIX_ROW] |= (1 << ENCODER_MATROX_COL);
}
} else {
if ((raw_matrix[ENCODER_MATRIX_ROW] & (1 << ENCODER_MATROX_COL))) {
matrix_changed = true;
raw_matrix[ENCODER_MATRIX_ROW] &= ~(1 << ENCODER_MATROX_COL);
}
}
#endif
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
virtual_matrix[row] |= (game_controller_matrix[row] | okmc_matrix[row]);
}
bool changed = memcmp(raw_matrix, last_raw_matrix, sizeof(last_raw_matrix)) != 0;
// changed = debounce(raw_matrix, matrix, MATRIX_ROWS, changed);
matrix_scan_kb();
return matrix_changed | changed;
}
void matrix_lpm(void) {
adcStop(&ADCD1);
#ifdef HC164_DS
setPinInputLow(HC164_DS);
#endif
#ifdef HC164_CP
setPinInputLow(HC164_CP);
#endif
#ifdef HC164_MR
setPinInputLow(HC164_MR);
#endif
#ifdef ANALOG_MATRIX_POWER_PIN
writePin(ANALOG_MATRIX_POWER_PIN, !ANALOG_MATRIX_POWER_ENABLE_LEVEL);
#endif
#ifdef ANALOG_MATRIX_WAKEUP_PIN
palEnableLineEvent(ANALOG_MATRIX_WAKEUP_PIN, PAL_EVENT_MODE_FALLING_EDGE);
#endif
// Set all row to input low
pin_t pins_row[MATRIX_ROWS] = MATRIX_ROW_PINS;
for (uint8_t x = 0; x < MATRIX_ROWS; x++) {
if (pins_row[x] != NO_PIN) {
setPinInputLow(pins_row[x]);
}
}
}

View File

@@ -0,0 +1,162 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 <stdint.h>
#include <stdbool.h>
// Analog key mode
enum {
AKM_GLOBAL = 0,
// Basic mode
AKM_REGULAR = 1, // Static actuation point
AKM_RAPID = 2, // Rapid Trigger
// Advance mode
AKM_DKS,
AKM_GAMEPAD,
AKM_TOGGLE,
};
// Analog key state
enum {
AKS_REGULAR_RELEASED,
AKS_REGULAR_PRESSED,
AKS_RAPID_RELEASED,
AKS_RAPID_PRESSED,
AKS_FULL_PRESSED,
};
#pragma pack(push)
#pragma pack(1)
typedef struct __attribute__((__packed__)){
uint8_t actn_pt;
uint8_t deactn_pt;
} activity_point_t;
typedef struct __attribute__((__packed__)){
uint8_t mode; // 1 byte
uint8_t state; // 1 byte
uint8_t travel; // 1 byte
uint8_t last_travel; // for debug
bool debug;
uint8_t r;
uint8_t c;
uint16_t value; // 2 bytes, value
uint16_t last_val; // 2 bytes, last value
activity_point_t regular; // 4 bytes, actuation point
union { // 4 bytes
activity_point_t rapid;
activity_point_t full;
};
union { // 2 bytes
uint8_t rpd_trig_sen; // rapid trig sensitivity
uint8_t okmc_idx;
uint8_t js_axis; // joystick x/y axis
uint8_t hold;
};
uint8_t rpd_trig_sen_rls;
} analog_key_t;
// size of analog_key_t is 16 bytes
typedef struct __attribute__((__packed__)) {
uint8_t actn_pt; // unit: 0.1mm
uint8_t deactn_pt; // unit: 0.1mm
} traval_config_t;
typedef struct __attribute__((__packed__)) {
uint8_t mode:2; // 2 bits, basic mode
uint8_t act_pt:6; // 6 bits
uint8_t rpd_trig_sen:6; // 6 bits rapid trig sensitivity, unit: 0.1mm
uint8_t rpd_trig_sen_deact:6; // 6 bits
uint8_t adv_mode:4; // 4 bits, advance mode
union { // 1 byte, additional information of advance mode
uint8_t adv_mode_data;
uint8_t okmc_idx; // okmc setting index
uint8_t js_axis; // joystick x/y axis
};
} analog_key_config_t;
// size of analog_key_config_t is 4 bytes
typedef struct __attribute__((__packed__)) {
uint8_t shallow_act:6; // unit: 0.1mm
uint8_t shallow_deact:6; // unit: 0.1mm
uint8_t deep_act:6; // unit: 0.1mm
uint8_t deep_deact:6; // unit: 0.1mm
} okmc_traval_config_t;
// size = 3 bytes
typedef struct
{
uint8_t shallow_act:4;
uint8_t shallow_deact:4;
uint8_t deep_act:4;
uint8_t deep_deact:4;
} okmc_action_t;
// size = 2 bytes
typedef struct __attribute__((__packed__)) {
okmc_traval_config_t travel; // 3
uint16_t keycode[4]; // 2*4
okmc_action_t action[4]; // 2*4
} okmc_config_t;
// size = 19 bytes
typedef struct __attribute__((__packed__)) {
uint8_t key_1_row:3;
uint8_t key_1_col:5;
uint8_t key_2_row:3;
uint8_t key_2_col:5;
uint8_t type;
} socd_config_t;
// size = 3 bytes
typedef struct __attribute__((__packed__)) {
uint8_t x; // 1
uint8_t y;; // 1
} point_t;
// size = 2 bytes
typedef struct __attribute__((__packed__)) {
analog_key_config_t global;
analog_key_config_t key_config[MATRIX_ROWS][MATRIX_COLS];
okmc_config_t okmc[OKMC_COUNT];
socd_config_t socd[SOCD_COUNT];
uint8_t name[PROFILE_NAME_LEN];
uint16_t crc16;
} analog_matrix_profile_t;
typedef struct __attribute__((__packed__)){
uint16_t zero_travel:12; // zero travel
uint16_t full_travel:12; // zero travel
} calibrated_value_t;
typedef struct __attribute__((__packed__)){
bool calibrated:1;
uint8_t pressed:1;
uint8_t wait_For_release:1;
uint8_t new_calib_value:1;
uint8_t state:4;
uint8_t confidence;
calibrated_value_t value;
uint32_t full_travel_time;
uint8_t cycle;
} calibration_t;
#pragma pack(pop)

View File

@@ -0,0 +1,158 @@
/* Copyright 2019 Nick Brassel (tzarc)
*
* 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 <string.h>
#if defined(EXTERNAL_EEPROM_WP_PIN)
# include "gpio.h"
#endif
# include "eeprom_he.h"
/*
Note that the implementations of eeprom_XXXX_YYYY on AVR are normally
provided by avr-libc. The same functions are reimplemented below and are
rerouted to the external i2c equivalent.
Seemingly, as this is compiled from within QMK, the object file generated
during the build overrides the avr-libc implementation during the linking
stage.
On other platforms such as ARM, there are no provided implementations, so
there is nothing to override during linkage.
*/
#include "wait.h"
#include "i2c_master.h"
#include "eeprom.h"
#include "eeprom_he.h"
// #define DEBUG_EEPROM_OUTPUT
#if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT)
# include "timer.h"
# include "debug.h"
#endif // DEBUG_EEPROM_OUTPUT
#ifdef USE_GPIOV1
# ifndef I2C1_SCL_PAL_MODE
# define I2C1_SCL_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN
# endif
# ifndef I2C1_SDA_PAL_MODE
# define I2C1_SDA_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN
# endif
#else
// The default PAL alternate modes are used to signal that the pins are used for I2C
# ifndef I2C1_SCL_PAL_MODE
# define I2C1_SCL_PAL_MODE 4
# endif
# ifndef I2C1_SDA_PAL_MODE
# define I2C1_SDA_PAL_MODE 4
# endif
#endif
static inline void fill_target_address(uint8_t *buffer, const void *addr) {
uintptr_t p = (uintptr_t)addr;
for (int i = 0; i < EXTERNAL_EEPROM_ADDRESS_SIZE; ++i) {
buffer[EXTERNAL_EEPROM_ADDRESS_SIZE - 1 - i] = p & 0xFF;
p >>= 8;
}
}
/* Override i2c_init */
void i2c_init(void) {
// Try releasing special pins for a short time
palSetLineMode(I2C1_SCL_PIN, PAL_MODE_INPUT);
palSetLineMode(I2C1_SDA_PIN, PAL_MODE_INPUT);
chThdSleepMilliseconds(10);
#if defined(USE_GPIOV1)
palSetLineMode(I2C1_SCL_PIN, I2C1_SCL_PAL_MODE);
palSetLineMode(I2C1_SDA_PIN, I2C1_SDA_PAL_MODE);
#else
palSetLineMode(I2C1_SCL_PIN, PAL_MODE_ALTERNATE(I2C1_SCL_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN);
palSetLineMode(I2C1_SDA_PIN, PAL_MODE_ALTERNATE(I2C1_SDA_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN);
#endif
}
void he_eeprom_driver_init(void) {
i2c_init();
#if defined(EXTERNAL_EEPROM_WP_PIN)
/* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */
writePin(EXTERNAL_EEPROM_WP_PIN, 1);
setPinInputHigh(EXTERNAL_EEPROM_WP_PIN);
#endif
}
void he_eeprom_driver_erase(void) {
#if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT)
uint32_t start = timer_read32();
#endif
uint8_t buf[EXTERNAL_EEPROM_PAGE_SIZE];
memset(buf, 0x00, EXTERNAL_EEPROM_PAGE_SIZE);
for (uint32_t addr = 0; addr < EXTERNAL_EEPROM_BYTE_COUNT; addr += EXTERNAL_EEPROM_PAGE_SIZE) {
he_eeprom_write_block(buf, (void *)(uintptr_t)addr, EXTERNAL_EEPROM_PAGE_SIZE);
}
}
void he_eeprom_read_block(void *buf, const void *addr, size_t len) {
uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE];
fill_target_address(complete_packet, addr);
i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE, 100);
i2c_receive(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), buf, len, 100);
}
void he_eeprom_write_block(const void *buf, void *addr, size_t len) {
uint8_t complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + EXTERNAL_EEPROM_PAGE_SIZE];
uint8_t * read_buf = (uint8_t *)buf;
uintptr_t target_addr = (uintptr_t)addr;
#if defined(EXTERNAL_EEPROM_WP_PIN)
setPinOutput(EXTERNAL_EEPROM_WP_PIN);
writePin(EXTERNAL_EEPROM_WP_PIN, 0);
#endif
while (len > 0) {
uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE;
int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset;
if (write_length > len) {
write_length = len;
}
fill_target_address(complete_packet, (const void *)target_addr);
for (uint8_t i = 0; i < write_length; i++) {
complete_packet[EXTERNAL_EEPROM_ADDRESS_SIZE + i] = read_buf[i];
}
i2c_transmit(EXTERNAL_EEPROM_I2C_ADDRESS((uintptr_t)addr), complete_packet, EXTERNAL_EEPROM_ADDRESS_SIZE + write_length, 100);
wait_ms(EXTERNAL_EEPROM_WRITE_TIME);
read_buf += write_length;
target_addr += write_length;
len -= write_length;
}
#if defined(EXTERNAL_EEPROM_WP_PIN)
/* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */
writePin(EXTERNAL_EEPROM_WP_PIN, 1);
setPinInputHigh(EXTERNAL_EEPROM_WP_PIN);
#endif
}

View File

@@ -0,0 +1,33 @@
/* Copyright 2019 Nick Brassel (tzarc)
*
* 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
#define EXTERNAL_EEPROM_BYTE_COUNT 1024 //
#define EXTERNAL_EEPROM_PAGE_SIZE 32
#define EXTERNAL_EEPROM_ADDRESS_SIZE 2
#define EXTERNAL_EEPROM_WRITE_TIME 5
#define EXTERNAL_EEPROM_I2C_BASE_ADDRESS 0b10100010
#ifndef EXTERNAL_EEPROM_I2C_ADDRESS
# define EXTERNAL_EEPROM_I2C_ADDRESS(loc) (EXTERNAL_EEPROM_I2C_BASE_ADDRESS)
#endif
void he_eeprom_driver_init(void);
void he_eeprom_driver_erase(void);
void he_eeprom_read_block(void *buf, const void *addr, size_t len);
void he_eeprom_write_block(const void *buf, void *addr, size_t len);

View File

@@ -0,0 +1,112 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "game_controller_common.h"
//#include "eeconfig.h"
#include "eeprom.h"
#include "nvm_eeprom_eeconfig_internal.h"
#include "analog_matrix.h"
uint8_t game_controller_mode;
point_t curve[CURVE_POINTS_COUNT];
float slope[CURVE_POINTS_COUNT - 1];
matrix_row_t game_controller_matrix[MATRIX_ROWS] = {0};
void game_controller_curve_init(point_t *pt) {
memcpy(curve, pt, CURVE_POINTS_COUNT * SIZE_OF_POINT_T);
if (curve[0].y == 0 && curve[1].y == 0 && curve[2].y == 0 && curve[3].y == 0) {
point_t default_curve[CURVE_POINTS_COUNT] = {{0, 0}, {10, 31}, {30, 95}, {40, 127}};
memcpy(curve, default_curve, sizeof(default_curve));
slope[0] = slope[1] = slope[2] = 3.175f;
}
}
void game_controller_mode_init(uint8_t mode) {
game_controller_mode = mode;
}
bool game_controller_mode_get(uint8_t *data) {
data[1] = game_controller_mode;
return true;
}
bool game_controller_mode_set(uint8_t mode) {
if (game_controller_mode == mode) return false;
game_controller_mode = mode;
// Save
if (!eeconfig_is_kb_datablock_valid()) eeprom_update_dword(EECONFIG_KEYBOARD, (EECONFIG_KB_DATA_VERSION));
analog_matrix_eeprom_update(&game_controller_mode, (uint8_t *)OFFSET_GAME_CONTROLLER_MODE_START, 1);
// usbDisconnectBus(&USBD1);
// usbStop(&USBD1);
// wait_ms(1000);
// usb_start(&USBD1);
return true;
}
bool game_controller_set_curve(point_t *pt) {
if (curve[0].x > curve[1].x || curve[1].x > curve[2].x || curve[2].x > curve[3].x) return false;
memcpy(curve, pt, sizeof(curve));
if (curve[0].x != curve[1].x) slope[0] = (float)(curve[1].y - curve[0].y) / (curve[1].x - curve[0].x);
if (curve[1].x != curve[2].x) slope[1] = (float)(curve[2].y - curve[1].y) / (curve[2].x - curve[1].x);
if (curve[2].x != curve[3].x) slope[2] = (float)(curve[3].y - curve[2].y) / (curve[3].x - curve[2].x);
analog_matrix_eeprom_update(curve, (uint8_t *)OFFSET_CURVE_PTS_START, CURVE_POINTS_COUNT * SIZE_OF_POINT_T);
return true;
}
bool game_controller_get_curve(uint8_t *data) {
memcpy(data, curve, sizeof(curve));
return true;
}
bool game_controller_xinput_enabled(void) {
return game_controller_mode & GC_MASK_XINPUT;
}
bool game_controller_type_enabled(void) {
return game_controller_mode & GC_MASK_TYPING;
}
void game_controller_clear(void) {
memset(game_controller_matrix, 0, sizeof(game_controller_matrix));
#ifdef JOYSTICK_ENABLE
# ifdef XINPUT_ENABLE
if (!game_controller_xinput_enabled())
# endif
{
extern void joystick_clear(void);
joystick_clear();
}
#endif
#ifdef XINPUT_ENABLE
# ifdef JOYSTICK_ENABLE
if (game_controller_xinput_enabled())
# endif
{
extern void xinput_clear(void);
xinput_clear();
}
#endif
}

View File

@@ -0,0 +1,65 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "stdint.h"
#include "analog_matrix_type.h"
/* Game controller axis */
enum {
GC_AXIS_X = 0,
GC_AXIS_Y,
GC_AXIS_Z,
GC_AXIS_RX,
GC_AXIS_RY,
GC_AXIS_RZ,
GC_AXIS_MAX,
};
enum {
GC_X_AXIS_LEFT = 0,
GC_X_AXIS_RIGHT,
GC_Y_AXIS_DOWN,
GC_Y_AXIS_UP,
GC_Z_AXIS_N,
GC_Z_AXIS_P,
GC_RX_AXIS_LEFT,
GC_RX_AXIS_RIGHT,
GC_RY_AXIS_DOWN,
GC_RY_AXIS_UP,
GC_RZ_AXIS_N,
GC_RZ_AXIS_P,
GC_MAX,
GC_BUTTON_0, // 13
GC_BUTTON_31 = GC_BUTTON_0+31,
GC_BUTTON_MAX,
};
enum {
GC_MASK_XINPUT = 0x01 << 0,
GC_MASK_TYPING = 0x01 << 1,
};
void game_controller_curve_init(point_t *pt);
void game_controller_mode_init(uint8_t mode);
bool game_controller_mode_get(uint8_t *data);
bool game_controller_mode_set(uint8_t mode);
bool game_controller_set_curve(point_t *pt);
bool game_controller_get_curve(uint8_t *data);
bool game_controller_xinput_enabled(void);
bool game_controller_type_enabled(void);
void game_controller_clear(void);

View File

@@ -0,0 +1,402 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "keychron_common.h"
#include "analog_matrix.h"
#include "game_controller_common.h"
#include "profile.h"
#include "action_socd.h"
#include "eeconfig.h"
#include "eeprom.h"
#include "nvm_eeprom_eeconfig_internal.h"
#ifdef ANANLOG_MATRIX
# ifndef PROF_TRIG_KEY_ROW
# define PROF_TRIG_KEY_ROW 2
# endif
# ifndef PROF_TRIG_KEY_COL
# define PROF_TRIG_KEY_COL 10
# endif
# ifndef PROF_1_KEY_ROW
# define PROF_1_KEY_ROW 4
# endif
# ifndef PROF_1_KEY_COL
# define PROF_1_KEY_COL 2
# endif
# ifndef PROF_2_KEY_ROW
# define PROF_2_KEY_ROW 4
# endif
# ifndef PROF_2_KEY_COL
# define PROF_2_KEY_COL 3
# endif
# ifndef PROF_3_KEY_ROW
# define PROF_3_KEY_ROW 4
# endif
# ifndef PROF_3_KEY_COL
# define PROF_3_KEY_COL 4
# endif
# define KEY_MASK(r, c) (virtual_matrix[r] & (1 << c))
enum {
ADV_MODE_CLEAR = 0,
ADV_MODE_OKMC,
ADV_MODE_GAME_CONTROLLER,
ADV_MODE_TOGGLE,
};
extern uint8_t profile_gobal_mode[PROFILE_COUNT];
extern uint16_t default_profiles[PROFILE_COUNT][MATRIX_ROWS][MATRIX_COLS];
static analog_matrix_profile_t profile[PROFILE_COUNT];
static uint8_t current_profile_index;
static analog_matrix_profile_t *cur_prof = &profile[0]; // current profile
uint8_t prof_combo = 0;
static uint8_t prof_ind_state = 0;
static uint32_t pro_ind_timer = 0;
void profile_init(bool reset) {
if (reset) {
// Write default profile setting
for (uint8_t i = 0; i < PROFILE_COUNT; i++)
profile_reset(i);
} else {
uint8_t *buf = (uint8_t *)malloc(EECONFIG_SIZE_ANALOG_MATRIX);
memset(buf, 0, EECONFIG_SIZE_ANALOG_MATRIX);
eeprom_read_block(buf, (void *)EECONFIG_BASE_ANALOG_MATRIX, EECONFIG_SIZE_ANALOG_MATRIX);
current_profile_index = buf[OFFSET_CURRENT_PROFILE];
if (current_profile_index >= PROFILE_COUNT) current_profile_index = 0;
cur_prof = &profile[current_profile_index];
// Load profile data
memcpy(profile, buf + OFFSET_PROFILES_START, PROFILE_SIZE * PROFILE_COUNT);
for (uint8_t i = 0; i < PROFILE_COUNT; i++) {
if (profile[i].global.mode == 0) profile[i].global.mode = profile_gobal_mode[i]; // global mode can't be 0
// Resotre to default if not in valid range
if (profile[i].global.act_pt == 0 || profile[i].global.act_pt > 40) profile[i].global.act_pt = DEFAULT_ACTUATION_POINT;
if (profile[i].global.rpd_trig_sen == 0 || profile[i].global.rpd_trig_sen > 39) profile[i].global.rpd_trig_sen = DEFAULT_RAPID_TRIGGER_SENSITIVITY;
if (profile[i].global.rpd_trig_sen_deact == 0 || profile[i].global.rpd_trig_sen > 39) profile[i].global.rpd_trig_sen_deact = profile[i].global.rpd_trig_sen;
}
free(buf);
}
}
analog_matrix_profile_t *profile_get(uint8_t index) {
return &profile[index];
}
analog_matrix_profile_t *profile_get_current(void) {
return cur_prof;
}
uint8_t profile_get_current_index(void) {
return current_profile_index;
}
bool profile_select(uint8_t prof_idx, bool indication) {
if (prof_idx >= PROFILE_COUNT) return false;
if (prof_idx != current_profile_index) {
current_profile_index = prof_idx;
cur_prof = &profile[prof_idx];
analog_matrix_clear();
update_travel_configs();
eeprom_update_dword(EECONFIG_KEYBOARD, (EECONFIG_KB_DATA_VERSION));
analog_matrix_eeprom_update(&prof_idx, (void *)OFFSET_CURRENT_PROFILE, 1);
analog_matrix_clear_advance_keys();
}
if (indication) {
# ifdef LED_MATRIX_ENABLE
if (!led_matrix_is_enabled()) led_matrix_enable_noeeprom();
# endif
# ifdef RGB_MATRIX_ENABLE
if (!rgb_matrix_is_enabled()) rgb_matrix_enable_noeeprom();
# endif
pro_ind_timer = timer_read32();
prof_ind_state = 0x86;
}
return true;
}
bool profile_get_raw_data(uint8_t prof_idx, uint16_t offset, uint8_t size, uint8_t *data) {
if (prof_idx >= PROFILE_COUNT || offset + size > sizeof(profile)) return false;
memset(data, 0, size);
if (offset + size > PROFILE_SIZE) size = PROFILE_SIZE - offset;
memcpy(data, (uint8_t *)(&profile[prof_idx]) + offset, size);
return true;
}
bool profile_set_traval(uint8_t prof_idx, uint8_t mode, uint8_t act_pt, uint8_t sens, uint8_t rls_sens, bool global, uint32_t row[]) {
analog_matrix_profile_t *prof = profile_get(prof_idx);
// Check validity
if (prof_idx >= PROFILE_COUNT || mode > AKM_RAPID || act_pt < 0 || act_pt > 39 || (global && mode == AKM_GLOBAL)) return false;
if (global) {
prof->global.mode = mode;
prof->global.act_pt = act_pt;
prof->global.rpd_trig_sen = sens;
prof->global.rpd_trig_sen_deact = rls_sens;
memset(row, 0xFF, sizeof(row[0]) * MATRIX_ROWS);
} else {
for (uint8_t r = 0; r < MATRIX_ROWS; r++)
for (uint8_t c = 0; c < MATRIX_COLS; c++) {
if (row[r] & (0x01 << c)) {
prof->key_config[r][c].mode = mode;
prof->key_config[r][c].act_pt = act_pt;
prof->key_config[r][c].rpd_trig_sen = sens;
prof->key_config[r][c].rpd_trig_sen_deact = rls_sens;
if (prof_idx == profile_get_current_index()) update_key_config(r, c);
}
}
}
return true;
}
bool profile_set_adv_mode(uint8_t *data) {
uint8_t prof_idx = data[0];
if (prof_idx >= PROFILE_COUNT) return false;
uint8_t mode = data[1];
uint8_t row = data[2];
uint8_t col = data[3];
uint8_t index = data[4];
if (row >= MATRIX_ROWS || col >= MATRIX_COLS) return false;
okmc_config_t okmc_config;
memset(&okmc_config, 0, sizeof(okmc_config));
analog_matrix_profile_t *prof = profile_get(prof_idx);
analog_key_config_t * p_key_cfg = &prof->key_config[row][col];
switch (mode) {
case ADV_MODE_CLEAR:
p_key_cfg->adv_mode = 0;
p_key_cfg->adv_mode_data = 0;
break;
case ADV_MODE_OKMC:
if (index >= OKMC_COUNT) return false;
okmc_config.travel.shallow_act = data[5];
okmc_config.travel.shallow_deact = data[6];
okmc_config.travel.deep_act = data[7];
okmc_config.travel.deep_deact = data[8];
memcpy(okmc_config.keycode, &data[9], sizeof(okmc_config.keycode));
memcpy(okmc_config.action, &data[17], sizeof(okmc_config.action));
prof->okmc[index] = okmc_config;
p_key_cfg->adv_mode = AKM_DKS;
p_key_cfg->okmc_idx = index;
break;
case ADV_MODE_GAME_CONTROLLER:
if (index >= GC_BUTTON_MAX) return false;
p_key_cfg->adv_mode = AKM_GAMEPAD;
p_key_cfg->js_axis = index;
break;
case ADV_MODE_TOGGLE:
p_key_cfg->adv_mode = AKM_TOGGLE;
p_key_cfg->adv_mode_data = 0;
break;
default:
return false;
}
if (prof_idx == profile_get_current_index()) update_key_config(row, col);
return true;
}
bool profile_set_socd(uint8_t *data) {
uint8_t prof_idx = data[0];
uint8_t row1 = data[1];
uint8_t col1 = data[2];
uint8_t row2 = data[3];
uint8_t col2 = data[4];
uint8_t index = data[5];
uint8_t type = data[6];
if (prof_idx >= PROFILE_COUNT || row1 >= MATRIX_ROWS || col1 >= MATRIX_COLS || row2 >= MATRIX_ROWS || col2 >= MATRIX_COLS || index >= SOCD_COUNT || type >= SOCD_PRI_MAX) return false;
// Same key is not allowed
if (type && row1 == row2 && col1 == col2) return false;
analog_matrix_profile_t *prof = &profile[prof_idx];
if (type) {
prof->socd[index].key_1_row = row1;
prof->socd[index].key_1_col = col1;
prof->socd[index].key_2_row = row2;
prof->socd[index].key_2_col = col2;
prof->socd[index].type = type;
} else {
memset(&prof->socd[index], 0, sizeof(socd_config_t));
}
return true;
}
bool profile_save(uint8_t prof_index) {
if (prof_index >= PROFILE_COUNT) return false;
analog_matrix_profile_t *prof = &profile[prof_index];
if (!eeconfig_is_kb_datablock_valid()) eeprom_update_dword(EECONFIG_KEYBOARD, (EECONFIG_KB_DATA_VERSION));
analog_matrix_eeprom_update(prof, (void *)OFFSET_PROFILES_START + prof_index * sizeof(analog_matrix_profile_t), sizeof(analog_matrix_profile_t));
return true;
}
bool profile_reset(uint8_t prof_index) {
if (prof_index >= PROFILE_COUNT) return false;
analog_matrix_profile_t *prof = &profile[prof_index];
memset(prof, 0, sizeof(profile[0]));
// Default
prof->global.mode = profile_gobal_mode[prof_index];
prof->global.act_pt = DEFAULT_ACTUATION_POINT;
prof->global.rpd_trig_sen_deact = prof->global.rpd_trig_sen = DEFAULT_RAPID_TRIGGER_SENSITIVITY;
for (uint8_t r = 0; r < MATRIX_ROWS; r++)
for (uint8_t c = 0; c < MATRIX_COLS; c++) {
prof->key_config[r][c].mode = default_profiles[prof_index][r][c] & 0x3;
prof->key_config[r][c].adv_mode = (default_profiles[prof_index][r][c] >> 2) & 0x07;
if (prof->key_config[r][c].adv_mode == AKM_GAMEPAD) {
prof->key_config[r][c].js_axis = default_profiles[prof_index][r][c] >> 5;
}
}
profile_save(prof_index);
return true;
}
void process_profile_select_combo(void) {
extern matrix_row_t virtual_matrix[MATRIX_ROWS];
if (prof_combo & KEY_PRESS_FN) {
if ((prof_combo & KEY_PRESS_P) == 0 && KEY_MASK(PROF_TRIG_KEY_ROW, PROF_TRIG_KEY_COL)) {
prof_combo |= KEY_PRESS_P;
} else if ((prof_combo & KEY_PRESS_P) && KEY_MASK(PROF_TRIG_KEY_ROW, PROF_TRIG_KEY_COL) == 0) {
prof_combo &= ~KEY_PRESS_P;
}
if ((prof_combo & KEY_PRESS_PROF_COMBO) == KEY_PRESS_PROF_COMBO) {
if (KEY_MASK(PROF_1_KEY_ROW, PROF_1_KEY_COL)) {
profile_select(0, true);
} else if (KEY_MASK(PROF_2_KEY_ROW, PROF_2_KEY_COL)) {
profile_select(1, true);
} else if (KEY_MASK(PROF_3_KEY_ROW, PROF_3_KEY_COL)) {
profile_select(2, true);
}
}
}
}
bool profile_set_name(uint8_t prof_idx, uint8_t len, uint8_t *name) {
if (prof_idx >= PROFILE_COUNT || len > 28) return false;
memset(profile[prof_idx].name, 0, PROFILE_NAME_LEN);
memcpy(profile[prof_idx].name, name, len);
return true;
}
bool process_record_profile(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case PROF1:
case PROF2:
case PROF3:
if (record->event.pressed) profile_select(keycode - PROF1, true);
return false; // Skip all further processing of this key
case MO(0)... MO(15):
if (record->event.pressed)
prof_combo |= KEY_PRESS_FN;
else
prof_combo &= ~KEY_PRESS_FN;
break;
default:
break;
}
return true;
}
void profile_indication_enable(void) {
pro_ind_timer = timer_read32();
prof_ind_state = 1;
}
void profile_indication_timer_check(void) {
if (pro_ind_timer && timer_elapsed32(pro_ind_timer) > 500) {
if ((prof_ind_state++ & 0xF) > 6) {
pro_ind_timer = prof_ind_state = 0;
# ifdef LED_MATRIX_ENABLE
if (!led_matrix_is_enabled()) led_matrix_disable_noeeprom();
# endif
# ifdef RGB_MATRIX_ENABLE
eeprom_read_block(&rgb_matrix_config, EECONFIG_RGB_MATRIX, sizeof(rgb_matrix_config));
if (!rgb_matrix_config.mode) {
eeconfig_update_rgb_matrix_default();
}
if (!rgb_matrix_is_enabled()) rgb_matrix_disable_noeeprom();
# endif
} else {
pro_ind_timer = timer_read32();
}
}
}
void profile_indication(void) {
if (prof_ind_state) {
static uint8_t prof_led_list[3] = PROFILE_LED_MATRIX_LIST;
rgb_matrix_set_color_all(prof_ind_state % 2 ? 0 : 255, 0, 0);
if (prof_ind_state & 0x80) {
rgb_matrix_set_color(prof_led_list[current_profile_index], prof_ind_state % 2 ? 0 : 255, prof_ind_state % 2 ? 0 : 255, prof_ind_state % 2 ? 0 : 255);
}
}
}
#endif

View File

@@ -0,0 +1,40 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "stdint.h"
#include "analog_matrix.h"
#include "action.h"
void profile_init(bool reset);
analog_matrix_profile_t *profile_get(uint8_t index);
analog_matrix_profile_t* profile_get_current(void);
uint8_t profile_get_current_index(void);
bool profile_select(uint8_t prof_idx, bool indication);
bool profile_get_raw_data(uint8_t prof_idx, uint16_t offset, uint8_t size, uint8_t *data);
bool profile_set_traval(uint8_t prof_idx, uint8_t mode, uint8_t act_pt, uint8_t sens, uint8_t rls_sens, bool global, uint32_t row[]);
bool profile_set_name(uint8_t prof_idx, uint8_t len, uint8_t *name);
bool profile_set_socd(uint8_t *data);
bool profile_reset(uint8_t prof_index);
bool profile_save(uint8_t prof_index);
bool profile_set_adv_mode(uint8_t *data);
void process_profile_select_combo(void);
bool process_record_profile(uint16_t keycode, keyrecord_t *record);
void profile_indication_enable(void);
void profile_indication_timer_check(void);
void profile_indication(void);

View File

@@ -0,0 +1,29 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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>
uint32_t sqrt_uint32(uint32_t n) {
uint32_t x = n;
uint32_t y = (x + 1) / 2;
while (y < x) {
x = y;
y = (x + n / x) / 2;
}
return x;
}

View File

@@ -0,0 +1,21 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "stdint.h"
uint32_t sqrt_uint32(uint32_t n);

View File

@@ -0,0 +1,144 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "quantum.h"
#include "analog_matrix.h"
#include "raw_hid.h"
#include "usb_main.h"
#include <stdio.h>
#include "game_controller_common.h"
extern USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor[];
extern const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[];
bool get_joy_stick_report_descriptor_info(uint16_t *pos_begin, uint16_t *pos_end) {
uint16_t share_report_len = ConfigurationDescriptor->Shared_HID.HIDReportLength;
uint8_t joystick_report_key_begin[4] = {0x05, 0x01, 0x09, 0x04};
bool find = false;
for (uint16_t i = 0; i < share_report_len - 4; i++) {
if (memcmp(SharedReport + i, joystick_report_key_begin, 4) == 0) {
// Search joystick report descriptor end position starting from pos_begin
for (uint16_t j = i; j < share_report_len - 2; j++) {
if (SharedReport[j] == 0xC0 && SharedReport[j + 1] == 0xC0) {
*pos_begin = i;
*pos_end = j + 2;
find = true;
break;
}
}
break;
}
}
return find;
}
void get_usb_descriptor_kb(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void **const DescriptorAddress, uint16_t *size) {
#if defined(JOYSTICK_ENABLE) && defined(XINPUT_ENABLE)
const uint8_t DescriptorType = (wValue >> 8);
const uint8_t DescriptorIndex = (wValue & 0xFF);
static bool recal = false;
static uint16_t pos_begin, pos_end;
static uint16_t original_hid_rpt_len = 0;
if (original_hid_rpt_len == 0) original_hid_rpt_len = ConfigurationDescriptor->Shared_HID.HIDReportLength;
switch (DescriptorType) {
case DTYPE_String:
# if defined(XINPUT_ENABLE)
if (!game_controller_xinput_enabled() && DescriptorIndex == 0xEE) {
*DescriptorAddress = NULL;
*size = 0;
}
# endif
break;
case DTYPE_Configuration:
# if defined(JOYSTICK_SHARED_EP)
{
if (game_controller_xinput_enabled()) {
if (!recal && get_joy_stick_report_descriptor_info(&pos_begin, &pos_end)) {
ConfigurationDescriptor->Shared_HID.HIDReportLength -= (pos_end - pos_begin);
recal = true;
}
} else {
uint8_t xinput_desc_len = sizeof(USB_Descriptor_Interface_t) + 0x11 + sizeof(USB_Descriptor_Endpoint_t) * 2;
ConfigurationDescriptor->Config.TotalInterfaces = TOTAL_INTERFACES - 1;
ConfigurationDescriptor->Config.TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t) - xinput_desc_len;
*size = sizeof(USB_Descriptor_Configuration_t) - xinput_desc_len;
}
}
# else
// TODO:
// uint8_t xinput_desc_len = sizeof(USB_Descriptor_Interface_t) + 0x11 + sizeof(USB_Descriptor_Endpoint_t) * 2;
// uint8_t joy_desc_len = sizeof(USB_Descriptor_Interface_t) + sizeof(USB_HID_Descriptor_HID_t) + sizeof(USB_Descriptor_Endpoint_t);
// if (xinput_mode) {
// static USB_Descriptor_Configuration_t *xinput_mode_desc = NULL;
// if (xinput_mode_desc == NULL) {
// xinput_mode_desc = (void *)malloc(sizeof(USB_Descriptor_Configuration_t));
// memcpy(xinput_mode_desc, &ConfigurationDescriptor, sizeof(USB_Descriptor_Configuration_t));
// // Move xinput descriptor
// memcpy(&xinput_mode_desc->Joystick_Interface, &xinput_mode_desc->Xinput_Interface, xinput_desc_len);
// }
// *DescriptorAddress = xinput_mode_desc;
// *Size -= joy_desc_len;
// } else {
// *Size -= xinput_desc_len;
// }
# endif
break;
case HID_DTYPE_Report:
switch (wIndex) {
# ifdef SHARED_EP_ENABLE
case SHARED_INTERFACE:
*DescriptorAddress = &SharedReport;
uint16_t len = ConfigurationDescriptor->Shared_HID.HIDReportLength;
if (game_controller_xinput_enabled()) {
static uint8_t *input_mode_rpt_desc = NULL;
if (input_mode_rpt_desc == NULL) {
input_mode_rpt_desc = (uint8_t *)malloc(original_hid_rpt_len);
memcpy(input_mode_rpt_desc, &SharedReport, original_hid_rpt_len);
if (pos_begin < original_hid_rpt_len - 4 && pos_end < original_hid_rpt_len - 2) {
// Move xinput descriptor
memcpy(input_mode_rpt_desc + pos_begin, input_mode_rpt_desc + pos_end, original_hid_rpt_len - pos_end);
}
}
*DescriptorAddress = input_mode_rpt_desc;
*size = len;
}
break;
# endif
}
}
#endif
}
void get_usb_vendor_descriptor_kb(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void **const DescriptorAddress, uint16_t *size) {
#if XINPUT_ENABLE
if (!game_controller_xinput_enabled()) {
*DescriptorAddress = NULL;
*size = 0;
}
#endif
}

View File

@@ -0,0 +1,74 @@
/* Copyright 2024 @ Keychron (https://www.keychron.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 "game_controller_common.h"
// bit 0~1: basic mode
// bit 2~4: adv mode
// bit 5~15: axis
// a-advance mode, b-mode
#define JSM(a, m) ((a << 3 | AKM_GAMEPAD) << 2) | (m & 3)
#define TGM(m) (AKM_TOGGLE << 2) | (m & 3)
#define AX(x) JSM(x, 0)
#define BTN(x) JSM((x + GC_BUTTON_0), 0)
/* Xbox mapping
BTN(0) - A
BTN(1) - B
BTN(2) - X
BTN(3) - Y
BTN(4) - LB
BTN(5) - RB
BTN(6) - View
BTN(7) - Menu
BTN(8) - L3
BTN(9) - R3
BTN(10) - Up
BTN(11) - Down
BTN(12) - Left
BTN(13) - Right
BTN(14) - Xbox
*/
/* Xinput buttons */
#define XB_A BTN(0)
#define XB_B BTN(1)
#define XB_X BTN(2)
#define XB_Y BTN(3)
#define XB_LB BTN(4)
#define XB_RB BTN(5)
#define XB_VIEW BTN(6)
#define XB_MEMU BTN(7)
#define XB_L3 BTN(8)
#define XB_R3 BTN(9)
#define XB_UP BTN(10)
#define XB_DOWN BTN(11)
#define XB_LEFT BTN(12)
#define XB_RGHT BTN(13)
#define XB_XBOX BTN(14)
#define XB_LT AX(4)
#define XB_RT AX(5)
/* Left Stick */
#define LS_LEFT AX(0)
#define LS_RGHT AX(1)
#define LS_UP AX(3)
#define LS_DOWN AX(2)
/* Right Stick */
#define RS_LEFT AX(6)
#define RS_RGHT AX(7)
#define RS_UP AX(9)
#define RS_DOWN AX(8)

View File

@@ -24,7 +24,7 @@ void eeconfig_init_kb_datablock(void) {
extern void debounce_config_reset(void);
debounce_config_reset();
#endif
#if defined(SNAP_CLICK_ENABLE)
#if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
extern void snap_click_config_reset(void);
snap_click_config_reset();
#endif

View File

@@ -38,7 +38,7 @@
#define EECONFIG_BASE_DYNAMIC_DEBOUNCE EECONFIG_END_HSUSB_REPORT_RATE
#define EECONFIG_END_DYNAMIC_DEBOUNCE (EECONFIG_BASE_DYNAMIC_DEBOUNCE + __EECONFIG_SIZE_DEBOUNCE)
#ifdef SNAP_CLICK_ENABLE
#if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
# include "eeconfig_snap_click.h"
# define __EECONFIG_SIZE_SNAP_CLICK EECONFIG_SIZE_SNAP_CLICK
#else
@@ -47,13 +47,22 @@
#define EECONFIG_BASE_SNAP_CLICK (EECONFIG_END_DYNAMIC_DEBOUNCE)
#define EECONFIG_END_SNAP_CLICK (EECONFIG_BASE_SNAP_CLICK + __EECONFIG_SIZE_SNAP_CLICK)
#ifdef ANANLOG_MATRIX
# include "analog_matrix_eeconfig.h"
# define __EECONFIG_SIZE_ANALOG_MATRIX EECONFIG_SIZE_ANALOG_MATRIX
#else
# define __EECONFIG_SIZE_ANALOG_MATRIX 0
#endif
#define EECONFIG_BASE_ANALOG_MATRIX EECONFIG_END_SNAP_CLICK
#define EECONFIG_END_ANALOG_MATRIX (EECONFIG_BASE_ANALOG_MATRIX + __EECONFIG_SIZE_ANALOG_MATRIX)
#if defined(KEYCHRON_RGB_ENABLE) && defined(RGB_MATRIX_ENABLE)
# include "eeconfig_custom_rgb.h"
# define __EECONFIG_SIZE_CUSTOM_RGB EECONFIG_SIZE_CUSTOM_RGB
#else
# define __EECONFIG_SIZE_CUSTOM_RGB 0
#endif
#define EECONFIG_BASE_CUSTOM_RGB EECONFIG_END_SNAP_CLICK
#define EECONFIG_BASE_CUSTOM_RGB EECONFIG_END_ANALOG_MATRIX
#define EECONFIG_END_CUSTOM_RGB (EECONFIG_BASE_CUSTOM_RGB + __EECONFIG_SIZE_CUSTOM_RGB)
#if defined(WIRELESS_CONFIG_ENABLE)

View File

@@ -37,6 +37,9 @@
#ifdef SNAP_CLICK_ENABLE
# include "snap_click.h"
#endif
#ifdef ANANLOG_MATRIX
# include "analog_matrix.h"
#endif
#include "config.h"
#include "version.h"
#ifdef STATE_NOTIFY_ENABLE
@@ -55,6 +58,25 @@
# define P2P4G_CELAR_MASK P2P4G_CLEAR_PAIRING_TYPE_C
#endif
#ifdef ANANLOG_MATRIX
# ifndef J_KEY_ROW
# define J_KEY_ROW 3
# endif
# ifndef J_KEY_COL
# define J_KEY_COL 7
# endif
# ifndef Z_KEY_ROW
# define Z_KEY_ROW 4
# endif
# ifndef Z_KEY_COL
# define Z_KEY_COL 2
# endif
#endif
#define KEY_MASK(r, c) (virtual_matrix[r] & (1 << c))
enum {
BACKLIGHT_TEST_OFF = 0,
BACKLIGHT_TEST_WHITE,
@@ -122,7 +144,13 @@ static inline void factory_timer_check(void) {
#endif
backlight_test_mode = BACKLIGHT_TEST_OFF;
eeconfig_init();
#ifdef ANANLOG_MATRIX
eeconfig_disable();
analog_matrix_eeconfig_init();
analog_matrix_clear_advance_keys();
#endif
if (!eeconfig_is_enabled()) eeconfig_init();
eeconfig_read_keymap(&keymap_config);
#if !defined(KEYCOMBO_OS_SELECT_ENABLE) && !defined(KEYCOMBO_OS_TOGGLE_ENABLE)
default_layer_set(default_layer_tmp);
@@ -130,7 +158,7 @@ static inline void factory_timer_check(void) {
#ifdef DYNAMIC_DEBOUNCE_ENABLE
debounce_config_reset();
#endif
#ifdef SNAP_CLICK_ENABLE
#if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
snap_click_config_reset();
#endif
#ifdef LED_MATRIX_ENABLE
@@ -319,8 +347,35 @@ bool factory_test_indicator(void) {
return true;
}
#ifdef ANANLOG_MATRIX
void analog_matrix_factory_reset(void) {
extern matrix_row_t virtual_matrix[MATRIX_ROWS];
if (factory_reset_state & KEY_PRESS_FN) {
if ((factory_reset_state & KEY_PRESS_J) == 0 && KEY_MASK(J_KEY_ROW, J_KEY_COL)) {
factory_reset_state |= KEY_PRESS_J;
} else if ((factory_reset_state & KEY_PRESS_J) && KEY_MASK(J_KEY_ROW, J_KEY_COL) == 0) {
factory_reset_state &= ~KEY_PRESS_J;
factory_reset_timer = 0;
}
if ((factory_reset_state & KEY_PRESS_Z) == 0 && KEY_MASK(Z_KEY_ROW, Z_KEY_COL)) {
factory_reset_state |= KEY_PRESS_Z;
} else if ((factory_reset_state & KEY_PRESS_Z) && KEY_MASK(Z_KEY_ROW, Z_KEY_COL) == 0) {
factory_reset_state &= ~KEY_PRESS_Z;
factory_reset_timer = 0;
}
if (factory_reset_state == KEY_PRESS_FACTORY_RESET && factory_reset_timer == 0) factory_timer_start();
}
}
#endif
bool factory_test_task(void) {
if (factory_reset_timer) factory_timer_check();
#ifdef ANANLOG_MATRIX
analog_matrix_factory_reset();
#endif
return true;
}

View File

@@ -33,6 +33,9 @@
#include "config.h"
#include "usb_descriptor.h"
#include "raw_hid.h"
#ifdef ANANLOG_MATRIX
# include "profile.h"
#endif
#ifdef KEYCOMBO_OS_SELECT_ENABLE
# ifndef MAC_BASE_LAYER
@@ -90,7 +93,7 @@ void keychron_common_init(void) {
extern void report_rate_init(void);
report_rate_init();
#endif
#ifdef SNAP_CLICK_ENABLE
#if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
extern void snap_click_init(void);
snap_click_init();
#endif
@@ -274,6 +277,11 @@ void keychron_common_task(void) {
is_siri_active = false;
siri_timer = 0;
}
#ifdef ANANLOG_MATRIX
process_profile_select_combo();
#endif
#if defined(WIN_LOCK_HOLD_TIME)
if (winlock_timer) {
if (keymap_config.no_gui) {

View File

@@ -126,6 +126,14 @@ enum {
// clang-format on
static_assert(NEW_SAFE_RANGE <= 0x7E1F /* QK_KB_31 */, "Keycode overflow");
#ifdef ANANLOG_MATRIX
enum {
KEY_PRESS_FN = 0x01 << 0,
KEY_PRESS_P = 0x01 << 1,
KEY_PRESS_PROF_COMBO = KEY_PRESS_FN | KEY_PRESS_P,
};
#endif
typedef struct PACKED {
uint8_t len;
uint8_t keycode[3];

View File

@@ -63,7 +63,7 @@ void get_support_feature(uint8_t *data) {
# ifdef DYNAMIC_DEBOUNCE_ENABLE
| FEATURE_DYNAMIC_DEBOUNCE
# endif
# ifdef SNAP_CLICK_ENABLE
# if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
| FEATURE_SNAP_CLICK
# endif
# ifdef KEYCHRON_RGB_ENABLE
@@ -74,6 +74,31 @@ void get_support_feature(uint8_t *data) {
data[2] = (FEATURE_QUICK_START | FEATURE_NKRO) >> 8;
}
# ifdef ANANLOG_MATRIX
void via_raw_hid_send(uint8_t src, uint8_t *data, uint8_t length);
void send_analog_matrix(uint8_t *data, uint8_t length) {
uint8_t offset = data[2];
uint8_t rows = 28 / ((MATRIX_COLS + 7) / 8);
uint8_t i = 3;
for (uint8_t row = 0; row < rows && row + offset < MATRIX_ROWS; row++) {
matrix_row_t value = analog_matrix_get_row(row + offset);
# if (MATRIX_COLS > 24)
data[i++] = (value >> 24) & 0xFF;
# endif
# if (MATRIX_COLS > 16)
data[i++] = (value >> 16) & 0xFF;
# endif
# if (MATRIX_COLS > 8)
data[i++] = (value >> 8) & 0xFF;
# endif
data[i++] = value & 0xFF;
}
via_raw_hid_send(RAW_HID_SRC_USB, data, length);
}
# endif
void get_firmware_version(uint8_t *data) {
uint8_t i = 0;
data[i++] = 'v';
@@ -104,6 +129,13 @@ void kc_raw_hid_send(uint8_t src, uint8_t *data, uint8_t len) {
}
bool kc_raw_hid_rx(uint8_t src, uint8_t *data, uint8_t length) {
# if defined(ANANLOG_MATRIX) && defined(VIA_ENABLE)
if (src == RAW_HID_SRC_USB && data[0] == id_get_keyboard_value && data[1] == id_switch_matrix_state) {
send_analog_matrix(data, length);
return true;
}
# endif
switch (data[0]) {
# ifdef VIA_ENABLE
case id_get_protocol_version:
@@ -145,7 +177,7 @@ bool kc_raw_hid_rx(uint8_t src, uint8_t *data, uint8_t length) {
# ifdef DYNAMIC_DEBOUNCE_ENABLE
| MISC_DEBOUNCE
# endif
# ifdef SNAP_CLICK_ENABLE
# if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
| MISC_SNAP_CLICK
# endif
# if defined(LK_WIRELESS_ENABLE) || defined(KC_BLUETOOTH_ENABLE)
@@ -174,7 +206,7 @@ bool kc_raw_hid_rx(uint8_t src, uint8_t *data, uint8_t length) {
debounce_rx(data, length);
break;
# endif
# if defined(SNAP_CLICK_ENABLE)
# if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
case SNAP_CLICK_GET_INFO ... SNAP_CLICK_SAVE:
snap_click_rx(data, length);
break;

View File

@@ -25,6 +25,9 @@
#ifdef RETAIL_DEMO_ENABLE
# include "retail_demo.h"
#endif
#ifdef ANANLOG_MATRIX
#include "profile.h"
#endif
__attribute__((weak)) bool process_record_keychron_kb(uint16_t keycode, keyrecord_t *record) {
return true;
@@ -33,6 +36,10 @@ __attribute__((weak)) bool process_record_keychron_kb(uint16_t keycode, keyrecor
bool process_record_keychron(uint16_t keycode, keyrecord_t *record) {
if (!process_record_keychron_common(keycode, record)) return false;
#ifdef ANANLOG_MATRIX
if (!process_record_profile(keycode, record)) return false;
#endif
#if defined(LK_WIRELESS_ENABLE) || defined(KC_BLUETOOTH_ENABLE)
extern bool process_record_wireless(uint16_t keycode, keyrecord_t * record);
if (!process_record_wireless(keycode, record)) return false;
@@ -47,7 +54,7 @@ bool process_record_keychron(uint16_t keycode, keyrecord_t *record) {
if (!process_record_factory_test(keycode, record)) return false;
#endif
#ifdef SNAP_CLICK_ENABLE
#if defined(SNAP_CLICK_ENABLE) && !defined(ANANLOG_MATRIX)
extern bool process_record_snap_click(uint16_t keycode, keyrecord_t * record);
if (!process_record_snap_click(keycode, record)) return false;
#endif
@@ -76,6 +83,9 @@ __attribute__((weak)) bool led_matrix_indicators_keychron(void) {
extern bool led_matrix_indicators_bt(void);
led_matrix_indicators_bt();
# endif
#ifdef ANANLOG_MATRIX
analog_matrix_indicator();
#endif
# ifdef FACTORY_TEST_ENABLE
factory_test_indicator();
# endif
@@ -92,6 +102,9 @@ __attribute__((weak)) bool rgb_matrix_indicators_keychron(void) {
extern bool rgb_matrix_indicators_bt(void);
rgb_matrix_indicators_bt();
# endif
#ifdef ANANLOG_MATRIX
analog_matrix_indicator();
#endif
# ifdef FACTORY_TEST_ENABLE
factory_test_indicator();
# endif