Домашняя метеостанция на Arduino и отправка данных на "Народный мониторинг". Метеостанция на Arduino с беспроводным датчиком температуры Метеостанция на arduino с беспроводным датчиком температуры

Загружать прошивку желательно до подключения компонентов, чтобы убедиться в том, что плата рабочая. После сборки можно прошить ещё раз, плата должна спокойно прошиться. В проектах с мощными потребителями в цепи питания платы 5V (адресная светодиодная лента, сервоприводы, моторы и проч.) необходимо подать на схему внешнее питание 5V перед подключением Arduino к компьютеру, потому что USB не обеспечит нужный ток, если например лента его потребует. Это может привести к выгоранию защитного диода на плате Arduino. Гайд по скачиванию и загрузке прошивки можно найти под спойлером на следующей строчке.

Содержимое папок в архиве

  • libraries – библиотеки проекта. Заменить имеющиеся версии
  • firmware – прошивки для Arduino
  • schemes – схемы подключения компонентов

Дополнительно

  • Как показал эксперимент, снаружи корпуса датчик температуры показывает на 0.5 градуса меньше, чем внутри! Нужно более удачно компоновать электронику, отводить и экранировать тепло от греющихся элементов…

  • Если дисплей показывает слишком тускло/на белом фоне
    На плате драйвера дисплея (к которой подключаются провода) есть крутилка контрастности, с её помощью можно подстроить контраст на нужный. Также контрастность зависит от угла взгляда на дисплей (это же LCD) и можно настроить дисплей на чёткое отображение даже под углом “дисплей на уровне пупка, смотрим сверху”. А ещё контрастность сильно зависит от питания: от 5V дисплей показывает максимально чётко и ярко, тогда как при питании от USB через Arduino напряжение будет около 4.5V (часть падает на защитном диоде по линии USB), и дисплей показывает уже не так ярко. Вывод настраивайте крутилкой при внешнем питании от 5V!

  • Если датчик CO2 работает некорректно (инфа от Евгения Иванова)
    Ну там в папке библиотеки сенсора в examples есть скетчи для калибровки. также ее можно запустить втупую замкнув на землю разъем “HD” на 7+ секунд.
    Само собой вот прямо на улице на морозе этим заниматься не обязательно… можно просто в бутылку набрать свежего воздуха с датчиком внутри и запечатать. калибровка проводится минимум 20 минут..
    По-умолчанию датчик поставляется с включенной автокалибровкой, которая происходит каждый день, и если датчик используется в невентелируемом помещении, то эта калибровка быстро уводит значения от нормы за горизонт, потому ее нужно обязательно отключать.
    Документация .

  • Автокалибровка датчика CO2 отключена в скетче!

  • Если у вас не работает датчик BME280 , скорее всего у него отличается адрес. В проекте используется библиотека Adafruit_BME280, у которой нет отдельной функции смены адреса, поэтому адрес задаётся вручную в файле библиотеки Adafruit_BME280.h почти в самом начале файла (лежит в папке Adafruit_BME280 в вашей папке библиотек, вы должны были её туда установить ), у моего модуля был адрес 0x76. Как узнать адрес своего модуля BME280? Есть специальный скетч, называется i2c scanner. Его можно нагуглить, можно . Прошиваете данный скетч, открываете порт и получаете список адресов подключенных к шине i2c устройств. Чтобы остальные модули вам не мешали – можно их отключить и оставить только BME280. Полученный адрес указываем в библиотеке, сохраняем файл и загружаем прошивку метео-часов. Всё!

  • Если отстают часы , проблема скорее всего в питании схемы. Если при смене блока питания на более качественный проблема не уходит, повесьте конденсатор по питанию RTC модуля (прям на плату на VCC и GND паять): обязательно керамический, 0.1-1 мкФ (маркировка 103 или 104, смотрите таблицу маркировок). Также можно поставить электролит (6.3V, 47-100 мкФ)

Настройки в прошивке

#define RESET_CLOCK 0 // сброс часов на время загрузки прошивки (для модуля с несъёмной батарейкой). Не забудь поставить 0 и прошить ещё раз! #define SENS_TIME 30000 // время обновления показаний сенсоров на экране, миллисекунд #define LED_MODE 0 // тип RGB светодиода: 0 - главный катод, 1 - главный анод #define LED_BRIGHT 255 // яркость светодиода СО2 (0 - 255) #define BLUE_YELLOW 1 // жёлтый цвет вместо синего (1 да, 0 нет) но из за особенностей подключения жёлтый не такой яркий #define DISP_MODE 1 // в правом верхнем углу отображать: 0 - год, 1 - день недели, 2 - секунды #define WEEK_LANG 1 // язык дня недели: 0 - английский, 1 - русский (транслит) #define DEBUG 0 // вывод на дисплей лог инициализации датчиков при запуске #define PRESSURE 1 // 0 - график давления, 1 - график прогноза дождя (вместо давления). Не забудь поправить пределы гроафика // пределы отображения для графиков #define TEMP_MIN 15 #define TEMP_MAX 35 #define HUM_MIN 0 #define HUM_MAX 100 #define PRESS_MIN -100 #define PRESS_MAX 100 #define CO2_MIN 300 #define CO2_MAX 2000

Продолжаем развивать нашу метеостанцию.

Перед тем, как перейти к обновлению, хочу внести немного ясности.

Мне написал один из наших коллег с вопросом, по какой причине введен сторожевой таймер?

Сторожевой таймер стоит на случай ч.п. Как показывает практика, ENC28J60 не тянет более (если не подводит память) 4 одновременных соединений. Учитывая сколько служебных соединений, постоянно происходит для поддержания работы самой сети, и просто левый трафик, создаваемый всяческими домашними игрушками (например, современные телевизоры, сканируют доступные хосты в сети и открытые у них порты) конструкция попросту уходит в ступор. ENC28J60 не умеет самостоятельно работать с сетевыми протоколами и все реализовано в библиотеках. Возможно дело именно в них.
Проверял все доступные библиотеки и разные модули (вдруг брак), но добиться стабильной работы в течении длительного времени у меня не получилось. Максимальный срок был порядка 3-4 недель.
Именно для этого там крутится "пес" и в случае чего дергает контроллер. После этого проблема ушла.
Также не отрицаю, что возможно в моей домашней сети есть определенные нюансы или проблемы. Но раз проблема была у меня, она может выплыть и у другого человека. Я пока нашел только такое решение.
Насколько мне известно, на чипах от Wiznet (W5100 и выше) этого нет, ну или просто плохо искали.

Переходим к обновлению

Самое главное, мы уходим от чипа ENC28J60 и переходим на W5100 . Я пытался реализовать все на старом чипе, но не хватает памяти микроконтроллера из-за очень больших библиотек для ENC28J60 . При использовании нового чипа, стандартной библиотеки от разработчика и всех внесенных изменений, остается еще более 20% свободной памяти микроконтроллера ATMega328 . А это, новые плюшки!

В этой версии (назовем её второй) добавлена возможность передачи показаний с датчиков по беспроводной связи используя частоту 433 мГц . Сами модули я брал у Китайцев, маркировка XY-MK-5V . Хочу отметить, что качество передачи далеко от совершенства. Возможны потери сигнала, шумы, не возможность одновременной передачи и т.д и т.п. Но их цена (менее $1 за комплект) компенсируют эти недостатки. Скажу Вам по секрету, что именно эти (самые дешевые) модули стоят во многих фирменных метеостанциях для домашнего использования. Ого, неожиданно?

Начнем с базовой станции

Мы переходим на Arduino UNO и Ethernet Shield (первой версии) на базе чипа W5100 . Это бутерброд и описывать его нету смысла. Я опишу только дополнительно задействованные контакты для модулей XY-MK-5V .

Модуль передатчика использует питание 5V , GND (куда без матушки то) и D2 пин на контроллере. Изменить контакт D2 (DATA) можно, используя функцию vw_set_tx_pin из библиотеки vw.

В отличии от предыдущего скетча, в этом задействованы две дополнительные библиотеки:

#include #include

Сам скетч

Скрытый текст

#include #include #include #include #include #include #include #include #define DHTTYPE DHT22 #define DHTPIN 5 DHT dht(DHTPIN, DHTTYPE); byte mac = {0x54, 0x34, 0x31, 0x31, 0x31, 0x31}; char server = "narodmon.ru"; int port = 8283; IPAddress ip(192,168,0,201); EthernetClient client; BMP085 dps = BMP085(); long Temperature = 0, Pressure = 0; float H, dP, dPt; bool interval = true; EasyTransferVirtualWire ET; struct SEND_DATA_STRUCTURE{ byte ID; // Идентификатор устройства int Temperature; // Температура float Pressure; // Давление float Humidity; // Влажность float dewPoint; // Точка росы/инея }; SEND_DATA_STRUCTURE broadcast; void setup() { // Инициализация сторожевого таймера (Watchdog timer) wdt_disable(); delay(8000); wdt_enable(WDTO_8S); // Инициализация консоли Serial.begin(9600); // Инициализация датчика DHT dht.begin(); // Инициализация модуля 433 мГц ET.begin(details(broadcast)); vw_set_ptt_inverted(true); vw_set_tx_pin(2); vw_setup(2000); // Стартуем сеть, если не дождались данных с DHCP сервера то // присваеваем себе адрес самостоятельно if (Ethernet.begin(mac) == 0) Ethernet.begin(mac, ip); // Инициализация 1-Wire Wire.begin(); delay(200); // Инициализация BMP180 с корректировкой высоты // dps.init(MODE_STANDARD, 3200, true); // Инициализация BMP180 dps.init(); Serial.println(Ethernet.localIP()); // Отправляем первые данные сразу после включения устройства send_info(true); } // dewPoint function NOAA // reference (1) : http://wahiduddin.net/calc/density_algorithms.htm // reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm double dewPoint(double celsius, double humidity) { // (1) Saturation Vapor Pressure = ESGG(T) double RATIO = 373.15 / (273.15 + celsius); double RHS = -7.90298 * (RATIO - 1); RHS += 5.02808 * log10(RATIO); RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO))) - 1) ; RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ; RHS += log10(1013.246); // factor -3 is to adjust units - Vapor Pressure SVP * humidity double VP = pow(10, RHS - 3) * humidity; // (2) DEWPOINT = F(Vapor Pressure) double T = log(VP/0.61078); // temp var return (241.88 * T) / (17.558 - T); } void send_info(bool eth) { bool fail = true; while(fail) { // Пытаемся считать данные с датчика влажности DHT до тех пор, пока не получим // результат. В 90% случаев все работает нормально, но нам нужны 100% if((H = dht.readHumidity()) >= 0) { // Получение влажности и температуры с датчика BMP180 dps.getPressure(&Pressure); dps.getTemperature(&Temperature); // Подсчитываем точку росы, если температура на улице выше 0 градусов Цельсия // и ожидаем результат выше 0, в противном случае выводим 0. Это необходимо // чтобы не вводить в заблуждения в зимее время года. // dP = Temperature>0?((dPt=dewPoint(Temperature*0.1, H))<0?0:dPt):0; dP = dewPoint(Temperature*0.1, H); // Отправляем данные в эфир 433 мГц broadcast.ID = 1; broadcast.Temperature = floor(Temperature*0.1); broadcast.Pressure = floor(Pressure/133.3*10)/10; broadcast.Humidity = floor(H*10)/10; broadcast.dewPoint = floor(dP*10)/10; ET.sendData(); delay(250); if(eth) { // Подключаемся к серверу "Народный мониторинг" if(client.connect(server, port)) { // Начинаем передачу данных // адрес_устройства_в_проекте, имя_устройства, GPS широта, GPS долгота client.print(F("#fe-31-31-0e-5a-3b#Arduino Uno#71.344699#27.200014\n")); // Температура client.print(F("#T0#")); client.print(Temperature*0.1); client.print(F("#Температура\n")); // Давление client.print("#P1#"); client.print(Pressure/133.3); client.print(F("#Давление\n")); // Влажность client.print("#H1#"); client.print(H); client.print(F("#Влажность\n")); // Точка росы\инея client.print("#T1#"); client.print(dP); client.print((dP <= 0)? F("#Точка инея\n"):F("#Точка росы\n")); //client.print(F("#Точка росы\n")); // Отправляем конец телеграммы client.print("##"); // Даем время отработать Ethernet модулю и разрываем соединение delay(250); client.stop(); } } // Останавливаем цикл, если передача завершена fail = !fail; break; } delay(250); } } void loop() { // Каждые 4 секунды сбрасываем сторожевой таймер микроконтроллера // Каждые 6 минут отправляем данные на "Народный мониторинг" // Каждые 30 секунд отсылаем данные в эфир 433 if(!(millis()%1000)) wdt_reset(); if(!(millis()%360000)) send_info(true); if(!(millis()%30000)) send_info(false); }

К самим модулям необходимо добавить антенну. Для 433 мГц достаточно обычного медного провода длинной 17 см . Без антенны можете забыть о нормальной работе.

Переходим к самой важной части этого обновления - локальная беспроводная станция

Для её реализации (на коленке) я использовал аналог Arduino NANO (на базе ATMega328 ) и TFT дисплей на чипе ST7735S с разрешением 128 x 160

Скрытый текст



Распиновка дисплей -> контроллер

============================= LED | 3.3V SCK | SCK (13) SDA | MOSI (11) A0 | DC (9) RESET | RST (8) CS | CS (10) GND | GND VCC | 5V ============================

Модуль приемник подключается также как передатчик, только DATA к пину D7 .

Пару снимков, как это выглядит:

Скрытый текст

Скетч приемника

Скрытый текст

#include #include #include #include int x, y; int w = 128, h = 160; int size; // 433 EasyTransferVirtualWire ET; struct SEND_DATA_STRUCTURE{ byte ID; // Идентификатор устройства int Temperature; // Температура float Pressure; // Давление float Humidity; // Влажность float dewPoint; // Точка росы/инея }; SEND_DATA_STRUCTURE broadcast; int Log_Temperature = -1; float Log_Pressure = -1; float Log_Humidity = -1; float Log_dewPoint = -1; // TFT #define cs 10 #define dc 9 #define rst 8 char Temperature, Pressure, Humidity, dewPoint; String info; TFT TFTscreen = TFT(cs, dc, rst); void setup(){ Serial.begin(9600); // Инициализация модуля 433 мГц ET.begin(details(broadcast)); vw_set_ptt_inverted(true); vw_set_rx_pin(7); vw_setup(2000); vw_rx_start(); // Инициализация и начальная настройка дисплея TFTscreen.begin(); TFTscreen.setRotation(2); TFTscreen.background(0, 0, 0); // Рисуем статические элементы // 1. Заходите к нам в гости TFTscreen.stroke(255, 255, 255); TFTscreen.setTextSize(1); TFTscreen.text(" ", 10, 10); // 2. Описание показаний с датчиков TFTscreen.text("mmHg", w/2+5, 80); TFTscreen.text("%", w/2+5, 100); TFTscreen.text("C", w/2+5, 120); broadcast.Temperature = 0; broadcast.Pressure = 0; broadcast.Humidity = 0; broadcast.dewPoint = 0; TFTPrint(); } void loop(){ if(ET.receiveData()){ if(broadcast.ID == 1) TFTPrint(); /* Serial.println(broadcast.Temperature); Serial.println(broadcast.Pressure); Serial.println(broadcast.Humidity); Serial.println(broadcast.dewPoint); Serial.println(); */ } } void changes(int size, int x, int y, bool up, bool clear = false) { if(clear) TFTscreen.stroke(0, 0, 0); else { changes(size, x, y, !up, true); TFTscreen.stroke((up)?0:255, 0, (up)?255:0); } if((size%2) == 0) size++; while(size > 0) { TFTscreen.line(x, y, x+(size--), y); ++x, (up)?--y:++y, --size; } /* while(size > 0) { TFTscreen.line(x, y, (up)?x+size-1:x, (up)?y:y+size-1); ++x, ++y, --size; } */ } int x_center(int w, int length, int size) { return floor((w-length*(size*5)+size*2)/2); } int x_alignment_right(int w, int length, int size) { return ceil(w-length*(size*5)+size*2); } void TFTPrint() { size = 3; // ================================================================================== // Вывод показаний температуры // ================================================================================== if(broadcast.Temperature != Log_Temperature) { TFTscreen.setTextSize(size); // Затираем устаревшие данные String info = String(Log_Temperature); info.concat(" C"); if(Log_Temperature > 0) info = "+"+info; info.toCharArray(Temperature, info.length()+1); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35); // Выводим новые показания info = String(broadcast.Temperature); info.concat(" C"); if(broadcast.Temperature > 0) info = "+"+info; info.toCharArray(Temperature, info.length()+1); // Меняем цвет значения температуры в зависимости от самой температуры int r, g = 0, b; if(broadcast.Temperature > 0) { r = map(broadcast.Temperature, 0, 40, 255, 150); // Красный b = map(broadcast.Temperature, 0, 40, 30, 0); // Изменяем оттенок для более наглядного перехода через ноль } else { r = map(broadcast.Temperature, -40, 0, 0, 30); // Изменяем оттенок для более наглядного перехода через ноль b = map(broadcast.Temperature, -40, 0, 150, 255); // Синий } TFTscreen.stroke(b, g, r); // ВНИМАНИЕ: в библиотеке перепутаны позиции цветов, место RGB используется BGR! TFTscreen.text(Temperature, x_center(w, info.length()+1, size), 35); } size = 1; // ================================================================================== // Вывод показаний давления // ================================================================================== if(broadcast.Pressure != Log_Pressure) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_Pressure); info.toCharArray(Pressure, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80); // Выводим новые показания info = String(broadcast.Pressure); info.toCharArray(Pressure, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(Pressure, x_alignment_right(w/2-5, info.length(), size), 80); changes(10, 106, 85, (broadcast.Pressure > Log_Pressure)?true:false); } else { changes(10, 106, 85, true, true); changes(10, 106, 85, false, true); } // ================================================================================== // Вывод показаний влажности // ================================================================================== if(broadcast.Humidity != Log_Humidity) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_Humidity); info.toCharArray(Humidity, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100); // Выводим новые показания info = String(broadcast.Humidity); info.toCharArray(Humidity, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(Humidity, x_alignment_right(w/2-5, info.length(), size), 100); changes(10, 106, 105, (broadcast.Humidity > Log_Humidity)?true:false); } else { changes(10, 106, 105, true, true); changes(10, 106, 105, false, true); } // ================================================================================== // Вывод показаний точки росы\инея // ================================================================================== if(broadcast.dewPoint != Log_dewPoint) { TFTscreen.setTextSize(size); // Затираем устаревшие данные info = String(Log_dewPoint); info.toCharArray(dewPoint, info.length()); TFTscreen.stroke(0, 0, 0); TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120); // Выводим новые показания info = String(broadcast.dewPoint); info.toCharArray(dewPoint, info.length()); TFTscreen.stroke(255, 255, 255); TFTscreen.text(dewPoint, x_alignment_right(w/2-5, info.length(), size), 120); changes(10, 106, 125, (broadcast.dewPoint > Log_dewPoint)?true:false); } else { changes(10, 106, 125, true, true); changes(10, 106, 125, false, true); } // Обновляем значения в логах для последующего сравнения показаний Log_Temperature = broadcast.Temperature; Log_Pressure = broadcast.Pressure; Log_Humidity = broadcast.Humidity; Log_dewPoint = broadcast.dewPoint; }

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

Как мне показалось, дизайн это та часть проекта, которая отнимает большую часть времени!

Скрытый текст

Часть данных сфабрикованы для отображения некоторых элементов дизайна.

Артефакты на дисплее, это пыль и прочая грязь скопившаяся за долго время нахождения дисплея в... где то там, ... ну там, не помню откуда его достал! Отстаньте!

В скетче имеются функции позиционирования. Они довольно примитивны, но позволяют добиться определенных эффектов.

  1. x_center
  2. x_alignment_right

Первая производит центровку текста, а вторая выравнивание по правой части указанной зоны. Все вычисления производятся относительно размеров заданного текста, исходя из выражения 1 size = 1PX х 1PX сегмента шрифта.

На дисплее также отображаются элементы соответствующие повышению или понижению той или оной величины показаний. Отображаются они в виде треугольников. Но в коде функции changes есть альтернативное отображение в виде треугольников повернутых на 45 градусов. Если показания повышаются то элемент красный, в противном случае, синий.

Кстати, цвет и оттенок основной температуры изменяется в зависимости от самой температуры. Довольно спорное решение, но на мой взгляд, визуально комфортное. Я некоторое время бился над ней, и понял, что значения в функции stroke , объекта TFT дисплея, указаны в неверном порядке. BGR место RGB . Это ошибка разработчика, ну или я что-то не понимаю.

PS : Все довольно интересно, но на мой взгляд заслуживает дальнейшего развития. Чем и займемся через какое то время.

Как большинство работающих людей, занятие собственными проектами отнимает единственно оставшееся свободное время. Поэтому уже давно не творил и «чесались руки» что-либо сделать. Данная возможность появилась как ни странно в университете. За окном сентябрь, 4 курс и надвигающийся курсовой по схемотехнике. Нам сказали, что курсовые можно будет делать в двух вариациях: бумажном и «железе».

На протяжении 5 лет бумажный курсовой в нашем университете делался по принципу «возьми старые и собери их воедино». Такой подход меня не устраивал своей рутинностью, поэтому я сразу же выбрал курсовой в «железе». В качестве сердца курсовых был предложен микроконтроллер Arduino ввиду своей легкообучаемости. После определения с типом курсового оставался ещё один вопрос: а что именно бы сделать. Так как опыта в программировании микроконтроллеров не было, то сразу же открыл гугл и начал изучать существующие проекты. Проектов много, некоторые из них довольно простые, некоторые гениальны (3D сканер, например), но подавляющее большинство не имело практического применения. А мне хотелось именно того, что не валялось бы потом на полке и не собирало там пыль. После получасового экскурса в мир Arduino, меня заинтересовало тема домашних метеостанций, да и проекты показались не очень сложными в реализации (что в основном и подкупило новичка).

Вот так была выбрана тема для курсового и со временем проблем вроде как не намечалось.

Выбор компонентов

Просматривая разные проекты я понимал, что мне вполне достаточно будет Nano или даже Pro Mini, но всё-таки выбрал Arduino Uno в надежде, что программирование для Arduino мне понравится и в дальнейшем реализую ещё какие-нибудь проекты. Паяльник до этого в руках ни разу не держал, поэтому для более легкой разработки решил также приобрести Sensor Shield v4.

Подробнее

Плата способствует быстрому подключению датчиков, модулей, серво моторов, интерфейсов Serial и I2C, а также выводит все порты контроллера формфактора Duemilanova/Uno(также может быть подключена и в серию мега, но с ограничениями и вытекающими последствиями). Поддерживает другие шилды поверх себя.


В качестве источников для метеорологических данных выбрал следующие датчики:


С датчиками определился. Но что делать с данными, поступающими от датчиков. Решил выводить на дисплей. Картинку хотелось цветную, поэтому монохромные решения отбросил сразу. После нескольких минут поиска был выбран TFT дисплей ST7735 размером 1,8 дюймов.

Подробнее

Поскольку дисплей использует 4-проводной SPI протокод для связи и имеет свой собственный пикселе-адресуемый буфер кадра, он может использоваться с любыми видами микроконтроллеров. 1.8-дюймовый дисплей имеет 128x160 цветных пикселя. Также имеется слот для карты памяти microSD, следовательно, можно легко загружать полноцветные растровые изображения из FAT16 / FAT32 файловой системы microSD карты.

Характеристики:

  • Диагональ дисплея - 1.8 дюймов, разрешение 128x160 пикселей, 18-битный цвет (262 144 цвета)
  • Контроллер со встроенной пиксельной адресацией буфера видеопамяти
  • Встроенный слот для microSD - использует более 2 цифровых линий
  • Совместим с 3.3 и 5V
  • Габариты: 34 мм х 56 мм х 6,5 м


Программирование контроллера Arduino

После того, как определились с компонентами для метеостанции, начнём программирование контроллера. Для прошивки Arduino использовалась среда разработки Arduino IDE. Также использовал библиотеки от Adafruit.

Перед тем, как перейти к скетчу, рассмотрим функционал:

  • Показания снимаются с датчиков каждые 10 секунд и обновляются на экране только те показатели, которые были изменены по сравнению с прошлым измерением
  • Реализована передача данных по COM порту

Скетч

#include // library for communication with I2C devices #include // Core library for all sensors #include // library for BMP180 #include // Core graphics library #include // Hardware-specific library #include // library for communication with SPI devices #include "dht.h" // library for DHT #define DHT22_PIN 2 // connect data pin of DHT22 to 2 digital pin #define TFT_CS 10 // connect CS pin of TFT to 10 digital pin #define TFT_RST 9 // connect RST pin of TFT to 9 digital pin // you can also connect this to the Arduino reset // in which case, set this #define pin to 0! #define TFT_DC 8 // connect DC pin of TFT to 8 digital pin Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); //initialize TFT #define TFT_SCLK 13 // connect SCLK pin of TFT to 13 digital pin #define TFT_MOSI 11 // connect MOSI pin of TFT to 11 digital pin dht DHT; Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085); //initialize BMP180 int bmpFlag = 0; struct { uint32_t total; uint32_t ok; uint32_t crc_error; uint32_t time_out; uint32_t connect; uint32_t ack_l; uint32_t ack_h; uint32_t unknown; } stat = { 0,0,0,0,0,0,0,0}; // struct for dht status void setup(void) { Serial.begin(9600); Serial.println("Meteo Test"); Serial.println(""); if(!bmp.begin()) // check connection for BMP180 { Serial.print("Ooops, no BMP180 detected ... Check your wiring or I2C ADDR!"); bmpFlag = 1; } tft.initR(INITR_BLACKTAB); // Initialize TFT and fill with black color tft.fillScreen(ST7735_BLACK); tft.setRotation(tft.getRotation() + 1); tft.setTextSize(1.5); delay(500); // delay in order to ensure that TFT was initialized } // last measured data float oldTemperature = 0, oldAltitude = 0, oldPressure = 0, oldDHTHumidity = 0, oldDHTTemperature; bool wasUpdate = false; void loop(void) { if(Serial.available() > 0) // we have data is Serial port { Serial.read(); // read byte from serial port and send last measured data printValue("Pressure", oldPressure, " hPa", false); printValue("Temperature", oldTemperature, " C", false); printValue("Altitude", oldAltitude, " m", false); printValue("Humidity", oldDHTHumidity, "%", false); printValue("DHT_temperature", oldDHTTemperature, " C", false); Serial.println("END_TRANSMISSION"); } sensors_event_t event; float temperature, altitude; if(bmpFlag == 0){ bmp.getEvent(&event); // get data from BMP180 if (event.pressure) { bmp.getTemperature(&temperature); float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA; altitude = bmp.pressureToAltitude(seaLevelPressure, event.pressure, temperature); } else { Serial.println("Sensor error"); } } uint32_t start = micros(); int chk = DHT.read22(DHT22_PIN);// get data from DHT22 uint32_t stop = micros(); stat.total++; switch (chk) // check status of DHT22 { case DHTLIB_OK: stat.ok++; break; case DHTLIB_ERROR_CHECKSUM: stat.crc_error++; Serial.print("Checksum error,\t"); break; case DHTLIB_ERROR_TIMEOUT: stat.time_out++; Serial.print("Time out error,\t"); break; case DHTLIB_ERROR_CONNECT: stat.connect++; Serial.print("Connect error,\t"); break; case DHTLIB_ERROR_ACK_L: stat.ack_l++; Serial.print("Ack Low error,\t"); break; case DHTLIB_ERROR_ACK_H: stat.ack_h++; Serial.print("Ack High error,\t"); break; default: stat.unknown++; Serial.print("Unknown error,\t"); break; } if(bmpFlag != 0 || !event.pressure) // update data { tft.fillRect(0, 30, 160, 6, ST7735_BLACK); tft.setCursor(0, 30); tft.setTextColor(ST7735_RED); printValue("ERROR BMP INITIALIZATION", 0, "", true); } else { if(event.pressure != oldPressure) { tft.fillRect(0, 30, 160, 7, ST7735_BLACK); tft.setCursor(0, 30); tft.setTextColor(ST7735_RED); printValue("Pressure", event.pressure, " hPa", true); oldPressure = event.pressure; wasUpdate = true; } if(temperature != oldTemperature) { tft.fillRect(0, 38, 160, 7, ST7735_BLACK); tft.setCursor(0, 38); tft.setTextColor(ST7735_WHITE); printValue("Temperature", temperature, " C", true); oldTemperature = temperature; wasUpdate = true; } if(altitude != oldAltitude) { tft.fillRect(0, 46, 160, 7, ST7735_BLACK); tft.setCursor(0, 46); tft.setTextColor(ST7735_BLUE); printValue("Altitude", altitude, " m", true); oldAltitude = altitude; wasUpdate = true; } } if(DHT.humidity != oldDHTHumidity) { tft.fillRect(0, 54, 160, 7, ST7735_BLACK); tft.setCursor(0, 54); tft.setTextColor(ST7735_GREEN); printValue("Humidity", DHT.humidity, "%", true); oldDHTHumidity = DHT.humidity; wasUpdate = true; } if(DHT.temperature != oldDHTTemperature) { tft.fillRect(0, 80, 160, 7, ST7735_BLACK); tft.setCursor(0, 80); tft.setTextColor(ST7735_YELLOW); printValue("DHT_temperature", DHT.temperature, " C", true); oldDHTTemperature = DHT.temperature; wasUpdate = true; } if(wasUpdate) { Serial.println("END_TRANSMISSION"); } wasUpdate = false; delay(10000); } void printValue(char* title, double value, char* measure, bool tftPrint) { if(tftPrint) // print data to TFT { tft.print(title); tft.print(": "); tft.print(value); tft.println(measure); } Serial.print(title); // send data to Serial port Serial.print(": "); Serial.print(value); Serial.println(measure); }

Самое время собрать корпус

Главным условием курсового было рабочий прототип в презентабельном виде. Поэтому пришлось купить корпус и, вооружившись напильником, любым способом засунуть метеостанцию в корпус.

В местном магазине радиоэлектроники был приобретён корпус.

Корпус

(На фото корпус немного не такой. У меня крышка прозрачная)



Затем, орудуя напильником, были проделаны отверстия для вывода датчиков и подачи питания. Датчики решил вывести наружу, так как во время тестирования системы без корпуса заметил, что задняя часть экрана сильно нагревается, что скажется на температуре внутри корпуса.

Корпус с отверстиями для датчиков и питания



Так как пришлось припаивать ножки к 2 датчикам и у одного из них я спалил дорожку, то решил не испытывать судьбу и не припаивать провода к датчикам (потренируюсь на чём-нибудь другом), а для того чтобы соединение было более-менее надёжным, решил перемотать изолентой.

Система перед "запихиванием" в корпус



Так как корпус намного больше Arduino (меньше не было), пришлось придумывать подпорку, чтобы плата не ездила внутри корпуса. Также из паралона была вырезана фигура, а в ней прямоугольник для экрана с целью скрыть внутренности корпуса. Суперклея под рукой не было, поэтому пришлось садить на двусторонний скотч.

Чудо-юда рыба-кит



Прикручиваем крышку, подключаем питание и ждём.

Законченная метеостанция в корпусе



После вывода результатов на экран, выявляем неприятную ошибку измерения влажности: DHT22 усердно выдаёт цифру 99,90% (крайне редко бывает 1,00%). Начинаем разбираться в чём проблема. Первое, что делаем - смотрим вывод значений в COM порт. Вроде всё нормально. После нексольких перезаливок, разборок и сборок корпуса в голову приходит мысль поискать ответ в гугле. Как и ожидалось русский гугл ничего дельного не сказал. Окей. Начинаем искать на английском и на одном из форумов натыкаемся на ребят с похожей проблемой. Первые четыре страницы обсуждения ничего дельного не дают, а на пятой странице находим ответ на наш вопрос:
Humidity sensors can easily be affected by the wrong gasses or very long exposure to high humidity IIRC. In the datasheet there is a procedure how to «reset» the sensor, you could give it a try.

Оставался вопрос только в том, когда и как я успел навредить DHT22. Но подходило время сдавать курсовой и поэтому я оставил решение этой проблемы на потом.

Послесловие

Курсовой был сдан. Метеостанция отложена на неопределенное время до закрытия всех хвостов в университете. Однако, к метеостанции пришлось вернутся раньше, чем я думал. Так сложилось, что в середине ноября я поменял рабочее место и в новой команде я познакомился с людьми, которые интересуются платформой Arduino и им подобными. Поэтому мой интерес к данной платформе не успев остыть, разгорелся снова. Я достал свою метеостанцию, подключил к компьютеру и вспомнил, что я реализовывал передачу данных с Arduino по COM порту. И тут мне пришла в голову идея, написать программу, принимающую данные через COM порт от Arduino и передавать эти данные на народный мониторинг , но это уже совсем другая история.

Также хотелось бы иметь беспроводные датчики и всё-таки реализовать метеостанцию на Arduino Pro Mini. Поэтому мною были заказаны 4 Arduino Pro Mini с питанием 3,3В, 4 радиомодуля nRF24L01+ и ещё кое-какие дополнительные датчики, о чём я также постараюсь рассказать в следующий раз. А пока я жду посылки, в планах реализовать подключение часов реального времени для возможности сохранения времени обновления данных и самих данных на microSD карту при условии отсутствия соединения с клиентом по COM порту.

Вы можете помочь и перевести немного средств на развитие сайта



Метеостанция своими руками.

Дело было вечером, делать было нечего после нового года. Как обычно, во время зимних новогодних каникул хочется занять голову да и руки тоже чем-нибудь полезным, творческим. В эти новогодние каникулы решил сделать метеостанцию своими руками. Готовиться начал заранее, все компоненты закупал и собирал перед новым годом, а основное программирование делал на каникулах.

(под катом много фотографий!)

Сначала пробегусь по компонентам, ссылки давать не буду, так как на eBay (в личном кабинете) товары ушли в архив. Многие компоненты покупал неспеша на аукционе eBay. Впервые опробовал аукцион, раньше всегда покупал «buy it now». Что могу сказать, если не спешить с покупками, то некоторые компоненты можно купить дешевле (разница иногда бывает в два раза).

Датчик давления ВМР085
Это основной датчик. Когда я увидел его на eBay, то понял, что хочу собрать именно домашнюю метеостанцию.
Прилетел датчик в обычном конверте, внутри обклеенном пупыркой.

Внутри конверта была визитка продавца и датчик, запакованный в антистатический пакет и завёрнутый в ещё один слой пупырки

Антистатический пакет был запаян, дабы влага во время перелёта не грозила датчику

Достаём датчик. С одной стороны припаяна линейка контактов, которые были вставлены в пенопласт, чтобы не погнулись. С другой стороны располагается сам датчик и маркировка контактов.




Все бы хорошо, но маркировка контактов нанесена в зеркальном виде.
Подключается датчик по шине I2C и питается от 3,3 В. То есть для нормального функционирования нужно 4 провода (+, -, SDA, SCL)
Опрашивать датчик можно 2 способами: или через библиотеку, или используя функции прямо скетче.
Пример программы:

#include

#define BMP085_ADDRESS 0x77 // I2C address of BMP085

Const unsigned char OSS = 0; // Oversampling Setting

// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;

Short temperature;
long pressure;

Void setup()
{
Serial.begin(9600);
Wire.begin();
bmp085Calibration();
}

Void loop()
{
temperature = bmp085GetTemperature(bmp085ReadUT());
pressure = bmp085GetPressure(bmp085ReadUP());
Serial.print(«Temperature: „);
Serial.print(temperature/10.0, DEC);
Serial.println(“ C»);
Serial.print(«Pressure: „);
Serial.print(pressure/133.322, DEC);
Serial.println(“ mm Hg»);
Serial.println();
delay(1000);
}

Void bmp085Calibration()
{
ac1 = bmp085ReadInt(0xAA);
ac2 = bmp085ReadInt(0xAC);
ac3 = bmp085ReadInt(0xAE);
ac4 = bmp085ReadInt(0xB0);
ac5 = bmp085ReadInt(0xB2);
ac6 = bmp085ReadInt(0xB4);
b1 = bmp085ReadInt(0xB6);
b2 = bmp085ReadInt(0xB8);
mb = bmp085ReadInt(0xBA);
mc = bmp085ReadInt(0xBC);
md = bmp085ReadInt(0xBE);
}

Short bmp085GetTemperature(unsigned int ut)
{
long x1, x2;
x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
x2 = ((long)mc << 11)/(x1 + md);
b5 = x1 + x2;

Return ((b5 + 8)>>4);
}

Long bmp085GetPressure(unsigned long up)
{
long x1, x2, x3, b3, b6, p;
unsigned long b4, b7;
b6 = b5 - 4000;
// Calculate B3
x1 = (b2 * (b6 * b6)>>12)>>11;
x2 = (ac2 * b6)>>11;
x3 = x1 + x2;
b3 = (((((long)ac1)*4 + x3)<>2;
// Calculate B4
x1 = (ac3 * b6)>>13;
x2 = (b1 * ((b6 * b6)>>12))>>16;
x3 = ((x1 + x2) + 2)>>2;
b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
b7 = ((unsigned long)(up - b3) * (50000>>OSS));
if (b7 < 0x80000000)
p = (b7<<1)/b4;
else
p = (b7/b4)<<1;
x1 = (p>>8) * (p>>8);
x1 = (x1 * 3038)>>16;
x2 = (-7357 * p)>>16;
p += (x1 + x2 + 3791)>>4;
return p;
}

// Read 1 byte from the BMP085 at "address"
char bmp085Read(unsigned char address)
{
unsigned char data;

Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 1);
while(!Wire.available())
;
return Wire.read();
}

Int bmp085ReadInt(unsigned char address)
{
unsigned char msb, lsb;
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 2);
while(Wire.available()<2)
;
msb = Wire.read();
lsb = Wire.read();
return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
unsigned int ut;
// Write 0x2E into Register 0xF4
// This requests a temperature reading
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x2E);
Wire.endTransmission();
// Wait at least 4.5ms
delay(5);
// Read two bytes from registers 0xF6 and 0xF7
ut = bmp085ReadInt(0xF6);
return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
unsigned char msb, lsb, xlsb;
unsigned long up = 0;
// Write 0x34+(OSS<<6) into register 0xF4
// Request a pressure reading w/ oversampling setting
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF4);
Wire.write(0x34 + (OSS<<6));
Wire.endTransmission();
// Wait for conversion, delay time dependent on OSS
delay(2 + (3< // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
Wire.beginTransmission(BMP085_ADDRESS);
Wire.write(0xF6);
Wire.endTransmission();
Wire.requestFrom(BMP085_ADDRESS, 3);
// Wait for data to become available
while(Wire.available() < 3)
;
msb = Wire.read();
lsb = Wire.read();
xlsb = Wire.read();
up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
return up;
}


Помимо этого в датчике есть собственный термо-сенсор для компенсации давления и альтиметр

Arduino Nano v3.0
Это сердце всей метеостанции. По простому говоря, контроллер в миниатюрном размере.
Покупал
Рассказывать подробно про контроллер не буду, так как до меня уже это сделали:


Посылка с lightake была сборная, контроллер пришел в пакете, где был USB-кабель и Arduino в запаянном антистатическом пакете.

Чтобы оценить размеры, рядом с Arduino положил монетку номиналом 1 руб.

Плата контроллера вблизи



USB-кабель хороший, с ферритовым кольцом. Питается Arduino по USB кабелю. Среду разработки можно скачать (страница для скачивания ). Язык «С»-подобный, с освоением проблем не было, так как на нем очень много программирую на работе.

LCD экран
На работе в закромах нашёл совместимый LCD 1602 экран. С подключением пришлось повозиться, так как даташита на него не нашёл. В результате LCD заработал.

Но после недолгой эксплуатации заметил, что мне этого экрана мало и вывести больше данных не получится, так как он имеет всего 2 строки по 16 символов в каждой. Поначалу кажется, что этих параметров хватит, но когда начинаешь программировать, то понимаешь, что максимум можно впихнуть 3-4 параметра. А если делать меню (я ведь подумывал сделать меню на этом экране), то свободного места остаётся на 1-2 параметра.
В итоге начал подыскивать себе другой экран. Сначала присматривался к графическому экрану от Nokia 3310 и даже в аукционе eBay участвовал, чтобы его купить, но не сложилось (чему я очень рад), поэтому мне пришлось отказаться от этого экрана. Сейчас я понимаю, что он был бы слишком мал для моих целей, так как есть с чем сравнивать.
Случайно просматривая шилды на Arduino, я наткнулся на графический экран 12864 на контроллере ST7920. У этого экрана и размер подходящий, и хорошее разрешение для моих нужд (128х64). То есть можно спокойно разместить 6-7 строк по 20 символов нормально читающегося шрифта. Так как экран графический, то помимо текста разными шрифтами можно разместить и графику. Короче, это именно то, что мне нужно было, все присутствовало в этом экране, поэтому я не выдержал и заказал.
Посылка пришла быстро и была упаковано стандартно: конверт-пупырка, внутри ещё слой пупырки и экран в антистатическом пакете:






Чтобы оценить размеры, рядом с LCD положил монетку номиналом 1 руб.




Чтобы быстро подключить экран к Arduino, к контактам LCD припаял линейку контактов. Подключать LCD можно по последовательной шине и по параллельной. Я выбрал первый вариант, так как свободных контактов Arduino и так мало.
Подключение (взято из сети):

- Контакт 1 (GND) подключается к общей шине
- Контакт 2 (VCC) подключается к шине питания +5V, причём потребляемый ток сравнительно небольшой и дисплей можно питать от встроенного стабилизатора Arduino.
- Контакты 4, 5 и 6 подключаются к цифровым выходам Arduino, образуя последовательный интерфейс SPI:
контакт 4 – (RS) – соответствует линии CS (например 7)
контакт 5 – (RW) – соответствует линии MOSI (например 8)
контакт 6 – (E) – соответствует линии SCK (например 3)
номера контактов Arduino могут быть любыми, главное не забыть потом правильно указать их в тексте программы при инициализации дисплея.
- Контакт 15 (PSB) соединяется с общей шиной.
- Контакты 19 (A) и 20 (K) – это питание подсветки (+5V и GND соответственно). Для регулировки яркости подсветки можно использовать переменный резистор 10кОм, включённый между шинами питания и GND. Напряжение с его движка подаётся на контакт 19 дисплея.
По этой инструкции я подключил все, кроме подсветки. В качестве питания подсветки я использовал ШИМ Arduino.
Для того, чтобы программно подключить LCD к Arduino, используется библиотека u8glib. Скачать можно . Если есть проблемы скачивания, то могу библиотеку залить на narod.ru.
Сама библиотека не сложная и позволяет выводить текст разным шрифтом, рисовать линию, рисовать простейшие геометрические фигуры (прямоугольник, круг), выводить на экран свои изображения, подготовленные специальным образом. В принципе, этого инструмента достаточно для большинства задач.
Вот результат простенькой программы:

Сама программа:

#include «U8glib.h»

U8GLIB_ST7920_128X64 u8g(3, 9, 8, U8G_PIN_NONE); // SPI E = 3, RW = 9, RS = 8

// Подпрограмма определения свободной памяти
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0? (int) &__heap_start: (int) __brkval);
}

Void setup(void) {
u8g.setFont(u8g_font_6x10); // шрифт
u8g.setRot180(); //Перевернул экран
analogWrite(6, 115); // Устанавливаем яркость экрана (анод подсветки на 6 pin)
}

Void loop(void) {
u8g.firstPage();
do {

u8g.setPrintPos(1, 12); // позиция
u8g.print(«Hello!!!»); // вывод текста
u8g.drawBox(0,22,128,9); // Закрашиваем прямоугольник белым
u8g.setColorIndex(0); // белые чернила, черный фон
u8g.setPrintPos(1, 30); // позиция
u8g.print(«Word...»); // вывод текста

U8g.setColorIndex(1); // белые чернила, черный фон
u8g.setPrintPos(1, 50); // позиция
u8g.print(«After start =»); // вывод текста
u8g.setPrintPos(85, 50); // позиция
u8g.print(millis() / 1000); // вывод число секунд после старта
u8g.setPrintPos(1, 64); // позиция
u8g.print(freeRam ()); // вывод сколько памяти занято
} while(u8g.nextPage());

Delay(200);
}

Часы реального времени DS1307
Ещё один компонент для моей метеостанции. На данном шилде реализованы часы реального времени. Заказывал их на аукционе eBay. Продавец прислал платку часов в нереально большой коробке


Внутри коробки было два листка А4 с рекламой и платка часов, обмотанная целлофаном


Хочу заметить, что плата не превышает размером 2 руб. монету, а коробка была размером 13х15х5 см.
Плата была упакована в антистатический пакет

Платка вблизи



С данным модулем мне пришлось повозиться. Во-первых, были трудности подключения. А во-вторых, кварц на данной плате никакой. Если бы знал, что на модуль потрачу столько времени, то, скорее всего, собрал бы его сам, благо в сети полно схем. Самая простейшая схема содержит 4-5 компонентов.
По поводу подключения. Я нашёл библиотеку, в которой было сказано, что интерфейс I2C можно подключать не на привычные аналоговые входы Arduino (А4 и А5), а на любые дискретные. Как написано, так и сделал. Сначала ничего не работало, после долгого танца с бубном часы завелись. Ну, подумал, всё, проблемы закончились, но после того, как я попытался этот же модуль подключить к другой Arduino, пляски с бубном продолжились. Много времени потратил на поиски решения данной проблемы и практически везде указывалось либо на неправильное подключение, либо на отсутствие подтягивающих резисторов на контактах SCL и SDA. Я уже хотел с паяльником в плату лезть, но на одном форуме случайно наткнулся на код, где было сказано, чтобы SCL и SDA подключать к стандартным портам I2C на Arduino. После стандартного подключения, все сразу заработало.
Теперь по поводу кварца. Не знаю, что там за кварц ставят китайцы, но часы с таким кварцем убегали в сутки на 10-11 сек. В месяц данная погрешность составляет 5 минут, а в год 1 час. Нафиг такие часы не нужны. Пришлось снова лезть в сеть и искать, как исправить данный баг. Первое попавшее решение говорит о том, что нужно заземлить кварц. Сделал - результат нулевой. Ещё где-то нашёл, что нужно найти старую материнку и выпаять оттуда часовой кварц. Сделал - результат есть. Теперь часы убегают не на 10-11 секунд, а на 1,5 секунды в сутки. Скажем так, стало лучше, но до идеала далеко. Так как больше с паяльником возится неохота, то было решено подводить часы программно, то есть раз в сутки подводить часы на нужную величину. После 10 суток, часы ушли не более, чем на секунду. Метод хорош, но только тогда, когда устройство синхронизации Arduino подключено к питанию, иначе часы работают от батарейки и все равно убегают.
Небольшая тестовая программа:

#include «Wire.h»
#define DS1307_I2C_ADDRESS 0x68 // SDA A4, SCL A5

Byte decToBcd(byte val)
{
return ((val/10*16) + (val%10));
}

Byte bcdToDec(byte val)
{
return ((val/16*10) + (val%16));
}

Void setDateDs1307(byte second, // 0-59
byte minute, // 0-59
byte hour) // 0-99
{

Wire.write(0);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.endTransmission();
}

Void getDateDs1307(byte *second,
byte *minute,
byte *hour)
{

Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();

Wire.requestFrom(DS1307_I2C_ADDRESS, 3);

*second = bcdToDec(Wire.read());
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read());
}

Void setup()
{
byte second, minute, hour;
Wire.begin();
Serial.begin(9600);

Second = 45;
minute = 5;
hour = 16;

SetDateDs1307(second, minute, hour);
}

Void loop()
{
byte second, minute, hour;

GetDateDs1307(&second, &minute, &hour);
Serial.print(hour, DEC);
Serial.print(":");
Serial.print(minute, DEC);
Serial.print(":");
Serial.println(second, DEC);

Delay(1000);
}


Здесь не использована библиотека, да и функции усечены, для чтения и записи времени.

Датчик температуры и влажности DHT11
Про данный датчик рассказывать нечего. Я бы его даже не стал использовать, если бы не нужна была влажность. К сожалению, я его не сфотографировал, когда получил, поэтому фотографий не будет. Фотографии датчика можно будет посмотреть ниже, где я его подключил к Arduino. Подключение датчика простое (+, цифровой выход, -). Обычно датчики делают четырёх контактные. При таком форм-факторе третий контакт ни к чему не подключают.
Для подключения к Arduino можно использовать библиотеку. Скачать можно .
Небольшая тестовая программа c выводом информации на LCD дисплей 1602:

// include the library code:
#include
#include

// Declare objects
dht11 DHT11;
LiquidCrystal lcd(12, 11, 6, 5, 4, 3);

#define DHT11PIN 7
int i;

Void setup()
{
lcd.begin(16, 2);
lcd.print(«Status: „);
i=0;
}

Void loop()
{
int chk = DHT11.read(DHT11PIN);
lcd.setCursor(8, 0);
switch (chk)
{
case 0: lcd.print(“OK „); break;// lcd.setCursor(11, 0); lcd.print(millis()/2000); break;
case -1: lcd.print(“Checksum error»); mErr(); break;
case -2: lcd.print(«Time out error»); mErr(); break;
default: lcd.print(«Unknown error»); mErr(); break;
}
delay(500);
lcd.setCursor(15, 0);
switch (i)
{
case 0: lcd.print("^"); lcd.setCursor(15, 1); lcd.print(" ");break;
case 1: lcd.print(«v»); lcd.setCursor(15, 1); lcd.print(" ");break;
default: lcd.setCursor(15, 1); lcd.print(«E»); break;
}
i=i+1;
if (i>1) i=0;
lcd.setCursor(0, 1);
lcd.print(«H=»);
lcd.setCursor(2, 1);
lcd.print((float)DHT11.humidity, 0);
lcd.setCursor(4, 1);
lcd.print("%");
lcd.setCursor(8, 1);
lcd.print(«T=»);
lcd.setCursor(10, 1);
lcd.print((float)DHT11.temperature, 0);
lcd.setCursor(12, 1);
lcd.print(«C»);

Void mErr()
{
lcd.setCursor(2, 1);
lcd.print("**");
lcd.setCursor(10, 1);
lcd.print("**");
i=5;
}


Минусы у датчика есть – данные с датчика идут только в целых числах, да и диапазон слабенький.

Вроде, про все компоненты написал. Осталось собрать все в единое целое.
Упс, чуть не забыл! Для того, чтобы все собрать устройство, нужен корпус. Корпус тоже заказывал на Ebay. Продавец оказался из Англии. Посылка дошла быстро, но фотографировать её не стал. Все фотографии корпуса ниже.

Сначала собрал все на столе с помощью специальных проводков. Написал тестовую программу и залил её в контроллер.



На самом деле синий цвет подсветки гораздо ярче. Даже при минимальной яркости (Bright=5) происходит засветка кадра.

Чтобы все собрать без проводов, было решено сделать мини материнскую плату, а платка Arduino и шилды надевались на разъёмы. В случае чего, их с лёгкостью можно быстро извлечь. LCD экран и кнопки для управления я решил также цеплять на разъёмах, только датчик температуры впаять на проводах.
Вот такая вышла платка



На последней фотографии я ещё до конца флюс не смыл. Под шилды рядом с разъёмами приклеил пористую резину, чтобы была хоть какая-то опора. Хотя на самом деле шилды в разъёмах на контактах и так прекрасно держатся.

Материнская плата с установленными шилдами и платой Arduino.

Вот так выглядит полное подключение к материнской плате


Вместо кнопок использовал самодельный шилд, спаянный на макетной плате. В качестве кнопок использовал кнопки из старых мышек.
Как видно, количество проводов убавилось.

Основная проблема размещения в корпус - это ровно выпилить паз под LCD экран. Как я ни старался, все равно идеально не получилось. Щели в некоторых местах были чуть больше 1 мм. Чтобы все смотрелось аккуратно, я взял чёрный герметик для аквариума и залил все щели, заодно экран крепил именно на этот герметик. После высыхания герметика снаружи обрезал излишки. При ярком освещении герметик видно, а при обычном - все сливается с корпусом.
Вот так выглядит корпус изнутри с установленным LCD экраном и материнской платой.

Вот так выглядит снаружи при ярком освещении (прошу прощения за отпечатки пальцев, увидел их, когда разбирал фотографии).

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

Вот так выглядит платка с кнопками:



Вот так выглядит плата-держатель:


Здесь видны направляющие, в которые вставляется плата с кнопками. Некоторые элементы паял для того, чтобы придать жёсткость плате.

Теперь все засовываем в корпус
Без подключения кнопок:


С подключением кнопок:

Закрываем корпус и включаем. Все прекрасно работает, кнопки отрабатывают, как нужно.

В конце размещаю небольшое видео работы устройства в разных режимах:
http://www.youtube.com/watch?v=KsiVaUWkXNA&feature=youtu.be
У кого видео здесь не отображается, вот ссылка на

Пора заканчивать обзор.
Немного напишу о программе, а потом краткие выводы. Когда писал программу, не думал, что очень быстро упрусь в ограничение в 30720 байт.


Пришлось оптимизировать код. Многие куски кода выносил в подпрограммы. Никогда бы не подумал, что оператор switch… case в компилированном виде занимает больше места, чем несколько if… else. Ещё экономит место правильное объявление переменных. Если объявлять массив long, хотя вполне можно обойтись byte, то перерасход памяти достигает 500 байт в зависимости от размерности массива. Когда пишешь программу, то об этом не думаешь, а уже потом, когда анализируешь программу, то понимаешь, что некоторые вещи сделал неправильно, и начинаешь оптимизировать код. После того, как проблемы с размером программы были решены, я упёрся в ограничение оперативной памяти. Выражалось это в том, что программа начинала виснуть после загрузки. Пришлось вводить подпрограмму подсчёта свободной оперативной памяти. В результате, был вынужден отказаться от одного алгоритма предсказывания погоды, так как он должен выводить пиктограммы на экран. Сам алгоритм работает, а вот вывод пиктограмм пришлось заремировать. У меня есть ещё задумки, как оптимизировать код, но в ближайшем будущем оставляю работать устройство, как есть, чтобы оценить работоспособность и выявить все баги.

Теперь небольшие выводы
Минусы
1) Цена. Оправдание этому минусу – хобби никогда не бывает дешёвым.

