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モードは次のようなものです。
- 1回の転送要求をされたら1バイト、もしくは1ワードずつ転送を行う。
- 指定された回数だけ転送を繰り返し行う。
- 通常は、「MARで指定されるアドレス⇒IOARで指定されるアドレス」という風に転送されます。
- 配列に記入してあるデータを周辺機器へ転送可能。
- IOARはデクリメントもインクリメントもされない。
- 転送回数は最大65336まで設定可能。
- 起動要因は、ITUチャネル0〜3のコンペアマッチ/インプットキャプチャA割り込み、
SCI0の送信データエンプティ、受信データフル割り込み、及び外部リクエスト等
▼プログラムでの設定
さぁ、いよいよプログラムの説明に入っていきたいと思います。(「説明」と言うよりも、
「設定の仕方」と言った方がいいかもしれません。)
まず、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」のアドレスです。
P1DR | P2DR | P3DR | P4DR | P5DR | P6DR | P7DR | P8DR | P9DR | PADR | PBDR |
0xC2 | 0xC3 | 0xC6 | 0xC7 | 0xCA | 0xCB | 0xCE | 0xCF | 0xD2 | 0xD3 | 0xD6 |
補足: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 |
0 | DTEによる割り込み(DEND)要求を禁止 |
1 | DTEによる割り込み(DEND)要求を許可 |
初期値0(「転送終了割り込み禁止」状態))
今回のプログラムでは、転送終了割り込みは使わないので、DTIEは0のままです。
DTIE = 0
▼データトランスファセレクト(DTS2〜0)
データ転送の起動要因を選択します。
BIT 2 |
BIT 1 |
BIT 0 |
説 明 |
DTS2 |
DTS1 |
DTS0 |
0 |
0 | 0 |
ITUチャネル0のコンペアマッチ/インプットキャプチャA割り込みで起動 |
1 |
ITUチャネル1のコンペアマッチ/インプットキャプチャA割り込みで起動 |
1 | 0 |
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カウント停止*/
細かい説明は、省略します。ここでやっている作業というのは、
- TCR:タイマーコントロールレジスタ
- GRA:ジェネラルレジスタA
- TIER:タイマーインタラプトイネーブルレジスタ
- TSTR:タイマースタートレジスタ
以上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]