この記事は、UT-virtual Advent Calender 2018 11日目の記事として執筆しました。
※初心者が、自分のぶち当たった壁について、同じような問題に遭遇した人のために、思考過程も含めて丁寧に(冗長に)書いた記事なので、モーターを制御するという目的だけで読む人には長ったらしく感じるかもしれませんが、ご了承ください。
#でっかいモーターを動かす
私の所属する東京大学VRサークル UT-virtual の駒場祭展示「FlyFlyFly」で、触覚提示のための筐体づくりの任を受けた。お客さんを乗せたまま上下出来るパワフルな動力が必要になった。油圧ポンプ、エアポンプ、電動ウィンチ等、パワーのありそうな動力をたくさん考えてみたが、ハードウェアの開発は初めてで予算も限られていたこともあり、扱いが簡単そうであんまり高くないAmazonで売ってたリニアアクチュエータを使ってみることにした。
#何もわからんからまず動かしてみよう
制御なんて高尚なことの前に、まず動かなきゃお話しにならない。まずは何も考えずに、電源のACアダプタに直刺しして、動くことを確認することにした。駆動には12V 3Aの直流が必要らしいので、秋月でちょうどよさそうなACアダプタを買ってきた。その時、この世には2.1mm DCプラグなどという規格が存在することを知る。
ACアダプタに書かれているマークによると、極性は内側がプラスで外側がマイナス(GND)であるらしいということが分かった。ということで、とりあえず手元にあったジャンパワイヤなどで直刺しして確かめてみた。
この確認で、とりあえずこいつが不良品でないことと、どうやら、縮む向きが正転で、伸びる向きが逆転、つまり、赤い導線にプラスをつなぐと縮んで、黒い導線にプラスをつなぐと伸びるらしいということまでが分かった。
#モータードライバとArduinoで制御してみる
とりあえず動くことはわかったので、今度はArduinoから制御できるところまで行ってみることにした。最初は、どんな回路を組めばいいのか、電子回路初心者の自分は途方に暮れていたが、どうやらモータードライバなるものを使えばうまく行きそうだということがわかった。しかし、モータードライバについて調べていたところ、今度は世の中にはブラシレスモータなる概念が存在することを知る。調べてみたところ、永久磁石が回るかコイルが回るかという違いらしいことがわかった。ブラシレスモータ(永久磁石が回転子)は専用のモータードライバがなければ動かないらしいが、アクチュエータのデーターシートを読んでみた感じ、ブラシレスではなさそうだったので、普通のモータードライバを使うことにした。12V 3A DCという大電流に耐えられるものということで、TB6643KQを使ってみることにした。ついでに仮配線のためのワニ口クリップも買った。
普段から、ICチップを触ってる人からしたら常識なのだろうが、ピンの番号が左右のどちらから始まるのかがデータシートに書いてなくて困ったので、ここに書いておく。つまりは、4番ピンをGNDに7番ピンを電源につないだ状態で、Arduinoから1番ピンに電流を流すと3番ピンから、2番ピンに電流を流すと5番ピンから大電流が流れるようになっているらしい。この通りの配線になるように、とりあえずブレッドボード上で組んでみた。しかし、12V 3AのACアダプタでは、回路中の損失などで微妙にパワー不足だったらしく、上手く動かなかったので、電力に余裕を持たせて、12V 5AのACアダプタを買ってきて、取り替えたところ上手く動いた。
void setup()
{
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
}
void loop()
{
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
delay(1000);
digitalWrite(6, LOW);
digitalWrite(5, HIGH);
delay(1000);
}
2個同時も行けた。
うるさいLチカ pic.twitter.com/eTocehL54J
— マサト (@1B_Co_blue) October 20, 2018
#Uduinoを使う
Uduinoのインポートやセットアップは公式ページのチュートリアル(英語)や「Unity2017でUDUINOを使ってArduino接続」に書いてある通りにやれば、特に問題なく進む。今回、椅子1つにつき、4本のアクチュエーターで制御することになっていて、前後の高さも変えられるようにしたいとのことだったので、そのためにまずはキーボード入力で動かせるような、C#のコードも書いた(デバッグ用なので、ifの処理等は適当)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Uduino;
public class BaseController : MonoBehaviour
{
//ピンの番号
const int FORE_LEFT_UP = 3;
const int FORE_LEFT_DOWN = 2;
const int FORE_RIGHT_UP = 5;
const int FORE_RIGHT_DOWN = 4;
const int BACK_LEFT_UP = 7;
const int BACK_LEFT_DOWN = 8;
const int BACK_RIGHT_UP = 13;
const int BACK_RIGHT_DOWN = 12;
//動かすときのピンのグループ
List<int> foreUp = new List<int> { FORE_LEFT_UP, FORE_RIGHT_UP };
List<int> backUp = new List<int> { BACK_LEFT_UP, BACK_RIGHT_UP };
List<int> allUp = new List<int> { FORE_LEFT_UP, FORE_RIGHT_UP, BACK_LEFT_UP, BACK_RIGHT_UP };
List<int> foreDown = new List<int> { FORE_LEFT_DOWN, FORE_RIGHT_DOWN };
List<int> backDown = new List<int> { BACK_LEFT_DOWN, BACK_RIGHT_DOWN };
List<int> allDown = new List<int> { FORE_LEFT_DOWN, FORE_RIGHT_DOWN, BACK_LEFT_DOWN, BACK_RIGHT_DOWN };
void Start ()
{
//ピンのモードの設定
UduinoManager.Instance.pinMode(FORE_LEFT_UP, PinMode.Output);
UduinoManager.Instance.pinMode(FORE_LEFT_DOWN, PinMode.Output);
UduinoManager.Instance.pinMode(FORE_RIGHT_UP, PinMode.Output);
UduinoManager.Instance.pinMode(FORE_RIGHT_DOWN, PinMode.Output);
UduinoManager.Instance.pinMode(BACK_LEFT_UP, PinMode.Output);
UduinoManager.Instance.pinMode(BACK_LEFT_DOWN, PinMode.Output);
UduinoManager.Instance.pinMode(BACK_RIGHT_UP, PinMode.Output);
UduinoManager.Instance.pinMode(BACK_RIGHT_DOWN, PinMode.Output);
}
void Update ()
{
ManualManipulate();
}
//キーボード操作用関数
void ManualManipulate()
{
//Qキーを押している間、前の足2本が上がる
if (Input.GetKeyDown(KeyCode.Q))
{
foreach (int value in foreUp)
{
UduinoManager.Instance.digitalWrite(value, State.HIGH);
}
}
if (Input.GetKeyUp(KeyCode.Q))
{
foreach (int value in foreUp)
{
UduinoManager.Instance.digitalWrite(value, State.LOW);
}
}
/*
以下、A:前足降下、W:後足上昇、S:後足降下、E:全足上昇、D:全足上昇として同様の処理を書いた。
*/
}
}
とりあえず、これで動いた。
#Arduinoにつけるシールドを作る
何とか動かすところまではたどり着いたが、ここまですべてブレッドボード上で実装していたので、ひどいスパゲッティコード(物理)が生まれてしまった。
なので、配線をまとめるシールドを作ることにした。Arduino用のユニバーサル基盤を買い、導線むき出しだったアクチュエーターに2.1mm DCプラグを取り付け、
とりあえず、1枚作ってみた。
(地味についているイヤホンジャックは別の機能用)
これをArduinoにマウントして、デバッグキーでアクチュエータの伸縮をやっていたところ、問題が発生した。突然動かなくなってしまったのだ。原因について考えてみたところ、
- 大電流を扱ってた割に、ヒートシンクもつけず、排熱がおろそかだった。
- 嬉々としてデバッグをやったときに、アクチュエータの正転・逆転を何度も急に切り替えたことで、アクチュエータで逆起電力が発生し、モータードライバに過剰な電流が流れてしまった。
この2点が怪しいのではないかということになった。そこで、
- ヒートシンクをつける。
- 電源付近にコンデンサーをつける。(データシートで推奨されてたのを見逃してた…)
- モータードライバが死んだら換装できるようにソケットをつける。
- 正転・逆転を急に切り替えないようにする。(デバッグ時も、はやる気持ちを抑える)
の4点を改善することにした。完成した写真、回路図がこちら。
ユニバーサル基盤の表面と裏面で、いい感じに配線を分けられたので、前よりもきれいになった。
#手動制御からの脱却
最後に、今まで全部手動でキーボード入力していたのを、タイムラインから自動で呼び出してもらえるように、以下の記述を追加した。
//動かすグループ・向きの種類
const int STOP = 0;
const int FORE_UP = 1;
const int FORE_DOWN = 2;
const int BACK_UP = 3;
const int BACK_DOWN = 4;
const int ALL_UP = 5;
const int ALL_DOWN = 6;
//現在の状態
int state = 0;
//その状態の残り時間
float restTime = 0.0f;
void Update ()
{
ManualManipulate();
//新規追加
CountDown();
}
//アクチュエータを動かし始めたいときに、動かしたいグループ・向きの変数、継続時間(単位:秒)を入れて使う
void Ignite(int targetState, float time)
{
List<int> targetList = null;
if(state == STOP)
{
switch (targetState)
{
case FORE_UP:
targetList = foreUp;
state = FORE_UP;
break;
case FORE_DOWN:
targetList = foreDown;
state = FORE_DOWN;
break;
case BACK_UP:
targetList = backUp;
state = BACK_UP;
break;
case BACK_DOWN:
targetList = backDown;
state = BACK_DOWN;
break;
case ALL_UP:
targetList = allUp;
state = ALL_UP;
break;
case ALL_DOWN:
targetList = allDown;
state = ALL_DOWN;
break;
}
foreach (int value in targetList)
{
UduinoManager.Instance.digitalWrite(value, State.HIGH);
}
restTime = time;
}
}
//動きを止める
void Stop()
{
if(state != STOP)
{
List<int> targetList = null;
switch (state)
{
case FORE_UP:
targetList = foreUp;
break;
case FORE_DOWN:
targetList = foreDown;
break;
case BACK_UP:
targetList = backUp;
break;
case BACK_DOWN:
targetList = backDown;
break;
case ALL_UP:
targetList = allUp;
break;
case ALL_DOWN:
targetList = allDown;
break;
}
foreach(int value in targetList)
{
UduinoManager.Instance.digitalWrite(value, State.LOW);
}
state = STOP;
restTime = 0.0f;
}
}
//残り時間が来たら、アクチュエータを止める
void CountDown()
{
if(restTime > 0.0f)
{
restTime -= Time.deltaTime;
if(restTime <= 0.0f)
{
Stop();
}
}
}
#ついに完成
シールドもスクリプトも完成したので、あとは組み立てて動かすだけ。
モータードライバを装着してArduinoにマウントし、
ケーブル類を挿し、
スイッチオン!
動いた!(わかりづらい)
#最後に
結局本番は、かなり心配していたモータードライバの故障にも見舞われず(換装できるようにして、いっぱい買い込んだのに…)、無事に3日間の展示を乗り切ることができた。かなり稚拙な設計(電源無駄に分けてある。まだちょっとスパゲッティ。コンデンサーのつき方ダサい。ちゃんと計算してない。など…)ではあったが、何とか動いてくれて本当に良かった。とはいえ、まだまだ改善点はあるので、さらにいいものにしていきたい。
最後に、この記事を、自分の未熟さゆえに死なせてしまったTB6643KQたちに捧ぐ。