Arduinoでジョイスティックの情報を取得する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

GroveモジュールのGrove-Thumb JoystickでスティックをX方向とY方向にスティックを回すと位置に応じたアナログ電圧を取得することができます。OLEDに取得したアナログ値を表示して動作確認を行いました。

OLEDは0.95インチ(HiLetgo製)を使用しています。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

以下では、Arduino UNOをArduinoと表記します。

Grove-Thumb Joystickを使用する

Grove-Thumb JoystickはSeeed Studioが製作しているGroveモジュールです。X方向とY方向にスティックを回すと操作位置に応じた電圧を出力します。詳細は下記のリンクを参考にしてください。

Grove – Thumb Joystick | Seeed Studio Wiki

ArduinoのアナログピンにGrove-Thumb Joystickの電圧を入力すると電圧値を取得することができます。本記事ではOLEDに取得したアナログ値とスティックの位置を座標に換算して表示します。

Grove-Thumb Joystickを以下ではジョイスティックとします。

広告

モジュールの特性の確認

モジュールの特性の確認
モジュールの特性の確認

ジョイスティックのWikiページに標準値が記載されていますが、使用するモジュールによって中央値と最大/最小値は若干変化することから使用するモジュールの特性を確認します。

シリアルモニターにジョイスティックを±X方向と±Y方向に倒したときの値を表示して確認します。

ジョイスティックの動作特性の確認
ジョイスティックの動作特性の確認

X軸のシリアルモニターの結果と同様にしてY軸もシリアルモニターで値を確認しました。X軸とY軸の結果をまとめると以下の通りになりました。

最小値(-側)中央値(操作なし)最大値(+側)
X軸242515779
Y軸258513764
モジュールの特性の確認

シリアルモニタでの確認の結果から本記事で使用するジョイスティックの基準値はX軸:515、Y軸:513とします。これらの値は基準値及び最小値/最大値を使ってOLED表示用のジョイスティックの座標に換算に使用します。

X軸方向の基準値に対する最小値と最大値の振れ幅は-側で273+側で264と少しずれていますが平均すると268になるためX軸方向の振れ幅を±268とします。Y軸方向も同様にして最大の振れ幅を±253とします。

広告

アナログ値をフィルタリングする

void TimerCnt(void){
  uint8_t i;
  uint16_t sumx=0;
  uint16_t sumy=0;

  meas.bufx[meas.wp] = analogRead(JOY_AO_X); //X軸の値を取得
  meas.bufy[meas.wp] = analogRead(JOY_AO_Y); //Y軸の値を取得

  if( ++meas.wp >= AIFILT_MAX ){ //保管先を更新
    meas.wp = 0;
  }

  for( i=0; i < AIFILT_MAX; i++ ){ //平均をとるため合計値を算出
    sumx += meas.bufx[i];
    sumy += meas.bufy[i];    
  }

  meas.x = sumx / AIFILT_MAX; //平均値を計算
  meas.y = sumy / AIFILT_MAX; //平均値を計算
}

アナログ値は外来ノイズの影響を受けるため取得したデータを平均化(ローパスフィルタの効果)して使用します。

TimerInt()関数は10ms毎にタイマー割り込みでコールされる自作の関数です。アナログ値のサンプリングタイミングを一定に保つため割り込みでアナログ値の取得と平均値を計算します。

analogRead()関数は引数で指定したピンのアナログ変換値を取得します。X軸、Y軸のアナログ値をそれぞれmeas.bufx[]、meas.bufy[]に格納します。

meas.wpはmeas.bufx[]及びmeas.bufy[]の配列にアナログ値を格納する場所を示したポインタ値です。meas.wpを更新することで配列に順番にアナログ値を格納します。

アナログ値の平均値を計算するため配列の合計値を計算し、配列の数(AIFILT_MAX)で割っています。meas.xとmeas.yに平均値を格納しアナログ値として使用します。

スポンサーリンク

OLEDライブラリを使用する

HiLetgo製の0.95インチ7ピン 65kカラー対応のOLEDディスプレイモジュールを使用しています。通信方式は3線のSPI通信を使用します。下記リンクにモジュールに関する情報が記載されています。

0.95″ Inch 7 Pin Colorful 65K SPI OLED Display Module

OLED用のドライバーICはSSD1331でありArduino環境に対応しているのでライブラリを追加して文字や記号を表示することができます。Arduino IDEでライブラリを追加して使用する方法を説明します。

Adafruit SSD1331 OLED Driverライブラリを追加する

Adafruit SSD1331 OLED Driver Library for Arduinoの追加
Adafruit SSD1331 OLED Driver Library for Arduinoの追加

