Code for Buffalo II DAC vB05   1 comment

/***************************************************************************************************
 * HIFIDUINO v. B05
 * 
 * November 25, 2010
 *
 * 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. 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.
 ***************************************************************************************************/

// 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)

#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 1  // FIR filter selection
#define JIT 2  // Jitter on-off
#define BRI 4  // LCD brightness adjustment
#define DPL 3  // 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=DEFAULTATTNU; // 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 brightness=0;   // To record LCD brightness level
byte select;         // To record current select option

boolean sharp;       // FIR filter mode
boolean jitter;      // Jitter reduction
boolean selectMode;
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 pusshing 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 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.
 */

// 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);
  // Calculating the sample rate for SPDIF
  DPLLNum*=400;
  DPLLNum/=21475;
  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. Once can choose any character as the bar
*/
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:
    writeSabreReg(0x0B, 0x81); // Mode control 2 register
    lcd.setCursor(4,3);
    lcd.print(" 0");
    break;
  case 1:
    writeSabreReg(0x0B, 0x85);
    lcd.setCursor(4,3);
    lcd.print("<L");
    break;
  case 2:
    writeSabreReg(0x0B, 0x89);
    lcd.setCursor(4,3);
    lcd.print(" L");
    break;
  case 3:
    writeSabreReg(0x0B, 0x8D);
    lcd.setCursor(4,3);
    lcd.print("LM");
    break;
  case 4:
    writeSabreReg(0x0B, 0x91);
    lcd.setCursor(4,3);
    lcd.print(" M");
    break;
  case 5:
    writeSabreReg(0x0B, 0x95);
    lcd.setCursor(4,3);
    lcd.print("MH");
    break;
  case 6:
    writeSabreReg(0x0B, 0x99);
    lcd.setCursor(4,3);
    lcd.print(" H");
    break;
  case 7:
    writeSabreReg(0x0B, 0x9D);
    lcd.setCursor(4,3);
    lcd.print(">H");
    break;
  }
}

/*
Custome 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 fucntion 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 sinle 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 putlses,
 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 (returs 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);
  }
}

/************************ 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 for Encoder pin for volume up
  digitalWrite(VOLUPPIN, HIGH);   // If H/W debouncing is implemented, set to LOW

  pinMode(VOLDOWNPIN, INPUT);     // Button for 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 for encoder switch 
  digitalWrite(SELECTPIN, HIGH);  // Enable pull-up resistor 

  pinMode(6, OUTPUT);             // Controls the brightness of LCD

  // 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.B043");

  delay(1000);

  // Assign and set default values for DAC
  dpllBW=1;     // This is the default value of dpll bandwidth (1=lowest)
  sharp=true;   // This is the default value of roll-off filter (sharp)
  jitter=true;  // This is the default value for jitter reduction (ON)

  writeSabreReg(25,0);  // Allow all DPLL settings

  setSabreVolume(DEFAULTATTNU);  // Set volume at startup otherwise full volume

  // 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("SHRP");
  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);

}  // 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
    // Update the sample rate display
    lcd.setCursor(4,0);
    lcd.print(" ");
    lcd.setCursor(4,0);
    lcd.print(sampleRate(), DEC);

    status=readStatus();  // Read status register
    lcd.setCursor(9,1);
    if(status&B00001000) lcd.print("DSD");
    else lcd.print("PCM");

    lcd.setCursor(8,3);
    if(status&B00000001) lcd.print("LOCK");
    else lcd.print("NLCK");

    lcd.setCursor(11,0);
    if(pulse++%2)lcd.write(0xDE);  // Print a "pulse"
    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 for "repeat" and 255 designated as an error code. The current
   Apple remote does not generate these codes.
   */
   
  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 brightness selection and value
    lcd.print("BR: ");   // Pring brightness label
    lcd.setCursor(4,0);
    lcd.print(brightness, DEC);
    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 BRI:
      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 BRI:
      lcd.setCursor(4,0);
      lcd.print(" ");
      lcd.setCursor(4,0);
      if (digitalRead(4) == digitalRead(2)) {  // CCW, decrease brightness
        brightness-=5;
        analogWrite(BRIPIN,brightness);
        lcd.print(brightness, DEC);
      }
      else {                                   // CW, increase brightness
        brightness+=5;
        analogWrite(BRIPIN,brightness);
        lcd.print(brightness, DEC);
      }
      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(14, 6);  // Register value for slow is 6
        lcd.setCursor(4,1);
        lcd.print("SLOW");
        sharp=false;           // Now in slow roll off filter
      }
      else {                   // Toggle to sharp roll off filter
        writeSabreReg(14, 7);  // register value for sharp roll off is 7
        lcd.setCursor(4,1);
        lcd.print("SHRP");
        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, 0xCA);    // Turn off Jitter reduction
        lcd.setCursor(4,2);
        lcd.print("OFF");
        jitter=false;                 // Now jitter reduction is off
      }
      else {
        writeSabreReg(0x0A, 0xCE);    // 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 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()
Advertisements

Posted November 26, 2010 by BlgGear in Code for Buffalo DAC

One response to “Code for Buffalo II DAC vB05

Subscribe to comments with RSS.

  1. Pingback: Remote Control for Buffalo II « H I F I D U I N O

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: