DMACでパルス発生(1)
H8のDMACでRCサーボ用のパルスを発生させる。


DMACとは?


前書き

 Direct Memory Access Controler(以下DMAC)とは、メモリからメモリへ、またはメモリ
から周辺機器の間をCPUを介さずに、直接転送できる機能です。このDMACは、大量のデータ
を高速で転送することが可能で、さらにソフトウェア上では初期の起動と終了処理だけで
済むので、プログラムにかかる負担も軽く非常に効率が良いのです。

 RCサーボ駆動型二足歩行ロボットの場合、全身で大量のRCサーボを使用し、それらを制御
しなければなりません。しかし、H8/3048FやH8/3052Fでは最大5つのPWMしか発生すること
ができないため、全く足りません。これでは片足くらいしか制御できません。

 そこで、割り込みを使ったプログラムも考えたのですが、割り込み処理の時間が遅いせい
なのか、12個のRCサーボを動かそうとすると±8°ごと(-16°,-8°,0°,8°,16°)
の制御しかできませんでした。このプログラムでは、満足に立つ事もできません。

 そこで登場したのが、DMACです。自分はこの時点でマイコンを始めてから半年くらいしか
経っていなかったのと、ハードウェアマニュアルを熟読していなかったことから、DMACの
存在を全く知りませんでした。しかし、TEKUROBO工作室の掲示板に書き込んでいらっしゃる
yukkyさんの助言で始めて存在を知り、今日までDMACばかりやってきたというわけです。

 では、このDMACはどのようなプログラムを書けば使えるかを説明していきます。

DMACを使ったLED発光プログラム

まずは実例を元に設定の仕方を説明していこうと思います。
今回のプログラムはいきなりRCサーボのプログラムではなく、
8ビットのLEDを順番に点滅させるプログラムの説明をしていきます。
(要するにナイトライダー)

細かい部分の説明(ポートの設定等)は省きますので、詳しく知りたい方は、
TEKUROBO工作室を訪れてみてください。きっと、ここよりも理解しやすいと思います。

#include<3048f.h>

