お待たせしました。ここからはPWM制御でDCモータを制御するためのプログラムを説明していきます。まずはDCモータの初期化プログラムです(リスト1)。
// PWM制御のための構造体 struct s_pwm_motor { int on; // モータがONかOFFかを記憶しておくための変数 unsigned int cycle; // PWMの周期(usec、GRBの設定値) unsigned int trig; // PWM波をHighにするきっかけ(GRAの設定値) }; ……(1) struct s_pwm_motor DcmSet[2]; void DcmInit(void) { int i; // PB(出力) // PB0: DC_PWM0, PB1: DC_BRK0, PB2: DC_PWM1, PB3: DC_BRK1 // PB4: DC_EN0, PB5: DC_EN1, PB6: DC_CW0, PB7: DC_CW1 PB.DR.BYTE = 0x30; ……(2) PB.DDR = 0xFF; // 変数初期化 for(i=0; i<2; i++) ……(3) { DcmSet[i].on = FALSE; DcmSet[i].cycle = 0; DcmSet[i].trig = 0; } // ITU3/4の初期設定 ……(4) ITU.TSTR.BIT.STR3 = 0; ITU.TSTR.BIT.STR4 = 0; ITU.TSNC.BIT.SYNC3 = 0; ITU.TSNC.BIT.SYNC4 = 0; ITU.TMDR.BIT.PWM3 = 0; ITU.TMDR.BIT.PWM4 = 0; ITU.TFCR.BYTE = 0x00; ITU.TOER.BYTE = 0x00; ITU.TOCR.BIT.OLS3 = 1; ITU.TOCR.BIT.OLS4 = 1; ITU3.TCR.BYTE = 0x43; // GRBでTCNTクリア,Φ/8 ITU3.TIOR.BYTE = 0x00; ITU3.TIER.BYTE = 0x00; ITU4.TCR.BYTE = 0x43; // GRBでTCNTクリア,Φ/8 ITU4.TIOR.BYTE = 0x00; ITU4.TIER.BYTE = 0x00; }
この関数ではまずポートBをすべて出力にしてPB4、5をHighにしています(2)。「PB.DR.BYTE= 0x30;」はPB4、5をHighにする処理ですが、PB4、5はそれぞれDC_EN0、1信号ですので、Highにすることでモータ動作を停止させるという意味になります。
モータ制御用の構造体を初期化した後(1)(3)、H8マイコンのITU機能に関するレジスタを初期化しています(4)。この時点では、ITUのタイマはスタートさせていませんし、TIOCA端子もPWMモードにはしていません。ただ、「ITU3.TCR.BYTE = 0x43;」でITUのカウンタが内部クロックの1/8でカウントするようにセットし、ITUのカウンタがGRBレジスタの値と一致したときにカウンタをクリアするようにしています。
そして、次の関数でPWM波のデューティ比を設定します(リスト2)。
//---------------------------------------------------------------------- // DcmSetCycle //---------------------------------------------------------------------- // [説明] // DCモータ用のPWM波の周期とデューティ比を設定します // ITUのPWMモードによりDCモータを駆動します // 制限事項としてduty=100を指定しても出力はHIGH固定となりません // 非常に短いLOWパルスが発生します // ただし、duty=0とした場合は出力はLOW固定となります // [入力] // ch :設定するDCモータを0,1で指定します // cycle :PWMの周期をusec単位で指定します(0〜21844) // duty :デューティ比を指定します(0〜100) // [出力] // なし // [戻り値] // なし //---------------------------------------------------------------------- void DcmSetCycle(int ch, unsigned int cycle, int duty) { // 設定値を構造体に記憶させる Set_cycle(&DcmSet[ch], cycle, duty); if(DcmSet[ch].on) { // 現在回転しているなら一度停止してから再スタートする DcmOn(ch, FALSE); DcmOn(ch, TRUE); } }
引数としてDCモータ番号とPWM波の周期とデューティ比を与えます。周期とデューティ比は初期化プログラムのリストにある構造体に記憶させます。また、DCモータがすでに回転していた場合、この関数に与えられたPWM波の設定を反映させるためにDcmOn()関数を呼び出します。
構造体に周期とデューティ比を記憶させるSet_cycle()関数が何を行っているかはリスト3のとおりです。
void Set_cycle(struct s_pwm_motor *m, unsigned int cycle, int duty) { if(cycle > 0x5554) cycle = 0x5554; ……(1) // ターゲットの内部クロックΦ=24.576MHz // 初期化関数内でITUはΦ*1/8で動作するように設定しているので約3MHz // cycleはusec単位なので3倍する cycle = cycle * 3; m->cycle = cycle; ……(2) if(duty <= 0) { ……(3) // デューティ比が0のときはPWM波をHighに // しないために周期より大きく取る m->trig = cycle + 1; } else if(duty>=100) { // デューティ比が100のときはPWM波を常にHighにする m->trig = 0; } else { // それ以外のときは周期*(100−デューティ比)%の間 // Lowとなる値をセットする m->trig = (cycle/100)*(100-duty); } }
構造体に記憶させる周期とデューティ比には少し細工が加えられています。まず周期ですが、こちらは引数のcycleをGRBレジスタにセットすることで決定します。DcmInit()関数の中で「カウンタがGRBレジスタ値と一致したらカウンタをクリアする」としたのは、GRBの値とITUのクロックからPWM波の周期を簡単に決められるようにするためです。
本連載のターゲットボードは内部クロック24.576MHzで動作しています。(厳密さを求めるならいけないことですが)約24MHzとすると、ITUはDcmInit()関数の中で内部クロックの1/8でカウントするようにしていますので、3MHzでカウントすることになります。
カウンタが3MHzで動くということは、違う見方をすると1秒間に3*10の6乗回カウントするということです。そして、引数のcycleはusec(マイクロセカンド=10の-6乗秒)を表すようにしたい(これは人間の都合ですが)ので、cycleを3倍にすることでH8/3048F-ONEが数えたカウント(=時間)と引数が一致するようになります。1秒間に3*10の6乗カウントするということは、1u秒に何回カウントするかと考えるとそのまま答えになります(2)。
ただし、ここで一点注意が必要です。前回紹介したように、ITUのGRA、GRBは16bitのレジスタです。16bitのレジスタは16進数で0x0から0xffffまでしか表現できません。そこで起こる一番の問題は「0xffffに1を足すと0になってしまう」ということです。そのため、この関数に与えるcycle(GRBの設定値)の引数には制限が加えられています。cycle数を3倍することと、デューティ比が0のときのGRAの値(3)をバグなく設定するために、cycleが0x5555(10進数で21845)以上だった場合は強制的に0x5554にしています(1)。
このあたりは完全にターゲットボードの仕様に合わせた処理になっていますので、汎用的ではないということに注意してください。
次にデューティ比ですが、デューティ比もそのまま構造体に記憶させるのではなく、ターゲットボードに即した値に計算しています。PWM波のHighを出力させるタイミングは、ITUのカウンタとGRAレジスタの値が一致したときになると前回書きました。デューティ比は周期信号の周期とパルス波の割合ですから、引数のdutyからGRAレジスタに設定する値を計算することで、カウンタとGRAが一致してからGRBと一致するまでの時間が周期*デューティ比の時間と一致するようになります。また、この関数はサーボモータ制御でも使用しています(3)。
Copyright © ITmedia, Inc. All Rights Reserved.