Внешний звуковой ЦАП под управлением платы Arduino (Nano) состоит из пяти основных компонентов:
- Ресивер WM8805
- ЦАП WM8740
- ФНЧ на NE5532
- Источника питания
- Плата Arduino (Nano), индикатор LCD1602 и органы управления (энкодер и одна кнопка)
Параметры S/PDIF (Toslink) ресивера WM8805:
- Напряжение питания
- DVDD +3,3 V
- PVDD +3.3 V
- Входной формат данных: S/PDIF
- Выходной формат данных:
- Left-Justified
- Right-Justified
- I2S
- Разрядность 24 бит
- Частота дискредитации 192 кГц
- Кол-во входов: 8
- Управление: 3-wire (SPI)
- jitter of 50 ps RMS
Параметры ЦАП на WM8740:
- Напряжение питания:
- +5 V Analog Supply (AVDD)
- +5 V Digital Supply (DVDD)
- Входной формат данных:
- Left-Justified
- Right-Justified
- I2S
- Разрядность 24 бит
- Частота дискредитации 192 кГц
- Уровень гармонических искажений THD+N
- -104 dB (0dB)
- -117 dB (-60 dB)
- Отношение сигнал/шум 107 dB
- Фильтр De-emphasis:
- No De-emphasis
- 32 kHz
- 48 kHz
- 44.1 kHz
- Фильтр ROLL-OFF:
- Sharp
- Slow
- Управление: 3-wire (SPI)
Ресивер WM8805 управляется при помощи шины 3-wire (SPI), содержит 8 входов S/PDIF (Toslink), но активны только 4 (программно ограничено кол-во входов), Поддерживает три выходных формата данных Left-Justified, Right-Justified и I2S (программно настроено на I2S 24-bit 192kHz), содержит систему опознавания ошибок.
ЦАП на WM8740 поддерживает прием трех форматов входных данных Left-Justified, Right-Justified и I2S, содержит переключаемые фильтры De-emphasis и ROLL-OFF, имеет систему MUTE, регулятор громкости на 255 шагов (127 дБ), каждый шаг 0,5 дБ, программно число шагов ограничено от 145 до 245 значений, что соответствует диапазону регулировки от -60 до -10 дБ (0…99 единиц громкости), 10 дБ зарезервировано под баланс. Управление осуществляется через шину 3-wire (SPI). Программно доступна только регулировка громкости и баланса, режим MUTE. Настроен на прием формата I2S 24-bit 192 kHz.
Вся информация о текущих настройках внешнего ЦАП отображается на индикаторе LCD1602 ( с модулем I2C), регулировка всех параметров осуществляется при помощи энкодера KY-040, параметр MUTE активируется отдельной кнопкой.
Схема ресивера
Схема ЦАП
Схема ФНЧ
Схема источника питания (стабилизаторов)
Для питания внешнего ЦАПа рекомендуется использовать три независимых источника питания, который состоит из одного трансформатора с тремя независимыми вторичными обмотками, одна из которых имеет отвод от середины для двух полярного источника питания. Каждый источник питания имеет свой диодный мост. Дополнительно как можно ближе к микросхемам по цепи питания устанавливаются фильтрующие конденсаторы 0,1 и 10…22 мкФ.
Arduino
#define CS 2 // ML WM8740 #define CLK 3 // MC WM8740 #define DATA 4 // MD WM8740 #define CSB 5 // CSB WM8805 #define SCLK 6 // SCLK WM8805 #define SDIN 7 // SDIN WM8805 #define SDOUT 11 // SDOUT WM8805 // 9 // CLK ENCODER // 8 // DT ENCODER // 10 // SW ENCODER // 12 // MUTE BUTTON #include <Wire.h> #include <LiquidCrystal_I2C.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 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}; unsigned long times,times1,oldPosition = -999,newPosition; byte menu,w,w1,www=1,mute; int vol,vol_d,ball,err,err_old,a[3],in; byte i,d1,d2,d3,d4,d5,d6,e1,e2,e3; void setup(){ 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(1, to_Timer);MsTimer2::start(); Serial.begin(9600); lcd.setCursor(0,0);lcd.print(" WM8805 WM8740 "); lcd.setCursor(0,1);lcd.print("2IS-24bit-192kHz"); if(EEPROM.read(100)!=0){for(int i=0;i<101;i++){EEPROM.update(i,0);}}// очистка памяти при первом включении pinMode(10,INPUT); // SW энкодер pinMode(12,INPUT_PULLUP); // кнопка MUTE pinMode(CS,OUTPUT); // ML WM8740 pinMode(CLK,OUTPUT); // MC WM8740 pinMode(DATA,OUTPUT);// MD WM8740 ////////////////////////////////////////// pinMode(CSB,OUTPUT); // CSB WM8805 pinMode(SCLK,OUTPUT); // SCLK WM8805 pinMode(SDIN,OUTPUT); // SDIN WM8805 pinMode(SDOUT,INPUT); // SDOUT WM8805 delay(100); vol = EEPROM.read(0);in = EEPROM.read(1);ball = EEPROM.read(9)-10; // Write WM8740 SPI WM8740Write(0b000100000000 + vol + ball);//reg0 WM8740Write(0b001100000000 + vol - ball);//reg1 WM8740Write(0b010000001000);//reg2 WM8740Write(0b011000000001);//reg3 WM8740Write(0b110000000000);//reg4 delay(100); // Write WM8805 SPI WM8805Write(0x00, 0x01); // RESET WM8805Write(0x03, 0xBA); // PPL_K 0xBA WM8805Write(0x04, 0x49); // PPL_K 0x49 WM8805Write(0x05, 0x0C); // PPL_K 0x0C WM8805Write(0x06, 0x08); // PLL_N 0x08 WM8805Write(0x07, 0b00101000);// 00 CLKOUTDIV[1:0] MCLKDIV FRACEN>!!! FREQMODE[1:0] WM8805Write(0x08, 0b01110000 + in);// MCLKSRC ALWAYSVALID FILLMODE CLKOUTDIS CLKOUTSRC RXINSEL[2:0]>000:PX0 WM8805Write(0x09, 0b11111111);// SPDIFINMODE[7:0]>gain WM8805Write(0x0A, 0b00000000);// MASK[7:0] WM8805Write(0x12, 0b00000000);// CHSTMODE[1:0] DEEMPH[2:0] CPY_N AUDIO_N CON/PRO WM8805Write(0x13, 0b00000000);// CATCODE[7:0] WM8805Write(0x14, 0b00000000);// CHNUM2[1:0] CHNUM1[1:0] SRCNUM[3:0] WM8805Write(0x15, 0b01110001);// TXSTATSRC TXSRC CLKACU[1:0] FREQ[3:0] // default WM8805Write(0x16, 0b00001011);// ORGSAMP[3:0] TXWL[2:0]>101:24bit MAXWL>1:24bit_max WM8805Write(0x1B, 0b00001010);// 00 AIFTX_LRP AIFTX_BCP AIFTX_WL[1:0] AIFTX_FMT[1:0] WM8805Write(0x1C, 0b01001010);// SYNC_OFF>0:no_sound AIF_MS>1:master AIFRX_LRP AIFRX_BCP AIFRX_WL[1:0]>11:24bit AIFRX_FMT[1:0]>10:I2S WM8805Write(0x1D, 0b11000000);// SPD_192K_EN WL_MASK SPDGPO WITHFLAG CONT READMUX[2:0] WM8805Write(0x1E, 0b00000100);// 00 TRIOP AIFPD OSCPD SPDIFTXPD SPDIFRXPD PLLPD delay(1000);lcd.clear(); } void loop(){ /// MENU ////////////////////////////// if(digitalRead(10)==LOW){menu++;if(menu>2){menu=0;};delay(200);lcd.clear();times=millis();w=1;w1=1;www=1;} /// MUTE ///////////////////////////// if(digitalRead(12)==LOW && mute==0){mute=1; WM8740Write(0b010000001001);delay(300);www=1;} if(digitalRead(12)==LOW && mute==1){mute=0; WM8740Write(0b010000001000);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<145){vol=145;}if(vol>245){vol=245;} WM8740Write(0b000100000000 + vol + ball);//reg0 WM8740Write(0b001100000000 + vol - ball);//reg1 Serial.print("VOLUME ");Serial.println(vol);// vol = 145...245 step 0.5 dB + 10 balance } 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("NO SOUND");} lcd.setCursor(0,1);lcd.print("COAX ");lcd.print(in); vol_d=vol-146;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){ if (newPosition != oldPosition){oldPosition = newPosition;www=0; ball=ball-newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1; if(ball<-10){ball=-10;}if(ball>10){ball=10;} WM8740Write(0b000100000000 + vol + ball);//reg0 WM8740Write(0b001100000000 + vol - ball);//reg1 Serial.print("BALANCE ");Serial.println(ball); } if(www==1||w==1){www=0;w=0; if(ball<0){lcd.setCursor(8,1);lcd.write((uint8_t)0); lcd.setCursor(0,1);lcd.print("L > R");} if(ball>0){lcd.setCursor(8,1);lcd.print(" "); lcd.setCursor(0,1);lcd.print("L < R");} if(ball==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(ball)/10;a[1]=abs(ball)%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){ 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>3){in=3;} WM8805Write(0x08, 0b01110000 + in); } 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(" "); }} ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// NO SOUND ///////////////////// if(millis()-times1>500){times1=millis();err = WM8805Read(0x0B);} if(err!=err_old){err_old = err;www=1;} /// EEPROM write ///////////////// if(millis()-times>10000 && w1==1){w1=0;EEPROM.update(0,vol);EEPROM.update(1,in);EEPROM.update(9,ball+10); www=1;if(menu!=0){lcd.clear();}menu=0;} }// loop void WM8740Write(int data){ digitalWrite(CLK,HIGH);digitalWrite(CS,HIGH);delay(1); for(int i = 15; i >= 0; i--){ digitalWrite(CLK,LOW); digitalWrite(DATA, (data >> i) & 0x01);delay(1); digitalWrite(CLK,HIGH);delay(1);} digitalWrite(CS,LOW);delay(1);digitalWrite(CS,HIGH); } void WM8805Write(byte reg, byte dout){ // WRITE_REG digitalWrite(SCLK,LOW);digitalWrite(CSB,LOW); for(int i = 7; i >= 0; i--){ digitalWrite(SCLK,LOW); digitalWrite(SDIN, (reg >> i) & 0x01); digitalWrite(SCLK,HIGH); } for(int i = 7; i >= 0; i--){ digitalWrite(SCLK,LOW); digitalWrite(SDIN, (dout >> i) & 0x01); digitalWrite(SCLK,HIGH); } digitalWrite(SCLK,LOW);digitalWrite(CSB,HIGH); } byte WM8805Read(byte reg){ // READ_REG byte dat[8],data = 0; reg = reg + (1 << 7); digitalWrite(SCLK,LOW);digitalWrite(CSB,LOW); for(int i = 7; i >= 0; i--){ digitalWrite(SCLK,LOW); digitalWrite(SDIN, (reg >> i) & 0x01); digitalWrite(SCLK,HIGH); } for(int i = 7; i >= 0; i--){ digitalWrite(SCLK,LOW); dat[i] = digitalRead(SDOUT); data = data + (dat[i] << i); digitalWrite(SCLK,HIGH); } digitalWrite(SCLK,LOW);digitalWrite(CSB,HIGH); return data; } void to_Timer(){newPosition = myEnc.read()/4;}