Всем привет!
Как-то раз зимой у меня сгорел греющий кабель в водопроводе (он не даёт замёрзнуть воде в трубах, проложенных близко к поверхности). Кабель конечно пришлось заменить, водопровод отогрелся и снова заработал, однако возникло жгучее желание "что-то с этим сделать". Хотя бы узнавать о его неисправности заранее, а в лучшем случае - ещё и автоматически отогревать. Идея в общем-то несложная: надо мерять температуру трубы и включать обогрев (при помощи любого электрического обогревателя), если она мёрзнет. Всё просто, но датчика температуры под рукой нет. Конечно, можно его заказать на всем известном китайском сайте, или на не менее известном российском, но это совершенно неспортивно. Потому попробуем изготовить датчик из имеющихся под рукой компонентов. Для этого нам понадобится: звуковая карта (наверняка найдётся в компьютере), два jack-разъёма (от наушников или микрофонов), один терморезистор и пара резисторов.
Disclaimer: всё нижеизложенное просьба воспринимать как забавный способ размять мозги и развлечься. Само собой, "по-хорошему" надо обзавестись нормальным датчиком, а не придумывать велосипед. Однако мне было интересно собрать что-то не очень типичное, а заодно и разобраться в генерации и анализе звука в коде.
Основная идея
Итак, у нас есть термистор. В теории он является источником аналогового сигнала, меняя своё сопротивление в зависимости от температуры. Чтобы оцифровать его нам потребуется аналогово-цифровой преобразователь (АЦП, ADC). В компьютере такой есть, и находится он в звуковой карте, оцифровывая сигнал, поступающий с микрофона. Беглое гугление по фразе thermistor sound card
приводит нас к старой статье, которая описывает практически то, что нам нужно [1]. Вкратце, суть в том, что звуковая карта для формирования звука создаёт напряжение на своём выходе, а для получения - замеряет соответственно напряжение на микрофонном входе и мы можем генерировать напряжение на выходе звуковой карты и замерять его на входе. Однако нам надо измерять сопротивление. Чтобы это сделать, можно собрать схему, известную как делитель напряжения [3], примерно таким образом.
Самым простым способом было бы сгенерировать постоянный сигнал на выходе звуковой карты, после чего замерять уровень громкости микрофона. Однако на входе и выходе звуковой карты расположены фильтрующие конденсаторы [2], потому произвольно выбранный сигнал при прохождении через них будет искажён. Не вдаваясь в подробности (о них мельком упомянем ниже), сигнал не будет искажаться, в частности, если он будет иметь вид некоторой синусоиды с частотой в рамках слышимого диапазона (примерно 20-20000 Гц). Более формально, уровень сигнала в момент времени t должен определяться формулой следующего вида:
Где t - время от начала генерации сигнала, в секундах, ω - частота, A - амплитуда (она же громкость). При этом на входе микрофона появится аналогичный синусоидальный сигнал, с такой же частотой, но с другой амплитудой A1.
Легко заметить, что генерируя подобный сигнал мы получим на цифро-аналоговом преобразователе (ЦАП) звуковой карты источник переменного напряжения V1, которое прямо пропорционально амплитуде генерируемого сигнала A (с некоторым коэффициентом k). На входе звуковой карты получим сигнал с амплитудой A2, которая прямо пропорциональна напряжению на входе V2 (с коэффициентом k2). Исходя из формулы делителя напряжения:
Откуда получаем искомое сопротивление термистора:
Здесь возникает проблема с определением коэффициентов k и k2, которые зависят от внутреннего устройства звуковой карты, а соответственно могут меняться. Заметим, что оба коэффициента нам не нужны, важно лишь их отношение (в некотором смысле это соотношение громкости звука на выходе карты и чувствительности микрофона). Чтобы его найти, можно откалибровать датчик: заменить термистор на резистор с известным сопротивлением, провести замеры, после чего получить из формулы соотношение k и k2. Однако это неудобно. Можно ли обойтись без калибровки? Наверное нет, но можно её автоматизировать. Вспомним, что в выходе звуковой карты есть отдельные контакты для левого и правого каналов. Мы можем на выход правого канала подсоединить резистор с известным сопротивлением, на выход левого - термистор, а потом просто сделать два замера: сначала калибровочный, а затем основной. Схема включения таким образом приобретает вот такой вид:
Как распознать синусоиду во входном сигнале
Небольшое отступление относительно того, как анализировать входной сигнал, который мы получим с микрофона. Сам по себе сигнал представляет собой последовательность измерений амплитуды сигнала на входе микрофона, т.е. по сути последовательность вещественных чисел. Рассматривая эти числа как функцию времени мы достоверно знаем, что она является синусоидой (причём более того, мы знаем её частоту ω - это ровно та частота, которую мы использовали при генерации выходного сигнала).
Известно, что любую достаточно гладкую функцию можно представить в виде суммы некоторого количество гармонических колебаний (синусоид) с разными частотами. Делается это при помощи преобразования Фурье, про которое была статья на Хабре [4]. Его результатом мы получим набор амплитуд и частот таких, что если складывать синусоиды с соответствующими параметрами, получится исходная функция.
Применительно к нашему входному сигналу, мы знаем, что он представляет собой одну единственную синусоиду. Потому, после применения преобразования Фурье, в его результате достаточно будет найти максимальную амплитуду - она и будет искомой. А далее её уже можно сравнивать с той амплитудой, с которой генерировался исходный сигнал.
Также можно отметить, что фильтрующие конденсаторы на входе и выходе звуковой карты работают как фильтр верхних частот, т.е. отсекают в сигнале все колебания с достаточно низкими частотами. Именно поэтому генерация равномерного сигнала не сработает - его частота по сути стремится к нулю, соответственно через фильтр он никогда не пройдёт.
Talk is cheap, show me the code
Для реализации был выбран C#. Во-первых, потому что хотелось его вспомнить. А во-вторых, была надежда на то, что можно полученный софт окажется кроссплатформенным (оправдалась лишь частично). Для работы со звуковыми устройствами после некоторого гугления была выбрана библиотека NAudio [5].
Генерация сигнала при помощи NAudio выглядит достаточно просто - создаём класс, унаследованный от ISampleProvider
и реализуем в нём Read
, который будет заполнять значениями сигнала предоставленный буфер.
Генерация сигнала на выходе звуковой карты
class ThermoSampleProvider : ISampleProvider
{
public const int SAMPLE_RATE = 44100;
WaveFormat format;
float freq;
float amp;
int alreadyGeneratedSamples;
public ThermoSampleProvider(float freq, float amp)
{
format = WaveFormat.CreateIeeeFloatWaveFormat(SAMPLE_RATE, 1);
this.freq = freq;
this.amp = amp;
alreadyGeneratedSamples = 0;
}
public WaveFormat WaveFormat
{
get
{
return format;
}
}
public int Read(float[] buffer, int offset, int count)
{
for (int i = 0; i < count; i++)
{
int sample = alreadyGeneratedSamples++;
buffer[offset + i] = amp * MathF.Sin(2 * MathF.PI * sample * freq / SAMPLE_RATE);
}
return count;
}
}
Получение данных с входного устройства особой сложности не представляет - достаточно подписаться на событие DataAvailable и включить запись.
Чтение сигнала с микрофона
class Recorder
{
const int RECORDING_RATE = 44100;
private WaveInEvent device;
public List<float> samples;
public Recorder(WaveInEvent device)
{
this.device = device;
}
public async Task Record(float duration)
{
device.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(RECORDING_RATE, 1);
samples = new List<float>();
device.DataAvailable += OnDataAvailable;
device.StartRecording();
await Task.Delay((int)(duration * 1000));
device.StopRecording();
}
private void OnDataAvailable(object sender, WaveInEventArgs e)
{
var buffer = new WaveBuffer(e.Buffer);
for (int i = 0; i < e.BytesRecorded / 4; i++)
{
samples.Add(buffer.FloatBuffer[i]);
}
}
}
Основная генерация и обработка сигнала оказывается очень простой. В силу асинхронности C# и самой NAudio можно не возиться с потоками и их синхронизацией, а просто запустить параллельно асинхронную генерацию звука и запись его же.
var recorder = new Recorder(inputDevice);
var provider = new ThermoSampleProvider(THERMO_FREQ, THERMO_AMP);
outputDevice.Init(provider.ToStereo(channel == MeasureChannel.Left ? 1 : 0, channel == MeasureChannel.Right ? 1 : 0));
outputDevice.Play();
await recorder.Record(duration);
outputDevice.Stop();
Потом из recorder получаем записанные данные, отрезая тишину в начале и в конце. После чего применяем преобразование Фурье и ищем максимальное по модулю число в его результате (результатом преобразования Фурье будут комплексные числа, амплитудой соответствующего синуса будет именно модуль этого числа).
Остальная часть проекта особого интереса не представляет - он оформлен в виде консольного приложения, которое имеет несколько команд: выдать входные и выходные устройства (на случай если их несколько, чтобы можно было точно указать название) и собственно провести замер.
Выводы
Тестирование показало, что такая конструкция замеряет температуру с достаточно приемлемой точностью (расхождение со стандартным датчиком DHT22) составляет не более полутора градусов. Однако требуется подбирать длительность замера - если она будет слишком маленькой, на выходе преобразования Фурье будут либо совсем неадекватные данные, либо вообще не будет синусоиды с ожидаемой частотой. Очень порадовала NAudio и вообще экосистема C# - проект без проблем заработал как под Windows, так и под Linux (openSUSE). Огорчило лишь то, что под 32-битной версией openSUSE мне не удалось его запустить.
Проект целиком доступен на Github
Бонус: поскольку запустить под x86 версию на C# мне не удалось, есть альтернативная версия, реализованная с использованием Qt на C++
Использованные источники
1. https://www.edn.com/measure-resistance-and-temperature-with-a-sound-card-2/
2. https://noiseengineering.us/blogs/loquelic-literitas-the-blog/ac-vs-dc-coupling-what-is-it
Комментарии (22)
FGV
10.12.2022 21:08+1После чего применяем преобразование Фурье и ищем максимальное по модулю число ...
А зачем собственно делать преобразование по полному спектру и искать? Частота же известна, поэтому достаточно выполнить ПФ для одной частоты и сразу получить амплитуду.
gleb_l
10.12.2022 23:41+6Закапывать в землю термистор, держать постоянно комп включённым, бояться заморозить водопровод из-за BSD или банального зависания, делать БПФ вместо банального сравнения амплитуд на каком-нибудь компараторе или ОУ, или просто используя готовое термореле (номенклатур навалом) - и только для того, чтобы пытаться парировать низкое качество греющего кабеля - это страшный гротеск.
Притом, что: а) частые включения-выключения сами по себе не факт, что продлят его работу (здесь лучше купить бренд и озаботиться качеством герметизации его оконечной гильзы - проку будет больше); б) по температуре поверхности трубопровода (особенно пластикового) сложно судить о температуре воды в нем; в) погрешность метода в 1.5% (относительно абсолютной температуры в К?) заставляет сильно задирать порог включения вверх, что в условиях зимы делает затею бессмысленной (сколько у вас дней зимой с температурой почвы выше +5 например?); г) система не поможет никак узнать об обрыве кабеля и сигнализировать о критической ситуации (если только кабель не греет почву ;))
С точки зрения детекции риска замораживания водопровода правильнее решать другую задачу - диагностику обрыва кабеля - здесь всего-то нужно токовое реле и звонок.
Если делать лютый DIY, и токовое реле ну никак нельзя заказать на озоне или Яндекс-маркете - то тут звуковуха как раз пригодится лучше - сделайте на магнитном сердечнике самодельный трансформатор тока, и сигнал с него подайте на вход карты. И вот здесь уже будет оправдан БПФ (хотя подойдёт и Хартли, так как нужна будет только реальная мощность основной частоты). Дальше уже можно роботом пушать сообщение в телеграм хозяина, если ток через нагреватель внезапно исчез :). Заметьте - и не нужно будет ничего закапывать - достаточно будет пропустить одну жилу питающего нагревательный кабель провода в катушечку, воткнутую через джек в комп - и все на виду, в тепле и сухости. Никаких блуждающих токов, коррозии, земляных работ - и гарантия полной электробезопасности. Трансформатор тока, если не хочется самому делать - можно выпаять из упса.
ftc Автор
11.12.2022 10:30+1Абсолютно верно, гротеск "как он есть". Я ж даже в начале статьи написал, что это в бОльшей степени для "развлечься-поковыряться", нежели для "собрать что-то реально полезное" и уж тем более конструкция не претендует на образец для подражания. Есть даже вторая часть этого гротеска - как с компа управлять релейным модулем.
С другой стороны, сама эта конструкция (с откопанным "из хлама" старым ноутбуком с убитой матрицей) проработала пару месяцев, пока не приехали датчики и контроллеры, так что как временное решение вполне себе работало. Зависания и BSOD-ы не сильно страшны, поскольку все такие датчики в любом случае стоит мониторить на предмет "работает" и "измеряет ли что-то нормальное". У меня такое сделано на базе OpenHAB (куда собираются все данные с датчиков) и бота в телеграме, который в случае чего уведомлениями спамит.
Да, я включал-выключал не сам кабель (у меня версия кабеля, которая заделывается в трубу), а тепловентилятор, который обогревал кессон вокруг оголовка скважины). Кессон я утеплил и поддерживал в нём +5. По факту, надо совсем немного догреть именно этот участок, так как ниже глубины промерзания вода не замёрзнет. Плюс, сама вода, если ей пользоваться, регулярно обновляется в трубе и тоже имеет температуру около +5. Зимой у меня получалось, что при -15 - -20 на улице, обогрев включается раз в полтора часа на примерно полторы-две минуты.
Земляные работы конечно же не нужны, поскольку даже если нет кессона, труба ввода воды (ПНД32) существенно уже, чем диаметр скважины (120мм, если я не ошибаюсь) и места под датчик в ней навалом.
А про трансформатор тока идея мне нравится, но там же придётся какой-то конвертер тока в напряжение собирать. Или путаю чего?
Andy_Big
11.12.2022 11:54+4А про трансформатор тока идея мне нравится, но там же придётся какой-то конвертер тока в напряжение собирать. Или путаю чего?
Придется. Девайс, конечно, сложный, но если очень постараться, то можно справиться с пайкой одного резистора, из которого этот конвертер и состоит :)
ftc Автор
11.12.2022 11:55+2Целый резистор. Ужас какой. Боюсь, не справлюсь :-)
Andy_Big
11.12.2022 12:40А если серьезно - совет использовать трансформатор тока для контроля целостности цепи вполне здравый, вот только я бы советовал купить готовый, на Али их полно на любой вкус. Ну и разумеется ток через цепь должен быть только переменным, контролировать трансформатором цепь с постоянным током по понятным причинам не выйдет :)
engine9
11.12.2022 13:34+1Постоянку тоже можно контролировать, через магнитный усилитель. Правда, используя управляющую обмотку в роли датчика тока. Возможно даже получится использовать какой-нибудь ферритовый многообмоточный дроссель из компьютерного БП, но это не точно.
engine9
11.12.2022 00:43+5Мне кажется, что тот же терморезистор правильнее использовать как часть времязадающей RC цепи и измерять не амплитуду, а частоту. Так повысится точность. Если хочется изврата с аналоговыми схемами и аудиокартой.
lxsmkv
11.12.2022 06:53+6Я думал будет что-то типа "резонансная частота трубы меняется при снижении температуры". Безо всяких терморезисторов, чисто на звуке. Вот это было бы вообще крышесносно.
gev
11.12.2022 15:50+3Нужно стучать по трубе, например, с помощью релюхи, записывать звук микрофоном и нейронками распозновать изменения (вода, или уже лед)!
iliasam
А если взять старую звуковую карту с Gameport, то и с синусоидами не придется заморачиваться - благо там были входы, специально рассчитанные на измерение сопротивления (переменных резисторов джойстика).
Если нужно измерить амплитуду только одной частоты в сигнале, то можно использовать https://ru.wikipedia.org/wiki/Алгоритм_Гёрцеля
ftc Автор
Интересная штука, не знал, спасибо!