В свободное время, и на этот раз написал инструкцию по изготовления небольшой метеостанции. Она будет выполнять функцию часов с датой и показывать температуры внутри и снаружи помещения. Как основной контролер будем использовать Arduino UNO, но подойдет и другая плата с Atmega328p на борту. Для отображения используем графический экран WG12864B. Также подключим два датчика температуры ds18b20. Один внутри помещения, второй вынесем наружу. Начнем.

В процессе изготовления самоделки нам понадобится:

Arduino UNO (Или любая другая Arduino совместимая плата)
- WG12864B графический экран
- ds18b20 датчик температуры, 2шт
- Блок питания 6 – 12 В
- Резисторы 4.7 Ком 0.25 Вт, 2 шт.
- Резисторы 100 ом 0.25 Вт
- Батарейный отсек для 4 батареек типа ААА «мизинчиковых»
- Коробка от картриджа приставки SEGA
- Изолента
- Соединительные провода
- Монтажная плата
- Кнопки
- Канцелярский нож
- Паяльник
- Припой, канифоль
- Двусторонний скотч

Шаг 1 Подготовка WG12864B3.
Тех, кто не работал до этого с экранами, может напугать большое количество модификаций, с виду одинаковых, экранов. Немного поясню. Большинство экранов такого типа работают на микросхемах ks0107/ks0108. Все экраны можно раздлить на 4 типа:

Вариант A: HDM64GS12L-4, Crystalfontz CFAG12864B, Sparkfun LCD-00710CM, NKC Electronics LCD-0022, WinStar WG12864B-TML-T

Вариант B: HDM64GS12L-5, Lumex LCM-S12864GSF, Futurlec BLUE128X64LCD, AZ Displays AGM1264F, Displaytech 64128A BC, Adafruit GLCD, DataVision DG12864-88, Topway LM12864LDW, Digitron SG12864J4, QY-12864F, TM12864L-2, 12864J-1

Вариант C: Shenzhen Jinghua Displays Co Ltd. JM12864

Вариант D: Wintek- Cascades WD-G1906G, Wintek - GEN/WD-G1906G/KS0108B, Wintek/WD-G1906G/S6B0108A, TECDIS/Y19061/HD61202, Varitronix/MGLS19264/HD61202

Выглядят они почти одинаково. Но пины подключение у них разные. Я выбрал, и вам рекомендую, WG12864B3 V2.0, но если экран пришел другой, или просто под руками такого нет, вы легко разберётесь с помощью таблицы:

Вкратце характеристики:

В интернете много разных схем подключения, и все вроде как рабочие. Все дело в том, что существуют не только разные экраны, но и два способа их подключения: последовательный и параллельный. При использовании подключения по последовательному порту – нам понадобится всего 3 выхода микроконтроллера. При параллельном минимум 13. Выбор в данном случаем очевиден, у Arduino и так не много выводов. Для параллельного соединения схема подключения следующая:

Для последовательного подключения, которое будем использовать мы, схема следующая:

WG12864B – Arduino UNO 1 (GND) - GND 2 (VCC) - +5V 4 (RS) – 10 5 (R/W) – 11 6 (E) – 13 15 (PSB) – GND 19 (BLA) – через резистор 100 Ом - +5V 20 (BLK) – GND

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

Резистор в 100 Ом нужен, чтобы напряжением в 5 вольт, случайно не сжечь диоды подсветки.

Шаг 2 Изготовление корпуса.
Для корпуса возьмем коробку от картриджа приставки Sega. Если не найдете под руками эту коробку, можно использовать и другой корпус. Главное, чтобы в него поместился экран и Arduino.

Срезаем прозрачную пленку, сверху коробки, так чтобы не оставалось кусков:

Затем, используя канцелярский нож, вырезаем окошко размером 37х69, для экрана.

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

Снимаем защитную бумажку со скотча, и приклеиваем на него наш экран:

С внешней стороны должно выглядеть так:

Ниже экран, также на двусторонний скотч, крепим Arduino, сделав предварительно вырезы под USB- порт и гнездо питания:

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

Шаг 3 Датчики температуры.
Мы будем использовать цифровые датчики температуры DS18B20. Используя их мы получаем большую точность измерения, погрешность не более 0,5 °C, в большом диапазоне температур -55 … + 125 °C. Кроме этого, датчик цифровой и все вычисления выполняет сам, а Arduino просто получает готовые показания. При подключении этого датчика не забывайте о подтягивающем резисторе, номиналом 4.7 КОм, между контактами DQ и VDD. Также возможно несколько вариантов подключения. С внешним питание, на мой взгляд лучший вариант, его и будем использовать:

При любом варианте питания, датчики подключаются параллельно:

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

Общий провод от обоих кнопок подключаем к GND, провод от первой кнопки подключаем к A0, от второй к A1.
Крепим на двусторонний скотч рядом с Arduino:

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

Рассчитайте провод необходимой длины, чтобы можно было вывесить датчик снаружи окна, главное, чтобы он был не больше метров 5, если нужна длина больше, надо будет уменьшать номинал подтягивающего резистора.

Провод от шины данных DQ обоих датчиков подключаем к pin 5 Arduino.
Vdd - +5 Arduino.
GND – GND Arduino.

Шаг 4 Питание.
Для питания можно использовать блок питания напряжением от 6 до 12 вольт. На конце провода блика питания следует напаять штекер, подходящий к гнезду питания Arduino:

Или можете поместить в корпус батарейный отсек для четырех батареек типа «ААА», «мизинчиковые». И подключить плюсовой провод от отсека к Vin Arduino, а минус к GND.

Шаг 5 Подготовка среды программировании.
Для начала необходимо скачать и установить Arduino IDE с официального сайта

А также добавить в две библиотеки, необходимые для скетча. OneWire – необходима для связи с датчиками ds18b20:

U8glib – используется для вывода информации на экран:

Скачиваем библиотеки. Затем распаковываем архивы, и перемещаем содержимое архивов в папку «libraries», находящуюся в папке с установленной Arduino IDE. Также можно добавить библиотеки через Arduino IDE. Для этого, не распаковывая архивы, запускаем Arduino IDE, выбираем в меню Скетч – Подключить библиотеку. В самом верху выпадающего списка выбираем пункт «Добавить.Zip библиотеку». Указываем место нахождения скачанных архивов. После всех действий, необходимо перезагрузить Arduino IDE.

Шаг 6 Редактирование скетча.
Датчики температуры работают по протоколу One Wire и имеют уникальный адрес для каждого устройства - 64-разрядный код. Добавлять команды поиска датчиков в скетч не целесообразно. Незачем нагружать Arduino каждый раз икать датчики. Поэтому вначале, собрав все вместе, заливаем в Arduino скетч, находящийся в меню Файл – Примеры – Dallas Temperature – OneWireSearch. Затем запускаем Инструменты - Монитор порта. Arduino должна найти наши датчики, написать адреса и показания температуры. Эти адреса необходимо записать или просто скопировать куда-нибудь. Теперь открываем скетч Ard_Tic_Tak_WG12864B_2_x_Term_Serial, и ищем строки:

Byte addr1={0x28, 0xFF, 0x75, 0x4E, 0x87, 0x16, 0x5, 0x63};//адрес внутреннего byte addr2={0x28, 0xFF, 0xDD, 0x14, 0xB4, 0x16, 0x5, 0x97};//адрес внешнего датчика

Заменяем адреса соответствующих местонахождению датчиков, на свои адреса.
У нас часы не используют модуль RTC (часы реального времени), поэтому необходимо откорректировать ход часов. Для удобства раскомментируйте строку (на экране появятся секунды):

//u8g.setPrintPos(44, 50); u8g.print(sek); // Выводим секунды для контроля правильности хода

Установите правильное время, через монитор порта. Для этого откройте монитор порта, дождитесь окончания первоначальных замеров температуры, и введите текущую дату и время в формате "день, месяц, год, часы, минуты, секунды". Без пробелов, числа разделяем запятыми или точками.

Если часы спешат, меняем значение на большее, рекомендую экспериментировать с шагом в 100 единиц. Если отстаю следует уменьшить значение в строке:

If (micros() - prevmicros >494000) { // поменять на другое для корректировки было 500000

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

За основу взят проект метеостанции из книги В. Петина "Проекты с использованием контроллера Arduino" 2-е издание (проект 5 приложения 2) . Использовалась среда Arduino IDE 1.8.5 в Windows 10.
При запуске скетча выдавалась ошибка

В интернете можно скачать библиотеки для Arduino, имеющие одинаковые названия, но разное содержимое. Скетч может не работать, если вы используете "не ту" библиотеку. Видимо, мне попались не те библиотеки. В проект добавил датчик BMP180 для измерения атмосферного давления и переработал скетч.

Схема соединений

Сканирование адресов

Сначала подключите к Arduino датчик BMP180 и индикатор LCD1602. Скомпилируйте скетч I2C scanner и запустите его, чтобы определить адреса устройств на шине I2C.

Каждые 5 секунд программа сканирует устройства и выдает адреса на COM порт. У меня найдены два устройства с адресами 0x3F и 0x77. BMP180 по умолчанию имеет адрес 0x77, значит LCD индикатор имеет адрес 0x3F.
В некоторых схемах книги перепутаны местами подключения сигналов SDA и SCL к плате Arduino. Должно быть: SDA — к A4, SCL — к A5. Если у модуля BMP180 пять выводов, то на вывод VIN подается +5 Вольт .

Монтажная схема

Теперь соберите схему полностью. Я использовал RGB светодиод с общим катодом, смонтированный на плате вместе с резисторами 150 Ом. Общий катод подключается к контакту GND, остальные выводы — по схеме. Вносить изменения в скетч не требуется, так как яркость светодиодов меняется по циклическому закону.
На схеме показано подключение RGB светодиода с общим анодом, как в книге .
Если на экране LCD1602 не видно символов, то покрутите регулятор яркости. Подсветка индикатора потребляет довольно большой ток, поэтому используйте блок питания на ток не менее 2 А. Я использовал USB хаб с внешним блоком питания на 2 А.
В схеме использовал пьезозвонок ЗП-22. Резистор, подключенный к звонку, на 100 Ом . Частоту звука можно изменить в программе. Выбрал частоту 1000 Гц. Если вам попался зуммер с фиксированной частотой звука, то включать и выключать его можно просто подачей и снятием напряжения, как обычный светодиод. При запуске скетча подается короткий звуковой сигнал. Можно включить периодическую подачу сигналов во время работы программы, раскомментировав строку //bzz(100); в скетче.
В проекте использовал датчик DHT11 в виде модуля с уже смонтированным резистором 4.7 кОм. Сопротивление может быть от 4.7 до 10 кОм.
Подключите контакт Vcc модуля часов DS1302 к шине +5 Вольт. Таким образом вы уменьшите разряд батареи, по сути она будет работать только тогда, когда отключится питание Arduino.

Программа (скетч)

Для обслуживания BMP180 использована библиотека bmp085. Значение давления зависит от высоты местности. Для корректного значения атмосферного давления надо подобрать высоту. Для этого отредактируйте строку dps.init(MODE_STANDARD, 10000, true); У меня высота равна 100 м (10000 см). Фрагмент расчета давления взят из примера BMP085_test2.ino библиотеки bmp085.

Скетч meteo_P

#include
#include
#include
#include "DHT.h"
#include
BMP085 dps = BMP085();
long Pressure = 0, Altitude = 0;
unsigned long time1 = 0;

#define DHTPIN 10
#define DHTTYPE 11 // 11 - DHT11, 22 - DHT22
DHT dht(DHTPIN, DHTTYPE);

int kCePin = 4; // RST DS1302
int kIoPin = 3; // Data DS1302
int kSclkPin = 2; // CLK DS1302
DS1302 rtc(kCePin, kIoPin, kSclkPin);

int REDpin = 9;
int GREENpin = 6;
int BLUEpin = 11;

LiquidCrystal_I2C lcd(0x3f, 16, 2); // укажите свой адрес 0x20...0xff address
unsigned long memTime;
int bzzPin = 8;

void HumTempRead() {
float hum = dht.readHumidity();
float temp = dht.readTemperature();
if (isnan(hum) || isnan(temp)) {
Serial.println("Failed to read from DHT sensor!");
lcd.setCursor(0, 1);
lcd.print("H=--% T=---");
lcd.setCursor(11, 1);
lcd.print((char)223);
lcd.setCursor(12, 1);
lcd.print("C ");
} else {
lcd.setCursor(0, 1);
lcd.print("H=");
lcd.setCursor(2, 1);
lcd.print(hum);
lcd.setCursor(4, 1);
lcd.print("% T=+");
lcd.setCursor(9, 1);
lcd.print(temp);
lcd.setCursor(11, 1);
lcd.print((char)223);
lcd.setCursor(12, 1);
lcd.print("C ") ;
}
}

void setup_bzz() {
pinMode (bzzPin, OUTPUT);
}

void bzz(int _bzzTime) {
tone(bzzPin, 1000 , _bzzTime); // частота 1000 Гц
}

void setup() {
Serial.begin(9600);
Wire.begin();
delay(1000);

dps.init(MODE_STANDARD, 10000, true); // 100 meters (высоту над уровнем моря в cм)

dht.begin();
setup_bzz();
bzz(100);

Lcd.init();
lcd.backlight();
lcd.home();
// lcd.setCursor(0, 0);

rtc.halt(false);
rtc.writeProtect(false);

//rtc.setDOW(FRIDAY); // Set Day-of-Week to FRIDAY установите день недели
//rtc.setTime(4, 58, 0); // Set the time to 12:00:00 (24hr format) установите время
//rtc.setDate(6, 8, 2010); // Set the date to August 6th, 2010 установите дату (число, месяц, год)
}

lcd.setCursor(8, 0);
lcd.print(rtc.getTimeStr());

if ((millis() - memTime > 2000) or (millis() < memTime)) { // DHT11/22 1 time each 2 seconds
HumTempRead();
memTime = millis ();
}
delay(100);

if (((millis() - time1) / 1000.0) >= 1.0) {
dps.calcTrueTemperature();
time1 = millis();
}
dps.getPressure(&Pressure);
Serial.print(" Pressure(Pa):");
Serial.println(Pressure);

long p2;
int pi;
p2 = (Pressure / 133.3224); // Па в мм рт.ст.
pi = trunc(p2); // отбрасывание дробной части числа

lcd.setCursor(0, 0);
lcd.print("P=");
lcd.setCursor(2, 0);
lcd.print(pi); // вывод атм. давл. на LCD
lcd.setCursor(5, 0);
lcd.print("mm");
// delay(3000);
//bzz(100); // раскомментируйте, если хотите слушать сигналы
{
for (int value = 0 ; value <= 255; value += 1) {
analogWrite(REDpin, value);
analogWrite(GREENpin, 255 - value);
analogWrite(BLUEpin, 255);
delay(5);
}

for (int value = 0; value <= 255; value += 1) {
analogWrite(REDpin, 255);
analogWrite(GREENpin, value);
analogWrite(BLUEpin, 255 - value);
delay(5);
}

for (int value = 0; value <= 255; value += 1) {
analogWrite(REDpin, 255 - value);
analogWrite(GREENpin, 255);
analogWrite(BLUEpin, value);
delay(5);
}
}
}

В Каталоге файлов вы можете скачать скетч и библиотеки, которые использовались в проекте.

Импортируйте в среду Arduino IDE библиотеки LiquidCrystal_I2C.zip, bmp085.zip, DS1302.zip и DHT.zip из скачанного архива. В меню пройдите Скетч Подключить библиотеку Добавить.ZIP библиотеку... и в окне выберите zip-архив библиотеки.
Загрузите скетч meteo_P. Замените в скетче адрес LCD1602 на значение, полученное при сканировании шины I2C. Скомпилируйте и запустите скетч.
Если скетч заработал, то откройте монитор порта и просмотрите выдаваемые сообщения. Подберите высоту в операторе dps.init(MODE_STANDARD, 10000 , true); , чтобы получить реальные значения давления.
Настройте часы. Раскомментируйте строку //rtc.setTime(4, 58, 0); и в скобках укажите текущее время (час, минуты и секунды через запятую) и перезагрузите скетч в контроллер. После того, как время установится, снова закомментируйте эту строку и опять перезапустите скетч.
Если вас раздражает иллюминация ночника, то вы можете ее настроить, изменив длительность задержки в циклах for в конце скетча. При delay(2); цикл длится 2-3 секунды, при delay(5); — от 4 до 5 секунд, при delay(30); — до 15-16 секунд. С таким же интервалом будет обновляться информация на индикаторе.
При автономном использовании метеостанции, т.е. без подключения к USB порту компьютера, закомментируйте в скетче строки со словами Serial ..., чтобы отключить вывод информации в монитор COM порта.

PS. В скетче книги и в примерах к библиотеке DHT указана строка определения #define DHTTYPE DHT 11 . Скетч запускается, но вылетает через несколько часов. Часы останавливаются, индикация не меняется. В мониторе порта появляется невнятное сообщение, в котором присутствует ссылка на dht.
В этой строке убрал буквы DHT, т.е. сделал #define DHTTYPE 11 . После этого скетч стал работать стабильно.

Статья обновлена 25.06.2018 г.

Использованные ресурсы
1. Петин В.А. Проекты с использованием контроллера Arduino (Электроника) 2-е издание, Спб. БХВ-Петербург, 2015 464 с.
2. Петин В. А., Биняковский А. А. Практическая энциклопедия Arduino. - М., ДМК Пресс, 2017. - 152 с.
3. http://arduinolearning.com/code/i2c-scanner.php
4. http://arduino.ru/forum/programmirovanie/ds1302lcd1602
5. http://роботехника18.рф/как-подключить-lcd-1602-к-arduino-по-i2c/
6. пример BMP085_test2.ino из библиотеки bmp085.zip
7. http://proginfo.ru/round/
8. http://homes-smart.ru/index.php?id=14&Itemid=149&option=com_content&view=article
9. http://iarduino.ru/lib/datasheet%20bmp180.pdf
10. http://it-donnet.ru/hd44780_dht11_arduino/

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

Наша метеостанция будет состоять из двух устройств: компактного автономного устройства, измеряющего погодные показатели, и устройства-ретранслятора, получающего эти показатели и отправляющего их на «народный мониторинг». Устройства будут связываться по беспроводному каналу связи на частоте 433 МГц. Автономная часть будет питаться от трёх пальчиковых батареек и сможет просуществовать на одном комплекте батарей до года при периоде опроса датчиков в 20 мин.

Такая конструкция позволяет не сверлить стены для прокладки проводов с улицы, где необходимо производить измерения, в помещение, где результатами этих измерений надо пользоваться.

Что для этого необходимо?

Для изготовления автономного передатчика нам понадобятся:

    Держатель пальчиковых батареек на x3 AA

Для изготовления ретранслятора нам понадобятся:

Так же удобно установить два светодиода для индикации процессов:

Для звуковой индикации разряда батареи автономной части удобно использовать пьезо-пищалку:

Как это собрать?

Сборка автономной части

Сборка ретранслятора

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


Исходный код

Код автономной части

meteo_sensor.ino #include #include #include #include // Таймаут между посылками (не более 65535) #define TIMEOUT 60000 // Количество попыток отправки посылки #define ATTEMPTS 3 // Информационный пин передатчика #define RF_PIN 5 // Пины датчика температуры и влажности #define GND1_PIN 10 #define VCC1_PIN 11 #define GND2_PIN 7 #define VCC2_PIN 8 #define DATA_PIN 12 #define CLK_PIN 9 AmperkaLine rf(RF_PIN) ; SHT1x sht1x(CLK_PIN, DATA_PIN) ; void loop(void ) ; // Функция усыпления платы. Каждые TIMEOUT секунд // будет вызываться функция loop_func. TEENSY3_LP LP = TEENSY3_LP() ; sleep_block_t* LP_config; void sleep_mode(void ) { LP_config = (sleep_block_t* ) calloc (1 ,sizeof (sleep_block_t) ) ; // Просыпаться будем по таймеру LP_config- > modules = (LPTMR_WAKE) ; // Задаём таймаут для таймера LP_config- > lptmr_timeout = TIMEOUT; // По истечении таймаута будет вызываться функция loop LP_config- > callback = loop; LP.Hibernate (LP_config) ; } // Функция включения периферии void periferial_start(void ) { // Включаем линию передачи данных pinMode(RF_PIN, OUTPUT) ; // Включаем питания и земли датчиков температуры и влажности pinMode(GND1_PIN, OUTPUT) ; pinMode(GND2_PIN, OUTPUT) ; pinMode(VCC1_PIN, OUTPUT) ; pinMode(VCC2_PIN, OUTPUT) ; digitalWrite(GND1_PIN, LOW) ; digitalWrite(GND2_PIN, LOW) ; digitalWrite(VCC1_PIN, HIGH) ; digitalWrite(VCC2_PIN, HIGH) ; // Включаем светодиод для индикации передачи pinMode(LED_BUILTIN, OUTPUT) ; digitalWrite(LED_BUILTIN, HIGH) ; // Выбираем в качестве опорного напряжения внутренний // источник (=1.2 В) analogReference(INTERNAL) ; } // Функция выключения периферии void periferial_stop(void ) { // Выключаем линию передачи данных pinMode(RF_PIN, INPUT) ; // Выключаем датчик температуры и влажности pinMode(GND1_PIN, INPUT) ; pinMode(GND2_PIN, INPUT) ; pinMode(VCC1_PIN, INPUT) ; pinMode(VCC2_PIN, INPUT) ; pinMode(18 , INPUT_PULLUP) ; pinMode(19 , INPUT_PULLUP) ; // Выключаем светодиод digitalWrite(LED_BUILTIN, LOW) ; } void setup(void ) { // Ничего не инициализируем, сразу засыпаем sleep_mode() ; } // Эта функция выполняется раз в TIMEOUT секунд void loop(void ) { unsigned long msg; byte temp, humidity, voltage; // Включаем периферию periferial_start() ; // Подождём, пока включится датчик температуры и влажности delay(30 ) ; // Получаем входные данные с сенсоров temp = (byte) (sht1x.readTemperatureC () + 40 .) * 2 ; humidity = (byte) sht1x.readHumidity () ; voltage = analogRead(A0) / 4 ; // Составляем из данных посылку msg = 0 ; msg | = voltage; msg <<= 8 ; msg | = humidity; msg <<= 8 ; msg | = temp; // Отправляем несколько раз посылку for (int i = 0 ; i < ATTEMPTS; i++ ) rf.send (msg) ; // Выключаем периферию periferial_stop() ; // После выхода из функции плата снова уснёт }

Код платы, работающей в помещении

receiver.ino #include #include #include #include byte mac = { 0x90 , 0xA7 , 0xDA , 0x0F , 0xBC , 0x75 } ; char server = "narodmon.ru" ; EthernetClient client; const int rfpin = 7 ; AmperkaLine rf(rfpin) ; void setup(void ) { pinMode(rfpin, INPUT) ; pinMode(6 , OUTPUT) ; Serial.begin (9600 ) ; Serial.println ("Started." ) ; } void loop(void ) { static unsigned long pushtimeout = 0 ; static float temp, humidity, voltage; unsigned long msg; int res; if ((res = rf.receive (& msg) ) == 0 ) { temp = ((float ) (msg& 0xFF ) ) / 2 . - 40 .; msg >>= 8 ; humidity = (float ) (msg& 0xFF ) ; msg >>= 8 ; voltage = (float ) (msg& 0xFF ) / 256 . * 1.2 * 10 * 1.1 ; digitalWrite(6 , HIGH) ; Serial.print ("Temp: " ) ; Serial.print (temp) ; Serial.print (", humidity: " ) ; Serial.print (humidity) ; Serial.print (", voltage: " ) ; Serial.println (voltage) ; digitalWrite(6 , LOW) ; } else Serial.println ("E" ) ; if (millis() - pushtimeout > 60000 * 5 ) { pushtimeout = millis() ; Serial.println ("Starting Ethernet..." ) ; if (Ethernet.begin (mac) == 0 ) { Serial.println ("Failed to configure Ethernet using DHCP" ) ; while (1 ) { } } delay(1000 ) ; Serial.println ("connecting..." ) ; if (client.connect (server, 8283 ) ) { Serial.println ("connected" ) ; client.println ("#90-A7-DA-0F-BC-75#Sensor#55.751775#37.616856#0.0" ) ; client.print ("#90A7DA0FBC7501#" ) ; client.print (temp, DEC) ; client.println ("#In" ) ; client.print ("#90A7DA0FBC7502#" ) ; client.print (humidity, DEC) ; client.println ("#Humidity" ) ; client.print ("#90A7DA0FBC7503#" ) ; client.print (voltage, DEC) ; client.println ("#Voltage" ) ; client.println ("##" ) ; } else Serial.println ("connection failed" ) ; { unsigned long tm = millis() ; while (millis() - tm < 5000 ) { if (client.available () ) { char c = client.read () ; Serial.print (c) ; } } } client.stop () ; } }

Регистрация метеостанции в «Народном мониторинге»

Чтобы данные, передаваемые нашим устройством, корректно отображались на народном мониторинге, необходимо выполнить следующее:


Демонстрация работы устройства

Что ещё можно сделать?

    Teensy прямо на борту имеет часы реального времени (RTC). Для их работоспособности не хватает только кварца. Можно купить кварц на 32,768 КГц в любом магазине радиоэлементов и припаять его. Тогда можно пробуждать Teensy по будильнику RTC. Достоинство в том, что можно будить устройство чаще в те часы, когда нужны более точные показания. Например, в рабочее время будить устройство каждые 5 минут, а в остальное - каждые полчаса.

Захотелось иметь свою метеостанцию, которая передает показания с датчиков на карту народного мониторинга (ищется в гугле за 5 секунд). Оказалось это не так сложно, как кажется. Рассмотрим, что было сделано.

Для данного действия я взял себе Arduino Uno и Ethernet Shield w5100 для нее. Все это заказывалось из Китая на Aliexpress.

Так же там заказал себе датчики: DHT22, DHT11, DS18B20, BMP280 (в планах еще датчики газа, дыма…)

Покурив форумы, гугл, яндекс, я нашел неплохой вариант скетча — https://student-proger.ru/2014/11/meteostanciya-2-1/

Там же в комментариях человек выкладывал дописанный скетч с датчиками освещенности, газа. Я взял их за основу.

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

Ниже приведу пример итогового скетча, что получился у меня.

В данный момент у меня подключены датчики:
DHT22 — 1шт.
DHT11 — 1шт.
BMP280 — 1шт.
DS18B20 — 2шт.

ВНИМАНИЕ! Перед тем как заливать скетч, не забудьте изменить MAC-адрес устройства, чтобы не пересекаться с другими (например взять Mac-адрес вашего мобильного телефона и изменить в нем последние буквы/цифры, что не «будоражило» вашу локальную сеть!

Примерная схема подключения (картинка взята на просторах интернета от данного скетча):

По техническим причинам у меня не получается выложить скетч прямо сюда. Поместил его в архив. Ссылка на него строчкой выше.

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

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

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

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

Сторожевой таймер стоит на случай ч.п. Как показывает практика, 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 : Все довольно интересно, но на мой взгляд заслуживает дальнейшего развития. Чем и займемся через какое то время.