2025年10月11日土曜日

ゲームコントローラの設計・製作

  ESP32マイコンのBLEを使い、ゲームコントローラの製作を行いました。


 右図は完成品です。PlayStationの操作ボタンをイメージして、設計しています。

 単3電池用の電池ケースを内蔵しています。


機能と回路設計

 基本はボタン10個です。操作したい本体が、ボタンのオン・オフで動作するので、アナログジョイスティックはありません。
 ボタンの他に、LEDを内蔵しています。電池の電圧を測定し、電池の容量が低下して、電圧が下がると、LEDで表示させる機能です。
 このため、回路はLED(電流制限抵抗を直列接続)とスイッチをESP32マイコンに接続しているだけです。

ケース設計

 コントローラのケースは、Fusion360で設計し、造形しました。基板はタッピングネジM3で止める構造です。






 基板を内蔵して完成となります。















プログラミング

 ゲーム本体とコントローラともESP32マイコンです。ゲーム本体をマスター、コントローラをスレーブとして、BLEのUART通信機能を使い通信を行います。マスター側からスレーブ側に要求があると、スイッチ情報とバッテリ情報を返すプログラムを作成します。

(1)ライブラリ「BluetoothSerial」

 ESP32マイコン同士をBLEの通信「BluetoothSerial」ライブラリをインクルードすると、スケッチ例としてマスタ側「SerialToSerialBTM」、スレーブ側「SerialToSerialBT」が利用できます。

 それぞれのプログラムを2台のESP32マイコンに書き込むと、お互いにペアリングして、通信できるようになります。
 右が通信している様子です。それぞれのシリアルモニタから、メッセージを入力すると、相手のモニタに表示されます。

(2)スイッチ情報をBLE通信で送る

 まずは、コントローラ(スレーブ側)に、スイッチ情報を読み取り、1秒ごとにマスター側に送信するプログラムに改良します。
 プログラムの追加は、ポート設定、ポート読み込み・BLE送信です。スレーブ名も「ESP32-Controller-Slave」に変更しました。
#define LED_R   23
#define LED_Y   17
#define LED_B   16
#define SW_START  33
#define SW_SELECT 32
#define SW_LU 27    // Left side UP Button
#define SW_LD 13    // Left side DOWN Button
#define SW_LR 12    // Left side RIGHT Button
#define SW_LL 14    // Left side LEFT Button
#define SW_RU 22    // Right side UP Button
#define SW_RD 18    // Right side DOWN Button
#define SW_RR 19    // Right side RIGHT Button
#define SW_RL 21    // Right side LEFT Button
#define DC_VOLT 35

#include "BluetoothSerial.h"
BluetoothSerial SerialBT;

String device_name = "ESP32-Controller-Slave";

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name);
  Serial.printf("The device with name \"%s\" is started.\nNow you can pair it with Bluetooth!\n", device_name.c_str());
  pinMode(LED_R,OUTPUT);
  pinMode(LED_Y,OUTPUT);
  pinMode(LED_B,OUTPUT);
  pinMode(SW_START,INPUT_PULLUP);  
  pinMode(SW_SELECT,INPUT_PULLUP);  
  pinMode(SW_LU,INPUT_PULLUP);  
  pinMode(SW_LD,INPUT_PULLUP);  
  pinMode(SW_LR,INPUT_PULLUP);  
  pinMode(SW_LL,INPUT_PULLUP);  
  pinMode(SW_RU,INPUT_PULLUP);  
  pinMode(SW_RD,INPUT_PULLUP);  
  pinMode(SW_RR,INPUT_PULLUP);  
  pinMode(SW_RL,INPUT_PULLUP);  
  pinMode(DC_VOLT,INPUT);  
}

void loop() {
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  int sw_lu=digitalRead(SW_LU);
  int sw_ld=digitalRead(SW_LD);
  int sw_ll=digitalRead(SW_LL);
  int sw_lr=digitalRead(SW_LR);
  int sw_ru=digitalRead(SW_RU);
  int sw_rd=digitalRead(SW_RD);
  int sw_rl=digitalRead(SW_RL);
  int sw_rr=digitalRead(SW_RR);
  int sw_str=digitalRead(SW_START);
  int sw_sel=digitalRead(SW_SELECT);
  int dcv=analogRead(DC_VOLT);
  SerialBT.printf("%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d:%4d\n",
    sw_lu, sw_ld, sw_lr, sw_ll, sw_ru, sw_rd, sw_rl, sw_rr, sw_str, sw_sel, dcv);
  delay(1000);
}
 マスター側は、スレーブ名を変更するだけで受信できます。

 スイッチ情報は、単純に「0」、「1」で送信しています。
 1秒ごとに、スイッチ情報を受信していることが分かります。最後の4桁はバッテリ情報です。

 これをゲーム本体に組み込むことで、コントローラ機能として働きます。



(3)送受信の改良

 これで送受信できるのですが、よくシリアルモニタを見ていると、タイムラグが発生していることが分かります。

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}
 上記の受信部のプログラムを見ると、データがあるか判断(available関数)して、データがあるときには、受信(read関数)し、相手に送信(write関数)する処理をしています。ここでは、1バイトごとしか受信処理ができません。
 このためスイッチ情報を送るプログラムでは、相手にスイッチ情報を15バイト(シリアルモニタの1行あたりの文字数)送信しています。つまり、15文字×20mS=300mS(0.3秒)受信にかかる計算になります。
 ここの部分はプロトコルを設計し、「16進数で送信する」、「改行を入れ、改行まで受信させる」などの改良が必要です。
 改良の例として、サンプル例の「Communication」にある「SerialEvent」のSerialEvent関数は参考になりそうです。

void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}
 このサンプル例では、改行コード「\n」を受信するまで定期的に処理し、改行コードを受信したら、受信フラグ「stringComplete」を「true」にする処理をしています。これで1文字ずつではなく、スイッチ情報ごとの受信ができるようになります。
 これを組み入れたマスター側のプログラム(loop部)が次です。
boolean stringComplete=false;
String inputString;
void loop() {
  serialBTEvent();
  if (stringComplete) {
    Serial.println(inputString);
    inputString = "";
    stringComplete = false;
  }
  delay(20);
}

void serialBTEvent() {
  while (SerialBT.available()) {
    char inChar = (char)SerialBT.read();
    if (inChar == '\n') {
      stringComplete = true;
    }else{
      inputString += inChar;
    }
  }
}
 プログラムを動作させると、受信したときの表示処理が速くなっていることが分かります。


 このプログラムは、まだ読み込むサンプルなので、16進にして送信するなどの処理を行っていませんが、スイッチ情報を計算で16進にして、SerialBT.printf関数のフォーマット指定子でで「%03x」とすると、16進で送信できます。
 右の図では、スイッチ10ビット、バッテリ情報8ビットで処理しています。

0 件のコメント:

コメントを投稿

LEDマトリックス表示装置の設計・製作 9(画像表示)

  LEDマトリックスに画像を表示させるには、いろいろな画像形式を知る必要があります。現在、よく使われるJPG形式やPNG形式は圧縮処理されて保存されています。これに対して、BMP形式やTIFF形式は非圧縮形式で、画像データがそのまま保存されています。このため、ファイルは大きくな...