Хабрахабр

Делаем простой гидроакустический модем

Привет, глубокоуважаемые!

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

Всем заинтересованным — милости просим под кат, в реверберирующий мир подводной связи!

А вот релевантная картинка, для привлечения внимания:

«В конечном счете смысл нашего существования — тратить энергию… И по возможности, знаете ли, так, чтобы и самому было интересно, и другим полезно.»
(С) АБС, «Полдень, XXII век»

Для экономии вашего времени — краткое содержание

  • Гидроакустические модемы пока не продают на Aliexpress
  • Есть простой и нетребовательный к вычислительным ресурсам метод детектирования тона, частота которого в 4 раза меньше частоты дискретизации; Для реализации хватит и Arduino
  • Пример кода для PC лежит на GitHub
  • Приемную и передающие антенны делаем из пьезопищалок по 10 р штука
  • Покупаем (или делаем сами) платку усилителя на TDA2030 на Ali за 50 рублей
  • Делаем ЛУТ-ом предусилитель, с суммарной стоимостью ~100 рублей
  • Подключаем и идем на водоем
  • Радуемся

Мотивационная прелюдия

Сейчас вы можете купить на Aliexpress или eBay практически все, что угодно. Особенно много всякого разного для самостоятельного изготовления чего-либо электронного на основе Arduino. Вы можете сделать (если просто купить неинтересно) мильен-стопервую метеостанцию с подключением к интернет, автоматическую кормушку для кота, контроллер домашней пивоварни, но пока еще вы не можете купить гидроакустический модем, конструктор для его изготовления или хотя бы модуль для адруино. Ну и хорошо! И не надо — сейчас мы расскажем как его сделать, а еще расскажем как он работает.

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

Что-нибудь обещающее долгое и увлекательное совершенствование, то, что в последствие можно перенести даже на ардуино, будь оно неладно.

Если подходить к вопросу материалистически, то нам хотелось предложить подробный туториал для изготовления некоего простого устройства, которое было бы более-менее в состоянии передавать данные в мелководном водоеме (мелководный гидроакустический канал — наиболее сложный), подразумевало бы максимум изготовление печатной платы при помощи ЛУТ, с общей стоимостью, не превышающей на минималочках пары-тройки сотен рублей.

Что мы будем делать сегодня?

  • вспомним, как сделать подходящую гидроакустическую антенну и изготовим парочку;
  • одну из антенн подключим к ПК через усилитель на TDA за ~50 рублей и получим передатчик;
  • для второй сделаем при помощи ЛУТ предусилитель за ~100 рублей;
  • напишем (я уже все написал и положил на Git) простой модем на C# и испытаем все на ближайшем водоеме;

Что нам для этого понадобится?

  • два пьезоэлемента. Например, от часов или открытки;
  • кабель RG-174/U (или аналогичный) ~5 метров;
  • безуксусный герметик;
  • водостойки лак;
  • фольгированный текстолит, в общей сложности примерно 100x200 мм;
  • усилитель на TDA2030 (например, такой, за 50 рублей);
  • комплектующие для предусилителя

Как оно работает?

Вся идея простейшего модема построена на, опять же, простейшем (совпадение?) детекторе определенного тона, про который, я к своему стыду не слышал. Рассказал мне про него совершенно невзначай andrey_9999a. Он, кстати, сделал и плату предусилителя.

В связи с этим мне вспомнилась цитата из книги Леонарда Сасскинда «Битва при черной дыре»:

Еще более надежно я отличу вино от пива. «Как ценитель вина, я более или менее уверен, что даже с закрытыми глазами смогу отличить красное от белого. Но вот дальше вкус меня подведет.»

Могу сказать про себя, что как заправский электронщик я более или менее уверен, что точно смогу спаять два толстых провода. Еще более надежно я отличу горячий паяльник от холодного даже с закрытыми глазами, но вот дальше навык меня подведет. Поэтому все, что касается разработки и изготовления плат — это работа моих товарищей и коллег andrey_9999a и StDmitriev.

Он является упрощенным частным случаем вычисления интеграла Фурье:
Итак, вернемся к детектору.

$\int_0^Tf(x)sin(\omega t)dt$

В случае цифрового сигнала, для вычисления амплитуды произвольной гармоники потребуется выполнить дискретное преобразование Фурье, для Ардуины это тяжеловато, но хитрость состоит в том, что если взять в качестве несущей частоты Fc такую, что она будет ровно в 4 раза меньше частоты дискретизации Fs, то амплитуду этой гармоники можно вычислять демонически проще.

В этом случае dt = 2π*(Fs/4)/Fs = π/2, и на период несущей приходится всего 4 сэмпла:

Если все сдвинуть на π/4 то сэмплы будут принимать только два значения: √2/2 и -√2/2, для простоты оставим только знаки — «+» и «-».

Суть же метода состоит в том, что синусную фазу мы представляем как последовательность знаков «+» «+» «-» «-», а косинусную как «+» «-» «-» «+».

Указатели головы и хвоста у них общие — bH и bT. Пусть входной сигнал лежит в буфере sn, у нас есть два кольцевых буфера усреднения для синусной и косинусной фазы — bs и bc размером N. Счетчик циклов усреденения C = 0. В начальный момент времени bH = N-1, bT = 0.

Берем из входного буфера по 4 сэмпла и складываем согласно последовательностям знаков.

Пример кода

a = sn(i)
bs(bH) = a
bc(bH) = a
s1 = s1 + a - bs(bT)
s2 = s2 + a - bc(bT)
bH = (bH + 1) % N
bT = (bT + 1) % N

a = sn(i+1)
bs(bH) = a
bc(bH) = -a
s1 = s1 + a - bs(bT)
s2 = s2 - a - bc(bT)
bH = (bH + 1) % N
bT = (bT + 1) % N

a = sn(i+2)
bs(bH) = -a
bc(bH) = -a
s1 = s1 - a - bs(bT)
s2 = s2 - a - bc(bT)
bH = (bH + 1) % N
bT = (bT + 1) % N

a = sn(i+3)
bs(bH) = -a
bc(bH) = a
s1 = s1 - a - bs(bT)
s2 = s2 + a - bc(bT)
bH = (bH + 1) % N
bT = (bT + 1) % N

После каждой обработанной четверки сэмплов проверяем счетчик циклов усреднения и если он перевалил за N, то вычисляем амплитуду cA несущей:

if ++cycle >= N cA = sqrt(s1 * s1 + s2 * s2) cycle = 0
end

Вот так это выглядит на идеальном сигнале:

1). Синим показан сам сигнал, красным — значения амлитуды несущей (все приведено к диапазону -1.. нет никаких шумов и все и так отлично работает. В данном случае N=2 т.к.

Теперь добавим немного белого шума и посмотрим, как на это отреагирует наш детектор:

На рисунке выше синим показан зашумленный сигнал, зеленым — исходный, а красным — значение амплитуды. Я добавил белого шума таким образом, чтобы соотношение сигнал-шум было равно 0 дБ. Т.е. В этом случае детектор при N=2 уже ничего не задетектировал, и минимальное N при котором он исправно работет равно 32. размер окна обработки в сэмплах составил 32*4 = 128 сэмплов.

Если задаться неким пороговым значением для этого параметра, то все можно бинаризовать, и говоря по-простому, мы сможем дать ответ на вопрос: присутствует ли заданный тон во входном сигнале или нет? То есть, теперь мы можем анализировать входной сигнал и оценивать некий параметр, который количественно показывает наличие частоты, в четыре раза меньше частоты дискретизации.

Это очень хорошо, но нам нужно передавать биты, а биты могут принимать два значения.

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

защитный интервал — ведь нам же еще нужно бороться с многолучевым распространением и реверберацией. Вместо этого «1» и «0» мы будем кодировать импульсами разной длины, между битами наличествует т.н. Говоря простым языком, защитный интервал — это то место (время), где затухнут все отражения предшествующего бита, все послезвучия и эхо.

Забегая вперед, примем к сведению, что при такой структуре сигнала алгоритм работы приемника очень сильно упрощается: ждем когда появился тон, засекаем начало, ждем, когда тон пропал и опять засекаем время — если полученное время больше похоже по длине на «1» то, наверное мы приняли бит со значением «1», если больше похоже на «0» — то видимо мы приняли бит, со значением «0».

В общем, можно сказать, что это некий вариант морзянки.

Софтовая часть модема

Для нетерпеливых — пример лежит на GitHub. Написан на скорую руку на C# (потому что для ПК я пишу на нем и мне просто так удобнее и быстрее).

Для воспроизведения и захвата звука с микрофонного входа используется замечательная библиотека NAudio.

Вся логика модема находится в классе SUAModem (Simple Underwater Acoustic Modem).

В конструктор передаются следующие параметры:

double sRateHz — частота дискретизации в Герцах;
int wSize — размер окна обработки в сэмплах;
int oneMultiplier — сколько «окон» длится бит со значением «1»
int zeroMultiplier — сколько «окон» длится бит со значением «0»
double eThreshold — порог, скажем о нем позже

Для формирования сигнала из массива байт есть метод ModulateData(byte[] data), который возвращает массив 16-ти битных знаковых сэмплов.

public short[] ModulateData(byte[] data)

public short[] ModulateData(byte[] data)
samples.AddRange(new short[defenseIntervalSmp]); } return samples.ToArray();
}

В основном цикле по передаваемым битам происходит заполнение списка samples. В зависимсоти от текущего передаваемого бита устанавливается длина sLim формируемого сигнала в сэмплах. После каждого бита добавляется защитный интервал.

Конечно...

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

Для генерации тона с частотой $F_n$ при заданной частоте дискретизации $F_s$ соответствующее значение $\delta$ вычисляется просто:

$\delta=2\pi F_n/F_s$

Для формирования и излучения сигнала есть метод TransmitData(byte[] data), который внутри себя вызывает ModulateData:

public double TransmitData(byte[] data)

public double TransmitData(byte[] data)
{ var samples = ModulateData(data); double txTime = ((double)samples.Length) / SampleRateHz; var rawBytes = new byte[samples.Length * 2]; for (int i = 0; i < samples.Length; i++) { var bts = BitConverter.GetBytes(samples[i]); rawBytes[i * 2] = bts[0]; rawBytes[i * 2 + 1] = bts[1]; } using (var ms = new MemoryStream(rawBytes)) { using (var rs = new RawSourceWaveStream(ms, new WaveFormat(Convert.ToInt32(SampleRateHz), 16, 1))) { using (var wo = new WaveOutEvent()) { wo.Init(rs); wo.Play(); while (wo.PlaybackState == PlaybackState.Playing) { Thread.SpinWait(1); } } rs.Close(); } ms.Close(); } return txTime;
}

О принятии очередного байта класс SUAModem сообщает при помощи события DataReceivedEventHandler.

Анализ происходит в отдельном потоке, в методе Receiver. Входные сэмплы послупают в анализ при помощи метода ProcessInputSignal(short[] data), где записываются в кольцевой буфер ring.

А сам приемник живет в методе Receive:

private void Receive()

private void Receive()
int a; while (rCnt >= 4) { a = ring[rRPos]; rRPos = (rRPos + 1) % rSize; rCnt--; dRing1[rHead] = a; dRing2[rHead] = a; s1 += a - dRing1[rTail]; s2 += a - dRing2[rTail]; rHead = (rHead + 1) % windowSize; rTail = (rTail + 1) % windowSize; a = ring[rRPos]; rRPos = (rRPos + 1) % rSize; rCnt--; dRing1[rHead] = a; dRing2[rHead] = -a; s1 += a - dRing1[rTail]; s2 += -a - dRing2[rTail]; rHead = (rHead + 1) % windowSize; rTail = (rTail + 1) % windowSize; a = ring[rRPos]; rRPos = (rRPos + 1) % rSize; rCnt--; dRing1[rHead] = -a; dRing2[rHead] = -a; s1 += -a - dRing1[rTail]; s2 += -a - dRing2[rTail]; rHead = (rHead + 1) % windowSize; rTail = (rTail + 1) % windowSize; a = ring[rRPos]; rRPos = (rRPos + 1) % rSize; rCnt--; dRing1[rHead] = -a; dRing2[rHead] = a; s1 += -a - dRing1[rTail]; s2 += a - dRing2[rTail]; rHead = (rHead + 1) % windowSize; rTail = (rTail + 1) % windowSize; if (++cycle >= windowSize) { cycle = 0; currentEnergy = Math.Sqrt(s1 * s1 + s2 * s2) / windowSize; double de = currentEnergy - prevEnergy; #region analysis if (skip > 0) skip -= windowSize * 4; else { if (isRise) { if (de > -Threshold) { riseSmp += windowSize * 4; } else { // analyse symbol isRise = false; double oneDiff = Math.Abs(oneDurationSmp - riseSmp); double zeroDiff = Math.Abs(zeroDurationSmp - riseSmp); if (oneDiff > zeroDiff) { // Mostly likely "0" AddBit(false); } else { // Mostly likely "1" AddBit(true); } samplesSinceLastBit = 0; skip = defenseIntervalSmp / 2; } } else { if (de > Threshold) { isRise = true; riseSmp = windowSize * 4; } } } #endregion prevEnergy = currentEnergy; if (bPos > 0) { samplesSinceLastBit += 4 * windowSize; if (samplesSinceLastBit >= defenseIntervalSmp * 2 + zeroDurationSmp + oneDurationSmp) { DiscardBits(); } } } } }

Из кода видно, что анализ ведется по 4 сэмпла, при желании можно сохранять состояние и вести обработку и по одному сэмплу, что будет полезно при переносе на какой-нибудь немощный МК.
По мере поступления данных вычисляется значение амлитуды s на частоте sRateHz/4. Вычисляется разница между предыдущим и текущим значением амплитуды и затем сравнивается с заданным порогом, подобранным экспериментально. Пример позволяет в реальном времени этот порог менять.

Резкое увеличение амплитуды свидетельствует о начале «бита», резкий (несколько менее резкий — из-за реверберации) спад — о завершении «бита».

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

Железная часть модема

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

Если у вас еще нет гидроакустических антенн, то самое время их сделать по нашему предыдущему туториалу.

У меня они остались с того раза, так что я этот шаг пропускаю.

Для нескольких десятков метров (может даже для сотни) нам этого вполне хватит. Ту антенну, которая предназначена для передачи мы подключаем к плате усилителя с алиэкспресс. На выход подключена наша гидроакустическая передающая антенна из пьезопищалки. Никаких хитростей тут нет — выход звуковой карты ноутбука идет на вход усилителя, который питается от свинцового АКБ 12 вольт 4 Ач. В моем случае это выглядит вот так:

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

Хоть пьезопищалки и очень чувствительны, все же этого недостаточно. С приемной антенной несколько сложнее. Нам придется собрать плату предусилителя с фильтром на полосу 5-35 кГц.
Коэффициент усиления мы берем 1000.

Схема, дизайн печатной платы и список компонентов предусилителя лежит у нас на GitHub: схема, дорожки — верхний слой и нижний слой, BOM.

Технология ЛУТ обсуждалась стотыщмильенов раз, но дайте же и нам внести свою лепту.

Фото технологического процесса

Итак, берем подходящий журнал, у нас под рукой оказался только этот:

Берем оттуда пару страниц и печатаем на них слои при помощи лазерного принтера.

Совмещаем при помощи иголочек и склеиваем по одной стороне, как показано на фото:

Перед применением утюга смачиваем тонер изопропиловым спиртом:

Утюжим через сложенный вчетверо лист А4:

Размачиваем теплой водой под краном:

После чего получаем заготовку, готовую к травлению: И отмываем остатки бумаги.

Лишнее отрезаем при помощи ножниц по металлу или кому чем удобнее.

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

В результате, после травления и отмывки тонера получаем такой полуфабрикат:

После напайки компонентов и отмывки плата выгладит вот так.

А так выглядит приемная часть в сборе. Питание осуществляется от такого же свинцового АКБ 12 вольт:

Небольшой дисклеймер по поводу фильтра

Если читатель захочет изменить полосу, то предлагаем пересчитать фильтр 8-ого порядка, собранный на дешевом 4-х канальном операционнике TL084C (DA2 на схеме), резисторах R10-R13, R15-R23 и конденсаторах C5-C8, C11, C12, C14 и С15.

На всякий случай приведем здесь АЧХ текущей реализации фильтра:

А вот еще проект для этого фильтра, созданный в приложении Qucs

Опыты и испытания

Для подключения к ноутбуку используем обычный Jack 3.5 мм, самый кончик — сигнал, средний — не подключен, земля — к земле прах к праху. Все усиления и любые эффекты микрофона нужно отключить, а громкостью придется поиграться, чтобы достичь оптимального уровня. Зашкал должен происходить при касании подключенной к предусилителю антенны пальцем и легком поглаживании.

Ниже представлен отрезок сигнала и можно даже на глаз определить где какие значения битов: Если просто положить одну пьезу на другую без усилителя и предусилителя и подключить их к аудиовходу и выходу, то все работает идеально.

Без труда можно «демодулировать» эту часть посылки: 1 0 0 0 1 1 0. Синим показан сам сигнал, красным — разница между текущим и предыдущим значениями амплитуды (фронт), зеленым — разница между предыдущим и текущим значениями (спад). Ноль у нас в два раза дольше единицы, а длительность защитного интервала равна длительности нуля.

5х1. Далее, также без усилителя и предусилителя опускаем наши антенны в металлический бак, размерами 3х1. У нас такой стоит в лаборатории, и мы завели себе правило, что не занимаемся никакой связью, если она хоть как-то не в состоянии работать в этом баке. 5. А с учетом того, что мы обычно проверяем готовые девайсы с энергетикой, рассчитанной на тысячи метров, можете представить что там творится. Дело в том, что в таком замкнутом объеме энергии особо некуда деваться — звук чудесно и многократно отражается от металлических стенок и в точке приема получается каша.

При том что первый в открытой воде работает до 8000 метров, а второй — до километра.
Конечно, все коммерческие продукты не используют такие примитивные схемы модуляции, о которой идет речь в статье и устроены гораздо сложнее, но нам сейчас важно понять азы и с пользой поделать что-то руками. Например, два наших модема RedLINE устойчиво работают в этом баке только на расстоянии не больше двух метров, а два uWAVE-а стабильно работают примерно на 1 метре.

В общем, опускаем антенны в бак на расстояние порядка 50 сантиметров и получаем уже нечто гораздо менее благообразное, чем при непосредственном контакте антенн:

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

В обоих случаях я передавал сообщение «123» и эти семь бит принадлежат символу единицы.
Выглядело это примерно так, потом интерфейс был немного переделан

:-)» состоящее из 19 байт занимает 9. Из скрина выше видно, что при тех настройках, передача сообщения «Hello, habr!!! К слову сказать, чтобы модем работал в нашем баке пришлось увеличить защитный интервал так, что скорость передачи упала до ~3 бит/с. 132 секунд, то есть скорость передачи составила 16,6 бит/с.

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

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

5 до 2 метров. Антенны приемника и передатчика опускались прямо с берега, глубина там резко уходит с 0. Из 20 переданных сообщений по 3 байта, в шести из них было побито по одному байту. В опыте, который показан на фото выше были, как ни странно, самые плохие условия, дистанция там была всего порядка 5 метров — это вообще была первоначальная настройка.

Потом, когда мы подключили приемник ко второму ноутбуку и перенесли на другой берег пруда (дистанция порядка 30 метров) передача проходила значительно лучше — на 40 сообщений размером от 3 до 13 байт было всего пару ошибок.

На следующем фото на карте видны места, где располагались антенны.

Заключение и дальнейшие изыскания

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

В этой системе определяют положение пингера, который периодически передает свою глубину. Чтобы не быть голословным относительно построения навигационных систем на простых сигналах, взгляните на интересную работу, в которой построили полноценную длиннобазисную навигационную систему. Так что да-да, горшки не боги обжигают, дорогу осилит идущий, терпение и труд, учиться, учиться, учиться — вот это вот все. Значение глубины при этом кодируется расстоянием между двумя простыми импульсами на определенной частоте.

Нечто подобное, но не DIY мы уже делали на базе наших модемов uWAVE, о чем даже попытались снять видео. Возможно, при наличии времени мы сделаем DIY-проект по позиционированию автономного пингера, излучающего простые сигналы. Будет очень интересно услышать ваши мнения на этот счет — очень важно иметь подтверждение, что делаешь что-то не напрасно.

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

  • сделать вычисление порога адаптивным
  • анализировать ширину сигналов автоматически
  • попробовать использовать разные длины для разных битовых комбинаций
  • прикрутить обожебоже помехоустойчивое кодирование
  • перенести все это на ардуино
  • громкость и порог приходится долго и нудно подбирать, поэтому хорошо бы добавить в предусилитель АРУ

На этом заседание объявлю закрытым, а если вас заинтересовала тема, вот список наших предыдущих статей:

Подводный GPS с нуля за год
Подводный GPS на подводном роботе: опыт использования
Мы сделали самый маленький в мире гидроакустический модем
К вопросу о влиянии цианобактерий на речевые функции президента
Делаем простую гидроакустическую антенну из мусора
Сеанс передачи видео звуком через воду с разоблачением
Подводный «GPS» на двух приемопередатчиках
Навигация под водой: пеленгуй не пеленгуй — обречен ты на успех
Подводный GPS: продолжение

P.S.

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

P.P.S.

Железки далеко не убирайте — в следующий раз мы с их помощью будем опять передавать «видео» через воду.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть