Code for Buffalo II DAC v B02   1 comment

/*
HIFIDUINO v. B02
 October 15, 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
 
 Although this is based on a confidential data sheet for which I've
 signed an NDA, I've also received permission to publish this code.
 
 Change log:
 v. B01 10/11/10: Volume control, LCD, Rotary Encoder
 v. B02 10/15/10: Added reading of sample rate
 */

/* 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.
 
 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
 */

// LIBRARIES USED IN THIS CODE

// include the  LCD library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);

// include the library code for I2C:
#include <Wire.h>

// #DEFINES

// Volume for Sabre32 is 0 to -127 db in .5 db steps

#define DEFAULTVOL 0x64 //-50 dB this is 50x2=100 or 0x64
#define MINVOL 0xC6    //-99dB this is 99X2=198 or 0xC6
#define MAXVOL 0x00     //-0 dB
#define DIMVOL 0x3C    //-60dB The volume level when dimming the volume

#define VOLUPPIN 4      // Button to increase  volume or RotEnc A terminal
#define VOLDOWNPIN 2    // Button to decrease volume or RotEnc B terminal

#define INTERVAL 2000   // Time interval in msec for doing something like reading the sample rate

// VARIABLE DECLARATIOM

byte regVal; // Variable to pass register value
byte regAddr; // Variable to pass register value
byte currVol=DEFAULTVOL; // Varialble to hold the current volume value
unsigned long previousMillis = 0; // Stores last recorded time

/*
ROTARY ENCODER
 
 The rotary encoder is connected to pins 2 (A) and pin 4 (B)
 and GND. It does not matter which terminal you connect to which pin.
 The third terminal is connected to GND. At each cycle, the rotary
 encoder would pull the pins LOW and we detect that transition and
 compare it with the level of the other pin
 
 For this code to work, the rotary encoder is "debounced" by installing capacitors
 between the signal pins an GND in order to minimize the noise generated by the
 mechanical switches of the rotary encoder. If you are using an optical rotary
 encoder or a high quality encoder, you might not need the capacitors, but the model
 I am using is a low cost ~ $1 rotary encoder
 */

// Interrupt service routine for rotary encoder. Determines direction.

volatile byte volUp=0;  // flags for the interrupt routine
volatile byte volDown=0;

void rotEncoder()
{
  if (digitalRead(2) == digitalRead(4))
  {
    volUp = 1;  //if on interrupt the encoder channels are the same, direction is clockwise
  }
  else
  {
    volDown = 1;  //if they are not the same, direction is ccw
  }
}

/*
Reading the Sample Rate in the Buffalo II DAC
 The sample rate can be calculated by reading the registers of the 32 bit DPLL value. For SPDIF
 this value is divided by 2^32/Crystal-Frequency. In Buffalo II, the Crystal frequency is
 80,000,000 Hz. To read the DPLL value we need to read 4 bytes because it is a 32-bit (4 bytes) number
 
 In Arduino (and other small microprocessors) it is NOT advisible to do floating point math because
 "it is very slow"; therefor we will use interger math.
 
 In order to increase the accuracy of the integer calculation, I did some evaluation of the DPLL
 register values for sample rates ranging from 44.1K to 192K. I noticed that I could multiply the value
 of the DPLL number by up to 400X without overflowing the 32-bits.
 
 The value of 2^32/80000000 is 53.687091 (which is a floating point number). If we use the integer part 
 (53 or 54) we will have an error in the calculation. For example, if we divide the DPLL number for a 44.1K
 sample rate signal by 53, the resultant values would be 44.677K and 43.849K if divided by 54. 
 If we divide by the actual number 53.687091, then we obtain 44.105K (The 4 Hz deviation from ideal is within
 the SPDIF specification and crystal tolerances of the source signal). Clearly the integer math is not very
 accurate.
 
 However, since we have 32 bit number to work with, we can multiply the DPLL number by 400 and then device
 by 400X53.687091=21475. If we do this, we obtain 44.105K which is within rounding off value of the exact
 result (the one using floating math)
 */

