VVVFインバーターの惰行時の制御

フィードバックなしに空回しモーターの惰行時の回転速度を推定する方法を紹介します。

前置き

こんにちは。自作のVVVFインバーターでモーターを回すのは楽しいですよね。
f:id:yuppi5:20170901102501j:plain

ところで、電車用のインバーターでは回転中に電圧を0にして惰行だこうする制御があります。普通のモーター用のインバーターでは回転中に電圧が0になることはないので、惰行は電車用のインバーター特有の制御です。

私が作っているインバーターでは電車のインバーターの再現をするのが目的なので、当然惰行もします。加減速はフィードバック無しでもある程度平気(V/f一定制御)ですが、惰行はフィードバックが無いと厳しいです。インバーター周波数と実際のモーターの回転数がかけ離れた状態で電圧を上げると、過大な電流が流れます。スイッチング素子が壊れるかもしれません。惰行をする場合、フィードバックは必須です。

しかし、私のインバーターでは負荷をかけてモーターを回すことはなく、空回しをさせています。よって計算することでフィードバックなしにモーターの回転数を推定できると考えました。

モーターを惰性で回して回転数を測定する

f:id:yuppi5:20170901100753j:plain
フォトインタラプターマイコンで測定しました。マイコンはLPC1114を使用しましたが、このマイコンのGPIOにはヒステリシス機能がついていました。この機能を有効にして外部割り込みでうまく回転数を測定しました。
回転数のグラフは以下のようになりました。横軸が時間、縦軸が回転数です。
f:id:yuppi5:20170901213728p:plain
インバーター周波数を180Hzにして落ち着いたあとに惰行を開始しました。この瞬間のモーターの回転数は約90rpsですが、都合上2倍にして表示しています。
180Hz(90rps)から、指数関数のような形で回転数が下がっていき、28秒あたりで回転が止まりました。
この線は4回測定した中央値ですが、線がガタガタですね。200回くらい試行しないといけなかったでしょうか。

曲線を近似する

上のようなグラフを曲線で近似して、回転数推定に必要なパラメーターを算出します。空回しなので、急に止まったりすることはなく、自然な減速をしていくはずです。結果だけ見たい方は近似した曲線と必要なパラメーターを算出するスクリプトに飛んでください。値を突っ込むと必要なパラメーターが出てくるスクリプトがあります。

微分方程式を立てる

モーターの構造を単純化します。
f:id:yuppi5:20170903003005p:plain
回転数を {\displaystyle f} と置き、

  • 摩擦抵抗 { \displaystyle = -\mu N = -T } (定数なのでまとめて { \displaystyle T} と置いた)
  • 回転周波数に比例する空気抵抗 { \displaystyle = -kf } ( { \displaystyle k} は定数)

があると仮定します。
回転周波数の2乗に比例する抵抗は面倒なので計算していません。
空気抵抗は、モーターの外扇の羽や回転子から生まれると考えます。

角加速度 { \displaystyle \frac{df}{dt} } は上記2つの抵抗の合計になります:
\[
\frac{df}{dt} = - kf - T
\]
特定したいのは、 { \displaystyle T}{ \displaystyle k} の値です。
私のインバーターは角加速度でモーターの回転数を増減させているので、この2つの値がわかれば十分です。
[追記]本来の運動方程式{ \displaystyle m \frac{df}{dt} = - kf - T } ( { \displaystyle m} は質量)と質量が必要ですが、 { \displaystyle k}{ \displaystyle T}{ \displaystyle m} ですでに除算されたことにすれば問題ないです。完全に質量のことを忘れていました。

微分方程式を解く・数値解析

※対数の底は { \displaystyle e}とします。
{ \displaystyle \frac{df}{dt} = - kf - T } を変形します:
\[
\frac{df}{dt} = - kf - T \\
\frac{ 1 }{ f + \frac{T}{k} } \frac{df}{dt} = -k
\]
両辺を { \displaystyle t }積分します:
\[
\int \frac{ 1 }{ f + \frac{T}{k} } \frac{df}{dt} dt = - \int k dt \\
\int \frac{ 1 }{ f + \frac{T}{k} } df = - \int k dt \\
\log \left| f + \frac{T}{k} \right| = - k t + C'
\]

