• Disclaimer: This Vendor's Forum is a paid-for commercial area. Unlike the rest of diyAudio, the Vendor has complete control of what may or may not be posted in this forum. If you wish to discuss technical matters outside the bounds of what is permitted by the Vendor, please use the non-commercial areas of diyAudio to do so.

C++ Easy and Scalable Software for BIII38Pro

Hi guys,

I have been looking for good, easy and scalable software for BIIIDAC, but couldn't find anything useful. I know, there are some guys writing the software, but it is mostly C or Microcontroller based and very difficult to follow or change.

I decided to write easy to follow software, based on C++ classes, which is as close to TPA Firmware as possible.
I use Atom + PlatformIO and Arduino DUE for this.
For remote control i use Apple Remote and the Display is 4,3" 480x272 TFT LCD mit Touchscreen SSD1963.

The software is based on TPA firmware, it behaves the same way: power up, reset, communication, configurateion, etc... is all based on TPA firmware.
The software gives you at this stage basic interaction, like volume change and input change, the rest of the configuration is read from the switches on the BIII board.
This software is easily scalable, you can replace any of the classes with your own or modify existing.
I have added some "interfaces" for editing, like eeprom or touch (didnt test the touch, but its easy to figure out how it works and correct it).
I also prepared the settings page for the tft screen so one can add it later on.


Main function is literaly 20 lines of code, it just takes the action from interface and apply to the DAC class.


As I said, the heart of the DAC Class is TPA Software.
The configuration file is the copy of TPA, ES9028_38.h file.
DAC class constructor is build of TPA functions, slightly modified for Arduino needs ( i use Wire.h for I2C communication ), but the funtions:
powerDACup();
initializeDAC();
configureDAC();
are practically the same you get with TPA firmware, those work the same way and are called the way TPA is doing it.


Simplified soft looks like this:
//--------------------------------------
setup:

dac = new DAC();
remoteInterface = new RemoteInterface();
touchInterface = new TouchInterface();
tftGraphics = new TFTGraphics();

//---------------------
loop:

action = remoteInterface->getAction();
if( action == NONE )
action = touchInterface->getAction( MAIN_MENU );


if( action == NONE ){
dac->readSwitchStates();
}


switch ( action ){
case NONE:
break;
case CHANNEL_LEFT:
tftGraphics->printChannel( dac->decreaseInput() );
break;
case CHANNEL_RIGHT:
tftGraphics->printChannel( dac->increaseInput() );
break;
case VOLUME_UP:
tftGraphics->printVolume( dac->increaseVolume() );
break;
case VOLUME_DOWN:
tftGraphics->printVolume( dac->decreaseVolume() );
break;
case ENTER:
tftGraphics->printVolume( dac->muteVolume() );
break;
default:
break;
}
action = NONE;

//-------------------------


All you need is Atom + Platform IO + ( Arduino Due, remote, TFT) and the repository:

GitHub - poninskit/TPA_Buffalo_IIIsePro38_Software: c++ Microcontroller Software for Buffalo III 9038 Pro DAC

happy coding.

here is how it looks like:

yqQ9CrrVu7Xb7bB5EOSaX3syui8JQDgekV3C0vzfhpB50Ts14nL_5qqSfa-5uBWXlxchDBVvNY-aBYIbZ6O6lKmd6GLhyO1bUiFTyI-CyNzCQqWMccbE78AaMSAHlFCVcXOZAhWz75iA8g5X4Tl9LAtLf9jCmD6hPGd-98JHRdehFDZ5FyZh9K2jfPah9j6rrLExJRpOeDui1UMz3TY0I8bA0g59LuWsxXn7q7cAlYgWq_DIGzMCyv-Tqk4flPTaKnZ7s3RJqr1pCxML1qLrwyUZytKhPQ1rLyCAvFdCTZbNycD5YGwoCdmdYC2bF6H5PR77D_VUYl7jPc6jSZi0ttjQw0R-uAya5-b4iVKukzJKcWtG1Ly-9ngr_ZgmWWIrJ5PP2Gkw3jmedrOMgNwKqv6Zlq2W0UYHB-l1f4zJxKuDkK-tbKI1LgIyzXgSz7rkJcU8yem0Ec71n-JgOiZqQlBXTUEu2D7_FNt2laGy7HwkhlcbpVbKEWgJAGNqMuYJj3rVx7z-XxdhN7SBMLYtai6PGzJuGrkBntoAK1fJ_QQUazjbF2Wa7BIDOuuB8MOBzTiJ-9Vpe5y6lpMZL6wHQ_4oYjdJ947LiGNV0ldSkKygpIn0qbqEqH8fi2DF_328lwrtgcogi0T42Y5MLhH_qCyEZfeUpB8=w629-h472-no


KxyP3k0N8YXF9lDxv3I8Y8xbGcpE-hKz_hzo5Fuop9frTlU8HqZufYSJRjNKnJ_NVJOTWDRR2Ud0grGy-eBTYfE6GvskEuF1P1rYPcF9Y_oOpVI0iFFgKwU9_kYLGFAGb3moU6uzfHhZyUJ0sskghIQE0Vsyt7ne0BBfPdMJRmGhjTZRbuknWelInA6cveBholIb4g-oS6bQEeNqNZLTmZiSbqrVHnPLz0NFq_SgEqqmu-BdKPUYsbdVC_aA0ar75EmqzIJHWZuH3iMbCvavcHHir4TY3hc4MEy1U-4RAtl6_7EZ6a4LmeQujjDBKWYPBisAcLC27RbanLlIzlm6MznOPlLpPqLTiGWQJGQcu_EBAa0M4PcUuzaRFR8btEbRGBGggmm2wqWqCkPUmq9dMn0F65r4OdPvuAugCqA5Uo7elQszUTmA3IoCCCN_amrB3UqbTR0J81R_-EHnJjBboMEudz_VXUJSrCpPfAbquzwSjUJz-N96ILliHtP7UIWrX2gfSMAma-YnFJu4QWY3znuDit8rX-fY2E0bH_-yYfgP7vOLJBZFiANAKZEIjGrhJXvlbf5nSdrgIKh1ATj0RpeeGYHTUGyrHGBbhNMhDOwTQLYItyu_R8V6fVrsjT29dAwPAAWlrabRqjpWJQc267pSz2RRQqM=w768-h291-no


zkRqDPEuRi6vSUFmGQ9mrf87AA4T5VbU8Cb9EACm9nOUybnZXs1Re1Izrj9AbYUArMhCHroG-LFfvDBh6mulLn1PlGjPxeMP5GuyHyDI7EAnqnuUlGMTet6GmbK4ptn7QKWT-xVbcnLstCNOC2dYd8ouSijqmZOH1XOF0IVoZqdbsU3niNNCudEN-SLmm58JGlF8tpCGLJpkLAgSBM3GHUvT4JFicfwjm06v_FZ-pIthaZKG_rMpVnJN1VDMdWTYLUS1P2Lhndt3NtlXp2zJEJqS8G4AwZE-yqRWQJCmNeQ8MA2glMnG8oxqGCn3zIiGMhCxbFEcakv4HFRJVAVw6asx__9zJd0VoDb6v4KEnyzNyZGvg1tWau4yjpzAWQWer63mkIzwQ9xOUH7yaCoAfg1jGh9-q9KPTFgHpm0lFhlUREZ1lOrhYkXBo9URFt_iMHNm_opKwlZU9CgbAuyso6VOqJXSqAIgT4F3CfBEFSxYspUgVtbiUeD8_liipG6oTqEEvQ72NkL-AHhjQu4NEyQ1VxzThtQ7XctJpkrnfTLWQC9XB3ptU9g0BmN8KW-KK8b-Sf8Rk2UEguhy3r7Kni3tBfcTkU5OcpoEnaXHNlJUNBqLxosO7k9Rk0h1dF9g8u3rM_PgY6DRlnK7WQ-xS5GBCXZVcNo=w707-h472-no


Mw6W0y_rPa2mg_m24aCDC9DSnqNeL5vcxooC5GJB87a0pS_lg7j2r7SFaywXjTHNqvj2t3zNMyUaj1IJLLp8YpdzHwAgI9D0sfbK3FcTKTf1CD55Hx6Q-44J9ACUwgMSNU3Yxml-iGZ6lhWiDPui2BXpDrkTydcB4lIyyuTjRq3OEGY7TV7d45tGCJDf8FOhYUAnHxfwUjjmIIRBonTiRBegGxjINz3DkmFFC-mbaeqtn4E1fAQjA0dHlIXQzQlucX0c1g0Zmp6ksbrDi7EQCDjPHdgu9P77j22hrDM7CH6c4OBnP3sofzmTegn8GODdxKqkcyIZQBn6TaFk8nEzQjHxbpDO-QUzEvBVxnBZWwMJoif2tJ2S1honc9rGMnOWP9bUV91l_yMYc9DTc_M4Fr5tmnTIY7GjILbBR5BUQGNIHew7HFNyyMNtcWfTQi9ShyrgW1LuAUpHg5KO3filFg3QemZHs4eODkl53l7OMQYqiAkEsjoYlvODz5nOOhWo9p_gQCj1hRvqAkbiQjqDdPlbYtYquBkUpuoYbosfvanQG_dwOv71rrL1sKowmY7dyKCkdFA2JpyuLO4JI7tpmB4XGpUV3NLv9HXg6PMl-AiYMzXV1OdV4d5KVF8Ho-NRIrbFu1OTNtYVAsbSG2HPmKtrA-Z7u4I=w708-h943-no


Optional DAC settings page could be written like this:

3VV3ScWokz9P-rWTDtX6Il9m_i-qgAycaq2Wv4CugTL94bN_KFR89jJ3V3DgusqjRMNNy7tUkGygcbna4ERSwbhnwU7PCOzGwcD1EYfNeRIyWEht_WI3bEEHhASw5xXXo6Djl6JlBCV2dMLS5k9bEaSYQOH4TyqPkNjTEFkA7ioWdTX4DsyQGzoiWeXGFt0RiLMBZEUqvMdNdhp1QHrZyIA5gktWkIrx2eyS4_67cuKAsNLM2heh5xMegR4wE1wk_Xa7O2I0z7sMkQdl9Mt5Y2EMDwyLuITmQNQjdwnsm7HOuWItTOVh-rX_jH2iWQGCc_p0lkncbYBuezyjjnX0jaiIKFg8efULXlfNeLQe_DSq-lFBeuszi0-u7naRyvKzF4Nk0UwvCbYXN0gcKvKlYsn6IfVGN_GUeR2BFnTIpb1ClbsiasApL7veCMTPTTJc2kbxnYKca6DAJzYVgudCZm6sy-M0wtLzxH570LYGKWFNMZM-J9HisNJDPyiBdyP2V5JnWR7mNDOY6NEI1uGSgKvVEZTW6xXZc_U8kORNu8rMysxa1murM3uHzDY2rWTZH_RwXs4VG_y46EEzCOrnetNqLdm1xEkaZx-OeATPh2dFCU6JTCspw0KKsxiiiq1Q6OPM00QGryoLQVjwuo3-MqmGFY27pyk=w405-h276-no





and this is how it works:

VID_20190507_095456.mp4 - Google Drive
 
Last edited:
You welcome, lets see if people like it, maybe it will grow.

I dont have much time to write it till the end, but the idea is to public the core, after that anyone could contribute by writing new classes ( new interfaces, display classes and so on ).
Then just publish new class: library + *.cpp + *.h, so anyone can use it.
...maybe it will work.


I havent work with Beaglebone, but its C++/C, so the core should work everywhere...
 
Last edited:
Hello Poni,

thank you for this project.

I have a lot of questions and hope it is not to much :)

First: I tried to figure out (by reading your code) how you did connect everything. Arduino Due , Eprom, Relais, TFT-Display etc.

Second I would like to connect/use a Led-Matrix for display to show only A: source and B: sampling rate. For the Display I have (Beam - beautiful 120 LED matrix — Hover Labs) there is a Arduino Library and it uses only few commands to display text (display() for static text and play() for scrolling text). So I would have to figure out how to read Sampling rate and convert it to small text bits (44.1k; 96k ...; DSD 64, DSD 128 ...). Also I would need advise how to implement a "class" of LED-Matrix to you code.

thx

Branko
 
Hi Drone,

I will try to put some light on how i did it.

There is a main file, where everything happens in loop. The purpose is to keep this file simple.
To go deeper I created classes, which i wrapped on Arduino classes. I suggest to inherit the class and in your inherited class you can make all the configuration and write your specific functions.

So, on the beginning you will find declarations (pointers to the objects that doesnt exists yet, to be specific) of the clsses I use later on:
...
//REMOTE CONTROLL
RemoteInterface* remoteInterface;
//TOUCH INTERFACE
TouchInterface* touchInterface;
//TFT SCREEN
TFTGraphics* tftGraphics;
...

to go deeper i will takte TFTGraphics class, that basically makes all the display stuff. This is the class you would like to replace if you use diffrent LCD.

So in setup I create instance of the class:
tftGraphics = new TFTGraphics();

that calls the constructor of the class, which is inheritance of UTFT class.
TFTGraphics:UTFT

My constructor looks like this (header):
TFTGraphics(byte model = SSD1963_480, int RS = 38, int WR=39, int CS = 40, int RST=41, int SER = 0);

if you have a look on UTFT constructor it take exact same parameters as my TFTGraphics class:
UTFT::UTFT(byte model, int RS, int WR, int CS, int RST, int SER)

With the difference, that I put default values in my class (header file), so by calling my constructor i also call constructor of UTFT with my specific values:

(header)
TFTGraphics(byte model = SSD1963_480, int RS = 38, int WR=39, int CS = 40, int RST=41, int SER = 0);
(cpp)
TFTGraphics::TFTGraphics(byte model, int RS, int WR, int CS, int RST, int SER)
:UTFT(model, RS, WR, CS, RST, SER)

So, now i have TFTGraphics class, that is specific case of UTFT class.
You can use any class you want... if you have LED-Matrix class, just inherit the class and put your values in constructor.
In inheritance of your "Led-Matrix" class you want to put all the methods you will possibly need. You can use all the functions of inherited class, and probably want to write methods like "printVolume", "printChannel", "printSampleRate"...

Now when you have your class MyLedMatrix:LedMatrix, which inherits the LedMatrix.
You just use your methods in main.cpp file.

for example, in case when user increses volume:

case VOLUME_UP:

//increase the volume of DAC
int volume = dac->increaseVolume();

//print the volume of the DAC in your specific class
MyLedMatrix->printVolume( volume );

break;

... and so you move on.... take the class that matches your hardware, inherit in your class...write mehods you need.... connect... next class...


So in your specific case, the BEAM class have two constructors....you normally take one and inherit it in your class ...


BEAM:
/*
This constructor used when multiple Beams behave like one long Beam
*/
Beam::Beam ( int rstpin, int irqpin, int numberOfBeams){
_rst = rstpin;
_irq = irqpin;
_beamCount = numberOfBeams;
activeBeams = numberOfBeams;
_gblMode = 1;
}

/*
This constructor used when multiple Beams behave like single Beam units
*/
Beam::Beam ( int rstpin, int irqpin, uint8_t syncMode, uint8_t beamAddress){
_rst = rstpin;
_irq = irqpin;
_syncMode = 0;
activeBeams = 1;
_currBeam = beamAddress;
_gblMode = 0;
}


Just as example I will take the first constructor... it would look something like this:

header:
MyBeam ( int rstpin = 1, int irqpin = 2, int numberOfBeams = 3) // here you put your values...i just put fake values 1,2,3...

.cpp:
MyBeam::MyBeam(int rstpin, int irqpin, int numberOfBeams)
:Beam( rstpin, irqpin, numberOfBeams)
{
//here you call the initial methods of class Beam, like
begin();
initBeam();
print("willkommen");
}


...then you can proceed with your methods:
MyBeam:: printVolume(int volume){

//here you call Beam mehod print like so:

print (volume);
or
play();
or
printFrame(...);

}
...



... after that you just put MyBeam class in main file....

MyBeam *myBeam;


in Setup
myBeam = new MyBeam(); // that calls default parameters you put in constructor of the class MyBeam (optinally you could enter parameters here in setup as well)


then you move to loop() and use your methods in given case... like VOLUME_UP, or CHANNEL_LEFT

case VOLUME_UP:

//increase the volume of DAC
int volume = dac->increaseVolume();

//print the volume of the DAC in your specific class
MyBeam->printVolume( volume );

break;


hope that helps... have a fun
 
Last edited:
Hi Drone,

I will try to put some light on how i did it.

There is a main file, where everything happens in loop. The purpose is to keep this file simple.
To go deeper I created classes, which i wrapped on Arduino classes. I suggest to inherit the class and in your inherited class you can make all the configuration and write your specific functions.

So, on the beginning you will find declarations (pointers to the objects that doesnt exists yet, to be specific) of the clsses I use later on:
...
//REMOTE CONTROLL
RemoteInterface* remoteInterface;
//TOUCH INTERFACE
TouchInterface* touchInterface;
//TFT SCREEN
TFTGraphics* tftGraphics;
...

to go deeper i will takte TFTGraphics class, that basically makes all the display stuff. This is the class you would like to replace if you use diffrent LCD.

So in setup I create instance of the class:
tftGraphics = new TFTGraphics();

that calls the constructor of the class, which is inheritance of UTFT class.
TFTGraphics:UTFT

My constructor looks like this (header):
TFTGraphics(byte model = SSD1963_480, int RS = 38, int WR=39, int CS = 40, int RST=41, int SER = 0);

if you have a look on UTFT constructor it take exact same parameters as my TFTGraphics class:
UTFT::UTFT(byte model, int RS, int WR, int CS, int RST, int SER)

With the difference, that I put default values in my class (header file), so by calling my constructor i also call constructor of UTFT with my specific values:

(header)
TFTGraphics(byte model = SSD1963_480, int RS = 38, int WR=39, int CS = 40, int RST=41, int SER = 0);
(cpp)
TFTGraphics::TFTGraphics(byte model, int RS, int WR, int CS, int RST, int SER)
:UTFT(model, RS, WR, CS, RST, SER)

So, now i have TFTGraphics class, that is specific case of UTFT class.
You can use any class you want... if you have LED-Matrix class, just inherit the class and put your values in constructor.
In inheritance of your "Led-Matrix" class you want to put all the methods you will possibly need. You can use all the functions of inherited class, and probably want to write methods like "printVolume", "printChannel", "printSampleRate"...

Now when you have your class MyLedMatrix:LedMatrix, which inherits the LedMatrix.
You just use your methods in main.cpp file.

for example, in case when user increses volume:

case VOLUME_UP:

//increase the volume of DAC
int volume = dac->increaseVolume();

//print the volume of the DAC in your specific class
MyLedMatrix->printVolume( volume );

break;

... and so you move on.... take the class that matches your hardware, inherit in your class...write mehods you need.... connect... next class...


So in your specific case, the BEAM class have two constructors....you normally take one and inherit it in your class ...


BEAM:
/*
This constructor used when multiple Beams behave like one long Beam
*/
Beam::Beam ( int rstpin, int irqpin, int numberOfBeams){
_rst = rstpin;
_irq = irqpin;
_beamCount = numberOfBeams;
activeBeams = numberOfBeams;
_gblMode = 1;
}

/*
This constructor used when multiple Beams behave like single Beam units
*/
Beam::Beam ( int rstpin, int irqpin, uint8_t syncMode, uint8_t beamAddress){
_rst = rstpin;
_irq = irqpin;
_syncMode = 0;
activeBeams = 1;
_currBeam = beamAddress;
_gblMode = 0;
}


Just as example I will take the first constructor... it would look something like this:

header:
MyBeam ( int rstpin = 1, int irqpin = 2, int numberOfBeams = 3) // here you put your values...i just put fake values 1,2,3...

.cpp:
MyBeam::MyBeam(int rstpin, int irqpin, int numberOfBeams)
:Beam( rstpin, irqpin, numberOfBeams)
{
//here you call the initial methods of class Beam, like
begin();
initBeam();
print("willkommen");
}


...then you can proceed with your methods:
MyBeam:: printVolume(int volume){

//here you call Beam mehod print like so:

print (volume);
or
play();
or
printFrame(...);

}
...



... after that you just put MyBeam class in main file....

MyBeam *myBeam;


in Setup
myBeam = new MyBeam(); // that calls default parameters you put in constructor of the class MyBeam (optinally you could enter parameters here in setup as well)


then you move to loop() and use your methods in given case... like VOLUME_UP, or CHANNEL_LEFT

case VOLUME_UP:

//increase the volume of DAC
int volume = dac->increaseVolume();

//print the volume of the DAC in your specific class
MyBeam->printVolume( volume );

break;


hope that helps... have a fun


Thank you for taking so much time. Will try to make my way through :)

BR

Branko
 
Hi

I havent done sample rate yet, but "HiFiDuino" and "DimDim" have...

You can download the project here...its huge, but you will find "void getSR()", you could use it as a basis.
TFT HiFiDuino Pro Project | Dimdim's Blog
H i F i D U I N O | Lot of Value, Little Money


I would proceed a little bit diffrent way here.

Anyway, you could use it as guide.... add new function to the DAC class. What I would change on the beginning, is that the function should not print anything.
Just take the input from DAC and return the result... String would be the easiest way to return the result, but the propper way would be to create like enum...with codes, and then define strings that describe the codes. You can print the strings after all as info text.
If that method works and returns proper results, anyone could change the strings and display whatever they want.

have a look on what I have done in getLockStatus... its empty example on how you could do this

LOCK_STATUS DAC::getLockStatus()

... this returns lock status enum

enum LOCK_STATUS
{
Unknown = 0,
Locked,
No_Lock
};

then you can write "translator" to human redable string

// Returns a text description of an ErrorCode
// keep at 28 characters
static char* dacLockString(LOCK_STATUS lock)
{
switch (lock)
{
case Unknown: return (char*)("");
case Locked: return (char*)("Locked");
case No_Lock: return (char*)("Unlocked");
default: return (char*)("");
}
}


at the end you just display string on screen:

tftGraphics->printInfoText( dacLockString( dac->getLockStatus() ) );

alternatively you could write last line like this:

LOCK_STATUS status = dac->getLockStatus();
String status_string = dacLockString( status );
tftGraphics->printInfoText(status_string);



I also think that this function should be divided...dont make it to big... maybe use signal type as parameter or write couple of functions for each signal type and then one funtion that manages it. Just to keep it simple.


have a fun!
 
Last edited:
Hi Branco,

glad you make progress...

I would like to help you with it, but dont have much time. I also noticed this function may not be as straight forward...

Anyway, you could take the function HiFiDuino / DimDim have done.... dont exactly know whoes version it is, but I hope they dont mind i post it here.

Its cut from DimDims ".cpp" file, version 2.13.... they make it this way... You would have to make something similar in DAC class...

And then print it after all...

If you pay attention you probably could even copy-paste the code in DAC Class...it should work with minor changes





/*
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 (original), 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 up to 400 and then divide by 400X53.687091=21475. If we do this,
we obtain 44.105K which is the same as the exact value.

I used a spreadsheet to calculate the multipliers for SPDIF and I2S and for both 80Mhz and 100Mhz

SPDIF 80Mhz: x80, %4295
SPDIF 100Mhz: x20, %859
I2S 80Mhz: x1, %3436
I2S 100Mhz: x4, %10995 (higher multiplier will overflow the 32bit value for 384KHz SR)
x5, %13744 (More accurate but only works up to 192KHz SR)

For I2S input format 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 wheter in spdif or I2S mode.
*/

// Sample rate reading routines

volatile unsigned long DPLLNum; // Variable to hold DPLL value
volatile unsigned long RegVal; // Variable to hold Register values

byte readDPLL(byte regAddr) {
Wire.beginTransmission(0x48); // Hard coded the Sabre/Buffalo device address
Wire.write(regAddr); // Queues the address of the register
Wire.endTransmission(); // Sends the address of the register
Wire.requestFrom(0x48,1); // Hard coded to Buffalo, request one byte from address
if(Wire.available()) // Wire.available indicates if data is available
return Wire.read(); // Wire.read() reads the data on the wire
else
return 0; // In no data in the wire, then return 0 to indicate error
}

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);
// The following calculation supports either 80 MHz or 100MHz oscillator
if (SPDIFValid){
#ifdef USE80MHZ
DPLLNum*=80; // Calculate SR for SPDIF -80MHz part
DPLLNum/=4295; // Calculate SR for SDPIF -80MHz part
#endif
#ifdef USE100MHZ
DPLLNum*=20; // Calculate SR for SPDIF -100MHz part
DPLLNum/=859; // Calculate SR for SDPIF -100MHz part
#endif
}
else { // Different calculation for SPDIF and I2S
#ifdef USE80MHZ
DPLLNum/=3436; // Calculate SR for I2S -80MHz part
#endif
#ifdef USE100MHZ
DPLLNum*=4; // Calculate SR for I2S -100MHz part
DPLLNum/=10995; // Calculate SR for I2S -100MHz part
#endif
}
if (DPLLNum < 90000) // Adjusting because in integer operation, the residual is truncated
DPLLNum+=1;
else
if (DPLLNum < 190000)
DPLLNum+=2;
else
if (DPLLNum < 350000)
DPLLNum+=3;
else
DPLLNum+=4;

if(bypassOSF) // When OSF is bypassed, the magnitude of DPLL is reduced by a factor of 64
DPLLNum*=64;

return DPLLNum;
}






Printing the sample rate itself seem to take lots of coding, but i dont know why it is so. Probably with some tricks there is a way to do this simply and short, but at the end this works...and that is what counts.






sr = sampleRate();
if(Status&B00001000) // Determines if DSD
{
sr*=64; // It is DSD, the sample rate is 64x
if (leandisp==true)
{
if (inputtype!=1)
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(255, 255, 255);
myGLCD.drawBitmap(215, 110, 120, 59, dsd_b);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(0, 0, 0);
myGLCD.drawBitmap(215, 110, 120, 59, dsd);
#endif WHITE

srold = 0;
}
inputtype = 1; // Input type is DSD.
}
else
{
myGLCD.print("DSD ", 220, 215);
inputtype = 1; // Input type is DSD.
srold = 0;
}
if(SRExact==true && (sr>(srold+50)||sr<(srold-50)))
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE

myGLCD.setFont(Ubuntubold);
myGLCD.printNumI(sr, 275-(myGLCD.getStringWidth("0000000")/2), 185); // Print DSD sample rate in exact format
myGLCD.setFont(SRFONT);
}
else // Print DSD sample rate in nominal format
if(sr>(srold+50)||sr<(srold-50))
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE
if(sr>6300000)
myGLCD.print("Inv. DSD", 275-(myGLCD.getStringWidth("Inv. DSD")/2), 185);
else
if(sr>6143000)
myGLCD.print("6.1 MHz", 275-(myGLCD.getStringWidth("6.1 MHz")/2), 185);
else
if(sr>5644000)
myGLCD.print("5.6 MHz", 275-(myGLCD.getStringWidth("5.6 MHz")/2), 185);
else
if(sr>3071000)
myGLCD.print("3.0 MHz", 275-(myGLCD.getStringWidth("3.0 MHz")/2), 185);
else
if(sr>2822000)
myGLCD.print("2.8 MHz", 275-(myGLCD.getStringWidth("2.8 MHz")/2), 185);
else
myGLCD.print("Unknown", 275-(myGLCD.getStringWidth("Unknown")/2), 185);
}
}
else
{ // If not DSD then it is I2S or SPDIF
if(SPDIFValid) // If valid spdif data, then it is spdif
{
if (leandisp==true)
{
if (inputtype!=3)
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(255, 255, 255);
myGLCD.drawBitmap(215, 120, 120, 43, spdif_b);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(0, 0, 0);
myGLCD.drawBitmap(215, 120, 120, 43, spdif);
#endif WHITE

srold = 0;
}
inputtype = 3; // Input type is S/PDIF.
}
else
{
myGLCD.print("S/PDIF ", 220, 215);
inputtype = 3; // Input type is S/PDIF.
srold = 0;
}
if(SRExact==true && (sr>(srold+50)||sr<(srold-50))) // Print PCM sample rate in exact format
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE

myGLCD.setFont(Ubuntubold);
myGLCD.printNumI(sr, 275-(myGLCD.getStringWidth("000000")/2), 185);
myGLCD.setFont(SRFONT);
}
else // Print PCM sample rate in nominal format
if (sr>(srold+50)||sr<(srold-50))
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE
if(sr>192900)
myGLCD.print("Inval. SR", 275-(myGLCD.getStringWidth("Inval. SR")/2), 185);
else
if(sr>191900)
myGLCD.print("192 KHz", 275-(myGLCD.getStringWidth("192 KHz")/2), 185);
else
if(sr>176300)
myGLCD.print("176 KHz", 275-(myGLCD.getStringWidth("176 KHz")/2), 185);
else
if(sr>95900)
myGLCD.print("96 KHz", 275-(myGLCD.getStringWidth("96 KHz")/2), 185);
else
if(sr>88100)
myGLCD.print("88 KHz", 275-(myGLCD.getStringWidth("88 KHz")/2), 185);
else
if(sr>47900)
myGLCD.print("48 KHz", 275-(myGLCD.getStringWidth("48 KHz")/2), 185);
else
myGLCD.print("44.1 KHz", 275-(myGLCD.getStringWidth("44.1 KHz")/2), 185);
}
}

else {
if (leandisp==true)
{
if (inputtype!=2) {
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(255, 255, 255);
myGLCD.drawBitmap(230, 100, 89, 77, pcm_b);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRoundRect(215, 100, 340, 180);
myGLCD.setColor(0, 0, 0);
myGLCD.drawBitmap(230, 100, 89, 77, pcm);
#endif WHITE

srold = 0;
}
inputtype = 2; // Input type is PCM.
}
else
{
myGLCD.print("PCM ", 220, 215); // Otherwise it is PCM
inputtype = 2; // Input type is PCM.
srold = 0;
}
if(SRExact==true && (sr>(srold+50)||sr<(srold-50))) // Print PCM sample rate in exact format
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE

myGLCD.setFont(Ubuntubold);
myGLCD.printNumI(sr, 275-(myGLCD.getStringWidth("000000")/2), 185);
myGLCD.setFont(SRFONT);
}
else // Print PCM sample rate in nominal format
if (sr>(srold+50)||sr<(srold-50))
{
#ifdef WHITE
myGLCD.setColor(0, 0, 0);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(255, 255, 255);
#else
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(190, 181, 399, 239);
myGLCD.setColor(0, 0, 0);
#endif WHITE
if(sr>385000)
myGLCD.print("Inv. PCM", 275-(myGLCD.getStringWidth("Inv. PCM")/2), 185);
else
if(sr>383900)
myGLCD.print("384 KHz", 275-(myGLCD.getStringWidth("384 KHz")/2), 185);
else
if(sr>352700)
myGLCD.print("352 KHz", 275-(myGLCD.getStringWidth("352 KHz")/2), 185);
else
if(sr>191900)
myGLCD.print("192 KHz", 275-(myGLCD.getStringWidth("192 KHz")/2), 185);
else
if(sr>176300)
myGLCD.print("176 KHz", 275-(myGLCD.getStringWidth("176 KHz")/2), 185);
else
if(sr>95900)
myGLCD.print("96 KHz", 275-(myGLCD.getStringWidth("96 KHz")/2), 185);
else
if(sr>88100)
myGLCD.print("88 KHz", 275-(myGLCD.getStringWidth("88 KHz")/2), 185);
else
if(sr>47900)
myGLCD.print("48 KHz", 275-(myGLCD.getStringWidth("48 KHz")/2), 185);
else
if(sr>44000)
myGLCD.print("44.1 KHz", 275-(myGLCD.getStringWidth("44.1 KHz")/2), 185);
else
myGLCD.print("32 KHz", 275-(myGLCD.getStringWidth("32 KHz")/2), 185);
}
}
}
 
Hello,

had some time on weekend and tried to get further:

the code from Dimitris (dimdim) v. 2.13 is for ES9018. The version for ES9038pro is v. 1.09 pro. this use different registers for reading the dpll values.

Also I would need how to tell what signal type is playing (DOD; DSD; PCM and Spdif or I2S) for the calculations. How do I get this info with your code?

Thank you

Branko

Here is the Sampling Rate part of dimdim ES9038 code (hope this is OK?):

// ----------------------------- Function to determine Sampling Rate
/* sig_type:
* 0 = DOP
* 1 = SPDIF
* 2 = I2S
* 3 = DSD
*/
void getSR()
{
volatile unsigned long DPLLNum; // Variable to hold DPLL value
volatile unsigned long RegVal; // Variable to hold Register values

DPLLNum=0;
// Reading the 4 registers of DPLL one byte at a time and stuffing into a single 32-bit number
DPLLNum|=ReadRegister(dac, 0x45);
DPLLNum<<=8;
DPLLNum|=ReadRegister(dac, 0x44);
DPLLNum<<=8;
DPLLNum|=ReadRegister(dac, 0x43);
DPLLNum<<=8;
DPLLNum|=ReadRegister(dac, 0x42);

SerialUSB.print(F("Raw DPLL number: ")); SerialUSB.println(DPLLNum);

/*
if (sig_type == 1)
{
DPLLNum*=20; // Calculate SR for SPDIF
DPLLNum/=859; // Calculate SR for SDPIF
}
else
{ // Different calculation for SPDIF and I2S/DSD
DPLLNum*=4; // Calculate SR for I2S/DSD
DPLLNum/=10995; // Calculate SR for I2S/DSD
}
*/

long sampleRate = 0;
sampleRate=(DPLLNum*MCLK)/42940;

SerialUSB.print(F("Sample Rate: ")); SerialUSB.println(sampleRate);

if (DPLLNum < 90000) // Adjusting because in integer operation, the residual is truncated
DPLLNum+=1;
else
if (DPLLNum < 190000)
DPLLNum+=2;
else
if (DPLLNum < 350000)
DPLLNum+=3;
else
DPLLNum+=4;


if ((sig_type==0) || (sig_type==3)) // SR calculation for DSD signals (either native or DoP)
{
if(sampleRate>461520)
{
SR = 0;
if (input == 0)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = 15;
setDPLLDSD();
}
SerialUSB.println(F("Invalid DSD, DPLL set to 15"));
}
else
if(sampleRate>451000)
{
SR = 1;
if (input == 0)
{
DPLL_DSD = Input[0].DPLLDSD1;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = Input[1].DPLLDSD1;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = Input[2].DPLLDSD1;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = Input[3].DPLLDSD1;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = Input[4].DPLLDSD1;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = Input[5].DPLLDSD1;
setDPLLDSD();
}
SerialUSB.println(F("44.8 MHz DSD"));
}
else
if(sampleRate>225700)
{
SR = 2;
if (input == 0)
{
DPLL_DSD = Input[0].DPLLDSD2;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = Input[1].DPLLDSD2;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = Input[2].DPLLDSD2;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = Input[3].DPLLDSD2;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = Input[4].DPLLDSD2;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = Input[5].DPLLDSD2;
setDPLLDSD();
}
SerialUSB.println(F("22.4 MHz DSD"));
}
else
if(sampleRate>112000)
{
SR = 3;
if (input == 0)
{
DPLL_DSD = Input[0].DPLLDSD3;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = Input[1].DPLLDSD3;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = Input[2].DPLLDSD3;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = Input[3].DPLLDSD3;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = Input[4].DPLLDSD3;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = Input[5].DPLLDSD3;
setDPLLDSD();
}
SerialUSB.println(F("11.2 MHz DSD"));
}
else
if(sampleRate>56000)
{
SR = 4;
if (input == 0)
{
DPLL_DSD = Input[0].DPLLDSD4;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = Input[1].DPLLDSD4;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = Input[2].DPLLDSD4;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = Input[3].DPLLDSD4;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = Input[4].DPLLDSD4;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = Input[5].DPLLDSD4;
setDPLLDSD();
}
SerialUSB.println(F("5.6 MHz DSD"));
}
else
if ((sampleRate>28000) || (sampleRate>1700))
{
SR = 5;
if (input == 0)
{
DPLL_DSD = Input[0].DPLLDSD5;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = Input[1].DPLLDSD5;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = Input[2].DPLLDSD5;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = Input[3].DPLLDSD5;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = Input[4].DPLLDSD5;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = Input[5].DPLLDSD5;
setDPLLDSD();
}
SerialUSB.println(F("2.8 MHz DSD"));
}
else
{
SR = 0;
if (input == 0)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 1)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 2)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 3)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 4)
{
DPLL_DSD = 15;
setDPLLDSD();
}
else if (input == 5)
{
DPLL_DSD = 15;
setDPLLDSD();
}
SerialUSB.println(F("Invalid DSD, DPLL set to 15."));
}
}

if (sig_type==1 || sig_type==2) // If signal is PCM (either SPDIF or I2S)
{
if(sampleRate>7690)
{
SR = 0;
if (input == 0)
{
DPLL_PCM = 15;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = 15;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = 15;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = 15;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = 15;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = 15;
setDPLLPCM();
}
SerialUSB.println(F("Invalid SR, DPLL set to 15."));
}
else
if(sampleRate>7675)
{
SR = 6;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM1;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM1;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM1;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM1;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM1;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM1;
setDPLLPCM();
}
SerialUSB.println(F("768K PCM"));
}
else
if(sampleRate>7050)
{
SR = 7;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM2;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM2;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM2;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM2;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM2;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM2;
setDPLLPCM();
}
SerialUSB.println(F("705.6K PCM"));
}
else
if(sampleRate>3835)
{
SR = 8;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM3;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM3;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM3;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM3;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM3;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM3;
setDPLLPCM();
}
SerialUSB.println(F("384K PCM"));
}
else
if(sampleRate>3510)
{
SR = 9;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM4;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM4;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM4;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM4;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM4;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM4;
setDPLLPCM();
}
SerialUSB.println(F("352.8K PCM"));
}
else
if(sampleRate>1910)
{
SR = 10;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM5;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM5;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM5;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM5;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM5;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM5;
setDPLLPCM();
}
SerialUSB.println(F("192K PCM"));
}
else
if(sampleRate>1756)
{
SR = 11;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM6;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM6;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM6;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM6;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM6;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM6;
setDPLLPCM();
}
SerialUSB.println(F("176.4K PCM"));
}
else
if(sampleRate>954)
{
SR = 12;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM7;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM7;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM7;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM7;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM7;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM7;
setDPLLPCM();
}
SerialUSB.println(F("96K PCM"));
}
else
if(sampleRate>878)
{
SR = 13;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM8;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM8;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM8;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM8;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM8;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM8;
setDPLLPCM();
}
SerialUSB.println(F("88.2K PCM"));
}
else
if(sampleRate>475)
{
SR = 14;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM9;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM9;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM9;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM9;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM9;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM9;
setDPLLPCM();
}
SerialUSB.println(F("48K PCM"));
}
else
if(sampleRate>430)
{
SR = 15;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM10;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM10;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM10;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM10;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM10;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM10;
setDPLLPCM();
}
SerialUSB.println(F("44.1K PCM"));
}
else
if(sampleRate>310)
{
SR = 16;
if (input == 0)
{
DPLL_PCM = Input[0].DPLLPCM11;
setDPLLPCM();
}
else if (input == 1)
{
DPLL_PCM = Input[1].DPLLPCM11;
setDPLLPCM();
}
else if (input == 2)
{
DPLL_PCM = Input[2].DPLLPCM11;
setDPLLPCM();
}
else if (input == 3)
{
DPLL_PCM = Input[3].DPLLPCM11;
setDPLLPCM();
}
else if (input == 4)
{
DPLL_PCM = Input[4].DPLLPCM11;
setDPLLPCM();
}
else if (input == 5)
{
DPLL_PCM = Input[5].DPLLPCM11;
setDPLLPCM();
}
SerialUSB.println(F("32K PCM"));
}
}
}
 
Hi Branko,

Just to start I also had some time to make changes lately and added remote controller functionality and some other minor changes. The new version is on github if you are interested.

Maybe i will have some time next weeks to program sample rate as well... would be the next step.


Now the sample rate form code you pasted, basically, if the code is written for the same Sabre ESS DAC it should work as it is, if you copy all the parts, functions and variables of course... and that's the problem I personally have with this code, its like spaghetti...all mixed up. So at the end if you want one function you need to copy half of the program all even all of it... Its still great job from DimDim, he made it work and that counts. I'm sure it works great on his application, but i would not copy it just as it is.


You ask:
"Also I would need how to tell what signal type is playing (DOD; DSD; PCM and Spdif or I2S) for the calculations. How do I get this info with your code?"

Look here, this is how DimDim is doing it:...he first check the Bit 0 from register 40 if the lock is on... 1 is on 0 no lock then from register 64 he gets signal type.

void getLockStatus()
{
byte r = ReadRegister(dac, 0x40); // Read Reg64
if (bitRead(r,0) == 1) // DAC has lock
{
lock = 1;
r = ReadRegister(dac, 0x64);
//SerialUSB.print("Reg100: "); SerialUSB.println(r, BIN);
//r = r << 4;
if (bitRead(r, 3) == 1)
{
sig_type = 0; // Decoded signal type is DoP
}
else if (bitRead(r, 2) == 1)
{
sig_type = 1; // Decoded signal type is SPDIF
}
else if (bitRead(r, 1) == 1)
{
sig_type = 2; // Decoded signal type is I2S
}
else if (bitRead(r, 0) == 1)
{
sig_type = 3; // Decoded signal type is DSD
}
SerialUSB.print(F("DAC has locked to a signal of type "));
SerialUSB.println(sig_type);
}
else if (bitRead(r,0) == 0)// DAC has no lock
{
SerialUSB.println(F("No Lock"));
lock = 0;
}
}


I would make 2 function from it... one getLockStatus (this returns lock status enum), and second one, getSignalType (return signal type enum).But go the way TPA is doing it, its great job... Look in the ES9028_38.h file, there are registers already defined.

// Register 64: Chip ID and Status - READ ONLY
#define R64_LOCK_STATUS_UNLOCKED 0b0
#define R64_LOCK_STATUS_LOCKED 0b1
#define R64_AUTOMUTE_INACTIVE 0b0
#define R64_AUTOMUTE_ACTIVE 0b1

you read the register 64 for lock and return lock status. I defined lock status enum LOCK_STATUS in my program, but you dont even need to use this one, you can return just byte or use TPA union... R64_CHIP_ID_STATUS

So there are many options, I followed the principle to return the enum (as i wanted to simplify the code), but if you want, you can even define R64_CHIP_ID_STATUS chip_status variable in DAC class and in getLockStatus you just set the chip_status.lock_status = xxx... one option ist to read the register 64 here and make switch from byte:

switch ( byte_from_64 ){
case: R64_LOCK_STATUS_LOCKED
return LOCK_STATUS::locked
....
}

or even better, read the whole register to union (i haven't test it but it should work) then use switch or if

R64_CHIP_ID_STATUS chip_status = readRegister(PE_ADDRESS, 64);
if ( chip_status.lock_status == R64_LOCK_STATUS_LOCKED ) {...}


TPA union is already there in ES9028_38.h:

typedef union {
struct {
B lock_status :1;
B automute_status :1;
B chip_id :6;
};
B byte;
} R64_CHIP_ID_STATUS;

You do the same with signal type... look again in ES9028_38.h file, the already defined union for us... you just need to read the register 100.
union is alrady defined
// Register 100: Input status - READ ONLY
typedef union {
struct {
B dsd_is_valid :1;
B i2s_is_valid :1;
B spdif_is_valid :1;
B dop_is_valid :1;
B reserved :4;
};
B byte;
} R100_INPUT_STATUS;

... but define the values first, something like this (I dont know the register settings here):
#define R100_DSD_VALID 0b0 ??? match the register values here (i dont know it)
#define R100_I2S_VALID 0b1 ???...


this is how you read the register:


//0x40 is equal to 64
#define REG_LOCK_MUTE 0x40
readRegister(PE_ADDRESS, REG_LOCK_MUTE);


So i would get the two functions first getLockStatus and getSignalType thats half of the way... later according to signal type you can calculate sample rate or switch dpll if needed

this is how TPA is writing DPLL register... for later use

R12_JE_DPLL_BW r12;
uint8_t dpll = sw2.dpll;
// we don't ever want the DPLL to be off
if (dpll == 0) dpll = 5;
r12.byte = R12_DEFAULT;
r12.dpll_bw_serial = dpll;
r12.dpll_bw_dsd = dpll;
writeRegister(DAC_ADDRESS, 12, r12.byte);

It maybe all confusing at the beginning, but TPA made here great job, but as it is Microcontroller C, wrapping it around the class is the best way to go and the most flexible.

Hope it will help.
 
Hello Poni,

as always I am impressed by your will to spend so much time and patience on these things.

As I starte with my idea of building my own dad it was all about "Modularity". I wanted to use only the things I need: DAC with PSU in separate enclosure. Source with PSU (BBB network streamer, USB to I2S etc) in separatete enclosures. So I could change or modify alway one thing per time and leaving the others in working state.

Therefor you approach to the software is so appealing to me: splitting it up in several classes and use only these you need.

Unfortunately this is beyond my skills (I try to get there but progress is slow :)).

I also think dimdim's code is great an I use it a lot to learn from.
But I think your code is the way to go.

Just add "classes" for all purposes and use only these you want for your DAC project.

For me it would be: several display types (TFT; TFT Nextion; LED matrix etc.); display sampling rate. DPLL and filter settings for each playing input; storing the latest settings in eprom (i. e. latest Input, volume etc.).

I would be glad to help. But this is over my level (the and skill). But I try :)

So I tank everyone in this great community willing to share.

Branko
 
Hi Branko,


"Just add "classes" for all purposes and use only these you want for your DAC project."
You nailed it with it, that's how it suppose to work...


The C approach of HiFiDuino and later DimDim is fine for small programs. I personally used C approach, with no classes to program my DAM1021 DAC, which is also on github. Its definitely easier to start, but along the way it always grows to mess.

This is simplified DAM1021:

An externally hosted image should be here but it was not working when we last tested it.



and how it works:

C0035.MP4 - Google Drive



The trigger to write C++ OO was actually TPA making the firmware public... I didnt want to write all the firmware from scratch, so I used TPA firmware for DAC. If you look closely DAC class is just wrap around TPA firmware and I think its perfect, as i can be sure all register are set properly and change just the bits I want.

The other way would be to make BBB nice state of art software, but it has to load operating system and all that stuff, powering down is an issue too. Microcontroller starts instantly and you just cannot make any harm to it, cut the power to turn it off is just fine... doing so with BBB is playing with fire.

But at the end I think most of the folks has their Buffalos up and running, so its all probably bit too late ;)

But yeah, for your modular build it would be perfect, especially having many displays would be easy to apply without messing with the code at all. But storing the settings in EEPROM is also easy to apply and no need to make any changes to existing code.


But as you have noticed it can be overwhelming at the beginning with all the classes, inheritances, definitions and so on. So I think one needs some basics in C++ Object Oriented programming to make best of it.

I have strong basics in C++ OO, so its intuitive somehow, but i know it can be pain in the *** for someone who is not familiar. C++ itself is also not the easiest language to follow.


Anyway, as I said, I want to make some changes next weeks, like sample rate, or EEPROM, but dont know when. Will let you know once I get some parts done, lock + signal type shouldn't be a problem, so i will start there.
 
Last edited:
Hello,

I wanted to display sampling rate and source. ( like this: SATIE)
Therefor I tried to get progress with the sampling rate :)

Also I had to build a „cape“ for the Arduino due to connect my displays simultaneously for testing purposes. Just received it from OSH-Park. Further I have to find out how to set brightness on my displays depending on ambient light by sensor. This may also be of interest for the TFT Display? It would be nice to dim the overall brightness after a few seconds a settings change is made.
Will try to post some info when I make progress.

Regards
Branko
 
Member
Joined 2007
Paid Member
But as you have noticed it can be overwhelming at the beginning with all the classes, inheritances, definitions and so on. So I think one needs some basics in C++ Object Oriented programming to make best of it.

I have strong basics in C++ OO, so its intuitive somehow, but i know it can be pain in the *** for someone who is not familiar. C++ itself is also not the easiest language to follow.

Greetings Poni,

I very much agree about the obstacle of learning C++. I have solved it by instead using Python for my DACs! :) Because my programming is only for my diyAudio hobby - i.e., not extremely demanding - things are working well. My previous project used the BBB for I2C to the ES9028 DAC, but I did not have all the control I wanted so I added an RPi for overall system control. The RPi doesn't touch the music, but gives 'hands-off' control for the entire system. My DIY components are not so pretty as yours (which are exceptional! :yes:), so with current software I can keep the generic rack boxes completely hidden.

My current project is to control ES90x8 DACs having USB sources. I'm now learning to fabricate a 'hat' with RPi GPIO pin compatibility. The 'hat' will maintain isolation of the controls, and the RPi can also be an audio input via USB. In addition to I2C control of the DAC, this 'hat' will also replace the 3.3v Trident to the BIII clock in order to switch configurations between running as 'true-synch' vs. oversampling mode. When I finally get code working, I'll report on the other thread linked above. For this new project, I hope to make really attractive boxes - but probably not as nice as yours. ;)

Cheers,

Frank
 
Hello,

hope you and your family/friends are all well and healthy.

As I have to stay at home due to the Virus-Situation, I would try to get some further with my Buffalo build.

Just build a test rig with different displays :)

Did you make some steps with the software?

Regards

Branko
 
Member
Joined 2007
Paid Member
Greetings Branko,

Thanks for your kind words. The USA is not well prepared for what is to come, but as a retired medical professional I'm fine. ...and also working on my own B3Pro project during these quiet times! :)

Perhaps you were inquiring about the C++ code, but it's quiet so here is an update on the Python-based project: The interface boards to provide galvanic isolation are all designed and ordered, and they should arrive in about 2 weeks. For my projects, I feel that a front-panel display would merely duplicate information that is more conveniently given in other formats. However, I realize that a nice OLED adds credibility to an impressive chassis and so I applaud those who wish to include them. :)

The interface board uses the 2X20 GPIO scheme from the Raspberry Pi because it has been adopted by many other SBC makers. [The Raspberry kernel is easiest to work with among those I have tried.] The board is very generalized in function and would allow either SPI or I2C control of small displays (along with all of the isolated functionality to/from the B3Pro). For my own project, I am bringing the RPi HDMI connection to the chassis back panel, should there be a need for a serious GUI, or even video display.

That's all for now. It will be several weeks still before any control code is written.

Best,

Frank
 

Attachments

  • Screen Shot 2020-03-19 at 2.08.54 PM.png
    Screen Shot 2020-03-19 at 2.08.54 PM.png
    360.4 KB · Views: 158