ミニ②:ホール効果ジョイスティック入力
データシートのない謎のジョイスティックに挑む、ラジコンカー製作の第2章。 テスター探偵から配線・プログラミングまで、Trial & Error をそのまま記録していきます。
Step 0: 構想と遭遇、テスター探偵
ホールセンサーを使った、JoystickなるものをECサイトで発見。PS5, Switchの交換部品?? どうせJoycon作るのならば、よくある抵抗分圧センサより、ホールセンサーなるもの攻略してみることにした。
3ピンなので、V, S, Gでしょうと、適当に3.7V電源で、テスター当てました。
向かって、左から G, S, V でした。
抵抗センサーと違って、当然、極性がありました。
電源と GND を逆につないだ瞬間、天国に召されました。
→ 機能不全となりましたので、分解してみたのが、下の写真右です。
▼ 今回使用したパーツ と JoyStick(ホールセンサー)分解写真
Step 1: JoyStick(ホールセンサー)モジュール作成
基板は自作。抵抗分圧センサータイプと合わせる形で、入出力ピンを配置。
加工作業中に手袋しなかったので、出血、、、
▼ JoyStick(ホールセンサー)モジュール
Step 2: 配線検討
ピン配置が判明したところで、M5StickC Plus2 との結線を検討します。アナログ値(ADC)が読み取れるピンを探し、以下の通りに配線することにしました。
ディスプレイに座標表示させるだけじゃなく、サーボ(FS90)も動かしてみます。
- x軸: G36
- y軸: G32
- push: G33
- servo sig: G0
▼ 配線図(ミニ②)
Step 3: プログラミング(第1段階:素の値を読む)
方針として、いきなり複雑なコードは書きません。まずはノイズ対策などを一切入れず、ジョイスティックから送られてくる「生のデータ」を読み取るシンプルなテストプログラムを作成しました。
💻 テストプログラム(main.cpp)
#include <Arduino.h>
#include <M5Unified.h>
static constexpr int PIN_JOY_X = 36;
static constexpr int PIN_JOY_Y = 32;
static constexpr int PIN_JOY_SW = 33;
void loop() {
const int x = analogRead(PIN_JOY_X);
const int y = analogRead(PIN_JOY_Y);
const int sw = (digitalRead(PIN_JOY_SW) == LOW) ? 1 : 0;
Serial.printf("X:%d, Y:%d, SW:%d\n", x, y, sw);
delay(100);
}
Step 4: 実装テストで Trial & Error
さあ、M5StickC Plus に書き込んでみます!ジョイスティックを倒すと、シリアルモニタの数値はどう変わるでしょうか?
無事に値が取れました!…が、シリアルプロッタで波形を見てみると課題が山積みです。
・触っていないのに値が常にプルプル波打っている(ノイズ)
・中心値が理想の「2048」ではなく、X は 1870 付近、Y は 1950 付近にズレている。
これぞアナログセンサー。そのままサーボに繋いだら、常にサーボがジリジリと震え続けてしまいますね。
▼ シリアルプロッタで見た「素の」アナログ値
Step 5: 改善版プログラム(ノイズ撃退&サーボ連動)
シリアルプロッタで可視化された「中心値のズレ」と「ノイズ」という2つの課題。これらをソフトウェアの力でねじ伏せ、サーボを綺麗に動かす第2段階のプログラムを実装しました!
💻 改善版のキーコード抜粋
// 1. 起動時キャリブレーション(中心値の自動補正)
void calibrateCenter() {
long sumX = 0, sumY = 0;
for (int i = 0; i < CAL_SAMPLES; ++i) {
sumX += analogRead(PIN_JOY_X);
sumY += analogRead(PIN_JOY_Y);
delay(20);
}
centerX = sumX / CAL_SAMPLES;
centerY = sumY / CAL_SAMPLES;
}
// 2. デッドゾーン(遊び)の設定
int applyDeadzone(int raw, int center) {
if (abs(raw - center) <= DEADZONE) { // DEADZONE = 200
return center;
}
return raw;
}
// 3. サーボモーターへの変換(map関数)
int xToServoUs(int correctedX) {
const int maxTravel = min(centerX, 4095 - centerX);
const int delta = constrain(correctedX - centerX, -maxTravel, maxTravel);
return map(delta, -maxTravel, maxTravel, 500, 2500);
}
💡 3つの改善ポイント
- キャリブレーション: 起動時に手を離した状態の値を10回測って平均化し、そこを「真の中心」として記憶させます。これで個体差もクリア!
- デッドゾーン: 中心から「±200」の細かいブレは「動いていない(中心のまま)」とみなすことで、サーボのブルブル(微振動・ハム音)を防ぎます。
- サーボ連動: 補正された綺麗なX軸の値を、サーボのパルス幅(500〜2500μs)に map() 関数で変換。スティックの動きにピタッと追従させます。
すごいよ!波形がとてもスティーディ(一直線)になった!!サーボの微振動もピタッと止まりました。
でも、X と Y のセンターのズレが大きい…(X=1871, Y=1955)。
これは安いアナログパーツによくある個体差です。起動時のキャリブレーションが 「今の中心」を学習するので、ズレていても制御上は問題ありません。ソフトウェアの力、恐るべし…!
▼ デッドゾーンとキャリブレーション適用後の「美しい一直線」
Step 6: 思わぬ罠!サーボが動かない(G0ピンの反抗)
完璧なアナログ値が取れるようになったので、いざサーボモーターを接続して動かそうとしたところ… ピクリとも動かないという事件が発生!
テスターでサーボの信号線(今回は G0 ピンに接続)を測ってみると、PWM(パルス)信号が出ているはずなのに、電圧が 3.3V に固定(HIGHのまま張り付き)になっていました。
プログラムは合っているはずなのに動かない。この「なぜだ!?」という瞬間がたまりません(笑)。
G0(GPIO0) は ESP32 のブートストラップピンです。
ESP32Servo ライブラリでは PWM がうまく出ないことがあります。
解決策は LEDC で 50Hz のパルスを直接生成すること。起動時に gpio_reset_pin も忘れずに。
💻 解決策:LEDCで直接PWMを叩き出す!
ライブラリがダメなら、ESP32 に内蔵されているハードウェアPWM機能「LEDC」を使って、直接 50Hz のパルスを作り出す作戦に変更しました。
// ESP32Servoライブラリを使わず、内蔵のLEDCで直接50HzのPWMを生成する
static constexpr int SERVO_LEDC_CHANNEL = 5;
static constexpr int SERVO_FREQ_HZ = 50;
static constexpr int SERVO_RESOLUTION_BITS = 14;
void setupServoPwm() {
gpio_reset_pin(static_cast<gpio_num_t>(PIN_SERVO)); // ピンを一度リセット!
ledcSetup(SERVO_LEDC_CHANNEL, SERVO_FREQ_HZ, SERVO_RESOLUTION_BITS);
ledcAttachPin(PIN_SERVO, SERVO_LEDC_CHANNEL);
}
コードを書き換えて再書き込み。起動直後に「ウィーン、ウィーン」とスイープテストが動いた瞬間は感動しました!
ジョイスティックを倒すと、サーボが指の動きに吸い付くように滑らかに追従します。アナログ入力と物理モーターが繋がった、最高の瞬間です!
ミニ② HW 完了。Hub 公開・動画掲載 GO。次はミニ③ ESP-NOW — フェーズ1 作戦会議から。
動画
ジョイスティックとサーボの完璧な連動(YouTube)
まとめ
ミニ②では、データシートのないホール効果ジョイスティックを M5StickC Plus に接続し、 X/Y/Push の ADC 読み取りから FS90 サーボ連動まで一気通貫で突破しました。
- 配線 v4(X=G36 / Y=G32 / Push=G33)+ GROVE 端子の活用
- 中央値キャリブレーション + デッドゾーンで入力を安定化
- G0 サーボは LEDC 直接 PWM で制御(ESP32Servo は使わない)
Trial & Error ログ
-
Step 0 — 極性逆接続
ジョイスティックの V/G を逆に接続 → 機能不全。分解して極性を確認(左から G, S, V)。 -
Step 2 — 配線 v4 確定
X=G36(HAT)、Y=G32 / Push=G33(GROVE)。Plus の GROVE 4ピン端子を初めて本格活用。 -
Step 4 — ADC がガタガタ
生analogReadだけでは値が不安定 → 起動時キャリブ + デッドゾーン 200 で改善。 -
Step 6 — G0 サーボが動かない
ESP32Servoでは 3.3V 固定のまま →gpio_reset_pin+ LEDC 50Hz で突破。 -
2026-06-24 — ミニ② HW 完了
Hub 公開・YouTube 掲載 GO。有線コントローラとして完成。
次のステップ
- ミニ③ ESP-NOW 無線コントロールへ — このジョイスティックを TX 側にして、ミニ①の車体を無線化
- Library:ホールセンサーの原理 — ADC と磁気センサーの仕組みを復習
- ミニ① モータ & サーボ駆動 — 無線化の受信側(RX)のベース
前: ミニ① モータ & サーボ | 次: ミニ③ ESP-NOW 無線 | プロジェクト概要
ジョイスティックの裏側を知る
この部品の中で磁石とセンサーがどうやってアナログ値を作っているのか?
ADC(アナログ・デジタル変換)の基礎を図解で解説しています。