Внешний ЦАП AK4393 (Arduino)

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

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

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

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

  • Плата Arduino Nano
  • Ресивер на DIR9001 (24 бит 96 кГц)
  • ЦАП на AK4393 (24 бит 96 кГц)
  • Цифровой регулятор громкости PGA2311
  • Фильтр-сумматор на NE5532
  • LCD1602
  • Энкодер KY-040

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

  • DIR9001
    • Входной формат — S/PDIF
    • Выходной формат:
      • 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 В
    • Диапазон частот дискретизации входного сигнала 28 — 108 кГц

Так как вход ресивера DIR9001 имеет вход уровня TTL, сигнал S/PDIF подается через преобразователь интерфейса MAX485. Ресивер аппаратно настроен на выходной сигнал формата 24 bit / I2S / 96 kHz.

В схеме ресивера присутствует индикатор режима работы светодиод D1, при подключении коаксиального кабеля с источником цифрового сигнала светодиод гаснет, при отключении кабеля загорается.

  • AK4393
    • Входные форматы:
      • 16bit LSB Justified
      • 20bit LSB Justified
      • 24bit MSB Justified
      • I2S Compatible
      • 24bit LSB Justified
    • De-emphasis filter
      • 38 кГц
      • 44,1 кГц
      • 48 кГц
      • 96 кГц
    • Напряжение питания
      • Digital power supply … 5 В
      • Digital power supply … 5 В
    • Уровень гармонических искажений -100 дБ
    • Динамический диапазон 120 дБ
    • Отношение сигнал/шум  120 дБ
    • Частота дискретизации до 108 кГц
    • Выходной сигнал — дифференциальные аудиовыходы

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

Схема фильтра сумматора

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

Программно диапазон регулировки громкости немного уменьшен, добавлена регулировка баланса.

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

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

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

KY-040

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

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

  • Регулировка громкости от -86,0 до +14,0 дБ, шаг регулировки 0,5 дБ

  • Регулировка баланса от -10 до +10 дБ, шаг регулировки 0,5 дБ

  • Фильтр De-emphasis filter 38 кГц, 44,1 кГц, 48 кГц, 96 кГц
#define SCK    5  // CCLK AK4393
#define DATA   4  // CDTI AK4393
#define CS     3  // CSN  AK4393
#define RES    2  // PDN  AK4393
 
#define SCLK   10 // SCLK PGA2311
#define SDATAI 9  // SDI  PGA2311
#define CSP    8  // CS   PGA2311
#define MUTE   11 // MUTE PGA2311
 
#define B_MUTE 12 // BUTTON MUTE
 
#define CLK    A0 // CLK ENCODER
#define DT     A1 // DT  ENCODER
#define SW     A2 // SW  ENCODER
 
// 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 a1[8] = {0b00000,0b10101,0b10101,0b10101,0b10101,0b10101,0b10101,0b00000};
  byte a2[8] = {0b00000,0b10100,0b10100,0b10100,0b10100,0b10100,0b10100,0b00000};
  byte a3[8] = {0b00000,0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b00000};  
  unsigned long times,oldPosition  = -999,newPosition;
  int vol,vol_d,z,z0,z1,vol_old,balanc,emp;
  byte w,w1,www,mute,menu;
 
void setup(){
 pinMode(CS,OUTPUT);
 pinMode(SCK,OUTPUT);
 pinMode(DATA,OUTPUT);
 pinMode(RES,OUTPUT);
 pinMode(CSP,OUTPUT);
 pinMode(SCLK,OUTPUT);
 pinMode(SDATAI,OUTPUT);
 pinMode(MUTE,OUTPUT);
 pinMode(SW,INPUT); 
 pinMode(B_MUTE,INPUT_PULLUP);
 Wire.begin();Serial.begin(9600);
 lcd.init();lcd.backlight();
 MsTimer2::set(1, to_Timer);MsTimer2::start();
 lcd.createChar(0,a1);lcd.createChar(1,a2);lcd.createChar(2,a3);
 lcd.setCursor(0,0);lcd.print(" DIR9001 AK4393 ");
 lcd.setCursor(0,1);lcd.print("     PGA2311    ");delay(2000);lcd.clear();
 vol = EEPROM.read(0);
 balanc = EEPROM.read(1)-20;
 emp = EEPROM.read(2);
 
  digitalWrite(RES,HIGH);
  digitalWrite(MUTE,HIGH); 
  WriteAK4393(0b01100000,0b00000110); // reset
  WriteAK4393(0b01100000,0b00000111); // 128fs | I2S
  switch(emp){
    case 0:  WriteAK4393(0b01100001,0b00001100);break;
    case 1:  WriteAK4393(0b01100001,0b00000000);break;
    case 2:  WriteAK4393(0b01100001,0b00001000);break;
    case 3:  WriteAK4393(0b01100001,0b00001010);break;
   }
 
  WritePGA2311(vol+balanc,vol-balanc);
 }
 
