数種類の振動を作りたいなら専用の振動ドライバーDRV2605Lがおすすめ

AdafuritのDRV2605LモジュールにLRAアクチュエータを載せた

振動で色々試したいなら自分でタイミングと強さを制御するより振動モータードライバのDRV2605Lにある115種類のプリセットから選ぶほうが簡単。I2Cで制御できてアドレスが変えられないものの、I2Cマルチプレクサを使って複数個制御もすんなり出来た。

DRV2605L搭載のモジュール自体は、日本からの入手性だと毎度おなじみAdafruitかsparkfunなんだけど、AdafuritのほうがSTEMMA QT/Qwiicコネクタ(以下Qwiicにします)搭載してて楽ですね。AdafruitからはQwiicコネクタで分岐する8chのI2Cマルチプレクサ(PCA9548搭載)が出ているので、8個まで簡単にQwiicシステムで繋いで制御できる。

Adafruit DRV2605L 触感フィードバックモジュール — スイッチサイエンス
STEMMA QT/Qwiic - PCA9548搭載8チャンネルMux拡張基板 — スイッチサイエンス

SeeedのXIAOと拡張ボードからPCA9548につないでDRV2605Lにつながっている

115種類ってどんなの?

データシートからスクショを載せておきます。引用の範囲でしょう。Adafruitに英語はあるので日本語のやつにしました。自分が使った素子の限界的にはトリプルクリックはトリプルに感じなかった。いくつか同じエフェクトが載っているのは謎。

https://www.ti.com/product/ja-jp/DRV2605L

振動素子自体はERMとLRAのどっちがいいか?

ERMよりもLRAのほうが変な挙動になった時に安全な感じがあった。あと、振動がどちらかといえば上品で小さい振動の表現力が高く感じる。どっちにしろ下手な実装するとマイコンのフラッシュに書き込む部分が死ぬので気をつけてほしい。

実はDRV2605L側でディレイできる

setWaveform関数を使って0.01秒単位から1.27秒までディレイできる。エフェクトが127までで、それ以降の数字を書き込むとディレイって判断されるようになっているので。AdafuritのPythonの方では関数化していたが、なぜかArduinoにはなかったので自分で実装した。もっと良い書き方あると思うのでそこは勘弁して……。

Adafruit_DRV2605 drv;

// 1.27秒までdelay可能 
void driveMotorWithDelay(int effectIndex, float delayTime = 0){
  float dTime = delayTime;
  if(dTime <= 0.01 || dTime >= 1.27) dTime = 0;
  int castedDelay = (int) (dTime*100.0);
  uint8_t delayedVal = 0x80 | castedDelay;
  drv.setWaveform(0, delayedVal);
  drv.setWaveform(1, effectIndex);
  drv.setWaveform(2, 0);
  drv.go();
}

複数個制御するときの注意点

AdafuritのArduino用DRV2605ライブラリを使ったんだけど、Adafruit_DRV2605型を配列にして実装したら、変なエラーがたまに出た。たまに、なのでこれが厄介だった。I2Cマルチプレクサはマイコン側には同一アドレスを提供するのでシンプルに配列をやめて単体の変数にして制御したら問題なかった。最初からそうしておけば……。複数個動かす最低限のコードは多分これ。検証していないので、もし動かなくて修正して体力気力が余っていたらコメントでお知らせしてください。

#define PCAADDR 0x70
#define NUM_DRVS 7
Adafruit_DRV2605 drv;

void setup(){
  Wire.begin();

  for (int i = 0; i < NUM_DRVS; i++) {
    pcaselect(i);
    drv.begin();
    drv.selectLibrary(6);
    drv.useLRA();
    drv.setMode(DRV2605_MODE_INTTRIG);
  }
}

void pcaselect(uint8_t channel) {
  if (channel > 7) return;
  Wire.beginTransmission(PCAADDR);
  Wire.write(1 << channel);
  Wire.endTransmission();
}

void driveMotor(int motorIndex, unsigned char effectIndex){
  if(motorIndex == noMotorIndex || motorIndex < 0) return;

  pcaselect(motorIndex);
  drv.setWaveform(0, effectIndex);
  drv.setWaveform(1, 0);
  drv.go();
}