Multiple MCP4922 SPI DACs on an Arduino

Pastedgraphic-1

I’ve had a few days on and off fiddling with getting an Arduino Nano, trying to get it to talk to a couple of Microchip MCP4922 DACs hanging off the SPI bus.

The diagram over at the excellent tronixstuff site (in Arduino and the SPI bus part II) was a good starting point – essentially connect the clocks/data outputs to all the inputs, and use digital output pins for selecting the SPI slave to output to, but it still wasn’t having it.

The solution in the end was to attach pull-up resistors to the DAC chip select pins, like this.

Multipledac

I had a thought that it could be possible to use the internal pin pull-ups, but a discussion over at Society of Robots suggests that this might not be reliable. I don’t know if all SPI devices need a pull-up resistor on chip select, but from my experiments, the MCP4922 certainly does need it.

Here’s a quick and really irritating demo of four oscillators (“sines” from ASys RS-95, RS-95e and Doepfer A-111, triangle from Roland System 100 Model 101) being swept by the four outputs of the DACs – the resulting LFOs should each be 90 degrees out of phase. There’s also a pot wired to an analogue input, which controls the speed of the oscillations.

This is a spectrogram (from everyone’s favourite old PC sound editor from years back…) of part of the above demo which proves the phasing of the LFOs. One of the oscillators was tuned a lot lower, hence one of the lower peaks.

Spectrogram_quadrature_lfo_ard

Must be turning into a hippy or something, I could stare at this spectrogram all day. I think I need an oscilloscope.

Tagged under , , , ,

