ESP-NOWを使ったモーターカーの無線操作に、今度はジョイスティックを使ってみましょう。
これまでの記事では、KUMITATE-C3に搭載された4つのボタンを使って、
- 前進
- 後退
- 左旋回
- 右旋回
を操作しました。
今回はボタンの代わりにジョイスティックを接続します。
ジョイスティックのX軸・Y軸をADCで読み取り、その情報をESP-NOWでモーターカー側へ送信します。
さらに、ジョイスティックを倒した方向だけでなく、倒した量も利用してモーターの速度を変えてみましょう。
今回作るもの
今回もKUMITATE-C3を2台使用します。
【コントローラー側】
ジョイスティック
│
├─ X軸
└─ Y軸
│
ADC
↓
KUMITATE-C3
│
│ ESP-NOW
▼
【モーターカー側】
KUMITATE-C3
│
KUMITATE-MOTOR
│
├─ 左DCモーター
└─ 右DCモーター
コントローラー側では、ジョイスティックをGPIO拡張コネクターへ接続します。
X軸とY軸のアナログ電圧をKUMITATE-C3のADCで読み取り、その情報をESP-NOWで送信します。
モーターカー側では受信した情報から左右のDCモーターを制御します。
ジョイスティックの基本はこちら
ジョイスティックの内部にはX軸・Y軸それぞれの可変抵抗があり、レバーの位置によって出力電圧が変化します。
その電圧をADCで読み取ることで、レバーの方向や倒した量を取得できます。
ジョイスティックの仕組みやADCによるX・Y値の読み取りについては、こちらの記事で詳しく解説しています。
ジョイスティックとは?マイコンでX・Y方向を読み取る仕組みと使い方

この記事ではジョイスティックからX・Y値を取得できることを前提として、ESP-NOWとモーター制御を中心に進めます。
ESP-NOWの基本はこちら
ESP-NOWそのものの仕組みや、2台のESP32でデータを送受信する方法についてはこちらの記事で解説しています。
ESP-NOWとは?ESP32同士で無線通信する方法を初心者向けに解説