C'は積分定数

\[
f + \frac{T}{k} = \pm e^{-kt + C'} \\
f + \frac{T}{k} = C e^{-kt} \\
(C = \pm e^{C'}) \\
f = C e^{-kt} - \frac{T}{k} \ ...\ (1)
\]
ここで初期回転数 { \displaystyle f_0 } を代入して { \displaystyle C } を消します。
回転数 { \displaystyle f }{ \displaystyle t=0 } のとき { \displaystyle f_0 } だから:
\[
f_0 = - \frac{T}{k} + C \\
C = f_0 + \frac{T}{k} \ ...\ (2)
\]
式(1)に式(2)を代入して:
\[
f(t) = \left( f_0 + \frac{T}{k} \right) e^{-kt} - \frac{T}{k}
\]
とりあえず式は解けました。
ここから変形をして { \displaystyle T } を消します。
モーターの回転が止まった { \displaystyle (f(t)=0)} ときの時間を { \displaystyle t_{end} } と置くと
\[
0 = \left( f_0 + \frac{T}{k} \right) e^{-k t_{end} } - \frac{T}{k} \\
\frac{T}{k} = \frac{ f_0 e^{-k t_{end}} }{ 1 - e^{-k t_{end}} } \ ...\ (3)
\]
{ \displaystyle T } は式(3)に { \displaystyle k } を掛けると算出できます:
\[
T = \frac{ f_0 e^{-k t_{end}} }{ 1 - e^{-k t_{end}} } k \ ...\ (4)
\]

式(1)に式(3)を代入:
\[
f(t) = \frac{ f_0 }{ 1 - e^{-k t_{end}} } ( e^{-k t} - e^{-k t_{end}} )
\]
ここから、適当な実測点 { \displaystyle t_1, f_1(=f(t_1))} を取って { \displaystyle k } に関する公式を出しますが…
\[
f_1 = \frac{ f_0 }{ 1 - e^{-k t_{end}} } ( e^{-k t_1} - e^{-k t_{end}} ) \\
0 = \frac{f_0}{f_1} \frac{ e^{-k t_1} - e^{-k t_{end}} }{ 1 - e^{-k t_{end}} } - 1 \ ...\ (5)
\]
式(5)が { \displaystyle 0 } になるような { \displaystyle k } を出せばいいんですが、これを解析的に解く({ \displaystyle k=□} の形にする)ことは不可能です。
{ \displaystyle k } に適当な値を当てはめて、数値的に解きます。

式(5)を頑張って変形すると:
\[
k_{n+1} = - \frac{1}{t_1} \log \left( \frac{ f_1 }{ f_0 } (1 - e^{-k_n t_{end}}) + e^{-k_n t_{end}} \right)
\]
初期値 { \displaystyle k_0 } (適当に 0.1 を代入しています)を入れて、出てきた { \displaystyle k_1 } を右辺に代入し計算して { \displaystyle k_2 } を出します。これを右辺に代入し計算して { \displaystyle k_3 } を出します。
という風に、何回か繰り返して { \displaystyle k } の値を数値的に算出します。15回ほど繰り返すといい具合に収束します。
この数値計算プログラムは次の近似した曲線と必要なパラメーターを算出するスクリプトに置いておきます。

近似した曲線と必要なパラメーターを算出するスクリプト

必要な値:
f:id:yuppi5:20170902215840p:plain

  • { \displaystyle f_0 } : 初期回転数
  • { \displaystyle t_1} : ある点(最初と最後以外の点・真ん中の少し左側あたりが良い)
  • { \displaystyle f_1} : { \displaystyle t_1}のときの回転数
  • { \displaystyle t_{end} } : 回転が止まったときの時間

{ \displaystyle k,\ T } は:

  • { \displaystyle k_{n+1} = - \frac{1}{t_1} \log \left( \frac{ f_1 }{ f_0 } (1 - e^{-k_n t_{end}}) + e^{-k_n t_{end}} \right) }
  • { \displaystyle T = \frac{ f_0 e^{-k t_{end}} }{ 1 - e^{-k t_{end}} } k }

近似する曲線の式は:

  • { \displaystyle f(t) = \frac{ f_0 }{ 1 - e^{-k t_{end}} } ( e^{-k t} - e^{-k t_{end}} ) }

微分方程式を解く・数値解析」より、{ \displaystyle k }数値計算で求めます:
{ \displaystyle f_0 } :
{ \displaystyle t_1} :
{ \displaystyle f_1} :
{ \displaystyle t_{end} }

{ \displaystyle k} :
{ \displaystyle T } :
入っている値は、今回測定したものです。

測定した回転数から必要なパラメーターを求める

近似した曲線と必要なパラメーターを算出するスクリプトスクリプトを使います。
もう値がスクリプトに入力されていますが、改めて示します。これが測定した値です:

  • { \displaystyle f_0 = 180 }
  • { \displaystyle t_1 = 12.4 }
  • { \displaystyle f_1 = 41.25 }
  • { \displaystyle t_{end} = 28 }

計算すると、パラメーターは次のようになりました:

  • { \displaystyle k = 0.105929 }
  • { \displaystyle T = 1.03544 }

グラフを書きます:
f:id:yuppi5:20170902221026p:plain
うまくフィットしました。高回転時ほど回転数の増減が激しいので、 { \displaystyle f_1 }{ \displaystyle f_0 } に近いところ、半分くらいのところを選ぶといいと思います。{ \displaystyle f_1 = 41.25 } は低すぎたかもしれません。

mbedのオンラインコンパイラでLPC1114のGPIOをレジスタで叩くときの注意点

LPC1114マイコンをライブラリの DigitalOut 等を使うことなく、mbedのオンラインコンパイラ上でレジスタを叩いて高速なGPIOを実現するさいの注意点です。レジスタを叩くことについては、以下のリンク先に詳しい解析が載っています。

monoist.atmarkit.co.jp

こちらのページのように LPC_GPIO0->DIR で GPIO ピンの入出力の方向を設定できますが、設定が反映されないピンがあります。
反映されないピンは以下の通りでした。

  • 3 (P0_10 / SWCLK)
  • 4 (P0_11 / R)
  • 9 (P1_0 / R)
  • 10 (P1_1 / R)
  • 11 (P1_2 / R)
  • 12 (P1_3 / SWDIO)
  • 23 (P0_0 / RESET)

ピンアサイン参考:Beginning LPC1114FN28 with Arch | Mbed

ピンアサインを見ると、SWCLK、R、SWDIO、RESET の機能を有するピンだけ入出力設定が反映されないことがわかります。
また、リンク先のピンアサイン表の「PIOx_y」の左側に何かが書かれているピンだけ設定が反映されない、とも言えます。

ピンの設定について調べていくと、以下のページが見つかりました。

digimono-boys.blogspot.jp
white-clouds-in-sky.blogspot.jp

どうやら、ピンアサイン表の一番左側にある機能がデフォルトの設定になるようです。

  • 例えば、ピン3はデフォルトで SWCLK が有効になっているので、入出力の方向を設定(LPC_GPIO0->DIR に代入する)しても GPIO の機能にはなりません。
  • また、ピン1は PIO0_8 が一番左側にあるので、このピンのデフォルトの設定は GPIO であることもわかります。

この GPIO や R、AD変換 などといった機能を設定するレジスタがあります。


以下は、ピン23以外(理由は後述)の全てのピンでLチカができるプログラムです。

#include "mbed.h"
int main(){
    LPC_IOCON->SWCLK_PIO0_10 = 0xd1;
    LPC_IOCON->R_PIO0_11 = 0xd1;
    LPC_IOCON->R_PIO1_0 = 0xd1;
    LPC_IOCON->R_PIO1_1 = 0xd1;
    LPC_IOCON->R_PIO1_2 = 0xd1;
    LPC_IOCON->SWDIO_PIO1_3 = 0xd1;
    //LPC_IOCON->RESET_PIO0_0 = 0xd1;
    
    LPC_GPIO0->DIR = 0xFFFF;
    LPC_GPIO1->DIR = 0xFFFF;
    while(1){
        LPC_GPIO0->DATA = 0xFFFF;
        LPC_GPIO1->DATA = 0xFFFF;
        wait(0.5);
        LPC_GPIO0->DATA = 0x0000;
        LPC_GPIO1->DATA = 0x0000;
        wait(0.5);
    }
}

問題のピンは、以下の行で GPIO モードに設定しています。設定値の 0xd1 はたまたま全てのピンで共通の値でした。

    LPC_IOCON->SWCLK_PIO0_10 = 0xd1;
    LPC_IOCON->R_PIO0_11 = 0xd1;
    LPC_IOCON->R_PIO1_0 = 0xd1;
    LPC_IOCON->R_PIO1_1 = 0xd1;
    LPC_IOCON->R_PIO1_2 = 0xd1;
    LPC_IOCON->SWDIO_PIO1_3 = 0xd1;
    //LPC_IOCON->RESET_PIO0_0 = 0xd1;

設定の値はこちらのユーザーマニュアル(https://www.nxp.com/documents/user_manual/UM10398.pdf)で見ることができます(「SWCLK_PIO0_10」等でページ内検索を掛けると該当の部分が出てきます)。


ピン23(P0_0) の設定はコメントアウトしてあります。筆者はISPモードで bin を書き込む LPCISP というソフト(mbed LPC1114でLチカしてみた(2) - しなぷすのハード製作記)を使っています。このソフトは自動でリセットを掛けてISPモードに突入して書き込みを行ってくれます。そのため、ピン23を GPIO モードに設定するとマイコンの電源が入った直後にこのピンが GPIO として働いてしまい、自動リセットは掛かりません。

どうしてもピン23を GPIO として使いたい、ピン23を GPIO に設定してしまってプログラムを新しく書き込めない…といった状況なら、手動でリセットを掛けて書き込みをする方法で状況を解決します。

www.nxp-lpc.comこちらのサイトの「ISP」の欄の要約になりますが、

  1. 電源を落とす
  2. ピン23(RESET)、ピン24(ISPエントリーピン) を Low にして電源を入れる
  3. ピン23を High にする

という手順のあと、LPCISPを使って書き込みをします。電源を落とさなければならないので、面倒な手順を踏むことになります。

TmBoxのループ再生を止めるブックマークレット

タイトルの通りです。

javascript:TARGET.addEventListener('ended', function(){TARGET.pause();}, false);

本当は removeEventListener でループ再生イベントそのものを消したかったのですが、無名関数が登録されていたためどうにもなりませんでした。どうにか方法を調べていましたが、うまくいかなかったためイベントを消すのはあきらめました。妥協案としてイベントを消す代わりに一時停止をイベントのあとに挿入しました。
ループ再生イベントは

  1. 一時停止
  2. 再生
  3. repeat数(再生回数)を1増やす

となっているので、この妥協案ではrepeat数が1つ余分に増えてしまいます。そこは妥協ということです。

TmBoxの視聴履歴、お気に入り履歴のページ移動をするブックマークレット

タイトルの通りです。

  • 新しい方へ移動
javascript:void(function(isNext,l){ var pagenum = ~~l.search.substr(6); if( isNext ){ if( l.search === "" ){ l.href += "?page=2"; }else{ l.href = l.protocol + "//" + l.hostname + l.pathname + "?page=" + ( pagenum + 1 ); } }else{ if( pagenum > 1 ){ l.href = l.protocol + "//" + l.hostname + l.pathname + "?page=" + ( pagenum - 1 ); } } })(false,location);
  • 古い方へ移動
javascript:void(function(isNext,l){ var pagenum = ~~l.search.substr(6); if( isNext ){ if( l.search === "" ){ l.href += "?page=2"; }else{ l.href = l.protocol + "//" + l.hostname + l.pathname + "?page=" + ( pagenum + 1 ); } }else{ if( pagenum > 1 ){ l.href = l.protocol + "//" + l.hostname + l.pathname + "?page=" + ( pagenum - 1 ); } } })(true,location);


実は1箇所以外どちらも同じ共通のソースです。最適化したい方は各自でどうぞ。

HSPで擬似構造体

HSPというのは小規模なソフトを作るときに非常に使いやすいんですが、それ以上になってくるとソースがごちゃごちゃになってきますよね。いや、ソースの書き方が汚いだけかもしれませんが。
構造体が使えればソースがスッキリしそうですね。
早速見て行きましょう。


宣言するときはこちら。

#define volume 0
#define expression 1
dim midi,2

参照するときはこちら。

mes "volume : " + midi.volume
mes "expression : " + midi.expression

HSPには、配列を参照する方法が2つあります。

dim n,2,2
mes n(0,1)
mes n.0.1

2行目の n(0,1) はHSP3で追加された配列参照の方法です。
3行目の n.0.1 はHSP2からある方法です。
今回の擬似構造体では3行目の配列参照の方法を使っています。


例えば。

dim n,2
mes n.0

この場合で言えば、0の部分を定数に置き換えてしまえばいいわけです。

#define first 0
#define second 1
dim n,2
mes n.first

構造体っぽいですか?っぽいですね。「っぽい」っていうのは見た目に関わってくるので割と大事。

これをC言語で書くとこうなります。

struct{
    int first;
    int second;
}n;

わお簡潔。名前に意味はありません。

2次元配列でも同じことをすればいいわけです。


もう1つ例をあげると、時間の情報を格納する変数もこれを使えばスッキリまとまります。
こんな感じ。

#define sec 0
#define min 1
#define hour 2
dim time,3
mes time.hour + "時" + time.min + "分" + time.sec + "秒"

お好みで年月日も追加できます。



お疲れ様でした。

C言語のHelloWorldについて

よく入門サイトで見かけるHelloWorldはこれ。

#include <stdio.h>
int main(){
    printf("HelloWorld");
    return 0;
}

でもこれ、実行したらウィンドウが開いて一瞬で閉じられてしまうんですよ。
初心者の私はここでつまずきました。(諦めそうになりました)
そこでこれを入れましょう。

getchar();

このgetcharという関数はstdio.hで定義されているのでまた新しいファイルをインクルードする必要はありません。楽チン。
最終的にソースはこんなカタチになるんですね。

#include <stdio.h>
int main(){
    printf("HelloWorld");
    getchar();
    return 0;
}

これを実行するとウィンドウが開いて HelloWorld が表示されます。さっきと違うのはここでウィンドウが閉じられないところです。キーボードのボタン(どれでもいい)を押すと return 0 が実行されウィンドウが閉じられます( = プログラムを終了する)。

getchar という関数はその場でプログラムの進行を止めてくれる便利な関数です。と言うと簡単なんですが、本当は違います。getchar はキーボードのボタンが押されるまでプログラムの実行を止め、押されたならばそのキーボードの値を返すという2つの役割を持つ関数です。ここではプログラムの進行を止めるためだけに使っています。

C言語 入門」で検索をかけて出てきたサイトのソースで、printf(~) で計算結果を表示させて、直後に return文が書かれている、というのをよく見かけます。よく見かけるというか getchar 挟んでいるのを私は見たことがないです。
Visual Studio Express 2012 を私は使っていますが、何でしょうか、環境によって return文の前で止まったりするんですかね?よくわかりません。

[追記]

デバッグなしで開始」がありました。こうすることで「続行するには何かキーを押してください . . . 」が末尾に挿入されてキーボード入力待ちになるんですね。getchar いらず。
でもF5キーを押してパッと実行させたいのでやはり getchar は有効ではないかと思います。Releaseビルドで特に有効ですね。

C言語で構造体の動的確保

自分用のメモみたいなものです。

#include <stdio.h>
#include <stdlib.h>
typedef struct{
	int x;
	int y;
}STR;
void main(){
	STR *s;
	s = (STR*)malloc(sizeof(STR) * 10);// 10は確保したい1次元配列の数
	s[1].x = 15;
	s[9].y = 990;
	printf("%d,%d",s[1].x,s[9].y);
	getchar();
	free(s);
}