Привет, глубокоуважаемые!
В этой статье, по многочисленным просьбам мы расскажем как сделать простейший гидроакустический модем: немного цифровой обработки сигналов, немного программирования, немного самодельных печатных плат и капля практической гидрологии.
Всем заинтересованным — милости просим под кат, в реверберирующий мир подводной связи!
А вот релевантная картинка, для привлечения внимания:
«В конечном счете смысл нашего существования — тратить энергию… И по возможности, знаете ли, так, чтобы и самому было интересно, и другим полезно.»
(С) АБС, «Полдень, 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.
Итак, вернемся к детектору. Он является упрощенным частным случаем вычисления интеграла Фурье:
В случае цифрового сигнала, для вычисления амплитуды произвольной гармоники потребуется выполнить дискретное преобразование Фурье, для Ардуины это тяжеловато, но хитрость состоит в том, что если взять в качестве несущей частоты Fc такую, что она будет ровно в 4 раза меньше частоты дискретизации Fs, то амплитуду этой гармоники можно вычислять демонически проще.
В этом случае dt = 2π*(Fs/4)/Fs = π/2, и на период несущей приходится всего 4 сэмпла:
Если все сдвинуть на π/4 то сэмплы будут принимать только два значения: √2/2 и -√2/2, для простоты оставим только знаки — «+» и «-».
Суть же метода состоит в том, что синусную фазу мы представляем как последовательность знаков «+» «+» «-» «-», а косинусную как «+» «-» «-» «+».
Пусть входной сигнал лежит в буфере sn, у нас есть два кольцевых буфера усреднения для синусной и косинусной фазы — bs и bc размером N. Указатели головы и хвоста у них общие — bH и bT. В начальный момент времени bH = N-1, bT = 0. Счетчик циклов усреденения C = 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)
{
double alpha = 0;
double phase = 0;
List<short> samples = new List<short>();
BitArray bits = new BitArray(data);
for (int i = 0; i < bits.Length; i++)
{
int sLim = (bits[i]) ? oneDurationSmp : zeroDurationSmp;
alpha = 0;
phase = 0;
for (int sIdx = 0; sIdx <= sLim; sIdx++)
{
alpha = Math.Sin(phase);
phase += delta;
if (phase >= alimit)
phase -= alimit;
samples.Add(Convert.ToInt16(alpha * short.MaxValue));
}
samples.AddRange(new short[defenseIntervalSmp]);
}
return samples.ToArray();
}
В основном цикле по передаваемым битам происходит заполнение списка samples. В зависимсоти от текущего передаваемого бита устанавливается длина sLim формируемого сигнала в сэмплах. После каждого бита добавляется защитный интервал.
Для генерации тона с частотой при заданной частоте дискретизации соответствующее значение вычисляется просто:
Для формирования и излучения сигнала есть метод TransmitData(byte[] data), который внутри себя вызывает ModulateData:
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.
Входные сэмплы послупают в анализ при помощи метода ProcessInputSignal(short[] data), где записываются в кольцевой буфер ring. Анализ происходит в отдельном потоке, в методе Receiver.
А сам приемник живет в методе 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 вольт:
На всякий случай приведем здесь АЧХ текущей реализации фильтра:
А вот еще проект для этого фильтра, созданный в приложении Qucs
Опыты и испытания
Для подключения к ноутбуку используем обычный Jack 3.5 мм, самый кончик — сигнал, средний — не подключен, земля — к земле прах к праху. Все усиления и любые эффекты микрофона нужно отключить, а громкостью придется поиграться, чтобы достичь оптимального уровня. Зашкал должен происходить при касании подключенной к предусилителю антенны пальцем и легком поглаживании.
Если просто положить одну пьезу на другую без усилителя и предусилителя и подключить их к аудиовходу и выходу, то все работает идеально. Ниже представлен отрезок сигнала и можно даже на глаз определить где какие значения битов:
Синим показан сам сигнал, красным — разница между текущим и предыдущим значениями амплитуды (фронт), зеленым — разница между предыдущим и текущим значениями (спад). Без труда можно «демодулировать» эту часть посылки: 1 0 0 0 1 1 0. Ноль у нас в два раза дольше единицы, а длительность защитного интервала равна длительности нуля.
Далее, также без усилителя и предусилителя опускаем наши антенны в металлический бак, размерами 3х1.5х1.5. У нас такой стоит в лаборатории, и мы завели себе правило, что не занимаемся никакой связью, если она хоть как-то не в состоянии работать в этом баке. Дело в том, что в таком замкнутом объеме энергии особо некуда деваться — звук чудесно и многократно отражается от металлических стенок и в точке приема получается каша. А с учетом того, что мы обычно проверяем готовые девайсы с энергетикой, рассчитанной на тысячи метров, можете представить что там творится.
Например, два наших модема RedLINE устойчиво работают в этом баке только на расстоянии не больше двух метров, а два uWAVE-а стабильно работают примерно на 1 метре. При том что первый в открытой воде работает до 8000 метров, а второй — до километра.
Конечно, все коммерческие продукты не используют такие примитивные схемы модуляции, о которой идет речь в статье и устроены гораздо сложнее, но нам сейчас важно понять азы и с пользой поделать что-то руками.
В общем, опускаем антенны в бак на расстояние порядка 50 сантиметров и получаем уже нечто гораздо менее благообразное, чем при непосредственном контакте антенн:
Хотя здесь использован значительно более долгий защитный интервал, все равно видно, что эхо гуляет почти до следующего бита, фронты и особенно спады сильно размыты. Но все еще можно определить содержание сообщения: 1 0 0 0 1 1 0
В обоих случаях я передавал сообщение «123» и эти семь бит принадлежат символу единицы.
Выглядело это примерно так, потом интерфейс был немного переделан
Из скрина выше видно, что при тех настройках, передача сообщения «Hello, habr!!! :-)» состоящее из 19 байт занимает 9.132 секунд, то есть скорость передачи составила 16,6 бит/с. К слову сказать, чтобы модем работал в нашем баке пришлось увеличить защитный интервал так, что скорость передачи упала до ~3 бит/с.
Мы проверяли самоделку в плавательном бассейне, где устойчиво она заработала на 10 метрах.
Еще мы баловались самоделкой на пруду. Я использовал активный гидрофон очень похожий по конструкции на предложенный в статье, только вместо пьезопищалки там применен датчик от парктроника, аккумулятор там смонтирован в катушку, на которую наматывается кабель:
Антенны приемника и передатчика опускались прямо с берега, глубина там резко уходит с 0.5 до 2 метров. В опыте, который показан на фото выше были, как ни странно, самые плохие условия, дистанция там была всего порядка 5 метров — это вообще была первоначальная настройка. Из 20 переданных сообщений по 3 байта, в шести из них было побито по одному байту.
Потом, когда мы подключили приемник ко второму ноутбуку и перенесли на другой берег пруда (дистанция порядка 30 метров) передача проходила значительно лучше — на 40 сообщений размером от 3 до 13 байт было всего пару ошибок.
На следующем фото на карте видны места, где располагались антенны.
Заключение и дальнейшие изыскания
Как и обещал, за мало рублей мы собрали рабочее устройство. Хоть практическая ценность его сомнительна, но сам процесс изготовления и настройки на водоеме будет очень полезен начинающим. На описанном методе детектирования несущей вполне можно придумать разных простых навигационных систем для любительского применения, а что особенно приятно, вычислительная сложность позволяет реализовать метод на простом микроконтроллере.
Чтобы не быть голословным относительно построения навигационных систем на простых сигналах, взгляните на интересную работу, в которой построили полноценную длиннобазисную навигационную систему. В этой системе определяют положение пингера, который периодически передает свою глубину. Значение глубины при этом кодируется расстоянием между двумя простыми импульсами на определенной частоте. Так что да-да, горшки не боги обжигают, дорогу осилит идущий, терпение и труд, учиться, учиться, учиться — вот это вот все.
Возможно, при наличии времени мы сделаем DIY-проект по позиционированию автономного пингера, излучающего простые сигналы. Нечто подобное, но не DIY мы уже делали на базе наших модемов uWAVE, о чем даже попытались снять видео. Будет очень интересно услышать ваши мнения на этот счет — очень важно иметь подтверждение, что делаешь что-то не напрасно.
Тем не менее, возвращаясь к основной теме, отметим, что можно было бы улучшить в предложенной схеме:
- сделать вычисление порога адаптивным
- анализировать ширину сигналов автоматически
- попробовать использовать разные длины для разных битовых комбинаций
- прикрутить
обожебожепомехоустойчивое кодирование - перенести все это на ардуино
- громкость и порог приходится долго и нудно подбирать, поэтому хорошо бы добавить в предусилитель АРУ
На этом заседание объявлю закрытым, а если вас заинтересовала тема, вот список наших предыдущих статей:
Подводный GPS с нуля за год
Подводный GPS на подводном роботе: опыт использования
Мы сделали самый маленький в мире гидроакустический модем
К вопросу о влиянии цианобактерий на речевые функции президента
Делаем простую гидроакустическую антенну из мусора
Сеанс передачи видео звуком через воду с разоблачением
Подводный «GPS» на двух приемопередатчиках
Навигация под водой: пеленгуй не пеленгуй — обречен ты на успех
Подводный GPS: продолжение
P.S.
Как всегда с удовольствием выслушаем замечания и предложения, обоснованную критику и одобряющие возгласы )
P.P.S.
Железки далеко не убирайте — в следующий раз мы с их помощью будем опять передавать «видео» через воду.
Автор: Aleksandr Dikarev