DAC CS8416 + CS4398 (Arduino)(2)

Ранее в http://rcl-radio.ru/?p=83845 рассматривался вариант создания внешнего ЦАП на основе ресивера CS8416 и ЦАП на CS4398. В этой статье аналогичный проект, главное отличие его от предыдущего проекта это полностью программное управление ресивером и ЦАП. Управление осуществляется при помощи платы Arduino через шину I2C.

Основное преимущество в программном управлении в отличии от аппаратного, это расширение функциональных возможностей. Например при программном управлении кол-во входов ресивера увеличивается до 8-и, появляется возможность менять выходной формат данных (Left-Justified, Right-Justified, I2S).

Основные параметры ресивера CS8416:

  • Напряжение питания:
    • +3.3 V Analog Supply (VA)
    • +3.3 V Digital Supply (VD)
    • +3.3 V or +5.0 V Digital Interface Supply (VL)
  • Входной формат данных: S/PDIF
  • Выходной формат данных: Left-Justified, Right-Justified, I2S
  • Разрядность 24 бит
  • Частота дискредитации 192 кГц
  • Фильтр De-emphasis:
    • No De-emphasis
    • 32 kHz
    • 48 kHz
    • 44.1 kHz
  • Кол-во входов: 8
  • Управление: I2C

Основные характеристики CS4398:

  • отношение сигнал/шум 120 дБ
  • динамический диапазон 120 дБ
  • уровень гармонических искажений -107 дБ
  • взаимное проникновение каналов 120 дБ
  • входной формат: Left-Justified, Right-Justified, I2S
  • разрядность 24 бит
  • частота дискретизации 192 кГц

С выхода ЦАП звуковой сигнал поступает на фильтр-сумматор собранный на ОУ NE5532:

В состав внешнего ЦАП под управлением Arduino входит:

  • Ресивер CS8416
  • ЦАП CS4398
  • Плата Arduino Nano
  • LCD1602 с модулем I2C
  • Энкодер KY-040

Для питания внешнего ЦАПа рекомендуется использовать три независимых источника питания, который состоит из одного трансформатора с тремя независимыми вторичными обмотками,  одна из которых имеет отвод от середины для двух полярного источника питания. Каждый источник питания имеет свой диодный мост. При этом аналоговая и цифровая земля объединяются через дроссель. Дополнительно как можно ближе к микросхемам по цепи питания устанавливаются фильтрующие конденсаторы 0,1 и 22 мкФ. Подключение к плате Arduino осуществляется тремя проводами (I2C) SDA SCL и GND к DGND цепи питания ЦАП и ресивера.

Управление внешним ЦАП разделено на несколько меню, навигация по меню осуществляется при помощи кнопки энкодера, поворот ручки энкодера меняет выбранный параметр, дополнительно используется одна кнопка для функции MUTE.

  • Меню громкости (99 шагов), индикация выбранного входа, индикатор MUTE и ERROR
  • Меню выбора входа

  • Меню фильтра De-emphasis
  • Меню выбора выходного формата

 

#include <Wire.h> 
#include <EEPROM.h>
#include <Encoder.h>  // http://rcl-radio.ru/wp-content/uploads/2019/05/Encoder.zip
#include <MsTimer2.h> // http://rcl-radio.ru/wp-content/uploads/2018/11/MsTimer2.zip
#include <LiquidCrystal_I2C.h> //Библиотека -  http://forum.rcl-radio.ru/misc.php?action=pan_download&item=45&download=1
 LiquidCrystal_I2C lcd(0x27,16,2);  // Устанавливаем дисплей 
 Encoder myEnc(9, 8);//CLK, DT
      byte v1[8] = {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07};
      byte v2[8] = {0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00};      
      byte v3[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F};
      byte v4[8] = {0x1F,0x1F,0x00,0x00,0x00,0x00,0x1F,0x1F};
      byte v5[8] = {0x1C,0x1C,0x00,0x00,0x00,0x00,0x1C,0x1C};
      byte v6[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C};
      byte v7[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07};
      byte v8[8] = {0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x00};
      int a[3],vol,vol_d,menu,in,dem,form;
      byte i,d1,d2,d3,d4,d5,d6,e1,e2,e3;
      unsigned long times,times1,oldPosition  = -999,newPosition;
      bool w,w1,www=1,mute;
      byte dem_dac,dem_res,for_dac,for_res,err,err_old;
 