Плюсы
1) Большой функционал устройства
2) Наращивание функций ограничивается только используемым контроллером и собственным желанием
3) Эстетическое удовольствие от созерцания и моральное удовльствие от того, что я все-таки собрал и доделал это устройство

Планирую купить +86 Добавить в избранное Обзор понравился +137 +304

Наблюдение за погодой - весьма увлекательное занятие. Я решил построить свою погодную станцию на базе популярного .

Прототип метеостанции выглядит так:

Функции моей метеостанции:

  • измерение и отображение комнатной и наружной температур;
  • отображение текущего времени (часы и минуты);
  • отображение текущих фазы Луны и лунного дня;
  • передача результатов измерений на компьютер через последовательное соединение;
  • передача результатов измерений по протоколу MQTT с помощью приложения на компьютере.


Hex
-файл
прошивки для (версия от 9 мая 2018 года) - .
Как прошить hex -файл в плату Arduino , я описал .

Микроконтроллер Arduino Nano 3.0

"Сердцем" моей метеостанции является микроконтроллер eBay ):

Для управления индикацией и опросом датчиков я использую таймер 1 Arduino , вызывающий прерывания с частотой 200 Гц (период - 5 мс).

Индикатор

Для отображения измеряемых показаний датчиков и текущего времени я подключил к Arduino четырехразрядный светодиодный индикатор Foryard FYQ-5643BH с общими анодами (аноды одинаковых сегментов всех разрядов объединены).
Индикатор содежит четыре семисегментных разряда и две разделительные (часовые) точки:

Аноды индикатора подключены через токограничивающие резисторы к выводам Arduino :

разряд 1 2 3 4
вывод A3 A2 D3 D9

Катоды сегментов подключены к выводам Arduino :

сегмент a b c d e f g p
вывод D7 D12 D4 D5 D6 D11 D8 D13

Сегмент индикатора светится, если на аноде соответствующего разряда высокий потенциал (1), а на катоде - низкий (0).

Я использую динамическую индикацию для отображения информации на индикаторе - в каждый момент времени активен только один разряд. Активные разряды чередуются с частотой 200 Гц (период отображения 5 мс). При этом для глаз мерцание сегментов незаметно.

Датчик температуры DS18x20

Для возможности удаленного измерения температуры я подключил датчик , который обеспечивает измерение наружной температуры в широких пределах. Датчик подключается к шине 1-Wire и имеет три вывода - питание (VCC ), данные (DAT ), земля (GND ):

вывод датчика VCC DAT GND
вывод Arduino 5V A1 GND

Между выводами VCC и DAT я включил подтягивающий резистор сопротивлением 4,7 кОм.

Для перевода между градусами Цельсия и Фаренгейта можно использовать такую табличку:

Я разместил датчик за окном дома в пластиковом корпусе от шариковой ручки:

\

В профессиональных метеостанциях для защиты термометра от прямых солнечных лучей и обеспечения циркуляции воздуха используется экран Стивенсона (англ. Stevenson screen ):

Датчик давления и температуры BMP280

Для измерения атмосферного давления традиционно используют ртутные барометры и барометры-анероиды.

В ртутном барометре атмосферное давление уравновешивается весом столба ртути, высота которого и ипользуется для измерения давления:

В барометре-анероиде используется сжатие и растяжение коробки под действием атмосферного давления:

Для измерения атмосферного давления и комнатной температуры в своей домашней метеостанции я использую датчик - маленький SMD -датчик размером 2 x 2,5 мм, основанный на пьезорезистивной технологии:

Платка с датчиком приобретена на торговой площадке eBay :

Датчик подключается к шине I2C (контакт данных - SDA/SDI , контакт синхронизации - SCL/SCK ):

вывод датчика VCC GND SDI SCK
вывод Arduino 3V3 GND A4 A5

Adafruit - файлы Adafruit_Sensor.h , Adafruit_BMP280.h , Adafruit_BMP280.cpp .

Единицы измерения атмосферного давления

Датчик через функцию readPressure выдает значение атмосферного давления в паскалях. Основной единицей измерения атмосферного давления служит гектопаскаль (гПа) (1 гПа = 100 Па), аналогом которого является внесистемная единица "миллибар " (мбар) (1 мбар = 100Па = 1гПа). Для перевода между часто используемой внесистемной единицей измерения давления "миллиметр ртутного столба " (мм рт. ст.) и гектопаскалями используются соотношения:
1гПа = 0,75006 мм рт. ст. ≈ 3/4 мм рт.ст.; 1 мм рт.ст. =1,3332 гПа ≈ 4/3 гПа.

Зависимость атмосферного давления от высоты над уровнем моря

Атмосферное давление может быть представлено как в абсолютной, так и в относительной форме.
Абсолютное давление QFE (англ. absolute pressure ) – это актуальное атмосферное давление, не учитывающее поправку над уровнем моря.
Атмосферное давление уменьшается примерно на 1 гПа при повышении высоты на 1 м:

Барометрическая формула позволяет определить коррекцию показаний барометра для получения относительного давления (в мм рт. ст.):
$\Delta P = 760 \cdot (1 - {1 \over {10^ { {0,0081350 \cdot H} \over {T + 0,00178308 \cdot H} }}})$ ,
где $T$ - средняя температура воздуха по шкале Ранкина, °Ra , $H$ - высота над уровнем моря, футы.
Перевод градусов Цельсия в градусы Ранкина:
$^{\circ}Ra = {^{\circ}C \cdot 1,8} + 491,67$
Барометрическая формула используется при барометрическом нивелировании - определении высот (с погрешностью 0,1 - 0,5 %). В формуле не учитывается влажность воздуха и изменение ускорения свободного падения с высотой. Для небольших перепадов высоты эту экспоненциальную зависимость можно с достаточной точностью аппроксимировать линейной зависимостью.
Относительное давление QNH (англ. relative pressure , Q-code Nautical Height ) – это атмосферное давление, учитывающее поправку к среднему уровню моря (англ. Mean Sea Level, MSL ) (для ISA и температуры 15 градусов Цельсия), и первоначально выставляется с учётом высоты, на которой находится метеостанция. Его можно узнать из данных метеослужбы, показаний откалиброванных приборов в публичных местах, аэропорту (из сводок METAR ), из Интернета.
Например, для расположенного рядом аэропорта Гомель (UMGG ) я могу посмотреть сводку фактической погоды METAR на ru.allmetsat.com/metar-taf/russia.php?icao=UMGG :
UMGG 191800Z 16003MPS CAVOK M06/M15 Q1014 R28/CLRD// NOSIG ,
где Q1014 - давление QNH на аэродроме равно 1014 гПа.
Историю сводок METAR можно получить на aviationwxchartsarchive.com/product/metar .
За нормальное относительное давление воздуха QNH принимается давление 760 мм рт. ст. или 1013,25 гПа (при температуре 0ºС, под широтой 45º Северного или Южного полушария).
Я выставил для барометра-анероида давление QNH с помощью винта настройки чуткости:

Прогноз погоды

Анализ изменения давления позволяет строить прогноз погоды, причем его точность тем выше, чем более резко меняется давление. Например, старое эмпирическое правило мореплавателей гласит - падение давления на 10 гПа (7,5 мм рт. ст.) за период 8 часов говорит о приближении сильного ветра.

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

За направление ветра в метеорологии принимается направление, откуда дует ветер:

Это направление сводится к восьми румбам.

Для предсказания погоды на основе атмосферного давления и направления ветра часто используется алгоритм Zambretti .

Датчик влажности

Для определения относительной влажности воздуха я использую модуль DHT11 (приобретен на торговой площадке eBay ):

Датчик влажности DHT11 имеет три вывода - питание (+ ), данные (out ), земля (- ):

вывод датчика + out -
вывод Arduino 5V D10 GND

Для работы с датчиком я использую библиотеку от Adafruit - файлы DHT.h , DHT.cpp .

Влажность воздуха характеризует количество водяного пара, содержащегося в воздухе. Относительная влажность показывает долю влаги в воздухе (в процентах) по отношению к максимальному возможному количеству при текущей температуре. Для измерения относительной влажности служит :

Для человека оптимальный интервал влажности воздуха - 40 ... 60 %.

Часы реального времени

В качестве часов реального времени я применил модуль RTC DS1302 (платка с часиками приобретена на торговой площадке eBay ):

Модуль DS1302 подключается к шине 3-Wire . Для использования этого модуля совместно с Arduino разработана библиотека iarduino_RTC (от iarduino.ru ).

Плата с модулем DS1302 имеет пять выводов, которые я соединил с выводами платы Arduino Nano :

вывод RTC VCC GND RST CLK DAT
вывод Arduino 5V GND D2 D1 D0

Для сохранения верных показаний часов при отключенном питании в гнездо на плате я вставил батарейку CR2032 .

Точность моего часового модуля оказалась не слишком высокой - часы спешат примерно на одну минуту за четверо суток. Поэтому я сделал сброс минут на "ноль" и часа на ближайший при удержании кнопки, подключенной к выводу A0 Arduino, после включения питания метеостанции. После инициализации вывод A0 используется для передачи данных через последовательное соединение.

Передача данных на компьютер и работа по протоколу MQTT

Для передачи данных через последовательное соединение к Arduino подключается USB -UART преобразователь:

Вывод Arduino используется для передачи данных в формате 8N1 (8 бит данных, без бита четности, 1 стоп-бит) со скоростью 9600 бит/с. Данные передаются пакетами, причем длина пакета - 4 символа. Передача данных осуществляется в "bit-bang " режиме, без использования аппаратного последовательного порта Arduino .

Формат передаваемых данных:

Параметр 1-й байт 2-й байт 3-й байт 4-й байт
наружная температура o пробел либо минус десятки градусов либо пробел единицы градусов
комнатная температура i пробел либо минус десятки градусов либо пробел единицы градусов
атмосферное давление p сотни мм р. ст. десятки мм рт.ст. единицы мм рт. с.
относительная влажность h пробел десятки процентов либо пробел единицы процентов
текущее время десятки часов единицы часов десятки минут единицы минут

MQTT

Golang приложение - клиент протокола MQTT , отправляющую принятую от метеостанции информации на сервер (MQTT -брокер) :

Сервис позволяет создать акаунт с бесплатным тарифным планом "" (ограничения: 10 соединений, 10 Кб/с):

Для мониторинга показаний метеостанции при этом можно использовать Android -приложение :

Питание

Для питания метеостанции я использую зарядное устройство от старого мобильного телефона Motorola , выдающее напряжение 5 В с током до 0,55 А и подключаемое к контактам 5V (+) и GND (-):

Также можно использовать для питания батарейку напряжением 9 В, подключаемую к контактам VIN (+) и GND (-).

Эксплуатация метеостанции

При запуске происходит инициализация и проверка датчиков.

При отсутствии датчика DS18x20 выдается ошибка "E1", при отсутствии датчика - ошибка "E3".

Затем запускается рабочий цикл метеостанции:

  • измерение и отображение наружной температуры;
  • измерение и отображение комнатной температуры;
  • измерение и отображение атмосферного давления и тренда его изменения;
  • измерение и отображение относительной влажности воздуха;
  • отображение текущего времени;
  • отображение фазы Луны и лунного дня.


Видео работы моей метеостанции доступно на моем -канале: https://youtu.be/vVLbirO-FVU

Отображение температуры

При измерении температуры индицируется две цифры температуры и для отрицательной температуры знак "минус" (с символом градуса в крайнем правом разряде);
для наружной температуры знак градуса отображается вверху:


для комнатной температуры - внизу:

Отображение давления

При измерении давления индицируются три цифры давления в мм ртутного столба (с символом "P " в крайнем правом разряде):

Если давление резко упало, то вместо символа "P " в крайнем правом разряде отображается символ "L ", если резко выросло - то "H ". Критерий резкости изменения - 8 мм рт. ст. за 8 часов:

Так как моя метеостанция отображает абсолютное давление (QFE ), то показания оказываются несколько заниженными по сравнению со сведениями в сводке METAR (в которой приводится QNH ) (14 UTC 28 марта 2018 года):

Отношение давлений (по сведениями ATIS ) составило ${1015 \over 998} = 1,017$. Возвышение аэропорта Гомель (код ИКАО UMGG ) над уровнем моря составляет 143,6 м. Температура по данным ATIS составляла 1 °C .

Показания моей метеостанции практически совпали с абсолютным давлением QFE по сведениями ATIS !

Максимальное/минимальное давления (QFE ), зарегистрированные моей метеостанцией за все время наблюдений:

Отображение относительной влажности воздуха

Относительная влажность воздуха отображается в процентах (в двух правых разрядах отображается символ процента):

Отображение текущего времени

Текущее время отображается на индикаторе в формате "ЧЧ:ММ", причем разделительное двоеточие мигает раз в секунду:

Отображение фаз Луны и лунного дня

Первые два разряда индикатора отображают текущую лунную фазу, а следующие два - текущий лунный день:

У Луны выделяются восемь фаз (приведены английские и русские (синим цветом - неточные) названия):

На индикаторе фазы отображаются пиктограммами:

фаза пиктограмма
растущий серп (полумесяц)
убывающий серп (полумесяц)

Передача данных на компьютер

Если соединить метеостанцию с USB -UART преобразователем (например, на базе микросхемы CP2102 ), подключенным к USB -порту компьютера, то можно с помощью терминальной программы наблюдать передаваемые метеостанцией данные:

Я разработал на языке программирования golang программу, ведущую журнал метеонаблюдений и отправляющую данные в сервис , и их можно просматривать на Android -смартфоне с помощью приложения :

По данным журнала метеонаблюдений можно, например, строить график изменения атмосферного давления:
пример графика с заметным минимумом давления


пример графика с незначительным ростом давления

Планируемые доработки:

  • добавление датчиков направления и скорости ветра

В метеостанциях для измерения скорости ветра используется трехчашечный анемометр (1), а для определения направления ветра - флюгер (2):

Также для измерения скорости ветра используются термоанемометры с нитью накала (англ. hot wire anemometer ). В качестве нагреваемой проволоки можно использовать вольфрамовую нить накала от лампочки с разбитым стеклом. В промышленно выпускаемых термоанемометрах датчик обычно располагается на телескопической трубке:

Принцип действия этого прибора заключается в том, что тепло отводится от нагревательного элемента вследствие конвекции воздушным потоком - ветром. При этом сопротивление нити накала определяется температурой нити. Закон изменения сопротивления нити накала $R_T$ от температуры $T$ имеет вид:
$R_T = R_0 \cdot (1 + {\alpha \cdot (T - T_0)})$ ,
где $R_0$ - сопротивление нити при температуре $T_0$, $\alpha$ - температурный коэффициент сопротивления (для вольфрама $\alpha = 4,5\cdot{10^{-3} {^{\circ}{C^{-1}}}}$).

С изменением скорости воздушного потока изменяется температура при неизменном токе накала (анемометр с постоянным током, англ. CCA ). Если температура нагревательного элемента поддерживается постоянной, то ток через элемента будет пропорционален скорости воздушного потока (анемометр с постоянной температурой, англ. CTA ).

Продолжение следует