// по материалам: //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 #define RL 1 // RL - горизонт, вертикаль 0...1 // Выходы для управления реле (вкл.вкл ЦАП/Блютуз) #define RELAY_POWER_USB 7 #define RELAY_POWER_BT 6 #define RELAY_POWER_MP3 10 #define RELAY_OUT_BT_LEFT 8 #define RELAY_OUT_BT_RIGHT 9 #define RELAY_OUT_MP3_LEFT 11 #define RELAY_OUT_MP3_RIGHT 12 // Кнопки #define BTN_MENU 2 // Кнопка меню #define BTN_EXECUTE 3 // Кнопка подтверждения #define INIT_ADDR 1023 // номер резервной ячейки #define INIT_KEY 1 // ключ первого запуска // ---------------- НАСТРОЙКИ ---------------- #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); 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() { lcdVuMeter.init(); lcdVuMeter.backlight(); lcdVuMeter.clear(); lcdVuMeter.setCursor(0, 0); lcdVuMeter.print("R"); // lcdVuMeter.createChar(4,znak_r); lcdVuMeter.setCursor(0, 1); lcdVuMeter.print("L"); lcdService.init(); lcdService.backlight(); lcdService.clear(); // lcdService.setCursor(0, 0); // lcdService.print("Menu"); // Кнопка управления (переключения) pinMode(BTN_MENU, INPUT_PULLUP); pinMode(BTN_EXECUTE, INPUT_PULLUP); // Выходы для управления реле (вкл.вкл ЦАП/Блютуз) pinMode(RELAY_POWER_USB, OUTPUT); pinMode(RELAY_POWER_BT, OUTPUT); pinMode(RELAY_POWER_MP3, OUTPUT); pinMode(RELAY_OUT_BT_LEFT, OUTPUT); pinMode(RELAY_OUT_BT_RIGHT, OUTPUT); pinMode(RELAY_OUT_MP3_LEFT, OUTPUT); pinMode(RELAY_OUT_MP3_RIGHT, OUTPUT); // lcdVuMeter.createChar(5,znak_l); // lcdVuMeter.begin(16, 2);// lcdVuMeter 16X2 // analogReference(INTERNAL); // если очень маленький уровень сигнала pinMode(A0,INPUT);// A0 - аналоговый вход R pinMode(A1,INPUT);// A1 - аналоговый вход L // первый запуск и установка занчений переменных в память if (EEPROM.read(INIT_ADDR) != INIT_KEY) { EEPROM.write(INIT_ADDR, INIT_KEY); usb = true; bluetooth = false; mp3 = false; EEPROM.put(10, usb); EEPROM.put(12, bluetooth); EEPROM.put(14, mp3); } else { usb = EEPROM.read(10); bluetooth = EEPROM.read(12); mp3 = EEPROM.read(14); } // поднимаем частоту опроса аналогового порта до 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); digitalWrite(RELAY_POWER_USB,HIGH); delay(500); digitalWrite(RELAY_OUT_BT_LEFT,HIGH); digitalWrite(RELAY_OUT_BT_RIGHT,HIGH); digitalWrite(RELAY_OUT_MP3_LEFT,HIGH); digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH); } if (bluetooth) { digitalWrite(RELAY_POWER_USB,HIGH); digitalWrite(RELAY_POWER_MP3,HIGH); delay(500); digitalWrite(RELAY_OUT_MP3_LEFT,HIGH); digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH); delay(1000); digitalWrite(RELAY_OUT_BT_LEFT,LOW); digitalWrite(RELAY_OUT_BT_RIGHT,LOW); digitalWrite(RELAY_POWER_BT,LOW); } if (mp3) { } lcdChars(); // подхватить коды полосочек updateMenu(); } 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"; } void updateMenu() { switch (menu) { case 0: menu = 1; break; case 1: lcdService.clear(); lcdService.print(">USB DAC"); lcdService.setCursor(2, 1); if(usb){ lcdService.print("On"); }else{ lcdService.print("Off"); } break; case 2: lcdService.clear(); lcdService.print(">Bluetooth"); lcdService.setCursor(2, 1); if(bluetooth){ lcdService.print("On"); }else{ lcdService.print("Off"); } break; case 3: lcdService.clear(); lcdService.print(">MP3 module"); lcdService.setCursor(2, 1); if(mp3){ lcdService.print("On"); }else{ lcdService.print("Off"); } break; case 4: menu = 0; menuShow = false; lcdService.clear(); break; } } void executeMenuAction() { switch (menu) { case 1: actionUSB(); break; case 2: actionBT(); break; case 3: actionMP3(); break; } EEPROM.put(10, usb); EEPROM.put(12, bluetooth); EEPROM.put(14, mp3); } void actionUSB() { // lcdService.clear(); lcdService.setCursor(2,1); lcdService.print("On "); digitalWrite(RELAY_POWER_BT,HIGH); digitalWrite(RELAY_POWER_MP3,HIGH); digitalWrite(RELAY_POWER_USB,HIGH); delay(500); digitalWrite(RELAY_OUT_BT_LEFT,HIGH); digitalWrite(RELAY_OUT_BT_RIGHT,HIGH); digitalWrite(RELAY_OUT_MP3_LEFT,HIGH); digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH); usb = true; bluetooth = false; mp3 = false; // delay(1500); } void actionBT() { // lcdService.clear(); lcdService.setCursor(2,1); lcdService.print("On "); digitalWrite(RELAY_POWER_USB,HIGH); digitalWrite(RELAY_POWER_MP3,HIGH); delay(500); digitalWrite(RELAY_OUT_MP3_LEFT,HIGH); digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH); delay(1000); digitalWrite(RELAY_OUT_BT_LEFT,LOW); digitalWrite(RELAY_OUT_BT_RIGHT,LOW); digitalWrite(RELAY_POWER_BT,LOW); usb = false; bluetooth = true; mp3 = false; // delay(1500); } void actionMP3() { // lcdService.clear(); lcdService.setCursor(2,1); lcdService.print("On "); digitalWrite(RELAY_POWER_USB,HIGH); digitalWrite(RELAY_POWER_BT,HIGH); delay(500); digitalWrite(RELAY_OUT_BT_LEFT,HIGH); digitalWrite(RELAY_OUT_BT_RIGHT,HIGH); delay(1000); digitalWrite(RELAY_OUT_MP3_LEFT,LOW); digitalWrite(RELAY_OUT_MP3_RIGHT,LOW); digitalWrite(RELAY_POWER_MP3,LOW); usb = false; bluetooth = false; mp3 = true; delay(1500); } uint32_t btnTimer = 0; void loop() { lcdService.setCursor(0,0); // Выводим на экран информацию какой модуль включен if(!menuShow){ if(usb){ lcdService.print("USB DAC"); lcdService.setCursor(0,1); lcdService.print(setUSBinput()); // actionUSB(); } if(bluetooth){ lcdService.print("Bluetooth"); // actionBT(); } if(mp3){ lcdService.print("MP3 module"); // actionMP3(); } } // bool btnMenuState = digitalRead(BTN_MENU); // bool btnExecuteState = digitalRead(BTN_EXECUTE); if (!digitalRead(BTN_MENU)){ menuShow = true; menu ++; updateMenu(); delay(100); while (!digitalRead(BTN_MENU)); } if (!digitalRead(BTN_EXECUTE)){ executeMenuAction(); updateMenu(); delay(100); while (!digitalRead(BTN_EXECUTE)); } // Тут будет код обработки данных от USB DAC для индикации режима его работы // if (digitalRead(8) && usb) { // lcdService.setCursor(0,1); // lcdService.print("Coaxial"); // } else if (digitalRead(9) && usb) { // lcdService.setCursor(0,1); // lcdService.print("Optical"); // } else if (digitalRead(10) && usb) { // lcdService.setCursor(0,1); // lcdService.print("Audio "); // } else { // lcdService.setCursor(0,1); // lcdService.print(" "); // } // lcdService.setCursor(11,1); // lcdService.print(digitalRead(8), digitalRead(9)); if (GAIN_CONTROL) gain = map(analogRead(POT_PIN), 0, 1023, 0, 150); 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 }