Внешний звуковой ЦАП состоит из нескольких недорогих компонентов, управление осуществляется при помощи 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;}
Библиотеки:
- CS3310.zip (полностью совместима с PGA2311)
- liquidcrystali2c.zip
- Encoder.zip
- MsTimer2.zip
Даташиты: