QMK Joystick 功能完全指南:从规则配置、轴映射到 HID 手柄 API

QMK Joystick 功能完全指南:从规则配置、轴映射到 HID 手柄 API QMK Joystick 功能完全指南从规则配置、轴映射到 HID 手柄 API【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware本篇技术指南以 QMK Firmwareqmk_firmware中内置的 Joystick游戏手柄功能为核心讲解如何把键盘固件扩展为一个符合 HID 规范的 joystick 设备——支持最多 6 个轴axis、32 个按键button与 1 个 8 方向帽式开关hat switch。读完本文你将掌握在rules.mk中开启功能的正确姿势、在config.h中定制轴数与分辨率的参数边界、通过JOYSTICK_AXIS_IN/JOYSTICK_AXIS_VIRTUAL两种方式声明物理轴与虚拟轴、在keymap.c中编写虚拟轴控制代码以及直接调用joystick_set_axis()、joystick_set_hat()、register_joystick_button()等底层 API 的完整方法。功能概览键盘如何变成游戏手柄Joystick 功能让 QMK 固件在 USB 枚举时呈现为一个 game controller 设备。来自模拟摇杆等外设的模拟量通过微控制器的 ADC模数转换器读取也可以由用户代码直接“注入”虚拟轴数据最终以 HID joystick report 的形式发送给主机。一个典型的模拟摇杆轴例如电位器本质上是一个分压电路滑动触点的位置决定了输出电压MCU 的 ADC 采样该电压即可推算轴的位置。这就是analog驱动的工作基础而digital驱动则完全不读 ADC轴的数值完全由用户代码决定。与功能直接相关的核心源码文件如下quantum/joystick.c功能主实现包含轴采样、量程换算、按键/轴/帽状态维护与发送逻辑quantum/joystick.h公开的数据结构、配置宏与函数声明quantum/process_keycode/process_joystick.c负责把QK_JOYSTICK_*键码转换为按键按下/释放调用builddefs/common_features.mk构建系统侧的功能开关与驱动合法性校验。启用功能与选择驱动在键盘目录或 keymap 目录的rules.mk中添加JOYSTICK_ENABLE yes默认使用的驱动是analog若需要改为digitalJOYSTICK_DRIVER digital从 builddefs/common_features.mk 可以看到构建期的具体行为JOYSTICK_ENABLE默认为no合法驱动仅有analog与digital两种写入其他值会在编译期触发CATASTROPHIC_ERROR启用后会自动追加编译process_joystick.c与joystick.c当驱动为analog时会额外把ANALOG_DRIVER_REQUIRED置为yes即同时依赖 ADC 驱动驱动选择最终通过-DJOYSTICK_ANALOG或-DJOYSTICK_DIGITAL传递给 C 编译器从而在 quantum/joystick.c 中决定是否#include analog.h。analog 驱动与 ARM 板的电压注意点使用analog驱动时ADC 采样依赖 ADC 驱动原文档链接为../drivers/adc已转换为仓库根目录相对路径。在 ARM 平台上需要注意必须使用 3.3V 为摇杆供电。虽然部分 ARM 板卡如 Helios带有 5V 引脚输出但 QMK 的 ADC 驱动并不支持 5V 采样超出量程的电压会导致读数失真甚至损坏引脚。在 config.h 中定制按键数、轴数与分辨率默认情况下Joystick 功能定义了2 个轴与8 个按键轴报告分辨率为8 位取值范围 -127 到 127。这些默认值可以在config.h中修改// 最小值 0最大值 32 #define JOYSTICK_BUTTON_COUNT 16 // 最小值 0最大值 6分别对应 X、Y、Z、Rx、Ry、Rz #define JOYSTICK_AXIS_COUNT 3 // 最小值 8最大值 16 #define JOYSTICK_AXIS_RESOLUTION 10这些边界并非文档约定而是硬性的编译期检查见 quantum/joystick.hJOYSTICK_BUTTON_COUNT 32时直接#errorJOYSTICK_AXIS_COUNT 6时直接#error当轴数与按键数同时为 0时会报错Joystick feature requires at least one axis or button即功能至少要声明一个轴或一个按键JOYSTICK_AXIS_RESOLUTION超出 816 范围时报错。分辨率直接决定了轴值范围JOYSTICK_MAX_VALUE在 quantum/joystick.h 中被定义为(1L (JOYSTICK_AXIS_RESOLUTION - 1)) - 1。例如 8 位分辨率对应最大值为 127范围 -12712710 位分辨率则对应 -511511。分辨率与 MCU ADC 精度的匹配需要注意ADC 采样精度上限受芯片约束。文档明确提示受支持的 AVR MCU 其 ADC 最大为 10 位多数 STM32 MCU 为 12 位。若JOYSTICK_AXIS_RESOLUTION设置得比 ADC 实际精度更高多出的位数只是数值换算的放大并不会提升真实采样精度。帽式开关Hat Switch启用 8 方向帽式开关在config.h中添加#define JOYSTICK_HAS_HAT从 quantum/joystick.h 可以看到只有定义了JOYSTICK_HAS_HATjoystick_t结构体中才会包含int8_t hat成员quantum/joystick.c 中的joystick_set_hat()也仅在宏开启时才会被编译。位置通过joystick_set_hat(value)设置。数值从顶部正北开始顺时针递增默认“居中”位置用-1表示布局如下0 7 N 1 NW .----. NE / \ 6 W | -1 | E 2 \ / SW --.-- SE 5 S 3 4也可以直接使用下表这些预定义名称DefineValueAngleJOYSTICK_HAT_CENTER-1JOYSTICK_HAT_NORTH00°JOYSTICK_HAT_NORTHEAST145°JOYSTICK_HAT_EAST290°JOYSTICK_HAT_SOUTHEAST3135°JOYSTICK_HAT_SOUTH4180°JOYSTICK_HAT_SOUTHWEST5225°JOYSTICK_HAT_WEST6270°JOYSTICK_HAT_NORTHWEST7315°这些常量同样定义在 quantum/joystick.h。轴的定义与两种配置宏当使用物理轴时必须在代码中提供轴配置数组通常写在keymap.c中joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] { JOYSTICK_AXIS_IN(A4, 900, 575, 285), JOYSTICK_AXIS_VIRTUAL };该示例定义了 2 个轴X 轴从A4引脚读取在默认 8 位分辨率下900575 的模拟值被线性映射为 -1270575285 映射为 0127Y 轴是虚拟轴不从任何引脚读取其值需要由用户代码主动更新。轴的两种配置宏见 quantum/joystick.hJOYSTICK_AXIS_IN(input_pin, low, rest, high)展开为{INPUT_PIN, LOW, REST, HIGH}即让 ADC 采样指定引脚。low、high、rest分别对应该轴模拟量的最小值、最大值、静止居中值JOYSTICK_AXIS_VIRTUAL展开为{NO_PIN, 0, JOYSTICK_MAX_VALUE / 2, JOYSTICK_MAX_VALUE}。input_pin为NO_PIN因此不会被 ADC 读取数值完全由用户代码提供。反转轴的小技巧将low与high对调即可实现轴方向反转。对应地joystick_config_t结构体包含 4 个成员见 quantum/joystick.hpin_t input_pin读取模拟值的引脚虚拟轴为NO_PINuint16_t min_digit模拟最小值uint16_t mid_digit静止/中点模拟值uint16_t max_digit模拟最大值。虚拟轴Virtual Axes下面的例子基于小键盘按键调整 X、Y 两个虚拟轴KC_P0作为“精细模式”修饰键按下后减小单步幅度joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] { JOYSTICK_AXIS_VIRTUAL, // x JOYSTICK_AXIS_VIRTUAL // y }; static bool precision false; static uint16_t precision_mod 64; static uint16_t axis_val 127; bool process_record_user(uint16_t keycode, keyrecord_t *record) { int16_t precision_val axis_val; if (precision) { precision_val - precision_mod; } switch (keycode) { case KC_P8: joystick_set_axis(1, record-event.pressed ? -precision_val : 0); return false; case KC_P2: joystick_set_axis(1, record-event.pressed ? precision_val : 0); return false; case KC_P4: joystick_set_axis(0, record-event.pressed ? -precision_val : 0); return false; case KC_P6: joystick_set_axis(0, record-event.pressed ? precision_val : 0); return false; case KC_P0: precision record-event.pressed; return false; } return true; }按下KC_P8/KC_P2分别把轴 1Y设为负/正方向的值KC_P4/KC_P6同理控制轴 0X松开时归零KC_P0按住时单步幅度从 127 降至 63实现精细操控。从源码看轴换算的内部流程物理轴的采样与量程换算逻辑位于 quantum/joystick.c 的joystick_read_axis()通过joystick_axis_sample()默认实现为analogReadPin()见 quantum/joystick.c读取原始模拟值先以mid_digit为参考点用(axis_val - ref) * -JOYSTICK_MAX_VALUE / (min_digit - ref)计算下半程映射若结果为正说明处于上半程改用max_digit计算正向映射最后把结果裁剪到[-JOYSTICK_MAX_VALUE, JOYSTICK_MAX_VALUE]即饱和钳位。此外joystick_axes[]数组本身以__attribute__((weak))定义quantum/joystick.c默认全部为虚拟轴你在keymap.c中定义的强符号数组会覆盖它。初始化时joystick_init()→joystick_init_axes()会对每个非虚拟轴执行gpio_set_pin_input()将引脚配置为输入模式。按键键码表Joystick 按键可以直接放入 keymap作为普通键码使用。按键按下时process_joystick()会调用register_joystick_button()释放时调用unregister_joystick_button()见 quantum/process_keycode/process_joystick.c。键码基址定义在 quantum/keycodes.hQK_JOYSTICK 0x7400QK_JOYSTICK_MAX 0x743F共 32 个按键可用KeyAliasesDescriptionQK_JOYSTICK_BUTTON_0JS_0Button 0QK_JOYSTICK_BUTTON_1JS_1Button 1QK_JOYSTICK_BUTTON_2JS_2Button 2QK_JOYSTICK_BUTTON_3JS_3Button 3QK_JOYSTICK_BUTTON_4JS_4Button 4QK_JOYSTICK_BUTTON_5JS_5Button 5QK_JOYSTICK_BUTTON_6JS_6Button 6QK_JOYSTICK_BUTTON_7JS_7Button 7QK_JOYSTICK_BUTTON_8JS_8Button 8QK_JOYSTICK_BUTTON_9JS_9Button 9QK_JOYSTICK_BUTTON_10JS_10Button 10QK_JOYSTICK_BUTTON_11JS_11Button 11QK_JOYSTICK_BUTTON_12JS_12Button 12QK_JOYSTICK_BUTTON_13JS_13Button 13QK_JOYSTICK_BUTTON_14JS_14Button 14QK_JOYSTICK_BUTTON_15JS_15Button 15QK_JOYSTICK_BUTTON_16JS_16Button 16QK_JOYSTICK_BUTTON_17JS_17Button 17QK_JOYSTICK_BUTTON_18JS_18Button 18QK_JOYSTICK_BUTTON_19JS_19Button 19QK_JOYSTICK_BUTTON_20JS_20Button 20QK_JOYSTICK_BUTTON_21JS_21Button 21QK_JOYSTICK_BUTTON_22JS_22Button 22QK_JOYSTICK_BUTTON_23JS_23Button 23QK_JOYSTICK_BUTTON_24JS_24Button 24QK_JOYSTICK_BUTTON_25JS_25Button 25QK_JOYSTICK_BUTTON_26JS_26Button 26QK_JOYSTICK_BUTTON_27JS_27Button 27QK_JOYSTICK_BUTTON_28JS_28Button 28QK_JOYSTICK_BUTTON_29JS_29Button 29QK_JOYSTICK_BUTTON_30JS_30Button 30QK_JOYSTICK_BUTTON_31JS_31Button 31按键数上限 32 与JOYSTICK_BUTTON_COUNT的编译期上限一致即使只启用了 8 个按键超出范围的键码也不会被处理register_joystick_button()内部有越界保护见 quantum/joystick.c。编程 API 参考以下 API 均声明于 quantum/joystick.h供用户在keymap.c或自定义代码中直接调用。struct joystick_t保存 joystick 的完整状态uint8_t buttons[]按位打包的按键状态数组长度按(JOYSTICK_BUTTON_COUNT - 1) / 8 1计算见 quantum/joystick.hint16_t axes[]每个已定义轴的模拟值数组int8_t hat帽式开关位置仅在启用JOYSTICK_HAS_HAT时存在bool dirty当前状态是否有变更、需要发送给主机。struct joystick_config_t描述单个轴成员已在“轴的定义”一节说明input_pin、min_digit、mid_digit、max_digit。void joystick_flush(void)若joystick_state.dirty为真则把报告发送给主机并清除 dirty 标记quantum/joystick.c。内部调用host_joystick_send(joystick_state)。void register_joystick_button(uint8_t button)将指定按钮置为按下状态并立即发送报告。参数button按键索引取值范围 031。实现joystick_state.buttons[button / 8] | 1 (button % 8)置 dirty 后 flushquantum/joystick.c。void unregister_joystick_button(uint8_t button)将指定按钮复位为释放状态并立即发送报告。参数button按键索引取值范围 031。实现joystick_state.buttons[button / 8] ~(1 (button % 8))quantum/joystick.c。int16_t joystick_read_axis(uint8_t axis)采样并处理指定轴的模拟值。参数axis要读取的轴。返回值有符号 16 位整数0表示静止/中点。注意joystick_read_axis()返回的是“按当前分辨率换算后的值”它通过joystick_axis_sample()读取原始 ADC 值并完成量程映射。void joystick_set_axis(uint8_t axis, int16_t value)设置指定轴的值。参数axis要设置的轴参数value要设置的值有符号 16 位整数受JOYSTICK_MAX_VALUE约束。实现上仅在新值与旧值不同时才置 dirtyquantum/joystick.c避免无谓的 USB 上报。虚拟轴的日常控制主要就靠这个函数。void joystick_set_hat(int8_t value)设置帽式开关的位置。参数value要设置的帽式开关位置使用JOYSTICK_HAT_*常量见上文表格。仅在定义JOYSTICK_HAS_HAT时可用quantum/joystick.c。后台任务joystick_task()固件主循环中会周期调用joystick_task()它遍历所有非虚拟轴并调用joystick_read_axes()完成一轮采样、换算与上报quantum/joystick.c。也就是说物理轴的值不需要你手动刷新——只要正确配置了joystick_axes[]主循环会自动保持轴数据更新虚拟轴则需要你自己在按键处理或自定义函数中调用joystick_set_axis()驱动。典型落地步骤小结在rules.mk中写入JOYSTICK_ENABLE yes并按需设置JOYSTICK_DRIVER analog或digital在config.h中按需调整JOYSTICK_BUTTON_COUNT、JOYSTICK_AXIS_COUNT、JOYSTICK_AXIS_RESOLUTION需要帽式开关时定义JOYSTICK_HAS_HAT在keymap.c中定义joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT]数组用JOYSTICK_AXIS_IN声明物理轴、JOYSTICK_AXIS_VIRTUAL声明虚拟轴在 keymap 中直接使用JS_0JS_31键码映射手柄按键或在process_record_user()中调用joystick_set_axis()/joystick_set_hat()实现自定义操控逻辑编译刷写后主机系统即会识别出一个 HID joystick 设备。需要进一步理解 ADC 采样底层原理时可继续阅读 docs/drivers/adc.md 与 ADC 驱动源码Joystick 功能的全部实现细节可对照 quantum/joystick.c 与 quantum/joystick.h 研读。【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考