/*************************************************************************************************** * HIFIDUINO v. B06e * * Saturday January 8th, 2011 * * Arduino code for Buffalo II DAC and other DACs based on the Sabre32 DAC chip. This code is adapted * from an earlier version developed for the Wolfson 8741 DAC chip. For more information visit * www.hifiduino.wordpress.com or www.hifiduino.blogspot.com * * (Although this code is based on a confidential data sheet for which I've signed an NDA, I've also * requested and have received permission to publish this code). * * Change log: * v. B06e 01/08/11: Supports 32-bit I2S input in accordance to Buffalo II input wiring of SabreDAC * Rearranged code for default conditions, correct reading of sample rate in I2S * Allows manual "best DPLL bandwidth" setting. Allows manual selection of I2S or * SPDIF. Can set default startup to I2S or SPDIF (in the code) * v. B05 11/25/10: Added remote control capability using the Apple Aluminum Remote Control. This * version enables remote volume control only. * v. B04 11/06/10: Added large numbers, tidy-up the UI, "pulse" indicator -every time the status * information is read. Also status indicator for PCM/DSD and Signal Lock * v. B03 11/02/10: Supports s/w debounce, added setting of FIR filter, DPLL bandwidth, jitter * reduction and adjustment of LCD brightness. * v. B021 10/18/10: No new functionality, but cleaned up the code and comments. * v. B02 10/15/10: Added reading of sample rate. * v. B01 10/11/10: Volume control, LCD, Rotary Encoder. ***************************************************************************************************/ /*************************************************************************************************** * look for "spdifStart" and set to true if you want the DAC to startup in SPDIF mode and false if * you want the DAC to startup in I2S mode * * ***************************************************************************************************/ // LIBRARIES #include <LiquidCrystal.h> // For LCD // Initialize the library with the numbers of the interface pins LiquidCrystal lcd(12, 11, 10, 9, 8, 7); #include <Wire.h> // For I2C #include <pins_arduino.h> // This is needed for a new pulseIn function for remote // CONSTANT DEFINITION // (The digital volume for Sabre32 is 0 to -127 db in .5 db steps) // I am using "attenuation" instead of "volume" because it is too confusing to my // head because increasing values of volume means decreasing values of attenuation#define DEFAULTATTNU 0x64 //-50 dB this is 50x2=100 or 0x64 #define MAXATTNU 0xC6 //-99dB this is 99X2=198 or 0xC6 #define MINATTNU 0x00 //-0 dB #define VOLUPPIN 4 // Button to increase volume or RotEnc A terminal #define VOLDOWNPIN 2 // Button to decrease volume or RotEnc B terminal #define SELECTPIN 5 // Switch to select function #define BRIPIN 6 // Pin to control the LCD brightness with analogWrite #define REMOTEPIN 3 // Pin for IR receiver (remote control) #define INTERVAL_SAMPLE 2 // Time interval in SECONDS for refreshing the sample rate #define INTERVAL_BOUNCE 2 // Time in milliseconds to debounce the rotary encoder #define INTERVAL_SWITCHBOUNCE 200 // Time in milliseconds to debounce switch #define INTERVAL_SELECT 4 // Time in sec to exit select mode when no activity #define VOL 0 // The order of selection when clicking the select switch #define FIL 2 // FIR filter selection #define JIT 3 // Jitter on-off #define INP 1 // Input selection #define DPL 4 // DPLL bandwidth setting #define B 0xFF // The character for a completely filled box #define A 0x20 // The character for blank // VARIABLE DECLARATION byte regVal; // Variable to pass register value byte regAddr; // Variable to pass register address value byte value; // Variable to pass some value to a function byte currAttnu; // Variable to hold the current attenuation value unsigned long displayMillis = 0; // Stores last recorded time for display interval unsigned long debounceMillis = 0; // Stores last recorded time for switch debounce interval unsigned long selectMillis = 0; // Stores last recorded time for being in select mode byte dpllBW; // To record DPLL bandwidth setting byte iirBW; // To record IIR filter setting byte brightness=0; // To record LCD brightness level byte select; // To record current select option boolean sharp; // FIR filter mode (if not sharp, then it is slow) boolean jitter; // Jitter reduction (either on or off) boolean selectMode; // To indicate whether in select mode or not boolean SPDIFValid; // To indicate whether SPDIF valid data boolean spdifStart=true; // To indicate default input at startup (true, then starts with spdif) boolean spdifIn; // To indicate in I2S/DSD or SPDIF input mode boolean conditioned=false; // To indicate if dpll has been conditioned (once at startup) byte pulse=0; byte status; // The following variables for the remote control feature int duration; // Duration of the IR pulse int mask; int c1; // Byte 1 of the 32-bit remote command code int c2; // Byte 2 of the 32-bit remote command code int c3; // Byte 3 of the 32-bit remote command code int c4; // Byte 4 of the 32-bit remote command code int IRkey; // The unique code (Byte 3) of the remote key int previousIRkey; // The previous code (used for repeat) /* LCD For the LCD, I am using a "standard" HD44780 20x4 display, and I am using the official Arduino LiquidCrystal library that comes with the standard installation. This is as standard as it can be and this type of LCD is available everywhere The pin assignment is different from the example code and it is as follows: * LCD RS pin to digital pin 12 * LCD Enable pin to digital pin 11 * LCD D4 pin to digital pin 10 * LCD D5 pin to digital pin 9 * LCD D6 pin to digital pin 8 * LCD D7 pin to digital pin 7 */ /* ROTARY ENCODER The rotary encoder is connected to digital pin 2 (A) and digital pin 4 (B). pin 2 (and pin 3) is an interrupt line in Arduino. It does not matter which terminal is connected to which pin. The third terminal is connected to GND. At each cycle, the rotary encoder will pull the pins LOW or HIGH and the transition is detected by the interrupt line. The interrupt service routine is specified below. Debounce In this version, the code implements debouncing by adding a few millisecond delay that pauses the code from evaluating the state of the pins. Typically, all the switching noise –the “bouncing” is generated in the first few milliseconds after the switch event. The code is optimized by calling the delay only if there is any activity in the rotary encoder. Activity in the rotary encoder is detected with an interrupt. The interrupt service routine is minimalist in design and determines if there is any motion in the rotary encoder. In addition, in order to minimize spurious interrupts –those generated by the switching noise, hardware debounce can be optionally implemented in the rotary encoder. Although the code will ignore all the interrupts generated during the debounce delay time, interrupts are still generated and it is still a good idea to minimize those spurious interrupts. There is just a slight change in the software if H/W debouncing is implemented, namely the pull up resistors in the pins are to be disabled. Further, the rotary encoder has an on-off switch, and the debouncing of the switch is also done in s/w (again the same h/w debouncing can be implemented, but it is optional). It is not based on interrupts and because pushing the switch manually will generate not only noise during the switching, but the switch can remain pressed for 100 milliseconds or more because one cannot lift the finger that fast. In this implementation and with my way of pushing down the switch, 200 msec is an appropriate value. INTERRUPT SERVICE ROUTINE FOR ROTARY ENCODER The interrupt service routine has been designed to be minimalist in nature. The code just sets a flag indicating that some activity has been detected in the rotary encoder. */ // Rotary encoder interrupt service routine static boolean rotating=false; void rotEncoder() { rotating=true; // If motion is detected in the rotary encoder, set the flag to true } /* READING THE SAMPLE RATE The sample rate can be calculated by reading the DPLL 32-bit register. For SPDIF DPLL value is divided by (2^32/Crystal-Frequency). In Buffalo II, the Crystal frequency is 80,000,000 Hz. In Arduino (and other small microprocessors) it is NOT advisable to do floating point math because "it is very slow"; therefore integer math will be used to calculate the sample rate. The value of 2^32/80,000,000 is 53.687091 (which is a floating point number). If we use the integer part (53 or 54) we get the following results for a 44.1K sample rate signal: divided by 53 the result is 44.677K; divided by 54, the result is 43.849K. Clearly there are large errors from being confined to integer math. The actual result, if we use floating point math and use all the significant digits is 44,105 Hz (the 5 Hz deviation from ideal 44100 Hz is within the specification of SPDIF and the tolerances of the crystals and clocks involved) In order to increase the accuracy of the integer calculation, we can use more of the significant digits of the divisor. I did some evaluation of the DPLL register values for sample rates ranging from 44.1K to 192K and noticed that I could multiply the value of the DPLL number by up to 400 without overflowing the 32-bits. Therefore, since we have 32 bit number to work with, we can multiply the DPLL number by 400 and then divide by 400X53.687091=21475. If we do this, we obtain 44.105K which is the same as the exact value. For I2S input the dpll number is divided by (2^32*64/Crystal-Frequency) Note the 64 factor. The value of this is 3435.97 which rounds off nicely to 3436 (which is only 0.0008% error). The resultant value for the sample rate is the same whether in spdif or I2S mode. */ // Sample rate reading routines volatile unsigned long DPLLNum; // Variable to hold DPLL value byte readDPLL(byte regAddr) { Wire.beginTransmission(0x48); // Hard coded the Sabre/Buffalo device address Wire.send(regAddr); // Specify the DPLL register from which to read value Wire.endTransmission(); Wire.requestFrom(0x48,1); // Hard coded to Buffalo, request one byte from address // specified with Wire.send() //while(!Wire.available()) { // Wait for byte to be available on the bus if(Wire.available()) { } return Wire.receive(); // Return the value returned by specified register } unsigned long sampleRate() { DPLLNum=0; // Reading the 4 registers of DPLL one byte at a time and stuffing into a single 32-bit number DPLLNum|=readDPLL(31); DPLLNum<<=8; DPLLNum|=readDPLL(30); DPLLNum<<=8; DPLLNum|=readDPLL(29); DPLLNum<<=8; DPLLNum|=readDPLL(28); if (SPDIFValid){ DPLLNum*=400; // Calculate SR for SPDIF (part 1) DPLLNum/=21475; // Calculate SR for SDPIF (part 2) } else { DPLLNum/=3436; // Calculate SR for I2S } return DPLLNum; } // Reading the status register. There is a status register providing the following information: // 1- dsd or pcm mode // 1- spdif valid or invalid // 3- spdif mode enabled or disabled. // 4- Jitter Eliminator locked/not locked to incoming signal byte readStatus() { // Hardcoded to the status register Wire.beginTransmission(0x48); // Hard coded the Sabre/Buffalo device address Wire.send(27); // Hard coded to status register Wire.endTransmission(); Wire.requestFrom(0x48,1); // Hard coded to Buffalo, request one byte from address // specified with Wire.send() //while(!Wire.available()) { // Wait for byte to be available on the bus if(Wire.available()) { } return Wire.receive(); // Return the value returned by specified register } /* CONTROLLING THE DIGITAL ATTENUATION (VOLUME) IN THE DAC The device address of Sabre DAC Datasheet specifies the address as 0x90 which is an 8-bit value. The wire library in Arduino uses 7-bit device addresses and the 8th R/W bit is added automatically depending on whether you use the write call [beginTransmission()] or the read call [requestFrom()]. Therefore, you will use the 7 most significant bits of the 8-bit address. In our example, 0x90 becomes 0x48 as follows: 0x90: 0101000 (we eliminate the rightmost bit) 0x48: 0010100 */ void writeSabreReg(byte regAddr, byte regVal) { Wire.beginTransmission(0x48); //Hard coded to the the Sabre/Buffalo device address Wire.send(regAddr); // Specifying the address of volume register Wire.send(regVal); // Writing the volume value into the register Wire.endTransmission(); } void setSabreVolume(byte regVal) { writeSabreReg(0, regVal); // set up volume in DAC1 writeSabreReg(1, regVal); // set up volume in DAC2 writeSabreReg(2, regVal); // set up volume in DAC3 writeSabreReg(3, regVal); // set up volume in DAC4 writeSabreReg(4, regVal); // set up volume in DAC5 writeSabreReg(5, regVal); // set up volume in DAC6 writeSabreReg(6, regVal); // set up volume in DAC7 writeSabreReg(7, regVal); // set up volume in DAC8 } /* The following function prints a bar at the left most column to indicate that we are in "select" mode. One can choose any character as the "bar" For example, I used the solid square character */ void printSelectBar(byte value){ lcd.setCursor(0,0); lcd.write(value); lcd.setCursor(0,1); lcd.write(value); lcd.setCursor(0,2); lcd.write(value); lcd.setCursor(0,3); lcd.write(value); } void setAndPrintDPLL(byte regVal){ switch(regVal){ case 0: lcd.setCursor(4,3); lcd.print("Be"); writeSabreReg(0x19,0x02); // Reg 25: Use best DPLL settings break; case 1: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x85); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print("<L"); break; case 2: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x89); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print(" L"); break; case 3: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x8D); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print("LM"); break; case 4: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x91); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print(" M"); break; case 5: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x95); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print("MH"); break; case 6: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x99); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print(" H"); break; case 7: writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B, 0x9D); // Reg 11: Set corresponding DPLL bandwidth lcd.setCursor(4,3); lcd.print(">H"); break; } } /* Custom characters: I am using custom characters to build large size characters. These characters are 9 times (3x3) the regular size. When you are far away using the remote control, you might need large characters to see what is being displayed. */ // Define 8 custom characters byte cc0[8] = { // Custom Character 0 B00000, B00111, B01111, B11111, B11111, B11111, B11111, B11111 }; byte cc1[8] = { // Custom Character 1 B11100, B11110, B11111, B11111, B11111, B11111, B11111, B11111 }; byte cc2[8] = { // Custom Character 2 B11111, B11111, B11111, B11111, B11111, B00000, B00000, B00000 }; byte cc3[8] = { // Custom Character 3 B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111 }; byte cc4[8] = { // Custom Character 4 B11111, B11111, B11111, B11111, B11111, B11111, B01111, B00111 }; byte cc5[8] = { // Custom Character 5 B11111, B11111, B11111, B11111, B11111, B11111, B00000, B00000 }; byte cc6[8] = { // Custom Character 6 B00000, B11111, B11111, B11111, B11111, B11111, B00000, B00000 }; byte cc7[8] = { // Custom Character 7 B00000, B11100, B11110, B11111, B11111, B11111, B11111, B11111 }; // Function to send custom characters to the display's RAM void DefineCustomChar(){ lcd.createChar(0,cc0); // cc0 becomes character 0 lcd.createChar(1,cc1); // cc1 becomes character 1 lcd.createChar(2,cc2); // cc2 becomes character 2 lcd.createChar(3,cc3); // cc3 becomes character 3 lcd.createChar(4,cc4); // cc4 becomes character 4 lcd.createChar(5,cc5); // cc5 becomes character 5 lcd.createChar(6,cc6); // cc6 becomes character 6 lcd.createChar(7,cc7); // cc7 becomes character 7 } // Array index into parts of big numbers. Each number consists of 9 custom characters // in a 3x3 matrix. To print a number, you use the array index corresponding to the number // times 3. For example to print the number 5, you will print bn1[15], bn1[16] and bn1[17] // for the first row of the large number, and then bn2[15], bn2[16] and bn2[17] and so on. // 0 1 2 3 4 5 6 7 8 9 char bn1[]={B,2,1, 2,1,A, 2,2,1, 2,2,1, 0,A,B, B,2,2, B,2,2, 2,2,B, B,2,1, B,2,1}; char bn2[]={B,A,B, A,B,A ,0,6,5, A,2,1, 5,6,B, 2,2,1, B,6,7, A,0,5, B,6,B, 5,6,B}; char bn3[]={4,3,B, 3,B,3, B,3,3, 4,3,B, A,A,B, 4,3,B, 4,3,B, A,B,A, 4,3,B, A,A,B}; // Functions for printing two large digits. Value is the column number // and number is the number to print. Works from 00-99 void printTwoNumber(byte value, byte number){ lcd.setCursor(value,1); // Printing line 1 of the two-digit number lcd.write(bn1[(number/10)*3]); lcd.write(bn1[(number/10)*3+1]); lcd.write(bn1[(number/10)*3+2]); lcd.write(A); // Blank lcd.write(bn1[(number%10)*3]); lcd.write(bn1[(number%10)*3+1]); lcd.write(bn1[(number%10)*3+2]); lcd.setCursor(value,2); // Printing line 2 of the two-digit number lcd.write(bn2[(number/10)*3]); lcd.write(bn2[(number/10)*3+1]); lcd.write(bn2[(number/10)*3+2]); lcd.write(A); // Blank lcd.write(bn2[(number%10)*3]); lcd.write(bn2[(number%10)*3+1]); lcd.write(bn2[(number%10)*3+2]); lcd.setCursor(value,3); // Printing line 3 of the two-digit number lcd.write(bn3[(number/10)*3]); lcd.write(bn3[(number/10)*3+1]); lcd.write(bn3[(number/10)*3+2]); lcd.write(A); // Blank lcd.write(bn3[(number%10)*3]); lcd.write(bn3[(number%10)*3+1]); lcd.write(bn3[(number%10)*3+2]); } /* The following function defines a new pulseIn function because the pulseIn function in the Arduino library does not exit if there is a pulse that does not end. Typically, this would not cause any problems if you are reading true pulses, but because the remote code I wrote measures "UP pulses" there is a chance that some noise would trigger a single pulse where the current pulseIn function would hang. The reason is the following: the IR receiver when it is not receiving any signals outputs HIGH. If there is a signal (a real pulse), it outputs LOW and then HIGH. If I were to measure DOWN pulses, this would be fine, but because the NEC protocol in the Apple remote uses distance between pulses to codify its information, I measure the time between pulses which is an "UP pulse". In reality these UP pulses are not really pulses, but the time between the real pulses from the remote control. This code is taken from the Arduino code base (thanks to users in the Arduino forum) and modified to check for end of pulse */ unsigned long newpulseIn(uint8_t pin, uint8_t state, unsigned long timeout) { uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); uint8_t stateMask = (state ? bit : 0); unsigned long width = 0; unsigned long numloops = 0; unsigned long maxloops = microsecondsToClockCycles(timeout) / 16; // wait for any previous pulse to end while ((*portInputRegister(port) & bit) == stateMask) if (numloops++ == maxloops) return 0; // wait for the pulse to start while ((*portInputRegister(port) & bit) != stateMask) if (numloops++ == maxloops) return 0; // wait for the pulse to stop while ((*portInputRegister(port) & bit) == stateMask){ if(width++ == maxloops) // added the check for end of pulse return 0; } return clockCyclesToMicroseconds(width * 20+16); // Recalibrated because of additional code // in the width loop } /* The following function returns the code from the Apple Aluminum remote control. The Apple remote is based on the NEC infrared remote protocol. Of the 32 bits (4 bytes) coded in the protocol, only the third byte corresponds to the keys. The function also handles errors due to noise (returns 255) and the repeat code (returns zero) The Apple remote returns the following codes: Up key: 238 135 011 089 Down key: 238 135 013 089 Left key: 238 135 008 089 Right key: 238 135 007 089 Center key: 238 135 093 089 followed by 238 135 004 089 (don't know why there is two commands) Menu key: 238 135 002 089 Play key: 238 135 094 089 followed by 238 135 004 089 (don't know why there is two commands) */ int getIRkey() { c1=0; c2=0; c3=0; c4=0; duration=1; while((duration=newpulseIn(REMOTEPIN, HIGH, 15000)) < 2000 && duration!=0) { // Wait for start pulse } if (duration == 0) // This is an error no start or end of pulse return(255); // Use 255 as Error else if (duration<3000) // This is the repeat return (0); // Use zero as the repeat code else if (duration<5000){ // This is the command get the 4 byte mask = 1; for (int i = 0; i < 8; i++){ // get 8 bits if(newpulseIn(REMOTEPIN, HIGH, 3000)>1000) // If "1" pulse c1 |= mask; // Put the "1" in position mask <<= 1; // shift mask to next bit } mask = 1; for (int i = 0; i < 8; i++){ // get 8 bits if(newpulseIn(REMOTEPIN, HIGH, 3000)>1000) // If "1" pulse c2 |= mask; // Put the "1" in position mask <<= 1; // shift mask to next bit } mask = 1; for (int i = 0; i < 8; i++){ // get 8 bits if(newpulseIn(REMOTEPIN, HIGH, 3000)>1000) // If "1" pulse c3 |= mask; // Put the "1" in position mask <<= 1; // shift mask to next bit } mask = 1; for (int i = 0; i < 8; i++){ // get 8 bits if(newpulseIn(REMOTEPIN, HIGH, 3000)>1000) // If "1" pulse c4 |= mask; // Put the "1" in position mask <<= 1; // shift mask to next bit } // Serial.println(c1, HEX); //For debugging // Serial.println(c2, HEX); //For debugging // Serial.println(c3, HEX); //For debugging // Serial.println(c4, HEX); //For debugging return(c3); } } /* The following describes some of the register configuarion for BuffII DAC: Set up for I2S/DSD support according to BuffII input wiring Register 14 (0x0E) DAC source, IIR Bandwidth and FIR roll off |0| | | | | | | | Source of DAC8 is DAC8 (D) |1| | | | | | | | Source of DAC8 is DAC6 | |0| | | | | | | Source of DAC7 is DAC7 (D) | |1| | | | | | | Source of DAC7 is DAC5 | | |0| | | | | | Source of DAC4 is DAC4 (D) | | |1| | | | | | Source of DAC4 is DAC2 | | | |0| | | | | Source of DAC3 is DAC3 (D) | | | |1| | | | | Source of DAC3 is DAC1 | | | | |1| | | | Reserved, must be 1 (D) | | | | | |0|0| | IIR Bandwidth: Normal (for PCM) | | | | | |0|1| | IIR Bandwidth: 50k (for DSD) (D) | | | | | |1|0| | IIR Bandwidth: 60k (for DSD) | | | | | |1|1| | IIR Bandwidth: 70k (for DSD) | | | | | | | |0| FIR Rolloff: Slow | | | | | | | |1| FIR Rolloff: Fast (D) For the way Buffalo II input is wired we use the following: 11111001 or 0xF9 for I2S, normal BW (PCM) and fast roll off (default for BuffII) ------------------ Set up to allow all DPLL settings Register 25 (0x19): DPLL Mode control |0|0|0|0|0|0| | | Reserved, must be zeros (D) | | | | | | |0| | DPLL Bandwidht: allow all Settings | | | | | | |1| | DPLL Bandwidht: Use best DPLL Settings (D) | | | | | | | |0| DPLL128x: Use DPLL setting (D) | | | | | | | |1| DPLL128x: Multiply DPLL by 128 Allow all DPLL settings: 00000000 or 0x00 Use best DPLL settings: 00000010 or 0x02 ------------------ 4- Set up for lowest DPLL bandwidth Register 11 (0x0B) (MC2) |1|0|0| | | | | | Reserved, Must be 100 (D) | | | |0|0|0| | | DPLL BW: No Bandwidth | | | |0|0|1| | | DPLL BW: Lowest (D) | | | |0|1|0| | | DPLL BW: Low | | | |0|1|1| | | DPLL BW: Medium-Low | | | |1|0|0| | | DPLL BW: Medium | | | |1|0|1| | | DPLL BW: Medium-High | | | |1|1|0| | | DPLL BW: High | | | |1|1|1| | | DPLL BW: Highest | | | | | | |0|0| DeEmph: 32k | | | | | | |0|1| DeEmph: 44.1k (D) | | | | | | |1|0| DeEmph: 48k | | | | | | |1|1| Reserved, do not use Lowest Bandwidth 44.1K De-emphasis select (these are chip default): 10000101 or 0x85 (or decimal 133) ------------------ Register 17 (0x11) (MC5) |1| | | | | | | | Mono Right (if set for MONO) |0| | | | | | | | Mono Left (if set for MONO) (D) | |1| | | | | | | OSF (Oversample filter) Bypass | |0| | | | | | | Use OSF (D) | | |1| | | | | | Relock Jitter Reduction | | |0| | | | | | Normal Operation Jitter Reduction (D) | | | |1| | | | | SPDIF: Auto deemph ON (D) | | | |0| | | | | SPDIF: Auto deemph OFF | | | | |1| | | | SPDIF Auto (Only if no I2S on pins) (D) | | | | |0| | | | SPDIF Manual (Manually select SPDIF input) | | | | | |1| | | FIR: 28 coefficients (D) | | | | | |0| | | FIR: 27 coefficients | | | | | | |1| | FIR: Phase invert | | | | | | |0| | FIR: Phase NO invert (D) | | | | | | | |1| All MONO (Then select Mono L or R) | | | | | | | |0| Eight channel (D) ------------------ Register 8 (0x08) Auto-mute level, manual spdif/i2s |0| | | | | | | | Use I2S or DSD (D) |1| | | | | | | | Use SPDIF | |1|1|0|1|0|0|0| Automute trigger point (D) I2S/DSD input: 01101000 (0x68) SPDIF input: 11101000 (0xE8) NOTE on auto/manual SPDIF selection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Apparentely, auto detection of spdif and I2S (with I2S/DSD enabled in reg 8), only works when the DAC powers up. Once you turn off auto-SPDIF and then turn on auto-SPDIF, it will not work with SPDIF input while reg 8 is still set for I2S/DSD. In order for SDPIF to work, reg 8 must be set for SPDIF and reg 17 for auto- SDPIF. In summary: reg 17 auto-SPDIF | reg 8 source | Result ~~~~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~|~~~~~~~ ON | I2S/DSD | SPDIF ON: Only works when DAC starts with these (default) settings ON | SPDIF | SPDIF ON: Works even when auto-SPDIF is set to OFF, then to ON (1) OFF | SPDIF | SPDIF OFF, I2S OFF (3) OFF | I2S/DSD | I2S ON (2) Thus for manual operation, use (1) to select SDPIF; use (2) to select I2S. I don't know what is the purpose of (3) Apparently manual spdif will also work if you write to register 18 (selecting the spdif input line. But I haven't tested this yet... ------------------ */ // The following routine sets up the registers in the DAC at startup void startDac(){ writeSabreReg(0x0E,0xF9); // Reg 14: BuffII I2S wiring, normal IIR BW and Fast rolloff sharp=true; // Set variable "sharp" to true (fast rolloff) writeSabreReg(0x0A,0xCE); // Reg 10: 32 bit I2S, jitter reduction ON jitter=true; // Set variable "jitter" to true if (spdifStart){ // Default startup is spdif writeSabreReg(0x08,0xE8); // Reg 8: Enable SPDIF input writeSabreReg(0x11,0x0C); // Reg 17: auto spdif detection ON writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B,0x85); // Reg 11: Set for Lowest DPLL bandwidth dpllBW=1; // Set variable "dpllBW" to 1 (0-7, 1 meaning lowest) spdifIn=true; // Indicates input is spdif } else { // Default startup is I2S/DSD writeSabreReg(0x11,0x04); // Reg 17: Auto spdif detection OFF writeSabreReg(0x08,0x68); // Reg 8: Enable I2S/DSD input writeSabreReg(0x19,0x02); // Reg 25: Use best DPLL settings dpllBW=0; // Set variable "dpllBW" to 0 (0-7, 0 meaning best) spdifIn=false; // Set variable to indicate input in I2S/DSD mode } writeSabreReg(0x12,0x10); // Reg 18: Reroute SPDIf (to see if it improves i2S dropouts) currAttnu=DEFAULTATTNU; // Startup volume (attenuation) level setSabreVolume(currAttnu); // Reg 0 to Reg 7 Set volume registers with startup volume level } /************************ MAIN PROGRAM ************************************************************/ void setup() { lcd.begin(20, 4); // Set up the LCD's number of columns and rows: DefineCustomChar(); // Create the custom characters Wire.begin(); // Join the I2C bus as a master // Attach Interrupts attachInterrupt(0, rotEncoder, CHANGE); // ISR for rotary encoder on pin 2 // Serial.begin(9600); // This is here for debugging // Set up the pin modes pinMode(VOLUPPIN, INPUT); // Button switch or Encoder pin for volume up digitalWrite(VOLUPPIN, HIGH); // If H/W debouncing is implemented, set to LOW pinMode(VOLDOWNPIN, INPUT); // Button switch or Encoder pin for volume down digitalWrite(VOLDOWNPIN, HIGH); // If H/W debouncing is implemented, set to LOW pinMode(REMOTEPIN, INPUT); // Pin for IR sensor digitalWrite(REMOTEPIN, HIGH); // Enable pull-up resistor pinMode(SELECTPIN, INPUT); // Button switch or encoder pin for switch digitalWrite(SELECTPIN, HIGH); // Enable pull-up resistor pinMode(6, OUTPUT); // Controls the brightness of LCD through a transistor // Print the welcome message and other labels to the LCD lcd.clear(); lcd.setCursor(2,1); lcd.print("H I F I D U I N O"); lcd.setCursor(7,2); lcd.print("v.B06e"); lcd.setCursor(4,3); lcd.print("JAN 8 2011"); delay(1000); // Startup the DAC with correct/default register values startDac(); // Display main screen lcd.clear(); lcd.setCursor(1,0); lcd.print("SR: vol -dB"); lcd.setCursor(1,1); lcd.print("Fi:"); if(sharp)lcd.print("SHR"); else lcd.print("SLOW"); lcd.setCursor(1,2); lcd.print("Jt:"); if(jitter) lcd.print("ON "); else lcd.print("OFF"); lcd.setCursor(1,3); lcd.print("PL:"); setAndPrintDPLL(dpllBW%8); printTwoNumber(13,currAttnu/2); //analogWrite(BRIPIN,10); // Set up screen brightness } // End setup() void loop() { // Print the sample rate (once every "INTERVAL_SAMPLE" time) if((millis() - displayMillis > INTERVAL_SAMPLE*1000)&&!selectMode) { displayMillis = millis(); // Saving last time we display sample rate status=readStatus(); // Read status register if(status&B00000100) SPDIFValid=true; // Determine if valid spdif data else SPDIFValid=false; // SR calculation depends on I2S or SPDIf // Update the sample rate display lcd.setCursor(4,0); lcd.print(" "); lcd.setCursor(4,0); lcd.print(sampleRate(), DEC); // Print type of input source data lcd.setCursor(8,1); if(status&B00000001) // There is lock on a signal if(status&B00001000) lcd.print("DSD "); // Determines if DSD else // If not DSD then it is I2S or SPDIF if(SPDIFValid) lcd.print("SPd "); // If valid spdif data, then it is spdif else lcd.print("i2S "); // Otherwise it is I2S else // There is no lock we print input selection if(spdifIn) { // spdifIn=true means we selected SDPIF input lcd.print("SPd"); lcd.print(127,BYTE); } else { // Otherwise, we selected I2S/DSD as input lcd.print("I/D"); lcd.print(127,BYTE); } // Print locked or no locked on incomming signal and type of input lcd.setCursor(8,3); if(status&B00000001) lcd.print("LOCK"); // There is lock on a signal else lcd.print("NLCK"); lcd.setCursor(11,0); if(pulse++%2)lcd.write(0xDE); // Print a "pulse" to indicate the controller is working else lcd.write(0xEB); } /* The following code is for the remote control. It handles the codes generated by the Apple remote control except 0 has been designated (by me) for "repeat" and 255 designated (by me) as an error code. The current Apple remote does not generate these codes so it is safe to designate them for other things. */ while(digitalRead(REMOTEPIN)==LOW){ if((IRkey=getIRkey())==255){ // Do nothing } else { if(IRkey==0){ // Repeat IRkey=previousIRkey; } else { // New command previousIRkey=IRkey; } } switch(IRkey){ case 11: // 11 is the up key, we will use for volume up if (currAttnu>MINATTNU) // Check if not already at minimum attenuation { currAttnu-=2; // Decrease attenuation 1 dB (increase volume 1 db) setSabreVolume(currAttnu); // Write value into registers printTwoNumber(13,currAttnu/2); // Divide by 2 to print in whole dBs } break; case 13: // 13 is the down key, we will use for volume down if (currAttnu<MAXATTNU) // Check if not already at maximum attenuation { currAttnu+=2; // Increase 1 dB attenuation (decrease volume 1 db) setSabreVolume(currAttnu); // Write value into registers printTwoNumber(13,currAttnu/2); // Divide by 2 to print in whole dBs } break; } } // End of remote control code /* To debounce the switch, we detect the first instance of the "click" and then ignore the switch: first during the switch "bouncing" time and then while the switch remains pressed because one cannot lift the finger so fast. We want to register a single "click" per push of the switch */ if((digitalRead(SELECTPIN)==LOW)&&((millis()-debounceMillis)>INTERVAL_SWITCHBOUNCE)) { selectMode=true; // Now we are in select mode printSelectBar(255); // Indicate we are in select mode lcd.setCursor(1,0); // Print input selection and value lcd.print("IN: "); // Pring input label lcd.setCursor(4,0); if(spdifIn) lcd.print("SPDIF "); else lcd.print("i2sDSD"); debounceMillis=millis(); // Start debounce timer selectMillis=millis(); // Start being-in-select-mode timer select++; // Move to next select option switch(select%5){ case VOL: printSelectBar(255); break; case INP: printSelectBar(255); lcd.setCursor(0,0); lcd.print(126,BYTE); break; case FIL: printSelectBar(255); lcd.setCursor(0,1); lcd.print(126,BYTE); break; case JIT: printSelectBar(255); lcd.setCursor(0,2); lcd.print(126,BYTE); break; case DPL: printSelectBar(255); lcd.setCursor(0,3); lcd.print(126,BYTE); break; } // End of switch } if(rotating) { delay(INTERVAL_BOUNCE); // debounce by waiting INTERVAL_BOUNCE time switch(select%5){ case VOL: if (digitalRead(4) == digitalRead(2)) // CCW { if (currAttnu<MAXATTNU) // Check if not already at maximum attenuation { currAttnu+=2; // Increase 1 dB attenuation (decrease volume 1 db) setSabreVolume(currAttnu); // Write value into registers printTwoNumber(13,currAttnu/2); // Divide by 2 to print in whole dBs } } else // If not CCW, then it is CW { if (currAttnu>MINATTNU) // Check if not already at minimum attenuation { currAttnu-=2; // Decrease attenuation 1 dB (increase volume 1 db) setSabreVolume(currAttnu); // Write value into registers printTwoNumber(13,currAttnu/2); // Divide by 2 to print in whole dBs } } break; case INP: if (spdifIn){ // If in spdif input mode, switch to I2S/DSD input mode writeSabreReg(0x11,0x04); // Reg 17: Auto spdif detection OFF writeSabreReg(0x08,0x68); // Reg 8: Enable I2S/DSD input writeSabreReg(0x19,0x02); // Reg 25: Use best DPLL settings dpllBW=0; // Set variable "dpllBW" to 0 (0-7, 0 meaning best) setAndPrintDPLL(dpllBW%8); // Print dpll value lcd.setCursor(4,0); lcd.print("i2sDSD"); spdifIn=false; // Now in I2S/DSD mode } else { // Not in spdif mode, switch to spdif mode writeSabreReg(0x08,0xE8); // Reg 8: Enable SPDIF input writeSabreReg(0x11,0x0C); // Reg 17: auto spdif detection ON writeSabreReg(0x19,0x00); // Reg 25: Allow all DPLL settings writeSabreReg(0x0B,0x85); // Reg 11: Set for Lowest DPLL bandwidth dpllBW=1; // Set variable "dpllBW" to 1 (0-7, 1 meaning lowest) setAndPrintDPLL(dpllBW%8); // Print dpll value lcd.setCursor(4,0); lcd.print("SPDIF "); spdifIn=true; // Now in SPDIF mode } selectMillis=millis(); // Update being-in-select-mode timer break; case FIL: // Toggle between Sharp and Slow filter if (sharp){ // If currently in sharp, toggle to slow roll off writeSabreReg(0x0E, 0xF8); // See register definition lcd.setCursor(4,1); lcd.print("SLW"); sharp=false; // Now in slow roll off filter } else { // Toggle to sharp roll off filter writeSabreReg(0x0E, 0xF9); // See register definition lcd.setCursor(4,1); lcd.print("SHR"); sharp=true; // Now in sharp roll off filter } selectMillis=millis(); // Update being-in-select-mode timer break; case JIT: if (jitter){ // If jitter reduction is on, turn off writeSabreReg(0x0A, 0xFA); // Turn off Jitter reduction lcd.setCursor(4,2); lcd.print("OFF"); jitter=false; // Now jitter reduction is off } else { writeSabreReg(0x0A, 0xFE); // If jitter reduction is off, turn on lcd.setCursor(4,2); lcd.print("ON "); jitter=true; // Now jitter reduction is on } selectMillis=millis(); // Update being-in-select-mode timer break; case DPL: if (digitalRead(4) == digitalRead(2)) { // CCW dpllBW--; // select lower bandwidth setAndPrintDPLL(dpllBW%8); } else { dpllBW++; // select higher bandwidth setAndPrintDPLL(dpllBW%8); } selectMillis=millis(); // Update being-in-select-mode timer break; } // End of switch rotating=false; // Reset the flag } // End of while(rotating) // When the being-in-select mode timer expires, we revert to volume/display mode if(selectMode&&millis()-selectMillis > INTERVAL_SELECT*1000){ selectMode=false; printSelectBar(A); lcd.setCursor(1,0); lcd.print("SR"); // Restore Sample Rate label select=VOL; // Back to volume mode } } // End of loop()
Advertisement
Pingback: Updated Code for Buffalo II DAC « H I F I D U I N O
Thank you again for sharing your project. I’m trying to control two different BuffaloII DAC with one Arduino, and I need to know the second address that is taken from the Buffalo when shorting the address pins on it’s pcb. I made a search, but can’t find any info. Standard address is 0×48, which is the second ?
The device address of Sabre DAC Datasheet specifies the address as 0×90 and 0x92which are 8-bit value. (This data straight out of the datasheet)
The wire library in Arduino uses 7-bit device addresses and the 8th R/W bit is added automatically depending on whether you use the write call [beginTransmission()] or the read call [requestFrom()]. Therefore, you will use the 7 most significant bits of the 8-bit address.
Thus:
0×90: 10010000 (we eliminate the rightmost bit and get…)
0×48: 01001000 (Address for I2C in Arduino)
0×92: 10010010 (we eliminate the rightmost bit and get…)
0×49: 01001001 (Address for I2C in Arduino)
(I took this from the comment, but noticed I had the binary values wrong, so they are corrected here)
Thank you very much.
I’m now able to control the two Buffolo at the same time.
I’ll try to make some change to the firmware in order to have a main volume (like in this moment) and also a separate control for each channel to have a kind of level control between them (useful to balance each single speaker)
Hi Michele,
are you using a dual mono Buffalo dac? i use one and would be glad if you could share your work?
Branko