Метеостанция: от идеи до реализации




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

Мне всегда хотелось чего-то большего, чем обычный градусник за окном или ЖК-экран метеостанции с температурой на улице и в комнате. Поэтому, когда я задумался, «что же такое сделать» для того, чтобы начать знакомство с миром микроконтроллеров, ответ нашелся сам собой — свою метеостанцию. Естественно, с отображением градусов на улице и внутри помещения, влажности и давления. И с подсветкой — мне всегда нравилась реализация прогноза погоды на Яндексе — одного взгляда на фон достаточно, чтобы понять, будет тепло или холодно, и насколько.

Весь дальнейший функционал был определен небольшим мозговым штурмом. Наличие подсветки, безусловно, плюс, но как быть ночью и вечером? Я решил установить ИК-сенсор, реагирующий на приближение. При подходе к прибору на комфортное расстояние включается подсветка, в иное время экран по умолчанию не горит. Использование ИК подтолкнуло к реализации управления прибором также по ИК-каналу — через пульт (поначалу были опасения о взаимных помехах, но они не подтвердились). Вполне естественно для такого устройства наличие часов.

В качестве основы для системы была выбрана Arduino, которую я только начинал осваивать. Саму Arduino я рассматриваю сейчас (да и тогда), как фреймворк — в первую очередь программный, позволяющий быстро строить необходимую систему, подключая при необходимости плагины-библиотеки. Да, мы можем писать и на чистом С/С++, но в большинстве рядовых задач это дает лишь незначительный прирост производительности, практически незаметный на фоне простоты и удобства загрузки скетчей в Arduino, а также обширнейшей коллекции библиотек для работы с различным железом. (Само собой, что есть особые задачи, но сейчас речь не о них).
В аппаратной же реализации я предпочитаю использовать синюю плату Arduino только на этапе прототипирования на «макетке», а по окончанию разработки устройств обычно проектирую одну-единственную плату с микроконтроллером и всем остальным. Так было и в тот раз.

Подбор компонентов я начал с экрана. Довольно быстро нашелся замечательный экран с RGB-подсветкой www.adafruit.com/products/398, что позволяло получить практически любые цвета. Он построен на популярном чипе HD44780 и поддерживается огромным количеством библиотек, в том числе и LiquidCrystal в Arduino.

Также я поинтересовался ценами на индивидуальное производство дисплеев. Минимальная цена за 1 экземпляр монохромного ЖК (как в типовой метеостанции) по ценам того времени была порядка 1 000 евро, для некоммерческого проекта в единичном экземпляре я счел такой ценник нецелесообразным.



В качестве ИК-датчика был выбран Sharp GP2Y0A02YK0F. Немаловажной характеристикой в моем случае являлась дальность обнаружения объектов, у этого датчика она равна 1.5 м, в то время как у многих других сенсоров она не превышает 30 см. Как показала себя эксплуатация, для небольшого экрана 16х2 полтора метра — действительно оптимальное расстояние.

В качестве датчика давления был выбран Bosch BMP085, работающий по I2C, влажности HH10D — с частотным выходом. Забегая вперед, скажу, что сейчас я бы ни в коем случае не использовал последний, а предпочел бы исключительно I2C-варианты, например HTU21D.

В качестве внешних сенсоров температуры использовались всеми любимые DS18B20. Их огромным преимуществом является возможность подключать (и отключать при необходимости) на 1 шину сразу несколько датчиков, без необходимости менять код программы. С беспроводной передачей данных температуры я в своем первом проекте я связываться не стал, тем более что у меня была возможность проложить провода без ущерба эстетике.

ИК-приемник был взят наиболее типовой, типа TSOP382. Пультом для метеостанции стал пульт от какого-то видеорегистратора. Зуммер (она же пищалка) — самый обычный пьезоизлучатель.



В качестве часов реального времени я выбрал DS1307, также работающую по I2C, кроме того, у меня была крошечная микросхема флеш-памяти 24AA256 на 64 Кб с I2C-интерфейсом. Ее я добавил в проект из чистого любопытства — попробовать работу с внешней памятью, записывая в нее данные о погодных условиях. Питание для проекта – внешнее, от блока питания. На входе стоит преобразователь/стабилизатор напряжения LM7805, сам узел питания очень похож на используемый в Arduino (хотя и не является клоном).

Разработку я выполнял итерациями, так намного проще выполнять отладку и знакомиться с новой для себя областью. На самом первом этапе с термометра DS18B20 считывались данные и выводились на ЖК-экран. На следующем — значения температуры должны были превращаться в RGB-код для подсветки.

Тут меня подстерегала особенность цветопередачи. Управление цветом экрана осуществляется при помощи широтно-импульсной модуляции для каждого из трех светодиодов подсветки (при помощи стандартной библиотеки) с 256 ступенями – казалось бы, вполне обычный 24-битный цвет. К сожалению, длины волн для цветов R, G и B монитора и светодиодов подсветки сильно различаются, и из-за этого обычные цвета, вроде #ffff00, на фоне моего ЖК-экрана выглядят совершенно иначе («уходят в сторону»). Поэтому мне пришлось написать на С# программу с тремя ползунками и color picker’ом, передающую три компоненты цвета в последовательный порт Arduino. Дальше пришлось вспомнить азы математики и составить функцию, преобразующую температуру в градусах по Цельсию в RGB-цветовую шкалу. Естественно, я уже не ограничивался желто-голубой шкалой Яндекса, и использовал и другие цвета — на основе своих ассоциаций.

Следующими этапом стали подключение IR дальномера, датчиков температуры и влажности. И, если с первыми двумя не возникло никаких сложностей (кроме пайки BMP085 в LCC8-корпусе), то датчик влажности доставил массу проблем.

Все дело в том, что итоговое значение относительной влажности рассчитывается по формуле, одним из аргументов которой является частота меандра на выходе датчика. Измерение частоты выполняется при помощи аппаратного таймера микроконтроллера. В 328, к сожалению, таких таймеров всего лишь три, и большая их часть уже задействована при ШИМе для подсветки дисплея. (Сейчас уже не помню все детали, возможно, что-то пропустил).

Из этой ситуации было несколько выходов. Если бы я разрабатывал устройство сегодня, я бы однозначно использовал только I2C-сенсоры. Другим вариантом было использовать более мощный микроконтроллер. Я же тогда выбрал третий вариант — установить отдельный микроконтроллер для работы со звуком (пищалка) и подсветкой дисплея (напомню, это был в первую очередь образовательный проект, и мне было интересно попробовать организовать взаимодействие между двумя МК). В него же перешла функция преобразования температуры в цвет, что облегчило на пару килобайт прошивку главного МК (в Atmega328 размер памяти программы составляет всего лишь 32Кб, моя прошивка в итоге вплотную приблизилась к этому пределу). Взаимодействие между МК было все так же организовано по I2C.

После этого были добавлены пульт, часы, флеш-память. Следующей стадией стало написание удобного меню, добавление программных фич (таких, как блокировка подсветки в текущем состоянии, режим больших цифр — как на уличных часах, с прокруткой всех параметров), поддержка нескольких датчиков температуры с их добавлением / удалением онлайн (да, я знаю, что так лучше не делать). Вполне привычное дело для ПК — и необычное поначалу для собранного тобой устройства, когда без изменения схемы в несколько раз возрастает функциональность проекта…

Пульт использовал какой-то свой проприетарный протокол. Я не стал заниматься его реверс-инжинирингом, мне было вполне достаточно шестнадцатеричного представления каждой кнопки, получаемого мной от библиотеки IRRemote. Для флеш-памяти я выбрал запись данных погоды каждые 10 минут, при длине записи в 16 байт этого хватает на 4 месяца.

Код, отвечающий за работу с флеш-памятьюLOGGER.h
<code class="cpp">#ifndef LOGGER_h #define LOGGER_h  #include <WProgram.h>  class LOGGER {  public:     LOGGER(int);     void storeRecord16(byte* buffer);     void getRecord16(unsigned int address, byte* buffer);     int getAddress();     void setAddress(int);  private:     int _FlashI2CAddress;     void _getAddress();     void _setAddress();     unsigned int _addr; };  #endif </code>
LOGGER.cpp
<code class="cpp">#include <WProgram.h> #include <Wire.h> #include "LOGGER.h" /* MEMORY MAP: 0000 - MSB of last written address 0001 - LSB of last written address ... 0040 - 7FFF - storage, */      LOGGER::LOGGER(int a)     {        _FlashI2CAddress = a;      } void LOGGER::storeRecord16(byte* buffer) {     byte c;     //get address      LOGGER::_getAddress();     //increase addr     delay(5);     _addr+=16;    if(_addr>0x7FF0) _addr = 64;      //store buffer     Wire.beginTransmission(_FlashI2CAddress);     Wire.send((byte) (_addr >> 8) & 0xFF); // MSB     Wire.send((byte) (_addr & 0xFF) );     // LSB     for ( c = 0; c < 16; c++)       Wire.send(buffer[c]);     Wire.endTransmission();     delay(20);     //save new addr     LOGGER::_setAddress(); } void LOGGER::getRecord16(unsigned int address, byte* buffer) {     byte c;     //set address     Wire.beginTransmission(_FlashI2CAddress);     Wire.send((byte) (address >> 8) & 0xFF); // MSB     Wire.send((byte) (address & 0xFF) );     // LSB     Wire.endTransmission();     Wire.requestFrom(_FlashI2CAddress,16);     for (c = 0; c < 16; c++ )       if (Wire.available()) buffer[c] = Wire.receive(); } void LOGGER::_getAddress() {     byte c;     Wire.beginTransmission(_FlashI2CAddress);     Wire.send(0);      Wire.send(0);      Wire.endTransmission();     Wire.requestFrom(_FlashI2CAddress,2);     for (c = 0; c < 2; c++ )       if (Wire.available()) _addr = _addr * 256 + Wire.receive(); } void LOGGER::_setAddress() {     Wire.beginTransmission(_FlashI2CAddress);     Wire.send(0); // pointer     Wire.send(0); // pointer     Wire.send((byte) (_addr >> 8) & 0xFF); // MSB     Wire.send((byte) (_addr & 0xFF) );     // LSB     Wire.endTransmission(); } void LOGGER::setAddress(int addr) {   _addr = addr;   LOGGER::_setAddress(); } int LOGGER::getAddress() {   LOGGER::_getAddress();   return _addr; } </code>


Выгрузка данных производится по команде из меню метеостанции (чем-то напоминает меню старых Нокиа или Самсунг, только без графики) в последовательный порт.

После этого законченная функционально метеостанция пробыла в виде Arduino и макетных плат где-то с неделю. За это время тестов обнаружилась возможность зависания (вызванная нарушением герметичности уличного датчика температуры). Конструкцию датчика я переделал (сегодня я бы брал только фабричный, например, такой ), но и саму возможность зависаний хотелось исключить в принципе. Кроме того, я предположил, что через 49 дней непрерывной работы произойдет переполнение функции millis(), что, в связи с особенностями алгоритма прошивки, также приведет к зависанию. Вещь должна быть надежной! Поэтому последним штрихом в системе стала активация watchdog таймера, в итоге любое зависание гарантированно не продлится более чем 2 сек + 5 сек на reboot (да, в наш цифровой век даже метеостанции нужно время на загрузку.

Итак, станция готова и успешно работает. Я считаю, что останавливаться на этом этапе неправильно. Устройство должно быть удобным и иметь законченный внешний вид, так, чтобы им можно было пользоваться с комфортом и показывать всем, а не только друзьям-гикам.

Я начал с похода в ближайший радиомагазин и выбора корпуса. Из 20-30 различных вариантов мне приглянулся тот, что на рисунке в начале статьи – из-за минимума требуемой переделки, из-за возможности удобно закрепить снаружи корпуса дальномер, так, чтобы он сильно не выпирал, из-за хорошей вентиляции (а значит, более точных показаний температуры в комнате). Сегодня, наверное, я бы заказал печать на 3D-принтере и сделал что-то вот в этом стиле:





(Сложно найти похожую картинку, а в 3D -моделировании я не профи. Экран располагается в верхней части, все остальное в подставке). И, разумеется, никаких проводов — 433МГц для сенсоров и Wi-Fi для связи с ПК.

После примерок и раздумий, как лучше расположить все, пришла очередь разработки платы. В своем первом проекте я использовал систему Eagle. Начертил контур платы, разместил на ней элементы и связи (да, этот проект я делал «неправильным» образом – без принципиальной схемы. Точнее, без схемы в Eagle), трассировщик с легкостью все развел. Отнес на производство…

На Хабре очень любят тему изготовления печатных плат, поэтому здесь сделаю отступление. С ЛУТом я знаком, но у меня нет желания/возможности делать платы, в том числе связываться с химией (хлорное железо та еще вещь). Зато при универе есть небольшое опытное производство, где по очень умеренным ценам (~$3 за кв.дм. на то время) могут изготовить практически любую двустороннюю ПП. Технология – фоторезист, отверстия – от 0.6мм, минимальная ширина дорожки – вроде бы 0.2мм (точно не крупнее, сейчас уже не вспомню). К сожалению, никаких паяльных масок, металлизации переходных отверстий за такую цену ждать не приходится, но это все решаемо. В конце концов, для прототипирования (а платы готовят за день-два) и мелкосерийного производства можно обойтись и без масок (имхо; пайку подразумеваю вручную и крупную).

Впрочем, когда я делал заказ, то про металлизацию переходных отверстий я не подумал. Eagle благополучно использовал ножки элементов в качестве таковых, и обошелся всего лишь 2-3 отдельно стоящими соединениями между слоями. Человек на выдаче обратил на это внимание и любезно предложил тончайший провод для создания переходов в отверстиях для элементов (то есть, сначала через отверстие к верхней и нижней контактной площадке припаивается проводок, после чего в него уже вставляется сама ножка элемента). Я попробовал так действовать, но результаты и объем дополнительной работы не внушали оптимизма. Пришлось перетрассировать плату, создав зоны запрета для расположения via вблизи всех PTH-элементов. Тут автоматический трассировщик доводить работу до конца отказался, и мне пришлось разводить оставшиеся цепи вручную (желания и времени менять систему проектирования уже не было). Сами переходные отверстия в итоге делались точно по такой же технологии, но их количество я свел к минимуму. Паять вновь заказанную плату было несравненно проще.

Далее – работа дрелью, ножом и напильниками, корпус приобрел окошко для экрана, крепления для платы. Пара вечеров – и устройство в корпусе, успешно работает. И все же, чего-то не хватает…

А не хватает наклейки на переднюю панель (особенно после прорезки окна в ней). Самоклеющаяся бумага – отличная вещь для подобных задач. То, что получилось – на титульном фото, строго не судите:) После этого внешний вид устройства изменился радикально…

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



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

Функциональная схема:





Рендер, реальная плата без паяльной маски. Не показан держатель литиевой батареи, пьезоизлучатель, DIN-разъем (питание, связь с ПК, датчики) и разная мелочевка. Кроме того, в реальном устройстве микроконтроллеры стоят в панельках.

Режим «Больших цифр», для лучшей читаемости издали:







Источник: habrahabr.ru/post/223829/