6 comments

  1. 7th January 2012Michael Hept says:

    Thank you very much, just had the same problem. Was fiddling around for hours. After adding some pullups it works like charm πŸ˜€

  2. 8th January 2017peter says:

    Sounds great. Will you share the arduino sketch to us?

  3. 9th January 2017ua726 says:

    Here goes – it looks fairly crude but it might work (and then again it might not).

    Analogue pot into pin 2, think this will control the speed of the oscillation. There’s a table each for sine and triangle, you can choose which one you want.


    // ******************************************************
    // Dual DAC test
    // requires two MCP4922 DACs
    // Outputs triangle waves 90 degrees out of phase
    // with each other

    #include

    #define FASTADC 1

    // defines for setting and clearing register bits
    #ifndef cbi
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
    #endif
    #ifndef sbi
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
    #endif

    #include

    const unsigned int LUTsize = 1<<8; // Look Up Table size: has to be power of 2 so that the modulo LUTsize // can be done by picking bits from the phase avoiding arithmetic int8_t sintable[LUTsize] PROGMEM = { // already biased with +127 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173, 176,179,182,184,187,190,193,195,198,200,203,205,208,210,213,215, 217,219,221,224,226,228,229,231,233,235,236,238,239,241,242,244, 245,246,247,248,249,250,251,251,252,253,253,254,254,254,254,254, 255,254,254,254,254,254,253,253,252,251,251,250,249,248,247,246, 245,244,242,241,239,238,236,235,233,231,229,228,226,224,221,219, 217,215,213,210,208,205,203,200,198,195,193,190,187,184,182,179, 176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130, 127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81, 78,75,72,70,67,64,61,59,56,54,51,49,46,44,41,39, 37,35,33,30,28,26,25,23,21,19,18,16,15,13,12,10, 9,8,7,6,5,4,3,3,2,1,1,0,0,0,0,0, 0,0,0,0,0,0,1,1,2,3,3,4,5,6,7,8, 9,10,12,13,15,16,18,19,21,23,25,26,28,30,33,35, 37,39,41,44,46,49,51,54,56,59,61,64,67,70,72,75, 78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124}; int8_t triangletable[LUTsize] PROGMEM = { 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e, 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e, 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e, 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e, 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e, 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe, 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde, 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe, 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xed,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1, 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcd,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1, 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xad,0xab,0xa9,0xa7,0xa5,0xa3,0xa1, 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8d,0x8b,0x89,0x87,0x85,0x83,0x81, 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6d,0x6b,0x69,0x67,0x65,0x63,0x61, 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4d,0x4b,0x49,0x47,0x45,0x43,0x41, 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2d,0x2b,0x29,0x27,0x25,0x23,0x21, 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0d,0x0b,0x09,0x07,0x05,0x03,0x01, }; #define dout_spi_ss_dac0 10 #define dout_spi_out 11 #define dout_spi_ss_dac1 9 #define dout_spi_clock 13 //#define dout_spi_ss_dac0 20 //#define dout_spi_out 22 //#define dout_spi_ss_dac1 49 //#define dout_spi_clock 21 #define analogue_pot 2 //#define triangle_ unsigned long delay_time = 1000; int out_val; #define wave_count 500 int v_1 = 0; int v_2 = LUTsize/4; int v_3 = LUTsize/2; int v_4 = LUTsize/2 + LUTsize/4; boolean v_1_asc = true; boolean v_2_asc = true; boolean v_3_asc = false; boolean v_4_asc = false; DAC dac1(dout_spi_out, dout_spi_clock, dout_spi_ss_dac0); DAC dac2(dout_spi_out, dout_spi_clock, dout_spi_ss_dac1); void setup() { pinMode( dout_spi_ss_dac0, OUTPUT); pinMode( dout_spi_out, OUTPUT); pinMode( dout_spi_ss_dac1, OUTPUT); pinMode( dout_spi_clock, OUTPUT); Serial.begin(9600); #if FASTADC // set prescale to 16 sbi(ADCSRA,ADPS2) ; cbi(ADCSRA,ADPS1) ; cbi(ADCSRA,ADPS0) ; #endif } void loop() { dac1.write_value(pgm_read_byte(sintable+v_1) << 4, 0); dac1.write_value(pgm_read_byte(sintable+v_2) << 4, 1); dac2.write_value(pgm_read_byte(sintable+v_3) << 4, 0); dac2.write_value(pgm_read_byte(sintable+v_4) << 4, 1); v_1++; v_2++; v_3++; v_4++; if(v_1==LUTsize) { v_1=0; } if(v_2==LUTsize) { v_2=0; } if(v_3==LUTsize) { v_3=0; } if(v_4==LUTsize) { v_4=0; } delayMicroseconds(30); delay_time = analogRead(analogue_pot); if(delay_time > 2 && delay_time < 512) { delayMicroseconds(delay_time << 4); } else if(delay_time > 512) {
    delay(delay_time >> 4);
    }

    }

  4. 9th January 2017ua726 says:

    If I can dig it out, I will do. (this post is five years old πŸ™‚ )

  5. 17th January 2017raphael says:

    Watt is thΓ© library use for #include dac.h ?

  6. 17th January 2017ua726 says:

    whups, yeah – that would help.

    dac.h


    #ifndef DAC_h
    #define DAC_h

    #if defined(ARDUINO) && ARDUINO >= 100
    #include "Arduino.h"
    #else
    #include "WProgram.h"
    #endif

    class DAC
    {
    public:
    DAC(int data_out, int spi_clock, int slave_select);
    void write_value(int sample, int output);
    private:
    int _data_out;
    int _spi_clock;
    int _slave_select;
    };

    #endif

    dac.cpp


    /**
    DAC
    */

    #if defined(ARDUINO) && ARDUINO >= 100
    #include "Arduino.h"
    #else
    #include "WProgram.h"
    #endif
    #include "DAC.h"

    DAC::DAC(int data_out, int spi_clock, int slave_select)
    {
    // standard setup of SPI for DAC
    _data_out = data_out;
    _spi_clock = spi_clock;
    _slave_select = slave_select;
    byte clr;
    pinMode(_data_out, OUTPUT);
    pinMode(_spi_clock,OUTPUT);
    pinMode(_slave_select,OUTPUT);
    digitalWrite(_slave_select,HIGH); //disable device

    SPCR = (1<> 8) & 0x00FF; // byte0 = takes bit 15 - 12
    if(output==0) {
    dacSPI0 |= (1 << 7); // A/B: DACa or DACb - Forces 7th bit of x to be 1. all other bits left alone. } dacSPI0 |= 0x10; // should be 0x30 to set SHDN and gain dacSPI1 = sample & 0x00FF; //byte1 = takes bit 11 - 0 dacSPI0 |= (1<<5); // set gain of 1 digitalWrite(_slave_select,LOW); SPDR = dacSPI0; // Start the transmission while (!(SPSR & (1<

Write a comment: