DAコンバータ MCP4922(SPI)を利用しD/A変換を行う

〔マイコン使用による電子部品の使い方に戻る〕


《PWM制御とは(PWMについて)》

PWM(Pulse Width Modulation)はパルス幅変調方式と言い、パルスの波(方形波)のON(HIGH)と
OFF(LOW)幅の比(デューティー比)を変化させて変調する制御方式です。
マイコンに内蔵しているアナログ出力はこの方式を採用しています。

PWM1
 この左図の様にT1とT2の時間を変化させます。
 T1とT2が同じ時間ならデューティー比は50%となります。

 また、マイコンのPWM出力でのON(HIGH)電圧は、
 マイコンに供給している電源と同じ電圧で出力されます。

ArduinoのPWM出力設定値は0〜255の値を指定して0〜5Vに変換し出力されます。
ArduinoをUSBにて接続した場合、ArduinoのPOWER-5V電源端子からはテスター測定値5.08Vでした。
PWM設定値 0 でのPWM出力端子(3番ピン)からの測定値は4.3mVで、設定値 255では5.07Vでした、
だから実際は 0〜255指定で4.3mV〜5.07Vに変換して出力されます。
秋月電子のLCDオシロキットでArduinoのPWM出力(3番ピン)波形を見てみましょう。
(尚、ArduinoはPWM出力可能端子は3,5,6,9,10,11番ピンとなっています。)

デューティー比50%
PWM(50%)波形
 analogWrite( 3 , 255/2 )を実行した波形です。
 出力電圧は約5VをON・OFFしていますね。
 T1(ON)とT2(OFF)の時間がだいたい半分ずつで、
 約1ms程(1周期なら2ms)ずつでしょうか。
 この出力をテスターで電圧測定すると2.529Vでした。
 周波数を測定した結果は490Hz(1秒間に490周期)でした。
 1000ms/490=2.04msなので計算でもだいたい合っていますね。


デューティー比30%
PWM(30%)波形
 analogWrite( 3 , 77 )を実行した波形です。
 T1(ON)が30%でT2(OFF)が70%の比です。
 よって、T1(ON)が0.6ms程ですね。
 この出力をテスターで電圧測定すると1.534Vでした。

 だからぁ、5V出力の方形波でもOFFの部分が有るからぁ、
 そのぶん、実際の出力電圧は5Vよりもぉ、
 低くなると言う事ですよね。

《DAC(デジタルーアナログコンバータ)》

今度は、マイコン内蔵のアナログ出力(PWM)ではなく専用ICのMCP4922を使用した記事です。
マイコン内蔵は 0-255の分解能ですが、このICは12ビットなので 0-4095の分解能です。
なので、0-255を0〜5Vに変換なら1カウント当たり19.5mVですが、0-4095なら1.2mVって事です。
(0-4095の分解能で0V〜VREFまでの電圧に変換します。)
また、MCP4922にはDACの回路が2個分入っていて、インターフェースがSPI通信です。

尚、こちらにI2C通信で行うMCP4725を使ったモジュールが有ります、
このMCP4725は、回路は1個分ですが、アドレスが2種類設定可能なので2個まではOKでしょう、
MCP4725モジュールの記事はこちらに追加しました、参照下さい。 *1)

まずは、LCDオシロキットでMCP4922が出力したアナログ電圧値の波形を見てみましょう。

DAC波形1  ArduinoはUSBで接続しています。
 VREFはArduinoの5.08V電源出力に配線しています。
 データ値 0〜4095内で 4095/2 と半分指定しました。
 なのでぇ、半分の2.537V(テスター計測値)が出力されています。
 また、テスターで計測した結果、データ値 0 指定では1.2mVで、
 4095指定では5.06〜5.07V辺りをふらふらと出力しています。
 左の写真も2.5V付近をふらふらして一直線でないですよね。
 これはUSB接続(PC)からのノイズが乗っている為です。


DAC波形2  この波形を見て下さい、
 USB接続から外部の電池接続に変更して取った波形です。
 一直線ですねぇ。
 テスターで計測しても2.5Vピッタリでした。(VREF=5V)

 上の方で記載のPWM制御だとデジタルON/OFFを行っての
 なんちゃってぇアナログ出力でしたが、
 MCP4922を利用すると左波形の様にちゃんとした一直線の
 アナログ出力が可能になります。


