こんにちは、タクト工房へようこそ!今回は前回製作した二輪車両ロボットをPS5コントローラで操縦できるようにしたので、その内容を紹介します。
↓前回の記事
今回使用するもの
- 2輪車両ロボット(前回製作したもの)
- PS5コントローラ(DualSense)
今回の方法は、PS5コントローラ(DualSense)を例に解説しますが、他のBluetooth対応ゲームコントローラでも動かすことはできます。
SDL2のインストール
SDL2(Simple DirectMedia Layer 2)は、ゲーム開発やマルチメディアアプリケーション向けのクロスプラットフォームライブラリです。ジョイスティックやゲームパッドの入力処理、グラフィックス、サウンドなどの機能を提供します。今回、PS5コントローラの入力を処理するためにSDL2のジョイスティックAPIを使用します。
ターミナルを開いて以下のコマンドを1行ずつ実行します
sudo apt-get update
apt-get install libsdl2-dev
このライブラリをインストールすることで、ジョイスティックのボタン入力やスティックの動きをプログラムで取得できるようになります。
PS5コントローラの接続とボタンマッピングの確認
1.Bluetooth接続
まずPS5コントローラをraspberrypiとBluetoothで接続させます。接続の仕方は、普通のパソコンと同様にraspberrypiの右上の画面にBluetoothのボタンがあるため、そちらをクリックし、接続させてください。もし、Bluetoothの一覧にPS5コントローラがなく、接続の仕方が分からない方は以下の記事を参考にしてください。
または、ターミナルで以下のコマンドを実行し、BluetoothデバイスをスキャンしてPS5コントローラを接続します。
bluetoothctl
scan on # デバイスを検索
pair <コントローラのMACアドレス>
connect <コントローラのMACアドレス>
trust <コントローラのMACアドレス>
2. ボタンマッピングを確認するコードの作成
以下のプログラム(sdl_joystick_test.c
)を作成し、ボタンやスティックのIDを確認します。
#include <stdio.h>
#include <stdlib.h>
#include <SDL2/SDL.h>
int main(int argc, char* argv[]) {
// 1. SDLを初期化します
if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError());
return 1;
}
// 2. ジョイスティックがいくつあるか調べます
int numJoysticks = SDL_NumJoysticks();
if (numJoysticks < 1) {
printf("ジョイスティックが見つかりませんでした\n");
SDL_Quit();
return 0;
}
else {
printf("ジョイスティックが %d 個 見つかりました\n", numJoysticks);
}
// 3. 0番目のジョイスティックを開いてみます
SDL_Joystick *joystick = SDL_JoystickOpen(0);
if (!joystick) {
fprintf(stderr, "ジョイスティックを開けませんでした: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
// ジョイスティックの名前などを表示
printf("使っているジョイスティック: %s\n", SDL_JoystickName(joystick));
// 軸(スティック)の数、ボタンの数を取得
int axes = SDL_JoystickNumAxes(joystick);
int buttons = SDL_JoystickNumButtons(joystick);
printf("軸(スティック)の数: %d, ボタンの数: %d\n", axes, buttons);
// 4. スティックやボタンの値を読み取るループ(Ctrl+Cで終了)
while (1) {
// SDLのイベントを更新する(この呼び出しで内部情報をリフレッシュ)
SDL_JoystickUpdate();
// 軸(スティック)の値を順番に取得して表示
// 例: 0番が左スティックのX軸、1番が左スティックのY軸 など
for (int i = 0; i < axes; i++) {
Sint16 axisVal = SDL_JoystickGetAxis(joystick, i);
// スティックの範囲は -32768 ~ 32767
printf("Axis %d: %d | ", i, axisVal);
}
// ボタンの押し込み状態を確認
for (int i = 0; i < buttons; i++) {
Uint8 buttonVal = SDL_JoystickGetButton(joystick, i);
// 押していれば1、押してなければ0
printf("Btn %d: %d | ", i, buttonVal);
}
printf("\n");
// ちょっと待つ (1/60秒くらい)
SDL_Delay(16);
}
// ここに来ることはないですが、一応書いておきます
SDL_JoystickClose(joystick);
SDL_Quit();
return 0;
}
3.実行
コードを書いたら、次にターミナルを開きます。ターミナルを開いたら以下のコマンドを書き、先ほどのコードを実行させます。
ディレクトリに移動
先ほど書いたコードを保存したディレクトリに移動します。今回はMycodeというディレクトリ(フォルダ)に保存したのでそこに移動します。
cd Mycode
コンパイル
以下のコマンドでコンパイルさせます。今回は、sdl_joystick_test.cという名前で作成しましたが、もし、他の名前で保存した方は、sdl_joystick_testのところを保存した名前に書き換えてください。
gcc sdl_joystick_test.c -o sdl_joystick_test -lSDL2
実行
以下のコマンドで実行させます。
./sdl_joystick_test
実行すると以下の写真のように表示されます。

実行した状態でコントローラのボタンを押すと値が変化することが確認できます。試しに、PS5の〇ボタンを押すとBtn 1の値が0から1に変わりました。コントローラのボタンを押すと、どのボタンがどのIDに対応しているかが分かります。

プログラム作成
PS5コントローラを使って車両ロボットを動かすプログラムを書いていきます。プログラムは前回の記事で行ったようにファイル分割して実行させます。もしやり方が分からない方は以下の記事を参考にしてください
ファイル構成
ファイル構成は以下のようになっています。前回のrobotのファイルにjoy.cといったメインプログラムを追加しました。今回エンコーダと超音波センサは使わないのでなくても構いません。また、前回のメインプログラムであるmain.cも使用しません。
robot/
│
├── main.c // メインプログラム
├── joy.c // メインプログラム
├── motor_control.c // モータ制御関連の実装
├── motor_control.h // モータ制御関連のヘッダファイル
├── encoder.c // エンコーダ関連の実装
├── encoder.h // エンコーダ関連のヘッダファイル
├── ultrasonic.c // 超音波センサ関連の実装
├── ultrasonic.h // 超音波センサ関連のヘッダファイル
├── config.h //ピン番号やハードウェア設定をまとめた定義ファイル
└── Makefile // コンパイル用のMakefile
Makefile
main関数が複数あるとエラーが起きるため、前回のメインプログラムをコメントアウト(6,14,15行目)し、joy.cといったメインプログラムを追加します。
CC = gcc
CFLAGS = -Wall -Wextra -O2
LIBS = -lSDL2 -lwiringPi
TARGET = joy
#OBJS = main.o motor_control.o encoder.o ultrasonic.o
OBJS = joy.o motor_control.o
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
#main.o: main.c config.h motor_control.h encoder.h ultrasonic.h
# $(CC) -c main.c $(CFLAGS)
joy.o: joy.c config.h motor_control.h
$(CC) -c joy.c $(CFLAGS)
motor_control.o: motor_control.c motor_control.h config.h
$(CC) -c motor_control.c $(CFLAGS)
encoder.o: encoder.c encoder.h config.h
$(CC) -c encoder.c $(CFLAGS)
ultrasonic.o: ultrasonic.c ultrasonic.h config.h
$(CC) -c ultrasonic.c $(CFLAGS)
clean:
rm -f *.o $(TARGET)
joy.c
以下がメインプログラムです。ボタンを押すと車両ロボットが前進、後進、右左旋回するプログラムを作成しました。
#include <stdlib.h>
#include <SDL2/SDL.h>
#include <stdio.h>
#include <wiringPi.h>
#include "motor_control.h"
int main(void) {
// 1. WiringPi初期化
if (wiringPiSetupGpio() == -1) {
printf("GPIOの初期化に失敗しました\n");
return 1;
}
// 2. SDLジョイスティック初期化
if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError());
return 1;
}
// 3. ジョイスティックを開く (0番目を使う)
SDL_Joystick* joystick = SDL_JoystickOpen(0);
if (!joystick) {
fprintf(stderr, "ジョイスティックを開けませんでした: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
// モータドライバ等の初期化
setupMotors();
while (1) {
// ジョイスティックの状態を更新
SDL_JoystickUpdate();
// ボタンの押し込み状態を確認 (PS5コントローラで対応するボタン番号は要確認)
Uint8 button_0 = SDL_JoystickGetButton(joystick, 0);
Uint8 button_1 = SDL_JoystickGetButton(joystick, 1);
Uint8 button_2 = SDL_JoystickGetButton(joystick, 2);
Uint8 button_3 = SDL_JoystickGetButton(joystick, 3);
// 動作例: ボタンごとにモータ動作
if (button_0 == 1) {
motor_R(-900); // 後進
motor_L(-900);
}
else if (button_1 == 1) {
motor_R(-900); // 右旋回(右後進+左前進)
motor_L(900);
}
else if (button_2 == 1) {
motor_R(900); // 前進
motor_L(-900);
}
else if (button_3 == 1) {
motor_R(900); // 左旋回(右前進+左後進)
motor_L(900);
}
else {
// 何も押してないときは停止
motor_R(0);
motor_L(0);
}
// 適度にCPU負荷を下げるため、少し待つ(10msとか)
delay(10);
}
// ここはループを抜けないと到達しないが、抜ける処理を追加するなら:
SDL_JoystickClose(joystick);
SDL_Quit();
return 0;
}
その他のプログラムは前回と同様なので前回の記事を参考にしてください。
PS5コントローラを使って車両ロボットを動かす
実行すると以下の動画のようにPS5コントローラで車両ロボットを制御することができました。
PS5コントローラのボタンとロボットの動作対応表
PS5コントローラのボタンを押したときに、ロボットがどのように動くかを以下の表にまとめました。各ボタンが対応する動作を確認しながら操作してみてください。
ボタン | 動作 |
---|---|
〇 | 後進 |
× | 右旋回 |
□ | 左旋回 |
△ | 前進 |
まとめ
今回は、PS5コントローラを使ってRaspberry Pi上で車両ロボットを制御する方法を紹介しました。他のコントローラでも応用できるため、ボタンの割り当てを確認しながら試してみてください!
コメント