107.タイマー割込みでPWMする


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とは、だいたい以下のように動作させます。

割込み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とリンクして下さい。

/******************************************************/
/* タイマー割込みPWMテスト          RIKIYA 2001.05.04 */
/*                                         pwmtest2.C */
/*                                          H8-3048/F */
/* メイン関数でport5のLEDを01表示で更新し続ける一方で */
/* ITU1によって約5msごとに割り込みを発生させ、        */
/* sw4からの入力によって割り込み処理でport5のLEDを    */
/* 変化させ,portAでモーター制御をする。               */
/* モーター制御は、割込みの5msを単位とするPWMを行う。 */
/* 変数cnt,modeをグローバル変数として、main,intimia1  */
/* の両方の関数で共用している                         */
/******************************************************/

#include <3048f.h>
#pragma interrupt(intimia1)
extern int cnt;           /*グローバル変数 startup.marでもexport宣言*/
extern int mode;          /*グローバル変数 startup.marでもexport宣言*/

/* メイン関数**********************************/
void main(void){

     P4.DDR = 0x00;         /*port4入力に設定 操作用sw1〜4   */
     P4.PCR.BYTE = 0xff;    /*port4プルアップon              */
     P5.DDR = 0xff;         /*port5出力に設定 表示LED       */
     PA.DDR = 0xff;         /*portA出力に設定 モータ制御    */

     cnt = 0;               /*割込み回数カウント用変数       */
     mode = 0;              /*デューティー比設定用変数       */

     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カウント開始         */

/*押されたボタンによって変数 mode を設定する****************/
     while(1){
          P5.DR.BYTE = 0x01;             /*LEDを○●表示*/

          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;
          }
     }

}

/*割り込み処理*********************************/
void intimia1(void){
     P5.DR.BYTE = 0x02;                  /*LEDを●○表示*/
     cnt++;
     if(cnt == 10){cnt = 0;}

     if(mode == 0){
          PA.DR.BIT.B0 = 0;
          PA.DR.BIT.B1 = 0;
     }

     switch(cnt){
          case 0: if(mode == 0){break;}
                  PA.DR.BIT.B0 = 1;
                  PA.DR.BIT.B1 = 0;
                  break;
          case 1: if(mode == 1){     /*デューティー比0.1*/
                       PA.DR.BIT.B0 = 0;
                       PA.DR.BIT.B1 = 0;
                  }
                  break;
          case 2: break;
          case 3: break;
          case 4: break;
          case 5: if(mode == 2){     /*デューティー比0.5*/
                       PA.DR.BIT.B0 = 0;
                       PA.DR.BIT.B1 = 0;
                  }
                  break;
          case 6: break;
          case 7: if(mode == 3){     /*デューティー比0.7*/
                       PA.DR.BIT.B0 = 0;
                       PA.DR.BIT.B1 = 0;
                  }
                  break;
          case 8: break;
          case 9: if(mode == 4){     /*デューティー比0.9*/
                       PA.DR.BIT.B0 = 0;
                       PA.DR.BIT.B1 = 0;
                  }
                  break;
          default:break;
     }
     ITU1.TSR.BIT.IMFA = 0;          /* 検知フラグを戻して再開 */
}

 

【先頭に戻る】


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の方が明るくなったりしてくるでしょう。そうなったら注意信号だと思ってください。

 

【ソフトウエア編TOPに戻る】

 


【表紙に戻る】