Автоматический Коммутатор Аудио Сигналов На Arduino

p.p.c
Offline
Зарегистрирован: 28.05.2013

Всем привет, есть задача разработать автоматический коммутатор аудио сигналов (т.е. осуществляем переход на резерв в случае отсутсвия сигнала на основном канале), элементом коммутации служит реле, попробовал изготовить аналоговое устройство в лабораторных условиях всё работало нормально, а вот на реальном оборудовании возникли трудности, так как есть различные шумы на выходе.
Решил сделать всё на мк, но так как не имею опыта решил попросить помощи.
Планируемый алгоритм достаточно простой, мониторим уровень звука, если сигнал ниже заданного порога то, ждём 10-20 сек и даём команду перехода на резерв. Определить уровень можно попробовать на базе схемы VU meter (ссылка).

Подскажите пожалуйста как правильно отредактировать код, дабы получилось необходимое устройство.

maksim
Offline
Зарегистрирован: 12.02.2012

Ну так покажите код, который не заработал в реальных условиях.

p.p.c
Offline
Зарегистрирован: 28.05.2013

Устройство аналоговое, вообще не было мк. Сейчас хочу переделать на мк, но не знаю как это правильно сделать.

p.p.c
Offline
Зарегистрирован: 28.05.2013

Пока ждал, сам написл код, посмотрите, возможно можно как то оптимизировать?



int analogPin = 0;     // номер порта к с которого считываем показания по 1 каналу
int val = 0;           // переменная для хранения считываемого значения по 1 каналу
int analogPin1 = 1;     // номер порта к с которого считываем показания по 2 каналу
int val1 = 0;           // переменная для хранения считываемого значения по 2 каналу

int ledPin1 = 10;  //индикация сигнала
int ledPin2 = 9;   //индикация сигнала
int ledPin3 = 8;  //индикация аварии
int count = 0; //счётчик для 1-го канала

int ledPin4 = 6;  //индикация сигнала
int ledPin5 = 7;  //индикация сигнала
int ledPin6 = 5;  //индикация аварии
int count1 = 0; //счётчик для 2-го канала


void setup()
{
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(ledPin4, OUTPUT);
  pinMode(ledPin5, OUTPUT);
  pinMode(ledPin6, OUTPUT);
}

void loop()
{
  //канал 1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  val = analogRead(analogPin);		// считываем значение
  digitalWrite(ledPin1, HIGH);		//устанавливаем начальное значение светодиодов
  digitalWrite(ledPin2, HIGH);		//устанавливаем начальное значение светодиодов
  digitalWrite(ledPin3, HIGH);          //устанавливаем начальное значение светодиодов

  if (val > 3)
  {
     digitalWrite (ledPin1, LOW);
     count =0;
  }
  else if(val <= 3)
  {
     digitalWrite(ledPin2, LOW);
     count++;
  }
  
  else{};
  
  if (count > 200)
  { 
    digitalWrite(ledPin3, LOW);
  }
  else
  {
    digitalWrite(ledPin3, HIGH);
  }
 //канал 2 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  
  val1 = analogRead(analogPin1);	// считываем значение
  digitalWrite(ledPin4, HIGH);		//устанавливаем начальное значение светодиодов
  digitalWrite(ledPin5, HIGH);		//устанавливаем начальное значение светодиодов
  digitalWrite(ledPin6, HIGH);

  if (val1 > 3)
  {
     digitalWrite (ledPin4, LOW);
     count1 =0;
  }
  else if(val1 <= 3)
  {
     digitalWrite(ledPin5, LOW);
     count1++;
  }
  
  else{};
  
  if (count1 > 200)
  { 
    digitalWrite(ledPin6, LOW);
  }
  else
  {
    digitalWrite(ledPin6, HIGH);
  }

  delay(100);
  }

 

Leshiy
Offline
Зарегистрирован: 19.07.2014

