Show page source of ギタコン試作4 #112582

== ギタコンの自作 試作4

[[PageOutline(start=3)]]

=== 試作4の内容
ごちゃごちゃしてきた配線をすっきりさせます。
また、ジャイロセンサーを追加して、wailing操作に対応します。
ただし、ジャイロセンサーをいくつか当たってみたのですが、はんだ付け無しで利用できるものを見つけられませんでした。
従い、ジャイロセンサーでのWailing対応は、はんだ付け作業が必須となります。ごめんなさい。

もしも、はんだ付け不要のジャイロセンサー(加速度センサーではない)をご存知の方がいらっしゃいましたら、教えてください。
(例えば、秋月電子さんの[http://akizukidenshi.com/catalog/g/gK-13010/ AE-BMX055]がはんだ付け不要で使えそうに見えますが、実は電源設定をジャンパーのはんだ付けで行う必要があるため、はんだ付け不要とはなりませんでした。そこで、どうせはんだ付けしなければならないのなら・・・ということで、ここでは超安いジャイロセンサーを使っています)

=== 試作4で使うもの一覧

試作1,3で使ったものに加えて、以下を使います。

追加費用は、合計で数百円+発送費用くらい。(別の方法で配線を整理するのであれば、線材の追加は不要)

||名称||値段||通販コード||
||ジャイロセンサーモジュール (MPU-6050) x1 [[Thumb(IMG_3750_.jpg, size=128x85)]] ||1個数百円程度|| Amazon: (中国からの発送品になります) [https://www.amazon.co.jp/gp/product/B008BOPN40/ref=od_aui_detailpages00?ie=UTF8&psc=1 MPU-6050 使用 3軸ジャイロスコープ・3軸加速度センサー モジュール] (2018年9月4日時点で1個229円)||
||各種長さが揃ったジャンパワイヤー [[Thumb(IMG_3643.JPG, size=256x93)]] || 400円程度 || 秋月電子: [http://akizukidenshi.com/catalog/g/gP-05160/ P-05160] ||


=== 配線をすっきりさせる
新しく入手した「短いジャンパーワイヤー」を使って、GNDや5Vの配線を置き換えました。
ジョイスティックのところへの信号線は適当な長さのケーブルが無かったので、やむなく反対側(手前側)のL/R端子に接続を迂回しました。
これで、ケーブルを手前側でまとめれば、そこそこすっきりするかなと思います。

[[Thumb(gtcon-05.jpg, size=512x384, caption=すっきりさせる前。これで実際にギターパートをプレイしろというのは無謀ですね。)]]

[[Thumb(IMG_3644_.jpg, size=512x238, caption=短いジャンパーワイヤーを使って、配線をすっきりさせた後。そこそこすっきりした。画面奥側(実際に指が出てくるところ)を邪魔するケーブルはなくなりました。))]]


=== wailingに対応させる
まずは、ジャイロのモジュールに、ヘッダピンをはんだ付けします。注意が必要な点が2つあります。
 1. L字型のピンではなく、ストレートタイプのピンを使うこと。[[BR]](L字型のピンを使うと、ジャイロモジュールはブレッドボードに対して垂直に立てて挿すことになりますが、そうするとウエイリングの激しい動作についていけない懸念があります。[[BR]]また、以下の説明では、ジャイロモジュールのZ軸(ジャイロモジュールを水平に置いたときに、ジャイロモジュールの上面に垂直に立てた法線)を中心にした回転をWailingとして検出する前提で記載しているため、ジャイロモジュールを立ててブレッドボードにつなぐと、Wailingの回転を検出できなくなります。
 2. 8つのピンをすべてはんだ付けするのではなく、VCCから数えて4つ分だけピンをはんだ付けします。[[BR]]8つすべてピンをはんだ付けしてしまうと、ブレッドボードの端と干渉して、ジャイロボードをブレッドボードに挿せなくなります。[[BR]]一方で、ヘッダピンの方は8つ(以上)連結してつながっていると思いますので、4つのところでパキッと折って使ってください。

[[Thumb(IMG_3742_.jpg, size=256x288, caption=VCCから4つ分のピンだけをはんだ付けする。残り半分ははんだ付けしない。)]]

次に配線です。今回使用するジャイロモジュール (MPU-6050) は、I^2^Cという通信規格でArduino等他の機器と接続します。(I^2^CはInter-Integrated Circuitの略で、アイスクエアシーと読みますが、アイツーシーでも通じます。)[[BR]]Arduino Microでは、D2ピンとD3ピンをI^2^Cでの通信に使用し、D2ピンをデータ送受信(SDA)、D3ピンがデータクロック(SCL)に用います。ですから、Arduino MicroのD2ピンとMPU-6050のSDAを、Arduino MIcroのD3ピンとMPU-6050のSCLを、それぞれつなぎます。また、MPU-6050のVCCとGNDは、それぞれArduino Microの5VとGNDにつなぎます。(このモジュールのVCCは、5Vと3.3Vのどちらをつないでもよいため、接続が簡単な5Vを使っています。) MPU-6050の他のピン(INTなど)は、何も接続しません。

配線をすっきりさせるために、MPU-6050の下側に、SDAとSCLの配線を通すようにしています。詳しくは、以下の図を見てください。このような通し方をしたくない場合は、後ほど出てくる配線図を見てください。

[[Thumb(IMG_3743_.jpg, size=256x360, caption=予め1-Eと1-F/2-Eと2-Fをオレンジの短いジャンプワイヤーでつないておいて・・・)]]

[[Thumb(IMG_3744_.jpg, size=256x346, caption=ジャンプワイヤーの上にかぶせるようにジャイロモジュールを挿してみた。なお、モジュールの左半分のピンをはんだ付けしていると、ブレッドボードの端に引っかかってうまく挿さらないので注意。)]]
[[Thumb(IMG_3745_.jpg, size=512x280, caption=Arduino Micro側のD2/D3(SDA/SCL)もつなぐとこんな感じ。線が邪魔にならないように束ねてみました。)]]

[[Thumb(gt-con-07_bb.png, size=472x240, caption=ここまでの配線を図示したもの。左端のジャイロモジュールの上にオレンジの縦の配線が2本ありますが、これはジャイロモジュールの下側に来ます。)]]
[[Thumb(gt-con-07B_bb.png, size=499x240, caption=ジャイロモジュールの下に配線を通したくない場合は、このような配線もできます。5Vを右上から右下に引っ張り込んでいることを見逃さないように。)]]


=== スケッチ修正
この辺りを参考にして、スケッチを作成しました。
 * [http://cranberrytree.blogspot.com/2014/06/gy-521mpu-6050.html 加速度+ジャイロのGY-521(MPU-6050)を使ってみた -1-] (特に縛りなく)
 * [http://playground.arduino.cc/Main/MPU-6050 MPU-6050 Accelerometer + Gyro] (The Arduino Playground)
 * [https://www.robotshop.com/jp/ja/mpu6050-6-dof-gyro-accelerometer-imu.html MPU6050 6 DOF ジャイロ加速度計 IMU (の中にある製品マニュアル)] (ロボショップ)

ちょっと長くなりますが、まずはスケッチ全体を引用します。
(スケッチの詳細に興味がない場合は、このページの最後にzipでまとめたものを置いておくので、そこまでは読み飛ばしていただいて結構です)

{{{ code c
// Simple example application that shows how to read four Arduino
// digital pins and map them to the USB Joystick library.
//
// Ground digital pins 9, 10, 11, and 12 to press the joystick 
// buttons 0, 1, 2, and 3.
//
// NOTE: This sketch file is for use with Arduino Leonardo and
//       Arduino Micro only.
//
// by Matthew Heironimus
// 2015-11-20
//--------------------------------------------------------------------

#define DEBUG           // シリアルへのデバッグ情報出力用
#include <Joystick.h>

const int BUTTONS = 6;  // RGBYP + Wailing (analogのPickは別)
const int PIN_STICK = A0; // Pick用のジョイスティックを接続するピン
const int BUTTON_WAILING = BUTTONS-1;
                        // BUTTONの最後1つを仮想的なWailingボタンにする
                        // 物理ボタンは無いが、ボタンとしては管理する

Joystick_ Joystick(
  JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD,
  BUTTONS, 0,           // Buttons, Hat Switch Count
  true,  false, false,  // X,Y,Z Axis
  false, false, false,  // Rx, Ry, Rz Axis
  false, false,         // rudder, throttle
  false, false, false   // accelerator, break, steering
);


const int TH_WAILING = 400;   // WAILINGと見なす角速度の閾値

// MPU-6050 Accelerometer + Gyro
#include <Wire.h>
 
#define MPU6050_SMPLRT_DIV        0x19   // R/W
#define MPU6050_CONFIG       0x1A   // R/W
#define MPU6050_GYRO_CONFIG       0x1B   // R/W
#define MPU6050_GYRO_ZOUT_H       0x47   // R
#define MPU6050_WHO_AM_I           0x75   // R
#define MPU6050_PWR_MGMT_1         0x6B   // R/W
#define MPU6050_I2C_ADDRESS       0x68

typedef union accel_t_gyro_union{
  struct{
    uint8_t z_gyro_h;
    uint8_t z_gyro_l;
  }
  reg;
  struct{
    int16_t z_gyro;
  }
  value;
};


unsigned long last_t;

void setup() {
  // Initialize Button Pins
  // Wailingの実ボタンはないのでBUTTONS-1すること
  for (int i = 12; i > 12-(BUTTONS-1); i--)
  {
    pinMode(i, INPUT_PULLUP);
  }

  // Initialize Joystick Library (AutoSendState=OFF)
  Joystick.begin(false);
  
  //Serial通信初期化
  Serial.begin(9600);

  // loop時間基準
  last_t = millis();

  // MPU-6050初期化
  Wire.begin();
  int error;
  uint8_t c;
  Serial.print("InvenSense MPU-6050");
  Serial.print("June 2012");
  error = MPU6050_read (MPU6050_WHO_AM_I, &c, 1);
  Serial.print("WHO_AM_I : ");
  Serial.print(c,HEX);
  Serial.print(", error = ");
  Serial.println(error,DEC);
  error = MPU6050_read (MPU6050_PWR_MGMT_1, &c, 1);
  Serial.print("PWR_MGMT_1 : ");
  Serial.print(c,HEX);
  Serial.print(", error = ");
  Serial.println(error,DEC);
  MPU6050_write_reg (MPU6050_PWR_MGMT_1, 0);

  // サンプルレートを最大化
  MPU6050_write_reg (MPU6050_SMPLRT_DIV, 0);

  // FSYNC input disabled, Gyrometer bandwidth=256Hz
  MPU6050_write_reg (MPU6050_CONFIG, 0);

  // FS_SELの設定
  error = MPU6050_read (MPU6050_GYRO_CONFIG, &c, 1);
  c |= 0x18;  // FS_SEL 3
  MPU6050_write_reg (MPU6050_GYRO_CONFIG, c);
}

// Constant that maps the phyical pin to the joystick button.
// Wailingは実ボタンではないので、ピンマップには含めない
const int pinToButtonMap = 13 - (BUTTONS-1);

// Last state of the button
// 6個目のボタンは、Wailingボタンとして扱う
int lastButtonState[BUTTONS] = {0,0,0,0,0,0};
int lastAnalogInput[2] = {0,0};
unsigned long lastButtonChangedTime[BUTTONS] = {0,0,0,0,0,0};

float last_gyro_z = 0;


void loop() {
  bool buttonStateChanged = false;
  unsigned long t = millis();

  // 1ms間隔でloop内処理が実行されるようにする
  // (前回の処理から1msに満たない時間しか経っていない場合は
  //  門前払いする)

  if (last_t >= t)
  {
    // 門前払いの前に、mills()のオーバーフロー対策もしてしまう
    if (last_t > (unsigned long)0xFFFF0000 &&
        t < (unsigned long)0x00001000)
    {
      for (int index = 0; index < BUTTONS; index++)
      {
        // 最終ボタン状態更新時刻を繰り上げて、正負が
        // 破綻しないようにする(この程度の対策で問題ないはず)
        lastButtonChangedTime[index] = 0;
      }
    }
    return;
  }
  else
  {
    last_t = t;
  }

  // Read digital pin values
  for (int index = 0; index < BUTTONS-1; index++)
  {
    int currentButtonState = !digitalRead(index + pinToButtonMap);
    if (currentButtonState != lastButtonState[index])
    {
      if (t > lastButtonChangedTime[index] + 10)
      {
        Joystick.setButton(index, currentButtonState);
        buttonStateChanged = true;
        lastButtonState[index] = currentButtonState;
        lastButtonChangedTime[index] = t;
#ifdef DEBUG
        Serial.print("*");
#endif
      }

#ifdef DEBUG
      Serial.print(t);
      Serial.print(":");
      Serial.print(index);
      Serial.print("=");
      Serial.print(currentButtonState);
      Serial.print("/");
      Serial.print(lastButtonState[index]);
      Serial.println("");
#endif
    }
  }

  // Read analog stick values
  {
    int currentAngularVelocity = analogRead(PIN_STICK);

    // ±5以下の入力ブレは無視する (LPF)
    if (abs(currentAngularVelocity - lastAnalogInput[0]) > 5)
    {
      Joystick.setXAxis(currentAngularVelocity);
      buttonStateChanged = true;
      lastAnalogInput[0] = currentAngularVelocity;
#ifdef DEBUG
      Serial.print(t);
      Serial.print(":");
      Serial.print("A0");
      Serial.print("=");
      Serial.print(currentAngularVelocity);
      Serial.println("");
#endif
    }
  }

  // ジャイロセンサー対応(MPU-6050)
  // 参考: http://cranberrytree.blogspot.com/2014/06/gy-521mpu-6050.html
  //      (MPU6050へのアクセス方法の参考として。実際にはジャイロ情報しか使わなかったですが)
  //      https://www.robotshop.com/jp/ja/mpu6050-6-dof-gyro-accelerometer-imu.html
  //      から、ZIPでダウンロードできるPDFマニュアル。その中にレジストリマップあり
  //      追加のモード設定の参考にした
  //
  // また、I2C通信の高速化(100kHz -> 400kHz)のため、
  // C:\Program Files (x86)\Arduino\hardware\arduino\avr\
  //  libraries\wire\src\utility\twi.h の最初にある
  //  #define TWI_FREQ 100000L
  //  の100000Lを400000Lに変更しておくこと。
  // 参考: https://sites.google.com/site/yamagajin/home/mpu6050
  
  int error;
  accel_t_gyro_union accel_t_gyro;
  error = MPU6050_read (MPU6050_GYRO_ZOUT_H, (uint8_t *) &accel_t_gyro, sizeof(accel_t_gyro));
 
  uint8_t swap;
#define SWAP(x,y) swap = x; x = y; y = swap
  SWAP (accel_t_gyro.reg.z_gyro_h, accel_t_gyro.reg.z_gyro_l);

  //FS_SEL_3 16.4 LSB / (°/s)
  float gyro_z = accel_t_gyro.value.z_gyro / 16.4;

  if (gyro_z > TH_WAILING)
  {
#ifdef DEBUG
    Serial.print(t);
    Serial.print("\t");
    Serial.print("[");
    Serial.print(gyro_z, 2);
    Serial.print("]");
    Serial.println("");
#endif

    if (!lastButtonState[BUTTON_WAILING])
    {
      Joystick.setButton(BUTTON_WAILING, true);
      lastButtonState[BUTTON_WAILING] = true;
      lastButtonChangedTime[BUTTON_WAILING] = t;
      buttonStateChanged = true;
#ifdef DEBUG
      Serial.print(t);
      Serial.println(":WAILING ON");
#endif
    }
  }

  // WailingボタンをONにした後200ms経過したら、自動でオフにする
  if (lastButtonState[BUTTON_WAILING] &&
      t > lastButtonChangedTime[BUTTON_WAILING] + 200)
  {
    Joystick.setButton(BUTTON_WAILING, false);
    lastButtonState[BUTTON_WAILING] = false;
    lastButtonChangedTime[BUTTON_WAILING] = t;
    buttonStateChanged = true;
#ifdef DEBUG
    Serial.print(t);
    Serial.println(":WAILING OFF");
#endif
  }

  // ボタンの状態変化があった時にだけ、PCにHID入力を通知する
  // 同時に、ボタン状態変化の最終時刻を更新する
  if (buttonStateChanged)
  {
    Joystick.sendState();
  }

  //delay(1);   // loop頭のtとlast_tの比較で代用
}



// MPU6050_read
int MPU6050_read(int start, uint8_t *buffer, int size){
  int i, n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);
  if (n != 1)
    return (-10);
  n = Wire.endTransmission(false);   // hold the I2C-bus
  if (n != 0)
    return (n);
  // Third parameter is true: relase I2C-bus after data is read.
  Wire.requestFrom(MPU6050_I2C_ADDRESS, size, true);
  i = 0;
  while(Wire.available() && i<size){
    buffer[i++]=Wire.read();
  }
  if ( i != size)
    return (-11);
  return (0);  // return : no error
}
 
// MPU6050_write
int MPU6050_write(int start, const uint8_t *pData, int size){
  int n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);        // write the start address
  if (n != 1)
    return (-20);
  n = Wire.write(pData, size);  // write data bytes
  if (n != size)
    return (-21);
  error = Wire.endTransmission(true); // release the I2C-bus
  if (error != 0)
    return (error);
  return (0);         // return : no error
}
 
// MPU6050_write_reg
int MPU6050_write_reg(int reg, uint8_t data){
  int error;
  error = MPU6050_write(reg, &data, 1);
  return (error);
}

}}}

以下、スケッチのキーポイントを説明していきます。

==== Wailingの判定方法
まず、Wailingの判定は、単純にMPU-6050のZ軸の回転 (つまり、ブレッドボードをギターっぽく持ってWailingしたときの、ブレッドボードの回転) で、回転の角速度が一定値以上かどうかで判定しています。もしもジャイロセンサーではなく加速度センサーを使って同じことをしようとすると、慣性もセンサーが拾ってしまって、回転の判定が非常に大変なことになりますが、ジャイロセンサーを使う場合は単純にZ軸の角速度が一定値以上かどうかを判定するだけで済みます。

具体的には、Z軸の角速度取得と、角速度の判定を、loop()の214行目付近で行っています。
なおMPU-6050から取得した16bitの値は、エンディアンを変換する必要があるため、それも合わせて行っています。

{{{ code c
  int error;
  accel_t_gyro_union accel_t_gyro;
  error = MPU6050_read (MPU6050_GYRO_ZOUT_H, (uint8_t *) &accel_t_gyro, sizeof(accel_t_gyro));
 
  uint8_t swap;
#define SWAP(x,y) swap = x; x = y; y = swap
  SWAP (accel_t_gyro.reg.z_gyro_h, accel_t_gyro.reg.z_gyro_l);

  //FS_SEL_3 16.4 LSB / (°/s)
  float gyro_z = accel_t_gyro.value.z_gyro / 16.4;

  if (gyro_z > TH_WAILING)
  {
//以下略
}}}

なお判定の閾値は、33行目のマクロで定義しています。反応が敏感すぎるようなら、400を500くらいに上げてみてください。
逆に、より敏感にするためには、300とか350とかにしてみて下さい。
{{{ code c
const int TH_WAILING = 400;   // WAILINGと見なす角速度の閾値
}}}
==== Wailingボタンを離す処理の追加
Wailingが発生したと判断された場合は、内部でBUTTON5を押す動作をします。BUTTON5に相当する物理ボタンは接続していないので、これは仮想的なボタンとなります。
Wailingもボタンとして扱う以上は、押すだけでなく、離す処理も必要になります。離す処理がないと、Wailingボタンが押しっぱなしの状態になり、
次のWailingが動作しなくなります。

loop()の249行目あたりで、この処理を入れています。
具体的には、(最初にWailingが発生したときにボタンが押され、) そこから200ms経過したらボタンを離すようにしています。
1度のWailing操作をすると、内部では1-2ms毎に複数のWailing判定が連続して発生しますが、それらはWailingボタンの追加押しになるので全て無視されます。
200ms経過しWailingボタンが離されると、再度Wailing操作でWailingボタンを押すことができるようになります。

{{{ code c
  // WailingボタンをONにした後200ms経過したら、自動でオフにする
  if (lastButtonState[BUTTON_WAILING] &&
      t > lastButtonChangedTime[BUTTON_WAILING] + 200)
  {
    Joystick.setButton(BUTTON_WAILING, false);
    lastButtonState[BUTTON_WAILING] = false;
    lastButtonChangedTime[BUTTON_WAILING] = t;
    buttonStateChanged = true;
#ifdef DEBUG
    Serial.print(t);
    Serial.println(":WAILING OFF");
#endif
  }
}}}

==== 処理の高速化
MPU-6050の情報はI^2^C通信を経由して取得するため、ほかのボタンやアナログスティックと比べると情報取得がどうしても遅くなります。
またMPU-6050のZ軸の角速度以外の情報 (X/Y/Z軸方向の加速度など) も取得して処理を行うと、1回の処理で5~6ms必要となるようでした。

一方で、ギタコンのボタン状態の読み出しは1ループあたり1~2ms程度で済ませたいですよね。そこで、処理の高速化が必要になります。

以下の高速化を一通り施すことで、1ループ当たりの時間が1~2ms程度となりました。


===== MPU-6050への余計なアクセスや、処理の削除
スケッチのベースにさせていただいた  [http://cranberrytree.blogspot.com/2014/06/gy-521mpu-6050.html 加速度+ジャイロのGY-521(MPU-6050)を使ってみた -1-] (特に縛りなく) や [http://playground.arduino.cc/Main/MPU-6050 MPU-6050 Accelerometer + Gyro] (The Arduino Playground) から、ギタコンで使う情報(Z軸の角速度)に関係しないところを全て削除しました。[[BR]][[BR]]そして、更に高速化するために、setup()の96行目近辺で以下の設定をしています。
   * MPU-6050のサンプリングレートを最大化
   * ジャイロセンサーの帯域幅を最大化

{{{ code c
  // サンプルレートを最大化
  MPU6050_write_reg (MPU6050_SMPLRT_DIV, 0);

  // FSYNC input disabled, Gyrometer bandwidth=256Hz
  MPU6050_write_reg (MPU6050_CONFIG, 0);
}}}

===== I^2^C通信の帯域幅の拡張
通常、Arduino(のWireライブラリ)を使うと、I^2^C通信のクロックは100kHzとなります。しかし、I^2^C通信は規格上400kHzのクロックを使うことができ、MPU-6050もそれに対応しています。そこで、I^2^C通信のクロックを400kHzに変更することで、I^2^C通信の速度を4倍にします。[[BR]][[BR]]具体的には、C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\wire\src\utility\twi.h を編集し、28行目あたりにあるTWI_FREQの値を100000Lから400000Lに変更します。(参考: [https://sites.google.com/site/yamagajin/home/mpu6050 MPU6050] (備忘録) )[[BR]][[BR]]なお通常、Program Files (x86) 以下にあるファイルを編集しようとすると、権限がなくファイルを開けなかったり書き込めなかったりします。いったんデスクトップにファイルをコピーして編集して元の場所に上書きコピーするとか、そのコピーをするにも管理者としてエクスプローラーを開いてやらないといけないとか、いくつかの工夫が必要です。

{{{ code c
  #ifndef TWI_FREQ
  #define TWI_FREQ 100000L
  #endif
}}}

===== delay(1); の削除
試作3までは、loop()の最後で必ずdelay(1);を実行し、1msのウエイトを入れていました。[[BR]]しかし、1ループ当たりの処理が例えば2ms掛かっているような状況でdelay(1)すると、合計で1ループあたり3ms掛かることになります。[[BR]]そのため、直前のループ開始時刻と今回のループ開始時刻を比較し、1ms経過していない場合のみ、1ms経過するまで待つ、といった処理に変更しました。[[BR]][[BR]]そして、この部分で、ついでにmills()のオーバーフローの対策を加えています。mills()のオーバーフローはArduinoに通電して49~50日程度連続して通電しないと発生しないので通常はオーバーフローに遭遇しない(、だからオーバーフロー対策を端折っちゃおう)、と考えますが、一方でギタコンをUSBでつなぎっぱなしにしているとそのうちいつか必ず発生するということでもあるので、ボタン動作が破綻しない程度の簡単な対策を入れています。[[BR]][[BR]]

{{{ code c
void loop() {
  bool buttonStateChanged = false;
  unsigned long t = millis();

  // 1ms間隔でloop内処理が実行されるようにする
  // (前回の処理から1msに満たない時間しか経っていない場合は
  //  門前払いする)

  if (last_t >= t)
  {
    // 門前払いの前に、mills()のオーバーフロー対策もしてしまう
    if (last_t > (unsigned long)0xFFFF0000 &&
        t < (unsigned long)0x00001000)
    {
      for (int index = 0; index < BUTTONS; index++)
      {
        // 最終ボタン状態更新時刻を繰り上げて、正負が
        // 破綻しないようにする(この程度の対策で問題ないはず)
        lastButtonChangedTime[index] = 0;
      }
    }
    return;
  }
  else
  {
    last_t = t;
  }
}}}


===== 複数の入力をまとめて1回で出力する
試作3までは、ボタンやスティックの状態変化を検出したときは、「検出したタイミングでその都度」ギタコンのボタンやスティックの状態を一式、ごっそりとUSB経由でPCに出力していました。

しかしこれだと、スティック操作をしながらボタンを押すなどといった場合に、1回の状態確認ループの中でスティックとボタンの状態変化を検出する都度、ギタコン全体のボタン/スティックの状態をPCに送るので、USBの通信時間分、1回ループの処理時間が長くなり、1回のループ当たり1~2msよりもっと処理時間がかかるようになってしまっていました。

そのため、ボタン等の状態変化を検出してもすぐにはUSB経由で送信せず、ループの終りにまとめて送信するようにしました。そうすれば、1回のループ当たりのUSB送信は1回で済みます。

このため、まずsetup()の70行目付近で、Joysthick_のライブラリを開始する際に、「状態変化時にUSBで自動送信する」機能(!AutoSendState)を無効化します。

{{{
  // Initialize Joystick Library (AutoSendState=OFF)
  Joystick.begin(false);
}}}

その上で、loop()の263行目付近(loop()の最後)で、ボタンの状態変化があった時にだけ、USB経由で現在のボタン等の状態を送信する処理を追加しています。

{{{ code c
  // ボタンの状態変化があった時にだけ、PCにHID入力を通知する
  // 同時に、ボタン状態変化の最終時刻を更新する
  if (buttonStateChanged)
  {
    Joystick.sendState();
    last_button_state_changed_t = t;
}}}

==== ジャイロセンサーの取得値の最大値の最大化 (Full-scaleの最大化)
MPU-6050は、初期状態では、ジャイロセンサーで取得できる角速度の最大値は250度/秒です。しかしこれだと、それほど激しくWailingしてなくてもこの上限値になってしまい、回転の強弱を正しく判別できません。そのためこの値の最大値を最大化することで、取得値のレンジを広げ、回転の強弱を区別できるようにします。[[BR]]
具体的には、FS_SELというパラメータを3に設定することで、取得できる角速度の最大値を2000度/秒に拡大します。[[BR]]setup()の処理がこれに該当します。[[BR]][[BR]]設定に必要な資料は、[https://www.robotshop.com/jp/ja/mpu6050-6-dof-gyro-accelerometer-imu.html MPU6050 6 DOF ジャイロ加速度計 IMU (の中にある製品マニュアル)] (ロボショップ) から得ました。

{{{ code c
  // FS_SELの設定
  error = MPU6050_read (MPU6050_GYRO_CONFIG, &c, 1);
  c |= 0x18;  // FS_SEL 3
  MPU6050_write_reg (MPU6050_GYRO_CONFIG, c);
}}}

=== まとめ
試作4では、配線をすっきりさせ、Wailingに対応しました。

個人的には、!PlayStation1用のKONAMI製ギターコントローラーと比べて、Wailingの反応が相当よく、きびきびと反応してくれるので気に入っています。

試作3のスケッチ全体のダウンロード: [[LinkAttach(gtcon-07_.zip)]] (Wailing対応+アナログスティック対応あり)

注意: 高速化のため、このスケッチをArduinoのIDEで開く前に、C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\wire\src\utility\twi.h を編集し、28行目あたりにあるTWI_FREQの値を100000Lから400000Lに変更することをお忘れなく。