DAC CS8416 + CS4354 + PGA2311 (Arduino)

Внешний звуковой ЦАП состоит из нескольких недорогих компонентов, управление осуществляется при помощи Arduino (Nano). Ресивер на ИМС CS8416 имеет 8 коммутируемых входов S/PDIF (Toslink), выходной цифровой сигнал ресивера формата I2S 24-бит с частотой дискредитации 192 кГц, аналогичные параметры имеет ЦАП на CS4354. Для регулировки громкости и баланса используется электронный регулятор громкости PGA2311. Вся информация о настройках выводится на LCD1602 (с модулем I2C), управление осуществляется при помощи энкодера (KY-040).

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

  • напряжение питания:
    • +3.3 V Analog Supply (VA)
    • +3.3 V Digital Supply (VD)
    • +3.3 V or +5.0 V Digital Interface Supply (VL)
  • отношение сигнал/шум 120 дБ
  • динамический диапазон 120 дБ
  • уровень гармонических искажений -107 дБ
  • взаимное проникновение каналов 120 дБ
  • выходной формат данных: I2S, RJ, LJ от 16 бит 32 кГц до 24 бит 192 кГц (программно настроен на I2S 24 бит 192 кГц)
  • 8 входов

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

  • напряжение питания:
    • +5-V analog supply
    • +1.8-V to +5-V power supplies
  • входной формат данных 24-bit I2S
  • разрядность 24 бит
  • частота дискредитации до 192 кГц
  • динамический диапазон 101 дБ
  • уровень нелинейных искажений не более –86 дБ

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

  • напряжение питания:
    • ±5-V Analog
    • +5-V Digital
  • диапазон регулировки громкости от -95,5 дБ до +31.5 дБ с шагом 0,5 дБ (255 ступеней)
  • динамический диапазон 120 дБ
  • коэффициент нелинейных искажений не более 0.0002%
  • меж канальные перекрестные помехи не более -130 дБ
  • входное напряжение 2.5 Vrms
  • режим MUTE

Управление ресивером CS8416 осуществляется при помощи шины I2C, управление регулятора громкости PGA2311 происходит с помощью шины 3-wire (SPI). ЦАП может работать только в аппаратном режиме.

Внешний ЦАП имеет очень простое управление, в основном (в первом) меню находится регулировка громкости — 100 шагов от 0 до 99 значения, шаг 0,5 дБ, во втором меню регулировка баланса ±10 дБ с шагом 0,5 дБ, в третьем меню коммутатор входов, в четвертом фильтр De-emphasis. Изменения настроек происходит при помощи энкодера, режим MUTE активируется отдельной кнопкой. Все настройки сохраняются в энергонезависимой памяти.

Дополнительно в основном меню помимо громкости отображается номер выбранного входа и режим работы VOLUME / MUTE / NO SOUND / ERROR (основной режим, ошибок нет / активен режим MUTE / коаксиальный кабель подключен, но нет звука / коаксиальный кабель не подключен).

Для питания внешнего ЦАПа рекомендуется использовать три независимых источника питания, который состоит из одного трансформатора с тремя независимыми вторичными обмотками,  одна из которых имеет отвод от середины для двух полярного источника питания. Каждый источник питания имеет свой диодный мост. Дополнительно как можно ближе к микросхемам по цепи питания устанавливаются фильтрующие конденсаторы 0,1 и 10…22 мкФ.

Схема ресивера

Схема ЦАП

Схема регулятора громкости

Схема источника питания (стабилизаторов)

Схема управления (Arduino)

#include <Wire.h> 
#include <EEPROM.h>
#include <CS3310.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
 CS3310 pga(2,3,4);//CS,SCLK,SDATAI
      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}; 
 unsigned long times,times1,oldPosition  = -999,newPosition;
 byte err,err_old,www,mute,z,z1,z0;
 int in,menu,w,w1,dem_res,dem,vol,vol_d,balanc=0;
 byte i,d1,d2,d3,d4,d5,d6,e1,e2,e3;
 int a[3];
 
void setup(){
   Wire.begin();Serial.begin(9600);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);
   MsTimer2::set(3, to_Timer);MsTimer2::start(); 
   pinMode(10,INPUT);// SW 
   pinMode(5,OUTPUT);      //выход управления MUTE
   pinMode(6,INPUT_PULLUP);// кнопка MUTE
   digitalWrite(5,LOW);   // MUTE ON
   lcd.setCursor(0,0);lcd.print(" CS8416  CS4354 ");
   lcd.setCursor(0,1);lcd.print(" I2S 24bit 192k ");
   delay(1000);
   lcd.setCursor(0,0);lcd.print("     PGA2311    ");
   lcd.setCursor(0,1);lcd.print(" Volume Control ");delay(1000);
   in = EEPROM.read(1);dem_res = EEPROM.read(3);vol = EEPROM.read(0);balanc = EEPROM.read(2)-20;
   set8416();delay(100);lcd.clear();
   digitalWrite(5,HIGH);   // MUTE OFF
   audio();
  }
 
