Мобильная метеостанция на Arduino / Хабр

Мобильная метеостанция на Arduino / Хабр Анемометр

4калибровка ультразвукового дальномера

После небольшого опыта использования данного прибора, выявилось, что измеренное расстояние отличается от действительного, причём чем больше расстояние от датчика до цели, тем больше отклонение. В связи с этим возникла необходимость в корректировке показаний дальномера.

Для этого я измерил на всём рабочем диапазоне датчика (от 0 до 400 см) расстояния и составил таблицу из двух столбцов: реальное расстояние и показания датчика. По этим данным построил график:

График отклонения показаний ультразвукового дальномера
График отклонения показаний ультразвукового дальномера

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

Зависимость получилась линейная. Прекрасно! Из курса геометрии мы помним, что уравнение прямой, проходящей через две точки: (x − x1)/(x2 − x1) = (y − y1)/(y2 − y1)

Подставив вместо x1, 2 и y1, 2 значения из таблицы и упростив выражение, я получил уравнение для графика измерений ультразвукового дальномера: y = 0,97x 0,27

В уравнении прямой коэффициент 0,97 возле «икс» является тангенсом угла наклона прямой к оси абсцисс. Назовём его tg(α). Можно посчитать, что угол наклона прямой равняется 44,13 градусам (арктангенс от 0,97), а в идеальном случае он должен быть равен 45 градусам (и уравнение прямой идеального дальномера очень простое: y = x, то есть каждому измеренному числу соответствует точно такое же реальное расстояние). Постоянная 0,27 – это смещение графика относительно нуля по оси ординат.

Посмотрим на следующий рисунок. Видны два прямоугольных треугольника, приподнятые над осью OX. Один из катетов, назовём его h, треугольника с углом α равен измеренному дальномером расстоянию Rизм за вычетом константы b, равной 0,27.

Катет другого треугольника равен реальному расстоянию Rреал. У обоих треугольников катеты h равны. Зная длину катета h и тангенс угла α (равный 0,97), мы узнаем реальное расстояние Rреал, поделив, длину катета h на tg(α),

Последовательность коррекции показаний ультразвукового дальномера
Последовательность коррекции показаний ультразвукового дальномера

Таким образом, для того чтобы скорректировать ошибку при измерениях дальномера, нужно следовать формуле: Rреал = (Rизм − b) / tg(α) = (Rизм − 0,27) / 0,97 Обратите внимание, что это выражение можно было получить из предыдущего уравнения, выразив x через y.

В вашем случае коэффициент и константа, естественно, будут отличаться. Эту коррекцию следует внести в скетч для Arduino. После этого ультразвуковой дальномер на датчике HC-SR04 будет показывать расстояние более точно.

Напоследок приведу видеодемонстрацию работы готового ультразвукового дальномера (до калибровки).

Метеостанция

Для сборки метеостанции использовали фоторезистор и датчик

https://www.youtube.com/watch?v=Hu5pwuqPEzo

(температура и влажность). Схема подключения:

Код на C
/* Meteo.ino
 *
 * Программа, регистрирующая влажность, температуру и яркость
 * Отправляет результаты на COM port
 * Формат вывода: H=1.0;T=1.0;LL=1;
 */

//Пин фоторезистора (аналоговый)
int lightPin = 0;

// Пин DHT-11 (цифровой)
int DHpin = 8; 

// Массив, хранящий данные DHT-11
byte dat[5]; 

// Первоначальная настройка
void setup()
{
	Serial.begin(9600); 
	pinMode(DHpin,OUTPUT); 
}

 /*
 * Выполняется после setup()
 * Основной бесконечный цикл
 */
void loop()
{
	delay(1000); // Замер примерно 1 раз в секунду
	int lightLevel = analogRead(lightPin); //Получаем уровень освещённости 

	temp_hum(); // Получаем температуру и влажность в переменную dat
	// И выводим результат
	Serial.print("H="); 
	Serial.print(dat[0], DEC);   
	Serial.print('.'); 
	Serial.print(dat[1],DEC);	
	Serial.print(";T="); 
	Serial.print(dat[2], DEC);	
	Serial.print('.'); 
	Serial.print(dat[3],DEC);	 
	Serial.print(";LL="); 
	Serial.print(lightLevel);
	Serial.println(";");
}

// Получить данные от DHT-11 в dat
void temp_hum() 
{ 
	digitalWrite(DHpin,LOW);
	delay(30);  
	digitalWrite(DHpin,HIGH); 
	delayMicroseconds(40);
	pinMode(DHpin,INPUT);
	while(digitalRead(DHpin) == HIGH);
	delayMicroseconds(80);
	if(digitalRead(DHpin) == LOW); 
	delayMicroseconds(80);
	for(int i=0;i<4;i  )
	{
	  dat[i] = read_data();
	}
	pinMode(DHpin,OUTPUT);
	digitalWrite(DHpin,HIGH);
} 

// Получить часть данных от DHT-11
byte read_data() 
{
	byte data; 
	for(int i=0; i<8; i  ) 
	{ 
		if(digitalRead(DHpin) == LOW) 
		{ 
			while(digitalRead(DHpin) == LOW); 
			delayMicroseconds(30);
			if(digitalRead(DHpin) == HIGH) 
			{
				data |= (1<<(7-i));
			}
			while(digitalRead(DHpin) == HIGH); 
		}
	} 
	return data; 
} 


После загрузки кода на Arduino она начинает посылать данные на COM порт в следующем формате:

H=34.0;T=24.0;LL=605;

Где:

Надо как-то его хранить в Caché. Для этого напишем хранимый класс Arduino.Info:


И добавим туда метод, который будет принимать данные в формате Arduino, и преобразовывать их объекты класса Arduino.Info:

/// Получаем поток данных в формате H=34.0;T=24.0;LL=605;n 
/// И преобразуем их в объекты класса Arduino.Info
ClassMethod ReceiveSerial(port = {..#SerialPort})
{
	try {
		open port:(:::" 0801n0":/BAUD=9600)
		set old = $IO
		use port
		for {
			read x //Читаем одну строку
			set Humidity = $Piece($Piece(x,";",1),"=",2)
			set Temperature =  $Piece($Piece(x,";",2),"=",2)
			set Brightness =  $Piece($Piece(x,";",3),"=",2)
			if (x '= "") {
				do ..AddNew(Temperature,Humidity,Brightness) // Добавляем данные
			}
		}
	} catch anyError {
		close port
	}
}

После этого нам нужно запустить Arduino и выполнить в терминале метод ReceiveSerial:

write ##class(Arduino.Info).ReceiveSerial()

Этот метод в бесконечном цикле будет собирать и сохранять данные, приходящие от Arduino.

Метеостанция (arduino pro mini, bme280, lcd1602)

Введение

Что можно вывести на двухстрочный экран, кроме «Hello world!»? Почему бы не отображать температуру влажность и давление?

Датчики предлагаемые как учебное пособие к arduino (DHT11, DHT22) показывают температуру и влажность воздуха. В учебных целях (для университета) понадобилось наблюдать так же и за давлением. Естественно на кафедре есть барометр, но почему бы не собрать свой? К тому же можно в дальнейшем накапливать показания в автоматическом режиме, и это неплохой опыт в изучении arduino.

Так или иначе из Китая были заказаны комплектующие и собрано данное устройство.

Необходимые комплектующие

Arduino Pro Mini
I2C для LCD (можно было заказать сразу в сборе, но так вышло чуть чуть дешевле)
LCD 1602
BME280

Для отправки скетча в arduino был использован USB-UART. Так же можно было использовать Raspberry Pi или компьютер с COM портом.

Схема подключения для прошивки и код программы

Из Китая USB-UART пришёл с набором проводков:

image

Их вполне хватило. Перемычку оставил на 3.3 вольта, несмотря на то что моя версия arduino питается от 5 вольт.

UART — Arduino
5v — VCC
TXD — RXD
RXD — TXD
GND — GND
CTS — DTR (опционально, у меня не работал, возможно потому что напряжение сигналов осталось 3.3В)

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

Необходимые сторонние библиотеки:

SparkFunBME280
LiquidCrystal I2C

Непосредственно код, с комментариями из примеров (на случай, если кому то понадобится что то менять).

Код
#include <stdint.h>
#include "SparkFunBME280.h"
#include "Wire.h"
#include "SPI.h"
#include <LiquidCrystal_I2C.h>

//Global sensor object
BME280 mySensor;
LiquidCrystal_I2C lcd(0x3F,16,2); //Адрес дисплея, в моём случае 0x3F

void setup()
{
  lcd.init();
  lcd.backlight();

	//***Driver settings********************************//
	//commInterface can be I2C_MODE or SPI_MODE
	//specify chipSelectPin using arduino pin names
	//specify I2C address.  Can be 0x77(default) or 0x76
	//For I2C, enable the following and disable the SPI section
  
  mySensor.settings.commInterface = I2C_MODE;
  mySensor.settings.I2CAddress = 0x76; //Адрес датчика, в моём случае не стандартный
	
	//For SPI enable the following and dissable the I2C section
	//mySensor.settings.commInterface = SPI_MODE;
	//mySensor.settings.chipSelectPin = 10;

	//***Operation settings*****************************//
	
	//renMode can be:
	//  0, Sleep mode
	//  1 or 2, Forced mode
	//  3, Normal mode
	mySensor.settings.runMode = 3; //В примере предлагают использовать Forced mode, но при обновлении раз в секунду достаточно Normal mode
	
	//tStandby can be:
	//  0, 0.5ms
	//  1, 62.5ms
	//  2, 125ms
	//  3, 250ms
	//  4, 500ms
	//  5, 1000ms
	//  6, 10ms
	//  7, 20ms
	mySensor.settings.tStandby = 5; //Очевидно чаще не нужно
	
	//filter can be off or number of FIR coefficients to use:
	//  0, filter off
	//  1, coefficients = 2
	//  2, coefficients = 4
	//  3, coefficients = 8
	//  4, coefficients = 16
	mySensor.settings.filter = 0;
	
	//tempOverSample can be:
	//  0, skipped
	//  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
	mySensor.settings.tempOverSample = 1;

	//pressOverSample can be:
	//  0, skipped
	//  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
    mySensor.settings.pressOverSample = 1;
	
	//humidOverSample can be:
	//  0, skipped
	//  1 through 5, oversampling *1, *2, *4, *8, *16 respectively
	mySensor.settings.humidOverSample = 1;
  	
	//Calling .begin() causes the settings to be loaded
	mySensor.begin();
}

void loop()
{
  //Буквы можно вывести один раз, а далее менять показания, но показания при изменении количества значащих цифр могут сдвигать строку.
  lcd.setCursor(0,0);
  lcd.print("H=");
  lcd.print((uint8_t)mySensor.readFloatHumidity());
  lcd.print("%");
  lcd.print(" T=");
  lcd.print(mySensor.readTempC());
  lcd.setCursor(13,0);
  lcd.print(" P:");

  lcd.setCursor(0,1);
  int mmH=mySensor.readFloatPressure()/133;
  lcd.print(mmH);
  lcd.print("mmH ");
  lcd.print(mySensor.readFloatPressure());
  lcd.setCursor(14,1);
  lcd.print("Pa");

  delay(1000);

}

Адрес датчика можно угадать, их всего два.

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

В данном случае:

И адрес будет 0x3F т.к. A0 — A2 разомкнуты:

image

Светодиод который обведён в овал лучше можно выпаять.

Схема подключения

Резистор выбирался как половина от сопротивления датчика (между VVC и GND), чтобы падения напряжения на нём было 1.7 вольта. Так же схему можно запитать от входа RAW, другим напряжением (например от кроны).

image

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

image

В моём случае всё питается от старого компьютерного блока питания. Можно питать от USB. Все комплектующие были приклеены оказавшемся под рукой клеем «Момент».

Итог

На рабочем месте появился 1602 прикрученный к столу, который показывает давление, влажность, температуру. Arduino можно перепрошить не снимая (возможно станет бегущей строкой).

Метеостанция на arduino от а до я. часть 3

Продолжение. Предыдущая часть.

Оглавление:

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

Исходный код для сервера составляет около 1300 строк, включая отступы, но это не должно вас пугать. Исходный текст снабжен подробными комментариями, в этом плане я не ошибусь, если скажу, что мои исходники описаны лучше чем любые другие которые вы только сможете найти. В комментариях прямо в исходном тексте вы найдете всю распиновку для подключения модулей и все необходимые ссылки на внешнюю документацию. Секрет прост — я писал комментарии для себя постоянно, «по ходу пьесы», поэтому никаких трудностей с документированием не испытал.

Про анемометры:  Умный дом HomeKit с голосовым управлением через Алису - Sprut.AI

Как я уже писал вы можете начать и не имея всех модулей под рукой. Например, можно начать не имея радиомодуля или ESP8266. Датчик барометрического давления BMP180 также может отсутствовать. Добавите потом. Правда в этом случае вам (возможно) придется самостоятельно закомментировать в скетче те участки кода, которые отвечают за взаимодействие с отсутствующими блоками, но скорее всего этого не потребуется. Главное, чтобы хоть что-то собралось и заработало, тогда веселее продолжать.

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

И всё таки я начну, пожалуй, с ESP8266, как самого проблемного в программировании и эксплуатации модуля. Причина кроется в разнообразии исполнения самих модулей и их прошивок.

Как я уже писал стандартные AT-прошивки для него имеют ряд недостатков:

Код для ESP8266 я не оформлял в отдельную библиотеку, а просто написал необходимые функции, поэтому скетч вышел таким длинным. Причём я реализовал только нужный мне функционал. Всё программирование для ESP с помощью AT команд сводится в итоге к парсингу строк и настройке задержек между командами.

Исходный код для сервера (центрального модуля) server.ino вы можете найти и скачать здесь.

Рядом я положил прошивку для ESP8266 в файле firmware/AT23-SDK101-nocloud.bin и в том же каталоге находится документация для любознательных. Прошив указанную прошивку вы можете быть уверены, что мой скетч у вас заработает с WiFi так как было задумано. С другими AT прошивками я не экспериментировал. Дело в том, что мне удалось таки отыскать «продвинутую» не AT прошивку, и даже немного поучаствовать в её создании, которая как нельзя лучше подходит для наших целей (вот она esp-link). Однако, как это часто случается, всё произошло уже после завершения работы над текущей версии метеостанции, поэтому решено было оставить всё так как есть.

Итак, в самом начале вам придётся прошить указанную AT прошивку. Сложного тут ничего нет, но и простого тоже. Как это сделать описано много где в сети — ESP8266 — подключение и обновление прошивки.

Поскольку у моего USB-TTL конвертора не хватило мощности по току и USB порт постоянно отваливался (вот это поворот!), то электрически я подключил модуль для его прошивки способом «Arduino в качестве простого USB-to-Serial TTL конвертора».

Так как я работаю в Linux, то и прошивал с помощью esptool.py. Для удобства прошивки я «наколхозил» небольшую вспомогательную плату с переключателями (здесь не описана).

После прошивки нужно установить скорость порта 57600 (так как для SoftSerial скорость порта в 115200 является большой и не гарантирует стабильную работу) командой

AT UART_DEF=57600,8,1,0,0

Далее нужно слегка изменить стандартные библиотеки Arduino IDE, а именно в файле arduino/hardware/arduino/avr/libraries/SoftwareSerial/SoftwareSerial.h изменить соответствующую строку на

#define _SS_MAX_RX_BUFF 128 // RX buffer size

в файле arduino/hardware/arduino/avr/cores/arduino/HardwareSerial.h изменить соответствующие строки на

#define SERIAL_TX_BUFFER_SIZE 128
#define SERIAL_RX_BUFFER_SIZE 128

и в файле arduino/hardware/arduino/avr/cores/arduino/USBAPI.h изменить соответствующую строку на

#define SERIAL_BUFFER_SIZE 128

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

Так или иначе предварительные манипуляции закончены.

Теперь переходим непосредственно к коду центрального блока (серверу) server.ino

В первых же строках вы должны изменить настройки доступа к вашей точке WiFi

const String SSID = "...";
const String PASSWORD = "...";

работу с веб сервером подробно рассмотрим позже.

Далее идут (закомментированные) отладочные определения:

//#define DEBUG
//#define DEBUG_RF
//#define DEBUG_ESP
//#define DEBUG_LOG_SD

Если что-то пойдёт не так вы всегда можете их раскомментировать, перекомпилировать и перезалить скетч и получить больше отладочной информации в консоли или записать её в файл на SD карту. Причем вы можете раскомментировать только то, что вам нужно. Например, барахлит модуль nRF24L01 ? Тогда раскоментируем только DEBUG_RF, и т.д.

Далее идут обширные комментарии с распиновкой, инициализацией и подробным описанием всей периферии.

Здесь вы можете изменить номер радиоканала для nRF24L01

#define RF_CHANNEL  73

Далее идёт void setup(), что там делается понятно из подробных комментариев. Ну и затем void loop(), код работы с веб-сервером пока не рассматриваем.

После заливки скетча, ваш центральный блок оживёт и что-то вам покажет, но не сразу, а спустя 10 минут — значение DELAY_LOCAL_SENSOR. Можете его изменить конечно же.
На дисплее должны отобразиться: комнатная температура и влажность (данные поступят от датчика DHT11) и барометрическое давление (от BMP180).

За отображение на дисплее LCD 16×4 отвечают функции:

void lcdClearRow(int row)

// Печатает на экране показания удалённых, уличных датчиков
void lcdPrintOutdoor(int temperature, int humidity, float voltage)

// Печатает на экране показания внутренних, домашних датчиков
void lcdPrintHome(int temperature, int humidity, int pressure)

void lcdPrintInfo(char info[LCD_MAX_COLS])
void lcdPrintStatus()
void lcdPrintLastSensorTime()

Дизайн дисплея LCD1604 следующий.

Дизайн дисплея LCD1604

В первой (верхней) строке печатается стилизованная иконка (идущий человечек) призванная обозначить погоду на улице (вышел на улицу, идёт по улице). Иконку придумывал сам, поэтому, если у вас есть лучшая идея (умещающаяся в 5х8 пикселов), можете указать в комментариях (в виде byte-массива). Поупражняться в пиксель-арте можно здесь Custom Character Generator for HD44780 LCD Modules. В этой же строке печатается напряжение питания заоконного модуля.

Во второй строке печатается «погода в доме» и атмосферное давление. Иконка дома стандартная, всем понятная.

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

И в последней четвёртой строке экрана с помощью функции lcdPrintStatus() печатается статусная информация, где

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

Возвращаясь к вопросу выбора железа, поясню про преимущества выбора текстового LCD1604 дисплея перед графическим. Дело в том, что модули LCD1604 купленные у различных продавцов в большинстве случаев будут одинаковыми и предсказуемыми в подключении и просты в программировании. Чего нельзя сказать о графических дисплеях, хотя нарисовать и показать на них можно гораздо больше. Разборчивость изображения с расстояния в несколько метров опять же лучше у текстового дисплея, таки да, на графическом дисплее можно сделать шрифт побольше, но тогда много ли можно на нём уместить?

Далее. Как только вы зальете скетч и убедитесь, что все работает как надо, то можете переподключить «материнскую» плату Arduino Mega к внешнему источнику питания. Или оставить как есть, подключённым к USB компьютера, чтобы посматривать на всю эту красоту в отладочной консоли.

Если у вас не все блоки в сборе, то вы можете закомментировать ненужный код. Хотя, повторюсь, должно работать и так, просто будете получать сообщения об ошибках в консоли. Как это обойти?

Например, вы ещё не приобрели датчик атмосферного давления BMP180. В скетче server.ino ищем строки, отвечающие за подключение соответствующих библиотек, в нашем случае это

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>

Комментируем этот блок.

Далее в Arduino IDE запускаем компиляцию кода (не прошивку) и смотрим на какие строки ругается компилятор. Комментируем эти строки. Операцию повторяем до тех пор, пока код не будет собираться нормально, без ошибок. Хорошей практикой будет перед редактированием создать копию скетча, чтобы, когда из солнечного Китая приедет нужный датчик, не повторять все операции обратно.

Ссылки на используемые библиотеки даны в исходном коде. Если такой ссылки нет, то использовалась стандартная библиотека Arduino IDE.

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

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

Мобильная метеостанция на arduino

Наверное, каждый кто начинает свое знакомство с Arduino, поморгав светодиодом и подключив кнопку, переходит к созданию своей метеостанции. Не исключением стал и я.

Ознакомившись с различными проектами в интернете, были составлены определенные требования. Устройство должно отображать время, температуру, давление и график его изменения. Также очень хотелось рассчитывать фазы луны и время восхода-захода солнца. А самым важным требованием было – мобильность. Создаваемая портативная метеостанция предполагалась использоваться в помощь для рыбалки, поэтому должна была иметь компактный размер и обладать определенной автономностью.

Создание корпуса для любой самоделки является довольно сложной задачей. В закромах был найден мобильный телефон Motorola T192.

image

Оказалось, что в него отлично становится экран от Nokia 3310 (конечно использовать саму Nokia 3310, возможно, было бы удобнее).
Управлять всем было поручено Arduino Pro Mini.

image

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

image

Для измерения давления применен датчик bmp180. В качестве часов был взят модуль ds1302, но для экономии места с него была взята только микросхема. Кварц был выпаян из материнской платы, а батарейку вынул с ноутбука (конечно же, можно и обычную cr2032 в термокембрике).

Про анемометры:  Устройство и принцип работы датчика Холла, схема подключения и применение. Как проверить датчик Холла в автомобиле

image

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

image

Для зарядки литиевого аккумулятора был использован модуль заряда Li-ion на TP4056.

image

Итоговый вид устройства (провода, торчащие справа, для прошивки, поэтому вскоре будут убраны.)

image

При написании программы использовались обычные средства. Единственное хотелось бы обратить внимание на библиотеку TimeLord.h (https://github.com/probonopd/TimeLord). С помощью ее функций, указав дату, координаты и часовой пояс можно определить время восхода-захода солна, фазу луны. Описание можно скачать по ссылке: TimeLord.pdf.

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

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

image

На экране отображается антенна (индикатор уровня сигнала). Это оставлен задел для тестирования радио модуля на 433 МГц, для получения правдивой температуры и влажности от внешнего модуля.

Исходник
#include <BMP085.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <DS1302.h>
#include <TimeLord.h>
#include <Wire.h>

//84x48
Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);
// Create a DS1302 object.
DS1302 rtc(12, 11, 10);// Chip Enable, Input/Output, Serial Clock
Time t = rtc.time();
String dayAsString(const Time::Day day) {
  switch (day) {
    case Time::kSunday: return "Sunday";
    case Time::kMonday: return "Monday";
    case Time::kTuesday: return "Tuesday";
    case Time::kWednesday: return "Wednesday";
    case Time::kThursday: return "Thursday";
    case Time::kFriday: return "Friday";
    case Time::kSaturday: return "Saturday";
  }
  return "(unknown day)";
}
//-------------
TimeLord tardis;
float const LATITUDE = 52.70;
float const LONGITUDE = 25.40;
//byte today[] = {0,0,12,22,03,16};
byte today[6];
//-------------
//long previousMillis = 0, previousMillis2 = 0;      // храним время последней команды
//long interval = 1800000;           // интервал между командами
//long interval = 3600000;           // интервал между командами
//long interval2 = 1000;           // интервал между командами
byte prevSecond = 99, prevHour = 99;
//-------------
BMP085 dps = BMP085();
long Temperature = 0, Pressure = 0;
float t_f = 0;

//-------------buttons--------------------
int buttonPushCounter = 0;
int buttonState = 0;
int lastButtonState = 0;

//-------------moon----------------------
//0.0 New Moon 0.99 - Almost new
const unsigned char PROGMEM moon0[] =
{ B00000111, B11100000,
  B00001000, B00010000,
  B00010000, B00001000,
  B00100000, B00000100,
  B01000000, B00000010,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B01000000, B00000010,
  B00100000, B00000100,
  B00010000, B00001000,
  B00001000, B00010000,
  B00000111, B11100000
};

//Waxing Crescent
const unsigned char PROGMEM moon1[] =
{ B00000111, B11100000,
  B00001000, B01110000,
  B00010000, B00111000,
  B00100000, B00011100,
  B01000000, B00001110,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B01000000, B00001110,
  B00100000, B00011100,
  B00010000, B00111000,
  B00001000, B01110000,
  B00000111, B11100000
};

//0.25 First Quarter
const unsigned char PROGMEM moon2[] =
{ B00000111, B11100000,
  B00001000, B11110000,
  B00010000, B11111000,
  B00100000, B11111100,
  B01000000, B11111110,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B01000000, B11111110,
  B00100000, B11111100,
  B00010000, B11111000,
  B00001000, B11110000,
  B00000111, B11100000
};

//Waxing Gibbous
const unsigned char PROGMEM moon3[] =
{ B00000111, B11100000,
  B00001011, B11110000,
  B00010111, B11111000,
  B00101111, B11111100,
  B01001111, B11111110,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B01001111, B11111110,
  B00101111, B11111100,
  B00010111, B11111000,
  B00001011, B11110000,
  B00000111, B11100000
};

//0.5 Full Moon
const unsigned char PROGMEM moon4[] =
{ B00000111, B11100000,
  B00001111, B11110000,
  B00011111, B11111000,
  B00111111, B11111100,
  B01111111, B11111110,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B01111111, B11111110,
  B00111111, B11111100,
  B00011111, B11111000,
  B00001111, B11110000,
  B00000111, B11100000
};

//Waning Gibbous
const unsigned char PROGMEM moon5[] =
{ B00000111, B11100000,
  B00001111, B11010000,
  B00011111, B11101000,
  B00111111, B11110100,
  B01111111, B11110010,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B01111111, B11110010,
  B00111111, B11110100,
  B00011111, B11101000,
  B00001111, B11010000,
  B00000111, B11100000
};

//0.75 Third Quarter  (Last Quarter)
const unsigned char PROGMEM moon6[] =
{ B00000111, B11100000,
  B00001111, B00010000,
  B00011111, B00001000,
  B00111111, B00000100,
  B01111111, B00000010,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B01111111, B00000010,
  B00111111, B00000100,
  B00011111, B00001000,
  B00001111, B00010000,
  B00000111, B11100000
};


//Waning Crescent
const unsigned char PROGMEM moon7[] =
{ B00000111, B11100000,
  B00001110, B00010000,
  B00011100, B00001000,
  B00111000, B00000100,
  B01110000, B00000010,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B01110000, B00000010,
  B00111000, B00000100,
  B00011100, B00001000,
  B00001110, B00010000,
  B00000111, B11100000
};
//=====================================================================================
void drawMoon(int moon_x, int moon_y, int phase) {
  display.fillRect(moon_x, moon_y, 16, 15, WHITE);
  display.drawBitmap(moon_x, moon_y, moon4,  16, 15, WHITE);
  switch (phase) {
    case 0:
      display.drawBitmap(moon_x, moon_y, moon0,  16, 15, BLACK);
      break;
    case 1:
      display.drawBitmap(moon_x, moon_y, moon1,  16, 15, BLACK);
      break;
    case 2:
      display.drawBitmap(moon_x, moon_y, moon2,  16, 15, BLACK);
      break;
    case 3:
      display.drawBitmap(moon_x, moon_y, moon3,  16, 15, BLACK);
      break;
    case 4:
      display.drawBitmap(moon_x, moon_y, moon4,  16, 15, BLACK);
      break;
    case 5:
      display.drawBitmap(moon_x, moon_y, moon5,  16, 15, BLACK);
      break;
    case 6:
      display.drawBitmap(moon_x, moon_y, moon6,  16, 15, BLACK);
      break;
    case 7:
      display.drawBitmap(moon_x, moon_y, moon7,  16, 15, BLACK);
      break;
    default:
      display.drawBitmap(moon_x, moon_y, moon4,  16, 15, WHITE);
  }
}
//===========================================================================================
void drawMoonDate(int moon_x, int moon_y, uint8_t * datetoday) {
  float phase;
  phase = tardis.MoonPhase(datetoday);
  if (phase >= 0.0   && phase <= 0.0625)  {
    drawMoon(moon_x, moon_y, 0);
  }; //0.000  New moon
  if (phase > 0.0625 && phase <= 0.1875)  {
    drawMoon(moon_x, moon_y, 1);
  }; //0,125
  if (phase > 0.1875 && phase <= 0.3125 ) {
    drawMoon(moon_x, moon_y, 2);
  }; //0.250  First Quarter
  if (phase > 0.3125 && phase <= 0.4375)  {
    drawMoon(moon_x, moon_y, 3);
  }; //0,375
  if (phase > 0.4375 && phase <= 0.5625)  {
    drawMoon(moon_x, moon_y, 4);
  }; //0.500  Full
  if (phase > 0.5625 && phase <= 0.6875)  {
    drawMoon(moon_x, moon_y, 5);
  }; //0,625
  if (phase > 0.6875 && phase <= 0.8125)  {
    drawMoon(moon_x, moon_y, 6);
  }; //0.750  Last Quarter
  if (phase > 0.8125 && phase <= 0.9375)  {
    drawMoon(moon_x, moon_y, 7);
  }; //0.875
  if (phase > 0.9375 && phase <= 1)       {
    drawMoon(moon_x, moon_y, 0);
  }; //0.990  Almost new
}
//=====================================================================================
void drawSignal(float streng) {
  display.fillRect(0, 0, 12, 6, WHITE);
  display.drawTriangle(0, 0, 8, 0, 4, 4, BLACK);
  display.drawLine(4, 0, 4, 6, BLACK);

  display.drawLine(6, 5, 6, 6, BLACK);
  display.drawLine(8, 4, 8, 6, BLACK);
  display.drawLine(10, 2, 10, 6, BLACK);
  display.drawLine(12, 0, 12, 6, BLACK);

}
//=====================================================================================
void drawBatteryState(float v_bat) {
  display.fillRect(68, 0, 16, 7, WHITE);
  display.drawRect(83, 2, 1, 3, BLACK);
  display.drawRoundRect(68, 0, 15, 7, 2, BLACK);
  // 3, 44  4, 2 0, 76  0, 152
  //4,200 full  4
  //4,048       3
  //3,896       2
  //3,744       1
  //3,592       0
  //3,440 zero  -

  if (v_bat > 4500)   {
    display.fillRect(70, 2, 10, 3, BLACK);
  }
  if (v_bat > 4048)   {
    display.drawRect(79, 2, 2, 3, BLACK);
  }
  if (v_bat > 3896)   {
    display.drawRect(76, 2, 2, 3, BLACK);
  }
  if (v_bat > 3744)    {
    display.drawRect(73, 2, 2, 3, BLACK);
  }
  if (v_bat > 3592)    {
    display.drawRect(70, 2, 2, 3, BLACK);
  }
}
//=====================================================================================
void drawTime(byte x, byte y) {
  display.fillRect(0   x, 0   y, 48, 7, WHITE);
  //display.fillRect(0 x, 0 y, 30, 7, WHITE);
  Time t = rtc.time();
  display.setTextColor(BLACK);
  display.setTextSize(1);
  display.setCursor(x, y);
  display.print(t.hr);
  display.print(":");
  display.print(t.min);
  display.print(":");
  display.print(t.sec);
}
//===========================================================================================
void updateMoonSunDate()
{
  Time t = rtc.time();
  today[0] = 0;
  today[1] = 0;
  today[2] = 12;
  today[3] = t.date;
  today[4] = t.mon;
  today[5] = t.yr - 2000;
}
//=====================================================================================
void drawSunRiseSet(byte x, byte y) {
  updateMoonSunDate();
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y   8, WHITE);

  if (tardis.SunRise(today)) // if the sun will rise today (it might not, in the [ant]arctic)
  {
    display.setCursor(x, y);
    display.print((int) today[tl_hour]);
    display.print(":");
    display.println((int) today[tl_minute]);
  }
  if (tardis.SunSet(today)) // if the sun will set today (it might not, in the [ant]arctic)
  {
    display.setCursor(x, y   8);
    display.print((int) today[tl_hour]);
    display.print(":");
    display.println((int) today[tl_minute]);
  }

}
//=====================================================================================
void drawPressure(byte x, byte y) {
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y   8, WHITE);
  display.setCursor(x, y);
  t_f = Temperature;
  display.println( t_f / 10, 1);
  display.setCursor(x, y   8);
  //display.println(ceil(Pressure / 133.3), 0);
  display.println(Pressure / 133.3, 1);
}
//=====================================================================================
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(75); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  //scale_constant = internal1.1Ref * 1023 * 1000
  //где
  //internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())
  //4.967/4600-------1215079.369565217
  // result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*100000
  result = 1132060 / result;

  return result; // Vcc in millivolts
  //http://blog.unlimite.net/?p=25
}
//===========================================================================================
void drawVcc(byte x, byte y, word vcc) {
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y   8, WHITE);
  display.setCursor(x, y);
  display.println(vcc);
}
//=====================================================================================
void pressure_drawLine(byte x, byte h)
{
  display.drawLine(x, 47 - h, x, 47, BLACK);
}
void pressure_drawGraph()
{
  display.fillRect(0, 25, 83, 47, WHITE);
  for (int i = 0; i <= 83; i  ) {
    pressure_drawLine(i, EEPROM.read(i));
  }
}

//===========================================================================================
void setup()
{
  Serial.begin(9600);
 
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  //-----------------------------------------------------------------------------
  //rtc.writeProtect(false);
  //rtc.halt(false);
  String day = dayAsString(t.day);
  //-----------------------------------------------------------------------------
  tardis.TimeZone(3 * 55);
  tardis.Position(LATITUDE, LONGITUDE);
  //-----------------------------------------------------------------------------
  Wire.begin();
  display.begin();
  display.setContrast(55);
  display.clearDisplay();

  display.drawLine(0, display.height() / 2, display.width(), display.height() / 2, BLACK);
  dps.init();

  updateMoonSunDate();
  drawMoonDate(34, 8, today);
  pressure_drawGraph();
  display.display();

  prevHour = rtc.time().hr;
  //EEPROM.write(0, 9);
  // for (int i=0; i <= 83; i  ){
  // EEPROM.write(i, random(1, 23));
  //   pressure_drawLine(i,EEPROM.read(i));
  //     Serial.println(EEPROM.read(i));
  // }

}
//=====================================================================================
void loop()
{
  unsigned long currentMillis = millis();
  unsigned long currentMillis2 = millis();
  byte temp;

  //timer---------------------- 1 hour
  //  if (currentMillis - previousMillis > interval) {
  //  previousMillis = currentMillis;
  if (rtc.time().hr != prevHour) {
    prevHour = rtc.time().hr;

    dps.getPressure(&Pressure);
    dps.getTemperature(&Temperature);

    for (int i = 0; i <= 82; i  ) {
      temp = EEPROM.read(i   1);
      EEPROM.write(i, temp);
    }
    EEPROM.write(83, ceil(Pressure / 133.3) - 740);
    pressure_drawGraph();
    display.display();


  }
  //timer---------------------- 1 sec
  // if (currentMillis2 - previousMillis2 > interval2) {
  //   previousMillis2 = currentMillis2;

  if (rtc.time().sec != prevSecond) {
    prevSecond = rtc.time().sec;

    dps.getPressure(&Pressure);
    dps.getTemperature(&Temperature);
    updateMoonSunDate();

    drawPressure(0, 8);
    drawTime(17, 0);
    drawSunRiseSet(53, 8);
    drawMoonDate(34, 8, today);
    drawBatteryState(readVcc());
    drawSignal(5);
    //    drawVcc(0, 16, readVcc());
    display.display();
  }
  //timer----------------------

  buttonState = digitalRead(8);
  // Serial.println(buttonState);
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      buttonPushCounter  ;
    }
  }
  lastButtonState = buttonState;
  if (buttonPushCounter % 2 == 0) {
    digitalWrite(2, HIGH);
  } else {
    digitalWrite(2, LOW);
  }
}
//=====================================================================================

Про анемометры:  Как работает газовый котел на сжиженном газе, какой выбрать

Разработка программного обеспечения

Основная причина выбора Arduino в качестве сердца устройства – это простота разработки, низкий порог вхождения на начальном этапе, наличие большого числа сторонних библиотек. За основу были взяты библиотеки:

  1. Arduino LoRa

  2. Max44009 – датчик освещенности

  3. GyverHTU21D – для работы с датчиком HTU21D

  4. ArduinoLowPower – для спящего режима

  5. BH1750 – датчик освещенности

  6. Хитрая система измерения напряжения батареи

Первый прототип прошивки собрали буквально за 1 вечер, используя готовые модули и примеры кода. Основным недостатком данного варианта стало относительное большое время работы в активном режиме – более 1,5 секунд. Дальше приступили к оптимизации кода.

  1. Скачали калькулятор для протокола Lora и начали подбирать оптимальные показатели для передачи на те расстояния, которые у нас были – около 30 метров внутри помещений и минимальное время передачи. Связь односторонняя, без подтверждения приема данных. Подобранные параметры представлены ниже.

    Скриншоты из калькулятора 

    Также отказались от передачи контрольной суммы, а саму проверку уже организовали на стороне сервера, просто проверяя корректность данных: грубо говоря температура не может быть выше 100 градусов, влажность меньше 0 и т.д.

  2. В первом варианте данные передавались в виде строки: 
    "id устройства#температура#влажность#освещенность#заряд батарей" 
    Как потом оказалось, эта строка занимала много байт и долго передавалась. Погуглив, решили использовать структуру (Struct) для передачи пакета информации, также перевели все числа с плавающей запятой в целые числа просто умножив их на 100. В итоге id, температура, влажность, освещённость, влажность почвы и заряд батареи поместился в 11 байт.

  3. Также нашли интересную статью о том, что команды DigitalRead, DigitalWrite выполняются продолжительное время и заменили их на управление через регистры.