MCP4922概要  MCP4922はSPI接続なので左図の様に複数個接続が
 可能です。
 動作としては
 @アナログ出力したい方のCS(SS)信号をLOWにする
  (MCP4922(1)に出力するならSS1端子をLOWです。)
 ALDAC信号はHIGHにする。
 BSCKクロック信号の立ち上がりでSDI信号を読込む
  から、SCKに同期してSDIからデータを送信する。
 C16ビットのデータを送信したらCS信号をHIGHにし
  LDAC信号をLOWにする。
  (LDACのLOWでデータを実際にOUT端子から出力)
  (OUTA/OUTBの選択は送信するデータで指示する)
SCKクロック信号の立ち上がりでSDIデータを読込むのだからSCKクロックのタイミングを早くすれば
早く通信が出来ると言う事です。 ちなみにデータシートには最高25℃で20MHzと書いて有ります。
尚、MCP4922/4921のデータシートはこちらを参照下さい。

16ビット送信データの詳細

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
A/B BUF GA SHDN D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
データの送信は15ビットのMSBから送ります。

bit15 A/B
 アナログを出力するチャンネルAorBを指定します。
  0:チャンネルA(OUTA)
  1:チャンネルB(OUTB)

bit14 BUF
 VREFの入力でバッファ制御を行うかの指示をします。
  0:バッファ制御をしない
  1:バッファ制御をする
  以下データシートの直訳です(Google翻訳)
  MCP492Xデバイスの入力バッファアンプは、低オフセット電圧と低ノイズを提供しています。
  各DACのコンフィギュレーションビットは、VREF入力がバッファまたはバッファなしモードを達成する
  ため、入力バッファアンプをバイパスすることができます。
  このビットのデフォルト値はバッファリングされていないです。
  バッファモードは、入力範囲と周波数応答にわずかな制限付きで、非常に高い入力インピーダンス
  を提供します。
  バッファモードは165 kΩ w/7 pFの典型的な入力インピーダンス、広い入力電圧範囲(0V〜VDD)
  を提供しています。

bit13 GA
 出力するゲインを選択します。
  0:2x (VOUT = 2 * VREF * D/4096) 入力値の2倍で出力されます
  1:1x (VOUT = VREF * D/4096)

bit12 SHDN
 シャットダウン制御を行うかの指示をします。
  0:出力端子からは出力されず、出力端子はハイインピーダンスとなります
  1:出力端子からアナログ値が出力されます

bit11-0 D11−D0
 出力するデータの内容を設定します(0−4095)。
 0-4095指定で0V-VREFの電圧に変換されます。

《MCP4922をArduinoに配線する》

配線図 MCP4922のOUTA出力を取合えずLEDに 接続しています。
VREFは今回Arduinoの5V電源に接続です。
ちなみにぃ、MCP4922の端子名の"NC"は
"NoConection"の意味で何処にも配線しません。

SHDN(9番ピン)
この端子をGNDに接続すると、低電力スタンバイモードで出力がハイインピーダンスになるみたいですね。 今回はそんなモードは利用しないのでVDDに接続です。

LDAC(8番ピン)
この端子はDACのラッチピンです。
通常はHIGHにして置いて、LOWの立下りタイミングで 実際に設定されたレジスタの内容を出力端子に電圧として出力します。

MCP4922を実際のなんらかの回路に組み込む場合は、 デジタルとアナログの電源を別けるとかのノイズ対策が必要になるでしょう。

実験T

IDEに下記のスケッチプログラムをコピーペーストして貼り付けて下さい。
IDEツールバーの「Upload」ボタンをクリックしてコンパイルとarduinoボードに書込みを行います。
※ 最新のIDEでは"SCK"名がマクロ定義されている様なので"DA_SCK"に変更しました。 *2)
---------------------------------------------------------------------
#define DA_SCK   13           // クロック信号出力ピン
#define DA_SDI   11           // データ信号出力ピン
#define DA_CS    10           // 選択動作出力ピン
#define DA_LDAC   9           // ラッチ動作出力ピン

