[Arduino]DRV2605でLRAを動かす

※ これは 2017/8/14 に個人ブログへ投稿した内容を転記したものです。

触感フィードバックをしようとして詰まったけど何故か突然上手く行った話。
紆余曲折しているので、[結論](#結論)を読んでから実践しましょう。

タイトルの呪文を解説しながら、実験環境を説明します。

# DRV2605 とは
[DRV2605 ERM および LRA 用ハプティクス・ドライバ、ライブラリおよびインテリジェント・コントロール・エンジン搭載 | TIJ.co.jp](https://www.tij.co.jp/product/jp/DRV2605)
I2C で叩くと良い塩梅で振動モーターを駆動してくれる便利な IC （超意訳）
ブレークアウト基板は [Adafruit](https://learn.adafruit.com/adafruit-drv2605-haptic-controller-breakout/overview) や [Sparkfun](https://learn.sparkfun.com/tutorials/haptic-motor-driver-hook-up-guide) から出てます。

Adafruit は EN ピンは HIGH 固定ですが、 Sparkfun は EN ピンが出ているので実デバイスに乗っけるならこちらを選んだほうが良さそう。
しかし私は Sparkfun から出ていたのを知らずに Adafruit の方を買ったので、この記事では Adafruit を使います。[マルツ](https://www.marutsu.co.jp/pc/i/574336)で買いました。
Sparkfunのほうは[スイッチサイエンス](https://www.switch-science.com/catalog/3098/)で売っています。

# LRA とは

> リニア・バイブレータ（LRA：Linear Resonant Actuator）。コイルに電流を流すことで電磁力を発生させ、これと磁石との反発力を使ってコイル自体を上下に振動させるものだ。
> [ハプティクス（触感フィードバック） - EDN Japan](https://ednjapan.com/edn/articles/1210/10/news010.html)

いわゆるガラケーに入ってたのは偏心モーター （ERM） で、ブルンブルンって感じの振動でしたよね。最近はカチっというクリック感が出せる LRA が主流だと思います。
LRA にはリンク先の絵にあるコイン状のものと、 iPhone で Taptic Engine と呼ばれているような四角いものがあります。[^1]
[^1]: Android は大概コイン状のものが載っていますが、 Taptic Engine と比べると触感のシャープさに欠けますね。チューニング次第かもしれませんが。

LRA は秋月で調達。ちょっとデカイですが、 Taptic Engine と同じで横方向に動くタイプです。
[リニア振動アクチュエータ　ＬＤ１４－００２: パーツ一般 秋月電子通商-電子部品・ネット通販](http://akizukidenshi.com/catalog/g/gP-06838/)

# 動かしてみよう
結線は超簡単。ArduinoのI2C用のピンをつなぐだけ。
[Arduino Code | Adafruit DRV2605L Haptic Controller Breakout | Adafruit Learning System](https://learn.adafruit.com/adafruit-drv2605-haptic-controller-breakout/wiring-and-test)

AdafruitからArduino用ライブラリが配布されてるのでこれを使います。
[GitHub - adafruit/Adafruit_DRV2605_Library: Arduino library for Adafruit DRV2605L Haptic Controller Breakout](https://github.com/adafruit/Adafruit_DRV2605_Library)

サンプルコードから basic を書き込み、いざ…

__しーん__

なんでやねん。 `Adafruit_DRV2605::begin()` を見に行くと

```cpp:Adafruit_DRV2605.cpp
  // ERM open loop
  
  // turn off N_ERM_LRA
  writeRegister8(DRV2605_REG_FEEDBACK, readRegister8(DRV2605_REG_FEEDBACK) & 0x7F);
  // turn on ERM_OPEN_LOOP
  writeRegister8(DRV2605_REG_CONTROL3, readRegister8(DRV2605_REG_CONTROL3) | 0x20);
```

デフォルトは ERM のオープンループ設定らしい。
`useLRA` という関数が用意されているので、これを使う。ついでに Library も LRA 用の 6 を選択する。

```cpp:basic.ino
void setup() {
  Serial.begin(9600);
  Serial.println("DRV test");
  drv.begin();
  
  drv.useLRA();
  drv.selectLibrary(6);
  
  // I2C trigger by sending 'go' command 
  // default, internal trigger when sending GO command
  drv.setMode(DRV2605_MODE_INTTRIG); 
}
```

これでも動かなかった。なんでー？
電流が足らないのかなーと思い、安定化電源つないだりオシロ覗いたりしたけど原因が分からず。
GitHub で他のライブラリを探してみる。 Adafruit と Sparkfun 以外で ２ つ見つかる。

[FlexModule/haptic/LRA/DRV2605L/software/arduino/DRV2605 at master · FyberLabs/FlexModule · GitHub](https://github.com/FyberLabs/FlexModule/tree/master/haptic/LRA/DRV2605L/software/arduino/DRV2605)

[GitHub - Seeed-Studio/Grove_Haptic_Motor](https://github.com/Seeed-Studio/Grove_Haptic_Motor)

このうち、後者で動いた。 `Auto calibration` も機能している模様。[^2]
[^2]: init が fail していますが、これは init に正常終了を表す 0 を返すためのリターンパスが書かれていないためのようです。

<img width="512" alt="20170814111847.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591453/278d5699-7faf-6592-c9f6-55d35749938a.png">

やはり Auto Calibration が要るのか？ と思い、もう一度 Adafruit のプログラムを書いてみる。

__Buzzzz__

動いた（白目）
結局、同じ現象を再現できず。どうも `RATED_VOLTAGE` (0x16) と `OD_CLAMP` (0x17) が小さいと鳴らないので、このせいだったのかもしれないが、電源を入れ直すとデフォルト値に戻るので理屈が合わない。
なお、データシートには明示されていないが[^3]、上記２つのパラメータはどうも絶対値（つまり定格 3V なら 0x03 ）ではなく、入力電圧に対する比率 （0xFF=100%） のような気がする。
[^3]: 英語力と読解力が貧弱なだけかもしれない
振動モーターの仕様をみると
>・駆動電圧：２Ｖ～６Ｖ
>・駆動電流：１２０ｍＡ（＠３Ｖ、バイポーラ駆動、１５０Ｈｚ、デューティー比５０％）
>・コイル構成：１相（バイポーラまたはユニポーラ駆動）
>・コイル仕様：直流抵抗２５Ω、インダクタンス２１０μＨ（＠１２０Ｈｚ）
>・本体共振周波数（ｆｏ）：約１５０Ｈｚ（配線材、固定方法等によって変動します）
> [リニア振動アクチュエータ　ＬＤ１４－００２: パーツ一般 秋月電子通商-電子部品・ネット通販](http://akizukidenshi.com/catalog/g/gP-06838/)

この場合、 5V を与えていて 3V 定格で動かすなら `RATED_VOLTAGE=0x99`、`OD_CLAMP=0xFF` でいいのかな？ 自信ないですが、ひとまず焼けはしなかったです。

# 結論
よくわかんないけど動いた。

Adafruit のライブラリに Auto calibration を追加したので、[ひっそり公開しておきます](https://github.com/at6ue/Adafruit_DRV2605_Library/tree/AutoCalibration)。

```cpp
bool Adafruit_DRV2605::autoCal(uint8_t ratedVoltage, uint8_t overdriveClamp, uint8_t driveTime)
```
GitHub 上のスケッチではデフォルト値を入れてあります。
`DRIVE_TIME(0x1B)` はデータシートによると周期の半分を入れれば良いらしい。
`Optimum drive time (ms) ≈ 0.5 × LRA Period`
というわけで、今回の振動モーターでは5Vを繋いだ前提で

```cpp
autoCal(0x99,0xFF,0x03)
```

として、モーターを固定してから実行すればOK。