D/A変換器・A/D変換器
この節では、ディジタルシステムであるCPUボードが、
例えば2.1Vというようなアナログ値の電圧を扱うための
デバイスとその扱い方をみていくことにしましょう。
I/Oポート
このボードに載っているCPUには、メモリやメモリマップドI/Oに対する
アクセス以外にも、1ビットのディジタル値(0または1)に応じた
電圧(1=高い電圧3V、0=低い電圧0V)を出力するための
I/Oポートという端子があります。
このEZ-USB FX2にはポートA(PA)からポートE(PE)までの5種類の
I/Oポートがあり、それぞれのポートは8本(8ビット)の0または1の値を
出力、あるいは入力できるピンから成り立っています。
もちろんその出力したい値、または入力した値は、
プログラムで設定したり、読み出したりすることができます。
ここでは、ポートE(PE)を使ってみることにしましょう。
I/Oポートを使用するときには、プログラムの最初に、
各ピンを入力あるいは出力のどちらとして使用するかを
設定する必要があります。
この設定はOEE(Output Enable E)という特殊な変数(実際には
メモリマップドされた1バイト変数)に値を書き込むことで行います。
具体的には、出力として使用したいピンの桁を1、
入力として使用したいピンの桁を0とした8ビットの2進数(1バイト)の
値を設定します。
演習
ポートE(PE)の0番(PE0)、PE1、PE6、PE7だけを出力、
残りのPE2, PE3, PE4, PE5を入力として使うときに
OEEに書き込むべき値はいくつになるでしょうか。
I/Oポートの操作
I/Oポートの各ピンに0または1の値を出力したり、
入力ピンの値を読み取るためには、IOEという特殊な変数を用います。
例えばPE0が出力として設定されているとして、
IOEの0ビット目を1にすると、PE0の値が1(つまり高い電圧3V)となります。
逆にPE3が入力として設定されているとすると、
IOEの3ビット目を読み取ると、PE3に加えられている電圧が
高い(3V=1)か低い(0V=0)かを知ることができます。
ビット演算・シフト
このように特にI/Oポートを操作するときには、特定のビットのみを
1または0にしたり、特定のビットの値のみを知る必要が生じます。
このようなときには、ビット演算と呼ばれる方法が便利です。
ビット操作は、AND演算子(&)やOR演算子(|)やNOT演算子(~)を用いて
変数の中の特定のビットのみを操作する技法で、
例えば次のような記述によって行うことができます。
変数aの0ビット目(最下位ビット)のみを1にする |
a = a | 0x01; |
変数aの2ビット目のみを1にする |
a = a | 0x04; |
変数aの4ビット目のみを0にする |
a = a & ~0x10; |
変数aの6ビット目が0であるかを調べる |
(a & 0x40)==0 |
また数ビットだけ左または右にシフトするための
演算子として、「<<」と「>>」があります。
例えば「a >> 1」は、変数aの値を1ビットだけ右にシフトした
値をあらわすことになります。
したがって例えば a = 0x32; であれば、
「a >> 1」は0x19となることになります。
なお、「a >> 1」自体は、変数aの値を1ビット右にシフトした値を計算しますが、
変数aに格納されている値自体は変化させないことに注意してください。
演習
これらのビット演算・シフト演算の内容を理解しましょう。
D/A変換器のいじり方
アナログ値の電圧を出力するための装置を
D/A変換器 (Digital-to-Analog Converter)と呼びます。
このボードには、TLV5626という型番のD/A変換器が載っていますので
これを扱うことにしましょう。
このD/A変換器の扱い方を知るためには、
D/A変換器TLV5626のデータシートを
読むことになります。
要点は、次のようなことになります。
- SCLK、DIN、/CSという3本の信号線で制御される
- これらを、p.6のFigure1(図1)のように変化させて与える
p.6の図1を見ると、D/A変換器TLV5626に与えるデータは
1ビットずつ、D15からD0までの16個を、この順に
SCLKに変化にあわせて与えること、そして最初に/CSを0にし、
最後に/CSを1にすること、を読み取ることができます。
具体的には、
- /CS=1, SCLK=1の状態から始める
- まず/CS=0とする
- 最初のデータ(ビット)をDINに設定し、SCLK=0とする
- 少し待つ
- SCLK=1とする
- 少し待つ
- 3.〜6.を16回繰り返す
- /CS=1とする
つまりD/A変換器TLV5626へのデータの転送は16ビット(2バイト)を
単位として行うことになります。
そして送るべきデータは、次の2組の16ビット(2バイト)となることが
記述されています。
- 0xd0 と 0x02 の組 (動作:内部参照電圧を設定する)
- [0xc0と出力値の上位4ビットを下位4ビットにシフトした値] と [出力値の下位4ビットを上位4ビットにシフトした値] の組
(この動作はD/A変換器の設計者が決めた「仕様」です。)
ここで「出力値」とは、D/A変換器から出力したい電圧を示す
数値で、次のような対応関係となっています。
出力値 | 出力電圧[V] |
0 | 0.00 |
.. | .. |
128 | 2.048 |
.. | .. |
255 | 4.096 |
つまり出力値=0(0x00)の0.00V、出力値=255(0xff)の4.096Vが両端で、
その間は、出力値と出力電圧が比例していることになります。
つまり出力値xに応じた出力電圧は、
(x ÷ 255) × 4.096 [V]と表すことができます。
この「出力値」を、上位4ビットと下位4ビットにわけ、
D/A変換器に送ることになるわけですが、
例えば値0x12を送る場合には、2回目の転送で
次のような2バイトを送ることになります。
演習
出力値0x12をD/A変換器に送るとき、
送るべき2バイトのデータがこのようになることを確認しましょう。
(ヒント:出力値が、どのように2バイトのデータに埋め込まれているか、を
考えるのがポイント。最初の「0xc0」は、おまじないと考えてかまいません。)
D/A変換器への値の転送プログラム
D/A変換器を操作する、次のような3つの関数があった仮定をしましょう。
- CS_DAC(x) : D/A変換器の/CSの値をx(x=0 or 1)に設定する
- CK(x) : D/A変換器のSCLKの値をx(x=0 or 1)に設定する
- DACwrite_byte(x) : SCLKとDINを操作し、D/A変換器に1バイトの値xを
転送する
この3つの関数を使うと、D/A変換器から出力値d(d=0x00〜0xff)の電圧を出力する
ためには、次のようなプログラムを記述すればよいことになります。
(わかりやすいように日本語でコメントを書いてありますが、
実際のエディタでは日本語は文字化けします。)
void DACwrite(unsigned char d)
{
/* 内部参照電圧を設定するおまじない */
CS_DAC(1); CK(1); /* 初期状態 */
CS_DAC(0);
DACwrite_byte(0xd0); DACwrite_byte(0x02);
CS_DAC(1);
/* 出力値dに対応する電圧を発生させる */
CS_DAC(0);
DACwrite_byte(0xc0 | (d >> 4));
DACwrite_byte(d << 4);
CS_DAC(1);
}
なお「>>」は、指定したビット分だけ右にシフトする演算子で、
例えば"0x53 >> 4"は0x05となります。
同様に「<<」は、指定したビット分だけ左にシフトする演算子です。
演習
このプログラムの動作を解読して、
D/A変換器から出力値dに対応した出力電圧が得られることを確認しましょう。
(第1日目はこのあたりまでを目安とするとよいでしょう)
D/A変換器の操作
先ほどののI/Oポート操作とビット演算を用いると、
D/A変換器TLV5626を操作するための、最低限の関数を
記述することができます。
D/A変換器TLV5626は、最終的には、SCLK, DIN, /CSという3本の信号線で
制御されますが、
これらは、次のような対応で、ポートEの各ピンに接続されています。
- SCLK : PE0
- /CS : PE2
- DIN : PE3
そこで、これらの値を0(低い電圧0V)または1(高い電圧3V)とするための
関数を次のように記述しておくと便利でしょう。
/* CK(d): D/A変換器のSCLKをd (d=0 or 1)に設定する関数 */
void CK(char d) {if (d == 1) IOE |= 0x01; else IOE &= ~0x01;}
/* CS_DAC(d): D/A変換器の/CSをd (d=0 or 1)に設定する関数 */
void CS_DAC(char d){if (d == 1) IOE |= 0x04; else IOE &= ~0x04;}
/* DO_DAC(d): D/A変換器のDINをd (d=0 or 1)に設定する関数 */
void DO_DAC(char d){if (d == 1) IOE |= 0x08; else IOE &= ~0x08;}
演習
これらの関数・ビット演算子などを活用し、
D/A変換器に出力値dに対応した電圧を出力するための
関数void DACwrite(unsigned char d)を完成させましょう。
CK(), CS_DAC(), DO_DAC()は前述の通りです。
関数void DACwrite_byte(unsigned char d)
の中身を作成します。
演習
D/A変換器に出力する電圧をスイッチで制御でき、
またそのときの出力値を7セグメントLEDに数値として表示するプログラムを記述し、
実行させてみましょう。
(例えば電圧値が、スイッチSW0を押すと1ずつ増加、SW1を押すと10ずつ増加、
SW2を押すと1ずつ減少、SW3を押すと10ずつ減少する、など)
D/A変換器から出力される電圧は、ボード上の「Aout」端子に現れます。
テスターを用いて、
こことGND(グランド=基準電圧)との間の電圧を計測して記録しましょう。
テスター接続用ケーブルを使用すると便利です。
A/D変換器
アナログ値の電圧を、ディジタル値に変換し、コンピュータに
取り込むための装置をA/D変換器 (Analog-to-Digital Converter)と呼びます。
このボードには、TLC548という型番のA/D変換器が載っていますので
これを扱うことにしましょう。
このA/D変換器は、与えられたアナログ電圧を次のような
1バイトのディジタル値に変換することができます。
与える電圧[V] | 変換値 |
0.0 | 0x00 |
.. | .. |
2.5 | 0x80 |
.. | .. |
5.0 | 0xff |
このA/D変換器の扱い方を知るためには、
A/D変換器TLC548のデータシートを
読むことになりますが、要点は以下のとおりです。
- CLOCK、DATAOUT、/CSという3本の信号線で制御される
- これらを、p.3の図のように変化させて与え、DATAOUTを読み取る
p.3の図を見ると、A/D変換器TLC548に与えるデータは、
/CS=1の状態から始めて、
まず/CS=0とした後にクロックCLOCKを0→1の変化を8回与え、
その後/CS=1とします。
そしてしばらく待ち、
再び/CS=0とした後でCLOCKの0→1の変化を8回与えながら、
A/D変換器から出力されてくるDATAOUTを1ビットずつ読み取り、
最後に/CS=1とすることになります。
このA/D変換器がディジタル値に変換した値は1バイト(8ビット)の
数値で、DATAOUTに最上位ビット(B7)から最下位ビット(B0)の順に
現れることがわかります。
これらの3つの信号線は、ポートE(PE)に以下のように接続されています。
- CLOCK : PE0
- /CS : PE1
- DATAOUT : PE4
このうちDATAOUTが接続されているPE4だけは、
A/D変換器から出力される値を読み取る必要がありますから、
「入力」に設定する必要がありますが、
その他のPE0, PE1, PE2, PE3は「出力」と設定することにしましょう。
なお/CS=0の期間にCLOCKを8回与える箇所が2回ありますが、
この1回目でアナログ電圧をディジタル値に変換だけ行われ、その結果を2回目に読み出しているためです。
そのため必ず2回必要です。
演習
PEの各ピンの入出力を上記のように設定するためには、
OEEにどのような値を設定すればよいでしょうか。
A/D変換器の操作
上記のことをふまえ、D/A変換器およびA/D変換器を操作するために
最低限必要な5本の信号線の値を0(低い電圧0V)または1(高い電圧3V)に
設定したり、A/D変換器から出力される値を読み取るための
関数を、次のように定義しておくことにしましょう。
/* CK(d): D/A変換器のSCLKをd (d=0 or 1)に設定する関数 */
void CK(char d) {if (d == 1) IOE |= 0x01; else IOE &= ~0x01;}
/* CS_ADC(d): A/D変換器の/CSをd (d=0 or 1)に設定する関数 */
void CS_ADC(char d){if (d == 1) IOE |= 0x02; else IOE &= ~0x02;}
/* CS_DAC(d): D/A変換器の/CSをd (d=0 or 1)に設定する関数 */
void CS_DAC(char d){if (d == 1) IOE |= 0x04; else IOE &= ~0x04;}
/* DO_DAC(d): D/A変換器のDINをd (d=0 or 1)に設定する関数 */
void DO_DAC(char d){if (d == 1) IOE |= 0x08; else IOE &= ~0x08;}
/* DI_DAC(): A/D変換器のDATAOUTを読み取る関数 */
char DI_ADC(){if ((IOE & 0x10) == 0) return(0); else return(1);}
演習
これらの関数・ビット演算子などを活用し、 A/D変換器に与えられている
アナログ電圧を1バイトのディジタル値に変換した値を返す
関数unsigned char ADCread(void)を記述し、実行させてみましょう。
続いて、A/D変換器の入力に、先ほどのD/A変換器の出力を与え、
D/A変換器から適当なアナログ電圧を出力し、それをA/D変換器で
取り込んで、その値を7セグメントLEDなどに表示させて、
両者を記録して対応関係を調べましょう。
なおこのとき、D/A変換器の出力(Aout)とA/D変換器の入力(Ain)を
接続することになりますが、
この接続では
テスター接続用ケーブル
を用います。
テスター接続用ケーブルは、AoutとAinを接続し、
かつ、テスターでAoutの電圧を測定できるようになっています。
インバータの入出力電圧特性の計測
A/D変換器・D/A変換器を活用し、ディジタル論理ICである
SN7404の入出力特性を計測してみましょう。
SN7404はインバータ(否定ゲート)が6個入ったICです。
このボードのVDDを電源電圧(+5V)、GNDをグランド(0V)に接続し、
INに与える電圧を変えたときにOUTに現れる電圧の
関係を調べてみることにします。
インバータでは、入力が0(低い電圧)のときには出力が1(高い電圧)、
入力が1(高い電圧)のときには出力が0(低い電圧)となるはずですので、
おおまかには次のようなグラフとなると考えられます
(図の横軸はいい加減です)。
そこでD/A変換器の出力である「Aout」をインバータのINに、
またインバータの出力をA/D変換器の入力である「Ain」に、
ケーブルを用いて接続します。
演習
D/A変換器に出力する電圧をスイッチによって制御し、
A/D変換器で読み取った電圧を7セグメントLEDに表示する
プログラムを記述し、インバータSN7404の入出力特性を
計測してみましょう。
なお7セグメントLEDには、D/A変換器から実際に出力されている電圧と
A/D変換器で読み取った電圧を、それぞれディジタル値から実際の電圧に
換算し、小数点以下1桁で表示をするよいでしょう。
なおインバータSN7404は、フラットケーブルで接続しますが、
接続する向き(VDD・GNDどうしが接続されているか、など)、
ケーブルがつながっている箇所などに注意します。
注意とヒント:
- 実験用CPUボードのCコンパイラでは実数型 (floatとdouble) を使えません。
- 実定数も使えません。
1.0などの小数点を含む定数、1e-10などの指数を含む定数は使えません。
- 小数点以下1桁の実数値を扱いたい場合には、
10倍 (100倍や1000倍でも可) して整数演算すると良いでしょう。
- 代入できる数値の限界
に注意しましょう。
- 接続ケーブルの配線
戻る