Внешний ЦАП WM8805 + PCM1753 (Arduino)

ЦАП — цифро-аналоговый преобразователь — это устройство, которое преобразует информацию из цифрового вида  в аналоговые сигналы, при этом максимально точно и без искажений.

Собрать внешний ЦАП на компонентах предложных в статье не сложно, он содержит несколько недорогих компонентов и в настройке практически не нуждается.

Входной цифровой сигнал имеет формат S/PDIF (цифровой аудио интерфейс разработанный фирмами SONY/PHILIPS, предназначен для передачи цифрового сигнала между аудио устройствами), ЦАП PCM1753 не поддерживает такой формат, для преобразования S/PDIF необходим ресивер на WM8805 который преобразует S/PDIF в цифровой сигнал в формат I2S. С выхода ЦАП звуковой сигнал поступает на фильтр-сумматор собранный на ОУ NE5532, а с выхода фильтра звуковой сигнал можно подавать на наушники с минимальным сопротивлением 32 Ом .

Внешний звуковой ЦАП содержит несколько основных компонента:

  • Плата Arduino Nano
  • Ресивер на WM8805 (24 бит 196 кГц)
  • ЦАП на PCM (24 бит 196 кГц)
  • Фильтр-сумматор на NE5532
  • LCD1602
  • Энкодер KY-040

Характеристики основных компонентов внешнего ЦАПа:

  • WM8805
    • Входной формат — S/PDIF
    • Кол-во входов … 8 (программно ограничено до 4-х)
    • Выходной формат:
      • 16-bit, MSB-first, right-justified
      • 24-bit, MSB-first, right-justified
      • 24-bit, MSB-first, left-justified
      • 24-bit, MSB-first, I2S
    • Напряжение питания
      • Digital power supply … 3,3 В
      • Digital power supply … 3,3 В
    • Диапазон частот дискретизации входного сигнала 32 — 196 кГц

Управление ресивером WM8805 программное, ресивер настроен на выходной сигнал формата 24 bit / I2S / 196 kHz.

  • PCM1753
    • Входные форматы:
      • I2S, left-justified / 16-, 18-, 20-, 24-bit
    • De-emphasis filter
      • 38 кГц
      • 44,1 кГц
      • 48 кГц
    • Digital Filter Rolloff
      • Sharp rolloff
      • Slow rolloff
    • Напряжение питания … 5 В
    • Коэффициент гармоник от 0.002% до 0,004% на уровне 0 дБ
    • Динамический диапазон 102 …106 дБ (192 … 44,1 кГц)
    • Отношение сигнал/шум  102 …106 дБ (192 … 44,1 кГц)
    • Частота дискретизации до 192 кГц
    • Работает совместно с фильтром-сумматоров на NE5532

Управление ЦАП PCM1753 программное, настроен на входной сигнал формата 24 bit / I2S / 196 kHz.

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

Рекомендуется как можно ближе к микросхемам по цепи питания устанавливаются фильтрующие конденсаторы 0,1 и 22 мкФ.

Подключение к плате Arduino

Во внешнем ЦАПе использован энкодер KY-040 для управления громкостью, коммутатором входов, фильтрами De-emphasis и Digital Filter Rolloff, дополнительно используется кнопка для активации режима MUTE. Информация о режимах работы внешнего ЦАП выводится на LCD дисплея LCD1602 на базе контроллера HD44780 совместно с модулем I2C. I2C модуль на базе микросхемы PCF8574 позволяют подключить символьный дисплей 1602 к плате Arduino всего по двум проводам SDA и SCL (А4 и А5), что дает возможность не использовать цифровые выходы Arduino при подключении дисплея.

Меню внешнего ЦАПа состоит из 4-х пунктов:

  • Регулятор громкости от -78 до 0 дБ

  • Коммутатор входов SPDIF

  • De-emphasis filter — 38 кГц | 44,1 кГц | 48 кГц

  • Digital Filter Rolloff Control

На главное меню помимо громкости выводится выводится дополнительная информация:

  1. Номер входа SPDIF
  2. De-emphasis filter — 38 кГц | 44,1 кГц | 48 кГц | 192 кГц (фильтр отключен)
  3. Digital Filter Rolloff Control, режимы Sharp rolloff и Slow rolloff
  4. Уровень громкости от 0 до 99 значений (шаг 0,5 дБ)
  5. Индикатор отображает режимы VOLUME, MUTE, NO SOUND — если на входе SPDIF нет сигнала или кабель не подключен
#define ML 2
#define MC 3
#define MD 4
 
#define CLK    A0 // CLK ENCODER
#define DT     A1 // DT  ENCODER
#define SW     A2 // SW  ENCODER
 
#define CSB   11 // CSB   WM8805
#define SCLK  8  // SCLK  WM8805
#define SDIN  9  // SDIN  WM8805
#define SDOUT 10 // SDOUT WM8805
 
#define MUTE 5
 
// LCD1602 I2C A4 = SDA, A5 = SCL
 
#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(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,oldPosition  = -999,newPosition,times1;
  byte i,d1,d2,d3,d4,d5,d6,e1,e2,e3;
  int vol,w,w1,w2=1,menu,mute,dem,a[3],dem_on,roll,in,err;
 
 
void setup(){
  pinMode(ML,OUTPUT);
  pinMode(MC,OUTPUT);
  pinMode(MD,OUTPUT);
  pinMode(SW,INPUT);
  pinMode(MUTE,INPUT_PULLUP);
  pinMode(CSB,OUTPUT);  // CSB   WM8805
  pinMode(SCLK,OUTPUT); // SCLK  WM8805
  pinMode(SDIN,OUTPUT); // SDIN  WM8805
  pinMode(SDOUT,INPUT); // SDOUT WM8805
  Wire.begin();Serial.begin(9600);
  lcd.init();lcd.backlight();
  MsTimer2::set(1, to_Timer);MsTimer2::start();
  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);
  delay(100);
  if(EEPROM.read(100)!=0){for(int i=0;i<101;i++){EEPROM.update(i,0);}}// очистка памяти при первом включении 
  vol = EEPROM.read(0);dem = EEPROM.read(1);roll = EEPROM.read(2);in = EEPROM.read(3);
  if(dem<3){dem_on=1;}else{dem_on=0;}
  // 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);
  audio();  
  }
 
void loop(){
  /// MENU //////////////////////////////
  if(digitalRead(SW)==LOW){menu++;if(menu>3){menu=0;};delay(200);lcd.clear();w=1;times=millis();w2=1;}
   /// MUTE ///////////////////
 if(digitalRead(MUTE)==LOW&&mute==0){mute=1;audio();delay(200);}
 if(digitalRead(MUTE)==LOW&&mute==1){mute=0;audio();delay(200);}  
  /// VOLUME ///////////////////////////////////////////////////
 if(menu==0){
   if (newPosition != oldPosition){oldPosition = newPosition;
     vol=vol+newPosition;myEnc.write(0);newPosition=0;w=1;times=millis();w2=1;vol_func();
     Serial.println(vol);
     audio();}
      lcd.setCursor(0,0);
      if(mute==1){lcd.print("MUTE     ");}
      if(err==0&&mute==0){lcd.print("VOLUME ");lcd.print("C");lcd.print(in);}
      if(err>0&& mute==0){lcd.print("NO SOUND ");} 
      lcd.setCursor(0,1);
      switch(dem){
        case 0: lcd.print("44.1k");break;
        case 1: lcd.print("48.0k");break;
        case 2: lcd.print("32.0k");break;
        case 3: lcd.print("192 k");break;
  }
        switch(roll){
        case 0: lcd.print("  SH ");break;
        case 1: lcd.print("  SL ");break;
  }
 
if(w2==1){w2=0;
     a[0]=(vol-156)/10;a[1]=(vol-156)%10;
      for(i=0;i<2;i++){
      switch(i){
        case 0: e1=10,e2=11,e3=12;break;
        case 1: e1=13,e2=14,e3=15;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>3){in=3;}
      WM8805Write(0x08, 0b01110000 + in);}
 
  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;w=1;times=millis();w2=1;
      if(dem<0){dem=3;}if(dem>3){dem=0;}
      if(dem<3){dem_on=1;}else{dem_on=0;}
      audio();}
 
  if(w2==1){w2=0;
  lcd.setCursor(0,0);lcd.print("DE-EMPHASIS FILT");  
  lcd.setCursor(0,1);
  switch(dem){
    case 0: lcd.print(" 44.1 kHz");break;
    case 1: lcd.print(" 48.0 kHz");break;
    case 2: lcd.print(" 32.0 kHz");break;
    case 3: lcd.print(" 192  kHz");break;
  }}} 
 
   /// Roll-off Filter //////////////////////////////////
  if(menu==3){
     if (newPosition != oldPosition){oldPosition = newPosition;
     roll=roll+newPosition;myEnc.write(0);newPosition=0;w=1;times=millis();w2=1;
     if(roll<0){roll=1;}if(roll>1){roll=0;}
     audio();}
 
  if(w2==1){w2=0;
  lcd.setCursor(0,0);lcd.print("Roll-off Filter");  
  lcd.setCursor(1,1);
  switch(roll){
    case 0: lcd.print("Sharp");break;
    case 1: lcd.print("Slow ");break;
  }}} 
 
  /// NO SOUND /////////////////////
  if(millis()-times1>500){times1=millis();err = WM8805Read(0x0B);}
 
 
  //////// EEPROM //////////////////////////////////////////////////
  if(millis()-times>10000 && w==1 && mute == 0){EEPROM.update(0,vol);EEPROM.update(1,dem);EEPROM.update(2,roll);EEPROM.update(3,in);w=0;w2=1;menu=0;lcd.clear();}
  }// loop
 
 
void WritePCM(byte reg, byte data){  // WRITE_REG SPI
   digitalWrite(MC,HIGH);digitalWrite(ML,LOW);
     for(int i = 7; i >= 0; i--){
        digitalWrite(MC,LOW);
        digitalWrite(MD, (reg >> i) & 0x01);
        digitalWrite(MC,HIGH);
        }
     for(int i = 7; i >= 0; i--){
        digitalWrite(MC,LOW);
        digitalWrite(MD, (data >> i) & 0x01);
        digitalWrite(MC,HIGH);
        }
    digitalWrite(MC,HIGH);digitalWrite(ML,HIGH);delay(1);
  }
 
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 audio(){  
  WritePCM(0x10, vol);//r16 [7:0]att 0-min 255-max
  WritePCM(0x11, vol);//r17 [7:0]att 0-min 255-max
  WritePCM(0x12, 0b01000000 + (mute << 1) + mute);//r18 [6]x128 [1:0]mute
  WritePCM(0x13, 0b00000000 + (dem_on << 4) + (dem << 5));//r19 [1:0]dac_on [4]De-Emphasis [6:5] De-Emphasis Sample Rate Selection
  WritePCM(0x14, 0b00000100 + (roll << 5));//r20 [2:0]100=I2S_24 [5]Filter Rolloff
  WritePCM(0x16, 0B00000001);//r22
  }  
 
void to_Timer(){newPosition = myEnc.read()/4;}  
void vol_func(){if(vol<156){vol=156;}if(vol>255){vol=255;}}

Comments

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

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