void loop(){
/// MENU /////////////////////////////////////// 
  if(digitalRead(SW)==LOW){menu++;if(menu>2){menu=0;}lcd.clear();times=millis();w=1;w1=1;www=1;delay(200);}
/// MUTE ///////////////////////////////////////
   if(digitalRead(B_MUTE)==LOW&&mute==0){mute=1;digitalWrite(MUTE,LOW);lcd.clear();lcd.setCursor(0,0);lcd.print("      MUTE   ");delay(300);}
   if(digitalRead(B_MUTE)==LOW&&mute==1){mute=0;myEnc.write(0);newPosition=0;digitalWrite(MUTE,HIGH);lcd.clear();w1=1;delay(300);}  
 
/////////////// VOLUME /////////////////////////////////////////////////////////
   if(menu==0&&mute==0){
   if (newPosition != oldPosition&&mute==0){oldPosition = newPosition;
   vol=vol+newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;vol_func();WritePGA2311(vol+balanc,vol-balanc);} 
   vol_d=map(vol,20,220,0,45);
   if(w1==1){
   for(z=0,z0=0,z1=0;z<=vol_d;z++,z1++){if(z1>2){z1=0;z0++;}
   if(z1==1){lcd.setCursor(z0,1);lcd.write((uint8_t)0);lcd.setCursor(z0+1,1);lcd.print("   ");}}
   if(z1==3){lcd.setCursor(z0,1);lcd.write((uint8_t)1);}
   if(z1==2){lcd.setCursor(z0,1);lcd.write((uint8_t)2);}w1=0;}
 
     lcd.setCursor(0,0);lcd.print("VOLUME ");
     if(vol*0.5-96>=0){lcd.print("+");}
     lcd.print(vol*0.5-96,1);lcd.print(" ");
     lcd.setCursor(14,0);lcd.print("dB");
   } // menu == 0  
 
/////////////// BALANCE +/- 10 dB /////////////////////////////////////////////////////////
   if(menu==1&&mute==0){
   if (newPosition != oldPosition&&mute==0){oldPosition = newPosition;
   balanc=balanc+newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;balanc_func();WritePGA2311(vol+balanc,vol-balanc);} 
   lcd.setCursor(0,0);lcd.print("BALANCE ");if(balanc>=0){lcd.print("+");}lcd.print(balanc*0.5,1);lcd.print(" ");lcd.setCursor(14,0);lcd.print("dB");
   if(w1==1){
   if(balanc<0){lcd.setCursor(balanc/4-2+7,1);lcd.print("  ");lcd.write((uint8_t)0);lcd.print("  ");}
   if(balanc==0){lcd.setCursor(balanc/4-2+7,1);lcd.print("  ");lcd.write((uint8_t)0);lcd.write((uint8_t)0);lcd.print("  ");}
   if(balanc>0){lcd.setCursor(balanc/4-2+8,1);lcd.print("  ");lcd.write((uint8_t)0);lcd.print("  ");}
   w1=0;}
   }// menu == 1
 
/////////////// De-emphasis filter /////////////////////////////////////////////////////////   
   if(menu==2&&mute==0){
   if (newPosition != oldPosition&&mute==0){oldPosition = newPosition;
   emp=emp+newPosition;myEnc.write(0);newPosition=0;times=millis();w=1;w1=1;emp_func();www=1;}
   lcd.setCursor(0,0);lcd.print("De-emphasis filt");lcd.setCursor(4,1);   
   if(www==1){www=0;
   switch(emp){
    case 0:  lcd.print("32.0 kHz");WriteAK4393(0b01100001,0b00001100);break;
    case 1:  lcd.print("44.1 kHz");WriteAK4393(0b01100001,0b00000000);break;
    case 2:  lcd.print("48.0 kHz");WriteAK4393(0b01100001,0b00001000);break;
    case 3:  lcd.print("96.0 kHz");WriteAK4393(0b01100001,0b00001010);break;
   }}
   }// menu == 2
 
 
//////// EEPROM //////////////////////////////////////////////////
  if(millis()-times>10000 && w==1 && mute == 0){EEPROM.update(0,vol);EEPROM.update(1,balanc+20);EEPROM.update(2,emp);w=0;menu=0;w1=1;lcd.clear();}
 
  }// loop
 
 
void WriteAK4393(byte reg, byte data){  // WRITE_REG SPI
   digitalWrite(SCK,HIGH);digitalWrite(CS,LOW);
     for(int i = 7; i >= 0; i--){
        digitalWrite(SCK,LOW);
        digitalWrite(DATA, (reg >> i) & 0x01);
        digitalWrite(SCK,HIGH);
        }
     for(int i = 7; i >= 0; i--){
        digitalWrite(SCK,LOW);
        digitalWrite(DATA, (data >> i) & 0x01);
        digitalWrite(SCK,HIGH);
        }
    digitalWrite(SCK,HIGH);digitalWrite(CS,HIGH);
  }     
 
void WritePGA2311(byte r_ch, byte l_ch){  // WRITE_REG SPI
   digitalWrite(SCLK,HIGH);digitalWrite(CSP,LOW);
     for(int i = 7; i >= 0; i--){
        digitalWrite(SCLK,LOW);
        digitalWrite(SDATAI, (r_ch >> i) & 0x01);
        digitalWrite(SCLK,HIGH);
        }
     for(int i = 7; i >= 0; i--){
        digitalWrite(SCLK,LOW);
        digitalWrite(SDATAI, (l_ch >> i) & 0x01);
        digitalWrite(SCLK,HIGH);
        }
   digitalWrite(CSP,HIGH);
}  
 
void to_Timer(){newPosition = myEnc.read()/4;}
void vol_func(){if(vol<20){vol=20;}if(vol>220){vol=220;}}
void emp_func(){if(emp<0){emp=3;}if(emp>3){emp=0;}}
void balanc_func(){if(balanc<-20){balanc=-20;}if(balanc>20){balanc=20;}}

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

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