アナログジョイスティックを QMK/Vial ファームウェアでマウスカーソル操作に使用するためのライブラリです。K-SILVER JS16 (TMR) / JH16 (Hall Effect) 等のアナログ出力ジョイスティックに対応しています。
- サブピクセル処理: x1000 スケールの内部精度で、速度 1.0 未満でも滑らかなカーソル移動
- ベクトル合成: X/Y 軸を合成ベクトルとして処理し、斜め方向も均一な速度で移動
- 円形デッドゾーン: 合成ベクトルの大きさで判定。全方向均一なデッドゾーン
- 二乗加速: スティックの傾き量の二乗に比例した加速度。傾け始めはゆっくり、大きく倒すほど速く加速
- 比例減速: スティックを戻すと、戻した量に応じて速度が比例的に減速。現在の傾き量に対応した速度上限へ向かって指数的に収束
- 移動平均フィルタ: ADC ノイズの除去
- 起動時自動センタリング: TMR センサーのウォームアップ待機と中心値の自動取得
- 非対称レンジ補正: 中心値が ADC レンジの中央にない場合でも方向ごとに正規化
- モデル選択:
config.hに#define JH16または#define JS16を記述するだけで各モデルの ADC レンジを適用 - 自動レンジ学習: モデル未定義なら実測値から ADC レンジを自動学習。モデルを問わず同一ファームウェアで動作し、学習結果は EEPROM に自動保存
- 取り付け向き補正:
JOYSTICK_ORIENTATIONで 90° 単位の回転補正に対応 - スクロールモード対応:
analog_stick_get_scroll_values()で加速なしの傾き量を取得可能 - ボタン対応: SW ピンによるマウスクリック(GPIO 直結、オプション)
- 全パラメータカスタマイズ可能:
config.hの#defineで上書き
- MCU: RP2040 (RP2040-Zero 等)
- QMK Firmware (Vial 対応版含む)
- ChibiOS (RP2040 の ADC ドライバを使用)
| File | Description |
|---|---|
qmk_analog_stick.h |
ヘッダファイル (デフォルトパラメータ定義 + API 宣言) |
qmk_analog_stick.c |
実装ファイル |
halconf.h |
ChibiOS HAL 設定 (ADC 有効化) |
mcuconf.h |
ChibiOS MCU 設定 (RP2040 ADC ドライバ有効化) |
qmk_analog_stick.h、qmk_analog_stick.c、halconf.h、mcuconf.h をキーボードディレクトリにコピーします。既に halconf.h や mcuconf.h が存在する場合は、手順 2・3 の内容を既存ファイルに追記してください。
keyboards/your_keyboard/
├── qmk_analog_stick.h
├── qmk_analog_stick.c
├── halconf.h
├── mcuconf.h
└── keymaps/default/
├── config.h
├── keymap.c
└── rules.mk
#pragma once
#define HAL_USE_ADC TRUE
#include_next <halconf.h>#pragma once
#include_next <mcuconf.h>
#undef RP_ADC_USE_ADC1
#define RP_ADC_USE_ADC1 TRUEジョイスティックの接続ピンとモデルを定義します。
#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
// ジョイスティックモデルを選択(省略すると自動レンジ学習モード)
#define JH16 // X: 8〜1023 / Y: 8〜782
// #define JS16 // X: 0〜1023 / Y: 0〜1023
// 自動レンジ学習モード + VIA/Vial 環境で学習レンジを保存する場合は必須
// #define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10
// ボタン機能を使う場合(オプション)
// #define JOYSTICK_SW_PIN GP13#include QMK_KEYBOARD_H
#include "qmk_analog_stick.h"
// ... キーマップ定義 ...
void keyboard_post_init_user(void) {
analog_stick_init();
}
report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
return analog_stick_update(mouse_report);
}POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = custom
SRC += analog.c qmk_analog_stick.canalog_stick_get_scroll_values() を使うと、加速カーブなしのリニアな傾き量(-1000〜+1000)を取得できます。特定レイヤーでスクロール動作に切り替える場合の実装例:
#define SCROLL_INTERVAL_MS 8 // スクロール蓄積の更新間隔(ms)
#define SCROLL_SPEED_DIV 6000 // 大きくすると遅く、小さくすると速い
#define SCROLL_MAX_SPEED 600 // 最高速度の上限(1〜1000)
static int32_t scroll_accum_h = 0;
static int32_t scroll_accum_v = 0;
static uint16_t scroll_timer = 0;
report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
if (IS_LAYER_ON(_SCROLL_LAYER)) {
int16_t stick_x, stick_y;
analog_stick_get_scroll_values(&stick_x, &stick_y);
if (stick_x > SCROLL_MAX_SPEED) stick_x = SCROLL_MAX_SPEED;
if (stick_x < -SCROLL_MAX_SPEED) stick_x = -SCROLL_MAX_SPEED;
if (stick_y > SCROLL_MAX_SPEED) stick_y = SCROLL_MAX_SPEED;
if (stick_y < -SCROLL_MAX_SPEED) stick_y = -SCROLL_MAX_SPEED;
if (timer_elapsed(scroll_timer) >= SCROLL_INTERVAL_MS) {
scroll_timer = timer_read();
scroll_accum_h += stick_x;
scroll_accum_v += stick_y;
}
mouse_report.x = 0;
mouse_report.y = 0;
mouse_report.h = (int8_t)(scroll_accum_h / SCROLL_SPEED_DIV);
mouse_report.v = (int8_t)(scroll_accum_v / SCROLL_SPEED_DIV);
scroll_accum_h %= SCROLL_SPEED_DIV;
scroll_accum_v %= SCROLL_SPEED_DIV;
} else {
mouse_report = analog_stick_update(mouse_report);
scroll_accum_h = 0;
scroll_accum_v = 0;
scroll_timer = timer_read();
}
return mouse_report;
}SCROLL_SPEED_DIVで全体速度を調整し、SCROLL_MAX_SPEEDで最高速度を抑制します- スクロール方向は
config.hのJOYSTICK_SCROLL_INVERT_V/JOYSTICK_SCROLL_INVERT_Hで上下・左右個別に反転できます(ライブラリ側で反転済みの値が返る) - カーソルモードは
analog_stick_update()をそのまま使用するため、スロットルの影響を受けません
ジョイスティックを初期化します。keyboard_post_init_user() 内で呼び出してください。
処理内容:
JOYSTICK_WARMUP_MSミリ秒待機 (TMR センサー安定化)- 64 回のサンプリングで X/Y 軸の中心値を自動取得(接続時センタリング)
- 移動平均バッファを中心値で初期化
JOYSTICK_SW_PIN定義時、ボタンピンを入力プルアップに設定- ADC レンジを設定(モデル定義があれば固定値、なければ 中心±
JOYSTICK_INITIAL_RANGEから自動学習開始) - 自動レンジ学習モードで保存機能有効時、EEPROM の保存済みレンジを読み込んで統合
- デバッグ有効時、中心値と ADC レンジをコンソール出力
注意: 初期化中はスティックに触れないでください。触れた状態の値が中心値として記録されます。
毎スキャンサイクルでマウスレポートを更新します。pointing_device_task_user() 内で呼び出してください。
引数:
| Name | Type | Description |
|---|---|---|
mouse_report |
report_mouse_t |
現在のマウスレポート |
戻り値:
更新されたマウスレポート (mouse_report.x、mouse_report.y、JOYSTICK_SW_PIN 定義時は mouse_report.buttons が設定される)
処理内容:
- ADC 読み取り + 移動平均フィルタ
- 各軸を -1000〜+1000 に正規化(非対称レンジ補正)
- X/Y の合成ベクトル(magnitude)を計算
- 円形デッドゾーン判定
- 現在の傾き量から速度上限 (speed_limit) を計算(
JOYSTICK_CURVE_POWER乗カーブ +JOYSTICK_CURVE_LOW_GAINによる低速域ブレンド) current_speed < speed_limitなら加速、current_speed > speed_limitなら比例減速- 取り付け向き補正を適用(
JOYSTICK_ORIENTATION) - 合成速度を X/Y 方向比率で分配
- サブピクセル蓄積 + 整数ピクセル変換
- ボタン状態読み取り (
JOYSTICK_SW_PIN定義時)
スクロールモード用に、加速カーブなしの正規化傾き量を返します。
引数:
| Name | Type | Description |
|---|---|---|
*out_x |
int16_t * |
X 軸の傾き量(-1000〜+1000) |
*out_y |
int16_t * |
Y 軸の傾き量(-1000〜+1000) |
処理内容:
- デッドゾーン処理済みの線形傾き量を返す
- 取り付け向き補正を適用
- スクロール反転設定(
JOYSTICK_SCROLL_INVERT_V/_H)を適用 analog_stick_update()の加速状態(current_speed、accel_accum、サブピクセル)をリセット
注意: スクロールモード中は analog_stick_update() の代わりにこちらを呼んでください。カーソルモードに戻った際に速度が跳ばないようにするため、毎サイクル加速状態をリセットします。
スティック傾き
↓
各軸を正規化 (-1000〜+1000)
↓
合成ベクトル magnitude = √(x² + y²)
↓
円形デッドゾーン判定 (magnitude > deadzone?)
↓ YES
デッドゾーン差し引き → adjusted_magnitude (0〜1000)
↓
カーブ適用: curved = (adjusted_magnitude / 1000)^CURVE_POWER × 1000
低速域ブレンド: curved = (LOW_GAIN × curved + (1000 - LOW_GAIN) × curved²/1000) / 1000
速度上限: speed_limit = curved × MAX_SPEED / 1000
↓
current_speed < speed_limit? → 加速: current_speed += adjusted_magnitude² × ACCEL_RATE / 1000000
current_speed > speed_limit? → 減速: current_speed -= (current_speed - speed_limit) × DECEL_RATE / 100
↓
取り付け向き補正 (JOYSTICK_ORIENTATION)
↓
方向分配: speed_x = current_speed × norm_x / magnitude
speed_y = current_speed × norm_y / magnitude
↓
サブピクセル蓄積 → mouse_report.x, mouse_report.y
スティックの傾き量の二乗が毎サイクルの加速度になります。倒し続けると速度が蓄積されていきます。
加速度/サイクル = (adjusted_magnitude / 1000)² × ACCEL_RATE
速度上限 = (adjusted_magnitude / 1000)² × MAX_SPEED
- 少し倒す: 加速度が小さい → ゆっくり加速 → 精密操作
- 大きく倒す: 加速度が大きい → 速く加速 → 素早い移動
- どの傾きでも 傾き量に対応した速度上限まで到達可能
- スティックをデッドゾーンまで戻す: 速度が即座にリセット
| 傾き | 加速度/cycle | 速度上限 (MAX_SPEED=6000) |
|---|---|---|
| 30% | 0.00144 | 540 (0.54 px/cycle) |
| 50% | 0.004 | 1500 (1.5 px/cycle) |
| 70% | 0.00784 | 2940 (2.94 px/cycle) |
| 100% | 0.016 | 6000 (6.0 px/cycle) |
傾き量→速度上限のカーブは 2 つのパラメータで調整できます。ハイブリッド速度モードが無効(JOYSTICK_ACCEL_THRESHOLD 0)のときのみ有効です。
JOYSTICK_CURVE_POWER(デフォルト2): カーブの指数。1でリニア、2で二次関数、3で三次関数。大きいほど浅い傾きが緻密になり、深い傾きで急激に速くなるJOYSTICK_CURVE_LOW_GAIN(デフォルト1000): 倒し始めの移動量の倍率(0〜1000)。500にすると倒し始めの速度が従来の半分になり、深く倒すほど緩やかに従来カーブへ近づいて、全倒しでは同じ最高速に到達する
JOYSTICK_CURVE_LOW_GAIN 500 のときの従来比:
| 傾き | 従来比の速度 |
|---|---|
| 倒し始め | 約 50% |
| 50% | 約 56% |
| 70% | 約 62% |
| 100% | 100%(最高速は変わらず) |
傾き量をしきい値で 2 つのゾーンに分けた速度モデルです(デフォルトで有効)。config.h で JOYSTICK_ACCEL_THRESHOLD 0 を定義すると無効になり、従来のカーブ方式(速度カーブの調整参照)で動作します。
#define JOYSTICK_ACCEL_THRESHOLD 900 // しきい値(0〜999、デフォルト 900、0 で無効)
#define JOYSTICK_DIRECT_SPEED 600 // 直接ゾーンの最高速(デフォルト 600)- 直接ゾーン(傾き ≦ しきい値): 傾き量に比例した速度をそのまま出力する。時間による加速がないため即応答で、倒し始めからしきい値までは緩やかに速度が変化する。しきい値ちょうどの傾きで
JOYSTICK_DIRECT_SPEEDに達する - 加速ゾーン(傾き > しきい値): しきい値の超過分(二乗)を加速度に変換し、
JOYSTICK_MAX_SPEEDまで時間とともに加速する。深く倒し込むほど速く加速する - 加速ゾーンから直接ゾーンへ戻ると、
JOYSTICK_DECEL_RATEに従って目標速度へ滑らかに減速する
精密操作は比例速度で直感的に、長距離移動は倒し込みで一気に、という使い分けができます。
注意: このモード中は JOYSTICK_CURVE_POWER / JOYSTICK_CURVE_LOW_GAIN は使用されません。加速の速さは従来どおり JOYSTICK_ACCEL_RATE で調整します。
スティックを戻すと speed_limit が下がり、current_speed がそれを超えた状態になります。その差分の DECEL_RATE% ずつ毎サイクル削減されます(指数的減衰)。
減速量/サイクル = (current_speed - speed_limit) × DECEL_RATE / 100
- ちょっと戻す: speed_limit が少し下がる → 緩やかな減速
- 大きく戻す: speed_limit が大きく下がる → 急減速
- デッドゾーンまで戻す: 最後の方向でゆっくり停止
デッドゾーンは X/Y 軸の合成ベクトルの大きさで判定します。
変更前(四角形) 本ライブラリ(円形)
+---+---+ .---.
| | | / \
+---+---+ | o |
| | | \ /
+---+---+ '---'
四角形デッドゾーンでは斜め方向で片方の軸だけ先に反応し、4方向に引っ張られる現象が発生します。円形デッドゾーンは全方向均一なデッドゾーンを提供します。
X/Y 軸を独立に処理すると、斜め方向で速度が不均一になります。本ライブラリでは合成ベクトルの大きさに対して加速度を計算し、方向比率で X/Y に分配します。
magnitude = √(norm_x² + norm_y²)
speed_x = current_speed × norm_x / magnitude
speed_y = current_speed × norm_y / magnitude
すべてのパラメータは config.h で #define することで上書きできます。
| Parameter | Description | Example |
|---|---|---|
JOYSTICK_X_PIN |
X 軸の ADC ピン | GP28 |
JOYSTICK_Y_PIN |
Y 軸の ADC ピン | GP29 |
config.h でモデルを定義すると固定 ADC レンジで動作します。
| Define | X 軸レンジ | Y 軸レンジ | 対象モデル |
|---|---|---|---|
#define JH16 |
8〜1023 | 8〜782 | K-SILVER JH16 (Hall Effect) |
#define JS16 |
0〜1023 | 0〜1023 | K-SILVER JS16 (TMR) |
| (未定義) | 自動学習 | 自動学習 | 全モデル対応(自動レンジ学習モード) |
個別の軸のレンジを上書きしたい場合は、JOYSTICK_ADC_X_MIN 等を config.h で定義してください。
// モデル定義後に個別上書きも可能
#define JH16
#define JOYSTICK_ADC_Y_MAX 800 // Y軸最大値だけ変更モデルを何も定義しない場合、ADC レンジを実測で自動学習します。モデルを問わず同一ファームウェアで動作させたい場合に使用してください。
- 起動時の中心値から
中心±JOYSTICK_INITIAL_RANGE(デフォルト250)の控えめなレンジで開始 - スティックを倒して実測値がレンジ外に出るたびに、その方向のレンジを自動拡張
- 数回全倒しすると実機のレンジに収束し、非対称なレンジ(JH16 の Y 軸など)も方向ごとに正しく学習される
特性:
- 学習が完了するまでは浅い傾きで最高速に達する(速度が出すぎる方向に誤差が出るだけで、最高速自体は変わらない)
- 学習結果は EEPROM に自動保存され、次回起動時の初期レンジとして読み込まれる(下記参照)
学習レンジの不揮発保存:
VIA/Vial 環境では、学習したレンジがデフォルトで EEPROM に自動保存されます。
- レンジ拡張が止まってから
JOYSTICK_RANGE_SAVE_DELAY_MS(デフォルト 3 秒)後に 1 回だけ書き込む(フラッシュ摩耗対策) - 起動時に保存値を読み込み、初期レンジと統合(広い方を採用)してから学習を再開する
- 保存先は VIA のカスタム設定領域(10 バイト)。
config.hに以下の定義が必須:この定義によりダイナミックキーマップ領域は 10 バイト後ろへずれて確保されるため、キーマップ領域を侵食することはない(予約が不足している場合はコンパイルエラーで検出される)#define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10
- 広いレンジのスティックから狭いレンジのスティックへ交換した場合は、保存された広いレンジが残って最高速に到達できなくなるため、Vial の EEPROM リセット(または
QK_CLEAR_EEPROM)で再学習させること
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_INITIAL_RANGE |
250 |
自動学習の初期半レンジ。実機の最小半レンジより小さい値にすること(大きいと最高速に到達できなくなる) |
JOYSTICK_RANGE_SAVE |
1(VIA 有効時) |
学習レンジの EEPROM 保存の有効/無効。VIA 無効環境ではデフォルト 0 |
JOYSTICK_RANGE_SAVE_DELAY_MS |
3000 |
レンジ拡張が止まってから保存するまでの待ち時間(ms) |
JOYSTICK_EEPROM_ADDR |
VIA カスタム設定領域 | 保存先アドレスの手動指定(VIA 無効環境で保存を使う場合に定義) |
JOYSTICK_SW_PIN を定義するとボタン機能が有効になります。未定義の場合、ボタン関連の処理は一切含まれません。
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_SW_PIN |
(未定義) | SW ピンの GPIO。定義するとボタン機能有効 |
JOYSTICK_SW_BUTTON |
MOUSE_BTN1 |
ボタンに割り当てるマウスボタン |
使用可能なマウスボタン定数:
| Constant | Description |
|---|---|
MOUSE_BTN1 |
左クリック (デフォルト) |
MOUSE_BTN2 |
右クリック |
MOUSE_BTN3 |
中クリック (ホイールクリック) |
MOUSE_BTN4 |
戻る |
MOUSE_BTN5 |
進む |
配線:
ジョイスティックの SW ピンは押下時に内部 GND に接続される(アクティブ LOW)ため、GPIO に直接接続するだけで動作します。外部プルアップ抵抗は不要です(MCU 内部プルアップを使用)。
Joystick SW -----> GPIOピン(内部プルアップ有効)
注意: ジョイスティックの SW ピンはキーマトリクスには直接接続できません。SW は押下時に内部 GND に短絡する 1 ピン出力のため、マトリクスの ROW-COL 間接続として機能しません。GPIO 直結で使用してください。
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_ORIENTATION |
0 |
取り付け向き補正(0: 標準、1: 時計回り90°、2: 180°、3: 反時計回り90°) |
JOYSTICK_SCROLL_INVERT_V |
0 |
縦スクロールの反転(1: 反転, 0: そのまま)。analog_stick_get_scroll_values() の Y 出力に適用 |
JOYSTICK_SCROLL_INVERT_H |
1 |
横スクロールの反転(1: 反転, 0: そのまま)。analog_stick_get_scroll_values() の X 出力に適用 |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_MAX_SPEED |
6000 |
最大速度 (x1000 スケール、6000 = 6.0 px/cycle) |
JOYSTICK_ACCEL_RATE |
16 |
加速度係数。大きいほど速く加速する |
JOYSTICK_DECEL_RATE |
8 |
減速率 (%)。スティックを戻したとき速度上限との差のこの割合ずつ減速する |
JOYSTICK_CURVE_POWER |
2 |
速度カーブの指数 (1: リニア, 2: 二次, 3: 三次) |
JOYSTICK_CURVE_LOW_GAIN |
1000 |
倒し始めの移動量の倍率 (0〜1000)。500 で倒し始めが半分になる。最高速は変わらない |
JOYSTICK_ACCEL_THRESHOLD |
900 |
ハイブリッド速度モードのしきい値 (0〜999)。傾きがこの値以下は比例速度、超えると加速。0 で無効(従来カーブ方式になる。詳細は Architecture 参照) |
JOYSTICK_DIRECT_SPEED |
600 |
ハイブリッドモードの直接ゾーン最高速 (x1000 スケール) |
ACCEL_RATE の目安:
| ACCEL_RATE | 全倒し時の速度上限到達時間 |
|---|---|
8 |
約10秒 |
16 |
約5秒 (デフォルト) |
32 |
約2.5秒 |
64 |
約1.25秒 |
モデル選択(JH16/JS16)で自動設定されます。個別に上書きする場合のみ定義が必要です。
| Parameter | Description |
|---|---|
JOYSTICK_ADC_X_MIN |
X 軸の ADC 最小値 |
JOYSTICK_ADC_X_MAX |
X 軸の ADC 最大値 |
JOYSTICK_ADC_Y_MIN |
Y 軸の ADC 最小値 |
JOYSTICK_ADC_Y_MAX |
Y 軸の ADC 最大値 |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_DEADZONE |
30 |
中心付近のデッドゾーン (ADC 値) |
JOYSTICK_SMOOTHING |
4 |
移動平均のサンプル数 |
JOYSTICK_RANGE_MARGIN |
10 |
レンジ端マージン (%、0〜50)。レンジ端の手前この割合の位置で最大傾き (±1000) に達する。端まで倒しきれない個体でも全方向で最高速に到達できる |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_WARMUP_MS |
1000 |
TMR センサー安定待ち時間 (ms) |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_DEBUG |
1 |
デバッグ出力 (1: 有効, 0: 無効) |
有効時、コンソールに約 1 秒ごとに以下の情報を出力します:
AnalogStick center: X=512 Y=498
AnalogStick range: X=8~1023 Y=8~782
Xr=450 cx=449 dx=0 | Yr=520 cy=519 dy=0 | spd=0
| Field | Description |
|---|---|
Xr / Yr |
スムージング後の ADC raw 値 |
cx / cy |
起動時に計測した中心値 |
dx / dy |
出力されたマウス移動量 (ピクセル) |
spd |
現在の速度 (x1000 スケール) |
自動レンジ学習モードで学習レンジが EEPROM に保存されたときは、次の行が出力されます:
AnalogStick range saved: X=8~1023 Y=8~782
デバッグ出力を確認するには:
- QMK Toolbox: 接続するとコンソール出力が表示される
- qmk console: ターミナルで
qmk consoleを実行
#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_DEADZONE 60
#define JOYSTICK_MAX_SPEED 3000
#define JOYSTICK_ACCEL_RATE 4
#define JOYSTICK_DECEL_RATE 30#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_DEADZONE 20
#define JOYSTICK_MAX_SPEED 12000
#define JOYSTICK_ACCEL_RATE 64
#define JOYSTICK_DECEL_RATE 80#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_SW_PIN GP13#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JS16#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
// JH16 / JS16 を定義しない → 自動レンジ学習モード
// 学習レンジの EEPROM 保存用(VIA/Vial 環境で必須)
#define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10本ライブラリは ADC でアナログ電圧を読み取る方式のジョイスティックに広く対応しています。動作確認済みのモデル:
- K-SILVER JS16 (TMR 方式)
- K-SILVER JH16 (Hall Effect 方式)
上記以外のアナログ出力ジョイスティックも、自動レンジ学習モードを使えばレンジ設定なしで動作します。ただしデジタル出力(SPI/I2C 等)のモデルには対応していません(例: 静電容量式の K-SILVER JL16 は出力方式が未確認のため、アナログ電圧出力であることを確認してから使用してください)。
- センシング方式: TMR (トンネル磁気抵抗効果) / Hall Effect
- 動作電圧: 1.8V 〜 3.3V
- 消費電力: 約 210 〜 215 uA
- 耐久性: 約 500 万回転
- ピン: VCC, GND, X 出力, Y 出力, SW (ボタン)
RP2040 で ADC として使用可能なピンは以下の 4 つです:
| Pin | ADC Channel |
|---|---|
| GP26 | ADC0 |
| GP27 | ADC1 |
| GP28 | ADC2 |
| GP29 | ADC3 |
注意: 一部のボードでは GP29 が VSYS 電圧計測用に使用されている場合があります。その場合は GP26/GP27/GP28 を使用してください。
Joystick RP2040-Zero
---------- -----------
VCC -----> 3.3V
GND -----> GND
X out -----> GP28 (ADC ピン)
Y out -----> GP29 (ADC ピン)
SW -----> GP13 (任意の GPIO、オプション)
K-SILVER JS16/JH16 は EC12/EC11 ロータリーエンコーダーのフットプリントに実装できますが、ピンの役割が異なるため配線に注意が必要です。
EC12 ピン Joystick ピン 接続先
--------- ------------ ------
Encoder A --> X output --> ADC ピン (GP28 等)
Encoder GND -> GND --> GND
Encoder B --> Y output --> ADC ピン (GP29 等)
Switch 1 --> VCC --> 3.3V 電源ライン (※)
Switch 2 --> SW --> GPIO ピン (※)
(※) 注意: EC12 のスイッチピンは通常キーマトリクスの ROW/COL に接続されています。ジョイスティックの VCC は常時 3.3V 給電が必要なため、Switch 1 のパッドが 3.3V 電源ラインに接続されていることを確認してください。Switch 2 (SW) はマトリクスではなく GPIO に直結する必要があります。
JOYSTICK_DEADZONEを大きくしてください (例:80)JOYSTICK_WARMUP_MSを大きくしてセンタリング精度を上げてください (例:3000)- 起動時にスティックに触れないでください
halconf.hでHAL_USE_ADC TRUEが定義されているか確認mcuconf.hでRP_ADC_USE_ADC1 TRUEが定義されているか確認rules.mkにSRC += analog.cが含まれているか確認- ジョイスティックの VCC が 3.3V に接続されているか確認
JOYSTICK_DEBUG 1でコンソール出力を確認し、ADC 値が変化するか確認
- 使用中のモデルに対応する
#define JH16または#define JS16がconfig.hに定義されているか確認 - モデル定義を削除して自動レンジ学習モードにすると、実機のレンジを方向ごとに自動学習します(接続後にスティックを数回全倒ししてください)
- 特定方向だけ加速が鈍い場合は、その方向のレンジ端まで通常の全倒しで届いていない可能性があります(一度きりの深押しやスパイクで学習レンジが広がった場合など)。
JOYSTICK_RANGE_MARGIN(デフォルト 10%)を大きくすると、端まで届かなくても最大傾きに達するようになります - それでも解消しない場合は、
JOYSTICK_ADC_X/Y_MIN/MAXを実際の ADC 出力レンジに合わせて個別定義してください JOYSTICK_DEBUG 1を有効にして起動時のログ(AnalogStick range:)と実際の ADC 値を比較してください
- 学習が収束するまでの仕様です。接続後にスティックをゆっくり一周(全倒し)させると実機のレンジに収束します
- 学習結果は EEPROM に保存されるため、発生するのは初回起動時(または EEPROM リセット後)のみです
- 気になる場合は
#define JH16/#define JS16で固定レンジにしてください
- 本ライブラリはベクトル合成と円形デッドゾーンで対策済みです
- それでも発生する場合は
JOYSTICK_ACCEL_RATEを小さくして最大速度を抑えてください JOYSTICK_SMOOTHINGを大きくすると入力が滑らかになります
config.hでJOYSTICK_SW_PINが定義されているか確認- SW ピンが GPIO に直接接続されているか確認(キーマトリクス経由では動作しません)
- テスターで SW ピンと GND 間を計測し、押下時に導通するか確認
- SW ピンが GND にショートしていないか確認
- 配線が正しいか確認(SW ピンはアクティブ LOW: 押すと GND に接続)
MIT
A library for using analog joysticks as mouse cursor controllers in QMK/Vial firmware. Compatible with analog output joysticks such as the K-SILVER JS16 (TMR) and JH16 (Hall Effect).
- Sub-pixel processing: Internal x1000 scale precision for smooth cursor movement even at speeds below 1.0 px/cycle
- Vector composition: X/Y axes are processed as a combined vector, providing uniform speed in all directions including diagonals
- Circular deadzone: Deadzone evaluated against the magnitude of the combined vector — uniform in all directions
- Quadratic acceleration: Acceleration proportional to the square of the tilt amount — slow at start, faster as you push further
- Proportional deceleration: Returning the stick reduces speed proportionally; current speed converges exponentially toward the speed limit for the current tilt
- Moving average filter: ADC noise reduction
- Startup auto-centering: TMR sensor warmup delay and automatic center value acquisition at boot
- Asymmetric range correction: Per-direction normalization even when the center output is not at the midpoint of the ADC range
- Model selection: Define
#define JH16or#define JS16inconfig.hto apply the correct ADC range for each model - Adaptive range learning: With no model defined, the ADC range is learned automatically from actual readings — one firmware works with any model, and the learned range is persisted to EEPROM
- Orientation correction:
JOYSTICK_ORIENTATIONsupports 90° rotation correction for non-standard mounting - Scroll mode support:
analog_stick_get_scroll_values()returns linear tilt values without acceleration - Button support: Mouse click via SW pin (direct GPIO connection, optional)
- Fully configurable: All parameters can be overridden via
#defineinconfig.h
- MCU: RP2040 (RP2040-Zero, etc.)
- QMK Firmware (including Vial-compatible forks)
- ChibiOS (uses the RP2040 ADC driver)
| File | Description |
|---|---|
qmk_analog_stick.h |
Header file (default parameter definitions + API declarations) |
qmk_analog_stick.c |
Implementation file |
halconf.h |
ChibiOS HAL configuration (enables ADC) |
mcuconf.h |
ChibiOS MCU configuration (enables RP2040 ADC driver) |
Copy qmk_analog_stick.h, qmk_analog_stick.c, halconf.h, and mcuconf.h into your keyboard directory. If halconf.h or mcuconf.h already exist, merge the content from steps 2 and 3 into the existing files.
keyboards/your_keyboard/
├── qmk_analog_stick.h
├── qmk_analog_stick.c
├── halconf.h
├── mcuconf.h
└── keymaps/default/
├── config.h
├── keymap.c
└── rules.mk
#pragma once
#define HAL_USE_ADC TRUE
#include_next <halconf.h>#pragma once
#include_next <mcuconf.h>
#undef RP_ADC_USE_ADC1
#define RP_ADC_USE_ADC1 TRUEDefine the joystick pins and select the joystick model.
#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
// Select the joystick model (omit both for adaptive range mode)
#define JH16 // X: 8~1023 / Y: 8~782
// #define JS16 // X: 0~1023 / Y: 0~1023
// Required to persist the learned range in adaptive range mode (VIA/Vial)
// #define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10
// Optional: enable button support
// #define JOYSTICK_SW_PIN GP13#include QMK_KEYBOARD_H
#include "qmk_analog_stick.h"
// ... keymap definitions ...
void keyboard_post_init_user(void) {
analog_stick_init();
}
report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
return analog_stick_update(mouse_report);
}POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = custom
SRC += analog.c qmk_analog_stick.cUsing analog_stick_get_scroll_values(), you can obtain linear (non-accelerated) tilt values (-1000 to +1000) for scroll behavior on a specific layer:
#define SCROLL_INTERVAL_MS 8 // Accumulation interval (ms)
#define SCROLL_SPEED_DIV 6000 // Larger = slower, smaller = faster
#define SCROLL_MAX_SPEED 600 // Maximum scroll speed cap (1~1000)
static int32_t scroll_accum_h = 0;
static int32_t scroll_accum_v = 0;
static uint16_t scroll_timer = 0;
report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
if (IS_LAYER_ON(_SCROLL_LAYER)) {
int16_t stick_x, stick_y;
analog_stick_get_scroll_values(&stick_x, &stick_y);
if (stick_x > SCROLL_MAX_SPEED) stick_x = SCROLL_MAX_SPEED;
if (stick_x < -SCROLL_MAX_SPEED) stick_x = -SCROLL_MAX_SPEED;
if (stick_y > SCROLL_MAX_SPEED) stick_y = SCROLL_MAX_SPEED;
if (stick_y < -SCROLL_MAX_SPEED) stick_y = -SCROLL_MAX_SPEED;
if (timer_elapsed(scroll_timer) >= SCROLL_INTERVAL_MS) {
scroll_timer = timer_read();
scroll_accum_h += stick_x;
scroll_accum_v += stick_y;
}
mouse_report.x = 0;
mouse_report.y = 0;
mouse_report.h = (int8_t)(scroll_accum_h / SCROLL_SPEED_DIV);
mouse_report.v = (int8_t)(scroll_accum_v / SCROLL_SPEED_DIV);
scroll_accum_h %= SCROLL_SPEED_DIV;
scroll_accum_v %= SCROLL_SPEED_DIV;
} else {
mouse_report = analog_stick_update(mouse_report);
scroll_accum_h = 0;
scroll_accum_v = 0;
scroll_timer = timer_read();
}
return mouse_report;
}- Use
SCROLL_SPEED_DIVto set the overall scroll speed, andSCROLL_MAX_SPEEDto cap the maximum - Scroll direction can be inverted per axis with
JOYSTICK_SCROLL_INVERT_V/JOYSTICK_SCROLL_INVERT_Hinconfig.h(the library returns pre-inverted values) - Cursor mode uses
analog_stick_update()directly with no rate limiting
Initializes the joystick. Call this inside keyboard_post_init_user().
What it does:
- Waits
JOYSTICK_WARMUP_MSmilliseconds for the TMR sensor to stabilize - Samples the X/Y axes 64 times to auto-detect center values (startup centering)
- Initializes the moving average buffer with the center values
- If
JOYSTICK_SW_PINis defined, configures the button pin as input with pull-up - Sets the ADC range (fixed values if a model is defined; otherwise adaptive learning starts from center ±
JOYSTICK_INITIAL_RANGE) - In adaptive range mode with persistence enabled, loads and merges the saved range from EEPROM
- If debug is enabled, prints center values and ADC range to the console
Note: Do not touch the stick during initialization. Any deflection will be recorded as the center value.
Updates the mouse report on every scan cycle. Call this inside pointing_device_task_user().
Arguments:
| Name | Type | Description |
|---|---|---|
mouse_report |
report_mouse_t |
Current mouse report |
Returns:
Updated mouse report with mouse_report.x, mouse_report.y, and (if JOYSTICK_SW_PIN is defined) mouse_report.buttons set.
What it does:
- ADC read + moving average filter
- Normalize each axis to -1000~+1000 (with asymmetric range correction)
- Calculate the combined vector magnitude (X/Y)
- Circular deadzone check
- Calculate speed limit from current tilt (
JOYSTICK_CURVE_POWERcurve + low-speed blend viaJOYSTICK_CURVE_LOW_GAIN) - If
current_speed < speed_limit: accelerate; ifcurrent_speed > speed_limit: proportionally decelerate - Apply orientation correction (
JOYSTICK_ORIENTATION) - Distribute combined speed to X/Y axes by direction ratio
- Sub-pixel accumulation + integer pixel conversion
- Read button state (if
JOYSTICK_SW_PINis defined)
Returns linear (non-accelerated) normalized tilt values for scroll mode.
Arguments:
| Name | Type | Description |
|---|---|---|
*out_x |
int16_t * |
X axis tilt (-1000~+1000) |
*out_y |
int16_t * |
Y axis tilt (-1000~+1000) |
What it does:
- Returns deadzone-processed linear tilt values
- Applies orientation correction
- Applies the scroll inversion settings (
JOYSTICK_SCROLL_INVERT_V/_H) - Resets
analog_stick_update()acceleration state (current_speed,accel_accum, sub-pixels)
Note: Call this instead of analog_stick_update() while in scroll mode. The acceleration state is reset every cycle to prevent the cursor from jumping when switching back to cursor mode.
Stick tilt
↓
Normalize each axis (-1000~+1000)
↓
Combined vector: magnitude = √(x² + y²)
↓
Circular deadzone check (magnitude > deadzone?)
↓ YES
Subtract deadzone → adjusted_magnitude (0~1000)
↓
Apply curve: curved = (adjusted_magnitude / 1000)^CURVE_POWER × 1000
Low-speed blend: curved = (LOW_GAIN × curved + (1000 - LOW_GAIN) × curved²/1000) / 1000
Speed limit: speed_limit = curved × MAX_SPEED / 1000
↓
current_speed < speed_limit? → Accelerate: current_speed += adjusted_magnitude² × ACCEL_RATE / 1000000
current_speed > speed_limit? → Decelerate: current_speed -= (current_speed - speed_limit) × DECEL_RATE / 100
↓
Apply orientation correction (JOYSTICK_ORIENTATION)
↓
Direction distribution: speed_x = current_speed × norm_x / magnitude
speed_y = current_speed × norm_y / magnitude
↓
Sub-pixel accumulation → mouse_report.x, mouse_report.y
The deadzone is evaluated against the magnitude of the combined X/Y vector.
Square deadzone (before) This library (circular)
+---+---+ .---.
| | | / \
+---+---+ | o |
| | | \ /
+---+---+ '---'
A square deadzone causes one axis to react before the other in diagonal directions, producing a four-directional pull effect. The circular deadzone provides a uniform deadzone in all directions.
Processing X and Y axes independently leads to non-uniform speed in diagonal directions. This library calculates acceleration against the combined vector magnitude, then distributes it to X/Y by direction ratio.
magnitude = √(norm_x² + norm_y²)
speed_x = current_speed × norm_x / magnitude
speed_y = current_speed × norm_y / magnitude
The tilt-to-speed-limit curve can be adjusted with two parameters. These apply only when hybrid speed mode is disabled (JOYSTICK_ACCEL_THRESHOLD 0).
JOYSTICK_CURVE_POWER(default2): Curve exponent.1= linear,2= quadratic,3= cubic. Higher values give finer control at small tilts and a sharper ramp at large tilts.JOYSTICK_CURVE_LOW_GAIN(default1000): Initial-tilt speed multiplier (0~1000). Setting500halves the speed at small tilts; the curve then gradually converges back so full tilt still reaches the same top speed.
Speed relative to default with JOYSTICK_CURVE_LOW_GAIN 500:
| Tilt | Relative speed |
|---|---|
| Small tilt | ~50% |
| 50% | ~56% |
| 70% | ~62% |
| 100% | 100% (top speed unchanged) |
A speed model that splits the tilt range into two zones at a threshold (enabled by default). Define JOYSTICK_ACCEL_THRESHOLD 0 in config.h to disable it and fall back to the legacy curve model (see Speed Curve Tuning).
#define JOYSTICK_ACCEL_THRESHOLD 900 // threshold (0~999; default 900; 0 to disable)
#define JOYSTICK_DIRECT_SPEED 600 // top speed of the direct zone (default 600)- Direct zone (tilt ≤ threshold): Speed is directly proportional to tilt. There is no time-based acceleration, so response is immediate and speed changes gently from initial tilt up to the threshold, reaching
JOYSTICK_DIRECT_SPEEDexactly at the threshold - Acceleration zone (tilt > threshold): The amount beyond the threshold (squared) is converted to acceleration, ramping up toward
JOYSTICK_MAX_SPEEDover time — the deeper the tilt, the faster the ramp - Returning from the acceleration zone to the direct zone decelerates smoothly toward the target speed per
JOYSTICK_DECEL_RATE
This gives intuitive proportional control for precision work, with a deep-tilt "boost" for long-distance movement.
Note: JOYSTICK_CURVE_POWER / JOYSTICK_CURVE_LOW_GAIN are not used in this mode. Tune the ramp speed with JOYSTICK_ACCEL_RATE as usual.
All parameters can be overridden in config.h using #define.
| Parameter | Description | Example |
|---|---|---|
JOYSTICK_X_PIN |
ADC pin for the X axis | GP28 |
JOYSTICK_Y_PIN |
ADC pin for the Y axis | GP29 |
Defining a model in config.h selects fixed ADC ranges:
| Define | X range | Y range | Target model |
|---|---|---|---|
#define JH16 |
8~1023 | 8~782 | K-SILVER JH16 (Hall Effect) |
#define JS16 |
0~1023 | 0~1023 | K-SILVER JS16 (TMR) |
| (none) | auto-learned | auto-learned | Any model (adaptive range mode) |
Individual axis ranges can be overridden after the model define:
#define JH16
#define JOYSTICK_ADC_Y_MAX 800 // override Y max onlyWhen no model is defined, the ADC range is learned automatically from actual readings. Use this when a single firmware should work with any joystick model.
- Starts with a conservative range of
center ± JOYSTICK_INITIAL_RANGE(default250) measured at boot - Whenever a reading falls outside the current range, the range expands in that direction
- After a few full tilts, the range converges to the actual hardware range — asymmetric ranges (such as the JH16 Y axis) are learned correctly per direction
Characteristics:
- Until learning converges, top speed is reached at a shallower tilt (the error is only toward being faster; top speed itself is unchanged)
- The learned range is automatically persisted to EEPROM and loaded as the initial range on the next boot (see below)
Persisting the learned range:
In VIA/Vial environments, the learned range is saved to EEPROM by default.
- A single write occurs
JOYSTICK_RANGE_SAVE_DELAY_MS(default 3 s) after range expansion stops (flash wear protection) - At boot, the saved values are loaded and merged with the initial range (the wider one wins), then learning continues
- Storage uses the VIA custom config area (10 bytes). The following define is required in
config.h:This reservation shifts the dynamic keymap area 10 bytes forward, so the keymap area is never encroached (an insufficient reservation is caught as a compile error)#define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10
- When swapping from a wider-range stick to a narrower-range one, the saved wide range would make top speed unreachable — reset the EEPROM (Vial's EEPROM reset or
QK_CLEAR_EEPROM) to re-learn
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_INITIAL_RANGE |
250 |
Initial half-range for adaptive mode. Must be smaller than the hardware's smallest half-range (a larger value would make top speed unreachable) |
JOYSTICK_RANGE_SAVE |
1 (when VIA is enabled) |
Enable/disable EEPROM persistence of the learned range. Defaults to 0 without VIA |
JOYSTICK_RANGE_SAVE_DELAY_MS |
3000 |
Delay (ms) after the last range expansion before saving |
JOYSTICK_EEPROM_ADDR |
VIA custom config area | Manual storage address (define this to use persistence without VIA) |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_SW_PIN |
(undefined) | GPIO pin for SW. Defining this enables button support |
JOYSTICK_SW_BUTTON |
MOUSE_BTN1 |
Mouse button assigned to the joystick switch |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_ORIENTATION |
0 |
Mounting orientation correction (0: standard, 1: CW 90°, 2: 180°, 3: CCW 90°) |
JOYSTICK_SCROLL_INVERT_V |
0 |
Invert vertical scroll (1: inverted, 0: normal). Applied to the Y output of analog_stick_get_scroll_values() |
JOYSTICK_SCROLL_INVERT_H |
1 |
Invert horizontal scroll (1: inverted, 0: normal). Applied to the X output of analog_stick_get_scroll_values() |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_MAX_SPEED |
6000 |
Maximum speed (x1000 scale; 6000 = 6.0 px/cycle) |
JOYSTICK_ACCEL_RATE |
16 |
Acceleration coefficient — higher values accelerate faster |
JOYSTICK_CURVE_POWER |
2 |
Speed curve exponent (1: linear, 2: quadratic, 3: cubic) |
JOYSTICK_CURVE_LOW_GAIN |
1000 |
Initial-tilt speed multiplier (0~1000). 500 halves the speed at small tilts while keeping the same top speed at full tilt |
JOYSTICK_ACCEL_THRESHOLD |
900 |
Hybrid speed mode threshold (0~999): proportional speed below the threshold, acceleration above it. 0 disables hybrid mode (legacy curve model; see Architecture) |
JOYSTICK_DIRECT_SPEED |
600 |
Top speed of the direct zone in hybrid mode (x1000 scale) |
JOYSTICK_DECEL_RATE |
8 |
Deceleration rate (%). Percentage of speed excess shed per cycle when returning the stick |
Set automatically by model selection. Override individually if needed.
| Parameter | Description |
|---|---|
JOYSTICK_ADC_X_MIN |
Minimum ADC value for X axis |
JOYSTICK_ADC_X_MAX |
Maximum ADC value for X axis |
JOYSTICK_ADC_Y_MIN |
Minimum ADC value for Y axis |
JOYSTICK_ADC_Y_MAX |
Maximum ADC value for Y axis |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_DEADZONE |
30 |
Center deadzone radius (ADC units) |
JOYSTICK_RANGE_MARGIN |
10 |
Range-edge margin (%, 0~50). Normalized tilt reaches its maximum (±1000) this fraction before the ADC range edge, so every direction can reach top speed even if the physical stick can't quite reach the learned/configured extremes |
JOYSTICK_SMOOTHING |
4 |
Moving average sample count |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_WARMUP_MS |
1000 |
TMR sensor stabilization wait time (ms) |
| Parameter | Default | Description |
|---|---|---|
JOYSTICK_DEBUG |
1 |
Debug output (1: enabled, 0: disabled) |
When enabled, the following is printed at startup and approximately once per second during use:
AnalogStick center: X=512 Y=498
AnalogStick range: X=8~1023 Y=8~782
Xr=450 cx=449 dx=0 | Yr=520 cy=519 dy=0 | spd=0
When the learned range is saved to EEPROM in adaptive range mode, the following line is printed:
AnalogStick range saved: X=8~1023 Y=8~782
To view debug output:
- QMK Toolbox: Connect the keyboard and console output appears automatically
- qmk console: Run
qmk consolein a terminal
#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_DEADZONE 60
#define JOYSTICK_MAX_SPEED 3000
#define JOYSTICK_ACCEL_RATE 4
#define JOYSTICK_DECEL_RATE 30#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_DEADZONE 20
#define JOYSTICK_MAX_SPEED 12000
#define JOYSTICK_ACCEL_RATE 64
#define JOYSTICK_DECEL_RATE 80#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JH16
#define JOYSTICK_SW_PIN GP13#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
#define JS16#define JOYSTICK_X_PIN GP28
#define JOYSTICK_Y_PIN GP29
// No JH16 / JS16 define → adaptive range mode
// Required to persist the learned range (VIA/Vial environments)
#define VIA_EEPROM_CUSTOM_CONFIG_SIZE 10This library is broadly compatible with any joystick that outputs analog voltage read via ADC. Confirmed working models:
- K-SILVER JS16 (TMR sensing)
- K-SILVER JH16 (Hall Effect sensing)
Other analog-output joysticks also work without any range configuration when using adaptive range mode. Digital-output models (SPI/I2C etc.) are not supported (e.g., the capacitive K-SILVER JL16 has an unconfirmed output type — verify it outputs analog voltage before use).
- Sensing method: TMR (Tunnel Magnetoresistance) / Hall Effect
- Operating voltage: 1.8V – 3.3V
- Current consumption: ~210–215 µA
- Durability: ~5 million rotations
- Pins: VCC, GND, X output, Y output, SW (button)
| Pin | ADC Channel |
|---|---|
| GP26 | ADC0 |
| GP27 | ADC1 |
| GP28 | ADC2 |
| GP29 | ADC3 |
Note: On some boards, GP29 may be reserved for VSYS voltage monitoring. In that case, use GP26, GP27, or GP28.
Joystick RP2040-Zero
---------- -----------
VCC -----> 3.3V
GND -----> GND
X out -----> GP28 (ADC pin)
Y out -----> GP29 (ADC pin)
SW -----> GP13 (any GPIO, optional)
EC12 pin Joystick pin Connect to
--------- ------------ ----------
Encoder A --> X output --> ADC pin (GP28, etc.)
Encoder GND -> GND --> GND
Encoder B --> Y output --> ADC pin (GP29, etc.)
Switch 1 --> VCC --> 3.3V power rail (※)
Switch 2 --> SW --> GPIO pin (※)
(※) Note: EC12 switch pins are normally connected to the key matrix ROW/COL lines. The joystick VCC requires a continuous 3.3V supply, so verify that the Switch 1 pad is connected to the 3.3V power rail. Switch 2 (SW) must be wired directly to a GPIO, not to the matrix.
- Increase
JOYSTICK_DEADZONE(e.g.,80) - Increase
JOYSTICK_WARMUP_MSto improve centering accuracy (e.g.,3000) - Do not touch the stick during startup
- Check that
HAL_USE_ADC TRUEis defined inhalconf.h - Check that
RP_ADC_USE_ADC1 TRUEis defined inmcuconf.h - Check that
SRC += analog.cis included inrules.mk - Check that joystick VCC is connected to 3.3V
- Enable
JOYSTICK_DEBUG 1and check the console to confirm ADC values are changing
- Confirm that the correct model (
#define JH16or#define JS16) is defined inconfig.h - Alternatively, remove the model define to use adaptive range mode, which learns the actual per-direction range automatically (tilt the stick fully a few times after connecting)
- If only one direction accelerates sluggishly, the stick may not reach that direction's range edge during normal full tilt (e.g., the learned range was widened by a one-time hard press or a spike). Increase
JOYSTICK_RANGE_MARGIN(default 10%) so maximum tilt is reached before the edge - If it persists, override
JOYSTICK_ADC_X/Y_MIN/MAXwith the actual measured ADC range - Enable
JOYSTICK_DEBUG 1and compare the startup log (AnalogStick range:) against the actual ADC values at full tilt
- This is expected until learning converges. Slowly swirl the stick once at full tilt after connecting
- Since the learned range is persisted to EEPROM, this only happens on the first boot (or after an EEPROM reset)
- If this bothers you, use fixed ranges with
#define JH16/#define JS16
- This library addresses this with vector composition and a circular deadzone
- If it still occurs, reduce
JOYSTICK_ACCEL_RATEto limit maximum speed - Increasing
JOYSTICK_SMOOTHINGsmooths out the input
- Check that
JOYSTICK_SW_PINis defined inconfig.h - Check that the SW pin is connected directly to a GPIO (not through the key matrix)
- Use a multimeter to confirm continuity between SW and GND when pressed
- Check that the SW pin is not shorted to GND
- Check the wiring (SW is active LOW: pressing connects it to GND)
MIT