Внешний ЦАП на PCM5102A построен на недорогих но качественных компонентах. Ранее в http://rcl-radio.ru/?p=88911 был уже рассмотрен пример создания внешнего ЦАП на PCM5102A совместно с ресивером на DIR9001, в этой статье будет рассмотрен пример создания внешнего ЦАПа на ресивере CS8416, так же в схему добавлен высококачественный регулятор громкости на PGA2311 (аналогичный пример был в http://rcl-radio.ru/?p=91011 но только с применением ЦАП на CS4354).
ЦАП на PCM5102A недорогой компонент, но обладает вполне качественными характеристиками:
- динамический диапазон 112 дБ
- отношение сигнал/шум 112 дБ
- уровень нелинейных искажений -93 дБ
- разрядность 16, 24 и 32 бит
- частота дискредитации от 8 до 384 кГц
Так же схема ЦАП на PCM5102A имеет минимальный набор внешних компонентов.
Схема ЦАП на PCM5102A
ЦАП на PCM5102A сконфигурирован (выход FMT на GND) на обработку входных данных формата I2S (16-24-32 bit 8 kHz to 384 kHz), ресивер CS8416 программно также настроен на выходной поток данных в формате I2S (24 bit 192 kHz). Ресивер на ИМС CS8416 имеет 8 коммутируемых входов S/PDIF (Toslink).
Схема ресивера на CS8416
Для регулировки громкости, баланса и поддержки режима MUTE используется цифровой регулятор громкости PGA2311.
Схема регулятора громкости на PGA2311
Основные характеристики 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)
Блок управления на базе Arduino Nano содержит экран LCD1602 с I2C модулем, энкодер ky-040 и одну кнопку для активации режима MUTE.
Схема источника питания (стабилизаторов)
#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 PCM5102A"); 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;www=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
Даташиты: