Документация к ES9039Q2M
Последнее поколение модулятора Hyperstream IV еще больше улучшает качество звука, потребляя при этом меньше энергии по сравнению с предыдущим поколением. Это улучшение аудио характеристик приводит к уменьшению цифрового шума внутри устройства, что позволяет воспроизводить звук с насыщенным и захватывающим звучанием.
ES9039Q2M могут использовать множество входных интерфейсов, включая TDM, I2S, LJ, RJ, DSD, DoP и S/PDIF. Они также могут быть запрограммированы с помощью нескольких методов ввода, включая I2C, SPI, а для облегчения программирования — новый режим с аппаратной поддержкой. ES9039Q2M также имеют программируемые FIR-фильтры для самых взыскательных аудиоэнтузиастов и профессионалов, выпускаются в 32-контактном корпусе QFN.
Вложение:
смысл? сам производитель позиционирует этот чипс для носимых устройств. но конечно для самодельщиков большой ++ это наличие HW конфигурирования с помощью ног микрухи. в остальном ни чего выдающегося.
А вы его хоть слушали в нормальном исполнении? Пока на мой взгляд этот мобильный чип король бюджетных сборок, при минимальных вложениях получаем максимум результата.
Недавно собрал довольно простой макет аналогичный AH-D6 Pro, для оценки потенциала чипа, ничего особого не ждал. Тем более, что был опыт работы с предыдущим поколением в виде ES9028Pro, который я забросил и забыл, как страшный сон.
Но чип приятно удивил, макет выдал взрослый звук, тому же ak4493 тягаться с ES9039Q2M очень сложно.
У LYNXа появился очередной «свежий пирожок» на ES9038PRO. Пишет, что звучит очень хорошо. Правда совершенно не понятно, на сколько хорошо.
А то, что он так пишет, лучше не брать во внимание.
Вопрос что собирать следующим решился, ждёмс..
Лично с ним по поводу этого пирожка разговаривал, мне он не так красочно рассказывал. Лучше не тратить на него время, не имея полного мануала и опыта работы с этими цап ничего путного не выйдет, будет пустая трата денег и времени.
Мануал у него есть, вот с каким трудом линкс его заполучил, нам не получить никогда, причем в нем есть куча ошибок, а информации в 15 раз больше чем в выложенном мануале официально. Там одних настроек больше сотни. А ASRC там не отключаемый! это он выяснил точно.
Вот 9039 возможно какого-то внимания и заслуживает, надо расспросить.
Собственно про ES9039Q2M речь и была. С inet-слухов чип достойный, что подтверждается словами Admin «ak4493 тягаться с ES9039Q2M очень сложно». И «ждемс..» относиться к проекту от Андрея, То что в нормальной обвязке потенциал у ES9039Q2M есть сомнений нет. Посмотрел что али в виде плат на этом чипе предлагает, имхо выброшенные деньги и время. Так что, надеюсь что появиться проект над которым можно будет приятно провести зимой время и в итоге получить удовольствие от звучания повторенной разработки Андрея.
Обвязка еще не все, вот на сколько достоверен и полон мануал на него....
Есть подозрение что даташит «кривой» ?
у ess пока не было мануалов без ошибок, скорее всего сделанных специально, тот же линкс много раз их там находил, да и он сам в своих мануалах и схемах точно так же делал специально ошибки.
«так же делал специально ошибки» — Дмитрий говорит, что это «защита от пиратского копирования» :)
У ES9039Q2M как раз вся прелесть, что есть HW режим, которого для основных задач более чем достаточно, те не надо совсем морочится с прошивкой. Аппаратно тоже неплохо поработали, пресловутого ESS IMD Hump я не наблюдаю, спектры довольно чистые 2 (небольшая) и 3 гармоники (THD при полной шкале сразу получился < 0.00008% и это на AH-F3 Superb, который снял с D19 и быстро подогнал под ES9039Q2M, никаких тонких настроек и оптимизаций), чего никак не удавалось добиться на ES9028 Pro, на котором был длинный хвост гармоник и какой-то мусор в ВЧ области, от которого мне никак не удавалось избавиться, и это точно был не фильтр (перепробовал разных более 10-ка), полагаю, что где-то с софтом по режимам пролетел, может чип такой попался (хотя стоит отдать должное цифры получались неплохие под 0.0001% так как уровень гармоник у всех был небольшой -128Дб и ниже). Ну и аппаратно, что просто добило проект на ES9028 Pro (на счет ES9038 Pro не знаю, выбрал 28-й из за меньшего выходного тока в надежде обойтись малой кровью с фильтром), то, что ess объединили все питания 1.2В аналоговое и цифровое внутри чипа, ну хоть бы заикнулись об это в даташите. Зато было забавно видеть у одного из брендовых аппаратов тоже россыпь стабов на 1.2в!!! Ладно бы я, но похоже они даже брендам не заморачиваются сообщать от таких нюансах в полной документации. Проекты D18 на ES9028 Pro и D17 на ak4497 Pro делал примерно в одно и то же время и просто в сравнении по бюджету + трудозатратам и итоговому результату все сложилось сильно не в пользу ES9028 Pro, а от ES9039Q2M я ничего не ждал, проект платы вообще слепил на коленке за пару дней, на прошивку время не тратил, запаял инженерную версию в довольно бюджетном исполнении, а результат сильно превзошел ожидания.
Ну и теперь главное о звуке ES9039Q2M, удивительно, но это первая сябра, которая меня не утомляет.
Похоже поняли свои косяки и перепилили чип по новому. Если не утомляет, наверное можно обратить внимание.
Есть мысль, что это старший чип с урезанным количеством каналов и немного упрощенный для более широкого использования.
непохоже на старший. в ES9039Q2M оказывается есть выход для соединения в цепочку. он понимает TDM причем их там оказывается можно чуть ли не 16 каналов набирать. конечно это все через регистры, и там этих регистров более 200, при этом большое количество имеет статус reserved, вот в этом моменте похоже что digital core у них в цапах одинаковое. скорее всего тупо сэкономили на производственных затратах, но в регистры заложена просто уйма моментов для приданию звуку окраски. просто удивительно что они туда эквалайзер и еще пару улучшайзеров не встроили.
З.Ы, не совсем понятно как китайцы на своих платах делают SE выхлоп всего на двух восьминожках и это не диффампы.
P.P.S. на сайте сабры выложен более свежий даташит чем в этой теме.
Взял алишную плату на 9039q2m, даже в стоке звук очень и очень порадовал.
Не поделитесь ссылкой на плату?
https://sl.aliexpress.ru/p?key=odCe3gU
https://sl.aliexpress.ru/p?key=xFCe3Pm
Вариант с двумя цапами похоже поинтереснее реализовали.
скорее ближе к даташиту/классике, I-V на ора1612 и сумматор xlr-rca на ора1611. когда ОУ меньше то китайцы явно в каком то месте сэкономили.
Понятное дело — барахлом подменяют, ценой подкупают. Хочешь сделать хорошо, сделай сам.
ESS9039Q2M DSD1024/PCM768KHZ
Хороший малыш:
https://aliexpress.ru/item/1005008496280554.html
А в паре с Luckfox Pico Max через USB вообще звучит замечательно :)
Вот только ИОН для AVCC там стоит ADP150… фильтруется конечно, но можно попробовать что-то более малошумящего
у ADР150 шум 9мкВ, для этих целей вполне, конечно есть что то бОлее малошумящее, но это уже совсем другая история.
Прямая замена на LP5907. У ADP150 не очень по 1/f шумам, сильно растут ниже 100hz. A вот LP5907 держится до 10hz
Коллеги, если кто освоил софтовое управление ES9039Q2M подскажите — при каких условиях на аналоговых выходах появляется 1/2 аналогового питания? Микра в целом отзывается, пишу и читаю регистры, управляю GPIO, но аналоговую секцию включить не удается, на выходах 0 вольт. На бит «DAC enable» в 0-м регистре не реагирует. Тактирование асинхронное, 49 Мег.
у пиндосов видел такую инициализацию для i2s
writeRegister(0x00, 0x80); // Register 0 Bit 7 to 1 Reset digital core (all settings are set to default) — on the off-chance
writeRegister(0x39, 0x01); // INPUT SELECTION [0] 1'b1: Automatically determine the input data format
writeRegister(0x2A, 0x00); // I2S IN ACTIVE
writeRegister(0x00, 0x02); // Register 0 Bit 1=1 Set DAC_MODE to enable DAC analog output
Благодарю, опробую такую последовательность.
присоединяюсь к вопросу. поделитесь управлением кто освоил. купил пару цапов на опыты
Неделю мучений и чат джипити запустил es9039q2m на ардуино! Строки с пиндоса реально помогли.
Подéлитесь описанием конструкции и исходниками?
Да конечно, скейч написан под ардуино нано, думаю без труда можно перепесать под свой контролер, сейчас работает с меню, громкость баланс, фазовый захват, полярность сигнала, авто формат, авто вход, настройка стерео и двойное моно, тоесть сам ставишь какой чип будет правый а какой левый, все фильтры, ещё дописал настройки по звуку, но не обкатал их, завтра займусь, короче заставил для эксперемента включить в меню вообще всё что умеет чип, допилю и скину. А так была плата с Китая, там блютуз и два ципа es9039q2m, Китай не стал заморачиватся с двойным моно и просто включил параллельно выходы, на плате был процессор который я так понял только включал в софт es9039q2m, но звук мне не понравился, короче выкинул проц, за одно спалил обе es9039q2m, пока игрался в хард режиме, заказал новый но он больше не запел в плате, скорее всего второго чипа не хватало, проц ждал два адреса от es9039q2m, короче пришлось играться с ардуино, звук на много лучше сейчас чем был с родным проц, сейчас в настройках появилась функция по фильтру гармоник, то есть подавляя вторую или 3 меняет восприятия.
Спасибо!
Куда скинуть?
Можно на vo_pushkov@mail.ru, а лучше оформить здесь на сайте статьёй, чтобы другие могли воспользоваться этой ценной информацией.
… или временно загрузить на пастебин, там можно указать срок хранение
Иначе вот такое получается, как на картинке
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
/* =========================
ПОДКЛЮЧЕНИЯ / АДРЕСА
========================= */
#define DAC_ADDR 0x49
#define LCD_ADDR 0x27
LiquidCrystal_I2C lcd(LCD_ADDR, 16, 2);
#define ENC_A 2
#define ENC_B 3
#define BTN 4
#define CHIP_EN_PIN 7 // HIGH=ON, LOW=RESET
/* =========================
РЕГИСТРЫ ES9039Q2M (DEC)
========================= */
#define REG_SYS 0x00
#define REG_IFACE_POL 0x03
#define REG_CLKCFG 0x04
#define REG_PLL_BW 0x05
#define REG_AUTO_INPUT 57
#define REG_SLOT_L 64
#define REG_SLOT_R 65
#define REG_VOL_L 74
#define REG_VOL_R 75
#define REG_FILTER 88
#define REG_PATH 90
#define REG_THD_C2_LSB 91
#define REG_THD_C3_LSB 107
#define REG_AM_ENABLE 123
#define REG_AM_TIME_L 124
#define REG_S1 0x1C
#define REG_S2 0x1D
#define REG_ID 225
/* =========================
ПРОТОТИПЫ
========================= */
bool wr(uint8_t r, uint8_t v);
bool wr16(uint8_t addrLSB, uint16_t val);
bool rd(uint8_t r, uint8_t &v);
bool pingDAC();
void chipOn();
void chipOff();
void chipReset(uint16_t ms=400);
bool lockStatus();
uint32_t fsFromS2();
void volWriteLR(uint8_t vL,uint8_t vR);
void setFilter(uint8_t f);
void writePathConfig();
void applySafe_VolBal();
void applySafe_Filter();
void applySafe_Path();
void applyAutoMute();
void applyTHD_C2();
void applyTHD_C3();
void queueApply(bool softR=false);
void runPending();
void showStatus();
void drawMenu();
void doAction(uint8_t idx);
void encISR();
void pollBtn();
void onChangeSafe(uint8_t code);
void onChangeDanger(uint8_t what);
/* =========================
СОСТОЯНИЕ / МЕНЮ
========================= */
uint8_t g_volBase=0; // 0..255 (0=0 dB)
int8_t g_balance=0; // -24..+24 (≈±12 dБ)
uint8_t g_dfilt=0; // 0..7
uint8_t g_path=0; // 0=ST,1=MonoL,2=MonoR,3=Swap
// bypass биты REG_PATH
bool g_bypIIR=false;
bool g_bypFIR4=false;
bool g_bypFIR2=false;
// интерфейс / клок
uint8_t g_pol=0; // 0..3
uint8_t g_pll=0x04; // 0x02..0x08 обычно
bool g_autoClk=true;
bool g_autoIn =true;
bool g_hardtrue; // «опасные» не писать
uint16_t g_settle=700; // мс после «опасных»
// AutoMute
bool g_amCh1=true;
bool g_amCh2=true;
bool g_amRampGND=true; // bit11
uint16_t g_amTime=0x00F; // [10:0]
// THD (signed 16-bit)
int16_t g_c2_L=0, g_c2_R=0;
int16_t g_c3_L=0, g_c3_R=0;
/* ===== Планировщик «опасных» ===== */
struct Pending { bool polarity=false, pll=false, autoclk=false, autoin=false, softReset=false; } pend;
/* ===== EEPROM / пресеты ===== */
#define CFG_VERSION 0x39 // меняй при правке структуры
struct Config {
uint8_t version;
uint8_t volBase; int8_t balance; uint8_t dfilt; uint8_t path;
bool bypIIR, bypFIR4, bypFIR2;
uint8_t pol, pll; bool autoClk, autoIn;
bool hardOnly; uint16_t settle;
bool amCh1, amCh2, amRamp; uint16_t amTime;
int16_t c2L, c2R, c3L, c3R;
uint8_t autosave;
uint16_t crc;
};
uint16_t crc16(const uint8_t* p, size_t n){
uint16_t c=0xFFFF;
for(size_t i=0;i<n;i++){ c ^= p[i]; for(uint8_t b=0;b<8;b++){ c = (c&1)?(c>>1)^0xA001:(c>>1); } }
return c;
}
#define EE_ADDR_LAST 0
#define EE_ADDR_PRE1 128
#define EE_ADDR_PRE2 256
#define EE_ADDR_PRE3 384
bool g_autosave=true;
uint8_t g_presetSel=1; // 1..3
/* ===== UI ===== */
volatile int16_t enc_delta=0;
bool btn_short=false, btn_lfalse, btn_dbl=false;
uint8_t menu_idx=0; bool edit=false;
const char* MENU[]={
// базовые
«Status»,«Volume»,«Balance»,«Filter»,«Path»,
// bypass
«Bypass IIR»,«Bypass FIR4»,«Bypass FIR2»,
// AutoMute
«AM Ch1»,«AM Ch2»,«AM RampGND»,«AM Time»,
// THD
«THD C2 L»,«THD C2 R»,«THD C3 L»,«THD C3 R»,
// интерфейс/PLL
«Polarity»,«DPLL BW»,«AutoClock»,«AutoInput»,
// пресеты
«Preset Load»,«Preset Save»,«Autosave»,
// прочее
«HardOnly»,«Reset»
};
const uint8_t MCNT = sizeof(MENU)/sizeof(MENU[0]);
/* =========================
I2C helpers
========================= */
bool wr(uint8_t r, uint8_t v){
Wire.beginTransmission(DAC_ADDR);
Wire.write®; Wire.write(v);
return (Wire.endTransmission()==0);
}
bool wr16(uint8_t addrLSB, uint16_t val){
return wr(addrLSB, (uint8_t)(val & 0xFF))
&& wr(addrLSB+1, (uint8_t)((val>>8)&0xFF));
}
bool rd(uint8_t r, uint8_t &v){
Wire.beginTransmission(DAC_ADDR); Wire.write®;
if(Wire.endTransmission(false)!=0) return false;
if(Wire.requestFrom((int)DAC_ADDR,1)!=1) return false;
v=Wire.read(); return true;
}
bool pingDAC(){ Wire.beginTransmission(DAC_ADDR); return (Wire.endTransmission()==0); }
/* =========================
ЧИП / СТАТУС
========================= */
void chipOn(){ pinMode(CHIP_EN_PIN,OUTPUT); digitalWrite(CHIP_EN_PIN,HIGH); }
void chipOff(){ digitalWrite(CHIP_EN_PIN,LOW); }
void chipReset(uint16_t ms){ chipOff(); delay(ms); chipOn(); delay(150); }
bool lockStatus(){ uint8_t s1=0; if(!rd(REG_S1,s1)) return false; return (s1&0x80)!=0; }
uint32_t fsFromS2(){
uint8_t s2=0; if(!rd(REG_S2,s2)) return 0; switch(s2&0x0F){
case 0: return 44100; case 1: return 48000; case 2: return 88200; case 3: return 96000;
case 4: return 176400; case 5: return 192000; case 6: return 384000; default: return 0; }
}
/* =========================
ЗАПИСИ В ЦАП (без mute)
========================= */
void volWriteLR(uint8_t vL,uint8_t vR){ wr(REG_VOL_L,vL); wr(REG_VOL_R,vR); }
void setFilter(uint8_t f){ wr(REG_FILTER, f & 0x07); }
void writePathConfig(){
uint8_t v=0;
if(g_bypIIR) v|=0x04;
if(g_bypFIR4) v|=0x02;
if(g_bypFIR2) v|=0x01;
wr(REG_PATH, v);
switch(g_path){
default:
case 0: wr(REG_SLOT_L,0x00); wr(REG_SLOT_R,0x01); break;
case 1: wr(REG_SLOT_L,0x00); wr(REG_SLOT_R,0x00); break;
case 2: wr(REG_SLOT_L,0x01); wr(REG_SLOT_R,0x01); break;
case 3: wr(REG_SLOT_L,0x01); wr(REG_SLOT_R,0x00); break;
}
}
void applySafe_VolBal(){
int16_t vL=g_volBase, vR=g_volBase;
if(g_balance>0) vL+=g_balance; else if(g_balance<0) vR+=(-g_balance);
if(vL<0)vL=0; if(vL>255)vL=255; if(vR<0)vR=0; if(vR>255)vR=255;
volWriteLR((uint8_t)vL,(uint8_t)vR);
}
void applySafe_Filter(){ setFilter(g_dfilt); }
void applySafe_Path(){ writePathConfig(); }
void applyAutoMute(){
uint8_t en = (g_amCh1?1:0) | (g_amCh2?2:0);
wr(REG_AM_ENABLE, en);
uint16_t t = ((g_amRampGND?1:0)<<11) | (g_amTime & 0x07FF);
wr16(REG_AM_TIME_L, t);
}
// THD: LSB->MSB, [15:0]=L, [31:16]=R
void applyTHD_C2(){
uint16_t wL=(uint16_t)g_c2_L, wR=(uint16_t)g_c2_R;
wr(REG_THD_C2_LSB+0,(uint8_t)(wL&0xFF));
wr(REG_THD_C2_LSB+1,(uint8_t)((wL>>8)&0xFF));
wr(REG_THD_C2_LSB+2,(uint8_t)(wR&0xFF));
wr(REG_THD_C2_LSB+3,(uint8_t)((wR>>8)&0xFF));
}
void applyTHD_C3(){
uint16_t wL=(uint16_t)g_c3_L, wR=(uint16_t)g_c3_R;
wr(REG_THD_C3_LSB+0,(uint8_t)(wL&0xFF));
wr(REG_THD_C3_LSB+1,(uint8_t)((wL>>8)&0xFF));
wr(REG_THD_C3_LSB+2,(uint8_t)(wR&0xFF));
wr(REG_THD_C3_LSB+3,(uint8_t)((wR>>8)&0xFF));
}
/* =========================
ОПАСНЫЕ — ПЛАНИРОВЩИК
========================= */
void queueApply(bool softR){ if(!g_hardOnly) pend.softReset |= softR; }
void runPending(){
static bool busy=false;
if(g_hardOnly){ pend=Pending(); busy=false; return; }
if(busy) return;
if(!(pend.polarity||pend.pll||pend.autoclk||pend.autoin||pend.softReset)) return;
busy=true;
if(pend.softReset){ wr(REG_SYS,0x80); delay(6); }
wr(REG_SYS,0x02); // аналог включен
if(pend.polarity) wr(REG_IFACE_POL,(g_pol&0x03));
if(pend.autoclk) wr(REG_CLKCFG, g_autoClk?0x40:0x00);
if(pend.pll) wr(REG_PLL_BW, g_pll);
if(pend.autoin) wr(REG_AUTO_INPUT,g_autoIn?0x01:0x00);
writePathConfig();
applySafe_VolBal();
setFilter(g_dfilt);
delay(g_settle);
pend=Pending();
busy=false;
}
/* =========================
EEPROM / PRESETS
========================= */
void cfgFromGlobals(Config &c){
c.version=CFG_VERSION;
c.volBase=g_volBase; c.balance=g_balance; c.dfilt=g_dfilt; c.path=g_path;
c.bypIIR=g_bypIIR; c.bypFIR4=g_bypFIR4; c.bypFIR2=g_bypFIR2;
c.pol=g_pol; c.pll=g_pll; c.autoClk=g_autoClk; c.autoIn=g_autoIn;
c.hardg_hardOnly; c.settle=g_settle;
c.amCh1=g_amCh1; c.amCh2=g_amCh2; c.amRamp=g_amRampGND; c.amTime=g_amTime;
c.c2L=g_c2_L; c.c2R=g_c2_R; c.c3L=g_c3_L; c.c3R=g_c3_R;
c.autosave=g_autosave?1:0;
c.crc=0;
c.crc=crc16((uint8_t*)&c, sizeof(Config)-2);
}
void globalsFromCfg(const Config &c){
g_volBase=c.volBase; g_balance=c.balance; g_dfilt=c.dfilt; g_path=c.path;
g_bypIIR=c.bypIIR; g_bypFIR4=c.bypFIR4; g_bypFIR2=c.bypFIR2;
g_pol=c.pol; g_pll=c.pll; g_autoClk=c.autoClk; g_autoIn=c.autoIn;
g_hardc.hardOnly; g_settle=c.settle;
g_amCh1=c.amCh1; g_amCh2=c.amCh2; g_amRampGND=c.amRamp; g_amTime=c.amTime;
g_c2_L=c.c2L; g_c2_R=c.c2R; g_c3_L=c.c3L; g_c3_R=c.c3R;
g_autosave=(c.autosave!=0);
}
bool eepromWrite(int base, const Config &c){
Config tmp=c;
for(unsigned i=0;i<sizeof(Config);++i) EEPROM.update(base+i, ((uint8_t*)&tmp)[i]);
return true;
}
bool eepromRead(int base, Config &c){
for(unsigned i=0;i<sizeof(Config);++i) ((uint8_t*)&c)[i]=EEPROM.read(base+i);
if(c.version!=CFG_VERSION) return false;
uint16_t calc=crc16((uint8_t*)&c, sizeof(Config)-2);
return (calc==c.crc);
}
void loadSlot(uint8_t slot){
int addr = (slot==1)?EE_ADDR_PRE1: (slot==2)?EE_ADDR_PRE2: EE_ADDR_PRE3;
Config c; if(eepromRead(addr,c)){ globalsFromCfg©; }
}
void saveSlot(uint8_t slot){
int addr = (slot==1)?EE_ADDR_PRE1: (slot==2)?EE_ADDR_PRE2: EE_ADDR_PRE3;
Config c; cfgFromGlobals©; eepromWrite(addr,c);
}
void autosaveWrite(){
if(!g_autosave) return;
static uint32_t last=0;
uint32_t now=millis();
if(now-last<1500) return; // экономим ресурс EEPROM
last=now;
Config c; cfgFromGlobals©; eepromWrite(EE_ADDR_LAST,c);
}
void autosaveLoadOnBoot(){
Config c; if(eepromRead(EE_ADDR_LAST,c)){ globalsFromCfg©; }
}
/* =========================
ЭКРАН / МЕНЮ
========================= */
void showStatus(){
lcd.clear();
lcd.print(lockStatus()? «Lock:Y »:«Lock:N „);
lcd.print(“P:»); lcd.print(g_pol);
lcd.setCursor(0,1);
uint32_t fs=fsFromS2();
if(fs){ lcd.print(«Fs:»); lcd.print(fs/1000); lcd.print(«k „); }
else { lcd.print(“Fs:? „); }
}
void drawMenu(){
lcd.clear();
lcd.print(“[»); lcd.print(menu_idx+1); lcd.print("/"); lcd.print(MCNT); lcd.print("] ");
lcd.print(MENU[menu_idx]);
lcd.setCursor(0,1);
switch(menu_idx){
case 0: lcd.print(«Status»); break;
case 1: lcd.print(«Vol: „); lcd.print(g_volBase); break;
case 2: lcd.print(“Bal: „); lcd.print((int)g_balance); break;
case 3: lcd.print(“Filt: „); lcd.print(g_dfilt); break;
case 4: lcd.print(“Path: „); lcd.print(g_path); break;
case 5: lcd.print(“Byp IIR: „); lcd.print(g_bypIIR ?“Y»:«N»); break;
case 6: lcd.print(«Byp FIR4: „); lcd.print(g_bypFIR4?“Y»:«N»); break;
case 7: lcd.print(«Byp FIR2: „); lcd.print(g_bypFIR2?“Y»:«N»); break;
case 8: lcd.print(«AM Ch1: „); lcd.print(g_amCh1?“ON»:«OFF»); break;
case 9: lcd.print(«AM Ch2: „); lcd.print(g_amCh2?“ON»:«OFF»); break;
case 10: lcd.print(«AM RampGND: „); lcd.print(g_amRampGND?“Y»:«N»); break;
case 11: lcd.print(«AM Time: „); lcd.print(g_amTime); break;
case 12: lcd.print(“THD C2 L: „); lcd.print(g_c2_L); break;
case 13: lcd.print(“THD C2 R: „); lcd.print(g_c2_R); break;
case 14: lcd.print(“THD C3 L: „); lcd.print(g_c3_L); break;
case 15: lcd.print(“THD C3 R: „); lcd.print(g_c3_R); break;
case 16: lcd.print(“Pol: „); lcd.print(g_pol); break;
case 17: lcd.print(“DPLL:0x»); lcd.print(g_pll,HEX); break;
case 18: lcd.print(«AutoClk: „); lcd.print(g_autoClk?“ON»:«OFF»); break;
case 19: lcd.print(«AutoIn: „); lcd.print(g_autoIn ?“ON»:«OFF»); break;
case 20: lcd.print(«Load P»); lcd.print(g_presetSel); break;
case 21: lcd.print(«Save P»); lcd.print(g_presetSel); break;
case 22: lcd.print(«Autosave: „); lcd.print(g_autosave?“ON»:«OFF»); break;
case 23: lcd.print(«HardOnly: „);lcd.print(g_hardOnly?“YES»:«NO»); break;
case 24: lcd.print(«Reset CHIP_EN»); break;
}
}
/* =========================
ВВОД / ОБРАБОТКА
========================= */
void encISR(){
static uint8_t prev=0;
uint8_t cur=(digitalRead(ENC_A)<<1)|digitalRead(ENC_B);
static const int8_t tab[4][4]={{0,-1,+1,0},{+1,0,0,-1},{-1,0,0,+1},{0,+1,-1,0}};
enc_delta+=tab[prev][cur]; prev=cur;
}
void pollBtn(){
static uint32_t t0=0,down=0; static bool last=false;
static uint32_t lastUp=0; static bool clickArmed=false;
const uint16_t DEB=20,L900, DBL=350;
bool raw=(digitalRead(BTN)==LOW); uint32_t now=millis();
if(raw!=last && (now-t0)>DEB){
t0=now; last=raw;
if(raw){ down=now; }
else{
uint32_t dur=now-down;
if(dur<600){
if(clickArmed && (now-lastUp)<DBL){ btn_dbl=true; clickArmed=false; }
else { btn_short=true; clickArmed=true; lastUp=now; }
}else if(dur>=LONG){ btn_ltrue; clickArmed=false; }
}
}
if(clickArmed && (now-lastUp)>=DBL) clickArmed=false;
}
void onChangeSafe(uint8_t code){
switch(code){
case 1: applySafe_VolBal(); autosaveWrite(); break;
case 2: applySafe_Filter(); autosaveWrite(); break;
case 3: applySafe_Path(); autosaveWrite(); break;
case 4: applyAutoMute(); autosaveWrite(); break;
case 5: applyTHD_C2(); autosaveWrite(); break;
case 6: applyTHD_C3(); autosaveWrite(); break;
}
}
void onChangeDanger(uint8_t what){
if(g_hardOnly) return;
if(what==10) pend.polarity=true;
if(what==11) pend.pll=true;
if(what==12) pend.autoclk=true;
if(what==13) pend.autoin=true;
queueApply(false);
autosaveWrite();
}
void doAction(uint8_t idx){
if(idx==0){ showStatus(); return; }
if(idx==20){ // Load preset
loadSlot(g_presetSel);
writePathConfig(); setFilter(g_dfilt); applySafe_VolBal();
applyAutoMute(); applyTHD_C2(); applyTHD_C3();
drawMenu(); return;
}
if(idx==21){ // Save preset
saveSlot(g_presetSel);
drawMenu(); return;
}
if(idx==24){
chipReset();
wr(REG_SYS,0x02);
writePathConfig(); setFilter(g_dfilt); applySafe_VolBal();
applyAutoMute(); applyTHD_C2(); applyTHD_C3();
drawMenu(); return;
}
}
/* =========================
SETUP / LOOP
========================= */
void setup(){
chipOn();
Wire.begin();
lcd.init(); lcd.backlight();
pinMode(ENC_A,INPUT_PULLUP); pinMode(ENC_B,INPUT_PULLUP); pinMode(BTN,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENC_A), encISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_B), encISR, CHANGE);
lcd.print(«ES9039Q2M +EEPROM»);
lcd.setCursor(0,1);
if(pingDAC()){ uint8_t id=0; rd(REG_ID,id); lcd.print(«I2C OK ID:»); lcd.print(id,HEX); }
else lcd.print(«I2C ERR»);
// загрузить автосохранённое состояние (если валидно)
autosaveLoadOnBoot();
// аналог ON, применить конфиг в железо
wr(REG_SYS,0x02);
writePathConfig(); setFilter(g_dfilt); applySafe_VolBal();
applyAutoMute(); applyTHD_C2(); applyTHD_C3();
drawMenu();
}
void loop(){
static uint32_t tPoll=0;
static uint32_t tStat=0;
if(millis()-tPoll>5){ pollBtn(); tPoll=millis(); }
int16_t d=enc_delta; enc_delta=0;
// редактирование
if(edit && d){
switch(menu_idx){
// базовые
case 1:{ int n=g_volBase+(d>0?+1:-1); if(n<0)n=0; if(n>255)n=255; g_volBase=n; onChangeSafe(1);}break;
case 2:{ int n=g_balance+(d>0?+1:-1); if(n<-24)n=-24; if(n>24)n=24; g_balance=n; onChangeSafe(1);}break;
case 3:{ int n=g_dfilt +(d>0?+1:-1); if(n<0)n=7; if(n>7)n=0; g_dfilt=n; onChangeSafe(2);}break;
case 4:{ int n=g_path +(d>0?+1:-1); if(n<0)n=3; if(n>3)n=0; g_path =n; onChangeSafe(3);}break;
// bypass
case 5: g_bypIIR = !g_bypIIR; onChangeSafe(3); break;
case 6: g_bypFIR4 = !g_bypFIR4; onChangeSafe(3); break;
case 7: g_bypFIR2 = !g_bypFIR2; onChangeSafe(3); break;
// AutoMute
case 8: g_amCh1 = !g_amCh1; onChangeSafe(4); break;
case 9: g_amCh2 = !g_amCh2; onChangeSafe(4); break;
case 10: g_amRampGND = !g_amRampGND; onChangeSafe(4); break;
case 11:{ int n=g_amTime+(d>0?+1:-1); if(n<0)n=0; if(n>0x07FF)n=0x07FF; g_amTime=(uint16_t)n; onChangeSafe(4);}break;
// THD
case 12:{ int n=g_c2_L+(d>0?+1:-1); if(n<-32768)n=-32768; if(n>32767)n=32767; g_c2_L=(int16_t)n; onChangeSafe(5);}break;
case 13:{ int n=g_c2_R+(d>0?+1:-1); if(n<-32768)n=-32768; if(n>32767)n=32767; g_c2_R=(int16_t)n; onChangeSafe(5);}break;
case 14:{ int n=g_c3_L+(d>0?+1:-1); if(n<-32768)n=-32768; if(n>32767)n=32767; g_c3_L=(int16_t)n; onChangeSafe(6);}break;
case 15:{ int n=g_c3_R+(d>0?+1:-1); if(n<-32768)n=-32768; if(n>32767)n=32767; g_c3_R=(int16_t)n; onChangeSafe(6);}break;
// интерфейс/PLL
case 16:{ int n=g_pol+(d>0?+1:-1); if(n<0)n=3; if(n>3)n=0; g_pol=n; onChangeDanger(10);}break;
case 17:{ int n=g_pll+(d>0?+1:-1); if(n<0x01)n=0x01; if(n>0x10)n=0x10; g_pll=n; onChangeDanger(11);}break;
case 18: g_autoClk=!g_autoClk; onChangeDanger(12); break;
case 19: g_autoIn =!g_autoIn; onChangeDanger(13); break;
// пресеты
case 20:{ int n=g_presetSel+(d>0?+1:-1); if(n<1)n=1; if(n>3)n=3; g_presetSel=n; }break;
case 21:{ int n=g_presetSel+(d>0?+1:-1); if(n<1)n=1; if(n>3)n=3; g_presetSel=n; }break;
case 22: g_autosave=!g_autosave; autosaveWrite(); break;
case 23: g_hard!g_hardOnly; autosaveWrite(); break;
}
drawMenu();
}
// навигация
if(!edit && d){
int n = (int)menu_idx + (d>0?+1:-1);
if(n<0) n=MCNT-1; if(n>=MCNT) n=0;
menu_idx=(uint8_t)n; drawMenu();
}
// клики
if(btn_short){ btn_short=false; if(menu_idx==0) showStatus(); else doAction(menu_idx); }
if(btn_dbl){ btn_dbl=false; if(!g_hardOnly){ pend.softReset=false; runPending(); showStatus(); } }
if(btn_long){ btn_lfalse; edit=!edit; lcd.setCursor(15,0); lcd.print(edit?"*":" "); }
// фон
runPending();
if(menu_idx==0 && millis()-tStat>800){ showStatus(); tStat=millis(); }
}
Естьполный скетч, старт — сразу Volume; кнопка — второе меню с Path, Bypass IIR/FIR2/FIR4, ASRC On/Off, THD C2, THD C3, Save/Load/Reset. THD — сразу применяется при вращении; одно значение пишется в оба канала
Если кому-то нужно!
ASRC On/Off = емнип на 9039 ASRC не отключаемый. он работает всегда просто на отдельных сочетаниях коэфф круглые. на забугорном diyaudio (опять емнип) разбирали этот момент .
При отключении если клок не стабильный я слышу мелкий треск, для эксперимента проверял при вкючении все чисто
Вообще от нестабильности клока треска не будет если все тактируется от одного генератора.
Есть один товарищ довольно хорошо известный, оно тут зарегистрирован но заходил давно, так вот он на 9038 pro потратил несколько лет, при этом собранный цап он без сожаления продал. У 9038 pro как он говорил ASRC как раз не отключается. Если ничего не попутал и не забыл. Мануал у него есть полный, а мануал на 9039 от полного мануала составляет процентов 60. 9039 и подобные раскритиковал.
Ну это как с женщиной, каждому своя! То что es9039q2m хорошо справляется со звуком, а её инженеры многое проработали это факт и на практике проверил, на мой слух при отключении asrc немного сцена становится шире и это только если сигнал идёт достаточно чистый, я не про формат, а про формирование этого i2s сигнала.