|
アウトプットコンペア
イントロダクション 他の単語は全部知っているか、少なくとも聞いたことのある人でも、アウトプットコンペア/インプットキャプチャの2つは知らないと言う人も多いのではないでしょうか? どちらも使ってみればとてつもなく便利な機能であることを実感すると思います。それを実感するためにまず一つ例題を出してみます。
「ディーティー比可変または固定のPWM信号を2つ出しなさい。」
これまでの知識でも確かにできない事じゃないですよね。でも、ちょっと面倒です。まず一つタイマーを走らせて、続いてもう一つ走らせて、一定時間待ってから片方を止めて、もう片方も止める。 これをプログラムに起こせば良いわけです。
では、例題に条件を追加します。
「さらに、PWM信号を出している間はLCDモニタに1〜255までの数字を常に書き続けること」
さて、困りましたね。PWM信号である以上は周期は一定に保たなければいけません。しかし、LCDモニタは前ページの解説からも分かるように一定の待ち時間が必要です。これらのタイミングをすべて計りながら信号を出すことはなかなか面倒です。これにさらにもう一つ条件を増やされでもしたら大変ですね。割り込み処理は避けられないでしょう。ではトドメです。
「ただし、2つのPWM信号は異なる周期であるとする」
少し前の私ならば「できるかっ!コノヤロウ」と言っていたかも知れません。複数のマイコンを使えば容易にできますが、単体ではなかなか難しいプログラムになりそうです。
ところが、こういう悩みを一瞬で解決してくれる機能が有ります。
それがアウトプットコンペアという機能です。
アウトプットコンペアの概要 アウトプットコンペアを説明するには次の図を見てもらうと理解が早いと思います。
H8/3048Fのタイマーは全部で5系統有り、この機能に関しては5つとも同じ使い方ができるので、上の図が5枚独立に並んでいると考えてください。 このGRAとGRBにある値をあらかじめ代入しておくと、TCNTがその値になったときに割り込みが掛かるのです。また、TCNTはGRAまたはGRBのコンペアマッチ時に自動的に0x0000にリセットする事もできますから、Bの地点でリセットおよび、あるポートの出力をLowにする。Aの地点で同一ポートの出力をHighにする、というふうにすれば、後は自動的にPWM信号が生成されますね。デューティー比を変えたいときにはGRAの値を変えてやるだけで次回からはそれに対応したデューティー比になります。これならば他にどんな作業をしながらでもほとんど自動で正確なPWM信号が出せます。 信号が2つ必要だというのであれば、ITU0とITU1の2つのタイマーを同じように使えば良いことです。GRBの値が同じならば同一周期のPWM信号になりますし、異なる値ならば違った周期を持つPWM信号も簡単に出せます。
これで命題は満たせました。でも実は、PWM信号だったらもっと簡単にできてしまうのです。 その名も「PWMモード」。やることは上の太字下線の部分と同じなのですが、それをプログラムではなくハードウェアがやってくれる機能があるのです。速度にして数クロック分のスピードアップ、プログラムにして数行分の楽ではありますが、わざわざ有るんだったらぜひとも使いましょう。
アウトプットコンペア(PWMモード)を使う 今回のサンプルファイルはこのファイルです。そろそろ慣れてきたと思うので、少し実践的な物にしました。内容としては次のような物です。
- ラジコン用のサーボモータを制御するための信号を出力する。信号は周期70Hz、Highの区間1.0〜2.0msecで構成されるPWM信号。デューティー比の変更はRS-232Cの受信割り込みを使い、パソコンから送られてきたunsigned
char型の数値に定数を掛けて1.0〜2.0msecにフィットさせる。
何に使われているかというと、現在我が研究室で行われている光造形研究グループがカメラのシャッターを切るのに使っているんですよね。パソコンからは2通りの数値だけを出すようにしておき、片方がシャッターを押す方、もう片方がシャッターを離す方になっています。
普通のアウトプットキャプチャは単なるタイマー割り込みですからどのポートでも扱えるのですが、ハードウェア的にPWM信号を生成するモードでは出力ポートは決まっています。ITU0のPWMモードを使った場合にはPortA-0になります。どのように調べるかというと、チップの回路図を見たときに
「PA-2/TP-2/TIOCA-0/TCLK-C」
と書いてある場所があるはずです。このうちの「TIOCA-0」というのがそうです。
アウトプットコンペアはハードウェアがほとんど担当するわけですから、プログラムの主役は初期化ルーチンであり、intprg.cの記述はほとんど補助的な物になります。まずは初期化ルーチンを見ていきましょう。
| main.c
void initITU(void) { ITU.TSTR.BYTE
= 0x00; //
ひとまず全タイマー停止 ITU.TSNC.BYTE
= 0x00; //
全タイマーは独立動作 ITU.TMDR.BYTE
= 0x01; //
ITU0のみPWMモードに設定 ITU.TFCR.BYTE
= 0x00; //
ITU3,4およびGRA3,4,GRB3,4は通常動作 ITU.TOER.BYTE
= 0x00; //
ITU3,4による端子出力は禁止 ITU.TOCR.BYTE
= 0xff; //
外部トリガ禁止 ITU0.TCR.BYTE
= 0x43; //
クロックの1/8で動作。GRBのコンペアマッチでTCNTのクリア ITU0.TIOR.BYTE
= 0x9a; //
GRAのコンペアマッチで1,GRBのコンペアマッチで0 ITU0.TIER.BYTE
= 0x03; //
IMFA,IMFBによる割り込み許可 OVIE禁止 ITU0.TSR.BYTE
= 0x00; //
全割り込みフラグクリア ITU0.TCNT
= 0x00; //
カウントクリア ITU0.GRA
= 25571; //
GRA初期値設定(12.8msec) ITU0.GRB
= 28571; //
GRB初期値設定(14.3msec)
ITU.TSTR.BIT.STR0
= 1; // ITU0スタート }
|
基本はタイマー割り込みですから、今さら多く説明することは有りませんが、今回のポイントは赤で示した部分です。 まずはPWMモードに設定すると言うことでTMDRを変更します。今回はサーボモータを1つしか動かす必要がなかったのでタイマーは1つしか使いません。したがって、TMDRは5系統あるタイマーのどれをPWMモードとして使用するか一括して管理していますが、そのうちの1つだけ「0」から「1」にしています。 次に、上で述べたTCNTのクリア条件ですが、GRBでクリアします。 また、GRAとGRBそれぞれの地点で出力をHighにするのかLowにするのかをTIORで設定しています。 当然と言えば当然ですが、GRAとGRBの初期値を与えておかなければ、RS-232Cから最初のデータが届くまではまったく波形がでませんから初期値を設定しておきます。ちなみにこれはサーボモータのニュートラル値です。 最後にタイマーをスタートさせると後は勝手にPWM信号が生成されます。
もう一つ重要な役割を背負うのが受信割り込みと、受信した数値に定数を掛ける部分ですね。このようになっています。
| intprg.c
unsigned char rxd;
/**********************************************************/ void
INT_ERI1(void) { SCI1.SSR.BYTE
&= 0xc7; //
Clear receive error flag } /**********************************************************/ void
INT_RXI1(void) { SCI1.SSR.BIT.RDRF
= 0 ; // Clear receive
data flag
rxd = SCI1.RDR; ITU0.GRA
= 26571 - ((rxd * 78) / 10 );
while(!SCI1.SSR.BIT.TDRE);
// エコーバック SCI1.TDR
= rxd; SCI1.SSR.BIT.TDRE
= 0;
} /**********************************************************/ void
INT_IMIA0(void) //
GRAのコンペアマッチ { ITU0.TSR.BIT.IMFA
= 0; //
割り込みフラグ解除 } void INT_IMIB0(void)
//
GRBのコンペアマッチ { ITU0.TSR.BIT.IMFB
= 0; //
割り込みフラグ解除 }
|
タイマー割り込みの処理も有るには有りますが、なんとも簡潔な内容でしょう。H8/3048Fがいかに信号生成能力に優れたマイコンであるかが伺えます。
GRAに設定する数値はこのような数式になります。これはかなり用途が限定されていますので無理に理解する必要はないと思いますが、16ビットタイマーに値を代入しなければいけないから2バイト送る必要があるなどと考える必要は全然無く、こうすれば8ビットでも必要十分な精度は得られるのです。もちろん、16ビットの分解能がギリギリまで要求されるような場面ではそんなことは言っていられませんが・・・
なお、GRAに値を代入した後で送信処理を行っていますが、これはパソコン側でソフトを作る際に、エコーバックが有った方が何かと都合が良い場合が多いので私がプログラムを作るときには基本的にいつも入っているコードです。設計思想の問題ですから、無駄を省いてできるだけ高速に走らせたいときには不要です。
さて、見せるまでもないメインルーチンですが、見て笑ってください。たったこれだけで機能を果たします。
| main.c
|
簡単すぎて忘れてしまう箇所が有るとすると、肝心の出力ポートのDDRの設定を忘れるか、最後にwhileループを入れるのを忘れて電源ONと同時に終わってしまうかのどちらかですね。いかに割り込みと言えど、プログラムが終了してしまっては働きません。


|