Сигнал времени. Определить наличие импульсов и их длительность

Я хочу определить точное время сигнала от FM-радиостанции. Сигнал передается в виде 5 коротких импульсов и одного длинного. Моя задача-определить наличие импульса в потоке аудиоданных и его продолжительность. Понятно, что сигнал принимается радиоприемником, а затем аудиосигнал подается на микроконтроллер atmega. Длительность импульса колеблется в диапазоне 100 ... 560 мс

Как можно контролировать появление этих импульсов с помощью микроконтроллера, а также определять их продолжительность? Ниже приведен пример компьютерной обработки. То же самое требуется реализовать и на микроконтроллере.

  1. У меня есть эталонный записанный сигнал в формате WAV. Этот записанный сигнал с выхода радиоприемника содержит как речь говорящего, так и музыку, а также сигнал точного времени:

  2. Я знаю, что сигнал времени центрирован на частоте 1 кГц, поэтому примените фильтрацию к этому сигналу. Фильтр имеет следующую частотную характеристику:

Сигнал после фильтра выглядит так:

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

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

Добавлено через несколько дней:

Мне удалось собрать всю конструкцию, скетч ниже делает следующее:

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

  2. Выполняет гомодинную обработку: выбирает только сигнал с частотой 1 кГц, все остальные частоты подавляются

  3. Выбирает оптимальный порог обнаружения для нахождения точного времени сигнала частотой 1 кГц на фоновом уровне.

  4. Вычисляет длительность каждого импульса

  5. Находит точное время по последнему импульсу с помощью формулы пересчета.

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

Но конечный результат, который я хочу получить,-это установка результирующей цифры на семисегментном дисплее. Посмотрев скетчи его работы, я обнаружил, что там постоянно обрабатываются значения в цикле, что опять же замедляет работу микроконтроллера, но для меня это неприемлемо. Как я понимаю, нужно написать отдельную функцию, в которую вы передаете значение часа и устанавливаете это значение на индикаторе только один раз в час. Но я не знаю, как это реализовать. Я прошу о помощи!

/ * Последнее редактирование 25 января 2021 года.
   Обнаружение гомодинного сигнала 1 кГц

  Эта программа непрерывно дискретизирует аналоговый вход A0 и использует гомодинную
схему обнаружения для идентификации сигнала на частоте 1 кГц (+/- 24 Гц при -3 дБ).

  Аналого - цифровой преобразователь установлен в "режим свободного хода" и принимает одну выборку каждые 104 мкс.
  Отсчеты умножаются на два генерируемых сигнала 1 кГц ("локальный генератор") в квадратуре друг к другу.
  Затем продукты фильтруются нижними частотами с постоянной времени 64 периода дискретизации (6,656 мс),
что дает сигналы (I, Q) с полосой пропускания 24 Гц.
  Наконец, сила сигнала вычисляется как I ^ 2 + Q ^ 2.

  Программа предназначена для Arduino Uno и, вероятно, будет работать на любом Arduino на базе AVR,
имеющем АЦП и работающем на частоте 16 МГц.

  Подробное объяснение см.
в разделе http://arduino.stackexchange.com/a/21175

  Автор: 2016 Edgar Bonet Orozco.
* /


// Вот все, что связано с параметрами приемника RDA5807M:
#include <Wire.h>

#define RDA5807M_RANDOM_ACCESS_ADDRESS 0x11
// регистры
#define RDA5807M_REG_CONFIG 0x02
#define RDA5807M_REG_TUNING 0x03
#define RDA5807M_REG_VOLUME 0x05
#define RDA5807M_REG_RSSI 0x0B
// флаги
#define RDA5807M_FLG_DHIZ 0x8000
#define RDA5807M_FLG_DMUTE 0x4000
#define RDA5807M_FLG_BASS 0x1000
#define RDA5807M_FLG_ENABLE word (0x0001)
#define RDA5807M_FLG_TUNE word (0x0010)
// маски
#define RDA5807M_CHAN_MASK 0xFFC0
#define RDA5807M_CHAN_SHIFT 6
#define RDA5807M_VOLUME_MASK word (0x000F)
#define RDA5807M_VOLUME_SHIFT 0
#define RDA5807M_RSSI_MASK 0xFE00
#define RDA5807M_RSSI_SHIFT 9

uint8_t volume = 7; // 0..15
uint16_t freq = 938; // 107.3 FM
uint16_t reg02h, reg03h, reg05h, reg0Bh;
// Конец приемника

unsigned long startTime; // Turn on two timers, this
unsigned long elapsedTime; // and this one
int threshold = 300; // Установите максимальный уровень шума
boolean timing = false; // Я не знаю...


#include <util / atomic.h>

// Используемый аналоговый вход должен находиться в диапазоне от A0 до A5.
const uint8_t analog_in = 0;

// Частота, которую мы хотим определить, в Гц.
const float SIGNAL_FREQ = 1000.0;

// Биты времени.
const float SAMPLING_FREQ = F_CPU / (128 * 13.0); // 9.615 кГц
const long PHASE_INC = round (SIGNAL_FREQ / SAMPLING_FREQ * (1L << 16));
const int LOG_TAU = 6; // tau = 64 / SAMPLING_FREQ = 6,656 мс

// Установите АЦП в постоянный режим преобразования "free running mode"
static void configure_adc ()
{
  ADMUX = _BV (REFS0) // ref = AVCC
           | _BV (ADLAR) // левая настройка результата
           | analog_in; // входной канал
  ADCSRB = 0; // режим свободного хода
  ADCSRA = _BV (ADEN) // включить
           | _BV (ADSC) // начать преобразование
           | _BV (ADATE) // автоматическое включение триггера
           | _BV (ADIF) // очистить флаг прерывания
           | _BV (ADIE) // включить прерывание
           | 7; // прескалер = 128
}

// Демодулированные (I, Q) амплитуды.
volatile int16_t signal_I, signal_Q;

// Interrupt handler is called every time the ADC is ready to read.
ISR (ADC_vect)
{
  // Считайте АЦП и преобразуйте его в число со знаком.
  int8_t sample = ADCH - 128;

  // Обновить фазу локального генератора.
  static uint16_t phase;
  phase + = PHASE_INC;

  // Умножить отсчеты на квадратные волны в квадратуре.
  int8_t x = sample;
  if (((phase >> 8) + 0x00) & 0x80) x = -1 - x;
  int8_t y = sample;
  if (((phase >> 8) + 0x40) & 0x80) y = -1 - y;

  // Фильтр нижних частот первого порядка.
  signal_I + = x - (signal_I >> LOG_TAU);
  signal_Q + = y - (signal_Q >> LOG_TAU);
}

/ * Return the power reading. * /
static uint16_t get_power_reading ()
{
  int16_t I, Q;
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
    I = signal_I;
    Q = signal_Q;
  }
  return sq ((int8_t) (I >> LOG_TAU)) + sq ((int8_t) (Q >> LOG_TAU));
}


// *********************************************** ***********************
void setup ()
{
  configure_adc ();
  Serial.begin (9600);

  // Пуск приемника:
  Serial.begin (9600);
  Wire.begin ();
  // Register 02h - включить, настройки
  reg02h = RDA5807M_FLG_ENABLE | RDA5807M_FLG_DHIZ | RDA5807M_FLG_DMUTE;
  setRegister (RDA5807M_REG_CONFIG, reg02h);

  // И тогда мы решили еще больше усилить бас: (Мне это не нужно, мне нужно как-то его убрать)
  reg02h | = RDA5807M_FLG_BASS;
  setRegister (RDA5807M_REG_CONFIG, reg02h);

  // Register 03h - выбор радиостанции
  // После сброса в регистре 03h значение по умолчанию равно 0
  // Таким образом, ДИАПАЗОН = 00 (87..108 МГц), ШАГ = 00 (100 кГц). Давайте оставим их такими, какие они есть
  reg03h = (freq - 870) << RDA5807M_CHAN_SHIFT; // chan = (freq - band) / space
  setRegister (RDA5807M_REG_TUNING, reg03h | RDA5807M_FLG_TUNE);
// Регистрация 05ч. Установите громкость, не меняйте остальные ритмы
  reg05h = getRegister (RDA5807M_REG_VOLUME); // Считайте текущее значение
  reg05h & = ~ RDA5807M_VOLUME_MASK; // Сброс битов
  reg05h | = volume << RDA5807M_VOLUME_SHIFT; // Установка нового тома
  setRegister (RDA5807M_REG_VOLUME, reg05h);
  // Конец приемника:
}

void loop ()
{
  // Печать показаний мощности каждые 8 мс.
  static const uint16_t print_period = 8;
  static uint16_t last_print;
  uint16_t now = millis ();
  if (now - last_print> = print_period) {
    //Serial.println (get_power_reading ()); // Постройте график, по которому можно определить оптимальный порог
    // Если сигнал превысил пороговое значение, начните синхронизацию:
    if (get_power_reading ()> threshold &&! timing)
    {
      startTime = millis ();
      timing = true;
    }
    // Если сигнал ниже порогового значения, остановите обратный отсчет и отобразите количество пройденного времени - длительность импульса:
    if (get_power_reading () <= threshold && timing)
    {
      elapsedTime = millis () - startTime;
      timing = false;
      //Serial.println(Истекшее время);
      if (elapsedTime> = 80 && elapsedTime <= 580)
      {
        int h = floor ((elapsedTime - 100) / 20); // Получаем время в часах, но импульсов 6, но нужен только последний.
        //Serial.println(h);
        // "valround" --- Округление до ближайшего целого числа,
        // "floor (x)" --- Округлить до целого числа
        if (h> = 1 && h <= 24) // Если время определено как ненормальное, выходящее за разумные пределы, то просто забудьте об этом:
        {
          Serial.println ("Exact time:");
          Serial.print (h);
          Serial.println (": 00");
        }
      }
    }
    last_print + = print_period;
  }
}


// Receiver start:
void setRegister (uint8_t reg, const uint16_t value) {
  Wire.beginTransmission (0x11);
  Wire.write (reg);
  Wire.write (highByte (value));
  Wire.write (lowByte (value));
  Wire.endTransmission (true);
}

uint16_t getRegister (uint8_t reg) {
  uint16_t result;
  Wire.beginTransmission (RDA5807M_RANDOM_ACCESS_ADDRESS);
  Wire.write (reg);
  Wire.endTransmission (false);
  Wire.requestFrom (0x11, 2, true);
  result = (uint16_t) Wire.read () << 8;
  result | = Wire.read ();
  return result;
}
// конец приемника

, 👍1

Обсуждение

Начните с кондиционирования сигнала до напряжения в пределах 0 – 1,5 В (без импульса) или 3-5 В (во время импульса). Зафиксируйте это на оптическом прицеле и разместите трассировку здесь. Вам будет намного легче помочь, если мы сможем увидеть, как на самом деле выглядит условный сигнал., @Edgar Bonet

вы действительно слышите сигнал времени по радио? ... как часто он передается? ... если вы его слышите, то на что он похож?, @jsotola

Вы имеете в виду широковещательный сигнал времени WWV / WWVH?, @jwh20

или на звуковые сигналы 1 кГц, @Juraj

Я получаю сигнал от вещательной станции, и временные импульсы звучат на звуковой частоте 1 кГц. Их бывает 6, иногда 5, когда есть накладки на вещание ведущего. Сигнал передается в конце каждого часа. Это не WWV/WWVH, @Антон

Ваш последний график показывает интенсивность сигнала около 40, которая для синусоидальной волны подразумевает амплитуду π√ (40 ÷8) ≈ 7 на АЦП в 8-битном режиме. При этом используется менее 6% диапазона АЦП. Кроме того, сильные остаточные колебания намекают на то, что среднее напряжение может быть далеко от 2,5 В. Возможно, вы захотите точно настроить кондиционирование сигнала, чтобы устранить эти проблемы. Вы также можете добавить слой сглаживания на уничтоженную интенсивность., @Edgar Bonet


1 ответ


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

1

