VVVFを作ってみよう(1)


初めまして、わかばと申します。

以後よろしくお願いいたします。



本講座では、初心者の方でもVVVFの音を出せるよう進めて参ります。

  1. VVVFの説明と、DDSの説明 (1) この記事
  2. DDSの説明 (2)
  3. Arduinoのコード用いたDDSの実践 (予定)
  4. VVVFのプログラムを用いて実践 (予定)

さて、皆さんはVVVFインバータというものをご存知でしょうか?

可変電圧可変周波数制御なんて言われていますね。

VVVFインバータ制御
https://ja.wikipedia.org/wiki/VVVF%E3%82%A4%E3%83%B3%E3%83%90%E3%83%BC%E3%82%BF%E5%88%B6%E5%BE%A1

そんなVVVFインバータですが

インターネットで検索をかけると装置の内容はとても詳しく紹介されています。

中には回路の作り方を紹介していらっしゃる記事も多く見かけます。(本当にありがたい)

しかしながら、自分が検索や実際に作る上で回路的には完成しそうだが

いざ肝心のプログラムが組めないと言うことがありました。

今回は、そういった回路は組めたがプログラムが組めない方の手助けになればと思いこの記事を書きました。

開発環境はArduino uno(AVRマイコンAtmega328pを想定しています)


素人なので間違い等あると思いますご了承ください。

また、この記事を参考にして発生した事故等私は責任を持てませんのでよろしくお願いいたします。


ボタン置いてみたかっただけで動きません

まず簡単にVVVFというものを紹介したいと思います。

VVVFというとよくこのような回路図を目にすると思います

この回路は3相交流を生成するために用いられます

原理を簡単に説明しますと、ハーフブリッジからそれぞれ位相の異なった正弦波を出力します。

3つのハーフブリッジをU、V、Wとしそれぞれの出力波形は

(全て出力50%時)となります。

出力50%というのは、三角波と正弦波の比率のことです

電気的にはまた変わってくると思われますが、今回はその点は無視しましょう。

正弦波が正の領域(1だけバイアスをかけています)ではオン時間が長く

正弦波が負の領域ではオン時間が短くなっているのがわかると思います。

さて、実際の波形は各相間電圧が重要なのでそれも示してみたいと思います

波形の条件は同じです

この波形をよく目にすることが多いのではないでしょうか?

ちなみにですがこれは9パルスモードです

さて

肝心なのは、この波形の生成方法です。

VVVFでは直流から交流を生成するのにPWMを用います。

PWM : pulse width modulation

https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%AB%E3%82%B9%E5%B9%85%E5%A4%89%E8%AA%BF

この方法では、信号波をノコギリ波や三角波で比較し大きければ1、小さければ0を出力します。

下図を参考にしてください

これは、出力する正弦波が0.5になる点(本来は0の点)と三角波が合わさっています。これが同期モードです。

非同期モードでは、0クロスポイントが合っていません。

非同期モードは主に出力が小さい(0~50%くらいまでの間)で使用します。

ですが、汎用のインバーターでは全領域(モーターが0~60Hzとか)で非同期モードを使用していることもあります。

 

いかかでしょうか?

少々疎い説明ですが、VVVFというものの説明はこのくらいにしたいと思います

ここからは、VVVFのやり方を説明していきたいと思います。

DDS方式

ファンクションジェネレータやmidi音源などに用いられるDDS方式をご存知でしょうか?

DDS方式 :Direct Digital Synthesizer

https://ja.wikipedia.org/wiki/%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%83%BB%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E3%83%BB%E3%82%B7%E3%83%B3%E3%82%BB%E3%82%B5%E3%82%A4%E3%82%B6

これは、マイコンの内部にあらかじめ波形データを書き込み

内部のプログラムで構成されたタイマとそれに対応する値を出力するというものです。

言葉だけではイメージしずらいと思うので画像を用意しました。

仮に、配列に正弦波の値が格納されていたとします

int sin_table[256] = [/*SINの値が入っている*/];

/*

for(int i = 0;i<=255;i++){

    sin_table[i] = sin(2*pi*i/255);

}

正弦波の1つ分の波形を256分割して配列にいれてます

なぜ2^8(=256)か後述します

*/

そして、オペレーターはsin_table[]の配列の値を指定する変数とします。

unsigned int  operator_sin = 0;

//負の数になるのは嫌なのでアンサインドで

さてこのように定義してこんなプログラムを作ってみたとします

(細かいところは許してください)

#inculde(“🔰”)

void main(){

int sin_table[256] = [/*SINの値が入っている*/];

unsigned int  operator_sin = 0;

for(int i  = 0; i <= 255; i++ ){

OutPut(sin_table[i]);

delay_ms(1000/256);

}

}

OutPut関数は何かしらの手法で外部に出力をしているものとします。

また、delay_ms(時間)は指定時間mS(ミリ秒=1/1000秒)だけプログラムを一時停止させる関数とします

さて、これはどんな動きをするかわかるでしょうか?

答えは、1Hzの正弦波を出力するプログラムです。

今回、波形サンプルが256個あるので1秒(1000mS)ですべて出し切らねばなりません。そのためdelay_msの待ち時間を1000/256としています。

forループから抜けるとオペレーターは0に戻るのでまた0から正弦波の出力が開始されます。

delay_ms(speed) としspeed変数を小さくすれば周波数が大きくなりますね。

また、波形の配列の中身を変えれば正弦波以外の波形も出力できますね。


さて

DDS方式によって任意の波形を出力することができるのはなんとなくわかっていただけたと思います。

ですが、このプログラムではmainループを占領してしまい、ほかの動作が行うことができません。

そこで割り込みというものを使用します。

ここから少し複雑かつマニアックな処理が増えてきます、できるだけわかりやすく書こうと思いますが、どうしてもハードウェアの部分も出できてしまうのでご容赦ください。


ワリコミショリ

割り込み処理とは何でしょうか?

割り込み処理

https://ja.wikipedia.org/wiki/%E5%89%B2%E3%82%8A%E8%BE%BC%E3%81%BF_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)

簡単に言うと、プログラムの途中で違うプログラムを実行するものです。

さてさて、その割り込み処理を用いて次のようなプログラムを作りました

#inculde(“🔰”)

int sin_table[256] = [/*SINの値が入っている*/];

unsigned int  operator_sin = 0;

unsigned int  operator_sin_adder = 0;

void main(){

interrupt_set(dds_output,10000);

while(1){

operator_sin_adder = 256*1/10000;

}

}

interrupt dds_output(){

operator_sin += operator_sin_adder;

operator_sin &= 255;

OutPut(sin_table[operator_sin]);

}

いくつか新しいものが追加されましたね。

それぞれ細かくみていきましょう。

interrupt_set(dds_output,10000);

これは、dds_outputと言う関数を10kHzで起動するようセットする関数としています。ちなみにこの10kHzをサンプリング周波数とします。

つまり、1秒間に10,000回割り込み処理が行われます。

なぜ10kHzなのかは、自分の経験からです。


と、いってしまうと味気ないのでちょっと説明

この周波数が高くなると割り込みが多く入ることになってしまい、マイコンの計算時間が少なくなってしまいます。

Arduino unoは当然シングルコアなので一つの処理しか行うことができません。その状況では割り込み処理が入るとメインループの処理を一旦停止し割り込み処理を実行します。

この時に、プログラムの実行時間が割り込みの周期(今回は100us)より大きくなってしまうとArduino unoは割り込み処理から抜けることができずフリーズしてしまいます。

また、割り込み処理の実行時間は、つねに一定(プログラムの行数と比例している)なので割り込み周波数を高くすると処理が占める割合が高くなり、メインループの処理が実行できなくなるなどの問題があります。

また、周波数が低いとサンプリング定理より出力できる周波数が小さくなってしまいます。

サンプリング定理

https://ja.wikipedia.org/wiki/%E6%A8%99%E6%9C%AC%E5%8C%96%E5%AE%9A%E7%90%86

このような点を考慮し、実際の動作とも交えて最適なのは10kHzとしました。

(DDSの場合サンプリング定理だけでは説明できないように思いますが、細かい点は考えないほうが楽です。)


operator_sin_adder = 256*1/10000;

これは、オペレーターの加算を行う変数です。

この値の計算は少し特殊です。

上の式のようになっております。

実は先ほどの割り込みを用いないときと考え方は変わっておりません。

この計算をすると、1秒間で正弦波のテーブルが出力したい周波数の回数だけ回ります。

回る

オペレーターが255を超えること

つまり位相が0に戻ること

今回は1Hzを出力したいので、1で計算をしています。

operator_sin += operator_sin_adder;

operator_sin &= 255;

OutPut(sin_table[operator_sin]);

オペレーターに計算された加算値を加算します。これによって位相が進みます。

その次の行では、オペレーターが正弦波のテーブルの最大値を超えないための処理です。本来であれば

operator_sin %= 255;

でもいいです。

この剰余演算は非常に重く、32bitマイコンでも計算には相当な時間がかかります。そのため、ここではちょうどすべてのビットが1になるよう工夫しています。(wikiのビット演算による効率化を参照してみて下さい)

剰余演算

https://ja.wikipedia.org/wiki/%E5%89%B0%E4%BD%99%E6%BC%94%E7%AE%97


ここで気が付いた方がいるかもしれませんが

operator_sin_adder = 256*1/10000;

この計算、1未満になってしまいます。

unsigned int  operator_sin_adder = 0;

定義がint型なのでコンパイラが警告をだし、結果は0が代入され正弦波どころか何も出力されません。

