Домашняя метеостанция на базе Arduino

в 12:20, , рубрики: arduino, БГУИР, ВМСиС, кафедра ЭВМ

Вместо введения

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

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

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

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

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

Подробнее

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

Домашняя метеостанция на базе Arduino - 1

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

  1. Датчик измерения давления и температуры BMP180:
    Подробнее

    Датчик BMP180 стал дальнейшим развитием датчика BMP085. BMP180 стал меньше по размерам, потребляет меньше электроэнергии, также датчик стал точнее и отличается более высокой стабильностью.
    Изображение BMP180

    Домашняя метеостанция на базе Arduino - 2

    Диапазон измерения давления 300-1100 ГПа
    Погрешность измерения давления 0,06 ГПа (режим энергосбережения), 0,02 ГПа (режим повышенной точности)
    Погрешность измерения высоты над уровнем моря 0,5 метра (режим энергосбережения), 0,17 метров (режим повышенной точности)
    Абсолютная точность измерения (p = 300..1100 ГПа, t = 0..65 С0, питание 3,3 В) Давление: — 4,0… + 2,0 ГПа Температура: ± 1 С0
    Среднее потребление тока (при частоте обновления данных 1 Гц) 3 мкА – режим энергосбережения, 32 мкА – расширенный режим
    Максимальный ток 650 мкА
    Время измерения давления 5 мс (обычный режим)
    Частота передачи данных максимум 3,4 МГц

  2. Датчик измерения температуры и влажности DHT22
    Подробнее

    Датчик температуры и влажности DHT-22 (AM2302), отличается от датчика DHT-11 большими точностью и диапазоном измерения влажности и температуры. Содержит в себе АЦП (аналогово-цифровой преобразователь) для преобразования аналоговых значений влажности и температуры. При подключении датчика к микроконтроллеру, рекомендуется между выводами VCC и SDA разместить подтягивающий pull-up резистор номиналом 10 кОм, а также конденсатор ёмкостью 100 нФ между питанием и «землёй». Но мне ничего паять не пришлось, так нашёл датчик, распаенный на плате с конденсатором и резистором.

    Изображение DHT22

    Домашняя метеостанция на базе Arduino - 3

    Напряжение питания 3,3 – 5 В
    Диапазон измерения влажности 0 – 100% с точностью 2 – 5%
    Диапазон измерения температуры — 40 – + 125 С0 с точностью ± 0,5 С0
    Частота опроса датчика не более раза в 2 секунды

  3. Датчик освещенности BH1750 (рассматривать его здесь не буду, так как во время припайки к нему ножек, спалил одну из дорожек).
    Изображение BH1750

    Домашняя метеостанция на базе Arduino - 4

С датчиками определился. Но что делать с данными, поступающими от датчиков. Решил выводить на дисплей. Картинку хотелось цветную, поэтому монохромные решения отбросил сразу. После нескольких минут поиска был выбран 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 - 5

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

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

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

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

Скетч

#include <Wire.h>	// library for communication with I2C devices
#include <Adafruit_Sensor.h>	// Core library for all sensors
#include <Adafruit_BMP085_U.h>	// library for BMP180
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h>	// Hardware-specific library
#include <SPI.h>	// 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);
}

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

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

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

Корпус

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

Домашняя метеостанция на базе Arduino - 6

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

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

Домашняя метеостанция на базе Arduino - 7
Домашняя метеостанция на базе Arduino - 8

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

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

Домашняя метеостанция на базе Arduino - 9

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

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

Домашняя метеостанция на базе Arduino - 10

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

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

Домашняя метеостанция на базе Arduino - 11
Домашняя метеостанция на базе Arduino - 12
Домашняя метеостанция на базе Arduino - 13

После вывода результатов на экран, выявляем неприятную ошибку измерения влажности: 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 порту.

Автор: VaDiKGts

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js