diff --git a/control-panel-for-for-dac/control-panel-for-for-dac.ino b/control-panel-for-for-dac/control-panel-for-for-dac.ino index f712029..2cf2749 100644 --- a/control-panel-for-for-dac/control-panel-for-for-dac.ino +++ b/control-panel-for-for-dac/control-panel-for-for-dac.ino @@ -2,6 +2,7 @@ //https://www.instructables.com/Simple-LCD-MENU-Using-Arduino/ // https://alexgyver.ru/lessons/ // https://www.hackster.io/mircemk/diy-arduino-vfd-display-20x2-vu-volume-unit-meter-37898f +// https://github.com/AlexGyver/FHTSpectrumAnalyzer/blob/master/Firmware/spertrum1602/spertrum1602.ino #define GAIN 5 // усиление 0...50 #define STEP 20 // плавность полос 0...20 @@ -21,27 +22,65 @@ #define INIT_ADDR 1023 // номер резервной ячейки #define INIT_KEY 1 // ключ первого запуска -//#include +// ---------------- НАСТРОЙКИ ---------------- +#define DRIVER_VERSION 0 // 0 - маркировка драйвера кончается на 4АТ, 1 - на 4Т +#define GAIN_CONTROL 0 // ручная настройка потенциометром на громкость (1 - вкл, 0 - выкл) + +#define AUTO_GAIN 0 // автонастройка по громкости (экспериментальная функция) +#define VOL_THR 35 // порог тишины (ниже него отображения на матрице не будет) + +#define LOW_PASS 30 // нижний порог чувствительности шумов (нет скачков при отсутствии звука) +#define DEF_GAIN 80 // максимальный порог по умолчанию (при GAIN_CONTROL игнорируется) + +#define FHT_N 256 // ширина спектра х2 +// вручную забитый массив тонов, сначала плавно, потом круче +byte posOffset[16] = {2, 3, 4, 6, 8, 10, 12, 14, 16, 20, 25, 30, 35, 60, 80, 100}; +// ---------------- НАСТРОЙКИ ---------------- + +// ---------------------- ПИНЫ ---------------------- +#define AUDIO_IN_L 0 // пин, куда подключен звук +#define AUDIO_IN_R 1 // пин, куда подключен звук +#define POT_PIN 7 // пин потенциометра настройки +// ---------------------- ПИНЫ ---------------------- + + +// --------------- БИБЛИОТЕКИ --------------- +#define LOG_OUT 1 +#include // преобразование Хартли #include #include +#define printByte(args) write(args); +double prevVolts = 100.0; +// --------------- БИБЛИОТЕКИ --------------- +#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) + #include // LiquidCrystal lcdVuMeter(2, 3, 4, 5, 6, 7);// RS,E,D4,D5,D6,D7 LiquidCrystal_I2C lcdVuMeter(0x27, 16, 2); LiquidCrystal_I2C lcdService(0x26, 16, 2); -byte z,z0,z1,w,www=1; -int ur,ul,urr,ull,urrr,ulll; -int x,i,u_maxr,u_maxl; -int u_l0[20],u_r0[20]; bool usb, bluetooth, mp3; int menu = 1; bool menuShow = false; +// ------------------------------------- ПОЛОСОЧКИ ------------------------------------- +byte v1[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111}; +byte v2[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111}; +byte v3[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111}; +byte v4[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111}; +byte v5[8] = {0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111}; +byte v6[8] = {0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111}; +byte v7[8] = {0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111}; +byte v8[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111}; +// ------------------------------------- ПОЛОСОЧКИ ------------------------------------- +byte gain = DEF_GAIN; // усиление по умолчанию +unsigned long gainTimer; +byte maxValue, maxValue_f; +float k = 0.1; + void setup() { - pinMode(8,INPUT_PULLUP); - byte znak_r[8]={0b00000,0b00000,0b11111,0b00101,0b00101,0b11010,0b00000,0b00000}; - byte znak_l[8]={0b00000,0b00000,0b11111,0b10000,0b10000,0b10000,0b00000,0b00000}; lcdVuMeter.init(); lcdVuMeter.backlight(); lcdVuMeter.clear(); @@ -89,8 +128,20 @@ void setup() { bluetooth = EEPROM.read(12); mp3 = EEPROM.read(14); } - - w = EEPROM.read(0); + + // поднимаем частоту опроса аналогового порта до 38.4 кГц, по теореме + // Котельникова (Найквиста) частота дискретизации будет 19 кГц + // http://yaab-arduino.blogspot.ru/2015/02/fast-sampling-from-analog-input.html + sbi(ADCSRA, ADPS2); + cbi(ADCSRA, ADPS1); + sbi(ADCSRA, ADPS0); + + // для увеличения точности уменьшаем опорное напряжение, + // выставив EXTERNAL и подключив Aref к выходу 3.3V на плате через делитель + // GND ---[2х10 кОм] --- REF --- [10 кОм] --- 3V3 + analogReference(EXTERNAL); + + if (usb) { digitalWrite(RELAY_POWER_BT,HIGH); digitalWrite(RELAY_POWER_MP3,HIGH); @@ -115,82 +166,22 @@ void setup() { } if (mp3) { } + lcdChars(); // подхватить коды полосочек updateMenu(); } - -void vu_metter() { -// if(digitalRead(8)==LOW){w++;www=1;if(w>4){w=0;}delay(200);EEPROM.update(0,w);} - - if(w==0&&www==1){www=0; - byte a1[8] = {0b10101,0b10101,0b10101,0b10101,0b10101,0b10101,0b10101,0b10101}; - byte a2[8] = {0b10100,0b10100,0b10100,0b10100,0b10100,0b10100,0b10100,0b10100}; - byte a3[8] = {0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b10000}; - byte a4[8] = {0b00011,0b00011,0b00011,0b0011,0b0011,0b0011,0b0011,0b0011};// - lcdVuMeter.createChar(0,a1);lcdVuMeter.createChar(1,a2);lcdVuMeter.createChar(2,a3);lcdVuMeter.createChar(6,a4);} - if(w==1&&www==1){www=0; - 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}; - byte a4[8] = {0b00000,0b0011,0b0011,0b0011,0b0011,0b0011,0b0011,0b00000};// - lcdVuMeter.createChar(0,a1);lcdVuMeter.createChar(1,a2);lcdVuMeter.createChar(2,a3);lcdVuMeter.createChar(6,a4);} - if(w==2&&www==1){www=0; - byte a1[8] = {0b10101,0b10101,0b10101,0b00000,0b00000,0b10101,0b10101,0b10101}; - byte a2[8] = {0b10100,0b10100,0b10100,0b00000,0b00000,0b10100,0b10100,0b10100}; - byte a3[8] = {0b10000,0b10000,0b10000,0b00000,0b00000,0b10000,0b10000,0b10000}; - byte a4[8] = {0b0011,0b0011,0b0011,0b0011,0b0011,0b0011,0b0011,0b0011}; - lcdVuMeter.createChar(0,a1);lcdVuMeter.createChar(1,a2);lcdVuMeter.createChar(2,a3);lcdVuMeter.createChar(6,a4);} - if(w==3&&www==1){www=0; - byte a1[8] = {0b00000,0b10101,0b10101,0b00000,0b00000,0b10101,0b10101,0b00000}; - byte a2[8] = {0b00000,0b10100,0b10100,0b00000,0b00000,0b10100,0b10100,0b00000}; - byte a3[8] = {0b00000,0b10000,0b10000,0b00000,0b00000,0b10000,0b10000,0b00000}; - byte a4[8] = {0b00000,0b0011,0b0011,0b0011,0b0011,0b0011,0b0011,0b00000}; - lcdVuMeter.createChar(0,a1);lcdVuMeter.createChar(1,a2);lcdVuMeter.createChar(2,a3);lcdVuMeter.createChar(6,a4);} - if(w==4&&www==1){www=0; - byte a1[8] = {0b00000,0b00000,0b10101,0b10101,0b10101,0b10101,0b00000,0b00000}; - byte a2[8] = {0b00000,0b00000,0b10100,0b10100,0b10100,0b10100,0b00000,0b00000}; - byte a3[8] = {0b00000,0b00000,0b10000,0b10000,0b10000,0b10000,0b00000,0b00000}; - byte a4[8] = {0b00000,0b00000,0b0011,0b0011,0b0011,0b0011,0b00000,0b00000}; - lcdVuMeter.createChar(0,a1);lcdVuMeter.createChar(1,a2);lcdVuMeter.createChar(2,a3);lcdVuMeter.createChar(6,a4);} - - urr = log(analogRead(0))*GAIN;if(urr>41){urr=41;} - ull = log(analogRead(1))*GAIN;if(ull>41){ull=41;} - - if(RL==0){lcdVuMeter.setCursor(0,0);lcdVuMeter.write((uint8_t)4);lcdVuMeter.setCursor(0,1);lcdVuMeter.write((uint8_t)5);} - if(RL==1){lcdVuMeter.setCursor(0,1);lcdVuMeter.print("L");lcdVuMeter.setCursor(0,0);lcdVuMeter.print("R");} - - if(urr2){z1=0;z0++;} - if(z1==1){lcdVuMeter.setCursor(z0+1,1);lcdVuMeter.write((uint8_t)0);lcdVuMeter.setCursor(z0+2,1);if(ur<39){lcdVuMeter.print(" ");}}} - if(z1==3){lcdVuMeter.setCursor(z0+1,1);lcdVuMeter.write((uint8_t)1);} - if(z1==2){lcdVuMeter.setCursor(z0+1,1);lcdVuMeter.write((uint8_t)2);} - - if(ull2){z1=0;z0++;} - if(z1==1){lcdVuMeter.setCursor(z0+1,0);lcdVuMeter.write((uint8_t)0);lcdVuMeter.setCursor(z0+2,0);if(ul<39){lcdVuMeter.print(" ");}}} - if(z1==3){lcdVuMeter.setCursor(z0+1,0);lcdVuMeter.write((uint8_t)1);} - if(z1==2){lcdVuMeter.setCursor(z0+1,0);lcdVuMeter.write((uint8_t)2);} - -////////////////////////////////////////////////////////// - -i++;if(i<19){u_l0[i]=abs(ull);u_r0[i]=abs(urr);}else{i=1;} - if(i==18){u_maxr=0;u_maxl=0; - for(x=1;x<=15;x++){ - u_maxl=max(u_maxl,u_l0[x]); - u_maxr=max(u_maxr,u_r0[x]);}} - - if(u_maxl<=ull){u_maxl=ull+1;} - if(u_maxr<=urr){u_maxr=urr+1;} - - if(u_maxl2){lcdVuMeter.write((uint8_t)6);}lcdVuMeter.print(" "); - lcdVuMeter.setCursor(urrr/3+1,1);if(urrr/3>2){lcdVuMeter.write((uint8_t)6);}lcdVuMeter.print(" "); - delay(3); +void lcdChars() { + lcdVuMeter.createChar(0, v1); + lcdVuMeter.createChar(1, v2); + lcdVuMeter.createChar(2, v3); + lcdVuMeter.createChar(3, v4); + lcdVuMeter.createChar(4, v5); + lcdVuMeter.createChar(5, v6); + lcdVuMeter.createChar(6, v7); + lcdVuMeter.createChar(7, v8); } + String setUSBinput() { return "Input: USB"; } @@ -314,6 +305,7 @@ void actionMP3() { delay(1500); } + uint32_t btnTimer = 0; void loop() { @@ -368,6 +360,50 @@ void loop() { // } // lcdService.setCursor(11,1); // lcdService.print(digitalRead(8), digitalRead(9)); + if (GAIN_CONTROL) gain = map(analogRead(POT_PIN), 0, 1023, 0, 150); - vu_metter(); + analyzeAudio(); // функция FHT, забивает массив fht_log_out[] величинами по спектру + + for (int pos = 0; pos < 16; pos++) { // для окошек дисплея с 0 по 15 + // найти максимум из пачки тонов + if (fht_log_out[posOffset[pos]] > maxValue) maxValue = fht_log_out[posOffset[pos]]; + + lcdVuMeter.setCursor(pos, 0); + + // преобразовать значение величины спектра в диапазон 0..15 с учётом настроек + int posLevel = map(fht_log_out[posOffset[pos]], LOW_PASS, gain, 0, 15); + posLevel = constrain(posLevel, 0, 15); + + if (posLevel > 7) { // если значение больше 7 (значит нижний квадратик будет полный) + lcdVuMeter.printByte(posLevel - 8); // верхний квадратик залить тем что осталось + lcdVuMeter.setCursor(pos, 1); // перейти на нижний квадратик + lcdVuMeter.printByte(7); // залить его полностью + } else { // если значение меньше 8 + lcdVuMeter.print(" "); // верхний квадратик пустой + lcdVuMeter.setCursor(pos, 1); // нижний квадратик + lcdVuMeter.printByte(posLevel); // залить полосками + } + } + + if (AUTO_GAIN) { + maxValue_f = maxValue * k + maxValue_f * (1 - k); + if (millis() - gainTimer > 1500) { // каждые 1500 мс + // если максимальное значение больше порога, взять его как максимум для отображения + if (maxValue_f > VOL_THR) gain = maxValue_f; + + // если нет, то взять порог побольше, чтобы шумы вообще не проходили + else gain = 100; + gainTimer = millis(); + } + } +} +void analyzeAudio() { + for (int i = 0 ; i < FHT_N ; i++) { + int sample = (analogRead(AUDIO_IN_L) + analogRead(AUDIO_IN_R)) / 2; + fht_input[i] = sample; // put real data into bins + } + fht_window(); // window the data for better frequency response + fht_reorder(); // reorder the data before doing the fht + fht_run(); // process the data in the fht + fht_mag_log(); // take the output of the fht }