Last active
October 14, 2025 22:46
-
-
Save BorisTestov/2d96e030f31fc903820b17d309beeaef to your computer and use it in GitHub Desktop.
Hall Effect Utilities for QMK Keychron Keyboards
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| /* | |
| * Hall Effect Utilities for QMK | |
| * | |
| * DISCLAIMER: | |
| * This code was written by Boris Testov for personal use on K6 HE keyboard. | |
| * You are free to use, modify, and distribute this code for any purpose, including | |
| * commercial use. However, this code is provided "AS IS" without any warranty of | |
| * any kind, express or implied. The author makes no guarantees about its | |
| * functionality, reliability, or suitability for any particular purpose. | |
| * Use at your own risk. | |
| * | |
| * Note: This code covers core analog matrix functionality but may not include every advanced feature available in the system. | |
| * | |
| * If you find this helpful, consider buying me a coffee: | |
| * https://buymeacoffee.com/boristestov | |
| * | |
| * Usage in keymap.c: | |
| * 1. Add include at top: #include "he_utils.h" | |
| * 2. Use functions in keyboard_post_init_user() | |
| * 3. See individual function comments below for detailed examples | |
| */ | |
| #include "quantum.h" | |
| #include "analog_matrix.h" | |
| #include "profile.h" | |
| #include "action_socd.h" | |
| #include "game_controller_common.h" | |
| #include "xinput_keycodes.h" | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| // ============================================================================= | |
| // CONSTANTS AND DEFINES | |
| // ============================================================================= | |
| // Advanced Mode Constants | |
| // ======================== | |
| // These control the advanced behavior modes for individual keys (from profile.c enum) | |
| #define ADV_MODE_CLEAR 0 // Clear/disable advanced mode (normal key behavior) | |
| #define ADV_MODE_OKMC 1 // Dynamic Key Strokes mode (OKMC - One Key Multi Code) | |
| #define ADV_MODE_GAME_CONTROLLER 2 // Game controller/XInput mode for analog axes and buttons | |
| #define ADV_MODE_TOGGLE 3 // Toggle mode - key acts as a toggle switch | |
| // Hall Effect Calibration Constants | |
| // ================================== | |
| // These commands are sent to the analog matrix controller via analog_matrix_rx() | |
| #define AMC_CALIBRATE 0x40 // Command to control calibration mode | |
| #define AMC_GET_CALIBRATE_STATE 0x41 // Query current calibration state | |
| #define AMC_GET_CALIBRATED_VALUE 0x42 // Retrieve calibrated values for a key | |
| // Calibration States | |
| // ================== | |
| // The analog matrix controller maintains these internal states during calibration | |
| #define CALIB_OFF 0 // Calibration inactive - normal operation | |
| #define CALIB_ZERO_TRAVEL_POWER_ON 1 // Auto zero calibration on power-up (not used in manual flow) | |
| #define CALIB_ZERO_TRAVEL_MANUAL 2 // Manual zero travel calibration active | |
| // LED: Solid RED - Keys should be completely released (not pressed at all) | |
| #define CALIB_FULL_TRAVEL_MANUAL 3 // Manual full travel calibration active | |
| // LED: Solid PURPLE - Press keys to maximum travel (turns GREEN when complete) | |
| #define CALIB_SAVE_AND_EXIT 4 // Save calibration data and exit to normal mode (auto-saved) | |
| #define CALIB_CLEAR 5 // Clear all stored calibration data | |
| // ============================================================================= | |
| // FUNCTION DEFINITIONS | |
| // ============================================================================= | |
| /* | |
| * ============================================================================ | |
| * BASIC KEY CONFIGURATION FUNCTIONS | |
| * ============================================================================ | |
| */ | |
| /** | |
| * @brief Set custom actuation point for a specific key | |
| * | |
| * @details | |
| * Configures the actuation distance for an individual key, overriding the global profile setting. | |
| * The system automatically calculates deactuation point as (actuation_point - 3) with a minimum of 0, | |
| * providing 0.3mm hysteresis to prevent bouncing. | |
| * | |
| * @param row Matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param actuation_point Actuation distance in 0.1mm units (10 = 1mm, 5 = 0.5mm, 0 = use global setting) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note FALLBACK BEHAVIOR: If actuation_point = 0: Uses global profile setting (cur_prof->global.act_pt) | |
| * @note If actuation_point > maximum allowed value: The key config stores the value as-is, but | |
| * the system will clamp it during processing. Values that exceed physical limits will | |
| * effectively behave as maximum travel. | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_key_actuation_point(0, 1, 3); // "1" key = 0.3mm actuation point | |
| * set_key_actuation_point(0, 2, 10); // "2" key = 1.0mm actuation point | |
| * set_key_actuation_point(1, 0, 0); // Use global setting for this key | |
| * ``` | |
| */ | |
| static inline void set_key_actuation_point(uint8_t row, uint8_t col, uint8_t actuation_point) { | |
| analog_matrix_profile_t *cur_prof = profile_get_current(); | |
| analog_key_config_t *p_key_cfg = &cur_prof->key_config[row][col]; | |
| // Set custom actuation point (0 = use global setting) | |
| p_key_cfg->act_pt = actuation_point; | |
| // Apply new key config | |
| update_key_config(row, col); | |
| } | |
| /** | |
| * @brief Configure rapid trigger sensitivity for a specific key | |
| * | |
| * @details | |
| * Sets up rapid trigger mode for an individual key with custom press and release sensitivities. | |
| * Rapid trigger activates/deactivates keys based on movement sensitivity rather than fixed positions. | |
| * This function automatically sets the key mode to AKM_RAPID, overriding any previous mode. | |
| * | |
| * @param row Matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param press_sensitivity Press sensitivity distance in 0.1mm units (10 = 1mm, 5 = 0.5mm, 0 = use global setting) | |
| * @param release_sensitivity Release sensitivity distance in 0.1mm units (10 = 1mm, 5 = 0.5mm, 0 = use global setting) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note FALLBACK BEHAVIOR: If press_sensitivity = 0: Uses global profile setting (cur_prof->global.rpd_trig_sen) | |
| * @note If release_sensitivity = 0: Uses global profile setting. If global release sensitivity | |
| * is also 0, falls back to global press sensitivity (cur_prof->global.rpd_trig_sen) | |
| * @note Values exceeding physical limits will be clamped during processing | |
| * @note All sensitivity values are internally scaled by TRAVEL_SCALE (6) for precision | |
| * @note MODE OVERRIDE: This function automatically sets the key mode to AKM_RAPID, overriding any previous mode | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_key_rapid_trigger(0, 1, 3, 3); // "1" key = 0.3mm press/release sensitivity | |
| * set_key_rapid_trigger(0, 3, 5, 8); // "3" key = 0.5mm press, 0.8mm release | |
| * set_key_rapid_trigger(2, 4, 0, 0); // Use global settings for this key | |
| * ``` | |
| */ | |
| static inline void set_key_rapid_trigger(uint8_t row, uint8_t col, uint8_t press_sensitivity, uint8_t release_sensitivity) { | |
| analog_matrix_profile_t *cur_prof = profile_get_current(); | |
| analog_key_config_t *p_key_cfg = &cur_prof->key_config[row][col]; | |
| // Set rapid trigger mode | |
| p_key_cfg->mode = AKM_RAPID; | |
| // Set sensitivities | |
| p_key_cfg->rpd_trig_sen = press_sensitivity; | |
| p_key_cfg->rpd_trig_sen_deact = release_sensitivity; | |
| // Apply new key config | |
| update_key_config(row, col); | |
| } | |
| /** | |
| * @brief Configure Last Key Priority SOCD (Simultaneous Opposing Cardinal Directions) | |
| * | |
| * @details | |
| * Sets up conflict resolution where the last pressed key takes priority when both keys are pressed. | |
| * SOCD is an array-based system with limited slots available (SOCD_COUNT = 20 by default). | |
| * This is commonly used for WASD gaming where A+D or W+S conflicts are resolved | |
| * by prioritizing whichever key was pressed most recently. | |
| * | |
| * @param key1_row First key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key1_col First key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param key2_row Second key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key2_col Second key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param socd_index SOCD configuration slot index (0 to SOCD_COUNT-1, so 0-19 by default) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note SOCD_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each socd_index represents one slot in the socd[SOCD_COUNT] array | |
| * @note Think of socd_index as "which conflict resolution rule number you're setting up" | |
| * @note When both keys are pressed simultaneously, the last pressed key takes priority | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_last_key_priority(1, 0, 1, 3, 0); // W vs S keys, using slot 0 | |
| * set_last_key_priority(1, 1, 1, 2, 1); // A vs D keys, using slot 1 | |
| * // You could set up to 18 more pairs using slots 2-19 | |
| * ``` | |
| */ | |
| static inline void set_last_key_priority(uint8_t key1_row, uint8_t key1_col, uint8_t key2_row, uint8_t key2_col, uint8_t socd_index) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (socd_index >= SOCD_COUNT) return; // Prevent array overflow | |
| // Configure SOCD entry - this modifies profile_get_current()->socd[socd_index] | |
| uint8_t data[7] = { | |
| cur_prof_idx, // Profile index (which profile to modify) | |
| key1_row, // Key 1 row position | |
| key1_col, // Key 1 col position | |
| key2_row, // Key 2 row position | |
| key2_col, // Key 2 col position | |
| socd_index, // Which array slot to use (0 to SOCD_COUNT-1) | |
| SOCD_PRI_LAST_KEYSTROKE // Last key priority type | |
| }; | |
| profile_set_socd(data); | |
| } | |
| /** | |
| * @brief Configure Snap Click (Deeper Travel Priority) SOCD | |
| * | |
| * @details | |
| * Sets up conflict resolution where the key pressed deeper takes priority when both keys are active. | |
| * Snap Click prevents accidental key presses by prioritizing the key pressed deeper. | |
| * During normal use, the deeper pressed key becomes active and blocks the shallower one. | |
| * | |
| * @param key1_row First key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key1_col First key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param key2_row Second key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key2_col Second key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param socd_index SOCD configuration slot index (0 to SOCD_COUNT-1, so 0-19 by default) | |
| * @param single_activation true for single activation mode, false for continuous | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note SOCD_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each socd_index represents one slot in the socd[SOCD_COUNT] array | |
| * @note Think of socd_index as "which conflict resolution rule number you're setting up" | |
| * @note single_activation = true: SOCD_PRI_DEEPER_TRAVEL_SINGLE (only deeper key activates) | |
| * @note single_activation = false: SOCD_PRI_DEEPER_TRAVEL (both can activate, deeper has priority) | |
| * @note single_activation = true: When both keys are pressed nearly to the bottom, keeps last active key | |
| * @note single_activation = false: When both keys are pressed nearly to the bottom, both keys remain active | |
| * | |
| * Example usage: | |
| * ```c | |
| * // Set up snap click for adjacent keys that might be accidentally pressed together | |
| * set_snap_click(2, 4, 2, 5, 0, true); // J vs K keys, single activation, slot 0 | |
| * set_snap_click(1, 4, 1, 5, 1, false); // R vs T keys, both active, slot 1 | |
| * // You could set up to 18 more pairs using slots 2-19 | |
| * ``` | |
| */ | |
| static inline void set_snap_click(uint8_t key1_row, uint8_t key1_col, uint8_t key2_row, uint8_t key2_col, uint8_t socd_index, bool single_activation) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (socd_index >= SOCD_COUNT) return; // Prevent array overflow | |
| // Configure SOCD entry - this modifies profile_get_current()->socd[socd_index] | |
| uint8_t data[7] = { | |
| cur_prof_idx, // Profile index | |
| key1_row, // Key 1 row position | |
| key1_col, // Key 1 col position | |
| key2_row, // Key 2 row position | |
| key2_col, // Key 2 col position | |
| socd_index, // Which array slot to use (0 to SOCD_COUNT-1) | |
| single_activation ? SOCD_PRI_DEEPER_TRAVEL_SINGLE : SOCD_PRI_DEEPER_TRAVEL // Snap click type | |
| }; | |
| profile_set_socd(data); | |
| } | |
| /* | |
| * ============================================================================ | |
| * ADVANCED SOCD MODES | |
| * ============================================================================ | |
| */ | |
| /** | |
| * @brief Configure Neutral SOCD (Simultaneous Opposing Cardinal Directions) | |
| * | |
| * @details | |
| * Sets up neutral SOCD where both keys are blocked when pressed simultaneously. | |
| * SOCD is an array-based system with limited slots available (SOCD_COUNT = 20 by default). | |
| * This is commonly used in fighting games where pressing both left and right results in no movement | |
| * to prevent directional conflicts. | |
| * | |
| * @param key1_row First key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key1_col First key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param key2_row Second key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key2_col Second key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param socd_index SOCD configuration slot index (0 to SOCD_COUNT-1, so 0-19 by default) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note SOCD_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each socd_index represents one slot in the socd[SOCD_COUNT] array | |
| * @note Think of socd_index as "which conflict resolution rule number you're setting up" | |
| * @note When both keys are pressed simultaneously, neither key activates (neutral state) | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_neutral_socd(2, 1, 2, 3, 0); // A vs D keys neutralize each other, slot 0 | |
| * set_neutral_socd(1, 4, 1, 18, 1); // W vs S keys neutralize each other, slot 1 | |
| * ``` | |
| */ | |
| static inline void set_neutral_socd(uint8_t key1_row, uint8_t key1_col, uint8_t key2_row, uint8_t key2_col, uint8_t socd_index) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (socd_index >= SOCD_COUNT) return; | |
| uint8_t data[7] = { | |
| cur_prof_idx, // Profile index | |
| key1_row, // Key 1 row position | |
| key1_col, // Key 1 col position | |
| key2_row, // Key 2 row position | |
| key2_col, // Key 2 col position | |
| socd_index, // Which array slot to use | |
| SOCD_PRI_NEUTRAL // Neutral SOCD type | |
| }; | |
| profile_set_socd(data); | |
| } | |
| /** | |
| * @brief Configure Key 1 Priority SOCD (Simultaneous Opposing Cardinal Directions) | |
| * | |
| * @details | |
| * Sets up SOCD where key1 always takes priority when both keys are pressed. | |
| * SOCD is an array-based system with limited slots available (SOCD_COUNT = 20 by default). | |
| * This creates a hierarchy where one key dominates another in conflict situations. | |
| * | |
| * @param key1_row First key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key1_col First key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param key2_row Second key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key2_col Second key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param socd_index SOCD configuration slot index (0 to SOCD_COUNT-1, so 0-19 by default) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note SOCD_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each socd_index represents one slot in the socd[SOCD_COUNT] array | |
| * @note Think of socd_index as "which conflict resolution rule number you're setting up" | |
| * @note When both keys are pressed together: only key1 activates, key2 is blocked | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_key1_priority_socd(1, 2, 2, 2, 0); // W always beats S, slot 0 | |
| * set_key1_priority_socd(2, 1, 2, 3, 1); // A always beats D, slot 1 | |
| * ``` | |
| */ | |
| static inline void set_key1_priority_socd(uint8_t key1_row, uint8_t key1_col, uint8_t key2_row, uint8_t key2_col, uint8_t socd_index) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (socd_index >= SOCD_COUNT) return; | |
| uint8_t data[7] = { | |
| cur_prof_idx, // Profile index | |
| key1_row, // Key 1 row position | |
| key1_col, // Key 1 col position | |
| key2_row, // Key 2 row position | |
| key2_col, // Key 2 col position | |
| socd_index, // Which array slot to use | |
| SOCD_PRI_KEY_1 // Key 1 priority type | |
| }; | |
| profile_set_socd(data); | |
| } | |
| /** | |
| * @brief Configure Key 2 Priority SOCD (Simultaneous Opposing Cardinal Directions) | |
| * | |
| * @details | |
| * Sets up SOCD where key2 always takes priority when both keys are pressed. | |
| * SOCD is an array-based system with limited slots available (SOCD_COUNT = 20 by default). | |
| * This creates a hierarchy where the second key dominates the first in conflict situations. | |
| * | |
| * @param key1_row First key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key1_col First key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param key2_row Second key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param key2_col Second key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param socd_index SOCD configuration slot index (0 to SOCD_COUNT-1, so 0-19 by default) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note SOCD_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each socd_index represents one slot in the socd[SOCD_COUNT] array | |
| * @note Think of socd_index as "which conflict resolution rule number you're setting up" | |
| * @note When both keys are pressed together: only key2 activates, key1 is blocked | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_key2_priority_socd(1, 2, 2, 2, 0); // S always beats W, slot 0 | |
| * set_key2_priority_socd(2, 1, 2, 3, 1); // D always beats A, slot 1 | |
| * ``` | |
| */ | |
| static inline void set_key2_priority_socd(uint8_t key1_row, uint8_t key1_col, uint8_t key2_row, uint8_t key2_col, uint8_t socd_index) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (socd_index >= SOCD_COUNT) return; | |
| uint8_t data[7] = { | |
| cur_prof_idx, // Profile index | |
| key1_row, // Key 1 row position | |
| key1_col, // Key 1 col position | |
| key2_row, // Key 2 row position | |
| key2_col, // Key 2 col position | |
| socd_index, // Which array slot to use | |
| SOCD_PRI_KEY_2 // Key 2 priority type | |
| }; | |
| profile_set_socd(data); | |
| } | |
| /** | |
| * @brief Configure Dynamic Key Strokes (DKS) / One Key Multi Code (OKMC) | |
| * | |
| * @details | |
| * Dynamic Key Strokes (DKS) allows one key to perform up to 4 different actions based on travel depth. | |
| * This is also known as One Key Multi Code (OKMC) in the codebase. | |
| * OKMC is an array-based system with limited slots available (OKMC_COUNT = 20 by default). | |
| * | |
| * Each action parameter is a 4-bit value where each bit controls one travel zone: | |
| * - Bit 0 (0x01): Add key on shallow_act (shallow press) | |
| * - Bit 1 (0x02): Add key on shallow_deact (shallow release) | |
| * - Bit 2 (0x04): Add key on deep_act (deep press) | |
| * - Bit 3 (0x08): Add key on deep_deact (deep release) | |
| * | |
| * IMPORTANT: OKMC uses ADDITIVE ACCUMULATION behavior: | |
| * - Keys get ADDED at each travel zone (never individually removed) | |
| * - Adding the same key multiple times has no effect (it's already active) | |
| * - ALL keys are released together when the physical key fully releases | |
| * | |
| * @param row Key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param okmc_index OKMC configuration slot index (0 to OKMC_COUNT-1, so 0-19 by default) | |
| * @param shallow_act Shallow actuation point in 0.1mm units (when to trigger shallow actions) | |
| * @param shallow_deact Shallow deactuation point in 0.1mm units (when to release shallow actions) | |
| * @param deep_act Deep actuation point in 0.1mm units (when to trigger deep actions) | |
| * @param deep_deact Deep deactuation point in 0.1mm units (when to release deep actions) | |
| * @param keycode1 First QMK keycode to send (KC_A, KC_LSFT, etc., 0 = no action) | |
| * @param keycode2 Second QMK keycode to send (KC_A, KC_LSFT, etc., 0 = no action) | |
| * @param keycode3 Third QMK keycode to send (KC_A, KC_LSFT, etc., 0 = no action) | |
| * @param keycode4 Fourth QMK keycode to send (KC_A, KC_LSFT, etc., 0 = no action) | |
| * @param action1 Action configuration for keycode1 (4-bit value controlling travel zones, 0 = skip) | |
| * @param action2 Action configuration for keycode2 (4-bit value controlling travel zones, 0 = skip) | |
| * @param action3 Action configuration for keycode3 (4-bit value controlling travel zones, 0 = skip) | |
| * @param action4 Action configuration for keycode4 (4-bit value controlling travel zones, 0 = skip) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note OKMC_COUNT is defined in analog_matrix_eeconfig.h (default: 20) | |
| * @note Each okmc_index represents one slot in the okmc[OKMC_COUNT] array | |
| * @note Think of okmc_index as "which DKS profile number you're setting up" | |
| * | |
| * Common action patterns: | |
| * - 0x01 = Add key on shallow press | |
| * - 0x02 = Add key on shallow release | |
| * - 0x04 = Add key on deep press | |
| * - 0x08 = Add key on deep release | |
| * - 0 = Skip this keycode (no action) | |
| * | |
| * Example usage: | |
| * ```c | |
| * // Gaming example: Walk on shallow, run on deep press | |
| * set_dynamic_key_strokes(2, 3, 0, // W key: Row 2, Col 3, OKMC slot 0 | |
| * 15, 12, 30, 25, // Travel: 1.5mm shallow act, 1.2mm shallow deact, 3.0mm deep act, 2.5mm deep deact | |
| * KC_W, KC_LSFT, 0, 0, // W key for walking, Shift for running | |
| * 0x01, 0x04, 0, 0); // W on shallow press, Shift on deep press, skip unused slots | |
| * | |
| * // Sequential accumulation: Each keycode gets added at different travel zones | |
| * // SHALLOW PRESS → SHALLOW RELEASE → DEEP PRESS → DEEP RELEASE | |
| * // A → A+B → A+B+C → A+B+C+D → all released | |
| * set_dynamic_key_strokes(1, 5, 1, // Different key, OKMC slot 1 | |
| * 10, 7, 25, 20, // Different travel points | |
| * KC_A, KC_B, KC_C, KC_D, // 4 different keycodes | |
| * 0x01, 0x02, 0x04, 0x08); // Add at: shallow press, shallow release, deep press, deep release | |
| * | |
| * // Keychron marketing example - shows actual behavior vs accumulation | |
| * // SHALLOW PRESS → DEEP PRESS → DEEP RELEASE → SHALLOW RELEASE | |
| * // C → C+LCTL → C+LCTL+H → C+LCTL+H+F → all released | |
| * // Note: C+LCTL gets replaced by H, then H gets replaced by F (not true accumulation) | |
| * set_dynamic_key_strokes(1, 5, 1, // Different key, OKMC slot 1 | |
| * 10, 7, 25, 20, // Different travel points | |
| * KC_C, KC_LCTL, KC_H, KC_F, // 4 different keycodes for different depths | |
| * 0x01, 0x04, 0x08, 0x02); // Add at: shallow press, deep press, deep release, shallow release | |
| * ``` | |
| */ | |
| static inline void set_dynamic_key_strokes(uint8_t row, uint8_t col, uint8_t okmc_index, | |
| uint8_t shallow_act, uint8_t shallow_deact, uint8_t deep_act, uint8_t deep_deact, | |
| uint16_t keycode1, uint16_t keycode2, uint16_t keycode3, uint16_t keycode4, | |
| uint8_t action1, uint8_t action2, uint8_t action3, uint8_t action4) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| if (okmc_index >= OKMC_COUNT) return; // Prevent array overflow | |
| // Configure OKMC entry - this modifies profile_get_current()->okmc[okmc_index] | |
| uint8_t data[25] = { | |
| cur_prof_idx, // [0] Profile index | |
| ADV_MODE_OKMC, // [1] Mode = OKMC/DKS | |
| row, // [2] Key row position | |
| col, // [3] Key col position | |
| okmc_index, // [4] Which OKMC slot to use (0 to OKMC_COUNT-1) | |
| shallow_act, // [5] Shallow actuation point | |
| shallow_deact, // [6] Shallow deactuation point | |
| deep_act, // [7] Deep actuation point | |
| deep_deact, // [8] Deep deactuation point | |
| // Keycodes (4 x 2 bytes = 8 bytes) [9-16] | |
| keycode1 & 0xFF, (keycode1 >> 8) & 0xFF, // [9-10] Keycode 1 | |
| keycode2 & 0xFF, (keycode2 >> 8) & 0xFF, // [11-12] Keycode 2 | |
| keycode3 & 0xFF, (keycode3 >> 8) & 0xFF, // [13-14] Keycode 3 | |
| keycode4 & 0xFF, (keycode4 >> 8) & 0xFF, // [15-16] Keycode 4 | |
| // Actions (4 x 2 bytes = 8 bytes) [17-24] | |
| // Each okmc_action_t: shallow_act:4, shallow_deact:4, deep_act:4, deep_deact:4 | |
| // Convert bit flags to 4-bit fields: bit 0->shallow_act, bit 1->shallow_deact, bit 2->deep_act, bit 3->deep_deact | |
| ((action1 & 0x01) ? 0x02 : 0) | (((action1 & 0x02) ? 0x02 : 0) << 4), // [17] shallow fields for action1 | |
| ((action1 & 0x04) ? 0x02 : 0) | (((action1 & 0x08) ? 0x02 : 0) << 4), // [18] deep fields for action1 | |
| ((action2 & 0x01) ? 0x02 : 0) | (((action2 & 0x02) ? 0x02 : 0) << 4), // [19] shallow fields for action2 | |
| ((action2 & 0x04) ? 0x02 : 0) | (((action2 & 0x08) ? 0x02 : 0) << 4), // [20] deep fields for action2 | |
| ((action3 & 0x01) ? 0x02 : 0) | (((action3 & 0x02) ? 0x02 : 0) << 4), // [21] shallow fields for action3 | |
| ((action3 & 0x04) ? 0x02 : 0) | (((action3 & 0x08) ? 0x02 : 0) << 4), // [22] deep fields for action3 | |
| ((action4 & 0x01) ? 0x02 : 0) | (((action4 & 0x02) ? 0x02 : 0) << 4), // [23] shallow fields for action4 | |
| ((action4 & 0x04) ? 0x02 : 0) | (((action4 & 0x08) ? 0x02 : 0) << 4) // [24] deep fields for action4 | |
| }; | |
| profile_set_adv_mode(data); | |
| } | |
| /* | |
| * ============================================================================ | |
| * TOGGLE MODE FUNCTIONS | |
| * ============================================================================ | |
| */ | |
| /** | |
| * @brief Set a key to toggle mode (AKM_TOGGLE) | |
| * | |
| * @details | |
| * Sets a key to toggle mode where the key acts like a toggle switch. | |
| * This is different from normal keys that are only active while being pressed down. | |
| * | |
| * Toggle behavior: | |
| * - First press: Key becomes active and stays active | |
| * - Second press: Key becomes inactive and stays inactive | |
| * - Key remains in its current state until pressed again | |
| * | |
| * Toggle mode is useful for: | |
| * - Custom Caps Lock behavior | |
| * - Gaming modes that need to stay active | |
| * - Modifier keys you want to "stick" (like software-based Sticky Keys) | |
| * - Mode switches in applications | |
| * | |
| * @param row Key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note Toggle mode configurations are per-profile and persist in EEPROM | |
| * @note You can have different toggle settings for different HE profiles | |
| * | |
| * Example usage: | |
| * ```c | |
| * set_key_toggle_mode(2, 0); // Make Caps Lock a proper toggle | |
| * set_key_toggle_mode(1, 0); // Make Tab a toggle for gaming mode | |
| * set_key_toggle_mode(3, 4); // Make a modifier key sticky | |
| * ``` | |
| */ | |
| static inline void set_key_toggle_mode(uint8_t row, uint8_t col) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| // Configure key for toggle mode | |
| uint8_t data[5] = { | |
| cur_prof_idx, // [0] Profile index | |
| ADV_MODE_TOGGLE, // [1] Mode = Toggle mode | |
| row, // [2] Key row position | |
| col, // [3] Key col position | |
| 0 // [4] Index (unused for toggle mode) | |
| }; | |
| profile_set_adv_mode(data); | |
| } | |
| /* | |
| * ============================================================================ | |
| * ANALOG GAME CONTROLLER FUNCTIONS | |
| * ============================================================================ | |
| */ | |
| /** | |
| * @brief Set a key to game controller mode (XInput button or axis) | |
| * | |
| * @details | |
| * Sets a key to game controller mode, allowing it to send XInput signals instead of normal keyboard keycodes. | |
| * This is useful for gaming applications that expect gamepad input. | |
| * | |
| * The analog sticks provide proportional output based on key travel distance. | |
| * Deeper presses result in larger analog stick deflection values. | |
| * | |
| * Available XInput Button Keycodes: | |
| * - XB_A, XB_B, XB_X, XB_Y - Face buttons | |
| * - XB_LB, XB_RB - Shoulder buttons | |
| * - XB_VIEW, XB_MEMU - View/Menu buttons | |
| * - XB_L3, XB_R3 - Stick press buttons | |
| * - XB_UP, XB_DOWN, XB_LEFT, XB_RGHT - D-pad buttons | |
| * - XB_XBOX - Xbox button | |
| * - XB_LT, XB_RT - Left/Right triggers (analog) | |
| * | |
| * Available XInput Analog Stick Keycodes: | |
| * - LS_LEFT, LS_RGHT, LS_UP, LS_DOWN - Left stick directions | |
| * - RS_LEFT, RS_RGHT, RS_UP, RS_DOWN - Right stick directions | |
| * | |
| * @param row Key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param xinput_keycode XInput keycode from xinput_keycodes.h | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * @note Game controller configurations are per-profile and persist in EEPROM | |
| * @note You can have different controller mappings for different HE profiles | |
| * | |
| * Example usage: | |
| * ```c | |
| * // Gaming setup: WASD as left stick, arrow keys as right stick | |
| * set_key_game_controller(1, 2, LS_UP); // W key -> Left stick up | |
| * set_key_game_controller(2, 0, LS_LEFT); // A key -> Left stick left | |
| * set_key_game_controller(2, 2, LS_DOWN); // S key -> Left stick down | |
| * set_key_game_controller(2, 3, LS_RGHT); // D key -> Left stick right | |
| * | |
| * // Face buttons | |
| * set_key_game_controller(1, 5, XB_Y); // T key -> Y button | |
| * set_key_game_controller(1, 6, XB_X); // Y key -> X button | |
| * set_key_game_controller(1, 7, XB_B); // U key -> B button | |
| * set_key_game_controller(1, 8, XB_A); // I key -> A button | |
| * | |
| * // Triggers (analog based on key travel) | |
| * set_key_game_controller(0, 1, XB_LT); // "1" key -> Left trigger | |
| * set_key_game_controller(0, 2, XB_RT); // "2" key -> Right trigger | |
| * ``` | |
| */ | |
| static inline void set_key_game_controller(uint8_t row, uint8_t col, uint16_t xinput_keycode) { | |
| uint8_t cur_prof_idx = profile_get_current_index(); | |
| // Extract axis index from xinput_keycode | |
| // XInput keycodes are encoded as: ((axis_index << 3 | AKM_GAMEPAD) << 2) | (mode & 3) | |
| uint8_t axis_index = (xinput_keycode >> 5) & 0x1F; // Extract bits 5-9 (axis index) | |
| // Configure key for game controller mode | |
| uint8_t data[5] = { | |
| cur_prof_idx, // [0] Profile index | |
| ADV_MODE_GAME_CONTROLLER, // [1] Mode = Game controller mode | |
| row, // [2] Key row position | |
| col, // [3] Key col position | |
| axis_index // [4] Axis/button index | |
| }; | |
| profile_set_adv_mode(data); | |
| } | |
| /** | |
| * @brief Set analog sensitivity curve for game controller axes | |
| * | |
| * @details | |
| * Configures the analog sensitivity curve that determines how key travel distance | |
| * maps to analog stick/trigger values for game controller mode. The curve is defined | |
| * by 4 control points that create 3 linear segments, with (0,0) as the fixed origin. | |
| * | |
| * @param x1 First control point X coordinate (key travel in 0.1mm units, 0-40) | |
| * @param y1 First control point Y coordinate (output intensity 0-127) | |
| * @param x2 Second control point X coordinate (key travel in 0.1mm units, 0-40) | |
| * @param y2 Second control point Y coordinate (output intensity 0-127) | |
| * @param x3 Third control point X coordinate (key travel in 0.1mm units, 0-40) | |
| * @param y3 Third control point Y coordinate (output intensity 0-127) | |
| * @param x4 Fourth control point X coordinate (key travel in 0.1mm units, 0-40) | |
| * @param y4 Fourth control point Y coordinate (output intensity 0-127) | |
| * | |
| * @note - Points must be in ascending X order: x1 < x2 < x3 < x4 | |
| * @note - X values: 0-40 representing 0.0mm to 4.0mm travel distance | |
| * @note - Y values: 0-127 where 127 = maximum analog output | |
| * @note - Curve applies globally to all analog game controller axes and persists in EEPROM | |
| * @note - By default curve settings are (0, 0), (10,31), (30,95), (40,127) | |
| * | |
| * Example usage: | |
| * ```c | |
| * //Linear curve (1:1 mapping) - smooth and predictable response | |
| * set_analog_sensitivity_curve(10, 32, 20, 64, 30, 96, 40, 127); | |
| * | |
| * // Sensitive curve (more output for less travel) - great for racing games | |
| * set_analog_sensitivity_curve(5, 40, 15, 80, 25, 110, 40, 127); | |
| * | |
| * // Gaming curve with dead zone - prevents accidental inputs | |
| * set_analog_sensitivity_curve(8, 0, 12, 30, 25, 100, 40, 127); | |
| * | |
| * // Typing-friendly curve - minimal sensitivity until deeper travel | |
| * set_analog_sensitivity_curve(12, 10, 20, 30, 30, 70, 40, 127); | |
| * ``` | |
| */ | |
| static inline void set_analog_sensitivity_curve(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, uint8_t x4, uint8_t y4) { | |
| // Validate curve points are in ascending order | |
| if (x1 >= x2 || x2 >= x3 || x3 >= x4) { | |
| return; // Invalid curve - points must be in ascending X order | |
| } | |
| // Create curve points array (point 0 is always {0, 0}) | |
| point_t curve_points[4] = { | |
| {x1, y1}, // Point 1: First control point | |
| {x2, y2}, // Point 2: Second control point | |
| {x3, y3}, // Point 3: Third control point | |
| {x4, y4} | |
| }; | |
| // Set the new curve | |
| game_controller_set_curve(curve_points); | |
| } | |
| /* | |
| * ============================================================================ | |
| * HE PROFILE MANAGEMENT FUNCTIONS | |
| * ============================================================================ | |
| * | |
| * These functions allow switching between 3 different HE profiles (0, 1, 2). | |
| * Check your profiles in profiles.c. | |
| * | |
| * Note: some profiles can use XInput instead of normal keys. | |
| * If you want to override this, copy profiles.c file to your keymap, change XInput keys (e.g. XB_XBOX) to 0 | |
| * and add SRC += profiles.c to rules.mk | |
| * | |
| * Global Profile Modes (in profiles.c profile_gobal_mode array): | |
| * The profile_gobal_mode array defines the default key behavior for each profile: | |
| * AKM_REGULAR: Static actuation points - keys activate at fixed travel distance (default: 20 = 2.0mm) | |
| * AKM_RAPID: Rapid trigger mode - keys activate/deactivate based on movement sensitivity (default: 4 = 0.4mm) | |
| * AKM_DKS: Dynamic Key Strokes mode - keys perform different actions at different travel depths | |
| * AKM_GAMEPAD: Game controller mode - keys send XInput signals (XB_A/B/X/Y buttons, LS_x/RS_x analog sticks, XB_LT/RT triggers) | |
| * AKM_TOGGLE: Toggle mode - keys act as toggle switches (press once = on, press again = off) | |
| * Individual keys can override the global mode using functions in this file. | |
| * Defaults defined in analog_matrix.h: DEFAULT_ACTUATION_POINT=20, DEFAULT_RAPID_TRIGGER_SENSITIVITY=4 | |
| * | |
| * DRY Configuration Pattern: | |
| * Instead of separate functions for each profile, use the switching approach: | |
| * uint8_t original_profile = get_current_he_profile(); | |
| * | |
| * switch_he_profile(0); // Configure gaming profile | |
| * set_key_actuation_point(2, 0, 5); // W key: 0.5mm | |
| * set_key_rapid_trigger(2, 0, 1, 1); // Very sensitive | |
| * | |
| * switch_he_profile(1); // Configure typing profile | |
| * set_key_actuation_point(2, 0, 15); // W key: 1.5mm | |
| * set_key_rapid_trigger(2, 0, 3, 5); // Less sensitive | |
| * | |
| * switch_he_profile(original_profile); // Restore original | |
| */ | |
| /** | |
| * @brief Switch to a different Hall Effect profile | |
| * | |
| * @details | |
| * Switches to a different Hall Effect profile (0-2). | |
| * All subsequent configuration functions will operate on this profile. | |
| * This is similar to QMK layer switching but for HE analog settings. | |
| * Like TO(1) which switches to a layer persistently, this switches the active HE profile. | |
| * | |
| * @param profile_index HE profile to switch to (0, 1, or 2) | |
| * | |
| * @note Profile index must be between 0 and PROFILE_COUNT-1 | |
| * @note LED indication is enabled when switching profiles | |
| * | |
| * Example runtime switching: | |
| * ```c | |
| * enum custom_keycodes { | |
| * HE_PROF_0 = SAFE_RANGE, // Switch to HE profile 0 | |
| * HE_PROF_1, // Switch to HE profile 1 | |
| * HE_PROF_2, // Switch to HE profile 2 | |
| * HE_CYCLE // Cycle through HE profiles | |
| * }; | |
| * | |
| * bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |
| * switch (keycode) { | |
| * case HE_PROF_0: | |
| * if (record->event.pressed) { | |
| * switch_he_profile(0); // Switch to HE profile 0 | |
| * } | |
| * return false; | |
| * case HE_PROF_1: | |
| * if (record->event.pressed) { | |
| * switch_he_profile(1); // Switch to HE profile 1 | |
| * } | |
| * return false; | |
| * case HE_PROF_2: | |
| * if (record->event.pressed) { | |
| * switch_he_profile(2); // Switch to HE profile 2 | |
| * } | |
| * return false; | |
| * case HE_CYCLE: | |
| * if (record->event.pressed) { | |
| * cycle_he_profile(); // Cycle through profiles | |
| * } | |
| * return false; | |
| * } | |
| * return true; | |
| * } | |
| * | |
| * // Configure different actuation points for "1" key on all profiles | |
| * void keyboard_post_init_user(void) { | |
| * uint8_t original_profile = get_current_he_profile(); | |
| * | |
| * // Profile 0: Gaming (very sensitive) | |
| * switch_he_profile(0); | |
| * set_key_actuation_point(0, 1, 3); // "1" key = 0.3mm | |
| * | |
| * // Profile 1: Typing (normal) | |
| * switch_he_profile(1); | |
| * set_key_actuation_point(0, 1, 15); // "1" key = 1.5mm | |
| * | |
| * // Profile 2: Heavy typing (less sensitive) | |
| * switch_he_profile(2); | |
| * set_key_actuation_point(0, 1, 35); // "1" key = 3.5mm | |
| * | |
| * switch_he_profile(original_profile); // Restore original profile | |
| * } | |
| * ``` | |
| */ | |
| static inline void switch_he_profile(uint8_t profile_index) { | |
| if (profile_index >= PROFILE_COUNT) { | |
| return; // Invalid profile index | |
| } | |
| profile_select(profile_index, true); // true = LED indication when switching; false = no LED indication | |
| } | |
| /** | |
| * @brief Get current Hall Effect profile index | |
| * | |
| * @details | |
| * Returns the currently active HE profile index (0-2). | |
| * | |
| * Useful for: | |
| * - Saving current profile before configuration | |
| * - Displaying current profile on OLED/LCD | |
| * - Conditional logic based on active profile | |
| * | |
| * @return Current HE profile index (0 to PROFILE_COUNT-1) | |
| */ | |
| static inline uint8_t get_current_he_profile(void) { | |
| return profile_get_current_index(); | |
| } | |
| /** | |
| * @brief Cycle to next Hall Effect profile | |
| * | |
| * @details | |
| * Cycles to the next HE profile in sequence: 0 → 1 → 2 → 0 → ... | |
| * Convenient for a single key that cycles through all available profiles. | |
| * | |
| * @note LED indication is enabled when switching profiles | |
| */ | |
| static inline void cycle_he_profile(void) { | |
| uint8_t current = profile_get_current_index(); | |
| uint8_t next = (current + 1) % PROFILE_COUNT; | |
| profile_select(next, true); // true = LED indication when switching; false = no LED indication | |
| } | |
| /* | |
| * ============================================================================ | |
| * DEBUGGING FUNCTIONS (CONSOLE) | |
| * ============================================================================ | |
| * | |
| * Console debugging functions for monitoring HE keyboard behavior in real-time. | |
| * | |
| * HOW TO ENABLE: | |
| * 1. Set CONSOLE_ENABLE = yes in rules.mk | |
| * 2. Use QMK Toolbox console tab or hid_listen tool to view output | |
| * 3. Debug messages appear as readable text with timestamps | |
| * | |
| * EXAMPLE IMPLEMENTATIONS (see keymap.c for full examples): | |
| * | |
| * 1. Profile switching debugging in process_record_user(): | |
| * case HE_PROF_1: | |
| * if (record->event.pressed) { | |
| * switch_he_profile(1); | |
| * send_debug_message("Profile -> 1"); // Shows profile changes | |
| * } | |
| * return false; | |
| * | |
| * 2. Key travel debugging for specific keys: | |
| * if (keycode == KC_W) { | |
| * if (record->event.pressed) { | |
| * send_debug_message("W key pressed 2.0mm"); | |
| * debug_keyevent(&record->event); // Full key event details | |
| * } else { | |
| * send_debug_message("W key released"); | |
| * debug_keyevent(&record->event); | |
| * } | |
| * } | |
| * | |
| * 3. Initialization debugging in keyboard_post_init_user(): | |
| * send_debug_message("HE Init started"); | |
| * send_debug_message("WASD -> analog stick"); | |
| * send_debug_message("A vs D: neutral SOCD"); | |
| * send_debug_message("HE Init complete"); | |
| * | |
| * 4. SOCD debugging (add to process_record_user for A/D keys): | |
| * if (keycode == KC_A || keycode == KC_D) { | |
| * if (record->event.pressed) { | |
| * send_debug_message(keycode == KC_A ? "A pressed" : "D pressed"); | |
| * } | |
| * } | |
| * | |
| * 5. Function call debugging: | |
| * set_key_actuation_point(2, 0, 15); // Set A key to 1.5mm | |
| * send_debug_message("A key actuation -> 1.5mm"); | |
| * | |
| * OUTPUT EXAMPLES: | |
| * DEBUG: Profile -> 1 | |
| * DEBUG: W key pressed 2.0mm | |
| * KEY: r1 c2 PRESS | |
| * TIME: 12345 TYPE: KEY | |
| * DEBUG: A vs D: neutral SOCD | |
| * | |
| * NOTE: If you get "There are not enough available endpoints" error when enabling console, | |
| * disable one or more of these features in rules.mk (set to 'no'): | |
| * MOUSEKEY_ENABLE, EXTRAKEY_ENABLE, NKRO_ENABLE, MIDI_ENABLE, | |
| * SERIAL_ENABLE, STENO_ENABLE, RAW_ENABLE, VIRTSER_ENABLE | |
| * Keep CONSOLE_ENABLE = yes for debugging | |
| */ | |
| #ifdef CONSOLE_ENABLE | |
| /** | |
| * @brief Send debug message via console | |
| * | |
| * @details | |
| * Sends a debug message to the console output with "DEBUG: " prefix. | |
| * Requires CONSOLE_ENABLE=yes in rules.mk to function. | |
| * | |
| * @param message Debug message string to send to console | |
| * | |
| * @note Only works when CONSOLE_ENABLE is enabled in rules.mk | |
| * @note Use QMK Toolbox console tab or hid_listen tool to view output | |
| */ | |
| static inline void send_debug_message(const char* message) { | |
| uprintf("DEBUG: %s\n", message); | |
| } | |
| /** | |
| * @brief Debug entire keyevent_t structure | |
| * | |
| * @details | |
| * Outputs detailed information about a key event to the console, including | |
| * key position, press/release state, timestamp, and event type. | |
| * Requires CONSOLE_ENABLE=yes in rules.mk to function. | |
| * | |
| * Output format: | |
| * - "KEY: r{row} c{col} {PRESS|RELEASE}" | |
| * - "TIME: {timestamp} TYPE: {event_type}" | |
| * | |
| * @param event Pointer to keyevent_t structure to debug | |
| * | |
| * @note Only works when CONSOLE_ENABLE is enabled in rules.mk | |
| * @note Use QMK Toolbox console tab or hid_listen tool to view output | |
| */ | |
| static inline void debug_keyevent(const keyevent_t* event) { | |
| // First message: key position and pressed state | |
| uprintf("KEY: r%d c%d %s\n", | |
| event->key.row, event->key.col, | |
| event->pressed ? "PRESS" : "RELEASE"); | |
| // Second message: time and event type | |
| const char* type_str = "UNKNOWN"; | |
| switch (event->type) { | |
| case TICK_EVENT: type_str = "TICK"; break; | |
| case KEY_EVENT: type_str = "KEY"; break; | |
| case ENCODER_CW_EVENT: type_str = "ENC_CW"; break; | |
| case ENCODER_CCW_EVENT: type_str = "ENC_CCW"; break; | |
| case COMBO_EVENT: type_str = "COMBO"; break; | |
| } | |
| uprintf("TIME: %u TYPE: %s\n", event->time, type_str); | |
| } | |
| #else | |
| /** | |
| * @brief Send debug message via console (stub function) | |
| * @details Stub function when CONSOLE_ENABLE is disabled. Does nothing. | |
| * @param message Unused debug message parameter | |
| */ | |
| static inline void send_debug_message(const char* message) { (void)message; } | |
| /** | |
| * @brief Debug entire keyevent_t structure (stub function) | |
| * @details Stub function when CONSOLE_ENABLE is disabled. Does nothing. | |
| * @param event Unused keyevent_t parameter | |
| */ | |
| static inline void debug_keyevent(const keyevent_t* event) { (void)event; } | |
| #endif | |
| /* | |
| * ============================================================================ | |
| * HALL EFFECT CALIBRATION FUNCTIONS | |
| * ============================================================================ | |
| * | |
| * CALIBRATION PROCESS OVERVIEW: | |
| * 1. Start with CALIB_ZERO_TRAVEL_MANUAL - ensure all keys are in rest position (not pressed) | |
| * 2. Controller automatically transitions to CALIB_FULL_TRAVEL_MANUAL after zero phase | |
| * 3. Manually press all keys to maximum travel depth - LED changes from PURPLE to GREEN when done | |
| * 4. Calibration data is automatically saved when complete - no manual save needed | |
| * | |
| * IMPORTANT NOTES: | |
| * - During calibration, keys are unresponsive and will not print anything | |
| * - User must manually press each key during the calibration phases | |
| * - If LED stays RED during calibration, one or more keys failed to calibrate properly | |
| * - If calibration fails (LED remains red), restart calibration by relaunching the keyboard | |
| * - No manual stop calibration function is implemented because keys are unresponsive during calibration | |
| */ | |
| /** | |
| * @brief Start Hall Effect calibration process | |
| * | |
| * @details | |
| * Initiates the manual two-phase calibration sequence: | |
| * 1. Zero travel calibration (LED: RED) - ensure all keys are released | |
| * 2. Full travel calibration (LED: PURPLE->GREEN) - manually press all keys to maximum depth | |
| * | |
| * WARNING: During calibration, keys become unresponsive and will not type anything! | |
| * User must manually press keys during each calibration phase. | |
| * The keyboard LED indicates the current phase and completion status. | |
| * | |
| * @note Keys become unresponsive during calibration | |
| * @note LED color indicates calibration phase: RED = zero phase, PURPLE = full phase, GREEN = complete | |
| * @note Includes 1 second delay to ensure all keys are released before starting | |
| * | |
| * Example usage: | |
| * ```c | |
| * case KC_CALIB_START: | |
| * if (record->event.pressed) { | |
| * start_calibration(); | |
| * } | |
| * return false; | |
| * ``` | |
| */ | |
| static inline void start_calibration(void) { | |
| wait_ms(1000); // 1 second delay to ensure all keys are released before calibration | |
| uint8_t data[4] = {0xA9, AMC_CALIBRATE, CALIB_ZERO_TRAVEL_MANUAL, 0}; | |
| analog_matrix_rx(data, 4); | |
| } | |
| /** | |
| * @brief Get current calibration state | |
| * | |
| * @details | |
| * Returns the current calibration state from the analog matrix controller. | |
| * Useful for monitoring calibration progress or implementing custom calibration logic. | |
| * | |
| * @return Current calibration state: | |
| * - CALIB_OFF: Normal operation | |
| * - CALIB_ZERO_TRAVEL_MANUAL: Zero calibration active (LED: RED) | |
| * - CALIB_FULL_TRAVEL_MANUAL: Full calibration active (LED: PURPLE->GREEN) | |
| * | |
| * Example usage: | |
| * ```c | |
| * uint8_t state = get_calibration_state(); | |
| * if (state == CALIB_OFF) { | |
| * // Calibration complete or not active | |
| * } | |
| * ``` | |
| */ | |
| static inline uint8_t get_calibration_state(void) { | |
| uint8_t data[20] = {0xA9, AMC_GET_CALIBRATE_STATE, 0}; | |
| analog_matrix_rx(data, 20); | |
| return data[3]; | |
| } | |
| /** | |
| * @brief Check if calibration is currently active | |
| * | |
| * @details | |
| * Returns true if calibration is currently in progress, false otherwise. | |
| * Convenient wrapper around get_calibration_state() for simple checks. | |
| * | |
| * @return true if calibration is active, false if normal operation | |
| * | |
| * Example usage: | |
| * ```c | |
| * if (is_calibrating()) { | |
| * // Add your logic during calibration process here | |
| * return; | |
| * } | |
| * ``` | |
| */ | |
| static inline bool is_calibrating(void) { | |
| return (get_calibration_state() != CALIB_OFF); | |
| } | |
| /** | |
| * @brief Get calibrated values for a specific key | |
| * | |
| * @details | |
| * Retrieves the stored calibration data for a single key position. | |
| * | |
| * ADC Value Notes: | |
| * - Higher ADC values = magnet closer to sensor | |
| * - Zero travel (key at rest) should have HIGHER ADC values than full travel | |
| * - Full travel (key pressed) should have LOWER ADC values | |
| * - If zero_travel <= full_travel, the key may not be calibrated correctly | |
| * | |
| * @param row Key matrix row index starting from 0 (0 = top row, increases downward) | |
| * @param col Key matrix column index starting from 0 (0 = leftmost column, increases rightward) | |
| * @param zero_travel Pointer to store the zero travel ADC value (key at rest position) | |
| * @param full_travel Pointer to store the full travel ADC value (key fully pressed) | |
| * | |
| * @return true if calibration data exists and was retrieved successfully, false otherwise | |
| * | |
| * @note To find valid row/col for a specific key, look at the LED matrix in ansi.c / iso_encoder.c / jis_encoder.c etc. | |
| * where __ means no key exists at that position. If no LED matrix exists, check | |
| * analog_matrix_mask[] and read each binary line right-to-left (1 = valid key, 0 = no key). | |
| * Example: 0b110111111111111 = cols 14,13,11,10,9,8,7,6,5,4,3,2,1,0 exist, col 12 missing. | |
| * | |
| * Example usage: | |
| * ```c | |
| * uint16_t zero, full; | |
| * if (get_key_calibration(1, 2, &zero, &full)) { // W key | |
| * // Successfully got calibration data | |
| * uint16_t range = zero - full; // Calculate travel range | |
| * if (range > 100) { | |
| * // Do something here | |
| * } | |
| * } else { | |
| * // Key not calibrated or invalid position or default values used | |
| * } | |
| * ``` | |
| */ | |
| static inline bool get_key_calibration(uint8_t row, uint8_t col, uint16_t *zero_travel, uint16_t *full_travel) { | |
| if (row >= MATRIX_ROWS || col >= MATRIX_COLS) { | |
| return false; | |
| } | |
| uint8_t data[20] = {0xA9, AMC_GET_CALIBRATED_VALUE, row, col, 0}; | |
| analog_matrix_rx(data, 20); | |
| if (data[4] == 0) { | |
| *zero_travel = (data[6] << 8) | data[5]; // Little endian conversion | |
| *full_travel = (data[8] << 8) | data[7]; // Little endian conversion | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @brief Clear all calibration data | |
| * | |
| * @details | |
| * Erases all stored calibration values and resets keys to factory defaults. | |
| * Keys will continue to work normally with default calibration values. | |
| * New calibration will overwrite old data, so clearing is optional. | |
| * | |
| * Usage: Use only for troubleshooting calibration issues. | |
| * | |
| * @note Keys continue to work with factory default calibration values after clearing | |
| * @note New calibration will overwrite old data automatically | |
| * | |
| * Example usage: | |
| * ```c | |
| * case KC_CALIB_CLEAR: | |
| * if (record->event.pressed) { | |
| * clear_calibration_data(); | |
| * } | |
| * return false; | |
| * ``` | |
| */ | |
| static inline void clear_calibration_data(void) { | |
| uint8_t data[4] = {0xA9, AMC_CALIBRATE, CALIB_CLEAR, 0}; | |
| analog_matrix_rx(data, 4); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Release Notes - Update 15.09.2025
set_analog_sensitivity_curvewhich ignored third control point and always used (0,0) as origin point