// DACに出力を行う処理の関数
void DACout(int dataPin,int clockPin,int destination,int value)
{
     int i ;

     // コマンドデータの出力
     digitalWrite(dataPin,destination) ;// 出力するピン(OUTA/OUTB)を選択する
     digitalWrite(clockPin,HIGH) ;
     digitalWrite(clockPin,LOW) ;
     digitalWrite(dataPin,LOW) ;        // VREFバッファは使用しない
     digitalWrite(clockPin,HIGH) ;
     digitalWrite(clockPin,LOW) ;
     digitalWrite(dataPin,HIGH) ;       // 出力ゲインは1倍とする
     digitalWrite(clockPin,HIGH) ;
     digitalWrite(clockPin,LOW) ;
     digitalWrite(dataPin,HIGH) ;       // アナログ出力は有効とする
     digitalWrite(clockPin,HIGH) ;
     digitalWrite(clockPin,LOW) ;
     // DACデータビット出力
     for (i=11 ; i>=0 ; i--) {
          if (((value >> i) & 0x1) == 1) digitalWrite(dataPin,HIGH) ;
          else                           digitalWrite(dataPin,LOW) ;
          digitalWrite(clockPin,HIGH) ;
          digitalWrite(clockPin,LOW) ;
     }
}
void setup() {
     // 制御するピンは全て出力に設定する
     pinMode(DA_SCK, OUTPUT) ;
     pinMode(DA_SDI, OUTPUT) ;
     pinMode(DA_CS,  OUTPUT) ;
     pinMode(DA_LDAC,OUTPUT) ;
     // 出力ピンの初期化
     digitalWrite(DA_SCK,LOW) ;
}
void loop() {
     digitalWrite(DA_LDAC,HIGH) ;
     digitalWrite(DA_CS,LOW) ;
     DACout(DA_SDI,DA_SCK,0,4095/2) ;   // VREF/2の電圧をOUTAから出す
     digitalWrite(DA_CS,HIGH) ;
     digitalWrite(DA_LDAC,LOW) ;        // ラッチ信号を出す(ここで実際に出力する様に指示)
     while(1) ;                         // 処理中断
}
---------------------------------------------------------------------
SPI通信は利用せずに、ソフトウエアでDACへデータを送信するスケッチのサンプルです。
このサンプルを実行させOUTA端子(14番ピン)をテスターで測定した結果は2.537Vでした。
(尚、LEDも薄く点灯しています)

DACout()関数について

DACout(dataPin,clockPin,destination,value)
 DAC(MCP4922)のレジスタにデータを書き込む通信処理を行います。
  dataPin   :データの入力ピン、MCP4922のSDIに接続しているピンを指定します
  clockPin  :クロック出力ピン、74MCP4922のSCKに接続しているピンを指定します
  destination :0=OUTAに出力 1=OUTBに出力
  value    :12ビットデータ値0−4095を指定します


実験U

IDEに下記のスケッチプログラムをコピーペーストして貼り付けて下さい。
IDEツールバーの「Upload」ボタンをクリックしてコンパイルとarduinoボードに書込みを行います。
---------------------------------------------------------------------
#include "pins_arduino.h"
#include <SPI.h>

#define LDAC   9              // ラッチ動作出力ピン

void setup() {
     // 制御するピンは全て出力に設定する
     pinMode(LDAC,OUTPUT) ;
     // SPIの初期化処理を行う
     SPI.begin() ;                        // SPIを行う為の初期化
     SPI.setBitOrder(MSBFIRST) ;          // ビットオーダー
     SPI.setClockDivider(SPI_CLOCK_DIV8) ;// クロック(CLK)をシステムクロックの1/8で使用(16MHz/8)
     SPI.setDataMode(SPI_MODE0) ;         // クロック極性0(LOW) クロック位相0
}
void loop() {
     int i ;

     i = 4095/2 ;
     digitalWrite(LDAC,HIGH) ;
     digitalWrite(SS,LOW) ;
     SPI.transfer((i >> 8) | 0x30) ; // Highバイト(0x30=OUTA/BUFなし/1x/シャットダウンなし)
     SPI.transfer(i & 0xff) ;        // Lowバイトの出力
     digitalWrite(SS,HIGH) ;
     digitalWrite(LDAC,LOW) ;        // ラッチ信号を出す
}
---------------------------------------------------------------------
こんどはSPI通信を利用したサンプルスケッチです。
動作は[実験T]と同じです。
SPIの通信は8ビット単位なので送信するデータを2回に分けて出力する。

同じ電圧の一直線波形では面白くないので下記の様にスケッチを変えてみましょう。
---------------------------------------------------------------------
void loop() {
     int i ;

     for (i=0 ; i < 4096 ; i=i+4) {
          digitalWrite(LDAC,HIGH) ;
          digitalWrite(SS,LOW) ;
          SPI.transfer((i >> 8)|0x30) ;
          SPI.transfer(i & 0xff) ;
          digitalWrite(SS,HIGH) ;
          digitalWrite(LDAC,LOW) ;
     }
}
---------------------------------------------------------------------
DAC波形3  と、この様にのこぎり波形になっちゃいま〜す。
 ファンクションジェネレータの様な波形発生もイケるかもね。
 ま、色々波形を作って遊んでみましょ。

 尚、I2Cで接続するDAC(MCP4725/MCP4726)の記事は
 こちらを参照下さい、但しPICでの記事ですがぁ...




変更(*2) 2015/04/23
追記(*1) 2014/12/25


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2015 Shigehiro Kimura All Rights Reserved.