SPRESENSEでローバーを作りたいなと思ったのですが、DCモーターは特性のばらつきが大きく2つ車輪(モーター)を低速で同じように回すのがなかなか難しく低速走行に課題がありました。
そこで、ブラシレスモーターなら低速でもうまく制御できるのではないかと思い、SPRESENSEでブラシレスモーターを動かすことを検討してみました。
ブラシレスモーターを回すしくみ
通常のモーターは回転軸にコイルがあり、ブラシを通じてコイルの磁束を変えることで回します。ブラシレスモーターはその逆で、回転軸と磁石を連結して、固定されたコイルの磁束を変えることで回します。回転軸にブラシが不要になるのでエネルギーロスが抑えられ、寿命も長くなるという利点があります。
ブラシレスモーターは基本的には3つのコイルのの磁束方向を変えることで制御をしていきます。3つのコイに流れる電流の方向を変化させるとそこに合成磁束ベクトルが発生するため下の図のように6方向に回転軸を制御することができます。
しかし、これでは決められた6方向にしか回せません。そこで、3つのコイルの磁束の変化を正弦波で変化をさせてあげるとスムースに回すことができます。
次のようにUVW端子の間で120°位相の異なる正弦波で電位差を作ることで軸を回転させることができます。
引用:ブラシレスモータのPWM駆動にて、生成する波形が正弦波(ベクトル制御)の場合と矩形波(120°通電制御)の場合に、モータの出力特性上どのような違いが起こりますか
オシレーターのようにアナログの正弦波を綺麗に出力できればよいですが、デジタルIOで電圧を制御する方法もあります。PWMでデューティ比を変化させればエネルギー的には同等のことができます。この場合、マイナスの電圧を発生させることはできません。しかし、電位差があれば電流は流れるはずですのでこれでもまわるでしょう。
引用:MathWorks:PWMを使用して電力制御するアルゴリズムを実装する
SPRESENSEによるブラシレスモーターの接続
これでだいたい原理はわかりました。SPRESENSEでブラシレスモーターを回すには3本のPWM出力を使えばよさそうです。低速で回転をさせたいので、高速なPWM出力を使う必要はありませんが、ここは念のためPWM出力のピンをアサインしておきます。
SPRESENSEのGPIOでは、モーターを動かすほどの電流が供給できませんので間にモータードライバIC「L293D」を入れています。モーター電源として拡張ボードの Vout 端子をもってきました。この端子はUSBから供給されますので、最大500mAはいけるので回すだけなら大丈夫でしょう。
せっかくなので、回転速度を調整できるようにボリュームを付けています。また回転方向を制御できるようにボタンも追加してみました。
SPRESENSEによるブラシレスモーターの制御
問題は正弦波をどうやって出力するかですが、ここは計算能力に優れたSPRESENSEですのでSIN関数を使うことにしました。SIN関数を高速に計算ができるように、ARM CMSIS のDSPライブラリを使うことにしました。ARM CPUの演算機能の性能をフルに引き出してくれます。
#define ARM_MATH_CM4
#define __FPU_PRESENT 1U
#include <arm_math.h>
const int IN1 = 9;
const int IN2 = 6;
const int IN3 = 5;
const int buttonPin = 0;
boolean direct = false; // direction true=forward, false=backward
void toggle_direction() {
direct = !direct;
}
void setup() {
Serial.begin(115200);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
analogWrite(IN1, 0);
analogWrite(IN2, 0);
analogWrite(IN3, 0);
attachInterrupt(buttonPin, toggle_direction, FALLING);
pinMode(3, OUTPUT);
digitalWrite(3, HIGH);
sleep(3); // need to wait for stablilizing the system
}
void loop() {
int value = analogRead(A4)/50;
if (value < 1) value = 1;
Serial.println(value);
const int deg_step = 3;
static int degree = 0;
float rad1 = (float)(degree)*PI/180.0;
float rad2 = (float)(degree+120)*PI/180.0;
float rad3 = (float)(degree+240)*PI/180.0;
float f_val1 = (arm_sin_f32(rad1)+1.0)/2.;
float f_val2 = (arm_sin_f32(rad2)+1.0)/2.;
float f_val3 = (arm_sin_f32(rad3)+1.0)/2.;
int val1 = (int)(255.*f_val1);
int val2 = (int)(255.*f_val2);
int val3 = (int)(255.*f_val3);
Serial.println(String(val1) + "," + String(val2) + "," + String(val3));
analogWrite(IN1, val1);
analogWrite(IN2, val2);
analogWrite(IN3, val3);
if (direct) {
degree = degree + deg_step;
if (degree >= 360) degree = degree - 360;
} else {
degree = degree - deg_step;
if (degree <= -360) degree = degree + 360;
}
if (abs(degree) >= 360) degree = degree - 360;
/// Control speed by this delay
delay(value);
}
実際に動かしてみる
実際に動かしてみた様子は次の通りです。思った以上にうまくいきました。超低速だと少しカクカクしますが、ここは原理上しかたなさそうです。デューティ比をうまく使えばサーボモーターのような使い方もできるかも知れません。
使ってみた感想
ブラシレスモーターを回すにはESCを使うのが当たり前だと思っていたので、こんなに簡単に出来るとは思いませんでした。何事もやってみるものですね。SIN関数が高速に計算できるので出来た技なのかも知れません。実際に活用する場合は、この処理全体をサブコアにまかせてしまえば、通信処理やセンサー処理などと並行処理ができるので、SIN関数を使っていても安定的に動かせると思います。こういったところも SPRESENSE ならではですね。ブラシレスモーターはDCモーターに比べてスムーズに動かせるので、今後工作の幅が広がりそうです。