Arduinoにはデジタル信号やPWM信号を出力するなどの機能が搭載されていますが、アナログ信号を出力する機能は搭載されていません。今回は、そんなアナログ信号の出力を実現するD/Aコンバータについて解説します。
目次
1. D/Aコンバータとは?
デジタル信号は、2進数の0と1で表現される信号です。電子回路の世界ではLOW/HIGHやON/OFFとして表すこともあります。一方、自然界の現象や人が感じる信号は全てアナログです。アナログ信号とは、大きさ・時間・量が連続しているのが特徴です。
D/Aコンバータとは、デジタル信号をアナログ信号に変換して出力する変換器を指します。
音声や映像はアナログ信号です。ところが近年では音声処理や画像・映像処理にデジタル信号を必要とします。デジタル信号はアナログ信号処理では対応できないフィルタ処理や圧縮に使われ、その処理されたデジタル信号をアナログ信号に戻すための変換にD/Aコンバータが使われています。
2. ArduinoでD/Aコンバータを使うには?
ArduinoにはArduino UnoやArduino Leonardo、Arduino Nanoなどさまざまな種類があります。
D/Aコンバータを内蔵するArduinoには、Arduino Dueがあります。しかし、DueはArduino Unoと形状が異なり、動作電圧も3.3Vと使い勝手が少し異なります。
広く普及しているArduino Unoでアナログ信号を出力するには、D/AコンバータICを外付けします。
3. D/AコンバータをArduinoで動かしてみよう
今回は、D/Aコンバータを搭載するシールドを使わず、D/AコンバータICを直接Arduinoに接続してアナログ信号を出力できるか試してみます。使用するのはROHMのD/AコンバータIC「BH2219FVM」(分解能-8bit、2ch)です。
このDAコンバータは、I2CやSPI方式のD/Aコンバータと異なり、3線式のシリアル通信で動作します。特定のプロトコルに準拠しないオリジナルの方式なので、スケッチ内でD/Aコンバータが正しく動くようにシリアル通信する処理を自分で作成する必要があります。
BH2219FVMの動かし方はデータシートに記載されています。データシートには、最大定格や電気的特性のほか、どのように信号を加えたら動作するかの仕様が記載されているので、これを見ながら処理方法を考えていきます。
BH2219FVMの制御方法はシンプルです。DI端子に、アナログ信号を出力するポート(4bit)と8bitのデータを続けて(4bit+8bit=12bit)1bitごと順次転送していきます。BH2219FVMがDI端子を読み込むのはCLK端子の立ち上がりです。12bitの転送が終わった後、LD端子をONすればデータに応じたアナログ信号が出力されます。
AO1ポートに2.5Vの電圧を出力する例で説明します。
最初の4bitに設定する「AO1ポート」を示す固定値は上記の表「チャンネル設定(BH2219FVM)」より「0000」です。
続くデータに設定するのは「2.5V」を示す8bitの値であり
「256 × 出力電圧 ÷ 電源電圧 = データ」
で求めます。結果は
「256 × 2.5[V] ÷ 5[V] = 128」
となります。
2.5Vは「電源電圧の半分」なので、データは「8bitで表現できる数(256)の半分」となり128です。128を2進数で表現すると「10000000」となります。
この「0000」と「10000000」を、CLK端子がONするタイミングに合わせて、DI端子を順次ON/OFF(1/0)することでBH2219FVMに送ります。最後にLD端子をON、OFFすればBH2219FVMからアナログ信号の2.5Vが出力されます。
下のスケッチは、ArduinoからBH2219FVMにデータをシリアル通信で送り、BH2219FVMのAO1端子に「のこぎり波」を出力するスケッチです。
#define LD 8 #define CLK 7 #define DI 6 boolean D[12]; //入力した数値を2進数に変換して配列D0~D7に格納 int binary(int out) { int i = 0; for (i = 0; i < 8; i++) { D[i] = out % 2; out = out / 2; } } //出力ポート指定を配列D8~D11に格納 int chSelect(int outPin) { int i; if (outPin == 1) { for (i = 8; i < 12; i++) { D[i] = LOW; } } else if (outPin == 2) { D[8] = HIGH; for (i = 9; i < 12; i++) { D[i] = LOW; } } } //配列をDACに出力 void outConfirm() { int i; for (i = 11; i >= 0; i--) { digitalWrite(DI, D[i]); digitalWrite(CLK, HIGH); digitalWrite(DI, LOW); digitalWrite(CLK, LOW); } digitalWrite(LD, HIGH); digitalWrite(LD, LOW); } //出力値と出力ポートを各変数に渡す int operationOut(int value, int ch) { binary(value); chSelect(ch); outConfirm(); } void setup() { //DAコンバータに使うピンの初期化 pinMode(LD, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DI, OUTPUT); digitalWrite(LD, LOW); } void loop() { int i; //のこぎり波を出力 for (i = 0; i <= 255; i++) { operationOut(i, 1); //AO1出力に固定 } }
このスケッチは理解のしやすさを重視しているため効率は良くありません。変換周期は少し遅めです。
それぞれの動作について、スケッチの中を1つずつ解説します。
#define LD 8 #define CLK 7 #define DI 6 boolean D[12];
最初に、接続するD/Aコンバータの端子とArduinoのピン番号を対応付けます。LDが8番ピン、CLKが7番ピン、DIが6番ピンです。続いてDI端子に送る12bitの情報を格納する配列Dを定義しています。12bitのそれぞれに対応する0/1を格納するため配列の要素も12個必要です。型は0と1しか格納しないのでboolean型を使用します。
void setup() { //DAコンバータに使うピンの初期化 pinMode(LD, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DI, OUTPUT); digitalWrite(LD, LOW); }
setup関数では、使用するピンの動作を設定します。3本のピンは全て出力に使用します。LD端子は、状態が不定になるのを防ぐためにsetup関数内でLOWを指定します。
void loop() { int i; //のこぎり波を出力 for (i = 0; i <= 255; i++) { operationOut(i, 1); //AO1出力 } }
loop関数では、後述のD/Aコンバータに信号を出力するoperationOut関数に「出力するデータ」と「ポート番号」を渡しています。「出力するデータ」にはfor文を使って0から255まで段階的に増加する値を設定します。loop関数が呼び出されるごとにこの動作が繰り返されるので、出力される電圧は「のこぎり波」となります。
int operationOut(int value, int ch) { chSelect(ch); binary(value); outConfirm(); }
operationOut関数では、引数を実際の処理を行う関数に渡しています。ポート番号(上位4bit)を0/1に展開するchSelect関数、出力するデータ(下位8bit)を0/1に展開するbinary関数、配列Dの中身をD/Aコンバータに転送するoutConfirm関数の3つを呼び出します。
int chSelect(int outPin) { int i; if (outPin == 1) { for (i = 8; i < 12; i++) { D[i] = LOW; } } else if (outPin == 2) { D[8] = HIGH; for (i = 9; i < 12; i++) { D[i] = LOW; } } }
chSelect関数では、上位4bitの操作を行っています。ポート番号1が引数から指定されると配列Dの8番から11番の要素に0を格納します。ポート番号2が引数から指定されると配列Dの8番の要素に1を格納、9番から11番の要素に0を格納します。
int binary(int out) { int i = 0; for (i = 0; i < 8; i++) { D[i] = out % 2; out = out / 2; } }
binary関数では、渡された10進数を8bitの2進数に変換してD[0]からD[7]に格納します。このテクニックは2進数変換で多用されています。「2で割った余りを求めて(一番下のbitは0/1のどちらか?)、2で割る(一番下のbitを捨てる)」をfor文で繰り返すことで「一番下から順次2進数に変換」しています。
void outConfirm() { int i; for (i = 11; i >= 0; i--) { digitalWrite(DI, D[i]); digitalWrite(CLK, HIGH); digitalWrite(DI, LOW); digitalWrite(CLK, LOW); } digitalWrite(LD, HIGH); digitalWrite(LD, LOW); }
最後のoutConfirm関数では、配列D内のデータをD/Aコンバータに転送してアナログ信号を出力しています。
配列Dの中の0/1はD/Aコンバータに転送する順番の逆の並びとなっています。そこでfor文を11から0まで降順でループしながら配列Dの中を正しい順番で出力しています。ループの中ではCLKの処理も同時に行っています。for文が終了した後、LD端子の立ち上がり信号を加えると、転送したデータに応じたアナログ信号がD/Aコンバータから出力されます。
以下は各ポイントの電圧をオシロスコープで観測した図です。
4. 機械のデジタルと人のアナログを繋ぐD/Aコンバータ
デジタル化の進歩に伴い、D/Aコンバータは、AV機器や通信機器、モーター駆動機器などさまざまな製品に使われています。
ArduinoにD/Aコンバータを使ったアナログ信号処理を加えることでいろいろなシーンでの活用が考えられます。
今回の例では、データの転送方式が少し複雑に感じたかもしれませんが、I2CやSPIなどの汎用的なデータ転送規格を持つD/Aコンバータであれば、より簡単に使うことができます。アナログ出力が必要な時は、一度D/Aコンバータの制御に挑戦してみてはいかがでしょうか。