Тема старая, но...

  if (val > 3)
  {
     digitalWrite (ledPin1, LOW);

     count =0; // НЕВЕРНО

Счётчик ошибок нельзя сбрасывать в ноль каждый раз, когда на аналоговом входе появится сигнал с уровнем больше 3 условных единиц. Потому что ваш детектор  будет реагировать на сигнал любой продолжительности так, как будто это хороший, годный сигнал. А это не так.

Правильнее будет уменьшать счётчик ошибок на некоторое значение, которое подберёте экспериментально, каждый раз, когда на аналоговом входе появляется сигнал. По достижению нуля считать, что сигнал высокостабилен и выполнять необходимые вам действия.

p.p.c
Offline
Зарегистрирован: 28.05.2013

Leshiy пишет:

Тема старая, но...

  if (val > 3)
  {
     digitalWrite (ledPin1, LOW);

     count =0; // НЕВЕРНО

Счётчик ошибок нельзя сбрасывать в ноль каждый раз, когда на аналоговом входе появится сигнал с уровнем больше 3 условных единиц. Потому что ваш детектор  будет реагировать на сигнал любой продолжительности так, как будто это хороший, годный сигнал. А это не так.

Правильнее будет уменьшать счётчик ошибок на некоторое значение, которое подберёте экспериментально, каждый раз, когда на аналоговом входе появляется сигнал. По достижению нуля считать, что сигнал высокостабилен и выполнять необходимые вам действия.

 

На самом деле устройство (в 5-ти экземплярах) уже более года успешно работает в боевых условиях , т.е можно предположить что все работает правильно. Конечно были исправления предыдущего кода, но основная логика не изменилась.

По поводу "сигнала любой длины", как известно звуковой сигнал это фактически переменный ток, т.е. он не может быть "любой длины" он постоянно меняется с "частотой звука".

Leshiy
Offline
Зарегистрирован: 19.07.2014

Хм... Ну если вы так ставите вопрос...

Рассмотрим далеко не гипотетическую ситауцию - пропадание сигнала на выходе спутникового тюнера в связи с дурной погодой, тучи там, зеркало тарелки снегом залепило итд.
В таких случаях сигнал может кратковременно восстанавливаться, на секунду, другую (или на меньшее время). При этом сам сигнал категорически не пригоден для его трансляции в эфир, кому нужны эти цифровые покрякивания?
Так как ваш код не анализирует качество сигнала, а анализирует лишь его наличие, или отсутствие, он будет считать, что пригодный для ретрансляции сигнал присутствует и будет гнать в эфир кашу, вместо звука.

По поводу определений - "сигнал любой длины" читать как "аудиосигнал такой длительности, которой достаточно для того, чтобы ваш код смог его распознать как пригодный для ретрансляции".

Но я не настаиваю, ваша радиостанция, ваши решения. Доказывать вам ошибочность вашего подхода я не стану, со временем вы и сами это поймёте.

p.p.c
Offline
Зарегистрирован: 28.05.2013

Теперь я вас понял :), да такое теоретически возможно, пока такое не наблюдали, как правило проблемы чаще возникают с самим тюнером, или компом который врезает рекламу, если будет возможность перепрошью ардуинки.

Спасибо за критику.

Leshiy
Offline
Зарегистрирован: 19.07.2014

Решением проблемы может стать декремент счётчика ошибок при появлении сигнала, а не его обнуление.

Допустим мы взяли пороговым значением, после которого возникает ошибка, отсутствие сигнала в течение 10 секунд.
В момент каждого детектирования тишины (в вашем случае val < 3) прибавляем к счётчику ошибок единицу.
В момент каждого детектирования звука val > 3 отнимаем от счётчика некое значение, хоть ту же единицу.

В итоге кратковременные появления сигнала, или какие-нибудь помехи, не будут обнулять счётчик, а значит он, в итоге, достигнет порогового значения и ардуино переключит вещание на резерв. Конечно это займёт больше 10 секунд, так как счётчик будет 'ездить' туда-сюда при появлении\пропадании звука, но при таком алгоритме верятность переключения на резерв гораздо выше, чем при алгоритме, обнуляющем счётчик.

Leshiy
Offline
Зарегистрирован: 19.07.2014

Вот моя реализация:

Переключает спутниковый тюнер между преднастроенными каналами по кругу, если звука нет ни на одном из преднастроенных каналов, подключает в качестве источника сигнала онлайн-трансляцию головной радиостанции. Если звука нет вообще нигде, то отрабатывает аварийный плейер на компе (но это уже к артдуине не относится). Во время забора сигнала с онлайна крутит тюнер, каждые десять секунд жмыкая на кнопку переключения каналов, если звук появится и он нормальный, то переключает вещание на спутник и переходит в исходное состояние. Индикаторов сигнала итп в коде нет, добавлять по вкусу. :)

