# Output frequency from an SPI DAC (MCP4922) is lower than expected

I'm using a 2-channel, 12-bit DAC (MCP4922, datasheet) with an Arduino Uno, using the library written for it here. With the clock divider set to 2, I should have the SPI clock frequency at 8MHz. If each value sent to the DAC takes about 12 of these clock cycles, that leaves me with about 650kHz as my output frequency (when I'm sending a simple on-off signal like below), but the fastest I see is about 25kHz.

void loop() { dac.output2(4095, 0); dac.output2(0, 4095); }

Am I misunderstanding how the transfer protocol works? How can I make this faster? I'd like to both understand why this is so slow, and also to get an output frequency of at least 50kHz.

• 1) Did you set the clock divider to `2`, or to `SPI_CLOCK_DIV2`? 2) Did you `setPortWrite(true)`? – Edgar Bonet Jun 14 '17 at 10:11
• @EdgarBonet Yes to both, unfortunately. – janizer Jun 14 '17 at 14:20

Let's do some maths...

• 16MHz master clock / 2 = 8MHz SPI clock
• 2 transactions = 16 bits (not 12!)
• 8,000,000 / 16 = 500,000
• 2 transitions per cycle
• 2 channels
• 2 + 2 = 4
• 500,000 / 4 = 125,000Hz

So in a perfect world you could expect 125kHz from the DAC. However the world is far from perfect.

Let's look at what it's actually doing when you use `output2()`:

``````void DAC_MCP49xx::output2(unsigned short data_A, unsigned short data_B) {
this->_output(data_A, CHANNEL_A);
this->_output(data_B, CHANNEL_B);

// Update the output, if desired.
// The reason this is only in the dual-output version is simple: it's mostly useless
// for the single-output version, as it would make more sense to tie the \LDAC pin
// to ground, or do it manually. However, there should be a single-call method
// to update *both* channels in sync, which wouldn't be possible with multiple
// separate DACs (for which there is latch()).
if (automaticallyLatchDual) {
this->latch();
}
}
``````

Assuming `automaticallyLatchDual` is false we can pretty much ignore that section. So it's calling `_output()` twice - once for each channel. And what does that do?

``````void DAC_MCP49xx::_output(unsigned short data, Channel chan) {
// Truncate the unused bits to fit the 8/10/12 bits the DAC accepts
if (this->bitwidth == 12)
data &= 0xfff;
else if (this->bitwidth == 10)
data &= 0x3ff;
else if (this->bitwidth == 8)
data &= 0xff;

// Drive chip select low
if (this->port_write)
PORTB &= 0xfb; // Clear PORTB pin 2 = arduino pin 10
else
digitalWrite(ss_pin, LOW);

// bit 15: 0 for DAC A, 1 for DAC B. (Always 0 for MCP49x1.)
// bit 14: buffer VREF?
// bit 13: gain bit; 0 for 1x gain, 1 for 2x (thus we NOT the variable)
// bit 12: shutdown bit. 1 for active operation
// bits 11 through 0: data
uint16_t out = (chan << 15) | (this->bufferVref << 14) | ((!this->gain2x) << 13) | (1 << 12) | (data << (12 - this->bitwidth));

// Send the command and data bits
SPI.transfer((out & 0xff00) >> 8);
SPI.transfer(out & 0xff);

// Return chip select to high
if (this->port_write)
PORTB |= (1 << 2); // set PORTB pin 2 = arduino pin 10
else
digitalWrite(ss_pin, HIGH);
}
``````

That's quite a lot. All those boolean mathematics operations and decision making slows things down a lot. So does hopping from function to function (yes, the compiler will possibly try and inline some things).

Even the `SPI.transfer()` isn't that efficient with checks and while loops, etc.

To get anywhere near the kind of speed you are looking for you would need to use non-blocking SPI code so you can be transferring a byte out of the SPI port at the same time as getting the next byte ready to send. You don't need to wait for the current SPI transfer to finish (as the SPI library does), you just need to be sure that the previous one has finished before you start the next one.