На даче холодно, и вы хотите за несколько часов до своего приезда туда включить обогреватель, или вас беспокоит возможность аварийного отключения системы отопления загородного дома в ваше отсутствие. Все эти проблемы можно решить с помощью GSM модуля, который умеет отправлять и получать SMS сообщения и реагировать на них, включая и выключая нужную нагрузку. В теории все просто, на практике же на пути реализации подобного устройства есть множество подводных камней.
Мой план заключался в том, чтобы создать простое и дешевое устройство, оснащенное двумя датчиками температуры, датчиком влажности, GSM модулем, а также твердотельным реле и розеткой для подключения нагрузки. То, что получилось в итоге, можно увидеть на фото. В качестве датчика температуры и влажности был выбран климатический сенсор BME280, его канал давления не используется. На фото его можно увидеть под прозрачным колпачком слева от основного модуля. Такое расположение уменьшает влияние тепловыделения внутри корпуса на показания датчика. В качестве колпачка используется китайская пластиковая пробирка с двумя отверстиями для вентиляции. Второй датчик температуры выносной, сделан на DS18B20. Он расположен внутри металлического зонда, с корпусом соединен кабелем через обычный аудио разъем для наушников. Зонд предназначен для измерения температуры непосредственно отопительной системы. Основной объем корпуса занимает твердотельное реле (я выбрал помощнее) и преобразователь из 220В в 5В для питания схемы. Розетка для подключения нагрузки смонтирована на задней стороне корпуса, на фото она не видна. OLED дисплей на базе контроллера SH1106 отображает показания датчиков, а также показывает, включена ли нагрузка. Для управления всей системой используется модуль Arduino Pro Mini в версии 3.3В 8МГц. Я не большой фанат этой платформы, но обилие библиотек, в том числе заботливо выпиленных автором, делает ее оптимальным выбором, когда нужно быстро сделать что то простое.
GSM модуль SIM800L размещен в отдельном металлическом корпусе для уменьшения создаваемых им помех на остальные части схемы. Как показала практика, помехи от этого уменьшаются не сильно. А радикально их уменьшает выносная антенна, подключенная экранированным кабелем к коаксиальному разъему, на фото выше она на переднем плане. Но об этом подробнее мы поговорим позже.
Про использование GSM модулей написано немало статей, в том числе на хабре, поэтому я буду избегать повторений и расскажу о том, что не встречалось мне в публикациях на эту тему, а именно о том, как сделать на базе этого модуля надежно работающее устройство.
В гаражах, где я частенько бываю, недавно поставили на въезде шлагбаум, который открывается, если позвонить на определенный номер. Судя по всему, он сделан на похожем GSM модуле. Меня удивило, как сложно бывает дозвониться по этому номеру, чтобы он открылся. Теперь я знаю множество причин для этого. Это знание стоило мне нескольких месяцев экспериментов и внушительного количества потраченных на них денег. Я надеюсь, что теперь это знание послужит кому то еще. Рассмотрим, на что важно обратить внимание, продвигаясь от очевидных аппаратных проблем к менее очевидным программным.
Первое, что важно сделать правильно, — вставить сим-карту
Мне казалось очевидным, что сим-карта вставляется скошенным уголком вперед. С неделю я пытался понять, почему модуль не желает регистрироваться в сети, попутно осваивая команды в терминале. В итоге на каком то англоязычном форуме я нашел упоминание о том, что вставлять ее нужно скошенным уголком назад. Странно, что она вообще вставляется и так и эдак.
Чтобы хорошо работать, нужно хорошо питаться
Требования к питанию у GSM модуля достаточно специфические. Он сделан на базе микросхемы, разработанной для кнопочных мобильных телефонов, и рассчитан на питание непосредственно от литиевого аккумулятора. Поэтому, 5В для него много, а 3.3В — мало. К тому же, в режиме передачи на максимальной мощности он способен потреблять ток до 2А. Если источник питания не способен обеспечить нужный ток, GSM модуль может перезагрузиться при попытке регистрации в сети и продолжить перезагружаться в бесконечном цикле. Периоды пикового потребления обычно длятся меньше секунды, поэтому есть соблазн применить слаботочный стабилизатор с накопителем энергии для периодов пиковой нагрузки. В качестве такого накопителя можно применить литиевый аккумулятор. При этом важно обеспечить возможность его отключения и важно не забыть ей воспользоваться, иначе отключение устройства от сети закончится глубоким разрядом аккумулятора и его необратимым повреждением. Другой вариант — это поставить вместо аккумулятора ионистор (суперконденсатор). Он не боится глубокого разряда. Но у него тоже есть проблемы с надежностью. Одна ячейка ионистора обычно рассчитана на напряжение от 2.5 до 3В. Ионисторы, рассчитанные на большее напряжение, состоят из нескольких ячеек (обычно из 2-х). При этом, однако, дисбаланс напряжения на ячейках может закончится пробоем ячейки. Такой дисбаланс легко получить за счет разницы в емкости ячеек или разницы в токе утечки. Следует также учитывать параметр внутреннего сопротивления ионистора. Ионисторы с большим внутренним сопротивлением на больших токах бесполезны, а ионисторы с малым сопротивлением стоят не дешевле аккумулятора. После того, как у меня ионистор скоропостижно скончался из-за дисбаланса ячеек, я просто применил преобразователь из 220В в 5В достаточной мощности. Чтобы понизить напряжение до нужного GSM модулю, я поставил между преобразователем и модулем обычный кремниевый диод. На таком диоде обычно падает 0.7В, так что модулю достаются необходимые 4.3В. После диода полезно поставить электролитический конденсатор большой емкости. Он сгладит провалы напряжения при внезапном включении передатчика.
От передающей антенны лучше держаться подальше
Даже после того, как я обеспечил GSM модулю требуемое питание, симптом перезагрузки периодически проявлялся, но на этот раз перезагружалась Arduino. Наблюдение за ее питанием при помощи осциллографа показало, что питание тут непричем. Судя по всему помеху создавал передатчик модуля, поскольку проблема возникала тем чаще, чем хуже были условия приема сигнала базовой станции. Столь радикальный эффект помех от передающей антенны вполне объясним, если вспомнить, что передатчик модуля способен выдать в антенну 2 ватта. Такая мощность может за 5 минут вскипятить миллилитр воды или нагреть ваше ухо на несколько градусов. Для борьбы с этой проблемой были опробованы разные методы. Для начала я подключил внешнюю антенну, которая располагалась снаружи корпуса и соединялась с модулем коротким коаксиальным кабелем. Однако, ожидаемого эффекта это не дало. Тогда я расположил модуль в отдельном металлическом корпусе, к которому снаружи крепилась антенна. Стало лучше, но не сильно. Радикально улучшил ситуацию только вынос антенны на некоторое расстояние от устройства за счет ее подключение коаксиальным кабелем достаточной длины.
Почему так происходит, легко понять из физических соображений. Типичная антенна — это 'четвертьволновой штырь', то есть половинка от дипольной антенны. Но, чтобы создать электрическое поле, половинки диполя недостаточно, нужна вторая половинка, тогда между отрицательно и положительно заряженными элементами антенны возникнет электрическое поле. У правильной штыревой антенны второй половиной является либо поверхность земли, либо корпус прибора, либо специальные проводящие 'противовесы'. Но для маркетологов все это слишком сложно, поэтому нам обычно продают только половинку от нормальной антенны. Как же она работает? Очень просто — второй половинкой является кабель, которым подключена антенна. То, что он экранирован, ничего не меняет. Внешняя поверхность его оплетки играет роль второй половинки дипольной антенны. При этом помеха легко наводится на проходящие по соседству провода несмотря на то, что кабель казалось бы экранирован. Ну а если кабеля нет, например мы спрятали модуль в металлический экран, из которого торчит антенна? Если экран большой (по сравнению с длиной волны), то он работает, как вторая половина излучателя, а если маленький, то излучают прочие провода, которые подведены к этому модулю, совершенно не важно, какие. Следующий рисунок иллюстрирует вышесказанное (плюсы и минусы показаны для наглядности, в реальности заряд элементов антенны меняет знак с частотой несущей).
Слева показана 'правильная' антенна, ее подводящий кабель не излучает помех. На среднем рисунке показана антенна, которую вы обычно покупаете. Здесь подводящий кабель является частью излучателя и создает помехи проходящим поблизости проводам. Справа показана ситуация, когда источник сигнала спрятан в компактный экранированный корпус. Здесь любые провода, подведенные к такому корпусу, являются частью излучателя.
Мораль заключается в том, что единственный надежный способ защититься от помех, создаваемых передающей антенной, — унести ее подальше от остальной электроники, подключив коаксиальным кабелем достаточной длины. Какая длина является достаточной? Расстояние естественно соизмерять с длиной волны, в данном случае это максимум 30 см. Это и есть минимальное расстояние на которое следует отнести антенну, но чем дальше, тем лучше.
Не все последовательные порты одинаково полезны
В простых AVR микроконтроллерах, которые все обычно и используют, аппаратный последовательный порт всего один, и он используется для загрузки программы. Поэтому, программная реализация последовательного порта является очень популярным решением. Я собираюсь доказать утверждение, которое многим покажется неожиданным, — для управления GSM модулем программная реализация последовательного порта непригодна вообще.
Суть проблемы в том, что программная реализация последовательного порта запрещает прерывания на все время передачи или приема очередного символа. Казалось бы, что в этом плохого, так многие делают. Например, реализация протокола 1-Wire для чтения термометров Dallas Semiconductor тоже запрещает прерывания на время передачи одного бита, то есть на 65 микросекунд. Это конечно тоже не слишком хорошо. Если в системе есть другие обработчики прерываний, они не смогут обеспечить время реакции на прерывание меньше этих 65 микросекунд. Если запрос на прерывание приходит, когда они запрещены, он будет обработан только после того, как прерывания разрешат снова. Например, аппаратный последовательный порт использует прерывания для того, чтобы положить в буфер приемника очередной принятый символ. Если следующий символ придет, пока не обработано прерывание от предыдущего, тот будет потерян. Это значит, что работать со скоростью больше 115200 бит в секунду аппаратный последовательный порт не сможет. В случае программной реализации последовательного порта все хуже. Для его работы нужно, чтобы время реакции на прерывание было меньше времени передачи одного бита. Это ограничивает нас скоростью 9600 бит в секунду.
Более серьезная проблема заключается в том, что программная реализация последовательного порта сама запрещает прерывания. Причем время, на которое она их запрещает (время передачи или приема одного символа) всегда примерно в 10 раз больше, чем максимальное время обработки прерывания, требуемое для корректной работы приемника того же программного последовательного порта. То есть, он всегда мешает сам себе до такой степени, что одновременно не может принимать и отправлять данные. Конечно, в большинстве случаев это и не требуется. В большинстве, но не в нашем случае с GSM модулем. Он таки может неожиданно для нас по собственной инициативе начать передавать данные (например при получении SMS сообщения). И в случае применения программной реализации последовательного порта это легко может привести к сбою протокола обмена с модулем. Поэтому, я просто применил один и тот же аппаратный последовательный порт и для программирования Arduino и для общения с GSM модулем. Неудобно конечно, но это единственный способ сделать надежно работающее устройство.
Асинхронному протоколу — асинхронный обработчик
Асинхронный протокол — это такой протокол, при котором одна сторона обмена может начать передавать информацию неожиданно для другой стороны, то есть без всякой синхронизации с ее сообщениями. Именно таков протокол обмена с GSM модулем. Он исправно отвечает на запросы со стороны Arduino, но может и начать передавать что то свое, например сообщить о принятом SMS сообщении. И это создает реальную проблему, поскольку ни одна из известных мне библиотек для работы с модулем под Arduino асинхронность протокола не учитывает вообще никак. Представим себе, что Arduino передала модулю команду, а модуль в тот же самый момент передал информацию о принятом SMS сообщении. Эта информация будет принята вместо ответа на команду. В результате в качестве ответа на команду библиотека вернет ошибку (в лучшем случае, в худшем все 'повиснет'), а сообщение о принятом SMS будет потеряно.
Починить это легко — нужно просто написать свой, асинхронный обработчик протокола. Асинхронный обработчик предъявляет только необходимый минимум требований к ответам модуля на его команды. На каждую команду модуль в итоге отвечает либо OK, либо ERROR. И это все, что нужно для того, чтобы зафиксировать ответ. Все остальные строки, которые приходят от модуля, обрабатываются независимо от того, пришли они в ответ на команду или сами по себе. Смысл этих строк всегда можно определить по их началу. Если строка начинается с +CSQ, то она содержит информацию о качестве сигнала. Если она начинается +CMT, то это информация о полученном SMS, и в ней содержится адрес отправителя. Первая строчка посылается в составе ответа на команду AT+CSQ, авторую модуль присылает по собственной инициативе, но для нас это различие абсолютно несущественно. Принятые SMS сообщения модуль направляет непосредственно в последовательный порт. Это позволяет избежать чтения их из памяти и последующего удаления. Чтобы мы могли распознать SMS сообщения в общем потоке сообщений от модуля, они должны начинаться с символа #, в противном случае сообщение игнорируется.
Созданная автором библиотека, реализующая вышеописанный подход, находится здесь.
Чтобы получать строки, начинающиеся с определенной последовательности символов, клиент создает специальный объект — ловушку. Таких ловушек он может создать любое количество. Полученные от модуля строки, отличные от OK, ERROR, которые не попали ни в одну из ловушек, просто игнорируются. Поскольку такая архитектура не требует полного анализа ответов модуля на множество различных типов команд, код библиотеки в разы компактнее любой из известных мне библиотек.
Что в итоге?
В итоге получилось устройство, которое надежно работает в зоне со слабым покрытием, даже лучше, чем среднестатистический телефон. Ниже приведена его полная схема.
Для заинтересовавшихся — ссылка на гитхаб, где вы найдете исходники проекта и описание команд, которые можно посылать устройству в SMS сообщениях.
Автор: oleg_v