void setup(){
   Wire.begin();
   delay(100);
   //Serial.begin(9600);
   MsTimer2::set(3, to_Timer);MsTimer2::start();
   lcd.init();lcd.backlight();
   lcd.createChar(1, v1);lcd.createChar(2, v2);lcd.createChar(3, v3);lcd.createChar(4, v4);lcd.createChar(5, v5);lcd.createChar(6, v6);lcd.createChar(7, v7);lcd.createChar(8, v8);
   lcd.setCursor(0,0);lcd.print("CS8416  00");lcd.print(wireRead(0x10,0x7F),BIN);
   lcd.setCursor(0,1);lcd.print("CS4398  0");lcd.print(wireRead(0x4C,0x01),BIN);
   delay(2000);
   pinMode(10,INPUT);// SW 
   pinMode(11,INPUT_PULLUP); // кнопка MUTE
  if(EEPROM.read(100)!=0){for(int i=0;i<101;i++){EEPROM.update(i,0);}}// очистка памяти при первом включении 
  vol = EEPROM.read(0);in = EEPROM.read(1);dem_dac = EEPROM.read(2);dem_res = EEPROM.read(3);for_dac = EEPROM.read(4);for_res = EEPROM.read(5);
// Write 8416
  Wire.beginTransmission(0x10);
  Wire.write (0x00);
//-------------------//
  Wire.write (0b00000000);
  Wire.write (0b10000010);// 128Fs
  Wire.write (0b00000000 + (dem_res << 4));
  Wire.write (0b00000000);
  Wire.write (0b10000000 + (in << 3));
  Wire.write (0b10000000 + (for_res));
  Wire.write (0xFF);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.endTransmission();
// End Write ////////////////////////////////////
delay(10);
// Write 4398 
  wireWrite(0x4C, 0x02, 0b00000010 + (dem_dac << 2) + (for_dac << 4));
  wireWrite(0x4C, 0x03, 0b00001001);// default
  wireWrite(0x4C, 0x04, 0b11000000);// default
  wireWrite(0x4C, 0x05, vol);
  wireWrite(0x4C, 0x06, vol);
  wireWrite(0x4C, 0x07, 0b10110000);// default
  wireWrite(0x4C, 0x08, 0b01000000);// default
  wireWrite(0x4C, 0x09, 0b00001000);// default
// End Write ////////////////////////////////////
  lcd.clear();
  }
 
