Switching Inputs on the Twisted Pear Audio Buffalo III-SE DAC

One of the neat features of the Buffalo III-SE is that it allows you to have both a SPDIF and I2S source connected and to switch between them using the on-board IP_S pins, thus obviating the need for an OTTO or other external switching mechanism. And if you’re controlling the BIII-SE with an Arduino you only need to use a single (digital) pin to switch between sources. Turns out it’s very easy to implement, but I couldn’t find any guidance on the web.

The following assumes that you have a SPDIF and I2S source connected the BIII-SE (block “1” in the picture below). ALl you need to do is connect a digital pin (I use pin #6) on the Arduino to the pin below the “S” on the IP_S header on the DAC – circled in red in the picture below with a red arrow pointing to it.

B3SE IPS

The code is simple as well.

Put this in your initialization block:

  #define IPSPIN 6 // Or whatever pin you're using
  pinMode(IPSPIN, OUTPUT);
  digitalWrite(IPSPIN, HIGH); //Enable pull-up resistor - default is SPDIF

And then just switch the pin HIGH when you want SPDIF and LOW when you want I2S.

If you’re using the HiFiDuino code, then just swap out the setAndPrintInputFormat function for this one:

void setAndPrintInputFormat(byte value){
  // This register also controls mono-8channel operation, thus more code...
  lcd.setCursor(8,0);
  switch(value){
  case 0:                            // Enable SPDIF for DATA 1 pin
    writeSabreReg(0x08,0xE8);        // Reg 8: Enable SPDIF input format
    bitSet(reg17L,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: write value into register
#ifdef DUALMONO
    bitSet(reg17R,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: write value into register
#endif DUALMONO
    writeSabreReg(0x12,0x01);        // Set SPDIF to input #1 (only input valid for BII is 1 and 5)
    spdifIn=true;                    // Indicates input format is spdif. 
    lcd.print("SPd");
    break;
  case 1:                            // Enable SPDIF for DATA 3 pin (For BIII use)
    writeSabreReg(0x08,0xE8);        // Reg 8: Enable SPDIF input format
    bitSet(reg17L,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: write value into register
#ifdef DUALMONO
    bitSet(reg17R,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: write value into register
#endif DUALMONO
    writeSabreReg(0x12,0x02);        // Set SPDIF to input #3 
    spdifIn=true;                    // Indicates input format is spdif.
    lcd.print("Sp3");
    break;
  case 2:                            // Enable SPDIF for DATA 7 pin (For BIII use)
    writeSabreReg(0x08,0xE8);        // Reg 8: Enable SPDIF input format
    bitSet(reg17L,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: write value into register
#ifdef DUALMONO
    bitSet(reg17R,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: write value into register
#endif DUALMONO
    writeSabreReg(0x12,0x40);        // Set SPDIF to input #7 
    spdifIn=true;                    // Indicates input format is spdif.
    lcd.print("Sp7");
    break; 
  case 3:                            // Enable SPDIF for DATA 7 pin (For BIII use)
    writeSabreReg(0x08,0xE8);        // Reg 8: Enable SPDIF input format
    bitSet(reg17L,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: write value into register
#ifdef DUALMONO
    bitSet(reg17R,3);                // Reg 17: auto spdif detection ON -Set bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: write value into register
#endif DUALMONO
    writeSabreReg(0x12,0x80);        // Set SPDIF to input #7 
    spdifIn=true;                    // Indicates input format is spdif.
    lcd.print("Sp8");
    break;
    
  case 4:                            // I2S 32 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitClear(reg10,4);               // Setting to I2S (2 bits required)
    bitClear(reg10,5);
    bitSet(reg10,6);                 // Setting to 32 bit (2 bits required)
    bitSet(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("I2S");
    break;
  case 5:                            // LJ 32 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitSet(reg10,4);                 // Set to LJ
    bitClear(reg10,5);
    bitSet(reg10,6);                 // Set to 32 bit
    bitSet(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("LJF");
    break;
  case 6:                            // RJ 32 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitSet(reg10,5);                 // Set to right justified format
    bitClear(reg10,4);
    bitSet(reg10,6);                 // Set to 32 bit
    bitSet(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("R32");
    break;
  case 7:                            // LRJ 24 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitSet(reg10,5);                 // Set to right justified format
    bitClear(reg10,4);
    bitClear(reg10,6);               // Set to 24 bits mode
    bitClear(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("R24");
    break;
  case 8:                            // LRJ 24 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitSet(reg10,5);                 // Set to right justified format
    bitClear(reg10,4);
    bitSet(reg10,6);                 // Set to 20 bits mode
    bitClear(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("R20");
    break; 
  case 9:                            // LRJ 16 bit
    bitClear(reg17L,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreLeftReg(0x11,reg17L);  // Reg 17: Auto spdif detection OFF
#ifdef DUALMONO
    bitClear(reg17R,3);              // Reg 17: manual SPDIF -Clear bit 3
    writeSabreRightReg(0x11,reg17R); // Reg 17: Auto spdif detection OFF
#endif DUALMONO
    writeSabreReg(0x08,0x68);        // Reg 8: Enable I2S/DSD input format
    spdifIn=false;                   // Set variable to indicate input format is I2S/DSD mode
    bitSet(reg10,5);                 // Set to right justified mode
    bitClear(reg10,4);
    bitClear(reg10,6);               // Set to 16 bit mode
    bitSet(reg10,7);
    writeSabreReg(0x0A,reg10);
    lcd.print("R16");
    break;   


    // lcd.write(127);                    // Print Arrow to indicate this is input seletion and not signal
  }

  if (spdifIn) { //If we're expecting SPDIF input, we want to pull IPS high on the DAC 
    digitalWrite(IPSPIN, HIGH); 
  } 
  else { //If we're not expecting SPDIF, pull IPS low
    digitalWrite(IPSPIN, LOW);
  }
}

Thanks to LeonvB and the folks at TPA for talking me through the process.

Advertisements

Hacking an Arduino Library to support a NewHaven OLED with an MCP23008 port expander

 

After looking at several sample Buffalo DAC projects I decided that I didn’t like the LCD screens most people were using. I had a Sansa Clip a few years back that used a very crisp OLED display – a quick search on Mouser and I found an inexpensive 20×4 NewHaven OLED (NHD-0420DZW-AY5-ND) that looked like it was  HD44780 compatible.  

It isn’t.

After reading a bunch of forum posts complaining about this display, I stumbled across someone who had tweaked the standard Arduino LiquidCrystal library to make it compatible with the OLED timing parameters: http://www.elcojacobs.com/controlling-an-oled-character-display-with-arduino/.  I swapped his library for LiquidCrystal and – huzzah! – my DAC was humming along with a beautiful OLED display.

The next step was to minimize the number of pins I was using on my Arduino using an I/O port expander.  By using the two i2c pins (A4 and A5) on the Arduino, a port expander gives you 8 outputs.  The downside is that the port expander makes things considerably slower – too slow to be usable.  So I was excited to find the LiquidTWI library as it promised to speed things up by “bursting” all 8 pins at once, rather than trying to address each pin serially: http://blog.lincomatic.com/?p=956.

And it worked!  Sort of.  I had to tweak the timing parameters again, but Elco had already shown me how to do that.

Everything worked perfectly except for custom characters – which is something the HiFiDuino code uses to display the volume.  You might be able to print one or two custom characters, but the display would ultimately go into WTF mode and you’d have to restart it.

I tried a very long “delay()” when custom characters were being written, but regardless of how long I waited, nothing helped.  As far as I could tell, the problem has something to do with the D7 pin on OLED display, which serves both as an input to the OLED and an output that indicates when the DAC is busy.  According to the datasheet, you are supposed to poll the busy pin and only write a command when the busy pin tells you the OLED has finished processing.

So, by hacking the Adafruit MCP23008 library, I was able to implement a function that reads the busy pin.  We only need to use this function when we are writing/creating custom characters, so it really doesn’t slow things down too much.  And the library takes care of invoking the function when necessary, so it’s invisible to anyone using the library.

BUT, BUT, BUT …

The volume display is tied to the movement of the rotary encoder, which uses an interrupt to indicate when it is being turned.  It seems the delay that the readBusy() function creates wreaks some havoc with the rotary encoder functionality, so a quick spin of the encoder doesn’t register.  So after all that, I’ve reverted back to directly connecting the OLED to the Arduino.

But maybe somebody else can benefit from the library I wrote.  If so, grab the code here: https://github.com/tharmas/LiquidJWM

HiFiDuino – Changes to Display Code

To control my Buffalo III-SE DAC, I use the source code and setup from hifiduino.wordpress.com.  I’ve noticed, however, that my display (a NewHaven OLED) appears to “flicker” because the sample rate and input source are updated every few seconds, even when they haven’t changed.  I made a few edits to the code so that the display will only be changed when the sample rate or input source changes.

First, I defined some new constants:

//Define constants used to identify incoming signal types 
#define NOLOCK -1 //constant used to identify that there is no lock on the incoming signal
#define SPDIF 1 //constant used to identify incoming signal as SPDIF
#define DSD 2 //constant used to identify incoming signal as DSD
#define PCM 3 //constant used to identify incoming signal as PCM

Then, some new variables:

unsigned long displayedSR = 0; // Holds the value of the last sample rate we received and are displaying
unsigned int displayedSigType = 0; // Holds the value of the signal type that is currently being displayed
boolean refreshDisplay = false; // Value that we will query to determine if we should update the display's sample rate

Then I created a new function to handle printing the sample rate and input source (not really necessary, but I find the code below easier to understand):

void printSampleRate(int sigType, long sampleRt) {
  //First, print out the signal type  
  lcd.setCursor(8,0);  //set the cursor to the start of the signal type character area
  if (sigType == NOLOCK) {  //No lock, but we should still show the input the user selected
    if(spdifIn) {  // spdifIn=true means we selected SDPIF input
      lcd.print("SP");  
    } else {  // Otherwise, we selected Serial Interface as input
      lcd.print("SE");
    }
    lcd.write(127);            // Print arrow to indicate it is input selection rather than an actual input      
  } else if (sigType == SPDIF) {  //We have a lock on a SPDIF signal
    lcd.print("SPd  ");    
  } else if (sigType == DSD) {    //We have a lock on a DSD signal
    lcd.print("DSD  ");
  } else {			   //If it's not SPDIF or DSD, it must be PCM
    lcd.print("PCM  ");    
  } 
  
  //Now, print out the sample rate
  lcd.setCursor(12,0);         	//Set the cursor to the start position of the sample rate characters
  lcd.print("        ");	//Clear the old sample rate reading
  lcd.setCursor(12,0);		//Go back to the sample rate character start position   
  
  if (sigType == NOLOCK) {		 //Update the display to show that we don't have a lock
    lcd.print(" No Lock");
  } else if(SRExact==true) {		 //We're going to display the exact sample rate 
    if (sigType != DSD) lcd.print(" ");  //Add a space before any non-DSD sample rate
    lcd.print(sampleRt, DEC);            //Print sample rate in exact format
  } else {				 //Print out nominal sample rates
    if(sigType == DSD){      // Print nominal DSD sample rates
      if(sampleRt>6143000) {
        lcd.print("6.1 MHz");
      } else if(sampleRt>5644000) {
        lcd.print("5.6 MHz");
      } else if(sampleRt>3071000) {
        lcd.print("3.0 MHz");
      } else if(sampleRt>2822000) {
        lcd.print("2.8 MHz");
      } else {
        lcd.print("UNKNOWN");
      }
    } else {  // If not DSD then it is I2S or SPDIF
      if(sampleRt>383900) {
        lcd.print("384 KHz");
      } else if(sampleRt>352700) {
        lcd.print("352 KHz");
      } else if(sampleRt>191900) {
        lcd.print("192 KHz");
      } else if(sampleRt>176300) {
        lcd.print("176 KHz");
      } else if(sampleRt>95900) {
        lcd.print(" 96 KHz");
      } else if(sampleRt>88100) {
        lcd.print(" 88 KHz");
      } else if(sampleRt>47900) {
        lcd.print(" 48 KHz");
      } else { 
        lcd.print(" 44 KHz");
      }        
    } //End of I2S/SPDIF nominal block    
 } //End of nominal sample rate block
} //End of printSampleRate function

I changed the first portion of the loop() function as follows:

  if(((millis() - displayMillis > INTERVAL_SAMPLE*1000)&&!selectMode) || refreshDisplay) {  //check to see if it's time to refresh the sample rate
    
    displayMillis = millis(); // Saving last time we displayed sample rate
    Status=readRegister(27);  // Read status register to see if we've got a lock on the signal
    int signalType = -1;      // Variable we will use to identify what type of signal we're receiving - set to undefined value until we have a signal 

    if (Status&B00000001) {   // Locked onto the signal, so determine whether we need to update display
    	sr=sampleRate();      // Get the sample rate from register
	//Determine what type of signal we are receiving
    	SPDIFValid = false;
        if (Status&B00000100) {
    	   signalType=SPDIF;
           SPDIFValid = true; 			// Incoming signal is valid SPDIF
    	} else if (Status&B00001000) {
    	   signalType=DSD;  			// Incoming signal is DSD
    	   sr*=64;				// For DSD, the sample rate is 64x
    	} else {
    	   signalType=PCM;			// If we have a lock and the incoming signal is not SPDIF or DSD, it must be PCM
      	}  // End of "lock signal" if statement
    } else {                                    // We can't lock onto the incoming signal
      signalType = NOLOCK;                      // Set our signal type to indicate that there is no lock
      sr = -1;                                  // Because we can't lock onto the signal, we can't figure out the appopriate sample rate - set to -1 (undefined)
    }  // End of "no lock" else statement
    
    if ((displayedSR != sr) || (displayedSigType != signalType) || refreshDisplay) {	//If the sample rate we were displaying does not match the current sample rate
                                                                                        //or if the signal type has changed, we need to update the display   
       printSampleRate(signalType, sr);         // Call the printSampleRate function and pass in the signal type and the sample rate
       displayedSR = sr;			// We've updated the displayed sample rate, so set the displayed sample rate variable to match
       displayedSigType = signalType;           // We've also updated the displayed signal type, so set the corresponding variable
       refreshDisplay = false;                  // We've refreshed the display, so we won't need to do this again until something changes
    } // End of "refresh display" if statement

  // Print out "heartbeat" to show that the software is still running...
    lcd.setCursor(0,1);
    if(pulse++%2)lcd.write(0xDE);  // Print a "pulse" to indicate the controller is working
    else lcd.write(0xEB);
  }

Finally, I added a line to the last “if” statement in the loop function, setting the refreshDisplay boolean equal to true:

 // When the being-in-select mode timer expires, we revert to volume/display mode
  if(selectMode&&millis()-selectMillis > INTERVAL_SELECT*1000){
    selectMode=false;  // No longer in select mode
    printSelectBar(A); // "Removes" the select bar by printing blanks
    select=VOL;        // Back to volume mode
    writeSettings();   // Write new settings into EEPROM, including current input
    refreshDisplay = true;  //We've left "menu" mode, so we need to update the sample rate and signal type
  }

Currently, I only have SPDIF inputs connected to my DAC, so I would appreciate people pointing out any bugs or issues they might encounter with the changes above.