Измерить звуковую частоту через длительность импульса

Здравствуйте, уважаемые пользователи StackExchange,

я хочу разработать инфракрасный бластер, который я могу подключить к разъему для наушников телефона или консоли по выбору. Чтобы сообщить, с какой частотой модулировать диод и как долго посылать импульсы, я просто генерирую звуковые сигналы для считывания и интерпретации Arduino.

Моя проблема в том, что я не могу надежно измерить частоту с его помощью. Чтобы измерить частоту, я использую прерывание аналогового компаратора и запускаю его на падающем фронте, таким образом, всегда получая прерывание после возникновения полного импульса. Я вызывал micros() при каждом прерывании, чтобы получить длительность импульса, но это было слишком неточно. Затем я попытался использовать Timer0 с прескалером 8 для измерения частоты, но он имеет значение только 255.

Теперь я переключился на Timer1 из-за его большего регистра TCNT. Однако по какой-то причине он уже переполняется на 255, хотя я не устанавливал регистры OCR1.

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

const int setFrequencySound = 1000;
const int receiveDataSound = 2000;
const int receiveDataEndSound = 3000;

const int readyMode = 0;
const int setFrequencyMode = 1;
const int receiveDataMode = 2;
const int sendDataMode = 3;

volatile unsigned long timeBefore;
volatile unsigned long timeNow;
volatile unsigned long delta;
volatile float audioFrequencyBefore;
volatile float audioFrequency;

volatile float irFrequency;
volatile int irData[100];
volatile int irDataIndex;

volatile int mode = 0;

ISR (ANALOG_COMP_vect)
{
timeNow = TCNT1;
Serial.print (timeNow);
Serial.println (" TCNT1");
//TCNT1 = 0; //регистр таймера сброса
}

void setup ()
{
Serial.begin (115200);
Serial.println ("Started.");
ADCSRB = 0;           // (Отключить) ACME: включить мультиплексор аналогового компаратора
ACSR =  bit (ACI)     // (Очистить) Флаг прерывания аналогового компаратора
      | bit (ACIE)    // Включение прерывания аналогового компаратора
      | bit (ACIS1);  // ACIS1, ACIS0: выбор режима прерывания аналогового компаратора (срабатывание на падающем фронте)

TCCR1B |= (0 << CS12) | (1 << CS11) | (0 >> CS10); //Активируйте таймер и установите значение предварительной настройки на 8
TCNT1 = 0; //Сбросить регистр таймера
}  // завершение настройки

void loop ()
{
// Вычислите частоту, используя длительность периода
//определяется с помощью временных меток, захваченных в
//ISR.
//дельта = Время сейчас - время до;
audioFrequency = 1000000000.0 / (float(timeNow) * 2.0);

if (millis() % 1000 == 0)
{
  Serial.print (audioFrequency, 5);
  Serial.print (" Hz, ");
  Serial.print (timeNow);
  Serial.println (" TCNT0");
}

//Не обрабатывайте одну и ту же частоту несколько раз
if (audioFrequency != audioFrequencyBefore)
{
  switch (mode)
  {
    case readyMode:
      if (audioFrequency == setFrequencySound)
      {
        mode = 1;

        Serial.println ("Start setting IR frequency.");
      }
      else if (audioFrequency == receiveDataSound)
      {
        mode = 2;

        Serial.println ("Start receiving IR data.");
      }
      break;
    case setFrequencyMode:
      irFrequency = audioFrequency + 25000;

      Serial.print ("IR frequency set to ");
      Serial.print (irFrequency, 10);
      Serial.println (" Hz.");
      
      mode = readyMode;
      break;
    case receiveDataMode:
      if (audioFrequency == receiveDataEndSound)
      {
        Serial.println ("Stop receiving IR data.");
        
        mode = readyMode;
        irDataIndex = 0;
      }
      else
      {
        irData[irDataIndex] = audioFrequency;

        irDataIndex++;

        Serial.print ("IR pulse received: ");
        Serial.print (irData[irDataIndex]);
        Serial.println (" microsecs.");
      }
      break;
    case sendDataMode:
      Serial.println ("Sending IR data...");
      break;
  }
}

audioFrequencyBefore = audioFrequency;

}  // конец цикла

, 👍3

Обсуждение

Пожалуйста, покажите полный компилируемый код (включая функцию цикла). Серийная печать в ISR не очень хорошая вещь. Таким образом, вы можете очень быстро заполнить последовательный буфер. Какую частоту вы пытаетесь измерить? Поскольку сигналы на стандартном разъеме для наушников часто бывают дифференциальными (например, в диапазоне +-1,2 В), как вы обрабатываете сигнал перед подачей его в Arduino?, @chrisl

Конечно, серийная печать в ISR — плохая идея, но мне нужно как-то получить информацию. Когда эта проблема будет устранена, я удалю эту команду печати. Частота, которую я пытаюсь измерить, - это звуковая частота сигнала, который я генерирую. Никакой песни, входа микрофона или чего-то еще, просто простой квадратный тон, который я генерирую. Ну, несколько тонов., @MyFairJulie


1 ответ


Лучший ответ:

5

Таймер 1 переполняется при 255, потому что именно так он был настроен ядром Arduino, поскольку он предназначен для обеспечения 8-битной ШИМ. Если вы хотите использовать таймер в обычном режиме, вам следует отменить инициализацию Arduino , установив значение TCR1A равным нулю.

Два комментария к ISR:

ISR (ANALOG_COMP_vect)
  {
    timeNow = TCNT1;
    Serial.print (timeNow);
    Serial.println (" TCNT1");
    //TCNT1 = 0; //регистр таймера сброса
  }

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

Наконец, я рекомендую вам прочитать о возможности “захвата входных данных” таймера 1. Это особенность таймера, предназначенного для решения именно той проблемы, с которой вы столкнулись здесь: таймер автоматически запишет временную метку для внешнего события. Это может быть вызвано повышением или понижением кромки на входном выводе 8 или аналоговым компаратором, если вы предпочитаете. Этот метод дает вам нулевое дрожание, которое не может быть достигнуто с помощью кода, даже выполняемого в прерывании.


Обновление: Отвечаю на дополнительный вопрос в комментарии.

Могу ли я подключить аудиопровод [...] непосредственно к контакту 8 и использовать функцию захвата входного сигнала?

Это зависит от формы сигнала, который выходит из этого провода. Во–первых, вы должны убедиться, что сигнал всегда остается между электрическими потенциалами GND и Vcc (т. е. 0 - 5 В), в противном случае вы рискуете повредить Arduino.

Обратите внимание, что если сигнал поступает от источника с высоким импедансом, он может безопасно выходить за пределы этого диапазона, поскольку все входы Arduino защищены диодами. Вам просто нужно ограничить ток в диодах менее чем 1 мА. Другими словами, для каждого Ком выходного сопротивления вы можете расширить (ненагруженный) допустимый диапазон напряжений на 1 В с каждой стороны. Если источник имеет низкий импеданс, вы можете сделать его высокоимпедансным, просто добавив последовательно резистор 10 Ком.

Второй момент заключается в том, что вы должны убедиться, что сигнал имеет достаточно большой диапазон напряжения, чтобы цифровой вход переключался между НИЗКИМ и ВЫСОКИМ уровнями. Согласно спецификации ATmega328P, при питании от 5 В входные пороговые значения обычно составляют около 2,1 В для переключения на НИЗКИЙ уровень и 2,6 В для переключения на ВЫСОКИЙ. Разница между этими пороговыми значениями обеспечивает некоторый гистерезис, который может помочь подавить шум. Обратите внимание, что это типичные значения. Если вы хотите гарантированного переключения, сигнал должен быть ниже 1,5 В, чтобы получить гарантированное НИЗКОЕ значение (это VIL), и выше 3 В, чтобы получить гарантированное ВЫСОКОЕ значение (VIH).

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

,

Интересно, значит, подсказка с таймером-1 будет работать на всех Arduino на базе 328p, верно? +1, @Tim_Stewart

@Tim_Stewart: Действительно. Это особенность ATmega328p (и многих других ATmega), а не платы Arduino, на которой она установлена., @Edgar Bonet

@EdgarBonet Могу ли я подключить аудиопровод (у меня есть соединительный кабель, подключенный к левому каналу разъема для наушников) напрямую к контакту 8 и использовать функцию захвата ввода?, @MyFairJulie

@MyFairJulie: см. ответ с поправками., @Edgar Bonet

@EdgarBonet На самом деле я уже использую аналоговый компаратор [как показано здесь](https://arduinoprosto.ru/q/21157/arduino-read-frequency-of-input-from-audio-jack). Может просто настроить прерывание захвата ввода? Или мне все еще нужно прерывание аналогового компаратора?, @MyFairJulie

@MyFairJulie: Если вы используете функцию захвата ввода Таймера 1, прерывание захвата ввода, вероятно, является лучшим местом для получения меток времени., @Edgar Bonet