|
A/Dコンバータ
イントロダクション マイコンはれっきとしたデジタルコンピュータです。したがって扱えるのは0Vと5Vの2種類だけで、アナログ電圧は扱えません。しかし、世の中にはセンサ出力としてアナログ電圧を出してくる物や、自身のバッテリー残量を計るためにアナログ電圧を測定したい場面があることも確かです。
そんなときにはA/Dコンバータを使います。普通はA/Dコンバータのチップが別売であって、それを汎用ポートに繋ぐのですが、幸いにもH8/3048FにはA/Dコンバータが8個も付いています。 ちなみにA/Dコンバータというのはアナログ電圧をデジタル数値に変換してくれる装置のことで、たとえばこのH8の場合には標準では0〜5Vを1024段階(10ビット)に分割して評価してくれます。だから、0Vならば0,5Vならば1023、2.5Vならば511という具合になります。ハードウェア的な構造は知ってしまうと「おぉっ」と感動してしまうので、自分で調べてみてください。意外と簡単です。
ひとつ注意しておかなければいけないことは、A/Dコンバータは決して瞬時に電圧をデジタル信号に変換してくれるわけではないことです。16MHz駆動の場合にはどんなに急いでも8325nsec必要です。1クロックが62.5nsecである事から考えると結構時間が掛かっていることが分かりますね。「十分やん」と思った人もいるかと思いますが、事実私はこの時間に泣かされました。
A/Dコンバータを使う
そろそろ決まり文句になってきましたが、まずはこのファイルをダウンロードしてください。A/Dコンバータも割り込み機能が使えますが、今回は省略します。興味があったら一度挑戦してみてください。と言っても、ノリはここまでに述べたシリアルの割り込みやタイマーの割り込み、この後に述べる外部割り込み(IRQ)と同じですので、すんなりできるはずです。
さて、サンプルプログラムの機能ですが、AN0(Port7-0)のアナログ入力をA/D変換し、それをPort-BのLEDに8ビットで出力するという物です。
| main.c
#include "iodefine.h"
void
main(void); void initADC(void); int ADconv(void);
#ifdef
__cplusplus } #endif } void main(void) { PB.DDR
= 0xff; //
Port-Bを出力に設定 PB.DR.BYTE
= 0x00;
initADC();
//
A/Dコンバータ初期化
while(1){
PB.DR.BYTE
= (ADconv() >> 2); }
}
|
今回は2つの関数を使っています。initADC(void)と、ADconv(void)です。どちらも自分で作った物ですから、気に入らなければ適宜編集して使ってください。
全体の流れですが、まずは初期化を行い、次にA/D変換を行った値を右に2ビットシフトさせてPort-Bにその値を8ビットで出力させています。 なぜ2ビットのシフトが必要かですが、H8のA/D変換は10ビットの分解能を持っています。したがって、得られるデータも当然10ビットなわけですが、Port-Bは8本しか線がないわけで、8ビットまでしか表現できません。だから分解能をわざと落として表現できるようにしているのです。こうしないと細かい値だけが見えて、肝心の一番大きな位と、その次に大きな位が分かりません。
初期化のルーチンは次のようになっています。
| main.c
void initADC(void) { AD.DRA
= 0; //
データレジスタをクリア AD.CSR.BIT.ADF
= 0; //
A/D変換終了フラグをクリア AD.CSR.BIT.ADIE
= 0; //
A/D変換終了割り込みを禁止 AD.CSR.BIT.ADST
= 0; //
A/D変換スタートフラグをクリア AD.CSR.BIT.SCAN
= 0; //
A/D変換は単一モード AD.CSR.BIT.CKS
= 1; //
変換時間は134ステート(高速) AD.CSR.BIT.CH
= 0; //
A/D変換はAN0ポートを使用 AD.CR.BYTE
= 0; }
|
H8には8本のA/Dコンバータがあると言いましたが(内2本はD/Aコンバータ兼用)、今回は1つしか使いませんので、「単一モード」と呼ばれるモードを使います。もし、複数のA/Dコンバータを使う必要のあるときには「スキャンモード」を使います。 変換時間は、速いに越したことはないだろうと言うことで134ステートにしています。ちなみにステート=クロックと考えてください。 変換に使うポートは今回はAN0番(Port7-0)を使いますのでCH
= 0です。各ポートの対応は下の表のようになります。
|
AD.CR.BIT.CHの値
|
使うポート
|
|
0
|
AN0(Port7-0)
|
|
1
|
AN1(Port7-1)
|
|
2
|
AN2(Port7-2)
|
|
3
|
AN3(Port7-3)
|
|
4
|
AN4(Port7-4)
|
|
5
|
AN5(Port7-5)
|
|
6
|
AN6(Port7-6)
|
|
7
|
AN7(Port7-7)
|
A/D変換の本体のルーチンは次の通りです。
| main.c
int ADconv(void) { int
ADdata;
AD.CSR.BIT.ADST
= 1; //
A/D変換開始 while(!AD.CSR.BIT.ADF)
; // 変換終了待ち ADdata
= AD.DRA >> 6; //
変換されたデータをADdataに格納 AD.CSR.BIT.ADF
= 0; //
変換終了フラグをクリア
return(ADdata);
//
変換した値を持って返る }
|
ADSTというレジスタに「1」を書き込むとA/D変換が始まります。変換するポートはinitADC(void)で指定したとおりですから、今回はAN0です。 変換が終わるとADFというフラグが「0」から「1」に変化しますので、それを確認したらデータを変数に格納します。ここでひとつ注意しなければいけないのは、AD.DRAというレジスタは本来16ビットのレジスタなのですが、変換したデータは10ビットでしかも左詰になっています。図で示すと次のようなイメージです。
|
AD9
|
AD8
|
AD7
|
AD6
|
AD5
|
AD4
|
AD3
|
AD2
|
AD1
|
AD0
|
0
|
0
|
0
|
0
|
0
|
0
|
下6ビットは0が詰まっています。したがって、変数に値を代入するときには右に6ビットシフトしておいた方が何かと都合がよいのです。
最後に次回のA/D変換のために変換終了フラグを元に戻して置いてから関数の呼び出し元に返ります。 もちろん、手ぶらで返るわけには行かないので、変換したデータはしっかりと持って返ります。
Tips A/D変換を行うには「基準電圧」という物が必要です。つまり、GNDと測定上限の電圧の2つを明確にしておき、それに対して入力電圧はいったいどのくらいの電位であるのかを求めるわけです。AKI-H8には標準では基準電圧が5V(本体の駆動電圧と同じ)になっていますが、ボードに少し細工をすることで12Vでも24Vでも(この辺になってくると少し怖いですが)測定することができます。
さて、携帯電話をはじめ、大抵の機器にはローバッテリーアラームが付いていることはご存じだと思います。あれは、親電源がレギュレータを通したときに、そのドロップ分を計算に入れると駆動電圧に満たなくなる電圧になると警報を出すわけです。
でもここで「あれ?」と思いませんか。そう、測定したい物は親電源の電圧です。しかし、それは安定している基準電圧(=駆動電圧)よりも大きいので測定できませんし、親電源そのものを基準電圧としたばあいには、そもそも基準電圧が下がってくるわけですから、いつまでたってもA/D変換の結果は100%フル充電のままです。
実はそういう場合には下図のような細工をします。
これだと、仮に抵抗値を同じ値としたとき、A/Dコンバータに入る電圧は親電源の半分になります。この値が基準電圧であるVcc以下ならば正確に測定できますね。知った瞬間、私は思わず感動してしまいました。


|