Пример "Метеостанция"
Сила Arduino как конструктора заключается в том, что абсолютно по любой железке вы сможете найти в Гугле подробное описание, библиотеку, схему подключения и пример работы: полностью готовый набор для интеграции в свой проект!
Попробуем "собрать" такой проект из скетчей-примеров, ведь именно для этого примеры и нужны! Нам понадобится:
- Arduino Nano
- Дисплей. Пусть будет LCD1602 с переходником на i2c
- Модуль реального времени, возьмём DS3231
- Термометр ds18b20
Начинаем гуглить информацию по подключению и примеру для каждой железки:
Из уроков из Гугла мы узнаём такую важную информацию, как схемы подключения: дисплей и часы подключаются к шине i2c, а датчик ds18b20 можно подключить в любой другой пин. Схема нашего проекта:
Качаем библиотеки для наших модулей и устанавливаем.
- Библиотеку дисплея нам дают прямо в статье: https://iarduino.ru/file/134.html.
- Библиотеку для часов по своему опыту советую RTClib (та, что в статье - не очень удобная). Для неё также понадобится библиотека Adafruit_BusIO.
- В статье про датчик температуры нам указали на библиотеку DallasTemperature.h, ссылку - не дали. Ну чтож, поищем сами "DallasTemperature.h", найдём по первой ссылке. Для неё нужна ещё библиотека OneWire, ссылку на неё дали в статье про термометр.
Итого у нас должны быть установлены 4 библиотеки. Сейчас наша цель - найти рабочие примеры для каждой железки, убедиться в их работоспособности и выделить для себя минимальный набор кода для управления модулем, это бывает непросто - в статьях бывают ошибки и просто нерабочий код: эти статьи чаще всего являются копипастой от менеджеров, далёких от темы. Я взял пример работы с дисплеем из статьи, а вот часы и термометр пришлось смотреть в примерах библиотеки. Немного почистим и причешем примеры, оставим только нужные нам функции получения значений или вывода:
Дисплей
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Устанавливаем дисплей
// адрес может быть 0x27 или 0x3f
void setup() {
lcd.init();
lcd.backlight(); // Включаем подсветку дисплея
// Устанавливаем курсор на вторую строку и нулевой символ.
lcd.setCursor(0, 1);
lcd.print("Hello!"); // пишем
}
void loop() {
}
Часы
#include "RTClib.h"
RTC_DS3231 rtc;
void setup () {
Serial.begin(9600);
// проверка, подключен ли модуль
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
// установка времени равному времени компиляции
// если у модуля был сброс питания!
if (rtc.lostPower()) {
Serial.println("RTC lost power, lets set the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// вывод значений времени
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
}
void loop () {
}
Термометр
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
void setup() {
Serial.begin(9600);
sensors.begin();
sensors.requestTemperatures(); // запрос температуры
float tempC = sensors.getTempCByIndex(0); // получаем
Serial.println(tempC); // выводим
}
void loop() {
}
Итак, примеры проверены, все модули работают корректно. Начнём соединять всё в один проект! Этот блок уроков является базовым, поэтому данный проект мы напишем в стиле "скетч" - закинем весь код в один файл и будем молиться, чтобы всё работало и не было конфликтов имён. В конце следующего блока уроков, в уроке о создании крупных проектов, мы вернёмся к этому примеру и сделаем его с более серьёзным подходом, без глобальных переменных и с разбивкой на самостоятельные файлы подпрограмм.
Первым делом соединяем в начале скетча все библиотеки, объявленные объекты, типы данных и переменные. Для красоты и понятности - сортируем: сначала настройки (define), затем подключенные библиотеки и в конце уже данные:
Начало скетча
// НАСТРОЙКИ
#define ONE_WIRE_BUS 2 // пин ds18b20
// БИБЛИОТЕКИ
#include "RTClib.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// ОБЪЕКТЫ И ПЕРЕМЕННЫЕ
// адрес может быть 0x27 или 0x3f
LiquidCrystal_I2C lcd(0x3f, 16, 2); // Устанавливаем дисплей
RTC_DS3231 rtc;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
void setup() {
}
void loop() {
}
Далее переносим инициализацию в setup()
:
setup()
void setup() {
// дисплей
lcd.init();
lcd.backlight(); // Включаем подсветку дисплея
// термометр
sensors.begin();
// часы
rtc.begin();
// установка времени равному времени компиляции
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
Отлично! Теперь самое сложное: нужно продумать логику работы программы. Разобьём на простые действия:
- 1 раз в секунду - вывод часов (ЧЧ:ММ:СС) и актуального значения с датчика
- 2 раза в секунду - мигание светодиодом на плате
- 5 раз в секунду - измерение температуры и усреднение
Вот так будет выглядеть наш loop()
:
loop()
void loop() {
// 2 раза в секунду
if (millis() - myTimer1 >= 500) {
myTimer1 = millis(); // сбросить таймер
toggleLED();
}
// 5 раз в секунду
if (millis() - myTimer2 >= 200) {
myTimer2 = millis(); // сбросить таймер
getTemp();
}
// каждую секунду
if (millis() - myTimer3 >= 1000) {
myTimer3 = millis(); // сбросить таймер
redrawDisplay();
}
}
Выполняемые по таймерам функции мы создадим и заполним
toggleLED()
void toggleLED() {
digitalWrite(13, LEDflag); // вкл/выкл
LEDflag = !LEDflag; // инвертировать флаг
}
getTemp()
void getTemp() {
// суммируем температуру в общую переменную
tempSum += sensors.getTempCByIndex(0);
sensors.requestTemperatures();
// счётчик измерений
tempCounter++;
if (tempCounter >= 5) { // если больше 5
tempCounter = 0; // обнулить
temp = tempSum / 5; // среднее арифметическое
tempSum = 0; // обнулить
}
}
redrawDisplay()
void redrawDisplay() {
// ВРЕМЯ
DateTime now = rtc.now(); // получить время
lcd.setCursor(0, 0); // курсор в 0,0
lcd.print(now.hour()); // часы
lcd.print(':');
// первый ноль для красоты
if (now.minute() < 10) lcd.print(0);
lcd.print(now.minute());
lcd.print(':');
// первый ноль для красоты
if (now.second() < 10) lcd.print(0);
lcd.print(now.second());
// TEMP
lcd.setCursor(11, 0); // курсор в 11,0
lcd.print("Temp:");
lcd.setCursor(11, 1); // курсор в 11,1
lcd.print(temp);
// ДАТА
lcd.setCursor(0, 1); // курсор в 0,1
// первый ноль для красоты
if (now.day() < 10) lcd.print(0);
lcd.print(now.day());
lcd.print('.');
// первый ноль для красоты
if (now.month() < 10) lcd.print(0);
lcd.print(now.month());
lcd.print('.');
lcd.print(now.year());
}
Для функционирования таймеров и счёта температуры нам также понадобились глобальные переменные, запишем их до setup()
:
uint32_t myTimer1, myTimer2, myTimer3;
boolean LEDflag = false;
float tempSum = 0, temp;
byte tempCounter;
И в целом наш проект завершён! По мере разборок с термометром выяснилась интересная особенность: чтение сильно тормозит код, команда requestTemperatures()
ждёт ответа датчика и блокирует выполнение кода, из-за чего часы не успевают идти 1 раз в секунду. Покопавшись в примерах, я нашёл асинхронный опрос датчика: в setup добавилась строчка sensors.setWaitForConversion(false);
. Соответственно вот весь код проекта:
meteoClock.ino
// НАСТРОЙКИ
#define ONE_WIRE_BUS 2 // пин ds18b20
// БИБЛИОТЕКИ
#include "RTClib.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// ОБЪЕКТЫ И ПЕРЕМЕННЫЕ
// адрес может быть 0x27 или 0x3f
LiquidCrystal_I2C lcd(0x3f, 16, 2); // Устанавливаем дисплей
RTC_DS3231 rtc;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
uint32_t myTimer1, myTimer2, myTimer3;
boolean LEDflag = false;
float tempSum = 0, temp;
byte tempCounter;
void setup() {
Serial.begin(9600); // для отладки
pinMode(13, 1);
// дисплей
lcd.init();
lcd.backlight(); // Включаем подсветку дисплея
// термометр
sensors.begin();
sensors.setWaitForConversion(false); // асинхронное получение данных
// часы
rtc.begin();
// установка времени равному времени компиляции
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
void loop() {
// 2 раза в секунду
if (millis() - myTimer1 >= 500) {
myTimer1 = millis(); // сбросить таймер
toggleLED();
}
// 5 раз в секунду
if (millis() - myTimer2 >= 200) {
myTimer2 = millis(); // сбросить таймер
getTemp();
}
// каждую секунду
if (millis() - myTimer3 >= 1000) {
myTimer3 = millis(); // сбросить таймер
redrawDisplay();
}
}
void toggleLED() {
digitalWrite(13, LEDflag); // вкл/выкл
LEDflag = !LEDflag; // инвертировать флаг
}
void getTemp() {
// суммируем температуру в общую переменную
tempSum += sensors.getTempCByIndex(0);
sensors.requestTemperatures();
// счётчик измерений
tempCounter++;
if (tempCounter >= 5) { // если больше 5
tempCounter = 0; // обнулить
temp = tempSum / 5; // среднее арифметическое
tempSum = 0; // обнулить
}
}
void redrawDisplay() {
// ВРЕМЯ
DateTime now = rtc.now(); // получить время
lcd.setCursor(0, 0); // курсор в 0,0
lcd.print(now.hour()); // часы
lcd.print(':');
// первый ноль для красоты
if (now.minute() < 10) lcd.print(0);
lcd.print(now.minute());
lcd.print(':');
// первый ноль для красоты
if (now.second() < 10) lcd.print(0);
lcd.print(now.second());
// TEMP
lcd.setCursor(11, 0); // курсор в 11,0
lcd.print("Temp:");
lcd.setCursor(11, 1); // курсор в 11,1
lcd.print(temp);
// ДАТА
lcd.setCursor(0, 1); // курсор в 0,1
// первый ноль для красоты
if (now.day() < 10) lcd.print(0);
lcd.print(now.day());
lcd.print('.');
// первый ноль для красоты
if (now.month() < 10) lcd.print(0);
lcd.print(now.month());
lcd.print('.');
lcd.print(now.year());
}
И вот так это выглядит в жизни
Как видите, ничего особо сложного здесь нет: взяли три примера "из гугла", проверили их, сократили, и объединили в проект. Да, своего кода тоже пришлось написать прилично, но иначе и не бывает! В программировании главное - практика и наработка собственных "ходов" и алгоритмов, которые потом очень быстро пишутся: я написал код к этому уроку со скоростью печати, особо не задумываясь и не отлаживая, это на самом деле всё очень просто! В уроке о создании крупных проектов мы вернёмся к этому примеру и сделаем его более взрослым: обернём всё в классы и разобьём на файлы, чтобы дорабатывать проект было проще.
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])