dac-usb-bluetooth/control-panel-for-for-dac/control-panel-for-for-dac.ino
Калинин Сергей Валерьевич 40de9299d1 Добавлен код анализатора спектра
2025-01-13 17:10:20 +03:00

410 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// по материалам:
//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 <FHT.h> // преобразование Хартли
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#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 <EEPROM.h>
// 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
}