では

double  operator_sin_adder = 0;

とすればいいのでしょうか?

答えはNOです。

double型 やfloat型のような浮動小数点型の演算は非常に重く専用の演算回路をもったマイコンでなければ今回の処理には向きません。

(あくまでArduino unoを使う場合の話です。)

そこで、固定小数点というものを用います。

固定小数点

https://ja.wikipedia.org/wiki/%E5%9B%BA%E5%AE%9A%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

浮動小数点

https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

固定小数点とは、簡単にいうと少数にならないように工夫しておくことです。

1/3 = 0.333…

(1*10) / 3=3.333…

1割る3は0.3..となってしまい値が得られません

ですが

10割る3なら3.3…と少なくとも3という値がえられます

プログラムでこの3が整数の3ではなく、0.3の3であるという区別をしなければならないので面倒ですが、floatやdoubleを使うよりはずっと楽です。


さて、例では10をかけましたが実際はさらに計算を高速かするためにこうします。

#inculde(“🔰”)

int sin_table[256] = [/*SINの値が入っている*/];

unsigned int  operator_sin = 0;

unsigned int  operator_sin_adder = 0;

void main(){

interrupt_set(dds_output,10000);

while(1){

operator_sin_adder = (256*1<<3)/10000;

}

}

interrupt dds_output(){

operator_sin += operator_sin_adder;

operator_sin &= 2047;//255;

OutPut(sin_table[operator_sin>>3]);

}

また、よくわからないものが増えましたね。それぞれ見ていきましょう。

>>3 

&

この演算子見たことはあるんじゃないでしょうか?

ビット演算

https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88%E6%BC%94%E7%AE%97

ということで<<は、ビットシフトです。

なぜ急にビットシフトを用いたのかというと、実はビットシフトは

data = data <<1;

data =data*2;

この2つは同じ計算結果になります。

一般に

data = data << n

data = data * 2^n

こう表すことができます。

なぜか気になる方は、この計算を2進数で考えると面白いですよ。

ちなみに

data = data >>1;

data =data/2^n;

この2つも同じ計算結果になります。


ちょっとだけ注意点を

ビットシフト演算は確かに高速な演算が可能な場合がありますが、独特なクセがあります。

まず、必ずしも高速とは限らない

マイコンの型によるのですが、バレルシフタを搭載していないマイコンではシフトの回数だけ計算時間がかかるので時間が伸びる可能性があります。

(減算は高速かも?)

バレルシフタ

https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%AC%E3%83%AB%E3%82%B7%E3%83%95%E3%82%BF

Arduino uno に搭載されているAtmega328pにはバレルシフタが搭載されておらず<<の数だけクロックが消費されています。

<<3くらいならいいのですが、それ以上になってくるとマイコン内の乗算器で計算したほうが早くなってしまいます。

(atmega328の乗算器は1clockで8bit*8bitを計算可能_とても優秀)

次に、正負型の場合注意が必要

正負の値をもつ型は先頭ビットで正負を判断しています。<<をしたとき先頭ビットに1が入ってしまうと値が変わるうえ正負も反転し大変なことになります。(なりました)

同様に>>でも同じ問題が発生します。

この手のミスはコンパイラはあまり警告してくれないのでなかなか気づきにくいです。正負のある値をビットシフトする際は一旦正の数に直して計算することをおすすめします。


さて、話を戻しましてもう一度プログラムをみてみましょう

#inculde(“🔰”)

int sin_table[256] = [/*SINの値が入っている*/];

unsigned int  operator_sin = 0;

unsigned int  operator_sin_adder = 0;

void main(){

interrupt_set(dds_output,10000);

while(1){

operator_sin_adder = (256*1<<3)/10000;

}

}

interrupt dds_output(){

operator_sin += operator_sin_adder;

operator_sin &= 2047;//ここの説明は後述します。ヒントはビットシフト

OutPut(sin_table[operator_sin>>3]);

}

今回は、固定小数点3桁にしました。×8ですね。

operator_sin_adder = (256*1<<3)/10000;

これで計算結果は…あれ、まだ1になりませんね。

でも上述通りこれ以上ビットシフトを多くはしたくありません。

さぁ、どうしましょうか…

というところで今回はここまで

次回は、波形テーブルを大きくする手法をご紹介したいと思います。

ここまでお読みいただきありがとうございます。

不明点等ございましたら、コメントの方にお願いします。

2019年11月12日 第2回を公開しました!

VVVFを作ってみよう(2)

VVVFを作ってみよう(1)” への1件のフィードバック

  1. うなぎ 返信する

    電子工作初心者です。
    VVVFの実装プログラムについて、丁寧に紹介されている本やサイトってあまり無いので、とても参考・勉強になります。
    第2回以降の掲載も楽しみにしてます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください