Моргания лампочки и замыкание контактов — дело интересное и полезное для первых шагов. Но, как все мы помним со школы, чем нагляднее эксперимент, тем он интереснее. Я продолжу развивать проект из предыдущих серий, и сегодня мы прикрутим термодатчик 1-Wire для того, чтобы контролировать температуру в твоем многострадальном холодильнике. Того и гляди, скоро у тебя появится «умный» холодильник :).
Так как я продолжаю повествование с некоей точки, а не с самого начала, то пройдусь по тому, что уже имеется. В нашем арсенале Arduino mega2560 с поднятой ОСРВ scmRTOS. Прошивка позволяет моргать светодиодом L на плате в разных последовательностях в зависимости от «аварийного» или «нормального» состояния, а также «плеваться» в консоль грязными ругательствами (ведь ты именно такие выбрал?) в качестве уведомления об изменении состояния. «Нормальность» состояния определяется замкнутостью контактного датчика. Последовательность можно менять из консоли. Исходники проекта выложены на GitHub.
Идея прикрутить термодатчик зародилась у меня еще до того, как я начал делать этот проект. Последовательность действий (а именно так и нужно действовать — последовательно и не пытаться забегать вперед) оттягивала этот момент, и я особо не забивал себе голову деталями. Но вот время пришло.
Так в чем проблема? А вот в чем: можно было взять обычный резистивный термодатчик и использовать встроенный АЦП микропроцессора. Но! Я взялся за этот проект с правилом: минимум паяльника и дополнительного нестандартного оборудования, все из коробки. При использовании же резистивного термодатчика необходимо городить делитель напряжения, а значит, требуется минимальное, но погружение в схемотехнику. Так что этот вариант отпал.
Остался второй вариант — цифровой термодатчик. Правда, с ним тоже беда. Цифровой термодатчик подключается по интерфейсу 1-Wire, а такого интерфейса на плате нет. Зато есть вариант минимальными усилиями сделать программную эмуляцию этого интерфейса. Дополнительный бонус этого решения — термодатчиков можно посадить целый рассадник на одну проводную линию, в отличие от резистивного термодатчика (естественно, рекомендуется ознакомиться с матчастью, так как есть ограничения на длину линии, общее сопротивление и прочее).
Немного о самом 1-Wire
1-Wire — это так называемый однопроводный интерфейс. Примечателен он тем, что для обмена данными с устройствами по этому интерфейсу требуется всего один сигнальный провод и один провод «земли». Что еще более примечательно, устройства спроектированы таким образом, что по этому же сигнальному проводу они и «кормятся» (то есть для питания устройства используется все тот же сигнальный провод), такой режим называется «паразитным питанием». Для реализации такого способа питания в устройствах ставят достаточно емкий конденсатор.
Для того чтобы начать сессию обмена данными, необходимо сформировать сигнал «сброс». Для этого мастер передает в линию данных 0 на время не менее 480 мкс. После чего мастер отпускает линию данных и начинает слушать линию. За счет резистора подтяжки линия данных установится в 1. Если на линии присутствует устройство (датчик), то он передаст мастеру подтверждение сброса, для этого он удерживает линию данных в 0 на время 60–240 мкс. Считав состояние линии, мастер узнает о присутствии на шине устройств, готовых к обмену.
</p>
Теперь, когда с подключением ты более или менее разобрался, приступим ко второй части нашего остросюжетного боевика. Нужно писать софт. Я, как и большинство программистов, создание ленивое, поэтому я вопросил у Всезнающего Гугла, что уже придумано до нас и надо ли изобретать велосипед.
Самое вразумительное, что я нашел, — это библиотека OneWire, рекомендуемая ардуиновцами. Также нам пригодится творчество еще одного товарища из ардуиновского сообщества — библиотека, реализующая протокол обмена данными с термодатчиком.
Можно просто взять, собрать все это в обычный скетч и прошить в железку, на чем и успокоиться. Но ты помнишь про холодильник? А значит, будем вкорячивать это добро в наш проект.
Предварительные причесывания
Начал я с простого — заставил хотя бы собираться библиотеки. Обе библиотеки используют ардуиновские функции, поэтому пришлось внести некоторые изменения. Для начала добавим файл OneWire_Port.h
в проект (он будет портом библиотеки OneWire для проекта) и приинклюдим его в файл OneWire.h
, а затем начнем причесывание. А именно:
- OneWire построена таким образом, что ей при создании экземпляра объекта скармливается номер ноги, на которой у тебя будет линия 1-Wire. Это тащит за собой кусочек мрака из недр библиотек Ардуино, поэтому я пошел простым путем и зашил хардкодом в конструктор класса OneWire нужные мне ноги. Да, теряем универсальность, но я пока не вижу применения с двумя шинами 1-Wire (хотя… ну да не сейчас). Исходя из схемы платы, я выбрал ногу PA6, которая выходит на колодку DIGITAL пин 28.
- OneWire использует задержки в микросекундах для реализации протокола 1-Wire, подсунем библиотечную функцию _delay_us() в файл
OneWire_Port.h
- OneWire любит отключать прерывания во время выполнения очень маленьких задержек (несколько микросекунд), и я ее понимаю. Но сразу же оглянемся и подумаем о том, что у нас все-таки будет ось. А значит, включение прерываний разумнее проредить немного, чтобы случайно не потерять контекст выполнения на неопределенное время. Библиотека использует ардуиновские функции работы с прерываниями, подсунем ей стандартные через файл
OneWire_Port.h
: - В драйвере термодатчика используется задержка, измеряемая в миллисекундах. Тут разумнее использовать вызов функции ОС, особенно учитывая размер этих задержек. Для замены sleep на вызов функции ОС пришлось немного погородить макросов в
OneWire_Port.h
, комментарии в коде.
Внедрение агента в банду
Теперь либы собираются, настал черед вкрутить их в код проекта. Как удостовериться, что оно заработало? Элементарно: создаем экземпляр класса OneWire, затем DallasTemperature с параметром шины, на которую подключены термодатчики, и начинаем все это активно использовать.
В проекте уже есть простенький терминал, добавляй туда команду, по которой будет производиться опрос термодатчика и вывод значения в терминал. Для удобства я добавил еще одну команду — поиск термодатчиков, по этой команде опрашивается линия, ответившие термодатчики заносятся в «кеш» библиотеки, после чего для найденных термодатчиков можно получить адреса и вывести их в терминал. Отмечу отдельно алгоритм поиска устройств на линии, очень увлекательный процесс, описан подробно в документации к iButton в разделе Network Capabilities.
Выносим в отдельный поток
Собственно, теперь ты убедился, что библиотека работает, термодатчик тоже что-то измеряет (в данном случае комнатную температуру). Давай теперь подключим все это добро к нашей системе сигнализации. Для этого необходимо создать отдельный поток, в котором будет производиться периодический опрос термодатчика и при возникновении аварии отправляться сообщение.
Немного подумав, я решил, что лучше сделать целый класс — движок работы с термодатчиками, унаследовав его от класса process<>, чтобы все собрать в одну кучку: сделать имплементацию функции-потока, дать этой функции доступ к членам класса, выставить наружу основные функции работы с термодатчиками.
Однако тут я уткнулся в жадность. Мне хотелось оставить возможность опроса термодатчиков из консоли и иметь сигнализацию. Сразу же возникает необходимость разделять общие ресурсы, так как теперь два потока будут дергать один термодатчик (а точнее, шину 1-Wire). Лезь в класс OneWire и добавляй ему приватного мембера OS::TMutex _mutex;
.
Здесь начинается интересное. Мьютекс мы завели, но пользоваться им внутри класса неразумно, так как библиотека работы с термодатчиком написана очень сильно интегрировано и на лету дергает функции байтовой, а не пакетной приема-передачи по 1-Wire. Для локализации массовых вызовов я создал два метода: open
и close
для шины 1-Wire.
Затем пришлось прошерстить всю библиотеку DallasTemperature и обернуть вызовы функций работы с шиной 1-Wire в оболочку _wire->open()
-> _wire->close()
.
Реализация функции потока обработки показаний термодатчика совсем проста. В цикле запрашивается температура, проверяется на вхождение ее в граничные диапазоны (которые сейчас захардкожены), при изменении состояния отправляется грязное ругательное сообщение. Напомню, что в прошлой реализации аварийного потока я заложил код источника сообщения AI_ANALOG
, который сейчас и использую. Приведу кусочек кода, чтобы не мучить тебя словами.
Дополнительно я решил добавить аварийное состояние при обрыве термодатчика, то есть когда непрерывно не удается получиться данные от термодатчика на протяжении некоторого времени, в данном случае десяти опросов.
Тут-то я и наступил на грабли. Я забыл про функцию инициации процесса измерения DallasTemperature::requestTemperatures
. В ней стоят задержки для того, чтобы подождать, пока термодатчик производит измерение. Но я поставил _wire->close()
перед этими задержками. В итоге я получил странную картину: при запросе из терминала начинали скакать показания термодатчика. А случалось вот что: поток движка термодатчиков запускал измерение, одновременно приходил я со своим запросом по терминалу, и в итоге мы оба читали какие-то неинициализированные значения.
Почесав затылок, я вынес отдельно функцию инициации процесса измерения и оставил ее вызов только внутри потока движка термодатчиков. Таким образом, при получении команды из терминала возвращается последняя измеренная температура. Работает даже быстрее, чем каждый раз дергать термодатчик и просить его померить вот прямо сейчас и прямо здесь.
Остается лишь добавить в поток обработки аварийных сообщений кейсы нового источника аварий.
Испытания огнем
Конечно же, огонь применять никто не собирается, пожаров нам только не хватает. Но полевые испытания провести стоит. Так как датчик достаточно инертный, то я решил извлечь хоть какую-то пользу от выделяемого компьютером тепла и засунул термодатчик под поток воздуха от процессорного кулера. Ура, температура поползла вверх!
Как только значения температуры перешагнули пороговое значение, тут же в терминал пришло ругательное сообщение. Следующим шагом была проверка на возврат в нормальное состояние.
Вот мы и сделали еще один сложный шаг к защите содержимого твоего холодильника не только от врагов, но и от разморозки. Теперь в твоем арсенале есть термодатчик, а так как используется линия 1-Wire, то ты уже самостоятельно можешь навесить и два, и три, и более термодатчиков. Надеюсь, что материал этой статьи раскрыл для тебя новые и интересные возможности, казалось бы, игрушечного Arduino и подогрел интерес к программированию встраиваемых систем. Помни, что только написание кода даст тебе знание и умение. Тренируйся, больше практики, старайся воплощать самые свои сумасшедшие идеи, и знание придет. Пиши, пиши, пиши! Железный привет, RESET :).
DANGER
Статическое электричество смертельно для микросхем, старайся избегать работы с микроконтроллерами в синтетической и шерстяной одежде, по возможности используй заземляющие браслеты.
WARNING
При работе с микроконтроллером постарайся убрать все металлические предметы, чтобы предотвратить случайное короткое замыкание и выход платы из строя.
WARNING
Используемые источники:
- https://xakep.ru/2015/05/10/arduino-digital-temp-wire/