/* LED発光パターンテーブル*/
char data[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

main()
{
	P2.DDR = 0xff;				/* 入出力設定(1:出力, 0:入力)*/
	P2.PCR.BYTE = 0x00;			/* プルアップ設定(1:ON, 0:OFF)*/
	
	dma_init();				/* 起動要因であるITU0の初期化*/
	
	while(1)
	{
		dma();
	}
}

dma()
{
	DMAC0A.MAR = &data[0];			/* 転送元(データ)のアドレス*/
	DMAC0A.IOAR = 0xc3;			/* 転送先のアドレス*/
	DMAC0A.ETCR = sizeof(data);			/* 転送回数 = 転送元データの数*/
	DMAC0A.DTCR.BYTE = 0x00;			/* DMACの設定:00000000*/
	DMAC0A.DTCR.BIT.DTE = 1;			/* DMAC0A起動*/

	ITU.TSTR.BIT.STR0 = 1;			/* ITU0 TCNTカウント開始*/
	while(DMAC0A.DTCR.BIT.DTE);			/* 転送が完了する(DTE=0の状態)まで待機*/
	ITU.TSTR.BIT.STR0 = 0;			/* ITU0 TCNTカウント停止*/
}

dma_init()
{
	ITU0.TCR.BYTE = 0x23;			/* GRAコンペアマッチ clock 1/8 */
	ITU0.GRA = 0xffff;				/* GRAを65535に設定 約33ms*/
	ITU0.TIER.BYTE = 0xf9;			/* ITU0のGRAによるコンペアマッチ割込みを許可*/
	ITU.TSTR.BIT.STR0 = 0;			/* ITU0 TCNTカウント停止*/
}
▼アドレスモード  DMACには、次の二つのモードがあります。  今回は、メモリ(発光順序のデータ)から周辺機器(LEDに接続しているポート)に転送 したいので、ショートアドレスモードを使います。 ▼ショートアドレスモード  さらに、ショートアドレスモードにも次の3つのモードがあります。  今回は、発光順序のデータを順番に転送したいので、I/Oモードを選択します。 ▼I/Oモードとは? 今回選択するI/Oモードは次のようなものです。 ▼プログラムでの設定  さぁ、いよいよプログラムの説明に入っていきたいと思います。(「説明」と言うよりも、 「設定の仕方」と言った方がいいかもしれません。)  まず、dma()関数を見てください。 DMAC0A.MAR = &data[0]; DMAC0A.IOAR = 0xc3; DMAC0A.ETCR = sizeof(data); DMAC0A.DTCR.BYTE = 0x00; DMAC0A.DTCR.BIT.DTE = 1;  何やら「DMAC0A」と言う文字が5つぐらいあります。この部分がDMACの設定をする部分です。 では、上から順番に説明していきます。 ▼メモリアドレスレジスタ(MAR) 「DMAC0A.MAR」 ここに、転送したいデータが入っている配列などの先頭アドレスを 記入すれば、データを順番に転送してくれます。 <転送元アドレスの設定>  プログラムでは、「&data[0]」になっています。配列の前に「&」を付けると、「その配列の アドレスである」と言う意味になります。 ▼I/Oアドレスレジスタ(IOAR) 「DMAC0A.IOAR」 ここでデータを送りつけたい周辺機器のアドレスを記入すれば、 先ほど設定したMARから順々にデータがやってきます。 <転送先アドレスの設定>  プログラムでは、「0xc3」になっています。「0xc3」は「P2.DR.BYTE」のアドレスです。
P1DRP2DRP3DRP4DRP5DRP6DRP7DRP8DRP9DRPADRPBDR
0xC20xC30xC60xC70xCA0xCB0xCE0xCF0xD20xD30xD6
補足:I/Oポートのアドレスにも転送は可能です。 ▼転送カウントレジスタ(ETCR) 「DMAC0A.ETCR」 ここに転送したい回数を記入すると、記入された回数分 メモリから周辺機器へ転送してくれます。 転送回数は1〜65535(0x0001〜0xFFFF)まで指定できます。 <転送回数の設定>  プログラムでは、「sizeof(data)」となっておりますが、これは、上の方のLED発光パターン テーブル「data[]」という配列に入っているデータの数と言う意味です。つまり、data配列には 15個のデータが入っているので、「sizeof(data)」= 15ということになります。 注意としては、sizeof()は関数ではありません。 ▼データトランスファコントロールレジスタ(DTCR) 「DMAC0A.DTCR.BYTE」 DMACの起動要因や、どのモードを使うのか?と言った 細かい設定をするです。8ビットのレジスタで構成さ れているので、一度に変更することも可能です。
7 6 5 4 3 2 1 0
DTE DTSZ DTID RPE DTIE DTS2 DTS1 DTS0
以上のような構成になっています。 ▼データトランスファイネーブル(DTE) データ転送の許可/禁止を設定できます。DTEビットは、DTE=0の状態をリードした(読み込んだ)後、 1をライトした(書き込んだ)とき1にセットされます。
BIT 7 説  明
DTE
0 データ転送禁止。I/Oモードとアイドルモードでは、指定された回数の転送を
終了したら0にクリア。
1データ転送許可
DTEの初期値は0(「転送禁止」状態) DTIE = 1の状態で、DTEが0にクリアされるとCPUに割り込みを要求します。 バイトサイズで一度に変更する場合は、ここを0のままにしておき、 後にビットサイズで0を1に変更します。。 (そうしないと動作しない可能性があります。) DTE = 0 ▼データトランスファサイズ(DTSZ) 1回に転送されるデータサイズを選択します。
BIT 6 説  明
DTSZ
0バイトサイズ転送(8bit)
1ワードサイズ転送(16bit)
DTSZの初期値は0(「バイトサイズ転送」状態) 今回のプログラムは8bit(1byte)の転送なので、バイトサイズ転送を選択します。 DTSZ = 0 ▼データトランスファインクリメント/デクリメント(DTID) I/Oモードまたはリピートモードの場合、データ転送後のMARのインクリメント/デクリメントを選択します。
BIT 5 説  明
DTID
0 データ転送後MARをインクリメント
(1)DTSZ=0のとき、転送後MARを+1
(2)DTSZ=1のとき、転送後MARを+2
1 データ転送後MARをデクリメント
(1)DTSZ=0のとき、転送後MARを-1
(2)DTSZ=1のとき、転送後MARを-2
アイドルモードの場合は、MARはインクリメントもデクリメントもされません。 今回のプログラムはdata[0]→data[14]のように、番号を増やしたいので、インクリメントを選択します。 DTID = 0 ▼リピートイネーブル(RPE) データ転送に、どのモードを使うか選択します。
BIT 4 BIT 3 説  明
RPE DTIE
0 0 I/Oモードで転送
1
0 0 リピートモードで転送
1 アイドルモードで転送
初期値0(「I/Oモード選択」状態)) 上の表を見ても分かるとおり、I/Oモードの時はDTIEの状態に影響されません。 今回のプログラムではI/Oモードを使うため、RPEは0のままです。 RPE = 0 ▼データトランスファインタラプトイネーブル(DTIE) DTEが0にクリアされたときのDTEによる割り込み(DEND)要求を許可/禁止します。 つまり、転送終了時の割り込みを許可するか禁止するか選択する。
BIT 3 説  明
DTIE
0DTEによる割り込み(DEND)要求を禁止
1DTEによる割り込み(DEND)要求を許可
初期値0(「転送終了割り込み禁止」状態)) 今回のプログラムでは、転送終了割り込みは使わないので、DTIEは0のままです。 DTIE = 0 ▼データトランスファセレクト(DTS2〜0) データ転送の起動要因を選択します。
BIT 2 BIT 1 BIT 0 説  明
DTS2 DTS1 DTS0
0 00 ITUチャネル0のコンペアマッチ/インプットキャプチャA割り込みで起動
1 ITUチャネル1のコンペアマッチ/インプットキャプチャA割り込みで起動
10 ITUチャネル2のコンペアマッチ/インプットキャプチャA割り込みで起動
1 ITUチャネル3のコンペアマッチ/インプットキャプチャA割り込みで起動
1 0 0 SCIチャネル0の送信データエンプティ割り込みで起動
1 SCIチャネル0の受信データフル割り込みで起動
1 0 DREQ端子の立下がりエッジ入力で起動(チャネルBの場合)
フルアドレスモード転送を指定(チャネルAの場合)
1 DREQ端子のLowレベル入力で起動(チャネルAの場合)
フルアドレスモード転送を指定(チャネルBの場合)
初期値(0,0,0)(「起動要因ITU0」状態)) 今回のプログラムではITU0のコンペアマッチA割り込みを使うので、DTS2=0,DTS1=0,DTS0=0となります。 今のところ、ITUのコンペアマッチ割り込みAしか使う予定がないと思います。 DTS2=0, DTS1=0, DTS0=0 ▼データトランスファコントロールレジスタ(DTCR)の設定 以上の8ビットの設定を全て合わせると...
7 6 5 4 3 2 1 0
DTE DTSZ DTID RPE DTIE DTS2 DTS1 DTS0
0 0 0 0 0 0 0 0
以上のようになり、「DTCR = 0x00」となります。 ▼DMAC設定の流れ DMACの設定は次の手順で行います。 1.転送元アドレス「MAR」の設定。 ↓ 2.転送先アドレス「IOAR」の設定。 ↓ 3.転送回数を「ETCR」に設定。 ↓ 4.DTCRの各ビットの設定。 ↓ 5.DTEビットを1に設定。 ↓ 6.I/Oモード ↓プログラムでもそのように設定します。 DMAC0A.MAR = &data[0]; DMAC0A.IOAR = 0xc3; DMAC0A.ETCR = sizeof(data); DMAC0A.DTCR.BYTE = 0x00; DMAC0A.DTCR.BIT.DTE = 1;  最後にDTEビットだけ変更しているのには理由があります。 DTEビットは特殊で、DTE=0の状態を読み込んでから1にしないと、DTE=1の状態になりません。 バイトデータ(DTCR.BYTEの部分)への代入だけでは、書き込んだだけで終了してしまい、 DTE=0の状態をリードする(読み込む)作業が抜けてしまうので、1にセットされません。 しかし、ビットサイズ(DTCR.BIT.DTE部分)での代入は以下のような特徴があります。 以上のように、3ステップを行います。 当然3段階の動作を行うためスピード自体は遅くなりますが、 そこまで神経質になるほど遅くなるとは思いません。 ▼起動要因ITU0の設定 DMACの起動要因として設定した、ITU0について説明していきます。 ITU0.TCR.BYTE = 0x23; /* GRAコンペアマッチ clock 1/8 */ ITU0.GRA = 0xffff; /* GRAを65535に設定 約33ms*/ ITU0.TIER.BYTE = 0xf9; /* ITU0のGRAによるコンペアマッチ割込みを許可*/ ITU.TSTR.BIT.STR0 = 0; /* ITU0 TCNTカウント停止*/ 細かい説明は、省略します。ここでやっている作業というのは、 以上4つの設定を行っております。 ▼TCRの設定 今回の条件は以下の通りです。 ・ITU0のGRAを使う。 ・立ち上がりエッジでカウント。 ・TCNTのカウントクロックをφ/8。 設定は以下の通りになり、ITU0.TCR.BYTE = 0x23;になります。
7 6 5 4 3 2 1 0
- CCLR1 CCLR0 CKEG1 CKEG0 TPSC2 TPSC1 TPSC0
0 0 1 0 0 0 1 1
▼GRAの設定  ここでは転送をするタイミングを設定します。前回のTCRでカウントクロックの設定をφ/8にしたのは、 目で追えるくらいのスピードにするためです。1クロックは、1 / ( 16MHz / 8 ) = 0.5μsecなので、 65535 * 0.5μsec = 32767.5となり、約33msecごとに、LEDが一つ移動していることになります。 0

[TOP]
SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送