void loop(){ 
  /// MENU //////////////////////////////
  if(digitalRead(10)==LOW){menu++;if(menu>3){menu=0;};delay(200);lcd.clear();www=1;}
 
 /// MUTE /////////////////////////////
 if(digitalRead(11)==LOW && mute==0){mute=1;wireWrite(0x4C, 0x04, 0b11011000);delay(300);www=1;} 
 if(digitalRead(11)==LOW && mute==1){mute=0;wireWrite(0x4C, 0x04, 0b11000000);delay(300);www=1;}
 
 /// VOLUME ////////////////////////////
  if(menu==0){
  if (newPosition != oldPosition){oldPosition = newPosition;www=0;
     vol=vol+newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
      if(vol<0){vol=0;}if(vol>99){vol=99;}
      wireWrite(0x4C, 0x05, vol);
      wireWrite(0x4C, 0x06, vol);
  }
  if(www==1||w==1){www=0;w=0;
      if(mute==1){        lcd.setCursor(0,0);lcd.print("MUTE   ");}
      if(err==0&&mute==0){lcd.setCursor(0,0);lcd.print("VOLUME ");}
      if(err>0&&mute==0){ lcd.setCursor(0,0);lcd.print("ERROR  ");}
 
      lcd.setCursor(0,1);lcd.print("COAX ");lcd.print(in);
     vol_d=99-vol;a[0]=vol_d/10;a[1]=vol_d%10;
      for(i=0;i<2;i++){
      switch(i){
        case 0: e1=9,e2=10,e3=11;break;
        case 1: e1=12,e2=13,e3=14;break;
        }
      switch(a[i]){
        case 0: d1=1,d2=8,d3=6,d4=1,d5=3,d6=6;break;
        case 1: d1=32,d2=2,d3=6,d4=32,d5=32,d6=6;break;
        case 2: d1=2,d2=8,d3=6,d4=1,d5=4,d6=5;break;
        case 3: d1=2,d2=4,d3=6,d4=7,d5=3,d6=6;break;
        case 4: d1=1,d2=3,d3=6,d4=32,d5=32,d6=6;break;
        case 5: d1=1,d2=4,d3=5,d4=7,d5=3,d6=6;break;
        case 6: d1=1,d2=4,d3=5,d4=1,d5=3,d6=6;break;
        case 7: d1=1,d2=8,d3=6,d4=32,d5=32,d6=6;break;
        case 8: d1=1,d2=4,d3=6,d4=1,d5=3,d6=6;break;
        case 9: d1=1,d2=4,d3=6,d4=7,d5=3,d6=6;break;
    }
      lcd.setCursor(e1,0);lcd.write((uint8_t)d1);lcd.setCursor(e2,0);lcd.write((uint8_t)d2);lcd.setCursor(e3,0);lcd.write((uint8_t)d3);
      lcd.setCursor(e1,1);lcd.write((uint8_t)d4);lcd.setCursor(e2,1);lcd.write((uint8_t)d5);lcd.setCursor(e3,1);lcd.write((uint8_t)d6);
 }}
 }
 //////// INPUT ////////////////////////////////////  
  if(menu==1){
    if (newPosition != oldPosition){oldPosition = newPosition;
     in=in-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
      if(in<0){in=0;}if(in>7){in=7;}
  Wire.beginTransmission(0x10);
  Wire.write (0x00);
  Wire.write (0b00000000);
  Wire.write (0b10000010);
  Wire.write (0b00000000 + (dem_res << 4));
  Wire.write (0b00000000);
  Wire.write (0b10000000 + (in << 3));
  Wire.write (0b10000000 + (for_res));
  Wire.write (0xFF);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.endTransmission();}
  delay(10);
  if(www==1||w==1){www=0;w=0;
  lcd.setCursor(0,0);lcd.print("INPUT SELECTOR");  
  lcd.setCursor(0,1);lcd.print("COAXIAL "); lcd.print(in);lcd.print(" ");
  }
  }
 
  /// DE-EMPHASIS //////////////////////////////////
  if(menu==2){
    if (newPosition != oldPosition){oldPosition = newPosition;
     dem=dem-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
      if(dem<0){dem=0;}if(dem>3){dem=3;}
 
  Wire.beginTransmission(0x10);
  Wire.write (0x00);
  Wire.write (0b00000000);
  Wire.write (0b10000010);
  Wire.write (0b00000000 + (dem_res << 4));
  Wire.write (0b00000000);
  Wire.write (0b10000000 + (in << 3));
  Wire.write (0b10000000 + (for_res));
  Wire.write (0xFF);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.endTransmission();
  delay(10);
  wireWrite(0x4C, 0x02, 0b00000010 + (dem_dac << 2) + (for_dac << 4));
  delay(10);
  }
  if(www==1||w==1){www=0;w=0;
  lcd.setCursor(0,0);lcd.print("DE-EMPHASIS SEL");  
  lcd.setCursor(0,1);
  switch(dem){
  case 0: lcd.print("No De-emphasis");dem_dac=0;dem_res=0;break;
  case 1: lcd.print("44.1 kHz      ");dem_dac=1;dem_res=2;break;
  case 2: lcd.print("48.0 kHz      ");dem_dac=2;dem_res=3;break;
  case 3: lcd.print("32.0 kHz      ");dem_dac=3;dem_res=1;break;
  }
  }
  }
 
  /// Serial Format Select //////////////////////////////////
  if(menu==3){
    if (newPosition != oldPosition){oldPosition = newPosition;
     form=form-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
      if(form<0){form=0;}if(form>2){form=2;}
 
  Wire.beginTransmission(0x10);
  Wire.write (0x00);
  Wire.write (0b00000000);
  Wire.write (0b10000010);
  Wire.write (0b00000000 + (dem_res << 4));
  Wire.write (0b00000000);
  Wire.write (0b10000000 + (in << 3));
  Wire.write (0b10000000 + (for_res));
  Wire.write (0xFF);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.endTransmission();
  delay(10);
  wireWrite(0x4C, 0x02, 0b00000010 + (dem_dac << 2) + (for_dac << 4));
  delay(10);
  }
  if(www==1||w==1){www=0;w=0;
  lcd.setCursor(0,0);lcd.print("FORMAT SELECT");  
  lcd.setCursor(0,1);
  switch(form){
  case 0: lcd.print("L-Just 24 bit");for_dac=0;for_res=0;break;
  case 1: lcd.print("I2S    24 bit");for_dac=1;for_res=5;break;
  case 2: lcd.print("R-Just 24 bit");for_dac=3;for_res=8;break;
  }
  }
  }
  ////////////////////////////////////////////////////////
 
  delay(100);
 
  if(millis()-times1>1000){times1=millis();err = wireRead(0x10,0x0C);delay(10);}
  if(err!=err_old){err_old = err;www=1;}
 
  if(millis()-times>10000 && w1==1){w1=0;EEPROM.update(0,vol);EEPROM.update(1,in);
  EEPROM.update(2,dem_dac);EEPROM.update(3,dem_res);EEPROM.update(4,for_dac);EEPROM.update(5,for_res);
  menu=0;www=1;lcd.clear();}
 
  }  
 
