Основа погодной станции – датчик BME280. В этом датчике, в отличие от датчика BMP280, имеется чувствительный элемент для измерения ещё и относительной влажности.
Добавив цифровой датчик ф. Даллас DS18B20 к уже имеющемуся датчику BME280 получаем, что наше устройство может измерять, обрабатывать и показывать:
Абсолютное атмосферное давление (в мм рт. ст. или в кПа).
Предыдущее значение абсолютного давления и разницу между текущим и предыдущим давлениями (в мм рт. ст. или в кПа). Можно судить о скорости изменения давления.
Высоту (относительную, в метрах).
Высоту относительно уровня моря (в метрах).
Внутреннюю температуру с BME280(в °С).
Относительную влажность (в %).
Внешнюю температуру (в °С, через DS18В20) + показывать минимальное и максимальное измеренное значение.
Для смены единиц измерения давления необходимо удержание кнопки Установка уровня 0 метров 2...3 секунды, после отпускания кнопки – давление будет измеряться альтернативными единицами измерения.
Схема погодной станции
Схема погодной станции была создана на основе: Arduino Pro Mini (можно использовать любую: Arduino UNO, Arduino Nano и подобные им), датчике атмосферного давления и влажности BME280(не путать с BMP280) и цифровом датчике температуры семейства DS1820.
Внешнее питание +9В подаётся на пин RAW Ардуино.
Отличия схемы варианта v3.14:
Добавлены диоды VD1, VD2 для питания датчика BME280 от +5В;
Внешнее питание подаётся сразу на VCC Ардуино (+5В).
Датчик DS18B20 включен по паразитной схеме питания (начиная с версии v3.5), что избавляет от необходимости точно позиционировать вилку датчика при подключении к розетке погодной станции, т.к. крайние выводы объединены и подключены к GND, а центральный вывод – информационный.
Отличия схемы варианта v3.16-v3.18:
Добавлена кнопка SB3 – BAROGRAPH
Добавлен делитель R1, R2 через который измеряется напряжение подключенного аккумулятора. Опорное напряжение AREF внутреннее 1,1 В.
В цепи питания подсветки дисплея где-то добавлял диод 1N4148, а где-то просто резистор 100 Ом. В обоих случаях работает нормально.
Датчик BME280/BMP280 применил на напряжение питания 5 В. Для Ардуино Pro Mini это наилучший вариант.
Датчик DS18B20 включен по активной схеме питания (некоторые экземпляры не всегда работают по паразитной схеме).
Датчик ф. Даллас семейства DS1820 и ему подобные имеет достаточную точность в широком диапазоне температур. Такой датчик я уже использовал при разработке МТР-1.2 – хороший, надёжный датчик. Если использовать герметичное исполнение, то можно измерять и температуру жидкостей, к примеру, воды.
Погодная станция может работать от встроенного аккумулятора Li-Ion +3.7 В, или от внешнего источника напряжением +6...+9 В (+5В на контакт VCC в версиях v3.13 и выше), тогда, в этом случае, напряжение питания необходимо подавать на контакт RAW. При работе от аккумулятора, используется повышающий DC-DC преобразователь MT3608(можно любой повышающий). На его выходе установлено напряжение +4.5...+5 В. Во избежании повреждения Ардуино, BME280, TFT экрана – следует обратить внимание, что перед подключением преобразователя к устройству, необходимо заранее выставить на его выходе указанное напряжение.
Если у вас напряжение питания не точно +5.0 В, а, к примеру, +4.5 В, то для точного измерения напряжения питания аккумулятора необходимо в строке [462] указать ваше напряжение питания на контакте VCC Ардуино.
Используемый мной образец BME280 работает в диапазоне напряжений +1.8...+5 В, что избавляет от необходимости применять стабилизатор на +3.3 В. Если у вас образец BME280 на напряжение +3.3 В, то добавьте для него стабилизатор на требуемое напряжение.
Скетч
Скетч имеет подробные пояснения, надеюсь будет легко понять как работает программа. Основа скетча – предыдущие разработки и опыты по использованию датчиков BME280 и DS1820.
Опубликованый скетч является последней версией v3.13. Историю доработок смотрите в подразделе Доработки. Предыдущие версии и последнюю версию и используемые библиотеки можно скачать в подразделе Приложение.
/*********************************************************
2018-05-27 Mr.ALB Тренировка в программировании Ардуино
BME280 I2C:
SCL - A5
SDA - A4
VCC - 3.3V
GND - GND
2018-07-17 v1.2 Добавлен датчик влажности DHT22:
между контактом OUT и VCC
подключить 10к
2018-08-24 v3.0 Подключение дисплея
TFT ST7735 128*160 1,8 дюйма
2018-09-05 v3.1 Автономная работа от
батареии Li-Ion +3.7В
2018-09-08 v3.2 Перерисовка только меняющихся значений
2018-11-02 v3.2.1 Минимальная яркость = 1
2018-11-11 v3.2.2 Прогрессбар подсветки
на главной странице
2019-05-09 v3.3 Использование модуля BME280
2019-12-27 v3.4 Разные системы измерения давления
(мм | кПа)
2019-12-29 v3.5 Использование датчика
температуры DS18B20
2020-01-01 v3.5 Незначительное улучшение
2020-01-16 v3.6 Усреднение измерение напр. питания
2020-01-25 v3.7 Корректное измерение напр. питания +
+ Вывод мин/макс темпер. с DS1820 +
+ Вывод на экран подключенного типа DS18xx
2020-02-02 v3.8 Управление яркостью экрана +
+ изменение функции центрирования строки +
+ оптимизация кода
2020-02-16 v3.9 Уменьшение яркости экрана
через 30 секунд
2020-05-16 v3.10 Сохранение предыдущего давления,
сравнение с текущим давлением
2020-06-24 v3.11 Оптимизация в значениях знаков после запятой
2020-07-09 v3.12 Доработка функции dark_light
+ оптимизация подсветки
2020-08-31 v3.13 Использование циклического буфера для компенсации
колебания значений высоты и напряжения батареи;
+ подключение DS18B20 на паразитное питание, т.е.:
VCC DS18B20 (3) соединить с GND(1), а выход (2)
через 4.7к на VCC Arduino;
+ обнуление высоты при включении
+ вывод высоты относительно уровня моря
Аппаратное подключение TFT:
TFT ST7735 ARDUINO
Pin 1 RST -[R 1k]- PIN 10
Pin 2 CS -[R 1k]- PIN 9
Pin 3 DC -[R 1k]- PIN 8
Pin 4 DIN -[R 1k]- PIN 11
Pin 5 CLK -[R 1k]- PIN 13
Pin 6 VCC - PIN VCC +5V
Pin 7 BL - PIN 3~ - для регулировки подсветки
использовать ШИМ
Pin 8 GND - PIN GND
Контакты TFT 1-5 подключать через резисторы 1к...2к
**********************************************************/
// Программа: Автор, Версия, Дата, Названиеconstchar* prog[] = {"Mr.ALB","v3.13","2020-08-31","BME280 "};
#include<EEPROM.h>// для EEPROM
// Подключение библиотеки для I2C#include<Wire.h>/* Графический дисплей TFT ST7735 1.8" */#include<Adafruit_GFX.h>// Ядро графической библиотеки#include<Adafruit_ST7735.h>// Библиотека для ST7735#define TFT_RST 10
#define TFT_CS 9
#define TFT_DC 8
// Аппаратное подключение TFT
// Создаём объект tftAdafruit_ST7735 tft = Adafruit_ST7735
(
TFT_CS,
TFT_DC,
TFT_RST
);
// Подключение библиотеки BME280#include<Adafruit_BME280.h>/* Чтобы всё заработало необходимо
1. Сканером I2C найти адрес BME280
или в библиотеке <Adafruit_BME280.h>:
1) Изменить адрес #define BME280_ADDRESS (0x77)
на адрес #define BME280_ADDRESS (0x76)
2) Закоментировать библиотеку
#include <Adafruit_Sensor.h>
*/
// Создаём объект барометраAdafruit_BME280 bme;
/* мБар - мм рт. ст.
1013.2472 - 760 k=1.33322
1005.24788 - 754
*/
// Уровень моря#define SEALEVELPRESSURE_HPA 1013.2472 // В мБар// Нулевой уровень для давления 754 мм рт.ст.
// Задайте значение соответствующее вашему положениюfloat null_level = 1005.24788; // В мБар// Коэффициент для перевода Па в мм рт.ст.constfloat kp = 133.322;
// переменные давленияfloat press_now,
press_last,
press_tmp,
press_prev,
altit;
// переменные температурыfloat temp,
temp_last,
temp_correct =-1;
// переменные влажностиfloat humidity_now,
humidity_last,
humidity_correct = 0;
#include<Buttons.h>// Подключение библиотеки кнопок#define BUTTON_SET_LEVEL 6 // Кнопка "установка уровня 0 м" #define BUTTON_LIGHT 5 // Кнопка подсветки дисплея#define LIGHT_PIN 3 // Вывод PWM для подсветки дисплея#define VBAT_PIN A0 // Ввод VBAT батареи питания, // через резистор 1к#define DARK_LIGHT 30000 // Задержка гашения экрана 30 секунд// Создание объекта для кнопки "установка уровня 0 м"Buttons buttonSetLevel(BUTTON_SET_LEVEL, 21);
// Создание объекта для кнопки подсветкиButtons buttonLight(BUTTON_LIGHT, 21);
// Подключение библиотеки OneWire#include<OneWire.h>// Установка OneWire на Pin4#define ONE_WIRE_BUS 4 // Номер пина к которому подключен DS18B20OneWire oneWire(ONE_WIRE_BUS);
// Подключение библиотеки Dallas Temperature#include<DallasTemperature.h>// Установка DallasTemperature для работы по OneWireDallasTemperature sensors(&oneWire);
// Переменные для DS1820float temperature, fahrenheit, last_temperature;
float temperatureMax = 0, temperatureMin = 0;
byte addr[8];
byte type_s;
byte resolution;
// Название типа датчика DS18ххconstchar* type_ds[] =
{
"DS1820","DS18S20","DS18B20","DS1822","DS1825"
};
// Чип датчика DS18ххconstchar* type_text[] =
{
"Chip = DS1820",//0"Chip = DS18S20 | old DS1820",//1"Chip = DS18B20",//2"Chip = DS1822",//3"Chip = DS1825",//4"is not family of DS18x20",//5"Resolution = "//6
};
// Переменные для экранаconstuint8_t byinfo = 24; // Позиция вывода информации по yconstuint8_t bxinfo = 10 * 6; // Позиция вывода информации по х
// Уровень подсветки 0...250;int level_light = 25;
// Строка вывода прогресс-бара подсветкиconstuint8_t light_str = 15;
// Переменные для обновления экрана по времениunsignedlong time_now,
time_check,
time_check_display,
time_button,
time_button_now;
constunsignedlong time_delta = 3000;
#define SCREEN_W 160 // Ширина экрана в рх#define SCREEN_H 128 // Высота экрана в рхuint8_t font_size = 1; // Размер шрифта/* Флаги */
// Для обновления экрана по времениboolean flagcheck =false;
// Для вывода надписей после установки яркостиboolean flagSetLight =true;
// Для измерения в кило Паскалях (kPa)byte flagPa = 0;
// Для DS1820boolean flagDS =false;
// Для снижения яркости экранаbyte flag_dark_light =false;
constfloat vcc = 4.51; // vcc - Напряжение питания Ардуино.
// Реальное напряжение после
// преобразователя, для точного
// измерения батареи питания
//Адреса хранения системы измерения и давленияint addrSYSPR = 1, addrPR = 2;
// Переменные и постоянные для циклического буфера#define BUFF_SIZE 16
// Циклический буфер для батареиint buffer1[BUFF_SIZE];
byte index1 = 0; // индекс циклического буфера// Циклический буфер для высотыint buffer2[BUFF_SIZE];
byte index2 = 0; // индекс циклического буфера
/***********************/
/* Настройка программы */
/***********************/voidsetup()
{
//Serial.begin(9600); для отладки
// Порт управления подсветкой экранаpinMode(LIGHT_PIN,OUTPUT);
// Порт для измерения батареи питанияpinMode(VBAT_PIN,INPUT);
// Опорный источник - внутренний
//analogReference(DEFAULT);// Инициируем работу с дисплеем 1.8" TFT
tft.initR(INITR_BLACKTAB);// initialize a ST7735S chip, black tab
fnClrScreen(); // Очистить экран
tft.setRotation(1); // Поворот экрана:
// 0 - 0° книжный
// 1 - 90° альбомный
// 2 - 180° книжный
// 3 - 270° альбомный
// Установка начальной яркостиanalogWrite(LIGHT_PIN, level_light);
if (!bme.begin(0x76)) // Старт датчика давления
{
//Если ошибка запуска BME280char* bme_error[] =
{
"Error!","Not find BME280 sensor!","Check contacts","Click Reset/off"
};
tft.setTextColor(ST7735_RED);
tft.setTextSize(2);
fnCenter(bme_error[0], 2, 1, 0);
tft.print(bme_error[0]);
tft.setTextSize(1);
fnCenter(bme_error[1], 1, 7, 0);
tft.print(bme_error[1]);
fnCenter(bme_error[2], 1, 9, 4);
tft.print(bme_error[2]);
tft.setTextColor(ST7735_YELLOW);
fnCenter(bme_error[3], 1, 14, 0);
tft.print(bme_error[3]);
while (1);
}
// Старт датчика DS1820
sensors.begin();
if (!sensors.getAddress(addr, 0))
flagDS =false; // Нет датчика DS1820else
{
flagDS =true;
/* Определяем устройство и его параметры */
//Считываем установленную точность
resolution = sensors.getResolution();
if(resolution<10) sensors.setResolution(10);
// Проверка точности
resolution = sensors.getResolution();
// Первый байт ROM указывает, какой чипswitch (addr[0])
{
case DS18S20MODEL:
type_s = 1;
break;
case DS18B20MODEL:
type_s = 2;
break;
case DS1822MODEL:
type_s = 3;
break;
case DS1825MODEL:
type_s = 4;
break;
default:
type_s = 5;
}
// Обновление значения датчика DS1820
sensors.requestTemperatures();
temperature = sensors.getTempC(addr);
temperatureMax = temperature;
temperatureMin = temperature;
}
// Заставка
fnZastavka();
// Считывание системы измерения давления из EEPROM
flagPa =EEPROM.read(addrSYSPR);
// Считывание предыдущего давления из EEPROM
press_prev = EEPROM_float_read(addrPR);
// Измерение текущего давления
press_now = bme.readPressure();
// Установка параметров давления и высотыif (flagPa)
{
press_now /= 1000; // Давление в кПа// обнуление текущей высоты
null_level = press_now * 10;
}
else
{
press_now /= kp; // Давление в мм рт. ст.
// обнуление текущей высоты
null_level = press_now * kp / 100;
}
// Запись текущего давления в EEPROM
EEPROM_float_write(addrPR, press_now);
// Вывод основного текста на экран
fnPrintText();
}
/***********************/
/* Рабочая программа */
/***********************/voidloop()
{
// Текущее время работы программы в мс
time_now =millis();
// Проверка интервала времени для обновления экранаif (time_now - time_check > time_delta)
{
time_check = time_now;
flagcheck =true;
}
// Энергосбережение: яркость экрана на минимумif (time_now - time_check_display > DARK_LIGHT)
{
time_check_display = time_now;
// Установка минимальной яркости через 30 секундanalogWrite(LIGHT_PIN, 3);
flag_dark_light =true;
}
buttonSetLevel.scanState();// Считывание состояния кнопки уровня
buttonLight.scanState(); // Считывание состояния кнопки подсветки// Было нажатие кнопки подсветкиif (buttonLight.flagPress ==true)
{
time_check_display = time_now; //Задержка гашения экрана
time_button =millis();
// Пока кнопка нажатаwhile (buttonLight.flagPress)
buttonLight.scanState();
time_button_now =millis();
if (time_button_now - time_button >= time_delta / 2)
{
// Если нажатие больше time_delta/2 сек,
// то...
// ...измерение и запись текущего давления в EEPROM
press_now = bme.readPressure();
if (flagPa == 0)
press_now /= kp; // Давление из мБар в мм рт.ст.else press_now /= 1000; // Давление из мБар в кПа
press_prev = press_now; // Смена предыдущего давления
EEPROM_float_write(addrPR, press_prev);//Запись давления
// ...запись текущей системы измерения давления в EEPROMEEPROM.write(addrSYSPR, flagPa);
}
else
{
// 2020-07-09 v3.12 Настройка яркости экранаif (flag_dark_light ==false)
{
if (level_light == 3)level_light = 25;
else level_light += 25; // Шаг яркостиif (level_light == 250)
{
level_light = 3; // Минимальная яркость
tft.fillRect(0, light_str * 8 + 2, 160, 3, ST7735_BLACK);
}
}
// Установка яркости
// если было гашение экрана через dark_light,
// то устанавливаем ранее выбранную яркостьanalogWrite(LIGHT_PIN, level_light);
// сброс флага dark_light
flag_dark_light =false;
/* Прогресс бар яркости подсветки */uint8_t bar_step = 0;
constuint8_t bar_step_const = 8 * 2;
tft.setTextSize(1);
for (uint8_t ip = 0; ip < level_light / 25; ip++)
{
tft.fillRect (1 + bar_step,
light_str * 8 + 2,
bar_step_const - 2,
3,
ST7735_WHITE
);
bar_step += bar_step_const;
}
}
flagSetLight =true;
}
// Было нажатие кнопки "Установка уровня 0"if (buttonSetLevel.flagPress ==true)
{
time_check_display = time_now; //Задержка гашения экрана
time_button =millis();
// Пока кнопка нажатаwhile (buttonSetLevel.flagPress)
buttonSetLevel.scanState();
time_button_now =millis();
if (time_button_now - time_button >= time_delta / 2)
{
// Если нажатие больше time_delta/2 сек,
// то смена размерности давления
flagPa =!flagPa; // Инверсия флага Па
fnPrintText(); // Вывод основных надписей// Смена у предыдущего давленияif (flagPa == 0)
{ // Из кПа в мм рт. ст.
press_prev /= kp;
press_prev *= 1000;
}
else
{ // Из мм рт. ст. в кПа
press_prev *= kp;
press_prev /= 1000;
}
}
else
{
// Если нажатие меньше time_delta/2 сек,
// то обнуление высотыif (flagPa) null_level = press_now * 10;
else null_level = press_now * kp / 100;
// Обнуление значений цикл.буфераfor (byte i = 0; i < BUFF_SIZE; i++)
{
buffer2[i] = 0;
}
}
flagcheck =true;
}
// Измерение параметров с BME280*********************
press_now = bme.readPressure(); // Давление в мБарif (flagPa == 0)
press_now /= kp; // Давление в мм рт. ст.else press_now /= 1000; // Давление в кПа
temp = bme.readTemperature(); // Температура в °С
humidity_now = bme.readHumidity();// Влажность в %// Конец измерения параметров с BME280***************
//Применение коррекции влажностиif (humidity_correct != 0) humidity_now += humidity_correct;
//Применение коррекции температурыif (temp_correct != 0) temp += temp_correct;
/* Если давление изменилось больше чем на 0,1 мм рт.ст.,
или на 0,01 кПа, то выводим на дисплей...
или если температура изменилась больше чем на 0,1°С
или если нажата кнопка обнуления высоты...
или если прошёл интервал времени >= 2 сек
*/if
(
(flagPa == 0) && ((press_now < (press_last - .1)) ||
(press_now > (press_last + .1))) ||
(flagPa == 1) && ((press_now < (press_last - .01)) ||
(press_now > (press_last + .01))) ||
(temp < (temp_last - .1)) ||
(temp > (temp_last + .1)) ||
flagcheck ==true||
flagDS && ((last_temperature > temperature + .1) ||
(last_temperature < temperature - .1))
)
{
flagcheck =false; // Сброс флага проверки по времениif (flagSetLight) // Если была настройка яркости
{
tft.fillRect(0, light_str * 8 + 2, 160, 3, ST7735_BLACK);
flagSetLight =false;
}
// Приблизительная высота в метрах
altit = bme.readAltitude(null_level);
int altit_buf = altit * 100; // Значение для цикл.буфера
//Serial.println(index2);
//Serial.print("altit = "); Serial.println(altit);
//Serial.print("altit_buf = "); Serial.println(altit_buf);
// Записываем в циклический буфер
fnAddReading(altit_buf, buffer2, index2);
// Вывод значения давления
tft.setTextColor(ST7735_YELLOW);
tft.fillRect(bxinfo, byinfo - 3, 6 * 10, 16, ST7735_BLACK);
tft.setTextSize(2);
tft.setCursor(10 * 6, byinfo - 2);
tft.print(press_now, 1);
// Вывод значения предыдущего давления
tft.setTextSize(1);
tft.fillRect(bxinfo, byinfo + 16, 6 * 10, 8, ST7735_BLACK);
tft.setCursor(bxinfo, byinfo + 16);
tft.print(press_prev, 1);
tft.print("/");
// Дельта текущего давления к предыдущемуfloat fdpress = press_now - press_prev;
// Если дельта меньше нуля, то голубымif (fdpress < 0)tft.setTextColor(ST7735_CYAN);
// Если дельта больше 0, то краснымelse tft.setTextColor(ST7735_RED);
tft.print(press_now - press_prev, 1);
// Вывод высоты
tft.setTextColor(ST7735_WHITE);
tft.fillRect(bxinfo, byinfo + 28, 6 * 12, 8, ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(bxinfo, byinfo + 28);
// Вывод усреднённого значения
altit = (float)fnAverage(buffer2) / 100;
//Serial.print("altit_avr = ");
//Serial.println(altit, 2);
//Serial.println();
tft.print(altit, 2);
tft.print("/");
// Вывод абсолютной высоты от уровня моря
tft.print(bme.readAltitude(SEALEVELPRESSURE_HPA), 1);
// Вывод значения температуры с BME280
tft.setTextColor(ST7735_ORANGE);
tft.fillRect(bxinfo, byinfo + 40, 6 * 6, 8, ST7735_BLACK);
tft.setCursor(bxinfo, byinfo + 40);
if (temp > 0) tft.print("+");
tft.print(temp, 1);
/* Вывод влажности */
/* Раскрашиваем значение в зависимости от % влажности */if (humidity_now < 30.0) // Сушь
tft.setTextColor(ST7735_RED);
if (humidity_now >= 30.0 && humidity_now < 60.0)// Сухо
tft.setTextColor(ST7735_YELLOW);
if (humidity_now >= 60.0 && humidity_now < 75.0)// Влажно
tft.setTextColor(ST7735_GREEN);
if (humidity_now >= 75.0) // Сыро
tft.setTextColor(ST7735_BLUE);
//Очистка места под значение
tft.fillRect(bxinfo, byinfo + 52, 6 * 8, 16, ST7735_BLACK);
tft.setTextSize(2);
tft.setCursor(bxinfo, byinfo + 52);
tft.print(humidity_now, 0);
if (flagDS)
{
// Обновление значения датчика DS1820
sensors.requestTemperatures();
temperature = sensors.getTempC(addr);
// Вывод значения температуры с DS1820
tft.setTextColor(ST7735_WHITE);
tft.fillRect(bxinfo, byinfo + 74, 6 * 11, 16, ST7735_BLACK);
tft.setTextSize(2);
tft.setCursor(bxinfo, byinfo + 74);
if (temperature > 0) tft.print("+");
tft.print(temperature, 1);
// Записываем максимальное значениеif (temperature > temperatureMax)
temperatureMax = temperature;
// Записываем минимальное значениеif (temperature < temperatureMin)
temperatureMin = temperature;
// Вывод максимального и минимального значений
tft.setTextColor(ST7735_LIGHTGRAY);
tft.fillRect(bxinfo, byinfo + 90, 6 * 12, 8, ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(bxinfo, byinfo + 90);
if (temperatureMin > 0) tft.print("+");
tft.print(temperatureMin, 1);
tft.print("/");
if (temperatureMax > 0) tft.print("+");
tft.print(temperatureMax, 1);
}
/*******************************
Проверка напряжения питания
*******************************/
// Чтение напряжения питания из порта VBAT_PIN
// v3.13 Добавление значения в циклический буфер
fnAddReading(analogRead(VBAT_PIN), buffer1, index1);
// Перевод бит в вольты vcc=4.51Vfloat vbat;
// v3.13 Усреднение значений циклического буфера
vbat = (float)fnAverage(buffer1) * vcc / 1024.0;
// Выводим значение напряжения питания батареи
// если запитываемся от батареиif (vbat > 2.5)
{
uint8_t x_vbat = 20 * 6 + 3, y_vbat = 14;
tft.fillRect(x_vbat, y_vbat, 6 * 6, 8, ST7735_BLACK);
tft.setCursor(x_vbat, y_vbat);
tft.setTextSize(1);
if (vbat < 3.7) tft.setTextColor(ST7735_YELLOW);
if (vbat < 3.3) tft.setTextColor(ST7735_RED);
if (vbat >= 3.7) tft.setTextColor(ST7735_GREEN);
tft.print("+");
tft.print(vbat);
tft.print("V");
fnBattary(23 * 6, 3, vbat); //Вывод пиктограммки
}
/* Конец проверки питания */
// Перезаписываем бывшие значения
temp_last = temp; //BMP280
press_last = press_now;
humidity_last = humidity_now;
last_temperature = temperature; //DS1820delay(5);
}
}
/* Функция - Вывод °С */void fnChrDegree
(
int16_t x,int16_t y,uint16_t color
)
{
uint8_t radius = 2;
tft.drawCircle(x += radius * 2, y += radius * 2 - 1, radius, color);
tft.setCursor(x + 3, y);
tft.setTextColor(color);
tft.print("C");
}
/* Функция символа батарейки */void fnBattary
(
int16_t x,int16_t y,float vbat
)
{
int16_t w = 19, h = 8;
// Контур батарейки
tft.drawRect(x, y, w, h, ST7735_WHITE);
// Пимпочка
tft.fillRect(x + w, y + 2, 2, 4, ST7735_WHITE);
// Вывод квадратиков наполненияif (vbat < 3.3)
fnPrintFillRect(x, y, 1, ST7735_RED);
if (vbat >= 3.3 && vbat < 3.5)
fnPrintFillRect(x, y, 1, ST7735_YELLOW);
if (vbat >= 3.5 && vbat < 3.7)
fnPrintFillRect(x, y, 2, ST7735_YELLOW);
if (vbat >= 3.7 && vbat < 3.9)
fnPrintFillRect(x, y, 3, ST7735_GREEN);
if (vbat >= 3.9)
fnPrintFillRect(x, y, 4, ST7735_GREEN);
}
/* Функция вывода квадратиков для символа батарейки*/void fnPrintFillRect
(
int16_t x,int16_t y,uint8_t max_i,int16_t color
)
{
tft.fillRect(x + 1, y + 1, 17, 6, ST7735_BLACK);
for (uint8_t i = 0; i < max_i; i++)
{
tft.fillRect(x + 2 + i * 4, y + 2, 3, 4, color);
}
}
/* Функция - Заставка */void fnZastavka()
{
fnClrScreen(); // Очистить экран
tft.setTextWrap(false);
tft.setTextColor(ST7735_ORANGE);
font_size = 3;
tft.setTextSize(font_size);
fnCenter(prog[3], font_size, 1, 0);
tft.print(prog[3]);
font_size = 2;
tft.setTextSize(font_size);
fnCenter(prog[0], font_size, 4, 0);
tft.setTextColor(ST7735_DARKGREEN);
tft.print(prog[0]);
font_size = 1;
tft.setTextSize(font_size);
fnCenter(prog[1], font_size, 11, 0);
tft.setTextColor(ST7735_GRAY);
tft.println(prog[1]);
fnCenter(prog[2], font_size, 12, 2);
tft.print(prog[2]);
delay(3600);
if (flagDS)
{
fnClrScreen(); // Очистить экран
tft.setTextColor(ST7735_ORANGE);
font_size = 3;
tft.setTextSize(font_size);
// Вывод названия
fnCenter(type_ds[0], font_size, 1, 0);
tft.print(type_ds[0]);
font_size = 1;
tft.setTextSize(font_size);
// Вывод чипа DS18xx
fnCenter(type_text[type_s], font_size, 7, 0);
tft.print(type_text[type_s]);
// Вывод точности изменения DS18xx
fnCenter(type_text[6], font_size, 9, 0);
tft.print(type_text[6]);
tft.print(resolution);
}
delay(3000);
}
/* Функция - Вывод основных надписей на экран */void fnPrintText()
{
fnClrScreen(); // Очистить экран
tft.setTextSize(1);
tft.setCursor(0, 3);
tft.setTextColor(ST7735_GREEN);
tft.print(prog[3]);
tft.print(prog[1]);
if (flagDS)
{
tft.print(" ");
tft.print(type_ds[type_s]);
}
/* Вывод давления */
tft.setTextColor(ST7735_LIGHTGRAY);
tft.setCursor(0, byinfo);//24
tft.print("Pressure:");
tft.setCursor(bxinfo + 6 * 11, byinfo);
tft.setTextSize(1);
// Вывод размерности давленияif (flagPa)
tft.print("kPa");
else
tft.print("mm");
tft.setCursor(0, byinfo + 15);
tft.print("Pres_pre:");
tft.setCursor(bxinfo + 6 * 11, byinfo + 15);
// Вывод размерности давленияif (flagPa)
tft.print("kPa");
else
tft.print("mm");
/* Вывод высоты */
tft.setCursor(0, byinfo + 28);
tft.print("Altitude:");
tft.setCursor(bxinfo + 6 * 12, byinfo + 28);
// Вывод размерности высоты
tft.print("m");
/* Вывод температуры */
tft.setCursor(0, byinfo + 40);
tft.print("Temper. :");
// Вывод размерности температуры
fnChrDegree(bxinfo + 6 * 7, 61, ST7735_LIGHTGRAY);
/* Вывод влажности */
tft.setCursor(0, byinfo + 52);
tft.print("Humidity:");
tft.setTextSize(1);
tft.setCursor(bxinfo + 6 * 8, byinfo + 54);
// Вывод размерности влажности
tft.print("%");
/* Вывод температуры с DS1820 */if (flagDS)
{
//Разделитель влажности и DS1820
tft.drawFastHLine(0, byinfo + 70, 160, ST7735_DARKGRAY);
/* Вывод температуры c DS1820 */
tft.setCursor(0, byinfo + 78);
tft.print("Temper. :");
// Вывод размерности температуры
fnChrDegree(bxinfo + 6 * 12, 100, ST7735_LIGHTGRAY);
// Вывод размерности температуры для Min/Max
fnChrDegree(bxinfo + 6 * 12, 112, ST7735_LIGHTGRAY);
/* Вывод минимальной и максимальной температуры c DS1820 */
tft.setCursor(0, byinfo + 88);
tft.print("Min/Max :");
}
}
/* Функция - центрирование строки */void fnCenter
(
char* string,uint8_t fntsize,uint8_t row,uint8_toffset
)
{
// Подсчёт количества символов в строкеboolean flag =true;
byte i = 0;
while (flag)
{
if (string[i] !='\0')i++;
else flag =false;
}
// Установка позиции вывода строки
tft.setCursor
(
(SCREEN_W - i * fntsize * 6) / 2 + fntsize * 6 / 2,
fntsize * 8 * row +offset
);
}
/* Функция - Cтирание экрана чёрным цветом */void fnClrScreen()
{
// Очистить экран, т.е. закрасить чёрным
tft.fillScreen(ST7735_BLACK);
// Курсор в начало экрана
tft.setCursor(0, 0);
}
/********************************
Функции для работы с EEPROM
*******************************/
/* Чтение float из EEPROM */float EEPROM_float_read(int addr)
{
byte raw[4];
for (byte i = 0; i < 4; i++)
{
raw[i] =EEPROM.read(addr + i);
}
float&num = (float&)raw;
return num;
}
/* Запись float в EEPROM */void EEPROM_float_write(int addr,float num)
{
byte raw[4];
(float&)raw = num;
for (byte i = 0; i < 4; i++)
{
EEPROM.write(addr + i, raw[i]);
}
}
/*****************************************
Функции для работы с циклическим буфером
*****************************************/
/* Функция заполнения циклического буфера
2020-08-31 v3.13 Mr.ALB
*/void fnAddReading(int reading,int*buffer,byte&ind)
{
// Запись в ячейку буфераbuffer[ind] = reading;
ind++; // Инкремент индекса буфера//Обнуление индекса буфераif (ind >= BUFF_SIZE)ind = 0;
}
/* Функция усреднения значений != 0
2020-08-31 v3.13 Mr.ALB
*/int fnAverage(int*buffer)
{
long sum = 0;
byte count = 0;
byte i = 0;
while (i < BUFF_SIZE)
{
// если значение != 0
// то суммируемif (buffer[i] != 0)
{
// суммирование значений
sum +=buffer[i];
// инкремент счётчика
// значений != 0
count++;
}
i++;
}
// Возвращение среднего
// из значений != 0return (int)(sum / count);
}
Реализация
Программа написана, отлажена, пришло время всё изготовить как единый модуль.
Хотелось собрать в минимальных размерах, но так, чтобы был лёгкий доступ к любым частям устройства. Поэтому сделал как модуль, в который втыкаются основные платы-части устройства.
Основой послужила монтажная плата с металлизированными отверстиями, что удобно для двухстороннего монтажа. Печатную плату разрабатывать не стал, сберёг своё личное время . Это устройство единичное и не имеет смысла делать какие-то печатные платы. Размер монтажной платы 63 * 50 мм. Куплена в Китае.
С нижней стороны платы установлена панелька на 24 контакта для Arduino Pro Mini. Там же смонтирован преобразователь DC-DC MT3608, и плата, на основе TP4056, для зарядки литиевого аккумулятора. Преобразователь использовал покупной, какой был под рукой, но можно использовать любой, хоть бы и самодельный, который выдаёт на выходе напряжение от +4.5 В до +5 В.
На нижней стороне платы смонтированы конденсаторы С1 и С2.
С верхней стороны платы размещены: датчик BME280, TFT экран 1.8", аккумулятор 3,7 В 400 мАч.
Вся разводка выполнена цветными одножильными монтажными проводками.
Напоминаю, что перед установкой платы Ардуино, BME280 и TFT экрана, заранее проверьте и выставите напряжение на выходе преобразователя DC-DC, чтобы случайно не повредить компоненты устройства.
Когда всё проверено, установите в панельки все компоненты. Подключите Ардуино к компьютеру и залейте в него программу.
При включении устройства на экране выводится заставка, где указывается дата и версия прошивки. Последняя версия v3.13 2020-08-31.
Если к погодной станции подключен датчик температуры DS1820, то на второй странице заставки выводится сообщение о его подключении. Определяется чип датчика и выводится точность измерения. Максимальная точность – 12 бит. Если внешний датчик температуры не подключен, то после вывода заставки программа переходит к измерениям текущих параметров с датчика BME280.
По просьбам читателей, добавил возможность измерения давления в кПа. У нас в РФ атмосферное давление измеряется в мм ртутного столба, а в Европейских странах, в основном, в Барах или Паскалях. Эти единицы измерения подобны и отличаются лишь десятичным разрядом. При измерении в кПа на экране выводится число по размерности соответствующее нашему значению, то есть три десятичных разряда перед запятой и два после.
Смена единиц измерения давления происходит при удержании кнопки Установка уровня 0 метров. Удерживать 2...3 секунды. При кратком нажатии на кнопку, происходит установка нулевого уровня для текущего атмосферного давления.
Основной режим измерения задан в мм рт. ст. Если вам необходимо чтобы погодная станция по умолчанию измеряла в кПа, то в программе в строке [184] установите флаг измерения в кПа flagPa=true;.
Если подключен внешний датчик температуры DS1820, то на экране внизу дополнительно выводится строчка с измеренным значением с этого датчика. Дополнительно в верху экрана указывается, что используется датчик DS1820(левее пиктограммы батарейки). Значение температуры с датчика BME280 выводится мелким шрифтом, перед строкой со значением влажности (Humidity).
Модуль закончен и вполне функционален. Удобен в использовании и легко можно его модернизировать, к примеру добавить ещё кнопочку, или обновить прошивку.
Для полного завершения этой конструкции требуется изготовить корпус. Как и прежде, буду его делать из пластика ABS. Когда корпус будет изготовлен, то добавлю фото, а пока так как есть.
Размеры получившегося модуля: 64 * 50 * 32 мм (ширина * высота * глубина). Потом размеры несколько увеличатся за счёт толщины пластика корпуса.
2020-01-02
Корпус
Закончил изготовление корпуса. Теперь погодная станция обрела законченный вид. Ниже на фото этапы изготовления корпуса. Хотелось его изготовить как можно меньше по габаритам. В итоге получился размерами 70 * 63 * 38 мм.
Прежде чем что-то сделать, приходится рассчитывать, проектировать на бумаге, выверять размеры. Корпус состоит из двух частей: нижней и верхней.
Модуль погодной станции вставлен в нижнюю часть. Пока примерка. Не стал устанавливать защитное стекло перед экраном. Решил на экран наклеить защитную плёнку, которая используется для экранов смартфонов.
Закончено изготовление частей корпуса и толкателей для кнопок управления. В нижней крышке есть отверстие-окно для индикации процесса зарядки аккумулятора. Когда аккумулятор зарядится, то включается синий светодиод, а пока идёт процесс заряда, в этом окне видно свечение красного светодиода.
Всё собрано вместе. Подключен внешний датчик температуры DS1820. На экране видны все надписи.
Подключен блок питания для зарядки аккумулятора. Идёт процесс заряда.
2020-02-07
Доработки
Версия v3.7 Доделал немного программу. Теперь на экран выводится минимальная и максимальная температура с датчика DS1820. Также на главный экран выводится конкретный тип подключенного датчика DS18xx. Немного улучшил возможность для точного измерения напряжения аккумулятора.
Версия v3.8 Доработка по оптимизации кода. Добавил гашение экрана на минимум через минуту с момента включения или после любого нажатия любой кнопки, что позволяет экономить энергию аккумулятора. Переделал функцию центрирования строки.
Версия v3.9 Модернизация. Уменьшение яркости экрана через 30 секунд.
Версия v3.10Программа переделана. Добавлено:
Сохранение в EEPROM предыдущего давления.
Сравнение предыдущего давления с текущим. Если разность между текущим давлением и предыдущим положительная – идёт рост давления, то значение разности выводится красным цветом. Если разность между текущим давлением и предыдущим отрицательная – идёт падение давления, то значение разности выводится голубым цветом. По цвету разности легко ориентироваться, что происходит с атмосферным давлением – рост или падение.
При включении погодной станции происходит считывание предыдущего сохранённого давления и запись текущего давления.
При смене системы измерения, значение предыдущего давления выводится в установленной системе измерения.
При длительном (2-3 секунды) нажатии на кнопку Подсветка/Light происходит принудительная запись в EEPROM текущего давления и текущей системы измерения. При последующем включении, на экране будет выводиться давление в сохранённой системе измерения, что удобно для тех, кто предпочитает смотреть давление в кПа.
Версия v3.11 Оптимизация в значениях знаков после запятой при выводе на экран.
Версия v3.12 Доработка функции dark_light(яркость экрана на минимум через 30 секунд). Теперь после того, как экран был переведён в сберегающий режим, по нажатии на кнопку SB2 (Light) происходит возврат в яркий режим подсветки с сохранением ранее выбранной настройки яркости. Дополнительно в вызове функции bme.begin(0x76) адресс платы 0x76 указан явно.
Версия v3.13
Использование циклического буфера для компенсации колебания значений высоты и напряжения батареи;
Подключение DS18B20 на паразитное питание, т.е.: VCC DS18B20 (3) соединить с GND(1), а выход OUT(2) через 4.7к на VCC Arduino. Такая доработка позволяет не обращать внимание на правильность подключения крайних выводов разъёма датчика DS18B20;
Обнуление высоты при включении;
Вывод высоты относительно уровня моря, что позволяет узнать высоту вашего положения над уровнем моря.
Версия v3.14 Настройка фильтрации, точности измерения и интервала измерения в BME280.
Версия v3.16. Особенности версии:
Отказ от цифровых фильтров и усреднения для значений.
Переделка и оптимизация функций и переменных.
Отказ от паразитного питания DS18B20, т.к. китайские датчики не работают в таком режиме.
Применение массива с давлениями измеренными через заданный интервал времени.
Настройка у самого модуля давления фильтрации, точности и интервала измерения.
Возможность использования модуля BMP280 или BME280(Настройка через условную компиляцию скетча). При использовании модуля BMP280 влажность устанавливается в 50%.
Возможность калибровки значений модуля давления: коррекция значения индикации давления, температуры и влажности.
Для точного измерения напряжения аккумулятора применил внутренний ИОН* на 1,1 В и делитель из резисторов 200 кОм + 51 кОм.
Добавлена кнопка Барограф – при нажатии на эту кнопку происходит переключение в экран барографа и обратно к основному экрану со значениями. На экране барографа выводятся: название, версия программы, два последних значения давления, график давления через заданный интервал времени, уровни: минимальный, нормальный, максимальный, оставшееся время до следующего измерения. Если последнее значение давления меньше предыдущего, то его цвет голубой, если же последнее давление больше предыдущего, то оно выводится жёлтым цветом.
Подключение датчика DS1820 в любой момент времени. Оптимизация кода.
Финальная версия v3.18
Полный функционал. Модуль давления BMP280/BME280 + Барограф + МЕНЮ настройки + Сохранение параметров в EEPROM:
Измеритель BMP280 или BME280 и DS18B20:
- Давление*, мм рт.ст. / кПа;
- Высота относительная, метры;
- Высота относительно уровня моря, метры.
- Температура внутренняя*, °С;
- Влажность относительная*, %;
- Температура внешняя*, °С.
Две системы измерения: мм рт.ст. / кПа.
Барограф в двух системах измерения.
МЕНЮ: 7 параметров настройки.
Сохранение параметров в EEPROM.
Управление тремя кнопками:
Кнопка 1:LIGHT / MENU.
Краткое нажатие: Регулировка яркости
Долгое нажатие: Переход к МЕНЮ настройки
Кнопка 2:Set "0" / <<*.
Краткое нажатие: Обнуление высоты
В режиме МЕНЮ: Уменьшение значения
Кнопка 3:BAROGRAPH / >>*.
Краткое нажатие: Переход к экрану Барографа и обратно
В режиме МЕНЮ: Увеличение значения
Долгое нажатие: Обнуление массива барографа
Конструкция погодной станции v3.18
Стартовая страница, на которой выводится используемый модуль давления, автор, версия, дата версии.
Страница вывода информации о подключенном датчике DS1820.
Информационная страница, на которой выводится: тип используемого модуля давления, версия, наличие подключенного датчика DS1820, напряжение аккумулятора, давление, предыдущее давление, разность текущего и предыдущего давлений, относительная высота в метрах, высота относительно уровня моря в метрах, температура модуля давления, относительная влажность, температура внешнего датчика DS1820, минимальная и максимальная температуры внешнего датчика DS1820.
Страница Барографа. Выводится версия, название, напряжение аккумулятора, значения двух последних измерений давления, последнее значение цветом указывает на рост или падение давления, время до следующего измерения, столбики измеренного давления, уровни давлений: максимальное, нормальное, минимальное.
Меню. Активный пункт подсвечен жёлтым цветом.
Кнопками <<>> изменяется значение активного пункта.
Кнопкой MENU осуществляется переход к следующему пункту меню.
Выход из меню на последнем пункте по нажатию на любую из кнопок <<>>.
Вот и закончен проект, который у меня растянулся на два года и несчитанное количество часов программирования и отладки созданного скетча. Зато испытываю чувство удовлетворения, что то, что задумывал два года назад, смог реализовать и вместить в 8-битный микроконтроллер ATMega328P на модуле Arduino Pro Mini.
Если вы захотите повторить мой проект, то все необходимые библиотеки и сам скетч публикую в свободном доступе. Захотите меня поблагодарить за труд, буду рад, не захотите – что ж...
P.S. Однако прошло времени уже почти два года после того, как сделал версию v3.18.7. Каждодневное пользование выявило ряд неточностей и желание всё же доделать до более оптимального варианта. Собрался с силами и окунулся вновь в программу этого проекта... пришлось много чего переделать, что-то убрать, что-то добавить и сейчас, глядя на макет уже нового устройства v5.2, испытываю радость, что сумел в этот микроконтроллер ATMega328P впихнуть, под завязку, улучшенный функционал... схема, описание и т.д. уже на другой странице. Корпус и монтаж переделывать не нужно, обновление программное.