2001/05/04 【ソフトウエア編TOPに戻る】
106のページで、PWMでのDCモーター制御についてご紹介しました。DCモーターを自在に操るためには不可欠なテクニックです。よーし!AKI-H8 3048/F CPUボードを使ってロボットの関節制御をバリバリやってやるぜ!と意気込むのも良いのですが、ちょっと待った。H8-3048/Fでは、PWM出力が最大で5チャンネルまでしか取れません...。片脚だけで4個のモーターがあるし、両脚と腰まで入れると全部で12個もモーターがあります。これでは全然足りません。あーどうしよう!外部にロジック回路を追加して、ハード的にマルチプレクサして多チャンネルに振るか?
などとも考えたのですが、外部に回路を追加しては面倒なので、結局ソフトウエアの力を借りることにしました。で、思いついたのがタイマー割込みを利用したPWMです。
107−1.タイマー割込みPWMの説明
タイマー割込みを使ったPWMとは、だいたい以下のように動作させます。

先ずITUタイマーによって、一定期間ごとに割込みを発生させます。で、割込み処理内で、割込みが発生した回数をカウントします。ただし、0(ゼロ)から10くらいの間で繰り返し数えます。上図では0〜7で数えています。カウント変数cntが0(ゼロ)の場合、任意のPIO出力のBITを1にします。そして、何度目かの割込みでPIOのBITを0(ゼロ)に戻します。上図ではcntが4の時に0に戻しています。またcntが0(ゼロ)に更新されると同じことを繰り返します。
上図のPIO出力波形を見ると、デューティー比0.5のパルス波形になっています。1になったBITを0(ゼロ)に戻すタイミング(cntの値)を早めたり、遅くしたりすると、デューティー比を可変することが出来ます。この方法を使えば、割り込み処理内で複数のPIO出力を制御することによって、複数のPWM出力を得ることができます。
割込み処理内では、主にcnt変数によってPWM出力を得るためのPIOの制御を行ないます。上図ではオレンジ色の期間が、その処理に費やされます。
通常の処理内では、どのcntの値でPIOを0(ゼロ)に戻すか、といった設定を行い、またその他諸々のロボット制御、センサー検知などの処理を行ないます。上図ではグリーンの期間が、その処理に費やされます。
割込みを発生させる周期は色々と試す必要がありますが、約5mSに設定します。オレンジ色の割込み処理内では、条件に照らしてPIOを1にしたり0(ゼロ)にしたりといった簡単な処理しか行なわないため、普通ならばグリーンの期間を充分に確保することができます。しかし、PWM出力を多く取ろうとして、制御するPIOの数が多くなってくると、だんだんオレンジ色の期間が増えて、グリーンの期間を圧迫してくる恐れもあります。あまりグリーンが圧迫されると正常なロボット制御は難しくなるでしょう。その場合は、割込みの周期を長くするか、効率の良いプログラムを再検討することになると思いますが、取り合えずこれで行ってみますか!
107−2.テストプログラム
以下にテストプログラムをご紹介します。
下のプログラムはポート4のスイッチSW1〜4を押すことによって、それぞれ設定された速度でモーターが回転します。プログラム上では、それぞれのスイッチに以下のデューティー比を設定してあります。
| スイッチ | デューティー比 |
| SW1 | 0.1 |
| SW2 | 0.5 |
| SW3 | 0.7 |
| SW4 | 0.9 |
つまり、SW1が一番遅く回転し、SW4が一番早く回転します。
ソースプログラムは、ここをクリックするとダウンロードできます。(pwmtest2.c)
また、ハードウエアについては、電子回路編 3.AKI-H8開発キットの回路の[13]PORTA 接続コネクタおよび、4.DCモーター駆動回路を参照して下さい。
なお、スタートアップオブジェクトは、今回割込みを利用しているので、startup2.marでないと動作しません。startup2.marはstartup.marに名前を変更してから、ソフトウエア編の3.コンパイル手順 3−1.スタートアップオブジェクトを作るを参照して、startup.objファイルを作成し、pwmtest2とリンクして下さい。
|
/******************************************************/ if(P4.DR.BIT.B4
== 0){ /* sw1 */
|
107−3.テストプログラムpwmtest2.cの説明
このプログラムは、メイン関数main( )と割込み処理intimia1( )から構成されます。
■ヘッダファイルの読み込みと、割込みの宣言
#include <3048f.h>
#pragma interrupt(intimia1)
extern int cnt; /*グローバル変数 startup.marでもexport宣言*/
extern int mode; /*グローバル変数 startup.marでもexport宣言*/
ここでは、3048fヘッダファイルの読み込みと、割込み関数intimia1の宣言、およびグローバル変数cnt, modeの宣言を行ないます。
メイン関数内の説明
■PIOの初期化
P4.DDR = 0x00; /*port4入力に設定
操作用sw1〜4*/
P4.PCR.BYTE = 0xff; /*port4プルアップon */
P5.DDR = 0xff; /*port5出力に設定 表示LED */
PA.DDR = 0xff; /*portA出力に設定 モータ制御 */
ここでは、ポート4とポート5、ポートAの初期化を行なっています。
■グローバル変数の初期化
cnt = 0;
/*割込み回数カウント用変数 */
mode = 0;
/*デューティー比設定用変数 */
ここでは、main( )とintimia1( )の両方の関数で参照する変数(グローバル変数)の初期化を行なっています。
■ITUタイマーの初期化
ITU1.TCR.BYTE = 0x23; /* GRAコンペアマッチ
clock 1/8 */
ITU1.GRA = 0x2710; /*
GRAを2710に設定 約5ms */
ITU1.TIER.BYTE = 0xF9; /* ITU1のGRAによるコンペアマッチ割込みを許可*/
ITU.TSTR.BIT.STR1 = 0; /* カウント停止状態 */
ITU.TSTR.BIT.STR1 = 1; /* ITU1 TCNTカウント開始
*/
ここでは、ITUタイマーチャンネル1の初期化を行なっています。割込み発生の周期を約5mSに設定しています。各レジスタについての詳細は、105.タイマー割込みで効率的な制御のページを参照して下さい。
■メイン関数の主な処理
while(1){ }で無限ループを作り、その中で押ボタンスイッチに対応した処理を行なっています。
P5.DR.BYTE = 0x01; /*LEDを○●表示*/
通常の処理中であることを示すため、ポート5のLED1を点灯させる処理を行ないます。つまり、ページ先頭の説明図で言うグリーンの期間であることを示します。
if(P4.DR.BIT.B4 == 0){
/* sw1 */
mode = 1;
}
else if(P4.DR.BIT.B5 == 0){ /* sw2 */
mode = 2;
}
else if(P4.DR.BIT.B6 == 0){ /* sw3 */
mode = 3;
}
else if(P4.DR.BIT.B7 == 0){ /* sw4 */
mode = 4;
}
else { /*
何も押されていない */
mode = 0;
}
これらは、押されたスイッチによって、modeというグローバル変数の値を書き換えています。mode変数で、PWMのPIO出力を0(ゼロ)に戻すタイミングを指示します。この値は割込み関数内の処理に使われます。
メイン関数でやってることはこれだけです。つまり、押されたスイッチに対応したmodeの値を設定しているだけです。
■割込み関数内の処理
P5.DR.BYTE = 0x02; /*LEDを●○表示*/
割込み処理中であることを示すため、ポート5のLED2を点灯させる処理を行ないます。つまり、ページ先頭の説明図で言うオレンジの期間であることを示します。
cnt++;
if(cnt == 10){cnt = 0;}
割込み処理の始めで、cnt変数の値を+1しておきます。ただし、値が10まで増えたら、強制的に0(ゼロ)に戻します。このcntの値をPWM出力するための基準値とします。
if(mode == 0){
PA.DR.BIT.B0 = 0;
PA.DR.BIT.B1 = 0;
}
mode
の値が0(ゼロ)の場合、モーターを停止状態にする取り決めにします。なので、PWM出力は0(ゼロ)にしておきます。ちなみに、DCモーター駆動ICのVIN1がPA.DR.BIT.B0のBITに相当し、VIN2がPA.DR.BIT.B1のBITに相当します。今回のテストプログラムでは、VIN1のみにPWM波形を与え、VIN2は0(ゼロ)に固定しておきます。
switch(cnt){ }で、現在のcnt変数の値でのPIO制御を行ないます。
case 0: if(mode == 0){break;}
PA.DR.BIT.B0 = 1;
PA.DR.BIT.B1 = 0;
break;
cntが0(ゼロ)の場合、modeの値が0(ゼロ)ならswitch処理を抜けて、何もしません。つまり、PWM出力は0(ゼロ)のままです。
modeの値が0(ゼロ)でなければ、ポートAのBIT0を1にします。PWM出力の始まりです。この後、一旦割込み処理を終え、次の周期の割込み処理を待ちます。
case 1: if(mode == 1){ /*デューティー比0.1*/
PA.DR.BIT.B0 = 0;
PA.DR.BIT.B1 = 0;
}
break;
cntが1の場合、もしmodeの値が1に設定されていれば、ポートAのBIT0を0(ゼロ)に戻します。つまり、パルス波形は割込み周期の5mSだけ出力されます。
case 2: break;
case 3: break;
case 4: break;
cntが2,3,4の場合、何もしません。つまり、ポートAのBIT0が0(ゼロ)なら0のまま、1なら1のまま変化させません。
case 5: if(mode == 2){ /*デューティー比0.5*/
PA.DR.BIT.B0 = 0;
PA.DR.BIT.B1 = 0;
}
break;
cntが5の場合、もしmodeの値が2に設定されていれば、ポートAのBIT0を0(ゼロ)に戻します。つまり、パルス波形は割込み周期x5の25mSの期間出力されます。
あとのswitch分の中身についても、これらと同じ処理です。
ITU1.TSR.BIT.IMFA = 0; /* 検知フラグを戻して再開 */
割込み処理の最後に、割込み発生時に立ったフラグをクリアして置きます。これで、新たな割込みの発生を受け付けることができます。
ちょっとややこしいかもしれませんが、お分かり頂けましたか?同じタイマーによる割込みを繰り返しているだけですが、cnt変数によって意味付けを変えて、波形を発生させています。
今回はポートAのBIT0(ゼロ)だけをパルス化しましたが、BIT1をパルス化することによって、DCモーターの正転/逆転を行なうことができます。
また、今回はswitch分の中でcase1,5,7,9のところでBIT0をクリアする処理を入れていますが、これを他のcase2,3,4,6,8等に入れることによって、デューティー比を調整することができます。
ところで、ポート5のLED1と2ですが、このテストプログラムを実行してみると、LED1が圧倒的に明るく点灯して、LED2は僅かに燈っているといった感じです。これは、そのままLED1のグリーン(通常の処理)期間、LED2のオレンジ(割込み処理)期間の差となって現れています。割込み処理内のお仕事を増やしすぎると、両方のLEDが同程度に点灯したり、または逆にLED2の方が明るくなったりしてくるでしょう。そうなったら注意信号だと思ってください。