dac-usb-bluetooth/control-panel-for-for-dac/control-panel-for-for-dac.ino

412 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 OPTOCOUPLE_USB 10
#define OPTOCOUPLE_COAX 11
#define OPTOCOUPLE_BT 12
// Входы звукового сигнала для анализатора спектра
#define AUDIO_IN_L 0
#define AUDIO_IN_R 1
// Кнопки
#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 30 // порог тишины (ниже него отображения на матрице не будет)
#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 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();
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);
// Входы индикаторов с USB ЦАП
pinMode(OPTOCOUPLE_USB, INPUT_PULLUP);
pinMode(OPTOCOUPLE_COAX, INPUT_PULLUP);
// Вход индикатора с BlueTooth адаптера
pinMode(OPTOCOUPLE_BT, INPUT_PULLUP);
// 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);
}
// Получаем тип подключенного входа к USB ЦАПу
String setUSBinput() {
if (!digitalRead(OPTOCOUPLE_USB) && usb) {
return "input: USB ";
} else if (!digitalRead(OPTOCOUPLE_COAX) && usb) {
return "input: Coaxial";
} else {
return "input: Off ";
}
}
// Получаем статус подключения устройства к bluetooth
String setBTstatus() {
if (!digitalRead(OPTOCOUPLE_BT) && bluetooth) {
return " connected";
} else {
return " ";
}
}
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 3:
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 | S/PDIF");
lcdService.setCursor(0,1);
lcdService.print(setUSBinput());
// actionUSB();
}
if(bluetooth){
lcdService.print("Bluetooth");
lcdService.setCursor(0,1);
lcdService.print(setBTstatus());
// 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));
}
// 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
}