Ээээ, а где тут спойлеры? А то эта простыня меня самого напугала.

const int numberOfSamples = 128;    //how many readings will be taken each time

int sample;                         //the value read from microphone each time
long signal;                        //the reading once you have removed DC offset
long averageReading;                //the average of that loop of readings
long runningAverage = 0;            //the running average of calculated values
const int averagedOver = 16;        //how quickly new values affect running average
                                    //bigger numbers mean slower

const int threshold = 800;          //пороговый уровень: до 800 звука нет, выше 800 звук присутствует
const int satRelayPin = 2;          //номер ножки, к которой подключено реле, переключающее каналы спутника
const int AudioChannelRelayPin = 4; //номер ножки, к которой подключено реле, переключающее источник сигнала (спутник\онлайн-трансляция)
int flag = 0;                       //переменная, указывающая состояние коммутатора: 0 - основной спутник, 1 - резервный спутник, else - онлайн-трансляция
long previoustime = 0;              //переменная, содержащая предыдущее значение таймера в миллисекундах
int satButtonTicker = 0;            //отсчитывает время, спустя которое будет замкнуты контакты реле, переключающего спутники на тюнере    

void setup() {
  pinMode(satRelayPin, OUTPUT);
  pinMode(AudioChannelRelayPin, OUTPUT);
  delay(100);
  previoustime = millis();
  
  //Serial.begin(9600);
}

void loop() {
  //=======================================================================================================  
  //делаем выборку аудиосигнала и усреднение его
  //=======================================================================================================

  long sumOfSquares = 0;
  for (int i=0; i<numberOfSamples; i++) { //take many readings and average them
    signal = analogRead(A0);              //take a reading
//    signal = sample - middleValue);   //work out its offset from the center
    signal *= signal;                     //square it to make all values positive
    sumOfSquares += signal;               //add to the total
  }
  averageReading = sumOfSquares/numberOfSamples;     //calculate running average
  runningAverage=(((averagedOver-1)*runningAverage)+averageReading)/averagedOver;

  //=======================================================================================================
  //в зависимости от состояния переменной 'flag' вызываем ту, или другую подпрограмму обработки результатов выборки звука
  //=======================================================================================================

  if (flag == 0 || flag == 1) {
    satellite(millis());
  }
  else {
    backtosatellite(millis());
  }
}

//=======================================================================================================
//подпрограмма, обрабатывающая сигнал с основного и резервного спутника
//=======================================================================================================

void satellite(unsigned long currenttime) {

  if (runningAverage>threshold){             //если звук есть
    previoustime += 40;                      //уменьшаем на 40 миллисекунд длительность детектированной тишины
    if (previoustime > millis()) {           //если значение переменной 'previoustime' больше, чем результат выдаваемый 'millis()', то, во избежание ошибки,
      previoustime = millis();               //устанавливаем значение переменной 'previoustime' равной millis()
      flag = 0;                              //так как звук присутствовал длительное время, восстанавливаем полный цикл работы программы, чтобы она не перешла
    }                                        //на онлайн-источник сигнала через месяц работы
    return;                                  //так как звук присутствует, выходим из подпрограммы
  }


  if (currenttime - previoustime > 6900) {    //если тишина длится ~7 секунд, то
    flag += 1;                                //так как мы перешли на резервный спутник, то увеличиваем переменную 'flag' на 1                                 
    if (flag == 1) {                          //если это первый вход в процедуру, то отрабатываем всё по полной
                                              //если второй, то сразу переходим к переключению коммутатора на онлайн-источник сигнала
                                              
    digitalWrite(satRelayPin, HIGH);          //щёлкаем реле, которое переключает тюнер на другой спутник
    delay(1000);                              //держим реле включенным секунду
    digitalWrite(satRelayPin, LOW);           //отпускаем реле
    previoustime = millis();                  //для ускорения перехода на онлайн-вещание, в случае отсутствия сигнала на резервном спутнике                                              
                                              //устанавливаем время детектирования отсутствия сигнала равным 5 секундам. 7000+delay(2000)+delay(1000)-5000 = 5000
      }
   }
}