Если голос и музыка накладываются поверх сигнала, который вы ищете, это сильно усложнит ситуацию. Первое, что вам нужно сделать, это отфильтровать весь нежелательный звук. Предел того, насколько агрессивно вы можете фильтровать, определяется шириной импульсов. Если вы хотите обнаружить импульсы 100 мс, ширина фильтра, вероятно, должна быть больше 10 Гц. Фильтр, который вы использовали в своем тесте, намного больше этого.

Учитывая ограниченную вычислительную мощность вашего типичного Arduino, может быть предпочтительнее выполнить всю обработку сигнала в аналоговой области, прежде чем оцифровывать его на Arduino. Если вам действительно придется делать все в Arduino, вам все равно придется регулировать амплитуду и смещение сигнала в аналоговой области так, чтобы он соответствовал диапазону АЦП.

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

Если у вас есть что-то похожее на Uno, вы должны иметь возможность загрузить этот скетч напрямую, хотя вы можете настроить постоянную времени , чтобы сделать фильтр более узким. Затем вы можете отправить ему свой тестовый сигнал, собрать обратно показания мощности, отправленные скетчем через последовательный порт, и построить их график. Это должно дать вам новый тестовый сигнал для прототипирования следующих этапов вашего конвейера обработки. Если все пойдет хорошо, вам просто нужно будет установить порог для обнаружения импульсов, а затем иметь некоторое условие на длину обнаруженных импульсов, чтобы отделить паразитные импульсы от законных.

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

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

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

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

const uint16_t LOW_THRESHOLD  = ...;
const uint16_t HIGH_THRESHOLD = ...;

// внутри цикла:
static enum { NO_PULSE, PULSE } state = NO_PULSE;
uint16_t power = get_power_reading();
if (state == NO_PULSE && power >= HIGH_THRESHOLD) {
    state = PULSE;
    register_pulse_start();
}
if (state == PULSE && power < LOW_THRESHOLD) {
    state = NO_PULSE;
    register_pulse_end();
}
,

Спасибо! Я использовал ваш код без изменений, и он работает! Только я использовал сигнал от генератора, так как у меня еще нет усиленного сигнала после приемника. Теперь выведите результат на последовательный порт. Я вижу огромные цифры только тогда, когда применяю 1 кГц! Скажи мне, что я могу делать дальше? Ведь опрос проводится довольно редко., @Антон

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

Я провел тест, на этот раз используя сигнал от телефонного генератора. Я изменил частоту сигнала и наблюдал за результатом. Я думаю, что оптимальный порог - 25. Я не могу дать тестовый сигнал, потому что Arduino редко строит точки, и мои короткие импульсы просто не фиксируются. Я приложил график в посте выше, @Антон

Я увеличил частоту дискретизации до 115200 бод, но не всегда удается обнаружить сигнал 200 мс... Можно ли еще больше увеличить частоту дискретизации?, @Антон

@Антон: Частота дискретизации "115200 бод" не имеет смысла. Частота дискретизации 155200 выборок в секунду - это больше, чем может выдержать Arduino: обработка выборок потребует 127% доступной мощности процессора. [Вы можете увеличить частоту дискретизации](https://www.gammon.com.au/adc ), но это не поможет обнаружить короткие импульсы. Вместо этого вы должны увеличить скорость печати образцов на последовательный порт, то есть уменьшить "print_period`. Увеличение скорости передачи данных потребуется только в том случае, если вы установите значение print_period менее 8 мс, что вряд ли будет полезно., @Edgar Bonet

Как можно сгладить значения, не получая запаздывания сигнала? Я заметил, что сигнал очень неровный и время не всегда точное., @Антон

@Антон: Каждый фильтр нижних частот, о котором я знаю, вносит некоторую задержку. Но запаздывание одинаково для восходящего и нисходящего краев импульса, поэтому оно не мешает измерению длины импульса. Re “сигнал очень зазубрен”: см. Исправленный ответ., @Edgar Bonet

Привет! Ваш код мне очень помог. Но теперь моя задача - обнаружить прямоугольные импульсы длительностью 460 мкс. Можно ли это сделать с помощью вашего кода? Что нужно в нем изменить? Я создал новый вопрос на этом сайте, и мне очень хотелось бы услышать ваши инструкции, так как мне очень понравилось ваше решение предыдущей проблемы. Спасибо!, @Антон