Данный вариант пока остался окончательным и используется в существующих устройствах. Раз в 3-4 дня приходят некорректные данные, но они успешно отсеиваются на стороне сервера. Ниже представлены осциллограммы до и после оптимизации кода и настроек Lora.

До оптимизации кода и настроек
До оптимизации кода и настроек

Скетч для метеостанции с dht11 на ардуино

#include <Wire.h>                 // библиотека для протокола IIC#include <LiquidCrystal_I2C.h>    // подключаем библиотеку LCD IICLiquidCrystal_I2C lcd(0x27,20,2); // присваиваем имя lcd для дисплея#include"DHT.h"// подключаем библиотеку для DHT11DHT dht(2, DHT11); // к какому порту подключаем датчик// создаем символ градуса и присваиваем имя "gradus"byte gradus[8] = {
0b01100,0b10010,0b10010,0b01100,0b00000,0b00000,0b00000,0b00000
};

// создаем русскую букву "П"byte P[8] = {
0b11111,0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b00000
};

// создаем русскую букву "У"byte Y[8] = {
0b10001,0b10001,0b10001,0b01111,0b00001,0b00001,0b01110,0b00000
};

// создаем русскую букву "Л"byte L[8] = {
0b00111,0b01001,0b10001,0b10001,0b10001,0b10001,0b10001,0b00000
};