ESP-NOWを初めて使用する場合は、先に基本的な送受信を確認しておくと分かりやすくなります。
用意するもの
今回は主に次のものを使用します。
コントローラー側
- KUMITATE-C3 × 1台
- 2軸ジョイスティック × 1個
モーターカー側
- KUMITATE-C3 × 1台
- KUMITATE-MOTOR × 1台
- DCモーター × 2個
- モーターカー用の車体・タイヤ
- モーター用電源
KUMITATE-C3は合計2台使用します。
まずは前後左右を決める
ジョイスティックからはX軸とY軸の2つの値を取得できます。
今回は、
前進
↑
│
│
左旋回 ← ● → 右旋回
│
│
↓
後退
という操作にします。
ジョイスティックが中央付近にある場合は停止です。
つまり、
Yが大きい → 前進
Yが小さい → 後退
Xが小さい → 左旋回
Xが大きい → 右旋回
中央付近 → 停止
という考え方です。
実際のX・Y軸の方向や値の増減は、使用するジョイスティックの取り付け方向によって変わります。
シリアルモニターで値を確認しながら調整してください。
中央にはデッドゾーンを作る
ジョイスティックは手を離しても完全に同じ中央値へ戻るとは限りません。
そこで中央付近にデッドゾーンを作ります。
12bit ADCで0~4095の値を取得する場合を例に、
const int CENTER = 2048;
const int DEAD_ZONE = 400;
としてみます。
すると、
1648 ~ 2448
程度を中央と判断できます。
ジョイスティックがこの範囲にある場合はモーターを停止します。
実際には使用するジョイスティックの中央値を測定し、それに合わせて調整してください。
今回はX・Yの値をESP-NOWで送る
ボタン操作版では、
FORWARD
BACKWARD
LEFT
RIGHT
STOP
という操作命令をESP-NOWで送信しました。
ジョイスティック版では少し方法を変えます。
今回は、X軸とY軸の値そのものを送信してみましょう。
そのため、送信用の構造体を作ります。
typedef struct {
int x;
int y;
} JoystickData;
これなら、
X = 2048
Y = 3500
といったジョイスティックの状態をそのままモーターカー側へ送信できます。
なぜ今回はX・Y値をそのまま送るの?
前後左右だけを操作するのであれば、コントローラー側で方向を判定してFORWARDなどを送っても構いません。
しかしジョイスティックには、倒した量を取得できるという特徴があります。
たとえば、
少し前へ倒す
↓
Y = 2600
大きく前へ倒す
↓
Y = 3900
のような違いがあります。
X・Y値そのものを送っておけば、モーターカー側でこの違いを使ってモーター速度を変えられます。
そこで今回は、ジョイスティックのアナログ入力らしさを生かすためにX・Y値を送信します。
コントローラー側の処理
コントローラー側の処理はシンプルです。
ジョイスティック
↓
X・YをADCで取得
↓
JoystickDataへ格納
↓
ESP-NOWで送信
これを一定間隔で繰り返します。
コントローラー側のプログラム
ジョイスティックを接続したADC対応GPIOを指定します。
以下のGPIO番号は例なので、実際にKUMITATE-C3で使用するADC対応GPIOへ変更してください。
#include <WiFi.h>
#include <esp_now.h>
const int JOYSTICK_X = 0;
const int JOYSTICK_Y = 1;
typedef struct {
int x;
int y;
} JoystickData;
JoystickData joystick;
// モーターカー側KUMITATE-C3の
// MACアドレスへ変更してください
uint8_t receiverAddress[] = {
0x24, 0x6F, 0x28, 0xAA, 0xBB, 0xCC
};
esp_now_peer_info_t peerInfo = {};
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW初期化失敗");
return;
}
memcpy(peerInfo.peer_addr, receiverAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("送信先登録失敗");
return;
}
}
void loop() {
joystick.x = analogRead(JOYSTICK_X);
joystick.y = analogRead(JOYSTICK_Y);
esp_now_send(
receiverAddress,
(uint8_t *)&joystick,
sizeof(joystick)
);
Serial.print("X: ");
Serial.print(joystick.x);
Serial.print(" Y: ");
Serial.println(joystick.y);
delay(50);
}
50msごとにジョイスティックのX・Y値を取得して、モーターカー側へ送信しています。
まずは前後左右だけ動かしてみる
最初から速度制御まで入れると分かりにくくなるので、まずはX・Y値から前後左右を判断してみましょう。
たとえば、
const int CENTER = 2048;
const int DEAD_ZONE = 400;
const int LOW_THRESHOLD = CENTER - DEAD_ZONE;
const int HIGH_THRESHOLD = CENTER + DEAD_ZONE;
とします。
そして受信した値を、
if (y > HIGH_THRESHOLD) {
forward();
}
else if (y < LOW_THRESHOLD) {
backward();
}
else if (x < LOW_THRESHOLD) {
turnLeft();
}
else if (x > HIGH_THRESHOLD) {
turnRight();
}
else {
stopAll();
}
と判定します。
これだけでもジョイスティックによる無線操作ができます。
モーターカー側のGPIO
KUMITATE-MOTORでは左右のDCモーターを制御します。
今回も、
const int B_1A = 7;
const int B_1B = 6;
const int A_1A = 5;
const int A_1B = 4;
を使用します。
モーターカー側のプログラム
受信したX・Y値によって前後左右を切り替えます。
#include <WiFi.h>
#include <esp_now.h>
const int B_1A = 7;
const int B_1B = 6;
const int A_1A = 5;
const int A_1B = 4;
const int CENTER = 2048;
const int DEAD_ZONE = 400;
const int LOW_THRESHOLD = CENTER - DEAD_ZONE;
const int HIGH_THRESHOLD = CENTER + DEAD_ZONE;
typedef struct {
int x;
int y;
} JoystickData;
JoystickData joystick;
unsigned long lastReceiveTime = 0;
void stopAll() {
digitalWrite(B_1A, LOW);
digitalWrite(B_1B, LOW);
digitalWrite(A_1A, LOW);
digitalWrite(A_1B, LOW);
}
void forward() {
digitalWrite(B_1A, HIGH);
digitalWrite(B_1B, LOW);
digitalWrite(A_1A, HIGH);
digitalWrite(A_1B, LOW);
}
void backward() {
digitalWrite(B_1A, LOW);
digitalWrite(B_1B, HIGH);
digitalWrite(A_1A, LOW);
digitalWrite(A_1B, HIGH);
}
void turnLeft() {
digitalWrite(B_1A, HIGH);
digitalWrite(B_1B, LOW);
digitalWrite(A_1A, LOW);
digitalWrite(A_1B, LOW);
}
void turnRight() {
digitalWrite(B_1A, LOW);
digitalWrite(B_1B, LOW);
digitalWrite(A_1A, HIGH);
digitalWrite(A_1B, LOW);
}
void onDataRecv(
const esp_now_recv_info_t *info,
const uint8_t *incomingData,
int len
) {
if (len != sizeof(joystick)) {
return;
}
memcpy(
&joystick,
incomingData,
sizeof(joystick)
);
lastReceiveTime = millis();
int x = joystick.x;
int y = joystick.y;
if (y > HIGH_THRESHOLD) {
forward();
}
else if (y < LOW_THRESHOLD) {
backward();
}
else if (x < LOW_THRESHOLD) {
turnLeft();
}
else if (x > HIGH_THRESHOLD) {
turnRight();
}
else {
stopAll();
}
}
void setup() {
Serial.begin(115200);
pinMode(B_1A, OUTPUT);
pinMode(B_1B, OUTPUT);
pinMode(A_1A, OUTPUT);
pinMode(A_1B, OUTPUT);
stopAll();
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW初期化失敗");
return;
}
esp_now_register_recv_cb(onDataRecv);
Serial.println("ジョイスティック操作待機中");
}
void loop() {
// 500ms以上受信できなければ停止
if (millis() - lastReceiveTime > 500) {
stopAll();
}
}
これで基本的なジョイスティック操作ができます。
実際に動かしてみよう
2台のKUMITATE-C3へそれぞれプログラムを書き込みます。
ジョイスティックを中央にして電源を入れます。
中央にある間は、
ジョイスティック中央
↓
STOP
↓
モーター停止
となります。
前へ倒すと、
ジョイスティック
↑
│
前
↓
Y値がしきい値を超える
↓
ESP-NOW
↓
左右モーター正転
↓
モーターカー前進
となります。
同じように後ろ・左・右へ倒して動作を確認しましょう。
ボタン操作との違い
ボタン操作の場合は、
押していない
押している
という2つの状態です。
一方、ジョイスティックでは、
中央
少し倒す
半分倒す
大きく倒す
最大まで倒す
という連続した操作量を取得できます。
せっかくADCで値を取得しているので、次はこの特徴を利用してみましょう。
ジョイスティックを倒した量で速度を変える
ここからがジョイスティック操作の面白いところです。
たとえば前方向へ少し倒した場合、
Y = 2600
だったとします。
さらに大きく倒すと、
Y = 3800
になったとします。
この値をPWMへ変換すれば、
少し倒す
↓
PWM小
↓
ゆっくり前進
大きく倒す
↓
PWM大
↓
速く前進
という操作ができます。
ADC値をPWM値へ変換する
たとえばArduinoのmap()を利用して、
int speed = map(
y,
HIGH_THRESHOLD,
4095,
0,
255
);
とすれば、ジョイスティックの値を0~255程度のPWM値へ変換できます。
範囲外にならないように、
speed = constrain(speed, 0, 255);
としておくと扱いやすくなります。
これによって、ジョイスティックを倒すほどモーター速度を上げられます。
前進しながら曲がることもできる
さらにX軸とY軸を同時に利用すると、より自然な操作ができます。
たとえばジョイスティックを、
前+右
へ倒したとします。
単純な4方向制御では、
前進
または、
右旋回
のどちらかになります。
しかし左右のモーター速度を別々に制御すれば、
左モーター → 速い
右モーター → 遅い
として、前進しながら右へ曲がることができます。
反対に、
左モーター → 遅い
右モーター → 速い
とすれば、前進しながら左へ曲がれます。
ジョイスティックを左右モーターへ変換する
考え方としては、
Y軸=前後方向の基本速度
X軸=左右の曲がり量
とすると分かりやすくなります。
たとえば、
Yだけ前
↓
左右とも同じ速度
↓
直進
です。
そこへX軸の値を加えることで、
前+右
↓
左モーターを速く
右モーターを遅く
↓
右へ曲がる
という制御ができます。
このように、ジョイスティックの2軸入力を左右2個のモーター出力へ変換することで、4方向だけではない滑らかな操作へ発展できます。
通信が途切れたら停止する
無線でモーターカーを操作する場合は、通信が途切れたときの処理も重要です。
今回のプログラムでは、
if (millis() - lastReceiveTime > 500) {
stopAll();
}
として、500ms以上ESP-NOWのデータを受信できなければ停止するようにしています。
これによって、
- コントローラーの電源が切れた
- 通信範囲外になった
- ESP-NOWの通信が途切れた
といった場合にモーターが動き続けることを防ぎます。
3種類の操作方法を比べてみよう
これまでKUMITATE-C3を使って、3種類の方法でモーターカーを無線操作してきました。
ボタン操作
デジタル入力
↓
ESP-NOW
↓
モーター
加速度センサー操作
I²C
↓
加速度センサー
↓
ESP-NOW
↓
モーター
ジョイスティック操作
ADC
↓
アナログ入力
↓
ESP-NOW
↓
モーター
最終的には同じモーターカーを動かしていますが、入力方法はまったく異なります。
これはマイコン学習でも重要なポイントです。
KUMITATE-C3のGPIO拡張コネクターを使う
今回のコントローラー側では、KUMITATE-C3のGPIO拡張コネクターへジョイスティックを接続します。
ADCに対応したGPIOを使うことで、X軸とY軸のアナログ電圧を読み取れます。
KUMITATE-C3本体のボタンだけでなく、外部デバイスを接続することで、さまざまな入力方法へ拡張できます。
今回は、
ジョイスティック → ADC
という組み合わせを使用しました。
モーターカー側にはKUMITATE-MOTOR
モーターカー側ではKUMITATE-MOTORを使用します。
KUMITATE-C3と組み合わせて左右2個のDCモーターを制御できるため、
- 前進
- 後退
- 左旋回
- 右旋回
- PWM速度制御
- 左右モーターの速度差による旋回
といった制御へ発展できます。
▶ KUMITATE-MOTORの詳細はこちら
https://kumicla.tatepro.com/asp-products/kumitate-motor/
まとめ
今回はジョイスティックとESP-NOWを使って、モーターカーを無線操作しました。
全体の流れは、
ジョイスティックを操作
↓
ADCでX・Y値を取得
↓
ESP-NOWで送信
↓
別のKUMITATE-C3で受信
↓
X・Y値をモーター制御へ変換
↓
KUMITATE-MOTORで左右のDCモーターを制御
となります。
今回のポイントは、
- ジョイスティックのX・Y値をADCで取得する
- 中央付近にデッドゾーンを設ける
- X・Y値をESP-NOWで送信する
- 受信した値から前後左右を判定する
- ジョイスティックを倒した量をPWM速度へ変換できる
- X・Yを同時に利用すれば滑らかな旋回にも発展できる
- 通信が途切れた場合は自動停止させる
というところです。
ボタンでは「押す・離す」というデジタル的な操作でしたが、ジョイスティックではADCを使うことで方向と操作量を同時に扱えるようになりました。
同じモーターカーでも、入力デバイスを変えるだけで操作方法やプログラムの考え方は大きく変わります。
ボタン、加速度センサー、ジョイスティックをそれぞれ試して、デジタル入力・I²C・ADCの違いも比較してみてください。