// 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 from address specified with Wire.send()
  while(!Wire.available()) {
  } // Wait for the data on the wire -if needed...
  return Wire.receive();
}
unsigned long sampleRate()
{
  DPLLNum=0;
  // Reading the 4 registers of DPLL 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=DPLLNum*400;
  DPLLNum=DPLLNum/21475;
  return DPLLNum;
}

/*
Buffalo volume setting routine
 
 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, 0.x90 becomes 0x48 as follows
 0x90: 0101000 (we eliminate the rightmost bit)
 0x48: 0010100
 */

void setSabreVolume(byte regVal)
{
  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x00); // Specifying the vol register of  DAC 1
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x01); // Specifying the vol register of  DAC 1
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x02); // Specifying the vol register of  DAC 2
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x03); // Specifying the vol register of  DAC 3
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x04); // Specifying the vol register of  DAC 4
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x05); // Specifying the vol register of  DAC 5
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x06); // Specifying the vol register of  DAC 6
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();

  Wire.beginTransmission(0x48); //hard coded the Sabre/Buffalo device  address
  Wire.send(0x07); // Specifying the vol register of  DAC 7
  Wire.send(regVal); // Writing volume value into register
  Wire.endTransmission();
}

/*
A little print routine...
 hardcoded to print on line 3 (4th line) of LCD
 Takes in the volume value in whole dB increment
 This means dividing the volumen register value by 2 first
 */

void printVol(byte regVal)
{
  if (regVal==9) // transition between two digit and one digit display
  {
    lcd.setCursor(12,3);
    lcd.print("0 ");  // Add a leading zero
  }
  if (regVal<10) // two digit to one digit transistion, right justification
  {
    lcd.setCursor(13,3);
    lcd.print(regVal, DEC);
  }
  else
  {
    lcd.setCursor(12,3);
    lcd.print(regVal, DEC);
  }
}

void setup() {
  // set up the LCD's number of columns and rows: 
  lcd.begin(20, 4);

  // Join the I2C bus as a master
  Wire.begin();

  // Attach Interrupts
  attachInterrupt(0, rotEncoder, CHANGE);  // ISR for rotary encoder

  // Set up the pin modes
  pinMode(VOLUPPIN, INPUT);       // Button or Encoder pin for volume up
  digitalWrite(VOLUPPIN, HIGH);   // Enable pull-up resistor

  pinMode(VOLDOWNPIN, INPUT);     // Button or Encoder pin for volume down
  digitalWrite(VOLDOWNPIN, HIGH); // Enable pull-down resistor    

  // Print the welcome message to the LCD.
  // And volume indicator

  lcd.setCursor(0,1);
  lcd.print("BUFFALO CONTROL v0.2");
  lcd.setCursor(0,3);
  lcd.print("   Volume: -   dB");

  // Set the default volume at power up

  setSabreVolume(DEFAULTVOL);
  printVol(DEFAULTVOL/2);

}

void loop() {

  // Print the sample rate (once every "INTERVAL" time)
  if(millis() - previousMillis > INTERVAL)
  { 
    previousMillis = millis(); // Saving last time we display sample rate
    lcd.setCursor(0,2);
    lcd.print("Sample Rate: ");
    lcd.print(sampleRate(), DEC);
  }

  // The following is to adjust the volume down (larger number)

  while(volUp==1)  // While there is CW motion in the rotary encoder
  {
    volUp=0;  // Reset the flag
    if (currVol// Check if already at min numerical Volume
    {
      currVol=currVol+2;         // Increase 1 dB
      setSabreVolume(currVol);   // Write value into registers
      printVol(currVol/2);
    }
  }

  // The following is to adjust the volume up (smaller numbers)

  while(volDown==1)  // While there is CCW motion in rotary encoder
  {
    volDown=0;  // clear the flag
    if (currVol>MAXVOL)   // Check if already at max Volume
    {
      currVol=currVol-2;         // Increase 1 dB
      setSabreVolume(currVol);   // Write value into registers
      printVol(currVol/2);
    }
  }
}
Advertisement

Posted October 15, 2010 by BlogGeanDo in Code for Buffalo DAC

One response to Code for Buffalo II DAC v B02

Subscribe to comments with RSS.

  1. Pingback: Reading Buffalo II SPDIF Sample Rate « 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 )

Connecting to %s

Follow

Get every new post delivered to your Inbox.