// создаем русскую букву "Ж"byte ZH[8] = {
0b10101,0b10101,0b10101,0b01110,0b10101,0b10101,0b10101,0b00000
};

// создаем русскую букву "Ь"byte znak[8] = {
0b10000,0b10000,0b10000,0b11110,0b10001,0b10001,0b11110,0b00000
};

voidsetup() {
  Serial.begin(9600); // запуск последовательного порта
  lcd.init();         // инициализация LCD дисплея
  lcd.backlight();    // включение подсветки дисплея

  lcd.createChar(1, gradus);
  lcd.createChar(2, P);
  lcd.createChar(3, Y);
  lcd.createChar(4, L);
  lcd.createChar(5, ZH);
  lcd.createChar(6, znak);
}

voidloop() {
  // если нужны точные значение, то используйте float, вместо bytebyte h = dht.readHumidity();    // считываем значение температурыbyte t = dht.readTemperature(); // считываем значение влажностиSerial.print("Temperature: ");
  Serial.println(t);   // отправляем значение температуры на мониторSerial.print("Humidity: ");
  Serial.println(h);   // отправляем значение температуры на мониторSerial.println(" "); // пустая строка

  lcd.setCursor(0,0);  // ставим курсор на 1 символ первой строки
  lcd.print("TEM");    // используем латинские буквы
  lcd.print(char(2));  // выводим русскую букву "П"
  lcd.print("EPAT");   // используем латинские буквы
  lcd.print(char(3));  // выводим русскую букву "У"
  lcd.print("PA: ");   // используем латинские буквы
  lcd.print(t);        // выводим значение температуры на LCD
  lcd.print(char(1));  // выводим знак градуса

  lcd.setCursor(2,1);  // ставим курсор на 3 символ второй строки
  lcd.print("B");      // используем латинские буквы
  lcd.print(char(4));  // выводим русскую букву "Л"
  lcd.print("A");      // используем латинские буквы
  lcd.print(char(5));  // выводим русскую букву "Ж"
  lcd.print("HOCT");   // используем латинские буквы
  lcd.print(char(6));  // выводим русскую букву "Ь"
  lcd.print(": ");     // используем латинские буквы
  lcd.print(h);        // выводим значение влажности на LCD
  lcd.print("%");      // выводим знак процентdelay(1000);
}

Создание базы данных

Начнем с самого начала, а именно с проектирования и создания базы данных.

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

Все SQL скрипты находятся в каталоге weather-station/server/php-sql/

С чего начинается проектирование БД? С логического и физического представления.

Логическое представление или схема базы данных:

Физическая схема опирается на конкретную СУБД и типы данных. Проще разбирать на конкретном примере. SQL скрипт make_tables.sql раскрывает логическую и физическую схемы.

В каждой таблице должно быть поле типа

id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT

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

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

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

Если в таблице будут храниться данные от несколько однотипных датчиков, то как же их различить? Для этого в каждой таблице вводится поле

idSensor INTEGER

Фактически это CLIENT_ADDRESS который мы прописывали в файле client/client.ino для каждого экземпляра удаленного датчика-клиента и в server/server.ino для датчика, который подключен непосредственно к серверу — центральному блоку.

В промышленных системах должна быть ещё одна таблица — соответствия idSensor и его словесного, человекочитаемого описания. Например, датчик с idSensor = 2 это «Температура, влажность в квартире» и т.д. Но в нашем проекте не будем усложнять, просто помните, что :

Далее. В таблицах хранятся следующие данные:

  • ipRemote — IP адрес метеостанции (сервера) с которого пришли данные, полезно для отладки и мониторинга,
  • dateCreate — дата время создания записи,
  • millis — полезно для отладки, это время в миллисекундах с момента начала выполнения скетча на Arduino,
  • temperature — температура,
  • humidity — влажность,
  • voltage — напряжение питания,
  • pressure — давление,
  • errors — количество ошибок (не используется). Задумывалось для хранения количества ошибок при передаче и т.п., чтобы можно было удаленно оценить состояние всей системы.

Как видим таблицы arduino_dht и arduino_bmp очень похожи, отличие только в полях pressure и humidity, и возникает желание свалить всё в одну кучу (таблицу). Но делать так не велит первая нормальная форма, множество начинающих программистов пытались её обойти, но ни у одного не получилось, и мы не будем. Это как не замечать закон всемирного тяготения, до поры до времени вполне может получиться.

Таблица arduino_error_log полезна при отладке — это журнал ошибок и прочих системных сообщений.

Создание БД и её пользователя с правами описано в make_db.sql

Создание и проектирование платы

Тестовый вариант собрали навесным монтажом с использованием макетной платы, провода МГТФ и готовых модулей. Все работало, но судя по показаниям мультиметра потребляло около 50 мА, что давало нам 40 часов работы.

Первый собраный вариант
Первый собраный вариант

Полный размер

Устройство хотелось сделать миниатюрным, дешевым и энергоэффективным, поэтому решили создавать свою плату с минимальным числом элементов и использовать “голые” компоненты. Среди участников проекта не было специалистов по разработке платы, поэтому насмотревшись видеороликов начали «творить» в Easyeda.

Для начала просто соединили все устройства между собой и добавили необходимую обвязку согласно даташитам, подключили к нужным ногам микроконтроллера. Использовать решили внутренний тактовый генератор Atmega на частоте 8 МГц(использовали модифицированное ядро от AlexGyver).

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

    1 вариант платы
    1 вариант платы
  2. Второй вариант плат оказался работоспособен, добавили тестовые точки для замера напряжений и сопротивлений, которые очень сильно упростили отладку в поиске коротких замыкании и неправильного подключения компонентов. Но без ошибок опять не обошлось: ошиблись с размером разъёма для прошивки – вместо шага 2.54мм установили что-то меньшего размера. Также оказалось, что Lora модуль мешает прошивки Atmega с использованием ISP программатора. Погуглив, нашли решения в виде 3 резисторов на 500 Ом по шинам MOSI, MISO, SCK. На основе этих плат было создано 3 устройства, которые сейчас продолжают работать в тестовом режиме уже более 5 месяцев. 

    2 вариант платы
    2 вариант платы
  3. Сейчас ждём 3 заказ плат (которые были отправлены в феврале, но, к сожалению, так и не пришли) Получили 3 вариант платы, где датчик MAX44009 был заменён на более дешевый BH1750 и добавлена вся необходимая обвязка для него, а также в качестве эксперимента добавили емкостной датчик для измерения влажности почвы. 

    3 вариант платы
    3 вариант платы
Оцените статью
Анемометры
Добавить комментарий

Adblock
detector