Let's Add 4+ More DACs/CVs with Quad DAC! (Also, I2C tips!)

With Adafruit’s MCP4728 Quad DAC, we can add 4 more DACs to Daisy Seed with relative ease!!


2 of the DACs in action

You can see this I2C breakout board being used to send CVs to modular synths in this video.

It’ll be perfect for sensor-based Daisy instruments!

Let’s learn how to use it!!

What Do We Need?
-Daisy Seed
-Breadboard
-Soldering tools
-Two 4.7k ohm resistors
-Adafruit MCP4728 Quad DAC
-Jumper wires
-LEDs and 220 ohm resistors (for testing)
-Multimeter (for testing)

To get started, solder header pins to the Quad DAC (honestly the hardest step of all of this!).

We’ll be programming in Arduino IDE since there’s an external library that’ll make our job easy.

Let’s Connect!
It’s pretty straightforward to connect the Quad DAC to Daisy! Follow this diagram.

One important detail to remember is that Daisy does not have a pull-up resistor so whenever we use an I2C component like the Quad DAC, we need to use 4.7k ohm resistors like shown in the diagram. And make sure to use the I2C1 pins.

That’s about it! Let’s test this thing.

Let’s Output Voltage!
Let’s first light up some LEDs and then measure the voltage with a multi-meter. Once we are 100% sure that it’s working as expected, then we can connect to our (expensive) modular rig.

Connect an LED like this. Let’s just start with 1 or 2 for now. I’m using 220 ohm resistors here.

Let’s open up the Arduino IDE next.
First, install the MCP4728 library. Sketch → Include Library → Manage Libraries → Search “MCP4728” and install “Adafruit MCP4728”

Copy this into the IDE.

// Basic demo for configuring the MCP4728 4-Channel 12-bit I2C DAC
#include <Adafruit_MCP4728.h>
#include <Wire.h>

Adafruit_MCP4728 mcp;

void setup(void) {
  Serial.begin(115200);
  
  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens
  
  Serial.println("Adafruit MCP4728 test!");

  // Try to initialize!
  if (!mcp.begin()) {
    Serial.println("Failed to find MCP4728 chip");
    while (1) {
      delay(10);
    }
  }

}

void loop() { 
    mcp.setChannelValue(MCP4728_CHANNEL_A, 4095);
    mcp.setChannelValue(MCP4728_CHANNEL_B, 0);
    mcp.setChannelValue(MCP4728_CHANNEL_C, 4095);
    mcp.setChannelValue(MCP4728_CHANNEL_D, 2000); 
}

We can change the DAC voltage with mcp.setChannelValue() in the void loop().

For example, having the value “4095” in mcp.setChannelValue(MCP4728_CHANNEL_A, 4095); will change the 1st DAC’s output (Channel A AKA “VA”) to 5 volts.

Your LEDs may light up as soon as you wire everything up. If so, change the voltage value to 0 for testing purpose.

Hit upload to flash this program into Daisy. Once it’s done, open up the Serial Monitor. You should see the brightness of LEDs change.

If you see the message “Failed to find MCP4728 chip”, your components may not be wired correctly.

In order to see the effect without having to open up Serial Monitor every time, just comment out or delete these two lines.

  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens

Next, let’s use a for-loop to create a more animated effect!

// Basic demo for configuring the MCP4728 4-Channel 12-bit I2C DAC
#include <Adafruit_MCP4728.h>
#include <Wire.h>

Adafruit_MCP4728 mcp;

void setup(void) {
  Serial.begin(115200);
  /*
  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens
  */
  Serial.println("Adafruit MCP4728 test!");

  // Try to initialize!
  if (!mcp.begin()) {
    Serial.println("Failed to find MCP4728 chip");
    while (1) {
      delay(10);
    }
  }

}

void loop() { 
  for (int i = 0; i <= 4095; i+=2){ 
    mcp.setChannelValue(MCP4728_CHANNEL_A, i);
    mcp.setChannelValue(MCP4728_CHANNEL_B, i);
    mcp.setChannelValue(MCP4728_CHANNEL_C, i);
    mcp.setChannelValue(MCP4728_CHANNEL_D, i); 
    delay(1); 
  }

}

For_Loop_Quad_DAC
(Nice!! But purple LED may have a different resistance? idk. But you get the point!)

MORE POWER!!!
I thought I was done at this point, but then I realized, “Wait, I’m using a 3.3 volt power so…”. After I checked with my multi-meter, the Quad DAC was outputting with the maximum voltage of 3.3 volt as I suspected (even though the value is set to 4095).

No need to panic! We can just power Daisy externally :slight_smile:

I simply took out an Arduino Uno and connected its 5V pin to Daisy’s Pin 39 (VIN). And I also connected Arduino’s ground to Daisy’s ground. See this diagram for reference.


Good enough for testing! Ideally, do this with an external battery.

By the way, it should still be safe to connect Daisy Seed to your computer via USB even while it’s powered externally. I did double check this!!

Now it’s outputting more than 3.3 volt!! :sparkles:

What’s Next?
Once you’re certain that everything is working as expected, you can grab a 3.5 mm jack and start controlling your modular synths with Daisy!!

Because it’s I2C, we could add even more DACs by connecting another Quad DAC and changing its address.

Please let us know if you end up using this in your project!!

8 Likes

Hi Takumi, this is great. Thank you.

What would you need to do to output up to 10v?

Also, is it feasable to add a second dac? you would just need to change the address?

1 Like

Thank you for checking it out, Jorge!

You would need a circuit with an op amp and that’s a whole another topic that I want to cover in a forum tutorial like this in the future. And perhaps a video further in the future!
In the meantime, there are tons of resources online!!

And yes, you can add more!! I have used 2 of the same I2C device by changing the address before.
I unfortunately only have one of the Quad DAC, so I can’t write, confirm, and share a code yet. But I’ll definitely consider getting another one and make a forum tutorial about connecting multiple I2C devices and changing address.

Hi Takumi,

Can you suggest an alternative quad dac? the MCP4728 seems very scarse for production.

Yeah, there was a shortage of them I think last year in the United States.

There is another approach to adding DAC and that is to use the PWM output of Daisy and running that into a lowpass filter circuit, which actually sounds easier than you think. It won’t be pristine clean but it gets the job done (especially for non-pitch CVs).
I always reference this video for that WERKSTATT-01 | LFO Quantizer Mod - YouTube . Just one resistor and one capacitor!
I have used this personally to make a custom eurorack controller :slight_smile:

But imo I think it’s worth waiting for it be back in stock.

Great tutorial, @Takumi_Ogata, thanks for sharing!

1 Like

Here is some very basic example code of this working without the Arduino library in C++ (although that library was my reference point for this). I am using 2 DACs with different addresses, essentially just sending off and on values to both and testing with an LED. Also worth mentioning that I could only get this working with TransmitDma. There’s probably better ways to do this, but for now this seems to work for me.

To find the DAC addresses I used the example from this post: Porting from Arduino: Wire.h - #8 by Elby


#include "daisy_seed.h"
#include <array>
#include <memory>
#include <string.h>

using namespace daisy;

// uint8_t output_buffer[8];
#define BUFF_SIZE 8
static uint8_t DMA_BUFFER_MEM_SECTION output_buffer[BUFF_SIZE];


int main(void)
{
    static DaisySeed seed;
    seed.Configure();
    seed.Init();
    seed.StartLog(true);
    System::Delay(1000);

    I2CHandle::Config _i2c_config;
    _i2c_config.periph = I2CHandle::Config::Peripheral::I2C_1;
    _i2c_config.speed  = I2CHandle::Config::Speed::I2C_400KHZ;
    _i2c_config.mode   = I2CHandle::Config::Mode::I2C_MASTER;
    _i2c_config.pin_config.scl  = {DSY_GPIOB, 8};
    _i2c_config.pin_config.sda  = {DSY_GPIOB, 9};
    
    // initialise the peripheral
    I2CHandle _i2c;
    _i2c.Init(_i2c_config);

    while(1) {  
        seed.PrintLine("Running..");

        System::Delay(1000);

        uint16_t channel_a_value {4000};
        uint16_t channel_b_value {2000};
        uint16_t channel_c_value {3000};
        uint16_t channel_d_value {4095};

        output_buffer[0] = static_cast<uint8_t>(channel_a_value >> 8);
        output_buffer[1] = static_cast<uint8_t>(channel_a_value & 0xFF);
        output_buffer[2] = static_cast<uint8_t>(channel_b_value >> 8);
        output_buffer[3] = static_cast<uint8_t>(channel_b_value & 0xFF);
        output_buffer[4] = static_cast<uint8_t>(channel_c_value >> 8);
        output_buffer[5] = static_cast<uint8_t>(channel_c_value & 0xFF);
        output_buffer[6] = static_cast<uint8_t>(channel_d_value >> 8);
        output_buffer[7] = static_cast<uint8_t>(channel_d_value & 0xFF);
        I2CHandle::Result i2cResult= _i2c.TransmitDma(0x64, &output_buffer[0], 8, NULL, NULL);
        if(i2cResult == I2CHandle::Result::OK) {
            seed.PrintLine("OK TRANSMISSION 1");
        }
        I2CHandle::Result i2cResult_2= _i2c.TransmitDma(0x60, &output_buffer[0], 8, NULL, NULL);
        if(i2cResult_2 == I2CHandle::Result::OK) {
            seed.PrintLine("OK TRANSMISSION 2");
        }

        System::Delay(1000);

        uint16_t channel_a_value_2 {0};
        uint16_t channel_b_value_2 {0};
        uint16_t channel_c_value_2 {0};
        uint16_t channel_d_value_2 {0};

        output_buffer[0] = static_cast<uint8_t>(channel_a_value_2 >> 8);
        output_buffer[1] = static_cast<uint8_t>(channel_a_value_2 & 0xFF);
        output_buffer[2] = static_cast<uint8_t>(channel_b_value_2 >> 8);
        output_buffer[3] = static_cast<uint8_t>(channel_b_value_2 & 0xFF);
        output_buffer[4] = static_cast<uint8_t>(channel_c_value_2 >> 8);
        output_buffer[5] = static_cast<uint8_t>(channel_c_value_2 & 0xFF);
        output_buffer[6] = static_cast<uint8_t>(channel_d_value_2 >> 8);
        output_buffer[7] = static_cast<uint8_t>(channel_d_value_2 & 0xFF);
        I2CHandle::Result i2cResult_3= _i2c.TransmitDma(0x64, &output_buffer[0], 8, NULL, NULL);
        if(i2cResult_3 == I2CHandle::Result::OK) {
            seed.PrintLine("OK TRANSMISSION 3");
        }
        I2CHandle::Result i2cResult_4= _i2c.TransmitDma(0x60, &output_buffer[0], 8, NULL, NULL);
        if(i2cResult_4 == I2CHandle::Result::OK) {
            seed.PrintLine("OK TRANSMISSION 4");
        }
    }
}
1 Like

Thank you for putting this together and sharing it!! Next time I have the Quad DAC set up with my Daisy, I would need to try this out.

Thank you for this tutorial!
Is it possible to do it with pd2dsy because I am unfortunatelly only familiar with PD.

Using this breakout board with pd2dsy is not possible as far as a I know.

There is another approach to adding DAC and that is to use the PWM output of the Daisy and running that into a lowpass filter circuit, which actually sounds easier than you think. It won’t be pristine clean but it gets the job done (especially for non-pitch CVs).
I always reference this video for that WERKSTATT-01 | LFO Quantizer Mod - YouTube .
Just one resistor and one capacitor!
I have used this personally to make a custom eurorack controller :slight_smile:

Could you please be more elaborate or direct me to some useful links?
This is a nice video, but I cannot make the connection with the pd2dsy.
Also, I am googling about the PWM and the Daisy Seed, but cannot find any information that would put some light on the subject.
Thank you

Googling “PWM Arduino” will yield helpful resources. Long story short, you can use PWM to output a varying signal from 0.0 to 3.3 volt (would be 0.0 to 5.0 volt in Arduino). So this is useful for changing the brightness of an LED for example!

So it’s this line in the pod.json file:

"component": "RgbLed"

Here’s pod.json in its entirety:

{
	"name": "pod",
	"som": "seed",
	"defines": {
		"OOPSY_TARGET_POD": 1,
		"OOPSY_TARGET_HAS_MIDI_INPUT": 1,
		"OOPSY_HAS_ENCODER": 1
	},
	"max_apps": 8,
	"display": {},
	"audio": {
		"channels": 2
	},
	"components": {
		"sw1": {
			"component": "Switch",
			"pin": 27
		},
		"sw2": {
			"component": "Switch",
			"pin": 28
		},
		"knob1": {
			"component": "AnalogControl",
			"pin": 21
		},
		"knob2": {
			"component": "AnalogControl",
			"pin": 15
		},
		"encoder": {
			"component": "Encoder",
			"pin": {
				"a": 26,
				"b": 25,
				"click": 13
			}
		},
		"led1": {
			"component": "RgbLed",
			"pin": {
				"r": 20,
				"g": 19,
				"b": 18
			}
		},
		"led2": {
			"component": "RgbLed",
			"pin": {
				"r": 17,
				"g": 24,
				"b": 23
			}
		}
	},
	"aliases": {
		"switch": "sw1",
		"button": "sw1",
		"switch1": "sw1",
		"button1": "sw1",
		"switch2": "sw2",
		"button2": "sw2",
		"encswitch": "encoder_rise",
		"enp": "encoder_press",
		"press": "encoder_press",
		"knob": "knob1",
		"ctrl": "knob1",
		"ctrl1": "knob1",
		"ctrl2": "knob2",
		"led": "led1"
	}
}

And in Pure Data, it’ll be [s led1_red @hv_param], [s led1_green @hv_param], and [s led1_blue @hv_param]. Similar thing for led2.
You can send 0.0 to 1.0 to these objects in Pure Data and the Daisy Seed will output 0.0 to 3.3 volt out of the pins. Just FYI, the Pod uses an RGB led that takes in three total PWM signals that are sent to each of the red, green, and blue colors separately.
I recommend watching this video that demonstrates how you can change the brightness of an LED with pd2dsy: https://youtu.be/KTM8pHN3_mM?t=530
It’s worth keeping in mind that the Pod is the Seed but with a breakout board!
So, I suggest that you connect an LED (standard two-pin LED is fine!) to the Daisy (for example, pin D20) and change the LED brightness using an LFO for example. Or map it to the potentiometer.

Now, this is good for something like an LED but as a control signal for audio hardware, it’ll be a bit noisy. So that’s why a lowpass filter circuit is needed! From here, please refer back to my previous post. You can keep the json file and Pure Data patch the same for simplicity sake. The name “led” is being used, but it’s still the PWM signal that you want.

Good luck!
If you have any more questions, please start a new thread about it (with a reference to this or my previous reply). But first! I highly implore that you put these components together and try it :slight_smile:

Hi, Would it be possible to do this with the Patch SM? I studied the diagram and Seed Pinout for pins it is using. In principle it seems possible.

P12: I2C1 SCL
P13: I2C1 SDA

P38: 3V3 Digital
P40: DGND

I am currently using I2C for 1306 OLED so also wondering if I can use on I2C with a separate address or if need to get one to connect to SPI instead.

I got the answer I was looking for on Discord that you can use a display and DAC, on a single I2C bus with different I2C addresses.