Arduino IDEのライブラリマネージャの検索欄にssd1331を入力するとライブラリの候補が表示されます。候補の中から「Adafruit SSD1331 OLED Driver Library for Arduino」をインストールします。

インストールする際にライブラリの依存関係をインストールするかを尋ねられることがあります。ライブラリの依存関係が不足しているとうまく動作しないことがあるためすべてをインストールすることをお勧めします。

PR:技術系の通信教育講座ならJTEX

OLEDライブラリの使用例

#include <Adafruit_SSD1331.h>

#define cs   10 //チップセレクト
#define rst  9 //リセット
#define dc   8 //DATA COMMAND
#define	BLACK 0x0000

Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, cs, dc, rst);

void setup() {
  oled.begin();
  oled.fillScreen(BLACK);
}

Adafruit_SSD1331.hをインクルードします。Adafruit_SSD1331クラスの変数としてoled(任意でよい)をインスタンス化します。第1引数にSPIクラスの変数のアドレスを指定します。Arduino環境ではSPIが標準で定義されているのでSPIを指定します。

第2引数にチップセレクトで使用するピン番号を指定します。

第3引数にData/Commandピン番号を指定します。SPI通信を3線で行うためData/Commandピンでデータの方向を選択して送受信します。

第4引数にRESピン番号を指定します。RESピンはHIGHにするとモジュールのリセットを解除します。LOWにするとリセット状態になります。

setup()内でAdafruit_SSD1331クラスのメンバー関数であるbegin()関数でOLEDの初期化を行います。fillScreen()関数は画面全体のカラーを指定した色で塗りつぶします。例ではBLACK(0x0000)を指定しています。色の指定は16bitデータ(0~65535の範囲)で行います。

oled.setTextSize(1); //5×7フォント
oled.setCursor(5, 10);
oled.setTextColor(RED); //テキストの色を指定
oled.print("X:");
oled.setCursor(10, 20);
oled.print(meas.x);

setTextSize()関数でフォントの大きさを指定します。1を指定すると5×7のフォントになります。2以上を指定すると1のフォント基準に倍数したフォントになります。0を指定すると1と同じフォントになります。

setCursor()関数で文字表示する座標を指定します。第1引数にX座標、第2引数にY座標を指定します。座標はピン側を上に見た時OLEDの左上の座標が(X,Y)=(0,0)になります。

setTextColor()関数はテキストの色を指定します。16ビットの値で色を指定します。

print()関数で文字を書き込んで表示します。例ではX:を表示後カーソルを移動してアナログ値を文字列で出力しています。次にジョイスティックの位置を表示する図形の表示方法を説明します。

oled.drawRect(40, 5, 50, 50, WHITE );
oled.drawCircle(65, 30, 24, YELLOW);
oled.drawLine(35, 30, 95, 30, WHITE);
oled.drawLine(65, 0, 65, 60, WHITE);
x = (meas.x - BASE_VALUE_X);
y = (meas.y - BASE_VALUE_Y);
x /= 11;
y /= 10;
oled.fillCircle(65+x, 30-y, 3, MAGENTA);

drawRect()関数は四角形を描きます。第1引数にX座標、第2引数にY座標、第3引数に幅、第4引数に高さ、第5引数に色を指定します。

drawCircle()関数は円を描きます。第1引数に円の中心のX座標、第2引数に円の中心のY座標、第3引数に円の半径、第4引数に円の色を指定します。

drawLine()関数は基準のポイントから次点のポイントまで線を描きます。第1引数に基準のX座標、第2引数に基準のY座標、第3引数に次点のX座標、第4引数に次点の座標、第5引数に色を指定します。

ジョイスティックの操作位置を表示するため図形の大きさに合わせてアナログ値の換算を行います。モジュールの特性の確認の結果からX軸方向の最大振れ幅は±268程度となります。図形中の半径を25としているので、この範囲に収めるため11で割った商を図形中での座標の指定に使用します。

同様にY軸方向の基準からのアナログ値の最大振れ幅は±253程度であり半径の25の範囲に収めるため10で割った商を図形中での座標の指定に使用します。

fillCircle()関数は円の中心の座標から指定する半径の塗りつぶしの円を描きます。第1引数に円の中心のX座標、第2引数に円の中心のY座標、第3引数に円の半径、第4引数に色を指定します。例では図形の中心座標に換算したアナログ値を加えて円の中心座標を表示しています。

ジョイスティックを操作するとアナログ値が更新されるため図形中を円が移動しているように表示できます。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

OLEDはSPI通信を3線式で行うためArduinoのMISO(12ピン)は使用しません。MOSI(11ピン)で送信と受信をDCピンでデータとコマンドを切り替えながら通信を行います。RESピンはリセットを使用しない場合はHIGHに固定しても問題ありません。CSピンはLアクティブとなるのでモジュールを常に使用する場合はLOWに固定しても問題ありません。

ArduinoのボードにGrove端子がないためワイヤーでGrove端子とArduinoを接続しました。

電源を入れるとOLEDにジョイスティックから取得したX軸及びY軸のアナログ値を表示します。ジョイスティックを操作すると図形上の円が移動します。

OLEDの表示(ジョイスティック操作)
OLEDの表示(ジョイスティック操作)

ジョイスティックを+X,+Y方向に最大になるように操作すると図形の右上に円が表示されます。また-X,-Y方向に最小になるように操作すると図形の左下に円が表示されます。

私が使用しているOLEDモジュールは初期不良なのか分かりませんが、モニターの中央部の液晶が変色しており、変色している部分の色が表現できなかったのが残念でした。OELDの表示の中央部付近の白色の線が青味が強く出ています。

広告

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <Adafruit_SSD1331.h>
#include <MsTimer2.h>

#define JOY_AO_X A0
#define JOY_AO_Y A1
#define cs   10
#define rst  9
#define dc   8

#define BASE_VALUE_X 515
#define BASE_VALUE_Y 513
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 1 //ベースタイマカウント値
#define TIM_MEAS 20
#define AIFILT_MAX 8

// Color definitions
#define	BLACK           0x0000
#define	BLUE            0x001F
#define	RED             0xF800
#define	GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0
#define WHITE           0xFFFF

typedef struct AIFILT{
  uint8_t wp;
  uint16_t bufx[AIFILT_MAX];
  uint16_t bufy[AIFILT_MAX];
  uint16_t x;
  uint16_t y;
};

Adafruit_SSD1331 oled = Adafruit_SSD1331(&SPI, cs, dc, rst);
int8_t cnt10ms;
int16_t timMeas;
AIFILT meas;

void TimerInt(void);
void mainTimer(void);
void mainApp(void);
void OledShow(void);

void setup() {
  uint8_t i;

  Serial.begin(115200);

  for(i=0; i < AIFILT_MAX; i++){
    meas.bufx[i] = analogRead(JOY_AO_X);
    meas.bufy[i] = analogRead(JOY_AO_Y);
    delay(10);
  }

  oled.begin();
  oled.fillScreen(BLACK);
  MsTimer2::set(10, TimerInt); // 10ms period
  MsTimer2::start();   
}

void loop() {

  mainTimer();
  mainApp();

}

/* メイン処理 */
void mainApp(void){

  if( timMeas == TIME_UP ){
    timMeas = TIM_MEAS;
    Serial.print("The X and Y coordinate is:");
    Serial.print(meas.x, DEC);
    Serial.print(",");
    Serial.println(meas.y, DEC);
    OledShow();
  }
}
/* callback function add */
void TimerInt(void){
  uint8_t i;
  uint16_t sumx=0;
  uint16_t sumy=0;

  ++cnt10ms;

  meas.bufx[meas.wp] = analogRead(JOY_AO_X);//X軸の値を取得
  meas.bufy[meas.wp] = analogRead(JOY_AO_Y);//Y軸の値を取得

  if( ++meas.wp >= AIFILT_MAX ){ //保管先を更新
    meas.wp = 0;
  }

  for( i=0; i < AIFILT_MAX; i++ ){ //平均をとるため合計値を算出
    sumx += meas.bufx[i];
    sumy += meas.bufy[i];    
  }

  meas.x = sumx / AIFILT_MAX; //平均値を計算
  meas.y = sumy / AIFILT_MAX; //平均値を計算
}
/* タイマ管理 */
void mainTimer(void){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT;

    if( timMeas > TIME_UP ){
      --timMeas;
    }

  }
}

/* OLED表示*/
void OledShow(void){
  int16_t x,y;

  oled.fillScreen(BLACK);
  oled.setTextSize(1);
  oled.setCursor(5, 10);
  oled.setTextColor(RED);
  oled.print("X:");
  oled.setCursor(10, 20);
  oled.print(meas.x); 
  oled.setCursor(5, 35);
  oled.setTextColor(GREEN); 
  oled.print("Y:");
  oled.setCursor(10, 45);
  oled.print(meas.y);
  oled.drawRect(40, 5, 50, 50, WHITE );
  oled.drawCircle(65, 30, 24, YELLOW);
  oled.drawLine(35, 30, 95, 30, WHITE);
  oled.drawLine(65, 0, 65, 60, WHITE);
  x = (meas.x - BASE_VALUE_X);
  y = (meas.y - BASE_VALUE_Y);
  x /= 11;
  y /= 10;
  oled.fillCircle(65+x, 30-y, 3, MAGENTA);
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR:技術系の通信教育講座ならJTEX

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました