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.
2
, or toSPI_CLOCK_DIV2
? 2) Did yousetPortWrite(true)
?