クローズドループ正弦波駆動 その2 ーブラシレスモータ駆動への道6ー
前回は磁気エンコーダを用いたクローズドループ正弦波駆動で位置指定による回転追従動作を確認いたしました。
ここではクローズドループ正弦波駆動による回転速度を指定した回転動作を目指します。
目次
クローズドループ正弦波による回転
以下が理解しやすい図でした。
正弦波の振幅を上げて回転トルクをあげて回転位置をフィードバックして正弦波の周期を制御します。
矩形波駆動の時にも参考した書籍も上図と同じフローでの正弦波駆動が紹介されていました。
コレを参考に磁気エンコーダでロータ位置を検知しつつ正弦波の振幅を調整してみたのですがうまく回転できませんでした。
クローズドループ実験
ブルブルしちゃってる#ブラシレスモータ駆動への道 pic.twitter.com/0tvLZ1lrD5— HomeMadeGarbage (@H0meMadeGarbage) May 12, 2022
エンコーダから回転スピード算出して正弦波周期を変えたり、エンコーダ値を正弦波の位相に対応させてフィードバックなどいろいろ試しましたがダメでした。
勉強
どうしてもうまくできないのでリフレッシュと理解を深めるために書籍を購入しました。
こちらの書籍の正弦波駆動も正弦波の振幅調整で回転速度制御が紹介されていましたが、正弦波の印可が半波のみで実施されていました。
全波 vs 半波
正弦波の印可方法は全波が良いのか半波が良いのか比較してみました。
全波と半波の正弦波をブラシレスモータの3相に120°位相をずらして印可します。
全波印可の方が明らかにスムーズにまわっています。半波は矩形波駆動とあまり変わっていない印象を受けました。
ここでは全波印可を採用します。
検討
書籍の方法がうまくいかないため別手法を考えます。
書籍によって使用しているモータやエンコーダの仕様が異なるので仕方のないことかもしれませんが
今さらモータ学習用キットを購入するのもシャクなので、独自に方法を探ります。
前回の位置指定を応用して、位置の指定を周期的に可変させて回転動作を保つ方法を検討しました。
やーっとクローズドループ正弦波駆動 実現
可変抵抗で回転速度指定
高速になると正弦波の振幅上昇 (動画ではプリドライバに印可する疑似正弦波をテスタで観測)初期動作でオープンループで回転させてエンコーダのオフセット検出してからクローズドで回転させている。#ブラシレスモータ駆動への道 pic.twitter.com/vogW9Oas7j
— HomeMadeGarbage (@H0meMadeGarbage) May 25, 2022
可変抵抗で回転速度指定し、コントローラで回転位置をループで制御して回転させています。
正弦波の振幅は所望回転位置とエンコードの値の差でのP制御で自動で決定します。
高速になると差が大きくなるので必然的に振幅が上昇します。
起動時にオープンループで回転させてエンコーダのオフセット検出して、後のクローズドループ制御に利用しています。
上の動画ではエンコーダ1周の値を7分割して正弦波を生成している(使用しているブラシレスモータの極数が14 [7ペア])ため、負荷かかって周期がずれてもそのまま回転してエンコーダとの差で振幅上昇して過電流が流れる問題がありました。こちらは改善が必要です。
クローズドループ正弦波駆動による回転
先ほどの機構を改善してさらにBlynkアプリでBLEで回転を制御できるようにしました。
構成
構成は前回と同様です。
- ESP32 評価基板
- プリドライバIC IR2302
- NMOS 2SK4017
- ブートストラップ用ダイオード 1N4148W
- ブラシレスモータ
- 磁気エンコーダ AS5048
Arduinoコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
#define BLYNK_PRINT Serial #define BLYNK_USE_DIRECT_CONNECT #include <BlynkSimpleEsp32_BLE.h> #include <BLEDevice.h> #include <BLEServer.h> #include <AS5048A.h> char auth[] = "トークン"; AS5048A angleSensor(SS, false); #define periodPin 36 #define ampPin 39 #define uHin 25 #define vHin 26 #define wHin 27 int uPWMCH = 0; int vPWMCH = 1; int wPWMCH = 2; float uSine, vSine, wSine; int period = 5000, amp; float K = 10.0; int State = 0; unsigned long oldTime = 0, nowTime, rotTime; int rotData, rotDataIni; int BottunState = 0; int u = 0, v, w; int i = 0; int diff; int offset = 0; //Core0 void rotPosition(void *pvParameters) { for (;;){ disableCore0WDT(); rotData = int(angleSensor.getRawRotation() >> 5); } } //ヴァーチャルピンデータ受信 BLYNK_WRITE(V0) { BottunState = param.asInt(); } BLYNK_WRITE(V1) { period = param.asInt(); } BLYNK_WRITE(V2) { K = param.asFloat(); } void setup() { Serial.begin(115200); angleSensor.begin(); Blynk.setDeviceName("motor"); Blynk.begin(auth); ledcSetup(uPWMCH, 20000, 10); ledcAttachPin(uHin, uPWMCH); ledcWrite(uPWMCH, 0); ledcSetup(vPWMCH, 20000, 10); ledcAttachPin(vHin, vPWMCH); ledcWrite(vPWMCH, 0); ledcSetup(wPWMCH, 20000, 10); ledcAttachPin(wHin, wPWMCH); ledcWrite(wPWMCH, 0); //回転位置検出 タスク xTaskCreatePinnedToCore( rotPosition , "rotPosition" // A name just for humans , 8192 // This stack size can be checked & adjusted by reading the Stack Highwater , NULL , 3 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL , 0); } void loop() { Blynk.run(); if(State == 0){ for(int i = 0; i < 73; i++){ u = i; v = u + 73.0 * 2.0 / 3.0; w = u + 73.0 / 3.0; u = u % 73; v = v % 73; w = w % 73; uSine = 400 *(1.0 + sin(2.0 * PI / 73 * u)) / 2.0; vSine = 400 *(1.0 + sin(2.0 * PI / 73 * v)) / 2.0; wSine = 400 *(1.0 + sin(2.0 * PI / 73 * w)) / 2.0; ledcWrite(uPWMCH, int(uSine)); ledcWrite(vPWMCH, int(vSine)); ledcWrite(wPWMCH, int(wSine)); delayMicroseconds(1000); } delay(50); rotDataIni = rotData % 73; State = 1; } if(State == 1){ diff = i + rotDataIni - rotData % 73; if(diff < 0) diff += 73; if(diff >= 73) diff -= 73; amp = diff * K; amp = constrain(amp, 0, 1023); u = i + rotDataIni; v = u + 73.0 * 2.0 / 3.0; w = u + 73.0 / 3.0; u = u % 73; v = v % 73; w = w % 73; uSine = amp *(1.0 + sin(2.0 * PI / 73 * u)) / 2.0; vSine = amp *(1.0 + sin(2.0 * PI / 73 * v)) / 2.0; wSine = amp *(1.0 + sin(2.0 * PI / 73 * w)) / 2.0; ledcWrite(uPWMCH, int(uSine)); ledcWrite(vPWMCH, int(vSine)); ledcWrite(wPWMCH, int(wSine)); delayMicroseconds(period); if(BottunState == 0){ i++; }else{ i--; } if(i >= 73) i-=73; if(i < 0) i += 73; } } |
最新版のBlynkではBLEが使用できないので、以前のバージョンのBlynkレガシーを使用してBLE通信しています。
疑似正弦波を生成するためにドライバ駆動ピン(IO25~27)はledcWriteを用いて20kHz PWM出力します。分解能は10ビット(0~1023) (L. 72-80)。
モータの回転位置rotData をデュアルコアのcore0で検出しています。
1周の分解能を14bitから9bit (512)にしています (L. 46)。
可変抵抗でモータの位置period を0~511で指定します (L. 67)。
起動時に1周期分オープンループで回転させて位相のオフセットを導出します (L. 97-120)。
正弦波1周期はエンコーダの1周の分解能 512/7≒73としています(使用しているブラシレスモータのロータの極数は14 [7ペア])。
位相のオフセット導出後はクローズドループで回転し続けます (L. 122-158)。
オフセットを加味した指定の回転位置 iとエンコーダによる実際の位置の差diff を導出し (L. 123-126)、正弦波の振幅を係数Kをかけて算出します(L. 128, 129)。
エンコーダの値は7分割して使用しています(512/7≒73)。
これによって正弦波の周期からモータ回転がずれても過電流が流れることを回避しています。
U相はオフセットを加味した指定の回転位置 iとして、
V相とW相はそれぞれ120°づつ位相をずらします (L. 131-137)。
位相と振幅に従って正弦波を生成します (L. 140-146)。
ループのディレイ時間periodによって回転速度を制御します (L. 148)。
BottunStateによって正転/逆転を判断し。指定する回転位置 iのインクリメント/デクリメントを判断します (L. 150-157)。
Blynkアプリで、モータ回転方向BottunStateと回転速度period、正弦波振幅のPs制御係数KをBLEで受信します (L. 51-62) 。
おわりに
ついに若干変則的ではありますがクローズドループ正弦波駆動による回転制御を実現できました。
いろいろやってみた結論としては回転位置の検出及びフィードバックにはロータの磁極自体をセンスするホール素子の使用がベストと気づかされた。
しかしロータの磁極数や位置はモータによって異なるので、磁気エンコーダと後付け磁気センサによる方法は汎用性は高くなるとも言えます。
正弦波駆動によって低速回転をマスターできたので今後応用を考えたいと思います。