Please use this identifier to cite or link to this item:
https://er.chdtu.edu.ua/handle/ChSTU/8596| Title: | Автоматизована система для управління теплицею на основі платформи Arduino |
| Authors: | МИРОНЮК, Тетяна ЯЛАНСЬКИЙ, Валерій |
| Keywords: | ARDUINO;ARDUINO NANO;ТЕПЛИЦЯ;АВТОМАТИЗОВАНА СИСТЕМА;МІКРОКОНТРОЛЕР |
| Issue Date: | 2023 |
| Abstract: | Метою виконання даної кваліфікованої роботи на здобуття освітнього ступеню «бакалавр» є створення автоматизованої системи для управління теплицею на основі платформи Arduino. Загальний обсяг роботи становить 78 сторінки. У роботі 26 рисунків, 1 таблиці. Для виконання роботи використано 11 літературних джерел. Основними завданнями кваліфікаційної роботи є виготовити і запрограмувати пристрій, який буде контролювати мікроклімат в теплиці. Також пристрій повинен бути простим і незатратним. В першому розділі визначається напрямок роботи. Поставлено декілька проблем, а саме: вибір мікроконтролера, вибір середовища розробки та мови програмування. В другому розділі визначається елементна база проекту. В третьому розділі описується принцип роботи та проводяться тести для реалізованої автоматизованої системи. |
| URI: | https://er.chdtu.edu.ua/handle/ChSTU/8596 |
| Appears in Collections: | 123 Комп’ютерна інженерія (Системне програмування) |
Files in This Item:
| File | Description | Size | Format | |
|---|---|---|---|---|
| 01_ТИТУЛКА_Яланський_ДРУК-merged.pdf Restricted Access | 3.03 MB | Adobe PDF | View/Open Request a copy |
Items in DSpace are protected by copyright, with all rights reserved, unless otherwise indicated.
Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
ФАКУЛЬТЕТ ІНФОРМАЦІЙНИХ ТЕХНОЛОГІЙ І СИСТЕМ
КАФЕДРА ІНФОРМАЦІЙНОЇ БЕЗПЕКИ ТА КОМП’ЮТЕРНОЇ ІНЖЕНЕРІЇ
ПОЯСНЮВАЛЬНА ЗАПИСКА
до кваліфікаційної роботи бакалавра
на тему:
«Автоматизована система для управління
теплицею на основі платформи Arduino»
ЧДТУ.231934.008 ПЗ
Виконав: студент 4 курсу, групи СП-1906
спеціальності 123 – «Комп’ютерна інженерія»
за освітньою програмою – «Системне
програмування»
Валерій ЯЛАНСЬКИЙ
Керівник
к.т.н., доцент Тетяна МИРОНЮК
Рецензент
к.т.н., доцент, начальник відділу персоналу
Віталій ЗАЖОМА
«ЗАХИСТ ДОЗВОЛЯЮ»
Завідувач кафедри ІБ та КІ
д.т.н., професор ______ Володимир РУДНИЦЬКИЙ
Черкаси 2023 року
Форма № Н-9.01
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
Факультет: інформаційних технологій і систем
Кафедра: інформаційної безпеки та комп’ютерної інженерії
Освітньо-кваліфікаційний рівень: Бакалавр
Спеціальність 123 – Комп’ютерна інженерія
Освітня програма Системне програмування
«ЗАТВЕРДЖУЮ»
Завідувач кафедри ІБ та КІ
д.т.н., професор ______ Володимир РУДНИЦЬКИЙ
«28» лютого 2023 року
ЗАВДАННЯ
на кваліфікаційну роботу бакалавра студенту
Яланському Валерію Андрійовичу
(прізвище, ім‘я, по батькові)
1. Тема роботи: Автоматизована система для управління теплицею на основі Arduino
Керівник роботи: к.т.н. Миронюк Тетяна Василівна
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання)
затверджені наказом університету від «24» лютого 2023 р. № 43/04
2. Строк подання студентом роботи:
3. Вихідні дані до роботи:
тип мікроконтролера Arduino;
мова програмування: C та C++;
середовище розробника: Arduino IDE.
4. Зміст розрахунково-пояснювальної записки (перелік питань, що їх належить розробити):
Вступ
1 Визначення напрямку дослідження для предметної області
2 Вибір елементної бази для автоматизованої системи
3 Проектування та опис роботи автоматизованої системи
Висновки
Додатки
Список використаних джерел
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових креслень, плакатів):
1. Специфікація
2. Текст програми
6. Консультанти розділів роботи:
Розділ Прізвище, ініціали Підпис, дата
консультанта завдання видав завдання прийняв
7. Дата видачі завдання: 27 лютого 2023 року
КАЛЕНДАРНИЙ ПЛАН
Термін
№ з/п Назва етапів роботи виконання Примітка
етапів роботи
1 Отримання завдання 01.03 – 14.03 виконано
2 Збір матеріалу 15.03 – 1.04 виконано
3 Обробка матеріалу 02.04 – 10.04 виконано
4 Вибір програмного забезпечення та його 11.04 – 15.04 виконано
обґрунтування
5 Розробка програмного продукту 16.04 – 26.04 виконано
6 Розробка пристрою 27.04 – 05.05 виконано
7 Синхронізація програмного продукту з виконано
пристроєм та тестування 06.05 – 11.05
8 Оформлення пояснювальної записки 12.05 – 22.05 виконано
9 Оформлення графічного матеріалу 22.05 – 31.05 виконано
10 Подання кваліфікаційної роботи на відгук та 01.06.23 виконано
рецензування
11 Захист кваліфікаційної роботи 08.06.23
Студент ____________________________ Валерій ЯЛАНСЬКИЙ
(підпис)
Керівник роботи _____________________________ Тетяна МИРОНЮК
(підпис)
АНОТАЦІЯ
Метою виконання даної кваліфікованої роботи на здобуття освітнього
ступеню «бакалавр» є створення автоматизованої системи для управління
теплицею на основі платформи Arduino.
Загальний обсяг роботи становить 78 сторінки. У роботі 26 рисунків, 1
таблиці. Для виконання роботи використано 11 літературних джерел.
Основними завданнями кваліфікаційної роботи є виготовити і
запрограмувати пристрій, який буде контролювати мікроклімат в теплиці.
Також пристрій повинен бути простим і незатратним.
В першому розділі визначається напрямок роботи. Поставлено декілька
проблем, а саме: вибір мікроконтролера, вибір середовища розробки та мови
програмування.
В другому розділі визначається елементна база проекту.
В третьому розділі описується принцип роботи та проводяться тести
для реалізованої автоматизованої системи.
Ключові слова: ARDUINO, ARDUINO NANO, ТЕПЛИЦЯ,
АВТОМАТИЗОВАНА СИСТЕМА, МІКРОКОНТРОЛЕР
ANNOTATION
The method of achieving this qualification work of the education of the
lighting level “bachelor” is the creation of an automated system for managing a
greenhouse based on the Arduino platform.
Work to became 78 slides. A work has 26 drawigs and 1 table. Take for
work 11 literary stuff.
The main task of the qualification work is to prepare and program
attachments to control the microclimate in the greenhouse. So the attachment is to
blame, but we will forgive and not cost.
The first section defines the direction of the work. Several problems are
posed, namely: the choice of the Arduino microcontroller, the choice of the
development environment and the programming language.
The element base of the project is defined in the second section. In the third
section, the connection diagram and code fragments are given.
Key words: ARDUINO, ARDUINO NANO, GREENHOUSE,
AUTOMATED SYSTEM, MICROCONTROLLER
ЗМІСТ
ВСТУП..................................................................................................................... 3
1 ВИЗНАЧЕННЯ НАПРЯМКУ ДОСЛІДЖЕННЯ ДЛЯ ПРЕДМЕТНОЇ
ОБЛАСТІ..................................................................................................................5
1.1 Вибір апаратної обчислювальної платформи мікроконтролера.........5
1.2 Вибір середовища для розробки............................................................7
1.3 Вибір мови програмування для розробки.............................................9
1.4 Постановка завдання.............................................................................10
2 ВИБІР ЕЛЕМЕНТНОЇ БАЗИ ДЛЯ АВТОМАТИЗОВАНОЇ СИСТЕМИ......11
2.1 Вибір мікроконтролера.........................................................................11
2.2 Вибір периферійних пристроїв для розробки.....................................12
3 ПРОЕКТУВАННЯ ТА ОПИС РОБОТИ АВТОМАТИЗОВАНОЇ
СИСТЕМИ............................................................................................................. 24
3.1 Опис роботи автоматизованої системи для управління теплицею на
основі платформи Arduino..........................................................................24
3.2 Перевірка мікроконтролера та компонентів автоматизованої
системи.........................................................................................................25
3.3 Проектування автоматизованої системи для управління теплицею
на основі платформи Arduino.....................................................................32
3.4 Принцип роботи автоматизованої системи........................................33
ВИСНОВКИ...........................................................................................................35
ДОДАТКИ:
А – 482.ЧДТУ.31931-01 Автоматизована система для управління
теплицею на основі платформи Arduino
ПЕРЕЛІК ВИКОРИСТАНИХ ДЖЕРЕЛ……………………………………….78
ЧДТУ.231934.008 ПЗ
Змн. Арк. № докум. Підпис Дата
РозрКобив Яланський В.А. Автоматизована Літ. Лист Листів
Керівник Миронюк Т.В. система для управління 2 55
Рецеанзент Зажома В.М. теплицею на основі
Н.Контроль Гресько С.О. Кафедра ІБ та КІ
ф платформи Arduino
Затвердив Рудницький В.М. гр. СП-1906
Пояснювальна записка
е
д
р
а
К
К
-
0
6
ВСТУП
Наша країна Україна є аграрною державою. Вирощування продуктів
харчування завжди приділяли велику увагу. Їх споживання є невід’ємним
фактором нашого буття. Для створення кращих умов вирощування різних
сільськогосподарських культур і не тільки, люди стали використовувати
теплиці.
Теплиця – конструкція зі стінами та дахом, зроблена з прозорого
матеріалу, такого як скло, плівка та полікарбонат в якій вирощують рослини,
які потребують регульованих кліматичних умов. Ці конструкції розміром від
невеликих до промислових приміщень. Мініатюрна теплиця відома як
парник. Температура всередині теплиці за рахунок сонячного світла стає
вищою, ніж температура навколишнього середовища назовні, захищаючи
приміщення в холодну погоду.
Багато комерційних теплиць є високотехнічним виробничими
приміщеннями для вирощування овочів або квітів. Теплиці заповнені
обладнанням, включаючи екрануючі установки, опалення, охолодження,
освітлення і можуть контролюватися комп’ютером для оптимізації умов
росту рослин. Для оцінки оптимальності ступенів і коефіцієнта комфорту
парникового мікро-клімату, тобто температури повітря, відносної вологості і
дефіциту тиску пари, використовують різні методи, щоб зменшити ризик
виробництва до культивування конкретної культури.
Щоб мінімізувати людську працю було вирішено створити
автоматизовану систему використавши контролер Arduino.
Автоматичне підтримання мікроклімату є основною метою при
здійсненні цього проекту. Автоматизація тепличних господарств має безліч
переваг. Завдяки автоматизації можна скоротити кількість працівників,
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 3
заощадить ресурси і спростити роботу в теплицях. Автоматизація охоплює
практично всі рівні роботи теплиці, будь то полив або провітрювання.
Також, метою проекту ми поставим собі за ціль виготовити сучасний
прототип контролера на база шилдів Arduino, з мінімальним
капіталовкладенням, який в своєму класі буде найдешевшим рішенням при
вирішенні поставленої задачі.
Сама по собі, розумна теплиця містить одну єдину систему в тепличних
спорудах, яка складається з різного роду датчиків, які керують різними
елементами та пристроями.
Розумна теплиця є автоматизованою конструкцією, здатна спростити
роботу в городах та садах. Одною з важливих особливостей розумних
теплиць є можливість монтування всіх складових системи абсолютно в будь-
яку, навіть раніше збудовану, теплицю.
Мінімальний функціонал, що необхідний при розробці автоматизованої
системи, включає наступні вимоги: підтримання мікроклімату в теплиці;
вимірювання температури грунту та навколишнього середовища; таймер або
годинник; контроль світла; підключення приладів наприклад: вентилятора чи
системи поливу
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 4
1 ВИЗНАЧЕННЯ НАПРЯМКУ ДОСЛІДЖЕННЯ ДЛЯ ПРЕДМЕТНОЇ
ОБЛАСТІ
Перед початком розробки проекту перед нами постала задача
визначитися з напрямком роботи. А саме: визначити який з контролерів
Arduino будем використовувати, вирішити в якому середовищі розробки
будем його програмувати, обрати мову програмування на якій буде писатися
код програми.
1.1 Вибір апаратної обчислювальної платформи мікроконтролера
Ще однією метою є зробити цей проект не затратним, щоб його міг
використовувати кожен. Ось чому було обрано саме мікроконтролер Arduino.
Arduino – це апаратна обчислювальна платформа для аматорського
конструювання, основними компонентами якої є плата мікроконтролера з
елементами вводу/виводу та середовище розробки Processing/Wiring на мові
програмування, що є спрощеною підмножиною C і C++. Arduino може
використовуватися для створення автономних інтерактивних об'єктів. Також
був вибір між видами контролера Arduino. А саме Arduino Uno, Arduino
Nano, Arduino Mega, та Arduino Mini.
На рисунку 1.1 наведено вид для контролера Arduino Mini.
Рисунок 1.1 – Arduino Mini
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 5
На рисунку 1.2 наведено вид для контролера Arduino Nano.
Рисунок 1.2 – Arduino Nano
На рисунку 1.3 наведено вид для контролера Arduino Mega.
Рисунок 1.3 – Arduino Mega
На рисунку 1.4 наведено вид для контролера Arduino Uno.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 6
Рисунок 1.4 – Arduino Uno
Маючи чотири варіанти, складаємо таблицю 1 характеристик, яка
допоможе нам визначитися який тип Arduino вибрати для проекту.
1.2 Вибір середовища для розробки
Для програмування мікроконтролера було вибране середовище
розробки Arduino IDE. Тому що ця програма одна з кращих і я вже мав з нею
справу. Arduino IDE — інтегроване середовище розробки для Windows,
MacOS та Linux, розроблене на C++, призначене для створення та
завантаження програм на Arduino-сумісні плати, а також на плати інших
виробників. Підтримує мови Сі та C++ з використанням спеціальних правил
структурування коду. Arduino IDE надає бібліотеку програмного
забезпечення, яка надає безліч загальних процедур введення та виведення.
Для написаного користувачем коду потрібні лише дві базові функції для
запуску ескізу та основного циклу програми, які скомпільовані та пов'язані із
заглушкою GNU. [1]
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 7
Таблиця 1.1 – Таблиця характеристик
На рисунку 1.6 показано інтерфейс середовища розробки Arduino IDE.
Рисунок 1.6 – Arduino IDE
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 8
В даному середовищі розробки і буде писатися код програми.
1.3 Вибір мови програмування для розробки
Для проекту була обрана мова програмування С та С++. Вони
найкраще підходить для програмування мікроконтролера. Мова
програмування С ++ хоч і була створена в кінці минулого століття, але не
втрачає свою популярність в сучасному світі програмування, оскільки й досі
є затребуваною. Звичайно, мова С ++ не є ідеальною, і має окремі недоліки:
мова важко розпізнається; складність мови створює незручності для її
вивчення; через довгий програмний код збільшується час компіляції і
виникають складнощі при використанні програми; підтримка модулів погано
реалізована.
Плюсів мови програмування С++ теж багато: висока сумісність коду з
мовою С; повна універсальність; за допомогою С++ вирішується велика
кількість типів завдань; програміст може вільно вибрати стиль
програмування (структурний, об’єктно-орієнтований, функціональний). Як
відомо, дуже багато С++ запозичила у мови С. Ці запозичення забезпечили
С++ потужними засобами низького рівня, що дозволяють розв’язувати
завдання системного програмування. Але С++ відрізняється від С в першу
чергу різним ступенем уваги до типів і структур даних. Це пов’язано з
появою понять класу, похідного класу і віртуальної функції. Це дає С++
більш ефективні можливості для контролю типів даних і забезпечує
модульність програми.
Особливістю мови C++ є відсутність принципу замовчування. Тобто, в
програмі необхідно оголосити всі змінні і константи із зазначенням їх типів.
Вважається, що програма на С++ – це набір функцій. Причому, тільки одна
функція програми може бути головною. Виконання будь-якої С++ програми
починається з першого оператора – саме main функції. На відміну від
традиційних структур C і Pascal в C++ членами класу є не тільки дані, але і
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 9
функції. Функції мають привілейований доступ до даних усередині об’єктів
цього класу і забезпечують інтерфейс між цими об’єктами і рештою
програми. Мова С++ є засобом об’єктного програмування, методики
проєктування та реалізації програм, яка замінила традиційне процедурне
програмування. [2]
1.4 Постановка завдання
На основі проведених досліджень функціоналу обраних для
дослідження компонентів та беручи за основу їх функціонал було визначено
деякі основоположні критерії, які буде розроблено під час реалізації
автономної системи: тип мікроконтролера Arduino Nano; середовище
розробки Arduino IDE; мова програмування С та С++.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 10
2 ВИБІР ЕЛЕМЕНТНОЇ БАЗИ ДЛЯ АВТОМАТИЗОВАНОЇ СИСТЕМИ
Потрібно визначитися з елементами, які будуть використовуватися в
нашому проекті. Також вибрати тип мікроконтролера Arduino.
2.1 Вибір мікроконтролера
Після перегляду характеристик «див. рисунок 1.5» було вирішено
зупинити увагу на Arduino Nano, а саме мікроконтролері ATmega328P який
працює на частоті 8 МГц – цього достатньо для забезпечення нормальної
роботи з датчиками, АЦП, та UART. Також даний мікроконтролер може
конфігурувати свій порт на внутрішню обробку аналогового сигналу, що
дозволяє працювати з аналоговими датчиками. Через велику кількість
датчиків та виконавчих елементів виникає необхідність у достатній
кількості пінів, що можуть програмуватися на ввід/вивід.
На рисунку 2.1 показано Arduino Nano Atmega328P.
Рисунок 2.1 –Arduino Nano
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 11
Даний мікроконтролер повністю задовольняє дану потребу для
підключення необхідний компонентів. Він має малу ціну, є відомим та його
легко знайти у продажі, що є дуже важливим для для проекту.
2.2 Вибір периферійних пристроїв для розробки
Оскільки мікроконтролер вже обрано, треба визначитися з пристроями
якими від буде керувати, а саме: датчиками, реле, модулями та іншими
пристроями.
Вибір датчика вологості ґрунту
Для вимірів значень вологості ґрунту буде використовуватися датчик
FC-28. Він складається з двох зондів, які дозволяють струму проходити через
грунт і отримувати значення опору для вимірювання вологості. Модуль
також містить потенціометр, який встановлює граничне значення. Це
порогове значення буде порівнюватися на компараторі LM393. Світлодіод
сигналізує значення вище або нижче порогового. Датчик FC-28 має
аналоговий і цифровий виходи, тому його можна використовувати як в
аналоговому, так і цифровому режимах. Напругу живлення на модуль можна
подавати від мікропроцесорного пристрою або зовнішнього джерела
живлення. На платі знаходяться два світлодіоди. Синій сигналізує про подачу
харчування н модуль, відповідно червоний загоряється при передачі сигналу
на мікроконтролер.
Технічні характеристики датчику: вхідна напруга 3.3V до 5V; вихідна
напруга від 0 до 4.2V; вхідний струм 35мА; вихідний сигнал: аналоговий та
цифровий; чотири контакти: VCC, А0, аналоговий вихід D0, цифровий вихід
GND. В аналоговому режимі датчик приймає вихідні значення від 0 до 1023.
Оскільки вологість вимірюється у відсотках, тому значення потрібно
зіставити зі значення від 0 до 100, а потім вивести їх на послідовному
моніторі.
На рисунку 2.2 показано датчик вологості грунту FC-28.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 12
Рисунок 2.2 – FC-28
Для роботи у цифровому режимі використовується компаратор LM393.
LM393 порівнює значення виходу датчика і граничне значення, і після цього
дає нам вихідне значення через цифровий висновок. Коли значення датчика
більше ніж порогове значення, цифровий вихід дасть нам 5В і загориться
світлодіод. В іншому випадку, коли значення буде менше ніж порогове на
цифровий вивід передаються 0В і світлодіод не засвітиться. [3]
Датчик температури та вологості цифровий ВМЕ280
Тут вибір пав на нове покоління датчиків тиску, що дозволяють
вимірювати не тільки значення атмосферного тиску, а й температуру і
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 13
вологість. Датчик характеризується високою точністю вимірювання, високою
швидкодією інтерфейсу та надмалим споживанням.
Характеристика:
Інтерфейси підключення: І2С
Максимальна швидкодія інтерфейсу: 3.4 МГц
Межі вимірювання температури: від -40 до 85 градусів
Точність виміру температури: від 0.5 до 1 градуса
Межі виміру вологості: від 0 до 100%
Точність виміру вологості: 3%
Межі вимірювання тиску: 1гПа
Напруга живлення: від 1.8 до 5 В
Споживання струму у режимі вимірювання тиску: 714 мкА
Споживання струму у режимі вимірювання вологості: 340 мкА
Споживання струму у режимі вимірювання температури: 450 мкА
Споживання струму у режимі сну: від 0.1 мкА до 0.5 мкА
Розміри модуля: 15 * 12 * 3 мм [3]
На рисунку 2.3 показано BME280.
Рисунок 2.3 – ВМЕ280
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 14
Датчик температури цифровий DC18B20
Датчик температури DC18B20 в захисному водонепроникному корпусі
з вологозахистом ІР67. Діапазон температур, що вимірюються датчиком,
знаходяться в межах від -55 до 125 градусів. Але ,оскільки, захисна оболонка
виготовлена з ПВХ, то рекомендується верхній діапазон вимірювання
обмежити ста градусами. Сам вимірювальний елемент розміщений у
герметичному корпусі, що забезпечує максимальний ступінь захисту датчика
та дозволяє проводити вимірювання температури в будь-яких умовах
вологості, запиленості, а також повному зануренню датчика в рідину.
Характеристика:
Червоний провід – VCC (харчування)
Зелений провід – DATA (дані)
Жовтий дріт – GND (земля)
Робоча напруга від 3 до 5.5 В
Точність 0.5 у діапазоні від -10 до 85 градусів
Робочий діапазон від -55 до 125 градусів
Вибір 9 чи 12 бітної розрядності
Інтерфейс 1-Wire
Унікальний 64-бітний ID у кожному чіпі
Паралельне включення сенсорів
Зонд в нержавіючої сталі діаметром 6 мм та довжиною 50 мм
Кабель діаметром 4 мм та довжиною 100 мм [4]
На рисунку 2.4 показано DS18B20.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 15
Рисунок 2.4 – DC18B20
Датчик освітленості
Модуль датчика світла із пороговим компаратором. Поріг спрацювання
компаратора регулюється змінним резистором.
Характеристика:
Чутливий елемент – фоторезистор
Вихід компаратора більш ніж 15 мА
Регулювання порога спрацювання змінним резистором
Робоча напруга від 3.3 до 5 В
Цифровий вихід датчика освітлення
Зручний отвір для кріплення
Розміри: 3.2 см * 1.4см
Використаний компаратор LM393
На рисунку 2.5 показано датчик освітленості
Рисунок 2.5 –Датчик освітленості
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 16
Модуль Реального часу DS3231
Модуль годинника реального часу на мікросхемі DS3231 має відмінну
точність ходу, так як кварцовий резонатор розміщений безпосередньо в
корпусі мікросхеми і застосована цифрова корекція точності ходу годинника
в залежності від температури навколишнього середовища.
Характеристика:
Мікросхема годинника: DS3231SN
Напруга живлення: від 2.3 до 5.5 В
Робоча температура: від -40 до 85 градусів
Точність ходу годинника: 5ppm (0.432 сек)
Резервне харчування: Lipo акумулятор
Параметри часу: годинник та календар. Включаючи секунди, години,
дні, дату, місяць та рік до 2100 з урахуванням високосного року
Сигнали календаря: два сигнали
Цифрові виходи: 1 Гц та 32.768 кГц
Інтерфейс: I2C 400 кГц
Адреса пристрою: 0*68
Точність датчика температури: 3 градуса [3]
На рисунку 2.6 показано DS3231.
Рисунок 2.6 –DS3231
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 17
Модуль сервоприводу SG90
Сервопривід SG90 – це, мабуть, найпоширеніша серва всіх часів. Її
ставлять авіа-моделі, судно-моделі, в роботи та інші самаробки. Легкий,
якісний міні сервопривід із пусковим моментом 2кг. Усередині корпусу
знаходиться невеликий модуль керування, який під дією вхідного сигналу
подає живлення відповідної полярності електродвигуна. Вхідний сигнал
керування містить дані про необхідне положення валу. Для визначення
поточного положення валу редуктор з'єднаний із двигуном змінного
резистора. Електроніка Tower Pro SG90 обчислює різницю між поточним
положенням редуктора та необхідним. Модуль управління орієнтуючись на
опір змінного резистора подає живлення необхідної полярності на двигун для
повороту редуктора, що приводить у відповідність положення передане
сигналом управління. Інформація про необхідне положення валу міститься у
шпаруватості імпульсів сигналу, що управляє. Частота сигналу, що управляє,
повинна бути постійна і складати 50 Гц. Добре – відношення тривалості
імпульсу до періоду. Найчастіше при аналізі параметрів сигналу, що
управляє, розглядають тривалість імпульсу. Для формування такого сигналу
зручно використовувати мікроконтролер, що має функцію широтно-
імпульсної модуляції вихідного сигналу.
Характеристика:
Швидкість без навантаження: 0.12 сек/60 град.
Крутний момент: 2кг/см
Температурний діапазон: від 0 до 50 градусів
Ширина мертвої зони: 4 мікросекунди
Робоча напруга живлення: від 3.5 до 5 В
Споживаний струм у русі: 50-80 мА
Споживаний струм в утриманні: 5-10 мА
Кут повороту: 120 градусів
Розміри: 3.3 см * 3 см * 1.3
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 18
Вага: 9 г [3]
На рисунку 2.7 показано cервопривід SG-90.
Рисунок 2.7 – Сервопривід SG-90
Модуль перетворювача PCF8574T
ідмінний I2C інтерфейсний модуль на мікросхемі PCF8574T для
розширення кількості портів вводу/виводу для контролерів Arduino,
мікроконтролерів STM8 та STM32 та мінікомп'ютерів Raspberry Pi та Orange
Pi. Може використовуватися як інтерфейсна плата для підключення РКІ 1602
та 2004 так і як самостійний пристрій. Для налаштування контрастності
дисплея встановлено змінний резистор. Для нормальної роботи дисплея
потрібно налаштувати його контрастність. При використанні модуля як
розширювач портів вводу/виводу слід враховувати, що висновок Р3 має
інверсний вихід з відкритим колектором. У деяких партіях розширювачів
встановлені мікросхеми PCF8574AT, які повністю ідентичні за
функціональними можливостями, але відрізняються діапазоном адрес шини
I2C замість 0x20-0x27, вони відгукуються на адреси 0x38-0x3f.
Характеристика:
Інтерфейсна мікросхема: PCF8574AT/T
Інтерфейс: I2C
Діапазон адрес I2C:
PCF8574T - 0x20-0x27
PCF8574AT - 0x38-0x3f
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 19
Максимальна кількість однотипних модулів, що підключаються: 8
Напруга живлення: 5 В
Розмір: 5.2 * 1.8 * 1.4 см
Сумісність: РКІ 1602 та 2004 [3]
На рисунку 2.8 показано модуль перетворювача PCF8574T.
Рисунок 2.8 –модуль перетворювача PCF8574T
LCD індикатор 2004
Текстовий дисплей 20 символів, 4 рядки. Побудований на базі
сумісного чіпа з HD44780, що дозволяє працювати з ним через стандартну
бібліотеку LiquidCrystal. Цей дисплей не підтримує кирилицю. Відображати
кириличні символи можна через символи, що визначаються користувачем.
З'єднання стандартне як і для всієї лінійки подібних дисплеїв на цьому
контролері. Використовувати дисплей можна як у 8 так і в 4-бітному режимі.
Характеристика:
Напруга живлення: від 3,3 до 5В (при напрузі 4В для високої
контрастності потрібно джерело негативної напруги для регулятора
контрастності)
Напруга логічних рівнів: від 3 до 5 В
Розміри модуля: 98 * 60 * 14 мм
Видима область: 75 * 25 мм [3]
На рисунку 2.9 показано LCD індикатор 2004.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 20
Рисунок 2.9 – LCD індикатор 2004
Плата реле з опторозв’язкою і живленням 5 В
Може керуватися безпосередньо з висновків мікроконтролера (Arduino
та подібним). Максимальний струм навантаження 10А при напрузі 250В.
Розміри модуля: 13.9 х 5.2 х 1.7 см.Якщо вам потрібна повна оптична
ізоляція, підключіть "Vcc" до виходу +5В Arduino, але не підключайтеся до
Gnd. Заберіть джампер Vcc до JD-Vcc. Підключіть окремі +5В до виводу JD-
Vcc і окремі Gnd. Це дасть живлення для транзисторів та обмотки реле. Якщо
вам достатньо ізоляції реле то можна просто запитати модуль від висновків
Arduino +5В і Gnd і залишити джампер Vcc to JD-Vcc на своєму
місці.Важливо: для замикання реле потрібно подати на вхід, що управляє,
низький логічний рівень (LOW), тоді реле замикається і світлодіод
загоряється, для розмикання реле потрібно подати рівень логічної одиниці
(HIGH).
На рисунку 2.10 показано плату реле.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 21
Рисунок 2.10 –Плата 8 реле
EC11 – інкрементальний енкодер.
Поворотний датчик, який відстежує напрямок та кут повороту осі.
Потім дані переводить у цифровий чи аналоговий формат. Має кнопку
вимикач та різьблення під гайку.
Характеристика:
Кількість імпульсів за 1 оборот: 20
Робоча напруга: 5 В
Розміри датчика: 28 * 23.5 мм без урахування ніжок
Розміри ручки потенціометра: 15 * 15 мм [3]
На рисунку 2.11 показано інкрементальний енкодер ЕС11.
Рисунок 2.11 –Енкодер
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 22
Блок живлення 220-5V-1А
Блок живлення для цього проекту вибраний вхідною напругою 220 В
АС і вихідною DC в 5 В і струмом в 1 А.
На рисунку 2.12 показано блок живлення 220-5V-1A.
Рисунок 2.12 – Блок живлення 220-5V-1A
Отже, було визначено з типом мікроконтролера Arduino, який буде
використовуватися в автоматизованій системі, а саме Arduino Nano ATmega
328P. Також вибрали всю елементну базу нашого пристрою, якими
контролер і буде керувати.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 23
3 ПРОЕКТУВАННЯ ТА ОПИС РОБОТИ АВТОМАТИЗОВАНОЇ
СИСТЕМИ
Потрібно перевірити мікроконтролер, описати роботу пристрою, а
також скласти схеми: електричну схему, вона буде показувати пристрій в
розрізі; монтажну схему, вона буде відображати всі з’єднання; схему
структури меню, яка буде показувати всі елементи меню.
3.1 Опис роботи автоматизованої системи для управління
теплицею на основі платформи Arduino
Відійшовши від нашої концепції дешевизни, був використаний
найточніший в лінійці Arduino цифровий барометр BME280. Для показників
температури грунту був використаний теж цифровий датчик DS18B20 з чуть
гіршими характеристиками, але повністю у вологозахищеному корпусі, що
цілком відповідає нашим вимогам. Виміряти вологість був вибраний
аналоговий датчик FC-28. Датчик освітленості вибраний теж аналоговий на
основі світлорезистора. Контроль і час виконання функцій ми поклали на
непоганий модуль реального часу DS3231, з мінімальними похибками в часі.
Сервоприводи SG-90 вибрані тільки як демонстраційні і для перевірки
працездатності системи. Модуль проміжних реле взятих з запасом в 8 реле,
на 6 реле шилдів Arduino немає. Енкодер ЕС11 вибраний разом з
вмонтованою кнопкою, що полегшує і спрощує налаштування і робить це
зручним і простим. Контролер цієї системи ATmega328P вибраний з лінійки
найдешевших Arduino. Дисплей LCD 2004 з CPI інтерфейсом, теж по
принципу ціна-якість, плата перетворювача до нього з SPI в І2С на
мікросхемі PCF8574T особливостей крім регулювання яркості немає.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 24
3.2 Перевірка мікроконтролера та компонентів автоматизованої
системи
Перевірка мікроконтролера Arduino Nano полягала в підключенні до
персонального комп’ютера і завантаженні тестової прошивки через
середовище Arduino. Тестова прошивка полягає в періодичному миганні
світлодіода, розташованого на платах і пов’язаного з 13-м піном контролера.
Ця прошивка дозволяє перевірити зв’язок контролера з персональним
комп’ютером, працездатність вбудованої пам’яті, виконання простих завдань
у вигляді встановлення високого рівня на будь-якому з пінів
мікроконтролера. При перевірці вибраного для системи контролера, була
визначення повна працездатність, що дозволяло в подальшому
використовувати їх для побудови системи управління.
Кожен датчик системи перевіряється за своїм власним алгоритмом.
Перевірка цифрового барометра BME280 полягала в підключенні до
мікроконтролеру.
На рисунку 3.1 наведено схему підключення елементів.
Рисунок 3.1 – Підключення компонентів для перевірки датчика температури
та вологості
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 25
При перевірці датчика не було виявлено проблем, була перевірена
працездатність, що дозволяло в подальшому використовувати датчик в
побудові системи стабілізації температури.
На рисунку 3.2 наведено підключення цифрового термометра DS18B20.
Рисунок 3.2 – Підключення цифрового термометра DS18B20
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 26
Перевірка датчика температури завершена успішно, що дозволяє в
подальшому використовувати його для побудови системи керування
температурою.
На рисунку 3.3 наведено підключення компонентів для перевірки
спрацьовувань оптопар і реле.
Рисунок 3.3 – Підключення компонентів для перевірки спрацьовувань
оптопар і реле
Перевірка проміжних реле також оптопар завершена успішно, що
дозволяє в подальшому використовувати його для комутації силових
елементів.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 27
На рисунку 3.4 наведено схему підключення компонентів для перевірки
екрану.
Рисунок 3.4 – Підключення компонентів для перевірки екрану
Для спрощення виводу інформації на екран було прийняте рішення
використати плату перетворювача з SPI в I2C так як у нас вже на цій шині є
обмін з датчиками BME280,DS18b20,DS3231 та енкодером. Перевірка LCD
екрану виявила такий дефект, як неповне засвітлювання знаків індикатора та
відсутність можливості виводити на екран символи в кирилиці .Так як дефект
неповного засвітлювання виправлений за допомогою розбирання і
протирання струмопровідної гуми спиртовим розчином , а ось з кирилицею
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 28
проблема не вирішена , підібрати бібліотеку так і не вийшло , через це всі
написи на екрані будуть виводитись на латиниці.
На рисунку 3.5 наведено схему підключення цифрового годинника
реального часу DS3231.
Рисунок 3.5 – Підключення цифрового годинника реального часу DS3231
Перевірка модуля реального часу не виявила розбіжностей від
заявлених в даташиті. Маючи своє живлення він найкраще підійшов під наш
проект розумної теплиці.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 29
На рисунку 3.6 наведено схему підключення компонентів для перевірки
сервоприводів SG-90.
Рисунок 3.6 – Підключення компонентів для перевірки сервоприводів SG-90
Перевірка модуля сервоприводів показала, що демо версія підійде для
перевірки працездатності нашого проекту, а в реальності потребує заміни на
більш потужний.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 30
На рисунку 3.7 наведено схему підключення інкрементного енкодера
EC11.
Рисунок 3.7 – підключення інкрементного енкодера EC11
Перевірка та робота модуля енкодера підтвердила його якість і опис
даташиту, брязкіт контактів відсутній, а вмонтована кнопка є зручною в
керуванні.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 31
3.3 Проектування автоматизованої системи для управління
теплицею на основі платформи Arduino
Перевіривши всі елементи складаємо монтажну схему.
На рисунку 3.8 наведено схему монтажних з’єднань.
Рисунок 3.8 – Монтажна схема з’єднань
Для нашого проекту розумної теплиці реалізуємо такий алгоритм
роботи: вимірювання внутрішньої температури та вологості приміщення та її
підтримання в заданих параметрах; вимірювання температури грунту та його
вологості та підтримання їх в заданих параметрах; вмикання клапану подачі
води з прив’язкою до вологості грунту; автоматичне ввімкнення
допоміжного освітлення при низькому природному або по таймеру; показник
реального часу, дня, року; відкривання вікон провітрювання за заданим
часом.
Маючи шилди Arduino визначаємо підключення пінів мікроконтролера
ATmega328 на роботу в потрібних нам режимах.
Складаємо електричну схему автоматизованої системи вигляд для якої
наведено на рисунку 3.9.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 32
Рисунок 3.9 – Електрична схема автоматизованої системи
3.4 Принцип роботи автоматизованої системи
При подачі живлення 5 В на екрані дисплею можна спостерігати такі
показники, як вологість та температуру в приміщенні, реальний час, роботу
проміжних реле, стан виміру трьох аналогових та одного з цифрових
датчиків та сервоприводів. Натиснувши короткочасно кнопку енкодеру і
повернувши його по часовій стрілці ми попадем в налаштування наших
виходів, яким можна присвоїти спрацювання від датчиків або від таймера
реального часу. Потім іде налаштування роботи сервопривода. Повернувши
енкодер в ліво, ми опинимося в меню налаштувань де можна виставить
вимкнення дисплею, час вимкнення та налаштування годинника реального
часу. Ще один поворот енкодера вліво дає змогу попасти в меню сервісу де
можна спостерігати на спрацювання проміжних реле та включати їх
самостійно для перевірки працездатності кінцевих пристроїв.
На рисунку 3.10 наведено структурну схему меню для реалізованої
автоматизованої системи.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 33
Рисунок 3.10 – Структурна схема меню для реалізованої автоматизованої
системи
Структура меню допомагає нам керувати нашим приладом. За
допомогою енкодеру можна переходити в різні розділи, щоб контролювати
або налаштувати систему, на певні параметри.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 34
ВИСНОВКИ
В досліджуваній кваліфікаційній роботі бакалавра було проведено
дослідження сфери автоматизації систем по контролю мікроклімату в
теплицях. Зокрема, предметом наукових досліджень була сфера
автоматизованої системи на базі Arduino.
Досліджуючи наявні методи впроваджень таких систем автоматизації
були виявлені як їх переваги, так і основні недоліки. Метою даної роботи
було створити автоматизовану систему для контролю і догляду в теплицях.
В першому розділі роботи було виявлено напрямок роботи, а саме:
проведено загальний огляд на задачу та сформовано перші кроки до її
розробки. Було обрано мікроконтролер, який буде використовуватися, а
також середовище для розробки тексту програми і мова на якій ця програма
буде писатися.
Другий розділ присвячений вибору елементної бази системи, якими і
буде керувати мікроконтролер. Також була складена таблиця порівняння
типів контролерів Arduino і був обраний оптимальний для рішення задачі
гаджет.
Результатом третього розділу став опис послідовної реалізації
розробленого проекту автоматизованої системи. Було проведено ряд
успішних тестів елементної бази, завдяки чому була складена схема з’єднань.
Отже, аналізуючи виконану роботу по створенню автоматизованої
системи для теплиці на базі Arduino було створено пристрій, який не тільки
виконує всі поставлені задачі, а й являється самим бюджетним варіантом
вирішення даної задачі.
ЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 35
ДОДАТОК А
«ЗАТВЕРДЖУЮ»
Завідувач кафедри ІБ та КІ
д.т.н., професор Володимир РУДНИЦЬКИЙ
__________________
«___» _____________ 2023 р.
Автоматизована система для управління теплицею на основі
платформи Arduino
Специфікація
482.ЧДТУ.31934-01
Листів 2
Розробник _______________ Валерій ЯЛАНСЬКИЙ
Керівник _______________ Тетяна МИРОНЮК
Черкаси 2023
2
482.ЧДТУ.31934-01
Позначення Найменування Примітка
Документація
482.ЧДТУ.31934-01 12 01 Текст програми
ДОДАТОК Б
Автоматизована система для управління теплицею
на основі платформи Arduino
Текст програми
482.ЧДТУ.31934-01 12 01
Листів 49
Розробник: Валерій ЯЛАНСЬКИЙ
Черкаси 2023
2
482.ЧДТУ.31934-01 12 01
// -------------------- НАЛАШТУВАННЯ ---------------------
#define ENCODER_TYPE 1 // тип енкодера (0 або 1). Якщо енкодер працює
некорректно , змінити тип
#define ENC_REVERSE 0 // 1 - інвертувать напрямок руху енкодера, 0 - ні
#define CONTROL_TYPE 1 // тип управління енкодера:
// 0 - утримання і поворот для зміни значень
// 1 - нажаття для входу в виміри, повторне нажаття для виходу (стрілочка міняеться на
пташку)
#define DRIVER_LEVEL 1 // 1 или 0 - рівень сигналу на драйвер/реле для приводу
#define SETT_TIMEOUT 100 // таймаут неактивності (сикунд) після якого автоматично
відкриється DEBUG і збережуться налаштування
// ------ серво/реле ------
#define SERVO1_RELAY 0 // 1 - замінить серво 1 на реле. 0 - нічого не робить
#define SERVO2_RELAY 0 // 1 - замінить серво 2 на реле. 0 - нічого не робить
// цифровий датчик температури ds18b20
#define DALLAS_SENS1 0 // 1 - до входу SENS1 підключений ds18b20, 0 - підключений
звичайний аналоговий датчик
// ------ термістори ------
#define THERM1 0 // 1 - до входу SENS1 підключений термистор, 0 - підключений
другий аналоговий датчик
#define THERM2 0 // 1 - до входу SENS2 підключений термистор, 0 - підключений
другий аналоговий датчик
#define THERM3 0 // 1 - до входу SENS3 підключений термистор, 0 - підключений
другий аналоговий датчик
#define THERM4 0 // 1 - до входу SENS4 підключений термистор, 0 - підключений
другий аналоговий датчик
#define BETA_COEF1 3435 // температурный коефіциєнт термістора 1 (див. даташит)
#define BETA_COEF2 3435 // температурный коефіцієнт термістора 2 (див. даташит)
#define BETA_COEF3 3435 // температурный коефіцієнт термістора 3 (див. даташит)
#define BETA_COEF4 3435 // температурный коефіцієнт термістора 4 (див. даташит)
// ------ адреса ------
#define LCD_ADDR 0x3f // адреса дисплею - 0x27 або 0x3f
#define BME_ADDR 0x76 // адрес BME280 - 0x76 або 0x77
// ----------------- НАЗВА КАНАЛІВ ------------------
C onst char *channelNames[] = {
"Channel 1",
"Channel 2",
"Channel 3",
"Channel 4",
"Channel 5",
"Channel 6",
"Channel 7",
3
482.ЧДТУ.31934-01 12 01
"Servo 1",
"Servo 2",
"Drive",
};
// -------------------- ПІНИ ---------------------
#define SW 0
#define RELAY_0 1
#define DT 2
#define CLK 3
#define RELAY_1 4
#define RELAY_2 5
#define RELAY_3 6
#define RELAY_4 7
#define RELAY_5 8
#define RELAY_6 9
#define DRV_SIGNAL1 10
#define DRV_PWM 11
#define DRV_SIGNAL2 12
#define SERVO_0 17
#define SERVO_1 A0
#define SENS_VCC A1
#define SENS_1 A2
#define SENS_2 A3
#define SENS_3 A6
#define SENS_4 A7
// -------------------- БІБЛИОТЕКИ ---------------------
#include "encMinim.h"
encMinim enc(CLK, DT, SW, ENC_REVERSE, ENCODER_TYPE);
#include <ServoSmooth.h>
ServoSmooth servo1;
ServoSmooth servo2;
//#include "LCD_2004_EN"
//LCD_2004_EN lcd(LCD_ADDR, 20, 4);
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(LCD_ADDR, 20, 4);
#include <EEPROM.h>
#include "RTClib.h"
RTC_DS3231 rtc;
DateTime now;
// adafr
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
4
482.ЧДТУ.31934-01 12 01
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme;
#if (DALLAS_SENS1 == 1)
#include <OneWire.h> // бібліотека протоколів датчиків
#include <DallasTemperature.h> // бібліотека датчика
OneWire oneWire(SENS_1);
DallasTemperature sensors(&oneWire);
#endif
// -------------------- ЗМІННІ ---------------------
struct {
boolean backlight = 1; // автовимкнення подсвітки дисплею по таймауту бездіяльності
(1 - розрішити)
byte backlTime = 60; // таймаут вимкнення дисплею, сикунди
byte drvSpeed = 255; // швидкість привода, 0-255
byte srv1_Speed = 40; // макс. швидкість серво 1, 0-255
byte srv2_Speed = 40; // макс. швидкість серво 2, 0-255
float srv1_Acc = 0.2; // прискорення серво 1, 0.0-1.0
float srv2_Acc = 0.2; // прискорення серво 2, 0.0-1.0
int16_t comSensPeriod = 10;
int8_t plotMode = 0;
} settings;
struct channelsStruct {
boolean state = 0; // стан каналу (вкл/викл)
boolean direction = 0; // напрям роботи
int8_t hour1 = 0; // час включення
int8_t hour2 = 1; // час виключення
int8_t sensor; // тип датчика (air temp, air hum, mois1...)
int8_t relayType; // тип реле (помпа, клапан, реле)
int8_t mode; // режим работи (таймер, rtc, сутки, датчик)
int8_t startHour = 0; // начальний час для таймера RTC
int8_t impulsePrd = 1; // період импульса
int16_t threshold = 30; // мін. поріг спрацювання
int16_t thresholdMax = 30; // макс. поріг спрацювання
int16_t sensPeriod = 2; // периіод опитування датчика (секунди)
uint32_t period = 100; // період визову
uint32_t work = 0; // період роботи
};
channelsStruct channels[10];
uint32_t timerMillis[10]; // лічильник мілліс
uint32_t driveTimer;
byte driveState;
boolean lastDriveState;
byte driveTimeout = 5; // таймаут руху приводу
boolean manualControl;
boolean manualPos;
5
482.ЧДТУ.31934-01 12 01
boolean controlState;
byte minAngle[] = {0, 0}; // мін.кут
byte maxAngle[] = {180, 180}; // макс.кут
//byte eeprAdr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 17};
byte impulsePrds[] = {1, 5, 10, 15, 20, 30, 1, 2, 3, 4, 6, 8, 12, 24};
byte relayPins[] = {RELAY_0, RELAY_1, RELAY_2, RELAY_3, RELAY_4, RELAY_5,
RELAY_6};
int sensorVals[6]; // темп. повітря, вологість, грунт1, грунт2, грунт3, грунт4
int8_t realTime[3];
float uptime = 0;
byte servoPos[2];
byte servoPosServ[2];
boolean channelStates[10];
boolean channelStatesServ[10];
int8_t debugPage;
int8_t arrowPos; // 0-3
int8_t navDepth; // 0-2
int8_t currentChannel = 0; // -3 - 14
int8_t currentMode; // 0-3
int8_t thisH[2], thisM[2], thisS[2];
byte currentLine;
uint32_t commonTimer, backlTimer, plotTimer;
int16_t plotTimeout = 5760;
boolean backlState = true;
int tempDay[15], humDay[15];
int sensDay_0[15], sensDay_1[15], sensDay_2[15], sensDay_3[15];
boolean serviceFlag;
boolean timeChanged;
boolean settingsChanged;
uint32_t settingsTimer;
const char *settingsNames[] = {
"Mode",
"Direction",
"Type",
"Mode",
"Direction",
"Limits",
"Mode",
"Direct.",
"Timeout",
};
6
482.ЧДТУ.31934-01 12 01
const char *modeNames[] = {
"Timer",
"Timer RTC",
"Day",
"Sensor",
"PID",
};
const char *relayNames[] = {
"Relay",
"Valve",
"Common",
};
const char *modeSettingsNames[] = {
"Period", // 0
"Work", // 1
"Left", // 2
"Period", // 3
"Work", // 4
"Start from", // 5
"Start", // 6
"Stop", // 7
"Period", // 8
"Sensor", // 9
//"Threshold", // 10
};
const char *sensorNames[] = {
"Air t.",
"Air h.",
"Sens1",
"Sens2",
"Sens3",
"Sens4",
};
const char *directionNames[] = {
"Off-On",
"On-Off",
"Min-Max",
"Max-Min",
"Close-Open",
"Open-Close",
};
7
482.ЧДТУ.31934-01 12 01
// -------------------- SETUP ---------------------
void setup() {
// ----- дисплей -----
lcd.init();
lcd.backlight();
lcd.clear();
// ----- RTC -----
rtc.begin();
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
now = rtc.now();
// ---------- BME ----------
bme.begin(BME_ADDR);
// --------- dallas ---------
#if (DALLAS_SENS1 == 1)
sensors.begin();
sensors.setWaitForConversion(false); // асинхронне отримання даних
#endif
// ----- СКИДАННЯ НАЛАШТУВАНЬ -----
boolean resetSettings = false;
if (!digitalRead(SW)) {
resetSettings = true;
lcd.setCursor(0, 0);
lcd.print(F("Reset settings OK"));
}
while (!digitalRead(SW));
// ----- ПЕРШИЙ ЗАПУСК І СКИДАННЯ -----
if (EEPROM.read(1022) != 50 || resetSettings) {
EEPROM.write(1022, 50);
for (byte i = 0; i < 10; i++) {
EEPROM.put(i * 30, channels[i]);
}
EEPROM.put(300, minAngle[0]);
EEPROM.put(301, minAngle[1]);
EEPROM.put(302, maxAngle[0]);
EEPROM.put(303, maxAngle[1]);
EEPROM.put(304, driveTimeout);
EEPROM.put(310, settings);
}
// ----- ЧИТАННЯ НАЛАШТУВАНЬ -----
for (byte i = 0; i < 10; i++) {
EEPROM.get(i * 30, channels[i]);
8
482.ЧДТУ.31934-01 12 01
}
minAngle[0] = EEPROM.get(300, minAngle[0]);
minAngle[1] = EEPROM.get(301, minAngle[1]);
maxAngle[0] = EEPROM.get(302, maxAngle[0]);
maxAngle[1] = EEPROM.get(303, maxAngle[1]);
driveTimeout = EEPROM.get(304, driveTimeout);
settings = EEPROM.get(310, settings);
switch (settings.plotMode) {
case 0: plotTimeout = 5760;
break;
case 1: plotTimeout = 240;
break;
case 2: plotTimeout = 4;
break;
}
// ----- серво -----
// налаштування серво
channelStates[7] = !channels[7].direction;
channelStates[8] = !channels[8].direction;
#if (SERVO1_RELAY == 0)
servo1.setSpeed(settings.srv1_Speed); // обмежити швидкість
servo1.setAccel(settings.srv1_Acc); // виставить прискорення (прискорення і
гальмування)
if (!channels[7].direction) servoPos[0] = minAngle[0];
else servoPos[0] = maxAngle[0];
// подключення серво
// серви при запуску знають, де вони стоять і не рухаються
servo1.attach(SERVO_0, 600, 2400, servoPos[0]);
// 600 и 2400 - довжина імпульсів, при яких серво крутить від 0 і 180. Вказуєм початковий
кут
if (!channels[7].state) servo1.stop();
servo1.setCurrentDeg(servoPos[0]);
servo1.setTargetDeg(servoPos[0]);
#endif
#if (SERVO2_RELAY == 0)
servo2.setSpeed(settings.srv2_Speed); // обмежити швидкість
servo2.setAccel(settings.srv2_Acc); // встановити прискорення (разгін і гальмування)
if (!channels[8].direction) servoPos[1] = minAngle[1];
else servoPos[1] = maxAngle[1];
servo2.attach(SERVO_1, 600, 2400, servoPos[1]); // однотипно
if (!channels[8].state) servo2.stop();
servo2.setCurrentDeg(servoPos[1]);
servo2.setTargetDeg(servoPos[1]);
#endif
9
482.ЧДТУ.31934-01 12 01
// ----- реле -----
for (byte i = 0; i < 7; i++) {
pinMode(relayPins[i], OUTPUT);
channelStates[i] = !channels[i].direction; // повернути реле на місця
digitalWrite(relayPins[i], channelStates[i]); // повернути реле на місця
}
pinMode(SENS_VCC, OUTPUT);
if (SERVO1_RELAY) {
pinMode(SERVO_0, OUTPUT);
digitalWrite(SERVO_0, channelStates[7]);
}
if (SERVO2_RELAY) {
pinMode(SERVO_1, OUTPUT);
digitalWrite(SERVO_1, channelStates[8]);
}
// привід
// частота на пінах 3 и 11 - 31.4 кГц (вліяє на tone() )
TCCR2A |= _BV(WGM20);
TCCR2B = TCCR2B & 0b11111000 | 0x01;
pinMode(DRV_PWM, OUTPUT);
pinMode(DRV_SIGNAL1, OUTPUT);
pinMode(DRV_SIGNAL2, OUTPUT);
digitalWrite(DRV_SIGNAL1, DRIVER_LEVEL);
digitalWrite(DRV_SIGNAL2, DRIVER_LEVEL);
analogWrite(DRV_PWM, settings.drvSpeed);
lastDriveState = !channels[9].direction;
channelStates[9] = lastDriveState;
manualPos = !lastDriveState;
// преривання на енкодер
if (ENC_REVERSE) attachInterrupt(0, enISR1, CHANGE);
else attachInterrupt(1, enISR2, CHANGE);
// ----- моніторінг -----
currentChannel = -1; // вікно дебаг
currentLine = 4;
drawArrow();
redrawScreen();
/*Serial.begin(9600);
delay(50);
Serial.println("start");*/
}
void enISR1() {
enc.tick();
}
void enISR2() {
enc.tick();
10
482.ЧДТУ.31934-01 12 01
}
// -------------------- LOOP ---------------------
void loop() {
backlTick(); // таймер неактивності підсвітки
debTick(); // таймер неактивності дисплею
controlTick(); // управління
plotTick(); // суточні графіки
readAllSensors(); // опитування датчиків
driveTick(); // робота приводу
if (!SERVO1_RELAY) servo1.tick(); // рух серво по встроєному таймеру!
if (!SERVO2_RELAY) servo2.tick(); // рух серво по встроєному таймеру!
if (currentChannel == -3) { // як що СЕРВІС
serviceTick();
} else { // як що відладка або налаштування
if (millis() - commonTimer > 1000) {
commonTimer = millis();
timersTick();
if (currentChannel == -1) {
if (debugPage == 0) redrawDebug();
else redrawPlot();
}
}
}
}
// -------------------- LOOP ---------------------
// -------------------- SERVICE ---------------------
void serviceTick() {
if (millis() - commonTimer > 1000) {
commonTimer = millis();
// управляем реле
for (byte i = 0; i < 7; i++)
digitalWrite(relayPins[i], channelStatesServ[i]);
if (SERVO1_RELAY) {
digitalWrite(SERVO_0, channelStatesServ[7]);
}
if (SERVO2_RELAY) {
digitalWrite(SERVO_1, channelStatesServ[8]);
}
// управляем серво
if (!SERVO1_RELAY) servo1.setTargetDeg(servoPosServ[0]);
if (!SERVO2_RELAY) servo2.setTargetDeg(servoPosServ[1]);
}
}
11
482.ЧДТУ.31934-01 12 01
void serviceIN() { // виконуємо при вході в сервіс
if (!SERVO1_RELAY) servo1.start();
if (!SERVO2_RELAY) servo2.start();
servoPosServ[0] = servoPos[0];
servoPosServ[1] = servoPos[1];
serviceFlag = true;
for (byte i = 0; i < 10; i++) channelStatesServ[i] = channelStates[i];
realTime[0] = now.hour();
realTime[1] = now.minute();
realTime[2] = now.second();
}
void serviceOUT() { // виконуємо при виході з сервісу
serviceFlag = false;
for (byte i = 0; i < 7; i++) digitalWrite(relayPins[i], channelStates[i]); // повернути реле на
місця
if (SERVO1_RELAY) {
digitalWrite(SERVO_0, channelStates[7]);
}
if (SERVO2_RELAY) {
digitalWrite(SERVO_1, channelStates[8]);
}
// вернуть серво на місце
if (!SERVO1_RELAY && !channels[7].state) servo1.stop();
if (!SERVO2_RELAY && !channels[8].state) servo2.stop();
if (!SERVO1_RELAY) servo1.setTargetDeg(servoPos[0]);
if (!SERVO2_RELAY) servo2.setTargetDeg(servoPos[1]);
// вернуть привід
driveState = 1;
if (timeChanged) {
timeChanged = false;
rtc.adjust(DateTime(2019, 0, 0, realTime[0], realTime[1], realTime[2]));
}
updateSettings();
}
// -------------------- SERVICE ---------------------
void backlTick() {
if (settings.backlight && backlState && millis() - backlTimer >= (long)settings.backlTime *
1000) {
backlState = false;
lcd.noBacklight();
}
}
void debTick() {
12
482.ЧДТУ.31934-01 12 01
if ( (currentChannel >= 0 || currentChannel == -2) && millis() - settingsTimer >
SETT_TIMEOUT * 1000L) { // як що не дебаг і спрацював таймер
settingsTimer = millis();
if (currentChannel == -2) updateSettings();
if (currentChannel >= 0) updateEEPROM(currentChannel);
currentChannel = -1;
navDepth = 0;
arrowPos = 0;
debugPage = 0;
currentLine = 4;
redrawScreen();
}
}
void backlOn() {
backlState = true;
backlTimer = millis();
lcd.backlight();
}
void loadPlot() {
lcd.createChar(0, row8);
lcd.createChar(1, row1);
lcd.createChar(2, row2);
lcd.createChar(3, row3);
lcd.createChar(4, row4);
lcd.createChar(5, row5);
lcd.createChar(6, row6);
lcd.createChar(7, row7);
} void drawArrow() {
if (currentChannel >= 0) {
// ----------------- НАСТРОЙКИ КАНАЛІВ -----------------
space(0, 0);
space(14, 0);
space(0, 1);
space(0, 2);
space(0, 3);
if (navDepth == 0) {
if (currentChannel > 6 && currentChannel < 9 && !navDepth) {
space(10, 3);
space(15, 3);
}
if (arrowPos == 0)
arrow(0, 0);
else if (arrowPos == 1)
arrow(14, 0);
else if (arrowPos == 2)
arrow(0, 1);
13
482.ЧДТУ.31934-01 12 01
else if (arrowPos == 3)
arrow(0, 2);
else if (arrowPos == 4) {
if (currentChannel > 6 && currentChannel < 9 && !navDepth) {
arrow(10, 3);
} else {
arrow(0, 3);
}
}
else if (arrowPos == 5) {
arrow(15, 3);
}
} else {
// ------------- НАСТРОЙКИ РЕЖИМІВ (navDepth == 1) -------------
byte thisMode = channels[currentChannel].mode;
space(0, 0);
space(14, 0);
space(0, 1);
space(0, 2);
space(0, 3);
if (thisMode == 0) {
space(7, 1);
colon(11, 1);
colon(14, 1);
space(8, 2);
colon(11, 2);
colon(14, 2);
}
if (thisMode == 3) {
colon(15, 3);
colon(5, 3);
}
switch (arrowPos) {
case 0: arrow(0, 0);
break;
case 1: arrow(14, 0);
break;
case 2:
if (thisMode == 0) arrow(7, 1);
else arrow(0, 1);
break;
case 3:
if (thisMode == 0) arrow(11, 1);
else arrow(0, 2);
break;
case 4:
if (thisMode == 0) arrow(14, 1);
else if (thisMode == 1) arrow(0, 3);
14
482.ЧДТУ.31934-01 12 01
else if (thisMode == 3) arrow(5, 3);
break;
case 5:
if (thisMode == 0) arrow(8, 2);
else if (thisMode == 3) arrow(15, 3);
break;
case 6:
if (thisMode == 0) arrow(11, 2);
break;
case 7:
if (thisMode == 0) arrow(14, 2);
break;
}
}
} else if (currentChannel == -1) {
arrow(0, 0);
} else if (currentChannel == -2) {
// ------------------ НАЛАШТУВАННЯ -------------------
colon(16, 0); colon(8, 1); colon(16, 1); colon(5, 2);
colon(16, 2); colon(5, 3); colon(16, 3); space(0, 0);
switch (arrowPos) {
case 0: arrow(0, 0);
break;
case 1: arrow(16, 0);
break;
case 2: arrow(8, 1);
break;
case 3: arrow(16, 1);
break;
case 4: arrow(5, 2);
break;
case 5: arrow(16, 2);
break;
case 6: arrow(5, 3);
break;
case 7: arrow(16, 3);
break;
}
} else if (currentChannel == -3) {
// ------------------ SERVICE -------------------
space(0, 0); space(11, 0); colon(9, 3);
colon(14, 0); colon(17, 0); space(0, 2);
space(2, 2); space(4, 2); space(6, 2);
space(8, 2); space(10, 2); space(12, 2);
colon(16, 1); colon(16, 2); colon(1, 3);
colon(16, 3);
//currentLine = 4;
switch (arrowPos) {
case 0: arrow(0, 0);
15
482.ЧДТУ.31934-01 12 01
break;
case 1: arrow(11, 0);
break;
case 2: arrow(14, 0);
break;
case 3: arrow(17, 0); // реле 1
break;
case 4: arrow(0, 2);
break;
case 5: arrow(2, 2);
break;
case 6: arrow(4, 2);
break;
case 7: arrow(6, 2);
break;
case 8: arrow(8, 2);
break;
case 9: arrow(10, 2);
break;
case 10: arrow(12, 2); // реле 6
break;
case 11: arrow(16, 1);
break;
case 12: arrow(16, 2);
break;
case 13: arrow(1, 3);
break;
case 14: arrow(9, 3);
break;
case 15: arrow(16, 3);
break;
}
}
}
void arrow(byte col, byte row) {
lcd.setCursor(col, row);
if (!controlState)
lcd.write(126);
else
lcd.write(62);
}
void space(byte col, byte row) {
lcd.setCursor(col, row);
lcd.print(F(" "));
}
void colon(byte col, byte row) {
lcd.setCursor(col, row);
lcd.print(F(":"));
}
16
482.ЧДТУ.31934-01 12 01
// заповння масиву значень з датчиків
// тут можна прописать інший татчик!
void getAllData() {
sensorVals[0] = floor(bme.readTemperature());
sensorVals[1] = floor(bme.readHumidity());
sensorVals[2] = analogRead(SENS_1);
sensorVals[3] = analogRead(SENS_2);
sensorVals[4] = analogRead(SENS_3);
sensorVals[5] = analogRead(SENS_4);
#if (DALLAS_SENS1 == 1)
sensorVals[2] = sensors.getTempCByIndex(0);
sensors.requestTemperatures();
#endif
#if (THERM1 == 1)
sensorVals[2] = getThermTemp(analogRead(SENS_1), BETA_COEF1);
#endif
#if (THERM2 == 1)
sensorVals[3] = getThermTemp(analogRead(SENS_2), BETA_COEF2);
#endif
#if (THERM3 == 1)
sensorVals[4] = getThermTemp(analogRead(SENS_3), BETA_COEF3);
#endif
#if (THERM4 == 1)
sensorVals[5] = getThermTemp(analogRead(SENS_4), BETA_COEF4);
#endif
}
#if (THERM1 == 1 || THERM2 == 1 || THERM3 == 1 || THERM4 == 1)
#define RESIST_BASE 10000 // опір при TEMP_BASE градусах по Цельсію (Ом)
#define TEMP_BASE 25 // температура, при якій виміряно RESIST_BASE (градусів
Цельсія)
#define RESIST_10K 10000 // точний опір 10к резистора (Ом)
float getThermTemp(int resistance, int B_COEF) {
float thermistor;
thermistor = RESIST_10K / ((float)1023 / resistance - 1);
thermistor /= RESIST_BASE; // (R/Ro)
thermistor = log(thermistor) / B_COEF; // 1/B * ln(R/Ro)
thermistor += (float)1.0 / (TEMP_BASE + 273.15); // + (1/To)
thermistor = (float)1.0 / thermistor - 273.15; // інвертуєм і конвертуєм в градуси по
Цельсію
return thermistor;
}
#endif
void timersTick() { // кожну секунду
// отримуємо час
now = rtc.now();
17
482.ЧДТУ.31934-01 12 01
realTime[0] = now.hour();
realTime[1] = now.minute();
realTime[2] = now.second();
uptime += (float)0.0000115741; // аптайм у нас в сутках! 1/(24*60*60)
for (byte curChannel = 0; curChannel < 10; curChannel++) {
if (channels[curChannel].state // якщо канал активний (on/off)
&& (curChannel >= 7 || (curChannel < 7 && channels[curChannel].relayType != 2) ) ) { //
для всих окрім СПІЛЬНИХ реле
switch (channels[curChannel].mode) {
case 0: // ---------------------- якщо таймер ----------------------
if (millis() - timerMillis[curChannel] >= channels[curChannel].period * 1000
&& channelStates[curChannel] != channels[curChannel].direction) {
timerMillis[curChannel] = millis();
channelStates[curChannel] = channels[curChannel].direction;
}
if (millis() - timerMillis[curChannel] >= channels[curChannel].work * 1000
&& channelStates[curChannel] == channels[curChannel].direction) {
channelStates[curChannel] = !channels[curChannel].direction;
}
break;
case 1: // ---------------------- якщо таймер RTC ----------------------
if (realTime[2] == 0 || realTime[2] == 1) { // перевірка кожну
хвилину(в перві дві секунди, на всяк випадок)!
if (channels[curChannel].impulsePrd < 6) { // якщо хвилинні періоди
if (channelStates[curChannel] != channels[curChannel].direction) { // якщо канал
ВИМКНЕНИЙ
byte waterTime = 0; // початок перевірки часу з 0
хвилини
for (byte j = 0; j < 60 / impulsePrds[channels[curChannel].impulsePrd]; j++) {
if (realTime[1] == waterTime) {
channelStates[curChannel] = channels[curChannel].direction; // ВКЛЮЧАЄМ
timerMillis[curChannel] = millis(); // взводим таймер
}
waterTime += impulsePrds[channels[curChannel].impulsePrd];
}
}
} else { // якщо часові періоди
if (realTime[1] == 0) { // перевірка в перші хвилини
часу!
if (channelStates[curChannel] != channels[curChannel].direction) { // якщо
ВИМКНЕНИЙ
byte waterTime = channels[curChannel].startHour; // початок перевірки
часу з старого часу
for (byte j = 0; j < 24 / impulsePrds[channels[curChannel].impulsePrd]; j++) {
if (waterTime < 24) {
if (realTime[0] == waterTime) {
channelStates[curChannel] = channels[curChannel].direction; // ВМИКАЄМ
timerMillis[curChannel] = millis(); // взводим таймер
}
18
482.ЧДТУ.31934-01 12 01
} else {
if (realTime[0] == waterTime - 24) {
channelStates[curChannel] = channels[curChannel].direction; // ВМИКАЄМ
timerMillis[curChannel] = millis(); // взводим таймер
}
}
waterTime += impulsePrds[channels[curChannel].impulsePrd];
}
}
}
}
}
if (channelStates[curChannel] == channels[curChannel].direction // якщо
ВКЛЮЧЕНИЙ
&& millis() - timerMillis[curChannel] >= channels[curChannel].work * 1000) { // І
відпрацював свій час
channelStates[curChannel] = !channels[curChannel].direction; //
ВИМИКАЄМ
}
break;
case 2: // ---------------------- якщо сутки ----------------------
if (channelStates[curChannel] != channels[curChannel].direction
&& (realTime[0] >= channels[curChannel].hour1 && realTime[0] <
channels[curChannel].hour2) ) {
channelStates[curChannel] = channels[curChannel].direction;
} else if (channelStates[curChannel] == channels[curChannel].direction
&& (realTime[0] < channels[curChannel].hour1 || realTime[0] >=
channels[curChannel].hour2)) {
channelStates[curChannel] = !channels[curChannel].direction;
}
break;
case 3: // ---------------------- якщо датчик ----------------------
if (millis() - timerMillis[curChannel] >= channels[curChannel].sensPeriod * 1000L) {
timerMillis[curChannel] = millis();
// більше максимума - включить
if (sensorVals[channels[curChannel].sensor] > channels[curChannel].thresholdMax)
channelStates[curChannel] = channels[curChannel].direction;
// меньше мінімума - виключить
if (sensorVals[channels[curChannel].sensor] < channels[curChannel].threshold)
channelStates[curChannel] = !channels[curChannel].direction;
/*
// без гистерезиса
if (sensorVals[channels[curChannel].sensor] > channels[curChannel].threshold)
channelStates[curChannel] = channels[curChannel].direction;
else
channelStates[curChannel] = !channels[curChannel].direction;
19
482.ЧДТУ.31934-01 12 01
*/
}
break;
}
}
}
// --- приміняєм ---
// реле
boolean atLeastOneValve = false; // флаг "хотя би одного" клапана
for (byte relay = 0; relay < 7; relay++) {
if (channels[relay].relayType == 1 // якщо реле - КЛАПАН
&& channelStates[relay] == channels[relay].direction) // і він ВКЛЮЧЕНИЙ
atLeastOneValve = true; // запоминаєм, що потрібно включить
общий канал
digitalWrite(relayPins[relay], channelStates[relay]); // включаєм/виключаєм всі реле
типів РЕЛЕ і КЛАПАН
}
for (byte relay = 0; relay < 7; relay++) {
if (channels[relay].relayType == 2) { // тільки для ОБЩИХ каналів реле
if (atLeastOneValve) channelStates[relay] = channels[relay].direction; // включить общий
else channelStates[relay] = !channels[relay].direction; // виключить общий
digitalWrite(relayPins[relay], channelStates[relay]); // примінить
}
}
// серво
if (SERVO1_RELAY) { // якщо реле
digitalWrite(SERVO_0, channelStates[7]);
} else {
if (channelStates[7]) servoPos[0] = minAngle[0];
else servoPos[0] = maxAngle[0];
if (channels[7].state) servo1.setTargetDeg(servoPos[0]);
}
if (SERVO2_RELAY) { // якщо реле
digitalWrite(SERVO_1, channelStates[8]);
} else {
if (channelStates[8]) servoPos[1] = minAngle[1];
else servoPos[1] = maxAngle[1];
if (channels[8].state) servo2.setTargetDeg(servoPos[1]);
}
// привод
if (lastDriveState != channelStates[9]) {
lastDriveState = channelStates[9];
driveState = 1;
}
20
482.ЧДТУ.31934-01 12 01
}
void driveTick() {
if (channels[9].state || serviceFlag || manualControl) {
if (driveState == 1) {
driveState = 2;
driveTimer = millis();
boolean thisDirection;
thisDirection = channelStates[9];
if (serviceFlag) thisDirection = channelStatesServ[9];
if (manualControl) thisDirection = manualPos;
if (thisDirection) {
digitalWrite(DRV_SIGNAL1, !DRIVER_LEVEL);
digitalWrite(DRV_SIGNAL2, DRIVER_LEVEL);
} else {
digitalWrite(DRV_SIGNAL1, DRIVER_LEVEL);
digitalWrite(DRV_SIGNAL2, !DRIVER_LEVEL);
}
}
if (driveState == 2 && millis() - driveTimer >= ((long)driveTimeout * 1000)) {
driveState = 0;
digitalWrite(DRV_SIGNAL1, DRIVER_LEVEL);
digitalWrite(DRV_SIGNAL2, DRIVER_LEVEL);
manualControl = false;
}
}
}
uint32_t sensorTimer;
uint32_t period;
byte sensorMode = 0;
void readAllSensors() {
if (millis() - sensorTimer >= period) {
sensorTimer = millis();
switch (sensorMode) {
case 0: // вкл живлення
sensorMode = 1;
period = 100;
digitalWrite(SENS_VCC, 1);
break;
case 1: // вимірюєм
sensorMode = 2;
period = 25;
//float temp(NAN), hum(NAN), pres(NAN);
//bme.read(pres, temp, hum, BME280::TempUnit_Celsius, BME280::PresUnit_Pa);
//double temperature, pressure;
//double humidity = bme.readHumidity(temperature, pressure);
// (air temp, air hum, mois1...)
getAllData();
21
482.ЧДТУ.31934-01 12 01
break;
case 2: // виключаєм
sensorMode = 0;
period = (long)settings.comSensPeriod * 1000;
digitalWrite(SENS_VCC, 0);
break;
}
}
}
/*
24 часа - 1,6 часа - 5760 000
1 час - 4 минути - 240 000
1 минута - 4 секунди - 4 000
*/
void plotTick() {
if (millis() - plotTimer >= plotTimeout * 1000L) {
plotTimer = millis();
// сдвигаем массивы
for (byte i = 0; i < 14; i++) {
tempDay[i] = tempDay[i + 1];
humDay[i] = humDay[i + 1];
sensDay_0[i] = sensDay_0[i + 1];
sensDay_1[i] = sensDay_1[i + 1];
sensDay_2[i] = sensDay_2[i + 1];
sensDay_3[i] = sensDay_3[i + 1];
}
// обновляем крайний элемент
tempDay[14] = sensorVals[0];
humDay[14] = sensorVals[1];
sensDay_0[14] = sensorVals[2];
sensDay_1[14] = sensorVals[3];
sensDay_2[14] = sensorVals[4];
sensDay_3[14] = sensorVals[5];
}
}
void updateEEPROM(byte channel) {
EEPROM.put(channel * 30, channels[channel]);
updByte(300, minAngle[0]);
updByte(301, minAngle[1]);
updByte(302, maxAngle[0]);
updByte(303, maxAngle[1]);
updByte(304, driveTimeout);
}
void updateSettings() {
EEPROM.put(310, settings);
settingsChanged = false;
22
482.ЧДТУ.31934-01 12 01
}
void updByte(int addr, byte val) {
byte buf = EEPROM.get(addr, buf);
if (val != buf) EEPROM.put(addr, val);
}
// мині-клас для работи з енкодером, версия 1.0
class encMinim
{
public:
encMinim(uint8_t clk, uint8_t dt, uint8_t sw, boolean dir, boolean type);
void tick();
boolean isClick();
boolean isHolded();
boolean isTurn();
boolean isRight();
boolean isLeft();
boolean isRightH();
boolean isLeftH();
private:
byte _clk, _dt, _sw;
boolean _type = false;
boolean _state, _lastState, _turnFlag, _swState, _swFlag, _turnState, _holdFlag;
byte _encState;
uint32_t _debTimer;
// 0 - нічого, 1 - ліво, 2 - право, 3 - правоНажатий, 4 - лівоНажатий, 5 - клік, 6 -
утримування
};
encMinim::encMinim(uint8_t clk, uint8_t dt, uint8_t sw, boolean dir, boolean type) {
if (dir) {
_clk = clk;
_dt = dt;
} else {
_clk = dt;
_dt = clk;
}
_sw = sw;
_type = type;
pinMode (_clk, INPUT);
pinMode (_dt, INPUT);
pinMode (_sw, INPUT_PULLUP);
_lastState = digitalRead(_clk);
}
void encMinim::tick() {
_encState = 0;
23
482.ЧДТУ.31934-01 12 01
_state = digitalRead(_clk);
_swState = digitalRead(_sw);
if (_state != _lastState) {
_turnState = true;
_turnFlag = !_turnFlag;
if (_turnFlag || !_type) {
if (digitalRead(_dt) != _lastState) {
if (_swState) _encState = 1;
else _encState = 3;
} else {
if (_swState) _encState = 2;
else _encState = 4;
}
}
_lastState = _state;
_debTimer = millis();
}
if (!_swState && !_swFlag && millis() - _debTimer > 80) {
_debTimer = millis();
_swFlag = true;
_turnState = false;
_holdFlag = false;
}
if (!_swState && _swFlag && !_holdFlag) {
if (_encState != 0 && millis() - _debTimer < 2000) {
_holdFlag = true;
}
if (_encState == 0 && millis() - _debTimer > 2000) {
_encState = 6;
_holdFlag = true;
}
}
if (_swState && _swFlag && _holdFlag) {
_debTimer = millis();
_swFlag = false;
}
if (_swState && _swFlag && !_holdFlag && millis() - _debTimer > 80) {
_debTimer = millis();
_swFlag = false;
if (!_turnState) _encState = 5;
}
}
boolean encMinim::isTurn() {
if (_encState > 0 && _encState < 5) {
24
482.ЧДТУ.31934-01 12 01
return true;
} else return false;
}
boolean encMinim::isRight() {
if (_encState == 1) {
_encState = 0;
return true;
} else return false;
}
boolean encMinim::isLeft() {
if (_encState == 2) {
_encState = 0;
return true;
} else return false;
}
boolean encMinim::isRightH() {
if (_encState == 3) {
_encState = 0;
return true;
} else return false;
}
boolean encMinim::isLeftH() {
if (_encState == 4) {
_encState = 0;
return true;
} else return false;
}
boolean encMinim::isClick() {
if (_encState == 5) {
_encState = 0;
return true;
} else return false;
}
boolean encMinim::isHolded() {
if (_encState == 6) {
_encState = 0;
return true;
} else return false;
}
switch (arrowPos) {
case 0:
if (currentChannel >= 0) updateEEPROM(currentChannel);
if (++currentChannel > 9) currentChannel = 9;
if (serviceFlag && currentChannel > -3) serviceOUT();
currentLine = 4;
break;
case 1: channels[currentChannel].state = true;
if (currentChannel == 7)
if (!SERVO1_RELAY) servo1.attach(SERVO_0, 600, 2400);
if (currentChannel == 8)
25
482.ЧДТУ.31934-01 12 01
if (!SERVO2_RELAY) servo2.attach(SERVO_1, 600, 2400);
currentLine = 4;
break;
case 2: currentLine = 1;
break;
case 3: channels[currentChannel].direction = false;
currentLine = 2;
break;
case 4:
if (curMode == 0) {
if (++channels[currentChannel].relayType > 2) channels[currentChannel].relayType = 2;
currentLine = 4;
} else if (curMode == 1) { // серво
if (currentChannel == 7) {
minAngle[0] += 10;
if (minAngle[0] > 180) minAngle[0] = 180;
} else if (currentChannel == 8) {
minAngle[1] += 10;
if (minAngle[1] > 180) minAngle[1] = 180;
}
currentLine = 3;
} else {
driveTimeout += 1;
if (driveTimeout > 254) driveTimeout = 255;
currentLine = 3;
}
break;
case 5:
if (curMode == 1) {
if (currentChannel == 7) {
maxAngle[0] += 10;
if (maxAngle[0] > 180) maxAngle[0] = 180;
} else if (currentChannel == 8) {
maxAngle[1] += 10;
if (maxAngle[1] > 180) maxAngle[1] = 180;
}
}
currentLine = 3;
break;
}
}
// міняєм при повороті вправо, настройки режимів
void rightHdepth1() {
switch (arrowPos) {
// режими (timer, day..)
case 0:
if (++channels[currentChannel].mode > 3) channels[currentChannel].mode = 3;
currentLine = 4;
break;
26
482.ЧДТУ.31934-01 12 01
case 1: currentLine = 0;
currentLine = 4;
break;
case 2:
if (thisMode == 0) { // період
thisH[0]++;
if (thisH[0] > 999) thisH[0] = 999;
} else if (thisMode == 1) { // импульс
channels[currentChannel].impulsePrd++;
if (channels[currentChannel].impulsePrd > 13) channels[currentChannel].impulsePrd = 13;
} else if (thisMode == 2) { // сутки
if (++channels[currentChannel].hour1 > 23) channels[currentChannel].hour1 = 23;
} else { // датчик
if (channels[currentChannel].sensPeriod < 60) {
channels[currentChannel].sensPeriod += 2;
} else {
channels[currentChannel].sensPeriod += 60;
}
}
currentLine = 1;
break;
case 3:
if (thisMode == 0) { // період
thisM[0]++;
currentLine = 1;
} else if (thisMode == 1) { // імпульс
channels[currentChannel].work++;
if (channels[currentChannel].work > 100) channels[currentChannel].work = 100;
currentLine = 2;
} else if (thisMode == 2) { // сутки
if (++channels[currentChannel].hour2 > 23) channels[currentChannel].hour2 = 23;
currentLine = 2;
} else { // датчик
if (++channels[currentChannel].sensor > 5) channels[currentChannel].sensor = 5;
currentLine = 2;
}
break;
case 4:
if (thisMode == 0) { // період
thisS[0]++;
currentLine = 1;
} else if (thisMode == 1) {
if (++channels[currentChannel].startHour > 23) channels[currentChannel].startHour = 23;
currentLine = 3;
} else if (thisMode == 3) {
if (channels[currentChannel].threshold >= 50)
channels[currentChannel].threshold += 10;
else
channels[currentChannel].threshold++;
if (channels[currentChannel].threshold > 1023) channels[currentChannel].threshold = 1023;
27
482.ЧДТУ.31934-01 12 01
currentLine = 3;
}
break;
case 5:
if (thisMode == 0) { // період
thisH[1]++;
currentLine = 2;
} else if (thisMode == 3) {
if (channels[currentChannel].thresholdMax >= 50)
channels[currentChannel].thresholdMax += 10;
else
channels[currentChannel].thresholdMax++;
if (channels[currentChannel].thresholdMax > 1023)
channels[currentChannel].thresholdMax = 1023;
currentLine = 3;
}
break;
case 6:
if (thisMode == 0) { // період
thisM[1]++;
currentLine = 2;
}
break;
case 7:
if (thisMode == 0) { // період
thisS[1]++;
currentLine = 2;
}
break;
}
}
// міняєм при повороті вліво, настройки каналів
void leftHdepth0() {
switch (arrowPos) {
case 0:
if (currentChannel >= 0) updateEEPROM(currentChannel);
if (--currentChannel < -3) currentChannel = -3;
if (!serviceFlag && currentChannel == -3) serviceIN();
if (currentChannel == 7)
servo1.detach();
if (currentChannel == 8)
servo2.detach();
currentLine = 4;
break;
case 1: channels[currentChannel].state = false;
currentLine = 4;
break;
case 2:
currentLine = 1;
28
482.ЧДТУ.31934-01 12 01
break;
case 3: channels[currentChannel].direction = true;
currentLine = 2;
break;
case 4:
if (curMode == 0) {
if (--channels[currentChannel].relayType < 0) channels[currentChannel].relayType = 0;
currentLine = 4;
} else if (curMode == 1) { // серво
if (currentChannel == 7) {
minAngle[0] -= 10;
if (minAngle[0] > 180) minAngle[0] = 0;
} else if (currentChannel == 8) {
minAngle[1] -= 10;
if (minAngle[1] > 180) minAngle[1] = 0;
}
currentLine = 3;
} else {
driveTimeout -= 1;
if (driveTimeout < 1) driveTimeout = 1;
currentLine = 3;
}
break;
case 5:
if (curMode == 1) {
if (currentChannel == 7) {
maxAngle[0] -= 10;
if (maxAngle[0] > 180) maxAngle[0] = 0;
} else if (currentChannel == 8) {
maxAngle[1] -= 10;
if (maxAngle[1] > 180) maxAngle[1] = 0;
}
}
currentLine = 3;
break;
}
}
// міняєм при повороті вліво, настройки режимів
void leftHdepth1() {
switch (arrowPos) {
case 0: if (--channels[currentChannel].mode < 0) channels[currentChannel].mode = 0;
currentLine = 4;
break;
case 1: currentLine = 4;
break;
case 2:
if (thisMode == 0) { // період
thisH[0]--;
} else if (thisMode == 1) { // импульс
29
482.ЧДТУ.31934-01 12 01
if (--channels[currentChannel].impulsePrd < 0) channels[currentChannel].impulsePrd = 0;
} else if (thisMode == 2) { // сутки
if (--channels[currentChannel].hour1 < 0) channels[currentChannel].hour1 = 0;
} else { // датчик
if (channels[currentChannel].sensPeriod < 60) {
channels[currentChannel].sensPeriod -= 2;
} else {
channels[currentChannel].sensPeriod -= 60;
}
if (channels[currentChannel].sensPeriod < 2) channels[currentChannel].sensPeriod = 2;
}
currentLine = 1;
break;
case 3:
if (thisMode == 0) { // период
thisM[0]--;
currentLine = 1;
} else if (thisMode == 1) { // імпульс
if (--channels[currentChannel].work < 1) channels[currentChannel].work = 1;
currentLine = 2;
} else if (thisMode == 2) { // сутки
if (--channels[currentChannel].hour2 < 0) channels[currentChannel].hour2 = 0;
currentLine = 2;
} else { // датчик
if (--channels[currentChannel].sensor < 0) channels[currentChannel].sensor = 0;
currentLine = 2;
}
break;
case 4:
if (thisMode == 0) { // період
thisS[0]--;
currentLine = 1;
} else if (thisMode == 1) {
if (--channels[currentChannel].startHour < 0) channels[currentChannel].startHour = 0;
currentLine = 3;
} else if (thisMode == 3) {
if (channels[currentChannel].threshold >= 50)
channels[currentChannel].threshold -= 10;
else
channels[currentChannel].threshold--;
if (channels[currentChannel].threshold < 0) channels[currentChannel].threshold = 0;
currentLine = 3;
}
break;
case 5:
if (thisMode == 0) { // період
thisH[1]--;
currentLine = 2;
} else if (thisMode == 3) {
if (channels[currentChannel].thresholdMax >= 50)
30
482.ЧДТУ.31934-01 12 01
channels[currentChannel].thresholdMax -= 10;
else
channels[currentChannel].thresholdMax--;
if (channels[currentChannel].thresholdMax < 0) channels[currentChannel].thresholdMax =
0;
currentLine = 3;
}
break;
case 6:
if (thisMode == 0) { // період
thisM[1]--;
currentLine = 2;
}
break;
case 7:
if (thisMode == 0) { // період
thisS[1]--;
currentLine = 2;
}
break;
}
}
void rightHservice() {
switch (arrowPos) {
case 0:
if (++currentChannel > 9) currentChannel = 9;
if (serviceFlag && currentChannel > -3) serviceOUT();
currentLine = 4;
break;
case 1: realTime[0]++;
correctTime();
currentLine = 0;
break;
case 2: realTime[1]++;
correctTime();
currentLine = 0;
break;
case 3: realTime[2]++;
correctTime();
currentLine = 0;
break;
case 4: channelStatesServ[0] = true;
currentLine = 2;
break;
case 5: channelStatesServ[1] = true;
currentLine = 2;
break;
case 6: channelStatesServ[2] = true;
currentLine = 2;
31
482.ЧДТУ.31934-01 12 01
break;
case 7: channelStatesServ[3] = true;
currentLine = 2;
break;
case 8: channelStatesServ[4] = true;
currentLine = 2;
break;
case 9: channelStatesServ[5] = true;
currentLine = 2;
break;
case 10: channelStatesServ[6] = true;
currentLine = 2;
break;
case 11:
if (SERVO1_RELAY) { // если реле
channelStatesServ[7] = 1;
} else {
servoPosServ[0] += 10;
if (servoPosServ[0] > 180) servoPosServ[0] = 180;
}
currentLine = 1;
break;
case 12:
if (SERVO2_RELAY) { // если реле
channelStatesServ[8] = 1;
} else {
servoPosServ[1] += 10;
if (servoPosServ[1] > 180) servoPosServ[1] = 180;
}
currentLine = 2;
break;
case 13: channelStatesServ[9] = true;
driveState = 1;
currentLine = 3;
break;
case 14: settings.comSensPeriod += 1;
currentLine = 3;
break;
case 15: if (++settings.plotMode >= 2) settings.plotMode = 2;
switch (settings.plotMode) {
case 0: plotTimeout = 5760;
break;
case 1: plotTimeout = 240;
break;
case 2: plotTimeout = 4;
break;
}
currentLine = 3;
break;
}
32
482.ЧДТУ.31934-01 12 01
}
void leftHservice() {
switch (arrowPos) {
case 0:
if (--currentChannel < -3) currentChannel = -3;
if (!serviceFlag && currentChannel == -3) serviceIN();
currentLine = 4;
break;
case 1: realTime[0]--;
correctTime();
currentLine = 0;
break;
case 2: realTime[1]--;
correctTime();
currentLine = 0;
break;
case 3: realTime[2]--;
correctTime();
currentLine = 0;
break;
case 4: channelStatesServ[0] = false;
currentLine = 2;
break;
case 5: channelStatesServ[1] = false;
currentLine = 2;
break;
case 6: channelStatesServ[2] = false;
currentLine = 2;
break;
case 7: channelStatesServ[3] = false;
currentLine = 2;
break;
case 8: channelStatesServ[4] = false;
currentLine = 2;
break;
case 9: channelStatesServ[5] = false;
currentLine = 2;
break;
case 10: channelStatesServ[6] = false;
currentLine = 2;
break;
case 11:
if (SERVO1_RELAY) { // якщо реле
channelStatesServ[7] = 0;
} else {
if (servoPosServ[0] >= 10) servoPosServ[0] -= 10;
else servoPosServ[0] = 0;
}
currentLine = 1;
33
482.ЧДТУ.31934-01 12 01
break;
case 12:
if (SERVO2_RELAY) { // якщо реле
channelStatesServ[8] = 0;
} else {
if (servoPosServ[1] >= 10) servoPosServ[1] -= 10;
else servoPosServ[1] = 0;
}
currentLine = 2;
break;
case 13: channelStatesServ[9] = false;
driveState = 1;
currentLine = 3;
break;
case 14: settings.comSensPeriod -= 1;
if (settings.comSensPeriod < 1) settings.comSensPeriod = 1;
currentLine = 3;
break;
case 15: if (--settings.plotMode < 0) settings.plotMode = 0;
switch (settings.plotMode) {
case 0: plotTimeout = 5760;
break;
case 1: plotTimeout = 240;
break;
case 2: plotTimeout = 4;
break;
}
currentLine = 3;
break;
}
}
void rightHsettings() {
if (arrowPos > 0) settingsChanged = true;
switch (arrowPos) {
case 0:
if (++currentChannel > 9) currentChannel = 9;
if (serviceFlag && currentChannel > -3) serviceOUT();
if (settingsChanged) {
applySettings();
updateSettings();
}
currentLine = 4;
break;
case 1: settings.backlight = true;
currentLine = 0;
break;
case 2:
if (settings.backlTime < 250) settings.backlTime += 5;
else settings.backlTime = 255;
34
482.ЧДТУ.31934-01 12 01
backlTimer = millis();
currentLine = 1;
break;
case 3:
if (settings.drvSpeed < 250) settings.drvSpeed += 10;
else settings.drvSpeed = 255;
currentLine = 1;
break;
case 4:
if (settings.srv1_Speed < 250) settings.srv1_Speed += 5;
else settings.srv1_Speed = 255;
currentLine = 2;
break;
case 5:
settings.srv1_Acc += (float)0.1;
if (settings.srv1_Acc >= 1) settings.srv1_Acc = 1;
currentLine = 2;
break;
case 6:
if (settings.srv2_Speed < 250) settings.srv2_Speed += 5;
else settings.srv2_Speed = 255;
currentLine = 3;
break;
case 7:
settings.srv2_Acc += (float)0.1;
if (settings.srv2_Acc >= 1) settings.srv2_Acc = 1;
currentLine = 3;
break;
}
}
void leftHsettings() {
if (arrowPos > 0) settingsChanged = true;
switch (arrowPos) {
case 0:
if (--currentChannel < -3) currentChannel = -3;
if (!serviceFlag && currentChannel == -3) serviceIN();
if (settingsChanged) {
applySettings();
updateSettings();
}
currentLine = 4;
break;
case 1: settings.backlight = false;
currentLine = 0;
break;
case 2:
if (settings.backlTime > 10) settings.backlTime -= 5;
else settings.backlTime = 5;
backlTimer = millis();
currentLine = 1;
35
482.ЧДТУ.31934-01 12 01
break;
case 3:
if (settings.drvSpeed > 10) settings.drvSpeed -= 10;
else settings.drvSpeed = 5;
currentLine = 1;
break;
case 4:
if (settings.srv1_Speed > 10) settings.srv1_Speed -= 5;
else settings.srv1_Speed = 5;
currentLine = 2;
break;
case 5:
settings.srv1_Acc -= (float)0.1;
if (settings.srv1_Acc < 0.1) settings.srv1_Acc = 0.1;
currentLine = 2;
break;
case 6:
if (settings.srv2_Speed > 10) settings.srv2_Speed -= 5;
else settings.srv2_Speed = 5;
currentLine = 3;
break;
case 7:
settings.srv2_Acc -= (float)0.1;
if (settings.srv2_Acc < 0.1) settings.srv2_Acc = 0.1;
currentLine = 3;
break;
}
}
void controlTick() {
enc.tick(); // відпрацювання енкодера
if (enc.isHolded()) {
manualControl = true;
driveState = 1;
manualPos = !manualPos;
}
if (enc.isClick()) {
if (CONTROL_TYPE == 1
&& !(arrowPos == 1 && navDepth == 1)
&& !(arrowPos == 2 && navDepth == 0)) {
controlState = !controlState;
drawArrow();
}
settingsTimer = millis();
if (backlState) {
backlTimer = millis(); // скинуть таймаут дисплея
if (currentChannel >= 0) {
if (arrowPos == 2 && navDepth == 0) { // заходим в настройки режима
navDepth = 1;
36
482.ЧДТУ.31934-01 12 01
arrowPos = 0;
s_to_hms();
}
if (arrowPos == 1 && navDepth == 1) { // натискання на Back
updateEEPROM(currentChannel);
navDepth = 0;
arrowPos = 2;
if (channels[currentChannel].mode == 0) hms_to_s();
}
currentLine = 4;
redrawScreen();
}
} else {
backlOn(); // включить дисплей
}
}
if (enc.isTurn()) {
settingsTimer = millis();
if (backlState) {
backlTimer = millis(); // скинуть таймаут дисплея
// позиції стрілки для каналів и режимів
if (!controlState && enc.isRight()) {
if (currentChannel >= 0) {
debugPage = 0;
if (navDepth == 0) {
if (channels[currentChannel].state) {
if (currentChannel > 6 && currentChannel < 9) {
if (++arrowPos > 5) arrowPos = 5;
} else {
if (++arrowPos > 4) arrowPos = 4;
}
} else {
if (++arrowPos > 1) arrowPos = 1;
}
} else {
switch (channels[currentChannel].mode) {
case 0: if (++arrowPos > 7) arrowPos = 7;
break;
case 1: if (++arrowPos > 4) arrowPos = 4;
break;
case 2: if (++arrowPos > 3) arrowPos = 3;
break;
case 3: if (++arrowPos > 5) arrowPos = 5;
break;
case 4: if (++arrowPos > 5) arrowPos = 5;
break;
}
}
} else if (currentChannel == -1) {
37
482.ЧДТУ.31934-01 12 01
// позиції стрілки для дебаг
arrowPos = 0;
if (++debugPage > 6) debugPage = 6;
if (debugPage == 0) {
lcd.clear();
redrawDebug();
} else {
lcd.clear();
redrawPlot();
}
} else if (currentChannel == -2) {
// позиції стрілки для налаштувань
if (++arrowPos > 7) arrowPos = 7;
} else if (currentChannel == -3) {
// позиції стрілки для сервісу
if (++arrowPos > 15) arrowPos = 15;
//redrawScreen();
}
drawArrow();
} else if (!controlState && enc.isLeft()) {
if (--arrowPos < 0) arrowPos = 0;
if (currentChannel == -1) {
if (--debugPage < 0) debugPage = 0;
if (debugPage == 0) {
lcd.clear();
redrawDebug();
} else {
lcd.clear();
redrawPlot();
}
}
drawArrow();
}
if (currentChannel < 7 && currentChannel >= 0) curMode = 0;
else if (currentChannel < 9) curMode = 1;
else curMode = 2;
thisMode = channels[currentChannel].mode;
#if (CONTROL_TYPE == 0)
if (enc.isRightH()) {
#elif (CONTROL_TYPE == 1)
if ((controlState && enc.isRight()) || enc.isRightH()) {
#endif
debugPage = 0;
if (currentChannel < 0) {
if (currentChannel == -2) rightHsettings();
else rightHservice();
38
482.ЧДТУ.31934-01 12 01
} else {
if (navDepth == 0) {
rightHdepth0();
} else {
rightHdepth1();
}
if (thisMode == 0) recalculateTime();
}
redrawScreen();
#if (CONTROL_TYPE == 0)
} else if (enc.isLeftH()) {
#elif (CONTROL_TYPE == 1)
} else if ((controlState && enc.isLeft()) || enc.isLeftH()) {
#endif
debugPage = 0;
if (currentChannel < 0) {
if (currentChannel == -2) leftHsettings();
else leftHservice();
} else {
if (navDepth == 0) {
leftHdepth0();
} else {
leftHdepth1();
}
if (thisMode == 0) recalculateTime();
}
redrawScreen();
}
} else {
backlOn(); // включить дисплей
}
}
}
void recalculateTime() {
for (byte i = 0; i < 1; i++) {
if (thisS[i] > 59) {
thisS[i] = 0;
thisM[i]++;
}
if (thisM[i] > 59) {
thisM[i] = 0;
thisH[i]++;
}
if (thisS[i] < 0) {
if (thisM[i] > 0) {
thisS[i] = 59;
thisM[i]--;
} else thisS[i] = 0;
39
482.ЧДТУ.31934-01 12 01
}
if (thisM[i] < 0) {
if (thisH[i] > 0) {
thisM[i] = 59;
thisH[i]--;
} else thisM[i] = 0;
}
if (thisH[i] < 0) thisH[i] = 0;
}
}
void correctTime() {
timeChanged = true;
for (byte i = 0; i < 1; i++) {
if (realTime[2] > 59) {
realTime[2] = 0;
realTime[1]++;
}
if (realTime[1] > 59) {
realTime[1] = 0;
realTime[0]++;
}
if (realTime[2] < 0) {
if (realTime[1] > 0) {
realTime[2] = 59;
realTime[1]--;
} else realTime[2] = 0;
}
if (realTime[1] < 0) {
if (realTime[0] > 0) {
realTime[1] = 59;
realTime[0]--;
} else realTime[1] = 0;
}
if (realTime[0] < 0) realTime[0] = 0;
}
}
void applySettings() {
analogWrite(DRV_PWM, settings.drvSpeed);
servo1.setSpeed(settings.srv1_Speed); // обмежить швидкість
servo1.setAccel(settings.srv1_Acc); // встановить прискорення(розгін і гальмування)
servo2.setSpeed(settings.srv2_Speed); // обмежить швидкість
servo2.setAccel(settings.srv2_Acc); // установить ускорение (розгін і гальмування)
}
// перевід секунд в ЧЧ:ММ:СС
void s_to_hms() {
uint32_t period = channels[currentChannel].period;
thisH[0] = floor((long)period / 3600); // секунди в часи
40
482.ЧДТУ.31934-01 12 01
thisM[0] = floor((period - (long)thisH[0] * 3600) / 60);
thisS[0] = period - (long)thisH[0] * 3600 - thisM[0] * 60;
period = channels[currentChannel].work;
thisH[1] = floor((long)period / 3600); // секунди в часи
thisM[1] = floor((period - (long)thisH[1] * 3600) / 60);
thisS[1] = period - (long)thisH[1] * 3600 - thisM[1] * 60;
}
// перевід ЧЧ:ММ:СС в секунди
void hms_to_s() {
channels[currentChannel].period = ((long)thisH[0] * 3600 + thisM[0] * 60 + thisS[0]);
channels[currentChannel].work = ((long)thisH[1] * 3600 + thisM[1] * 60 + thisS[1]);
}
void drawPlot(byte pos, byte row, byte width, byte height, int min_val, int max_val, int
*plot_array) {
int max_value = -32000;
int min_value = 32000;
for (byte i = 0; i < 15; i++) {
if (plot_array[i] > max_value) max_value = plot_array[i];
if (plot_array[i] < min_value) min_value = plot_array[i];
}
lcd.setCursor(0, 1); lcd.print(max_value); lcd.print(F(" "));
//lcd.setCursor(0, 1); lcd.print(label);
lcd.setCursor(0, 2); lcd.print(F(">")); lcd.print(plot_array[14]); lcd.print(F(" "));
lcd.setCursor(0, 3); lcd.print(min_value); lcd.print(F(" "));
for (byte i = 0; i < width; i++) { // каждий стовбець параметрів
int fill_val = plot_array[i];
fill_val = constrain(fill_val, min_val, max_val);
byte infill, fract;
// знайти кількість цілих блоків з обліком мінімума і максимума для відображення в
графіку
if (plot_array[i] > min_val)
infill = floor((float)(plot_array[i] - min_val) / (max_val - min_val) * height * 10);
else infill = 0;
fract = (float)(infill % 10) * 8 / 10; // найти кількість залишившихся полосок
infill = infill / 10;
for (byte n = 0; n < height; n++) { // для всіх рядків графіка
if (n < infill && infill > 0) { // пока ми нижче рівня
lcd.setCursor(pos + i, (row - n)); // заповнюєм повними ячейками
lcd.write(0);
}
if (n >= infill) { // якщо достигли межі
lcd.setCursor(pos + i, (row - n));
if (fract > 0) lcd.write(fract); // заповнюєм дробні ячейки
else lcd.write(16); // якщо дробні == 0, заливаємо пустою
for (byte k = n + 1; k < height; k++) { // все що зверху заливаємо пустими
41
482.ЧДТУ.31934-01 12 01
lcd.setCursor(pos + i, (row - k));
lcd.write(16);
}
break;
}
}
}
}
void redrawPlot() {
loadPlot();
lcd.setCursor(1, 0);
switch (debugPage) {
case 1: lcd.print(F("TEMP"));
drawPlot(5, 3, 15, 4, 0, 45, (int*)tempDay);
break;
case 2: lcd.print(F("HUM"));
drawPlot(5, 3, 15, 4, 0, 100, (int*)humDay);
break;
case 3: lcd.print(F("SEN1"));
drawPlot(5, 3, 15, 4, 0, 1023, (int*)sensDay_0);
break;
case 4: lcd.print(F("SEN2"));
drawPlot(5, 3, 15, 4, 0, 1023, (int*)sensDay_1);
break;
case 5: lcd.print(F("SEN3"));
drawPlot(5, 3, 15, 4, 0, 1023, (int*)sensDay_2);
break;
case 6: lcd.print(F("SEN4"));
drawPlot(5, 3, 15, 4, 0, 1023, (int*)sensDay_3);
break;
}
}
void redrawChannels() {
// вивід назви каналу
if (currentLine == 0 || currentLine == 4) {
lcd.setCursor(1, 0);
lcd.print(channelNames[currentChannel]);
// вивід стану
lcd.setCursor(15, 0);
if (channels[currentChannel].state) lcd.print(F("On"));
else lcd.print(F("Off"));
}
// вивід налаштувань
if (channels[currentChannel].state) {
byte curMode;
if (currentChannel < 7) curMode = 0;
42
482.ЧДТУ.31934-01 12 01
else if (currentChannel < 9) curMode = 1;
else curMode = 2;
if (currentLine == 1 || currentLine == 4) {
lcd.setCursor(1, 1);
clearLine();
lcd.setCursor(1, 1);
if (channels[currentChannel].relayType != 2) {
lcd.print(settingsNames[curMode * 3 + 0]); spaceColon();
lcd.print(F(" <"));
lcd.print(modeNames[channels[currentChannel].mode]);
lcd.print(F(">"));
} else {
lcd.print("---");
}
}
if (currentLine == 2 || currentLine == 4) {
lcd.setCursor(1, 2);
clearLine();
lcd.setCursor(1, 2);
lcd.print(settingsNames[curMode * 3 + 1]); spaceColon();
lcd.print(directionNames[channels[currentChannel].direction + curMode * 2]);
}
if (currentLine == 3 || currentLine == 4) {
lcd.setCursor(1, 3);
clearLine();
lcd.setCursor(1, 3);
lcd.print(settingsNames[curMode * 3 + 2]); spaceColon();
if (curMode == 0) {
lcd.print(relayNames[channels[currentChannel].relayType]);
}
if (curMode == 1) {
lcd.setCursor(11, 3);
if (currentChannel == 7) {
lcd.print(minAngle[0]);
lcd.setCursor(16, 3);
lcd.print(maxAngle[0]);
} else if (currentChannel == 8) {
lcd.print(minAngle[1]);
lcd.setCursor(16, 3);
lcd.print(maxAngle[1]);
}
}
if (curMode == 2) {
//lcd.setCursor(11, 3);
lcd.print(driveTimeout);
}
}
43
482.ЧДТУ.31934-01 12 01
}
}
void redrawSettings() {
byte curSetMode = channels[currentChannel].mode;
if (currentLine == 4) {
lcd.setCursor(1, 0);
lcd.print(F("<"));
lcd.print(modeNames[channels[currentChannel].mode]);
lcd.print(F(">"));
lcd.setCursor(15, 0); lcd.print(F("Back"));
}
if (currentLine == 1 || currentLine == 4) {
lcd.setCursor(1, 1);
clearLine();
lcd.setCursor(1, 1);
switch (curSetMode) {
case 0: lcd.print(modeSettingsNames[0]); spaceColon();
break;
case 1: lcd.print(modeSettingsNames[3]); spaceColon();
break;
case 2: lcd.print(modeSettingsNames[6]); spaceColon();
break;
case 3: lcd.print(modeSettingsNames[8]); spaceColon();
break;
}
switch (curSetMode) {
case 0:
lcd.setCursor(8, 1);
if (thisH[0] < 10) lcd.print(F("00"));
if (thisH[0] >= 10 && thisH[0] < 100) lcd.print(F("0"));
lcd.print((byte)(thisH[0]));
lcd.print(F(":"));
if (thisM[0] < 10) lcd.print(0);
lcd.print((byte)(thisM[0]));
lcd.print(F(":"));
if (thisS[0] < 10) lcd.print(0);
lcd.print((byte)(thisS[0]));
break;
case 1: lcd.print((int)(impulsePrds[channels[currentChannel].impulsePrd]));
if (channels[currentChannel].impulsePrd < 6)
lcd.print(F(" m "));
else
lcd.print(F(" h "));
break;
case 2: lcd.print((byte)(channels[currentChannel].hour1));
44
482.ЧДТУ.31934-01 12 01
lcd.print(F(" h "));
break;
case 3:
if (channels[currentChannel].sensPeriod < 60) {
lcd.print(channels[currentChannel].sensPeriod);
lcd.print(F(" s "));
} else {
lcd.print((float)channels[currentChannel].sensPeriod / 60, 1);
lcd.print(F(" m "));
}
break;
}
}
if (currentLine == 2 || currentLine == 4) {
lcd.setCursor(1, 2);
clearLine();
lcd.setCursor(1, 2);
switch (curSetMode) {
case 0: lcd.print(modeSettingsNames[1]); spaceColon();
break;
case 1: lcd.print(modeSettingsNames[4]); spaceColon();
break;
case 2: lcd.print(modeSettingsNames[7]); spaceColon();
break;
case 3: lcd.print(modeSettingsNames[9]); spaceColon();
break;
}
switch (curSetMode) {
case 0:
lcd.setCursor(9, 2);
if (thisH[1] < 10) lcd.print(0);
lcd.print((byte)(thisH[1]));
lcd.print(F(":"));
if (thisM[1] < 10) lcd.print(0);
lcd.print((byte)(thisM[1]));
lcd.print(F(":"));
if (thisS[1] < 10) lcd.print(0);
lcd.print((byte)(thisS[1]));
break;
case 1: lcd.print(channels[currentChannel].work);
lcd.print(F(" s "));
break;
case 2: lcd.print((byte)(channels[currentChannel].hour2));
lcd.print(F(" h "));
break;
case 3: lcd.print(sensorNames[channels[currentChannel].sensor]);
lcd.print(F(" "));
45
482.ЧДТУ.31934-01 12 01
lcd.print(sensorVals[channels[currentChannel].sensor]);
break;
}
}
if (currentLine == 3 || currentLine == 4) {
lcd.setCursor(1, 3);
clearLine();
lcd.setCursor(1, 3);
switch (curSetMode) {
case 0:
lcd.print(modeSettingsNames[2]); spaceColon();
if (channels[currentChannel].period > 0) {
long period = channels[currentChannel].period - (millis() - timerMillis[currentChannel]) /
1000L;
byte leftH = floor((long)period / 3600); // секунди в години
byte leftM = floor((period - (long)leftH * 3600) / 60);
byte leftS = period - (long)leftH * 3600 - leftM * 60;
//lcd.print(/*string*/(leftH) + ":" + /*string*/(leftM) + ":" + /*string*/(leftS));
lcd.print(leftH);
lcd.print(":");
lcd.print(leftM);
lcd.print(":");
lcd.print(leftS);
}
break;
case 1:
lcd.print(modeSettingsNames[5]); spaceColon();
lcd.print(channels[currentChannel].startHour);
lcd.print(F(" h "));
break;
case 2:
break;
case 3:
lcd.print("minV:");
lcd.print(channels[currentChannel].threshold);
lcd.setCursor(11, 3);
lcd.print("maxV:");
lcd.print(channels[currentChannel].thresholdMax);
break;
}
}
}
void redrawDebug() {
//lcd.clear();
lcd.setCursor(1, 0); lcd.print(F("DEBUG"));
lcd.setCursor(7, 0); lcd.print(F("S1:"));
if (channels[7].state) {
//lcd.print(/*string*/(servoPos[0]));
46
482.ЧДТУ.31934-01 12 01
//lcd.print(F(" "));
#if (SERVO1_RELAY == 0)
if (channelStates[7]) lcd.print(F("Max"));
else lcd.print(F("Min"));
#else
lcd.print(channelStates[7]);
#endif
}
else lcd.print(F("-"));
lcd.setCursor(14, 0); lcd.print(F("S2:"));
if (channels[8].state) {
//lcd.print(/*string*/(servoPos[1]));
//if (servoPos[0] < 100) lcd.print(F(" "));
#if (SERVO2_RELAY == 0)
if (channelStates[8]) lcd.print(F("Max"));
else lcd.print(F("Min"));
#else
lcd.print(channelStates[8]);
#endif
}
else lcd.print(F("-"));
lcd.setCursor(0, 1); lcd.print("H:"); lcd.print(sensorVals[1]);
lcd.setCursor(5, 1); lcd.print(sensorVals[2]); lcd.print(F(" "));
lcd.setCursor(9, 1); lcd.print(sensorVals[3]); lcd.print(F(" "));
lcd.setCursor(13, 1); lcd.print(sensorVals[4]); lcd.print(F(" "));
lcd.setCursor(17, 1); lcd.print(sensorVals[5]);
if (sensorVals[4] < 10) lcd.print(F(" "));
else if (sensorVals[4] < 100) lcd.print(F(" "));
lcd.setCursor(0, 2); lcd.print(F("T:")); lcd.print((int)(sensorVals[0])); lcd.write(223);
lcd.setCursor(5, 2); lcd.print(F("R:"));
for (byte i = 0; i < 7; i++) {
if (channels[i].state) lcd.print(channelStates[i]);
else lcd.print(F("-"));
}
lcd.setCursor(15, 2); lcd.print(F("D:"));
if (channels[9].state) lcd.print(lastDriveState);
else lcd.print(F("-"));
lcd.setCursor(0, 3);
if (realTime[0] < 10) lcd.print(F(" "));
lcd.print((byte)(realTime[0])); lcd.print(F(":"));
if (realTime[1] < 10) lcd.print(0);
lcd.print((byte)(realTime[1])); lcd.print(F(":"));
if (realTime[2] < 10) lcd.print(0);
lcd.print((byte)(realTime[2]));
lcd.setCursor(12, 3); lcd.print(F("U:")); lcd.print(uptime);
47
482.ЧДТУ.31934-01 12 01
}
void redrawMainSettings() {
if (currentLine == 4) lcd.clear();
if (currentLine == 0 || currentLine == 4) {
clearLine2(0);
lcd.setCursor(1, 0); lcd.print(F("SETTINGS"));
lcd.setCursor(11, 0); lcd.print(F("A-BKL:"));
if (settings.backlight) lcd.print(F("On"));
else lcd.print(F("Off"));
}
if (currentLine == 1 || currentLine == 4) {
clearLine2(1);
lcd.setCursor(0, 1); lcd.print(F("BKL-TOUT:")); lcd.print(settings.backlTime);
lcd.setCursor(13, 1); lcd.print(F("DRV:")); lcd.print(settings.drvSpeed);
}
if (currentLine == 2 || currentLine == 4) {
clearLine2(2);
lcd.setCursor(0, 2); lcd.print(F("S1_SP:")); lcd.print(settings.srv1_Speed);
lcd.setCursor(10, 2); lcd.print(F("S1_ACC:")); lcd.print(settings.srv1_Acc, 1);
}
if (currentLine == 3 || currentLine == 4) {
clearLine2(3);
lcd.setCursor(0, 3); lcd.print(F("S2_SP:")); lcd.print(settings.srv2_Speed);
lcd.setCursor(10, 3); lcd.print(F("S2_ACC:")); lcd.print(settings.srv2_Acc, 1);
}
}
void redrawService() {
if (currentLine == 4) lcd.clear();
if (currentLine == 0 || currentLine == 4) {
clearLine2(0);
lcd.setCursor(1, 0); lcd.print(F("SERVICE"));
lcd.setCursor(12, 0);
if (realTime[0] < 10) lcd.print(F(" "));
lcd.print((byte)(realTime[0])); lcd.print(F(":"));
if (realTime[1] < 10) lcd.print(0);
lcd.print((byte)(realTime[1])); lcd.print(F(":"));
if (realTime[2] < 10) lcd.print(0);
lcd.print((byte)(realTime[2]));
}
if (currentLine == 1 || currentLine == 4) {
clearLine2(1);
lcd.setCursor(0, 1); lcd.print(F("R1 2 3 4 5 6 7"));
lcd.setCursor(14, 1); lcd.print(F("S1:"));
if (SERVO1_RELAY) { // якщо реле
48
482.ЧДТУ.31934-01 12 01
lcd.print(channelStatesServ[7]);
} else {
lcd.print((byte)(servoPosServ[0]));
}
}
if (currentLine == 2 || currentLine == 4) {
clearLine2(2);
lcd.setCursor(1, 2); for (byte i = 0; i < 7; i++) {
lcd.print(channelStatesServ[i]);
lcd.print(" ");
}
lcd.setCursor(14, 2); lcd.print(F("S2:"));
if (SERVO2_RELAY) { // если реле
lcd.print(channelStatesServ[8]);
} else {
lcd.print((byte)(servoPosServ[1]));
}
}
if (currentLine == 3 || currentLine == 4) {
clearLine2(3);
lcd.setCursor(0, 3); lcd.print(F("D:"));
lcd.setCursor(2, 3);
if (channelStatesServ[9]) lcd.print(F("OPEN"));
else lcd.print(F("CLOS"));
lcd.setCursor(7, 3); lcd.print(F("SP:"));
lcd.print(settings.comSensPeriod);
lcd.setCursor(14, 3); lcd.print(F("PP:"));
switch (settings.plotMode) {
case 0: lcd.print("DAY");
break;
case 1: lcd.print("HR ");
break;
case 2: lcd.print("MIN");
break;
}
}
}
void redrawScreen() {
if (currentLine == 4) lcd.clear();
if (navDepth == 0) { // корінь mеню
if (currentChannel >= 0) { // для всіх крім DEBUG
redrawChannels(); // вивід налаштувань каналів
} else if (currentChannel == -1) {
redrawDebug();
49
482.ЧДТУ.31934-01 12 01
} else if (currentChannel == -2) {
redrawMainSettings();
} else if (currentChannel == -3) {
redrawService();
}
} else {
redrawSettings();
}
drawArrow();
}
void spaceColon() {
lcd.print(F(": "));
}
void clearLine() {
for (byte i = 0; i < 19; i++) {
lcd.print(F(" "));
}
}
void clearLine2(byte row) {
lcd.setCursor(0, row);
for (byte i = 0; i < 20; i++) {
lcd.print(F(" "));
}
}
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ
1. Programing Microcontroller – Research desigh lab
2. Герберт Шілдт. С++ для початківців. Крок за кроком
3. Radiovin.com.ua
4. DS18B20 - Programmable Resolution 1-Wire Digital Thermometer -
Maxim Integrated Products, Inc., 2018
5. Уллі Соммер. Програмування мікроконтролерних плат Arduino; 2012 г.,
256 стр.
6. Стівен Прата. Мова програмування C++
7. Страуструп. Програмування. Принципи і практика використання C++
8. Бєлов Ю. А., Карнаух Т. О., Коваль Ю. В., Ставровський А. Б. Вступ до
програмування мовою С++. Організація обчислень: навч. посіб. Київ:
Видавничо-поліграфічний центр "Київський університет", 2012. 175 с.
9. Методичні рекомендації до підготовки кваліфікаційної роботи
бакалавра для здобувачів освітнього ступеня «бакалавр» зі
спеціальностей 123 Комп’ютерна інженерія та 125 Кібербезпека усіх
форм навчання [Електронний ресурс] / [упорядники: С.О. Гресько,
С.Ю. Куницька]; М-во освіти і науки України, Черкаський державний
технологічний університет. – Черкаси: ЧДТУ, 2018. – 50 с.
10.Exploring Arduino: Tools and Techniques for Engineering Wizardry 2016. -
336 с.
11.Роберт Седжвік: Алгоритми на C++. Аналіз структури даних.
Сортування. Пошук. Алгоритми на графах
тЛистЧДТУ.231934.008 ПЗ
Зм. Лист № докум. Підпис Дата 78