すごい人たちが作っている自作vvvfインバータなるものを自分も作ってみたいと思い、信号の生成から始めることにしました。自分の手元にあったマイコンは定番のArduinoではなく、ESP32だったのですが、最近はネット上にESP32に関する記事がたくさん出てきているうえ、扱いも似ているため先人の知恵を借りながらコードを書きました。
※初心者のためコードについては汚いものになります。ご了承ください。
記事とか、本とかでvvvfインバータの信号は正弦波と三角波をコンパレータで比較してPWM信号を作り出すとのことだったので正弦波と三角波を作れたらとりあえず動くんじゃねっていう甘い考えでやってます。
どうやってやるか
正弦波信号を生成しようとしたときに方法がいくつかあります。
①オペアンプを使って発振させる
②内蔵のDACを使う
③PWM信号として出力する
これらの方法を検討しましたが、①についてはプログラムによって周波数の変更ができないということで却下となりました。また、②のDACは精度も悪くなくこれでいける、って思っていたのですがなんとESP32にはDACが2個しかない(たぶん多いほうなんだろうけど)ってことでvvvfには4chほしいと思っていたのでこれもボツ。③のPWMで出力してローパスフィルターに通すことにしました。
PWMをどうやって使えばいいのか
よくある定番のコードといえばLチカ!で、これをうまいこと調整してやれば好きな周波数のPWM出せるじゃんと考えたことがあったんですが、まあdigitalWrite()の処理がなんか遅くて1kHzとかが限界だった気がします。(なんか自分はできなかっただけです。他の方はできてるかもしれません。)
調べるとLEDCというPWMのライブラリがあるらしく
int Pin = 27;
int freq = 300; // 周波数
int resolution = 8; // 分解能
int duty = 128; // 8bitなのでdutyは1~225で指定
void setup(){
ledcAttach(Pin, freq, resolution);
ledcWrite(Pin, duty);
}
こんな感じで周波数300Hz、Duty比50%のPWM信号が出せるようです。(2024/9/6時点)
(指定の仕方が最近変わったりしていて調べながら混乱してました。)
まだこれだけだとloop()関数になんかいろいろ書くことになって、vvvfインバータとしての周波数の変化を書くのが大変だと思ったので訳も分からないままですが、タイマ割り込みでduty比を変化させることにしました。
タイマ割り込み
タイマ割り込みはうまく解説できないので説明はほぼなし
(参考:https://docs.espressif.com/projects/arduino-esp32/en/latest/api/timer.html)
hw_timer_t *timer0 = NULL;
portMUX_TYPE timerMux0 = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t phase = 0;
void IRAM_ATTR onTimer0(){ // 割り込みする関数
if (phase >= 119){
phase = 0;
}
else{
phase++;
}
Serial.println(phase);
}
void setup(){
Serial.begin(115200);
timer0 = timerBegin(10000000); // 使用するタイマー周波数
timerAttachInterrupt(timer0, onTimer0); // 使用するタイマー、割り込みする関数
timerAlarm(timer0, 5000000, true, 0);
}
void loop(){
}
このコードのイメージとしては10000000を1周期とするタイマーを用意して、5000000数えたら割り込みを行うという感じです。(まちがってるかもしれない)
タイマ割り込みも調べたら素晴らしい記事が出てくるので参考にしてください。ちなみにタイマ割り込みも仕様(書き方)が最近変わった?とかで、古い記事だとうまくいかないかもしれません。
実行すると1秒に2回、関数が実行されてカウントアップされていくと思います。Arduino IDEのシリアルモニターから見れると思います。119まで行くと0に戻ります。
結局どんな感じになったのか
この二つを組み合わせて、PWM信号のduty比をタイマ割り込みによって変更するという手法を取りました。
#include <math.h>
#define TABLE_SIZE 120
int sinValuesU[TABLE_SIZE]; // 三相の正弦波用
int sinValuesV[TABLE_SIZE];
int sinValuesW[TABLE_SIZE];
hw_timer_t *timer0 = NULL; //使用するタイマー
hw_timer_t *timer1 = NULL;
portMUX_TYPE timerMux0 = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t waveV;
volatile uint32_t phase = 0; // タイマ割り込み時の位相。(0~119)で1周期
volatile uint32_t sin_max = 32; // 0~255 正弦波信号の最大電圧
const uint32_t freq=250000; // 波形生成のベース周波数
const uint8_t Pin_0 = 13; // 三角波用のピン
const uint8_t Pin_1 = 27; // #1正弦波用のピン
const uint8_t Pin_2 = 14; // #2
const uint8_t Pin_3 = 12; // #3
const uint8_t resol = 8; // PWM出力の分解能bit
volatile bool incre = true; // 三角波用の増減判定
void IRAM_ATTR onTimer0(){
// 三角波用
if (incre){
waveV=waveV + 4;
if(waveV >= 160){
incre = false;
}
}
else{
waveV=waveV - 4;
if(waveV <= 0){
incre = true;
}
}
ledcWrite(Pin_0, waveV);
}
void IRAM_ATTR onTimer1(){
// 正弦波用
if (phase >= 119){
phase = 0;
}
else{
phase=phase+1;
}
ledcWrite(Pin_1, sinValuesU[phase]);
ledcWrite(Pin_2, sinValuesV[phase]);
ledcWrite(Pin_3, sinValuesW[phase]);
}
void setup() {
Serial.begin(115200);
// 3相のsinの値用の配列の作成
for (int x = 0; x < TABLE_SIZE; x++){ //xは0~119
float valueU = (sin((3 * x - 90)* DEG_TO_RAD) / 2 + 0.5) * sin_max;
float valueV = (sin((3 * x + 30)* DEG_TO_RAD) / 2 + 0.5) * sin_max;
float valueW = (sin((3 * x + 150)* DEG_TO_RAD) / 2 + 0.5) * sin_max;
sinValuesU[x] = round(valueU);
sinValuesV[x] = round(valueV);
sinValuesW[x] = round(valueW);
Serial.println(round(valueU));
}
ledcAttach(Pin_0, freq, resol);
ledcAttach(Pin_1, freq, resol);
ledcAttach(Pin_2, freq, resol);
ledcAttach(Pin_3, freq, resol);
// timer0の設定
timer0 = timerBegin(10000000); // 使用するタイマー周波数
timerAttachInterrupt(timer0, onTimer0);
timerAlarm(timer0, 319, true, 0);
// timer1の設定
timer1 = timerBegin(10000000); // 使用するタイマー周波数
timerAttachInterrupt(timer1, onTimer1);
timerAlarm(timer1, 1500, true, 0);
}
void loop() {
}
こんな感じでとりあえず動きそうな感じで仕上げました。PWM信号の周波数は250kHzになってます。(よくわからんけどタイマ割り込みが動く限界に近かった。)タイマーは割り込み1回につき正弦波の位相を3度進めるようにして、三相の正弦波にするため、配列に格納する値が120度ずつずれたものになっています。また、その位相をもとにsetup()関数の中で計算して必要な値を配列に格納しています。(sinの計算をしないから動作が早くなるらしい?)このコードでは正弦波と三角波のタイマーを分けていますが、同期モードでモーターを回したいときは一つのタイマーで両方扱えばいいかななんてことを考えてます。
今はtimerAlarm()の中の値を変えて周波数を変えるということをしてます。(ゴミ設計)
コピペしてもまともに使えないと思います。このコードでは一度書き込むと一定の周波数で動くのでまだモーターは回せません。回したいです。
チャンネルの多いオシロスコープもってないので周波数を下げて肉眼で確認しました。
うまくいっていると勝手に信じてます。
三角波はこんな感じでした。三角波については調整のしづらい関数になっているのでそのうち変更したいです。(やっぱりゴミ設計)
結論
・こんなのでいいのかわからない
おわり
参考になったらうれしいです。
誤り、コードのミスなどあったら是非ご指摘ください。助かります。