void loop(){
//// MENU ///////////////////////// 
  if(digitalRead(10)==LOW){menu++;if(menu>3){menu=0;};delay(200);lcd.clear();times=millis();www=1;w1=1;} 
/// MUTE ///////////////////////////////////////
   if(digitalRead(6)==LOW&&mute==0){mute=1;digitalWrite(5,LOW);menu=0;w=1;www=1;delay(300);}
   if(digitalRead(6)==LOW&&mute==1){mute=0;digitalWrite(5,HIGH);menu=0;w=1;www=1;delay(300);}
 
 /// 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;vol_func();audio();
  }
  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>=22&&mute==0){lcd.setCursor(0,0);lcd.print("ERROR   ");}
      if(err==8&&mute==0){ lcd.setCursor(0,0);lcd.print("NO SOUND");}
 
      lcd.setCursor(0,1);lcd.print("COAX ");lcd.print(in);
     vol_d=vol-101;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);
 }}}
 
 
/// BALANCE ////////////////////////////
   if(menu==1&&mute==0){
  if (newPosition != oldPosition){oldPosition = newPosition;www=0;
     balanc=balanc-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
     if(balanc<-20){balanc=-20;}if(balanc>20){balanc=20;}vol_func();audio();}
  if(www==1||w==1){www=0;w=0;
      if(balanc<0){lcd.setCursor(8,1);lcd.write((uint8_t)0);
                 lcd.setCursor(0,1);lcd.print("L > R");}                
      if(balanc>0){lcd.setCursor(8,1);lcd.print(" ");
                 lcd.setCursor(0,1);lcd.print("L < R");}
     if(balanc==0){lcd.setCursor(8,1);lcd.print(" ");
                 lcd.setCursor(0,1);lcd.print("L = R");}
      lcd.setCursor(0,0);lcd.print("BALANCE ");
 
      a[0]=abs(balanc)/10;a[1]=abs(balanc)%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==2&&mute==0){
    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;}set8416();}
  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==3&&mute==0){
    if(newPosition != oldPosition){oldPosition = newPosition;dem=dem-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;
    if(dem<0){dem=4;}if(dem>4){dem=0;}}
  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_res=0;break;
  case 1: lcd.print("44.1 kHz      ");dem_res=2;break;
  case 2: lcd.print("48.0 kHz      ");dem_res=3;break;
  case 3: lcd.print("32.0 kHz      ");dem_res=1;break;
  case 4: lcd.print("AUTO          ");dem_res=4;break;}
  set8416();}} 
/////// EEPROM ////////////////////////////////////////////////
  if(millis()-times>10000 && w1==1){w1=0;
  EEPROM.update(1,in);EEPROM.update(3,dem_res);
  EEPROM.update(0,vol);EEPROM.update(2,balanc+20);
  menu=0;www=1;lcd.clear();}
/////// ERROR /////////////////////////////////////////////////
  if(millis()-times1>1000){times1=millis();err = wireRead(0x10,0x0C);delay(10);Serial.println(err);}
  if(err!=err_old){err_old = err;www=1;}
  delay(100);} // END LOOP 
 
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 set8416(){
  Wire.beginTransmission(0x10);
  Wire.write (0x00);
  Wire.write (0b00000000);
  Wire.write (0b10000000);// 256Fs
  Wire.write (0b00000000 + (dem_res << 4));// De-emphasis
  Wire.write (0b00000000);
  Wire.write (0b10000000 + (in << 3));// input
  Wire.write (0b10000101);// I2S
  Wire.write (0xFF);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.write (0x00);
  Wire.endTransmission();
  }  
 
void audio(){pga.setVol(vol+balanc,vol-balanc);/* byte 1...255 === -95.5...+31.5 dB (step 0.5 dB)*/}  
void vol_func(){if(vol<0){vol=0;}if(vol>200){vol=200;}}
void balanc_func(){if(balanc<-20){balanc=-20;}if(balanc>20){balanc=20;}}
void to_Timer(){newPosition = myEnc.read()/4;}

Библиотеки:

Даташиты:

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

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