void wireWrite(byte addr, byte reg, byte data){
  Wire.beginTransmission(addr);
  Wire.write (reg);
  Wire.write (data);
  Wire.endTransmission();
  }
 
byte wireRead(int addr, int reg){
  Wire.beginTransmission(addr);
  Wire.write (reg);
  Wire.endTransmission();
  delay(10);
  Wire.requestFrom(addr,1);
  while(Wire.available()<1);
  byte value = Wire.read();
  return value;
  }  
 
void to_Timer(){newPosition = myEnc.read()/4;}

Comments

  1. Tried on a breadbord and noticed that my cs8416 is damaged, remove the line that reads it and after reading the cs4398 shows the menu (wich I cant select, probably for not be able to read the cs8416). On the format selection is there anyway to upgrade the choices, like you did on the dac with the wm8740? Thanks and keep up the good work!

  2. Привет, меня зовут Мишель,
    Я не говорю по-русски, и я надеюсь, что автоматический перевод будет работать.
    Моя электронная почта: mzovatto@yahoo.com
    Я создаю ЦАП с чипом CS4398 с независимым источником питания для цифровых и аналоговых требований. Аналоговый каскад полностью дискретный.
    Я попробовал ваш проект, но добавил второй чип CS4398, потому что мне нужна конфигурация с двойным моно.
    Мне удалось связаться с двумя CS4398, используя разные шестнадцатеричные адреса, 0x4C и 0x4D.
    Таким образом, я смог использовать один чип только для левого канала, а другой — для правого канала.
    Я не хочу использовать микросхему CS8416 в качестве приемника.
    У меня есть интерфейс USB WAVEIO и приемник AK4113 (коаксиальный). Оба они имеют выход I2S.

    Я не хочу использовать микросхему CS8416 в качестве приемника.
    Моя цель состоит в том, чтобы построить мультиплексный коммутатор 74hc157 перед cs4398, чтобы переключаться между двумя входными интерфейсами.
    Я пытался удалить часть вашего программного обеспечения, касающуюся CS8416, но я не могу заставить его работать…
    Мой вопрос: не могли бы вы мне помочь?
    Я хотел бы сохранить функцию громкости, выбор двух каналов I2S, тип данных и расстановку акцентов.

    Вам нужна схема? Я сделал это в орле.
    Спасибо заранее.

Добавить комментарий

Войти с помощью: