概要
M5Stack社のユニット紹介シリーズです。今回は任意の電圧が出力できるDACユニットになります。SKUはU012なので結構古いのですが、あまり使っているのをみたことがありません。ESP32にもDAC出力はありますもんね。
商品

- M5Stack用DACユニット(スイッチサイエンス)
- DAC Converter Digital to Analog I2C Unit (MCP4725)(公式ストア)
コネクタが赤なのでI2C接続のユニットになります。

表側はこんな感じで、I2C接続でMCP4725を利用していると書いてあります。出力は0-3.3Vとありますね。M5StackはGroveに5Vを供給しているのに3.3V出力?

すでにプラスに結線がありますが、オシロスコープに接続してあります。裏側にはケースにDACと凹んでいますね。

本来は付属している上記のコネクタ経由で接続するのが正しいはずです。
スケッチ例
#include <Adafruit_MCP4725.h> // http://librarymanager/All#Adafruit_MCP4725 https://github.com/adafruit/Adafruit_MCP4725
Adafruit_MCP4725 dac;
void setup() {
dac.begin(0x60, &Wire); // call Wire.begin()
Wire.begin(32, 33); // Re Init(M5StickC)
Serial.begin(115200);
delay(1000);
}
void loop() {
dac.setVoltage(0, false);
dac.setVoltage(1024, false);
dac.setVoltage(4095, false);
}
ライブラリはAdafruit_MCP4725を利用します。AdafruitさんのライブラリはWireクラスを毎回初期化しちゃうので好きじゃないんですが、もう一つのはWire固定だったのでこっちを使います。
ESP32はWireとWire1とI2Cが2系統あって、ボードによって使う系統が違うのでライブラリで指定できる必要があります。ただしAdafruitのライブラリは勝手にデフォルト値でI2Cを初期化しちゃうので、特殊な値で動かしたい場合には適していません。
なので、最初にdac.begin()を呼び出したあとに、自分でWire.begin()で再度初期化し直しています。dac.begin()で21, 22などのWireのデフォルトっぽいPINの設定が変わっちゃうので初期化順番には注意してください。
実行結果

あれ、出力が4Vぐらい出ていますね。3.3Vじゃないのか、、、
上記に回路図がありました。MCP4725はあまり電流を流せないので、VOUTからLM321MFXというオペアンプを通して、出力していました。MCP4725はVCCの電圧で出力するので最大5Vのはずですが、いろいろなロスなどで4Vぐらいに電圧が下がっているのかな?
ON・OFFは2.7KHzぐらいでした。この速度はI2Cの通信速度に依存しているので、もっと速度を引き上げてみたいと思います。
I2Cの速度変更方法
Wire.begin(32, 33, 100000L);
通常は、begin()のときに速度を設定します。未指定のときには100KHzになります。
Wire.setClock(100000L);
もしくは、WireクラスのsetClock()関数でも設定が可能です。
void loop() { dac.setVoltage(0, false, 400000L); dac.setVoltage(4095, false, 400000L); }
しかしながら、Adafruit_MCP4725は実は上記のように、I2Cの速度を設定して呼び出しています。
bool Adafruit_MCP4725::setVoltage(uint16_t output, bool writeEEPROM, uint32_t i2c_frequency) { i2c_dev->setSpeed(i2c_frequency); // Set I2C frequency to desired speed uint8_t packet[3]; if (writeEEPROM) { packet[0] = MCP4725_CMD_WRITEDACEEPROM; } else { packet[0] = MCP4725_CMD_WRITEDAC; } packet[1] = output / 16; // Upper data bits (D11.D10.D9.D8.D7.D6.D5.D4) packet[2] = (output % 16) << 4; // Lower data bits (D3.D2.D1.D0.x.x.x.x) if (!i2c_dev->write(packet, 3)) { return false; } i2c_dev->setSpeed(100000); // reset to arduino default return true; }
該当ソースを確認したところ、上記になっていました。デフォルト値が400KHzで、関数の最後で100KHzに戻しています。I2Cのデフォルトは100KHzなんですが、関数の中で勝手に戻すのは悪質だと思います。100KHz以外で動かしている環境で、このライブラリを使うとおかしな設定に変更されてしまいます。このへんの設計がAdafruitのライブラリは好きじゃないんですよね。
100KHzで動かす
void loop() { dac.setVoltage(0, false, 100000L); dac.setVoltage(4095, false, 100000L); }

I2Cの転送レート400KHzのときに2.7KHzで動いていたものを、100KHzの転送レートにすると1KHzぐらいになりました。I2Cの通信オーバーヘッドだけじゃないので、4分の1にはならなかったですね。
800KHzで動かす
void loop() { dac.setVoltage(0, false, 800000L); dac.setVoltage(4095, false, 800000L); }

倍にしたら3.6KHzですね。
1.6MHzで動かす(800KHzのまま)

MCP4725の上限は3.2MHzで、ESP32の上限は5MHzぐらいのはずですが、Arduinoライブラリでの上限は800KHzまでのようです。
設定範囲確認
#include <Wire.h> void setup() { Serial.begin(115200); delay(1000); Wire.begin(32, 33); // 上限確認 for (uint32_t c = 100000; c <= 1600000; c += 100000) { Wire.setClock(c); uint32_t c1 = Wire.getClock(); Serial.printf("c=%10d, c1=%10d\n", c, c1); } // 下限確認 for (uint32_t c = 100000; c > 0; c /= 2) { Wire.setClock(c); uint32_t c1 = Wire.getClock(); Serial.printf("c=%10d, c1=%10d\n", c, c1); } } void loop() { }
上記を動かしてみました。
c= 100000, c1= 100000 c= 200000, c1= 200000 c= 300000, c1= 300751 c= 400000, c1= 400000 c= 500000, c1= 500000 c= 600000, c1= 606060 c= 700000, c1= 701754 c= 800000, c1= 800000 c= 900000, c1= 800000 c= 1000000, c1= 800000 c= 1100000, c1= 800000 c= 1200000, c1= 800000 c= 1300000, c1= 800000 c= 1400000, c1= 800000 c= 1500000, c1= 800000 c= 1600000, c1= 800000 c= 100000, c1= 100000 c= 50000, c1= 50000 c= 25000, c1= 25000 c= 12500, c1= 12500 c= 6250, c1= 9768 c= 3125, c1= 9768 c= 1562, c1= 9768 c= 781, c1= 9768 c= 390, c1= 9768 c= 195, c1= 9768 c= 97, c1= 9768 c= 48, c1= 9768 c= 24, c1= 9768 c= 12, c1= 9768 c= 6, c1= 9768 c= 3, c1= 9768 c= 1, c1= 9768
結果はこんな感じです。下限は9768Hzで、上限800KHzまでですね。
速度 | 設定値 | 備考 |
9.7KHz | 9768L | Arduino for ESP32の最低値 |
100KHz | 100000L | I2C標準 |
400KHz | 400000L | I2Cハイスピード |
800KHz | 800000L | Arduino for ESP32の最高値 |
こんな感じになります。通常は100KHz400KHzを使うと思います。通信ができない場合には、速度を落としてみて、正常に通信できるようになったらプルアップ抵抗などの調整が必要です。速度を落としても通信できない場合には何かが間違っていることが多いです。
まとめ
dac.setVoltage(4095, false, 800000L);
ちなみに2つ目の引数は、設定値を保存するかの値になります。trueに設定すると次回電源を入れたときにその値が出力されます。つまりESP32内蔵のDACのようにスピーカー出力に使うような制御ではなく、比較的ゆっくりと電圧を変更させるか、出力電圧を調整可能な電源みたいな用途に適しています。
コメント
全く同じ問題で悩んでいましたが、公式のサンプルコードを読むと下記のような出力のようです。
4096で3.3Vではないのがポイントです。
https://github.com/m5stack/M5Stack/blob/master/examples/Unit/DAC_MCP4725/DAC_MCP4725.ino
設定 出力
0 0V
1024 1.2V
2048 2.4V
2816 3.3V
https://github.com/m5stack/M5Stack/blob/master/examples/Unit/DAC_MCP4725/DAC_MCP4725.ino
情報ありがとうございます!