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 <DAC.h>

    #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 <avr/pgmspace.h>

    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<<SPE)|(1<<MSTR) | (0<<SPR1) | (0<<SPR0);
      clr=SPSR;
      clr=SPDR;
    }


    void DAC::write_value(int sample, int output)
    {
      // splits int sample in to two bytes
      byte dacSPI0 = 0;
      byte dacSPI1 = 0;
      dacSPI0 = (sample >> 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<<SPIF)))     // Wait til the end of the transmission
      {
      };

      SPDR = dacSPI1;
      while (!(SPSR & (1<<SPIF)))     // Wait til the end of the transmission
      {
      };
      digitalWrite(_slave_select,HIGH);
     
    }

Write a comment: