H8マイコンによるコントローラ(ソフトウエア)
Last update: <2017/03/01 18:17:51 +0900>
ハプティックコントローラは,ハードウエアを正しく作成し,かつ適切なソフトウエアを組み込むことによって始めて所定の動作をする.ここでは,制作したコントローラ用のマイコン用プログラムを作成する方法について考えてみる.当然ながら,対象となるハードウエア(マイコンボードやAD変換器など)についてよく知っていなければ,正しいプログラムを書けない.また,回路設計ですでに述べたように,マイコンを使って何をしようとするのか,整理しておかないと,プログラムとして実装できない.
以降,ここで必要な要素として,1.PWM出力,2.AD変換器の駆動,3.シリアル通信の3つについて記述する.
- PWM出力
マイコンボードによって,PWM信号を出力するための方法を考える.PWM制御の原理については,すでに3章の回路設計で触れた.
H8マイコンでPWM信号を出力するためには,マイコンに内蔵されているタイマを使う.H8には16ビットタイマと8ビットタイマがあるが,当然,16ビットのほうが精度がよい.あえて8ビットを選択する理由もないので,ここでは16ビットタイマを利用する.
マイコンのハードウエアマニュアルの16ビットタイマの概要を読んでみると,PWMモードという項目がある.GRAとGRBという16ビットのレジスタを使って,出力端子からPWM波形を出力するものである.このモードについて調べてみると,GRAやGRBに任意の数値を代入すれば,任意のデューティー比の波形が出力できる,というもののようである.コンペアマッチというのは,数値の比較(コンペア)をしてマッチ(一致)する条件である.多種のコンペアマッチ条件があるので,詳細をみてみる.
図5.1:PWMモードの動作例(H8/3069 F-ZTATTM ハードウェアマニュアルpp.403より抜粋)
図5.1はPWMモードの動作例である.グラフの一番下の波形(TIOCA)がPWM出力波形である.カウンタは0から一定の速度で増えていき,GRBと等しくなるまで出力はON,その後,カウンタがGRAと等しくなるまで出力はOFFとなり,カウンタがGRAになると0にクリアされる,というものである.要するに,GRAに100を設定しておくと,GRBを50にすれば,デューティー比50%のPWM波形,GRBを10にすれば10%のPWM波形が出力される,ということになる.
PWM出力の要領がわかったので,具体的な設定・操作について調べる.図5.2はPWMモードに必要な設定手順である.細かいレジスタは後回しにして,簡単な概略は次のとおりである.(1)(2)で16ビットのアップカウンタを準備する. (3)ではカウンタがGRAと等しくなれば1を出力し,(4)ではカウンタがGRBと等しくなれば0を出力するように設定する.(5)ではPWMモードに設定し,出力端子がPWM波形を出すように準備する.(6)でアップカウンタを始動させる.各項目について知らなければプログラム化できないので,さらに詳細を見ていく.
図5.2:PWMモードの設定手順(H8/3069 F-ZTATTM ハードウェアマニュアルpp.402より抜粋)
- (1)(2).カウンタの準備
PWMの元となるカウンタを準備する.図5.2に出てくる16TCRというのは,タイマコントロールレジスタのことで,このレジスタにより16ビットタイマの設定をおこなう.実際にカウントに使うタイマカウンタは16TCNTという名前のレジスタになっていて,16ビットで構成されている.タイマは16TCNT0〜16TCNT2まで3つ存在し, 16TCNT0を使う場合は,16TCR0で設定をするわけである.図5.3に16TCRの具体的な設定について示す.
図5.3:16TCRの設定(H8/3069 F-ZTATTM ハードウェアマニュアルより抜粋)
16TCRの第5,6ビットでは,カウンタをクリアする要因について選択できる.図5.4で詳細を示すが,ここではGRAと一致したときにカウンタをクリアしたいわけだから,第6ビット(CCLR0)が0,第5ビット (CCLR1)が1となればよさそうである.
図5.4:16TCRの第5〜6ビット(CCLR1〜CCLR 0)の設定(H8/3069 F-ZTATTM ハードウェアマニュアルより抜粋)
16TCRの第3,4ビットでは,クロックエッジの設定である.この設定は,外部クロックを使うときのタイミングを選択するものである.ここでは外部クロックを使わずCPU内部のクロックを使うので,無視してよさそうである.
16TCRの第1〜3ビットでは,タイマプリスケーラの設定である.タイマをプリ(あらかじめ)スケール(縮尺を決める)するもので,タイマがどれぐらいの頻繁にカウントされるのかを設定できるものである.図5.5にプリスケーラ設定項目を示す.まずCPU内部クロックを使うので,ビット2(TPSC2)は0である.次に内部クロックのψでカウントというのは,内部クロックと同じタイミングでタイマが増えることを表している.ψ/2でカウントというのは,内部クロックが2回変化するとタイマが1回変化する(2分周)ことを意味する.ψ/4は4分周,ψ/8は8分周である.PWMタイマが速いほうが,より細かな制御ができることから,ここでは分周なしの設定として内部クロックψでカウントを選択する.したがって,第1ビット(TPSC1),第0ビット(TPSC0)はともに0となる.
16TCRの第7ビットは予約ビットであり,無視することができる.
図5.5:16TCRの第2〜0ビット(TPSC2〜TPSC 0)の設定(H8/3069 F-ZTATTM ハードウェアマニュアルより抜粋)
以上の設定から,16TCRのビット列を上位からならべると *01**000(2)と表せる.*は0でも1でもどちらでもよい.0とするなら00100000(2)と表せる.よって,16TCRレジスタには,0x20(16)を代入しておけばよい.
- (3)(4).GRA,GRBの設定
GRA,GRBはジェネラルレジスタA,Bと呼ばれ,16ビットで構成されている.これらのレジスタはタイマカウンタと比較されたりするのに用いられる.ここで用いるPWMモードでは,GRAの値はPWM信号のONとOFFの合計時間(一周期),GRBの値はPWM信号のONの時間を設定する.いうなれば,GRAが分母,GRBが分子に相当し,GRAが大きいほどPWM出力の分解能が高いことがわかる.ただし,GRAを大きくすると,PWM信号の一周期が大きくなり,この周期が人間の可聴域に到達するとモータ制御において音が発生してしまう.カウンタは16TCRで設定したように,内部クロック(20MHz)と同じタイミングで増加する.したがって,可聴域の最大周波数を20kHzと仮定すると,GRAは1000より大きくできないことになる.ここではGRAレジスタに1000を代入しておき, GRBを0から1000の間で変化させることにした.
- (5).PWMモードの設定
タイマのモードを選択するにはタイマモードレジスタTMDRを設定する.図5.6にTMDRの設定を示す.
図5.6:TMDRの設定(H8/3069 F-ZTATTM ハードウェアマニュアルより抜粋)
PWMモードに大きく関連するのは,TMDRの第0〜2ビットである.3つのチャネルのタイマをPWM動作させるためには,全て1を代入することになる.第6ビットは位相計数モードという別モードへの設定なので,0を代入する.第5ビットは,PWMモードや出力とは直接関係ない.残りのビットは予約ビットであり,無視することができる.
以上の設定から,TMDRのビット列を上位からならべると *0***111(2)と表せる.*は0でも1でもどちらでもよい.0とするなら00000111(2)と表せる.よってレジスタTMDRには,07 (16)を代入しておけばよい.
- (6).タイマの起動
タイマスタートレジスタTSTRを利用する.図5.7に示すとおり,動作・停止させたいタイマを第2〜0ビットで指定すればよいことになる.3チャネルすべて動作開始する場合は,ビット列が00000111(2)であり,07 (16)を代入すればよい.停止させる場合は00(16)である .
図5.7:TSTRの設定(H8/3069 F-ZTATTM ハードウェアマニュアルより抜粋)
プログラムの初期化部分は,具体的には下記のようになる.
/* PWM用タイマの設定 */
/* PWMモード(3069マニュアル378ページ) */
/* GRAとコンペアマッチでカウンタクリア */
/* 立ち上がりエッジでカウント */
/* 分周なし */
TCR0=0x20; /* 00100000 */ /* GRAのコンペアマッチでTCNTをクリア,分周なし*/
TCR1=0x20; /* 00100000 */ /* GRAのコンペアマッチでTCNTをクリア,分周なし*/
TCR2=0x20; /* 00100000 */ /* GRAのコンペアマッチでTCNTをクリア,分周なし*/
/* カウンタ0〜GRB H */
/* カウンタGRB〜GRA L */
GRA0=1000; /* 波長幅(20MHz/1000=20kHz) */
GRA1=1000; /* 波長幅(20MHz/1000=20kHz) */
GRA2=1000; /* 波長幅(20MHz/1000=20kHz) */
GRB0=0; /* モータトルク値(Max1000) */
GRB1=0; /* モータトルク値(Max1000) */
GRB2=0; /* モータトルク値(Max1000) */
TMDR=0x07; /* 00000111 */ /* PWM ch0,1,2をアクティブ */
プログラムのループ内でPWMの値を変更することになるが,これを変えるにはGRB0〜GRB2に値を代入すればよいことになる.
- AD変換器のドライバ
ここでは外付けのAD変換器を動かすためのプログラムについて記述する.MCP3208を制御する信号線は4つあり,各信号線の詳細は3.1で述べた.これらの信号線はポート4(P4)に接続されていることから,マイコン側ではポート4をアクセスすればよいことになる.もう一度,ピンの接続について整理しておく.
- P40: CLK マイコンから出力
- P41: ~CS マイコンから出力
- P42:DOUT マイコンに入力
- P43: DINマイコンから出力
H8マイコンのI/Oポートは入力・出力兼用となっている.I/Oポートを利用する上で気をつけなければいけないことは,入力と出力をしっかり決めておくことである.マイコンと周辺機器側の入力同士が接続されるのであれば問題ないが,出力同士が接続されるとショートの恐れがある.マイコンの入出力端子は,各端子の入出力をビット単位で指定できるのが一般的である.ここでは,ポート4(P4)の第3〜0ビットのうち,第2ビット(P42)のみが入力であることを注意しておく.
I/Oポートの入出力を決めるレジスタはデータディレクションレジスタ(DDR)と呼ばれ,この場合P4DDRが関連する.例えばポート4の第2ビットを入力とするなら,出力が1,入力が0の場合,P4DDRの第2ビットを0とすることになる.すなわち****1101(2)と表せ,FB(16)を代入しておけばよい.
次にマイコンとAD変換器であるMCP3208の具体的なやり取りについて考える.図5.8はシリアル通信のタイムチャートである.この図とマニュアルの記述を総合すると,以下のような手順になるだろう.
図5.8:MCP3208のシリアル通信タイムチャート(MCP3204/3208データシートより抜粋)
-
(1).~CSは常に1だが,AD変換と通信をしたいときには~CSを0にする.~CSの立下りでAD変換が停止し,ホールド状態になる.したがって,通信しない間は~CSに1に戻さなければならない.
-
(2).MCP3208に指示をだすために5ビット送信する.データの送信はDINを使う.DINにデータを1ビット置いたら,CLKを0から1に立ち上げる.MCP3208は立ち上がりでデータを読み取る.開始ビットは必ず1で,次にシングルエンドかディファレンシャルかを選択する.シングルエンドとはAD変換の入力を単線で利用するモード,ディファレンシャルとは2線の電位差を測定するモードである.ここでは シングルエンドで利用するので1を送信する.残りの3ビットで読み込みたいチャネルを選択する.例えばCH0のAD入力結果を要求するならD2:D1:D0=000,CH1であればD2:D1:D0=001 という具合である.
-
(3).MCP3208からAD変換結果として13ビットを受信する.データの受信にはDOUT を用いる.DOUT にデータを乗せるには,CLKを1から0に立ち下げる.開始ビットは必ず0で,引き続きMSBから12ビット送信されてくる.受信側は受け取ったビット列を上位から順にならべていけばよい.
初期化の部分では,具体的には次のようなコードになる.
/* DIR用制御IOの設定 */
P4DDR=0xFB; /* 11111011 P40>CLK P41>-CS P42DIN */
これは,ポート4の上位4ビットはモータ制御のDIRに使用しているため,全部出力にしておかなければならない.P42のみ入力であることは先に述べた.
AD変換部分では,次のようになる.
void send(unsigned char bit) /* 3208とのシリアルデータ送信 */
{
P4DR &= ~0X01; /* CLK(P40)をLに */
if(bit)
P4DR |= 0X08; /* DIN(P43)をHに */
else
P4DR &= ~0X08; /* DIN(P43)をLに */
P4DR |= 0X01; /* CLK(P40をLに */
}
unsigned char recv(void) /* 3208とのシリアル受信 */
{
unsigned char bit;
P4DR ^= 0X01; /* CLK(P40)上げ */
bit = (P4DR&0X04)>>2; /* DOUT(P42)から1ビット入力 */
P4DR ^= 0X01; /* CLK(P40)下げ */
return(bit);
}
unsigned int serial_main(unsigned char ch)
{
unsigned int data;
/* IOポートの設定 */
P4DR &= 0XF0;
P4DR ^= 0X02; /* 3208_CS=H */
P4DR ^= 0X02; /* 3208_CS=L */
send(1); /* start bit */
send(1); /* SGL/~DIFF */
send((ch>>2)&0X01); /* D2(ch) */
send((ch>>1)&0X01); /* D1(ch) */
send((ch)&0X01); /* D0(ch) */
send(0);
send(0);
P4DR ^= 0X01; /* 3208_CLK=L */
data = recv(); /* B11 */
data = (data<<1)|recv(); /* B10 */
data = (data<<1)|recv(); /* B09 */
data = (data<<1)|recv(); /* B08 */
data = (data<<1)|recv(); /* B07 */
data = (data<<1)|recv(); /* B06 */
data = (data<<1)|recv(); /* B05 */
data = (data<<1)|recv(); /* B04 */
data = (data<<1)|recv(); /* B03 */
data = (data<<1)|recv(); /* B02 */
data = (data<<1)|recv(); /* B01 */
data = (data<<1)|recv(); /* B00 */
P4DR ^= 0X01; /* 3208_CLK=H */
P4DR ^= 0X02; /* 3208_CS=H */
return(data);
}
戻る