//=======================================================================================================
//подпрограмма, возвращающая вещание с онлайн-трансляции на спутник
//=======================================================================================================
void backtosatellite(unsigned long currenttime) {

  digitalWrite(AudioChannelRelayPin, HIGH); //щёлкаем реле, которое переключает коммутатор на онлайн-трансляцию

  if (runningAverage<threshold){            //если звук со спутника не появился
    previoustime = millis()-10;             //устанавливаем значение переменной 'time' равной millis(), так как нам нужно достоверное определение наличия 
    satButtonTicker++;                      //сигнала со спутника, мы ждём 30 секунд чистого сигнала без единого пропадания. 
  }                                         //Любой провал сигнала сбрасывает счётчик в 0
  else {
    satButtonTicker -= 10;                  //для исключения ситуации переключения каналов тюнера при наличии сигнала со спутника, уменьшаем значение счётчика на 10
    }  
    
  if (currenttime - previoustime > 30000) {   //если звук есть ~30 секунд, то 
    digitalWrite(AudioChannelRelayPin, LOW);  //щёлкаем реле, которое переключает коммутатор на работу от спутника
    delay(1000);
    flag = 0;                                 //устанавливаем переменную 'flag' в 1. Теперь будет вызываться подпрограмма обработки основного спутника
    previoustime = millis();
    return;
  }

  if (satButtonTicker < 0) {satButtonTicker = 0;}
  if (satButtonTicker > 999) {          
    satButtonTicker = 0;
    digitalWrite(satRelayPin, HIGH);          //щёлкаем реле, которое переключает тюнер на другой спутник
    delay(1000);                              //держим реле включенным секунду
    digitalWrite(satRelayPin, LOW);           //отпускаем реле
  }
}

 

p.p.c
Offline
Зарегистрирован: 28.05.2013

Переключение каналов реализовано просто замыканием контактов?

Кстати у себя в проекте я еще и raspberry юзал, для удаленного контроля, ребута тюнера, компа ну и + с нее же у меня идет онлаин вещание(резерв), а вообще если бы я сейчас делал, многое сделал бы иначе :).

Leshiy
Offline
Зарегистрирован: 19.07.2014

Да, банальное реле, замыкающее кнопку на тюнере. Тюнер тупой, поэтому так. Да и вообще - чем проще, чем надёжней. Точно так же поключается онлайн-трансляция, с помощь двухканального реле. Источником сигнала служит допотопный комп, наличие сигнала с онлайна, перезапуск плейера, если сигнала нет, отправка уведомлений по почте итп. возложено на Pira CZ Silence Detector.

Вся эта байда, вдобавок ко всему, запитана через один здоровый контактор, который управляется с сотового телефона. Звонишь, жмёшь кнопку и он - ба-бах! - вырубает питание по студии на 40 секунд (ну это если по отдельности срубить проблему не выходит).

p.p.c
Offline
Зарегистрирован: 28.05.2013

если комп вырубится (зависнет), то об отсутсвии вещания можно так и не узнать, хотя если комутатор сигнала стоит и после компа, это частично проблему решит, но опять таки не будет региональной рекламы, а это чревато :).

Leshiy
Offline
Зарегистрирован: 19.07.2014

Кстати ещё одна причина использования тупого тюнера заместо новомодных виснущих линуксовых боксов - наилютейшая стабильность. Он у меня годами не выключается и не перезапускается.

p.p.c
Offline
Зарегистрирован: 28.05.2013

у нас приходится использовать "новомодный виснущий линукс", ибо сигнал идет в закрытых PID-х, а для того что б не платить много денег за подъем, кодируем звук в AC3, тут либо ставить професиональный тюнер либо что то на линуксе, но професиональный стоит денег почти как все оборудование на удаленной точке (без учета передатчика :)).

Leshiy
Offline
Зарегистрирован: 19.07.2014

p.p.c пишет:

если комп вырубится (зависнет), то об отсутсвии вещания можно так и не узнать, хотя если комутатор сигнала стоит и после компа, это частично проблему решит, но опять таки не будет региональной рекламы, а это чревато :).

Узнаю, узнаю. ;) У меня это дело контролируется не одним компом, а цепочкой.

1 - сам источник онлайн-сигнала
2 - эфирные компы
3 - айскаст-сервер
4-5-6-7-8 - компы-ретрансляторы по области

Ктонить да и свистнет. Да и вообще, я как-то не помню зависаний критичных компов за последний год, подбирались тщательно, плюс никаких замен на новые до выработки ресурса (читай пока не задымят).

Региональная реклама будет палюбас, так как во-первых онлайн-сигнал это тот-же сигнал, что и со спутника, только идёт по нету. То есть в нём все влёты-вылеты, все ДТМФ-метки и прочая есть. А во-вторых вещалка настроена так, что если не отдетектила метку влёта в местный рекламный блок, то спустя 5 минут она жёстко рвёт сигнал и выводит  местную врезку. Некрасиво, зато претензий нет.

 

p.p.c
Offline
Зарегистрирован: 28.05.2013

всмысле виснет комп который врезает рекламу, т.е. 4 5 6 7 8.

Leshiy
Offline
Зарегистрирован: 19.07.2014

p.p.c пишет:

у нас приходится использовать "новомодный виснущий линукс", ибо сигнал идет в закрытых PID-х, а для того что б не платить много денег за подъем, кодируем звук в AC3, тут либо ставить професиональный тюнер либо что то на линуксе, но професиональный стоит денег почти как все оборудование на удаленной точке (без учета передатчика :)).

То есть вы сами поднимаете сигнал на спутник? Головная радиостанция? Ууууу!

p.p.c
Offline
Зарегистрирован: 28.05.2013

у нас 2 радиостанции, и 2 телеканала, подъем сигнала осуществляется в Киеве на сириус, до Киева по выделенный линии :)

Дальше стоит обсуждать в личке, не будем засорять тему, кому то может пригодится.

Leshiy
Offline
Зарегистрирован: 19.07.2014

p.p.c пишет:

всмысле виснет комп который врезает рекламу, т.е. 4 5 6 7 8.

Аааа. Ну пока не замечены в этом. Да и перекрёстную пинговалку можно нарисовать, хотя не панацея конечно. Удалённые 'студии' пока контролирую вручную, пару раз в сутки смотрю их состояние LiteManager'ом, в случае необходимости жёстко ребутаю по питанию. Может, когда разберусь с ардуиной полчше, чего и наваяю. Я его себе 4 дня назад только купил, как раз под создание детектора-коммутатора. У меня сейчас есть такой-же, но на компе отдельном, а это как-то громоздко, вот и решил автономный модуль собрать.

Ок, если что, стучите в личку.

p.p.c
Offline
Зарегистрирован: 28.05.2013

Могу отдать свои наработки, есть разводки печатных плат, чертежы корпусов для лазерной резки, фотки готовых устройств, ну и всякие сопутсвущие мелочи.

Leshiy
Offline
Зарегистрирован: 19.07.2014

Спасибо, но я как-то на макетной гетинаксине всё леплю, может оно и выглядит хуже, зато этап разработка-внедрение драматически сокращается. Корпуса беру у китайцев, серенькие, невзрачные и дешёвые. Всё равно им в тумбочке лежать. :) А так не откажусь посмотреть, будет, думаю, мне полезно. Ещё раз спасибо.

p.p.c
Offline
Зарегистрирован: 28.05.2013

лички тут нет, скайп ***.

Leshiy
Offline
Зарегистрирован: 19.07.2014

постучал в скайп

p.p.c
Offline
Зарегистрирован: 28.05.2013

нету, свой дайте

Leshiy
Offline
Зарегистрирован: 19.07.2014

vktr36