Принять значение температуры с контроллера на контроллер

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

PPeterr пишет:

Мне нужен некий минимальный набор кода (наиболее простой), но способный обеспечить вполне надёжную работу системы автоматического поддержания температуры в помещении. Основные исполнительные органы - котёл на дизельном топливе, насосы перекачивающие воду. У котла - своя автоматика защиты и контроля процесса горения (штатная, заводская). Я лишь планирую включать/выключать котёл с помощью реле.

Да Вы, батенька, большой оптимист.

То, что сформулировано Вами - это достаточно сложная задача. Судя по Вашим постам на форуме, Вам она явно не по зубам. 

Ключевое слово - надежность.

Попытайтесь написать в "Ищу исполнителя". 

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

По поводу температур (датчиков и дальнейших разбирательств с ними) - я в тупике. Полагаю на данном этапе пока оставить их в покое. Как я себе представляю - наиболее существенным остаётся вопрос как работать с отрицательными значениями температур. Его я оставлю на потом - если проект будет двигаться далее, когда оставшиеся компоненты будут задействованы - удлиню провод и засуну датчик в морозилку. И попытаюсь как-нибудь настроить работу с отрицательными температурами. За совет про signBit = raw & 0x8000; - спасибо. Так и буду пытаться получить желаемое.

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

Далее планирую заняться подключением платы часов реального времени (DS3231). Прошу вашего совета - к какому контроллеру лучше подключить. По моим поверхностным представлениям - можно подключить как к nano, так и к UNO. При этом - в настоящий момент у меня на nano прописан код считывания и пересылки данных с датчиков температуры, скетч использует 9% памяти устройства. На UNO - код прорисовки меню для TFT LCD дисплея и код приёма и вывода значений температур. Скетч использует 54% памяти устройства.

sadman41
Offline
Зарегистрирован: 19.10.2016

b707 пишет:

sadman41 пишет:

Итого - что без домыслов в двух первых байтах при обрыве и при замыкании?

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

Для передачи бита есть таймслот. Его запускает МК прижиманием DQ к земле. Затем слушает.  За это время сенсор в ответ должен (судя по диаграмме из даташита) прижать DQ к земле на 60uS, если передает "0", и менее, чем на 15uS, если передает "1". OneWire::read_bit() просто читает состояние пина через N uS (13uS + накладные расходы). Т.е. она принимает за факт, что если через 15uS на шине присутствует GND, то передавался "0". Если на шине V_pull-up - передавался "1". Начальное состояние пина после старта таймслота не контролируется, что ускоряет чтение, но вносит недетектируемую ошибку в принимаемые биты. В большинстве случаев наводки гасятся через pull-up резистор, поэтому тем, кто складывает два бита - просто везет. Но защиты от чтения оторванной или короткозамкнутой шины данная библиотека не содержит. 

Именно поэтому DallasTemerature, как я понимаю, не доверяет сформированным байтам и в обязательном порядке производит сверку по CRC. 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

PPeterr пишет:

Далее планирую заняться подключением платы часов реального времени (DS3231). Прошу вашего совета - к какому контроллеру лучше подключить. По моим поверхностным представлениям - можно подключить как к nano, так и к UNO.

Никакой разницы нет. Где свободны A4/A5 и нет жестких ограничений по времени выполнения кода - туда и подключайте. Ну и, в принципе, часы откликаются быстро, так что задержек особых не будет (особенно если их дергать не каждый луп, а через 1000мс минимум). Думайте не куда подключить, а как ими управлять - время выставить, то-сё. Потом будет понятно - куда их выгодней прицепить.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Я подключил ЧРВ к nano (провода соединил). Стал искать библиотеку - и нашёл много вариантов - начиная от того, что используют библиотеку от более ранних версий (DS1307) и заканчивая тем, что есть "самопальные". При этом - зачастую используют две библиотеки для выполнений просто вывода значений времени в Serial. Можете ли посоветовать некий наименее запутанный вариант для меня? Мне в итоге от этого ЧРВ нужно будет чтобы в заданное время началась выполняться процедура и закончила своё выполнение через определённо время.

Ещё любопытно: наткнулся на информацию, что в DS3231 есть встроенный датчик температуры. Мелькнула мысль - насколько разумно попытаться применить этот датчик как датчик температуры воздуха в помещении (но не понятно, какие дополнительные пины и куда подключать, какая библиотека и какая точность датчика).

А управлять ЧРВ мне требуется с TFT LCD экранчика - я уже и менюшку прорисовал для выставления времени включения системы по таймеру.

На UNO у меня пины A4/A5 задействованы на SD карточку TFT LCD дисплея. И немотря на то, что карточкой я пока пользоваться не планирую, я встречал мнение, что и SD карточку и ЧРВ на эти пины можно подключить одновременно (как бы параллельно).

sadman41
Offline
Зарегистрирован: 19.10.2016

PPeterr пишет:
Я подключил ЧРВ к nano (провода соединил). Стал искать библиотеку - и нашёл много вариантов - начиная от того, что используют библиотеку от более ранних версий (DS1307) и заканчивая тем, что есть "самопальные".

Да вы, я смотрю, любитель брать вершины без страховки - датчики читать без CRC, библиотеку брать от другого чипа.
 
Лично я для своего самопала потрошил это: https://github.com/Makuna/Rtc/

PPeterr пишет:
При этом - зачастую используют две библиотеки для выполнений просто вывода значений времени в Serial. Можете ли посоветовать некий наименее запутанный вариант для меня? Мне в итоге от этого ЧРВ нужно будет чтобы в заданное время началась выполняться процедура и закончила своё выполнение через определённо время.

Есть два варианта - в лупе читать время и принимать решение на основании считанных H:m:s или выставлять Alarm на RTC, который дернет ногу через ногу INT ардуиновское ExternalInterrupt (см. attachInterrupt()). Мне кажется, что во втором случае вы больше дров наломаете.

PPeterr пишет:

Ещё любопытно: наткнулся на информацию, что в DS3231 есть встроенный датчик температуры. Мелькнула мысль - насколько разумно попытаться применить этот датчик как датчик температуры воздуха в помещении.

По моему мнению - нинасколько. Мерять вы будете температуру не в помещении, а в корпусе. А туда еще, поди, запихаете модуль питания или еще что-нить нагревающееся. По СанПиновской методике  это вообще не измерение. Датчик там для другого - часы изменяют "ход", основываясь на его показаниях. Термокомпенсация.

Кстати, точность 0.5 с 18B20 можете получить выставив разрешение на датчике в 9bit. Но только один раз ставьте (в setup()), а то дыру ему в ROM прожжете )) Судя по всему вы собираетесь долбить в датчики на каждом лупе (что, кстати, разогреет сенсоры дополнительно и внесет искажения до 0.5C).

arduinec
Offline
Зарегистрирован: 01.09.2015

sadman41 пишет:

Кстати, точность 0.5 с 18B20 можете получить выставив разрешение на датчике в 9bit. Но только один раз ставьте (в setup()), а то дыру ему в ROM прожжете ))

Ещё лучше выставить разрешение один раз и сохранить установки в датчике.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

sadman41 пишет:

Лично я для своего самопала потрошил это: https://github.com/Makuna/Rtc/

Спасибо. Скачал, загрузил библиотеку. Открыл пример "DS3231_simple". Открыл монитор порта - какие-то иероглифы. Взглянул на код - обратил внимание на "Serial.begin(57600);" до этого во всех скетчах у меня стояло 9600. В итоге, в мониторе порта поставил 57600 бод - и только после этого стала отображаться дата, время и температура (корректные). Вопрос: так сколько теперь в моём общем коде должно быть установлено в Serial.begin (чтобы и ЧРВ работали и всё остальное)?

И ещё вопос - после загрузки этого примера, я увидел надпись внизу "скетч использует 29% памяти устойства". Так это что - я обязан смириться с тем, что ЧРВ как минимум займут 29% памяти контоллера?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

PPeterr пишет:

И ещё вопос - после загрузки этого примера, я увидел надпись внизу "скетч использует 29% памяти устойства". Так это что - я обязан смириться с тем, что ЧРВ как минимум займут 29% памяти контоллера?

Не обязаны.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Я поменял Serial.begin в "примере" на 9600 и в мониторе порта. Всё работает. "Пример" от этого не ста ли работать некорректно? (показания в мониторе порта коректные) 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Теперь Ваш Serial работает в 6 раз медленнее и, соответственно, расходует в 6 раз больше времени контроллера. Насколько это критично для Вашего проекта, не знаю. Я обычно прежде, чем запустить любой пример, устанавливаю скорость порта в 115200.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Спасибо. Оставлю значит пока что 9600 везде (где-то встречал, что это популярная скорость передачи).

Стал смотреть пример "DS3231_alarms". Прочитал также в описании, что эти ЧРВ имеют два будильника - как я понял, один из них можно настроить на начало какой-то задачи, второй - на завершение. Именно это мне от часов и нужно. В коде примера "alarm" написано:


// Interrupt Pin Lookup Table
// (copied from Arduino Docs)
//
// CAUTION:  The interrupts are Arduino numbers NOT Atmel numbers
//   and may not match (example, Mega2560 int.4 is actually Atmel Int2)
//   this is only an issue if you plan to use the lower level interupt features
//
// Board           int.0    int.1   int.2   int.3   int.4   int.5
// ---------------------------------------------------------------
// Uno, Ethernet    2       3
// Mega2560         2       3       21      20     [19]      18 
// Leonardo         3       2       0       1       7

Не могу понять что это означает. У меня МК nano - насколько мне известно, он аналогичен UNO. Что тут говорится про пины 2 и 3?

Ещё увидел запись в коде (касаемо подключения ЧРВ):

// SQW --->  (Pin19) Don't forget to pullup (4.7k to 10k to VCC)

Это что получается, для работы будильника я должен ещё один повод бросить (от SQW ЧРВ до 19 пина МК)?

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

PPeterr, этот фрагмент напоминает, что аппаратные прерывания в Uno/Nano находятся на 2 и 3 ногах.

Если Вы еще раз перечитаете то, что сами же цитировали, то обнаружите, что 19-я нога используется для прерывания в контроллере Mega2560, значит, вероятнее всего, именно к нему и относится замечание. В Uno/Nano это будет другой пин, но его так же нужно подтягивать к питанию.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Спасибо. Я правильно понимаю, что если я хочу использовать функции двх будильников, заложенные в DS3231, то выходами для будильников (alarm) будут именно 2-ая и 3-я нога и, соответственно, эти ноги нельза занимать на другие нужды?

По поводу вывода "SQW" на DS3231  в примере "DS3231_alarms" - подскажите про него, на что влияет, и куда его подключать, если у меня nano. Просто, другой пример ("DS3231_simple") работает без подключения этого вывода.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

1. Во-первых, не выходами, а входами. Во-вторых, никто не обязывает Вас получать сигналы от будильника именно через прерывания (хотя это удобно). И в третьих - да, если Вы хотите получать сигналы от будильника именно через прерывания, то использовать для этого нужно именно 2 и 3 пины, на других пинах аппаратных прерыаний нет (правда, из этого правила есть исключения, но они не поддерживаются непосредственно библиотеками Ардуино).

2. Абсолютно аналогично Uno.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Вчера кое-как заставил светодиод (пин 13) включаться в определённое время и выключаться в определённое. Без использования функций "будильника". По-простому, типа: "если минуты больше ... но меньше ... - то включить светодиод. Иначе - выключить". Вроде, работает.

А написать решил - вопрос возник по поводу блока питания. В конечном итоге проект планирует содержать следующие платы:

1. UNO; 2. nano; 3. часы реального времени; 4. три датчика температуры DS18B20; 5. плата с 4-мя реле.

Нашёл у себя вот такой адаптор:

Подскажите, можно ли его применить для питания всех устройств, задействованных в проекте?

b707
Offline
Зарегистрирован: 26.05.2017

В первую очередь посмотрите, какие у вас реле - чаще всего попадаются на 12в

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

У меня вот такой набор:

Здесь отсутствует МК "nano" - я его позже покупал, когда осознал что на UNO пинов не хватит на всё. Мне кажется, что плата реле вся должна работать от 5В. Но приду домой - уточню.

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Посмотрел - плата с реле называется HL-54S. Вот так выглядит:

и имеет такую схему подключения:

Как я понимаю - электромагнитные катушки реле питаются напряжением 5В.

Вопрос: если предположить, что имеющийся у меня адаптор выдаёт, как и написано, 5.4В с током 1.2А, - нормально ли это, если подать с него питание на всё? Не много ли это 5.4В, если написано, что номинальное напряжение питания микроконтроллера должно быть 5.0В?

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Здравствуйте.

За прошедшее время я составил необходимый набор функций (как мне кажется) программного кода для контроллера UNO.

Напомню, из чего состоит проект на данный момент. Три датчика температуры опрашиваются с периодичностью 1 сек. контроллером nano. Также на nano поступает сигнал с ЧРВ. Затем nano передаёт набор из нескольких цифр через серийный порт на контроллер UNO (тепрература 1; температура 2; температура 3; часы; минуты; секунды). Контроллер Uno принимает эти 6 значений и обрабатывает их, давая в итоге информацию на выходе (в порт): требуемое состояние для 4-ёх устройств (по сути - для 4-ёх реле) насос 1, насос 2, котёл, водонагреватель.

Так вот, на мой взгляд, для контроллера UNO я заложил всё что хотел (не хватило ресурсов UNO, поэтому два графика из трёх я вывел с шагом 0,5 градуса, а один - с шагом 0,1 градуса).

Весь макет на данный момент выглядит так:

Главное меню содержит динамические данные с датчиков температуры и ЧРВ, а также отображает состояние исполнительных механизмов и актуальную "опцию" (алгоритм работы):

При нажатии сенсорной кнопки "Temp" - открывается окно с выставлением требуемой температуры в помещении. Здесь задаётся желаемая температура (которую в итоге должен поддерживать контроллер), с шагом 0.5 градуса:

Если с главного окна нажать кнопку "Menu", то открывается окно со ссылками на различные другие окна (меню):

Здесь - кнопки "Out", "Econ", "Fast" - просто меняют "опцию" (актуальный алгоритм работы для контроллера).

Кнопка "Graph" открывает окно с динамическим отображением графиков температур с трёх датчиков:

Кнопка "DiapG" открывает окно с выставлением диапазона по оси времени для графиков (от 2 минут до 18 часов):

Кнопка "Work" открывает окно со статическим отображеним состояний трёх исполнительных механизмов (двух насосов и котла) за прошедшее время - выводится время изменения состояния и само состояние (вкл/выкл) для исполнительных механизмов в это время. Очередная запись делается при изменении состояния хотя бы одного механизма:

Кнопка "Timer" открывает окно с выставлением требуемого времени (часы и минуты) включения системы отопления. Отложенный старт:

Сам код контроллера UNO (весь) на данный момент выглядит так:


#include <Adafruit_GFX.h>    //Подгружается необходимая библиотеа
#include <MCUFRIEND_kbv.h> //Подгружается необходимая библиотеа
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.
#include <TouchScreen.h>  //Подгружается необходимая библиотеа

#define sizeTrend 120 //для графиков, количество точек измерений по оси времени

// most mcufriend shields use these pins and Portrait mode:
uint8_t YP = A1;  // must be an analog pin, use "An" notation!
uint8_t XM = A2;  // must be an analog pin, use "An" notation!
uint8_t YM = 7;   // can be a digital pin
uint8_t XP = 6;   // can be a digital pin
uint8_t Landscape = 0;

uint16_t TS_LEFT = 920;
uint16_t TS_RT = 150;
uint16_t TS_TOP = 940;
uint16_t TS_BOT = 120;


// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define WHITEGREY  0xC638

//Различные переменные:
byte a=0; // номер текущего меню (окна), переменная
int t=225; //переменная, являющаяся уставкой (заданием) требуемой температуры в комнате (225 -> 22.5 градуса)
int tW=400; //переменная для граничных значений уставки (верхняя граница, бОльшая температура диапазона) температуры воды системы отопления в зависимости от температуры наружного воздуха (400 -> 40.0)
byte b=12; //переменная для меню Timer (обозначает час суток)
byte c=30; //переменная для меню Timer (обозначает минуты часа)
boolean j;//переменная для контроля появления новых данных в серийном порту (меняет значение один раз за период поступления данных из порта)
byte d; //переменная для исключения случайных граничных значений (суммарых) - счётчик количетва выполнения условий
byte e; //переменная для исключения случайных граничных значений температуры, для определения, какое условие отслеживается в данный момент:
// e=1 - условие на включение насосов в режиме антизамерзания
// e=2 - условие на выключение насосов в режиме антизамерзания
// e=3 - условие на срабатывание граничных значений пониженной температуры в режиме поддержания температуры в комнате
// e=4 - условие на срабатывание граничных значений повышенной температуры в режиме поддержания температуры в комнате
// e=5 - условие на отключение насосов при выходе из режима антизамерзания в опции Таймер
// e=6 - условие на срабатывание граничных значений пониженной температуры в режиме поддержания температуры в комнате в опции Таймер
// e=7 - условие на срабатывание граничных значений повышенной температуры в режиме поддержания температуры в комнате в опции Таймер
// e=8 - условие на срабатывание граничных значений пониженной температуры в режиме поддержания температуры в комнате в опции Экономия электроэнергии
// e=9 - условие на срабатывание граничных значений повышенной температуры в режиме поддержания температуры в комнате в опции Экономия электроэнергии
// e=10 - условие перехода в режим поддержания температуры в комнате (+5) если температура воздуха в комнате менее 5 градусов (условие на срабатывание)
int g[]={1,3,15,30,90,180,270,360,540}; //переменная, задающая через сколько секунд появляется новое значение на графике
byte h;//переменная задающая индекс для g при выборе диапазона времени отображения графика
int q;//переменная отсчёта количества полученных данных между очередными выводами на график
boolean floorPump=0;//переменная для управления (и отображения состояния) насоса тёплых полов (on/off)
boolean radiatorPump=0;//переменная для управления (и отображения состояния) насоса радиаторов (on/off)
boolean kotel=0;//переменная для управления (и отображения состояния) котлом (on/off)
boolean boiler=0;//переменная для управления (и отображения состояния) водонагревателем (on/off)
boolean prev_fP=0; //переменная для регистрации предыдущего состояния насоса полов (вкл/выкл) для фиксирования времени срабатывания
boolean prev_rP=0; //переменная для регистрации предыдущего состояния насоса радиаторов (вкл/выкл) для фиксирования времени срабатывания
boolean prev_k=0; //переменная для регистрации предыдущего состояния котла (вкл/выкл) для фиксирования времени срабатывания

int in1[sizeTrend]; //массив для графика - температура в комнате
byte in2[sizeTrend]; //массив для графика - температура воды
char in3[sizeTrend]; //массив для графика - температура на улице

byte hourWork[19]; //массив для вывода времени включения и отключения оборудования (часы)
byte minuteWork[19]; //массив для вывода времени включения и отключения оборудования (минуты)
byte secondWork[19]; //массив для вывода времени включения и отключения оборудования (секунды)
boolean floorPumpWork[19]; //массив для создания отображения состояния насоса тёплых полов
boolean radiatorPumpWork[19]; //массив для создания отображения состояния насоса радиаторов
boolean kotelWork[19]; //массив для создания отображения состояния котла

//относится к получению данных из порта:
String inString;
String message;

byte option=0; //переменная для определения (задания), какая опция регулирования активна (в работе) в данный момент:
//0 - "Out of home" (режим антизамерзания системы);
//1 - "Fast heat" режим быстрого прогрева, натоп (масимальная тепловая мощность системы);
//2 - "Room temperature" режим поддержания заданной температуры воздуха в комнате;
//3 - "Timer" режим отложенного пуска системы на прогрев температуры в комнате. Включается при достижении временем заданного значения;
//31 - режим натоп в опции Таймер;
//32 - режим поддержания температуры в комнате в опции Таймер;
//4 - "Economy electricity" режим работы при отключении электричества (основного)


//Код для вырисовывания различных меню (окон):
void getMenu_Main() { //Главное окно (главное меню дисплея) 
   a=0;
   tft.fillScreen(WHITEGREY); //очистка диспея
   getMyButton(2, 268, " Temp"); //вырисовывание сенсорной кнопки
   getMyButton(121, 268, " Menu"); //вырисовывание сенсорной кнопки
   tft.setCursor(0, 5); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(BLACK); // Указываем цвет текста
   tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
   tft.println(" Option:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println();
   tft.println();
   tft.println(" Room:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println();
   tft.println(" Water:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println();   
   tft.println(" Outdoor:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println();
   tft.println(" Time:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println();
   tft.println(" Floor pump:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println(" Radiator pump:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println(" Kotel:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.println(" Water heater:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   delay (300);
   }

  
void getMenu_Temp() { //меню (окошко) выставления требуемой температуры воздуха в помещении
    a=1;
    tft.fillScreen(WHITEGREY); //очистка диспея
    tft.setCursor(0, 5); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(BLACK); // Указываем цвет текста
    tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
    tft.println(" Need room"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    tft.print(" temperature:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    getMyButton(2, 268, " Exit"); //вырисовывание сенсорной кнопки
    getMyButton(121, 268, "  OK"); //вырисовывание сенсорной кнопки
    getMyButton(121, 168, "   v"); //вырисовывание сенсорной кнопки
    getMyButton(121, 116, "   ^"); //вырисовывание сенсорной кнопки
    getLookTemp();
    delay (300);
   }


void getMenu_Other() { //окошко (меню) имеющее сенсорные кнопки (ссылки) на другие требуемые меню
   a=2;
   tft.fillScreen(WHITEGREY); //очистка диспея
   getMyButton(2, 268, " Exit"); //вырисовывание сенсорной кнопки
   getMyButton(121, 268, "  Out"); //вырисовывание сенсорной кнопки
   getMyButton(2, 216, " Fast"); //вырисовывание сенсорной кнопки
   getMyButton(121, 216, " Econ"); //вырисовывание сенсорной кнопки
   getMyButton(2, 164, " Timer"); //вырисовывание сенсорной кнопки
   getMyButton(121, 164, " Work"); //вырисовывание сенсорной кнопки
   getMyButton(2, 112, " Graph"); //вырисовывание сенсорной кнопки
   getMyButton(121, 112, " DiapG"); //вырисовывание сенсорной кнопки
   delay (300); 
  }


void getMenu_Timer() { //окошко (меню) задания времени включения котла в режиме "прогрев"
   a=3;
   tft.fillScreen(WHITEGREY); //очистка диспея
   tft.setCursor(0, 5); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(BLACK); // Указываем цвет текста
   tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
   tft.print(" Wake up at:"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   getMyButton(2, 268, " Exit"); //вырисовывание сенсорной кнопки
   getMyButton(121, 268, "  OK"); //вырисовывание сенсорной кнопки
   getMyButton(0, 168, "   v"); //вырисовывание сенсорной кнопки
   getMyButton(0, 116, "   ^"); //вырисовывание сенсорной кнопки
   getMyButton(121, 168, "   v"); //вырисовывание сенсорной кнопки
   getMyButton(121, 116, "   ^"); //вырисовывание сенсорной кнопки
   delay (300);
  }


void  getMenu_Graphic() { //окошко (меню) отображения графика
   a=4;
   tft.fillScreen(BLACK);
   getMyButton(2, 268, " Press"); //вырисовывание сенсорной кнопки
   delay (300);
  }


void getMenu_Work() { //окно с отображением времени включения/выключения насосов и котла
   a=5;
   tft.fillScreen(BLACK); //очистка диспея
   tft.setCursor(105, 0); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
   tft.setTextColor(MAGENTA); // Указываем цвет текста
   tft.print("Flo ");
   tft.setTextColor(RED); // Указываем цвет текста
   tft.print("Rad ");
   tft.setTextColor(YELLOW); // Указываем цвет текста
   tft.println("Kot");
   tft.setCursor(0, 16); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
    for (byte i; i<19; i++) {
     tft.setTextColor(BLUE); // Указываем цвет текста
      if (hourWork[i]<10) {
       tft.print("0");
       tft.print(hourWork[i]);
     }
    else if (hourWork[i]>=10) {
      tft.print(hourWork[i]);
    }
    tft.print(":");
    if (minuteWork[i]<10) {
      tft.print("0");
      tft.print(minuteWork[i]);
    }
   else if (minuteWork[i]>=10) {
      tft.print(minuteWork[i]);
    }
    tft.print(":");
    if (secondWork[i]<10) {
      tft.print("0");
      tft.println(secondWork[i]);
    }
    else if (secondWork[i]>=10) {
      tft.println(secondWork[i]);
    }

    if (floorPumpWork[i]==1) {
      tft.fillRect (105, (16+i*16), 40, 16, GREEN);
    }
    if (radiatorPumpWork[i]==1) {
      tft.fillRect (152, (16+i*16), 40, 16, GREEN);
    }    
    if (kotelWork[i]==1) {
      tft.fillRect (199, (16+i*16), 40, 16, GREEN);
    }
   }
}


void getMenuDiap() { //меню (окошко) выставления диапазона времени графиков
    a=6;
    tft.fillScreen(WHITEGREY); //очистка диспея
    tft.setCursor(0, 5); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(BLACK); // Указываем цвет текста
    tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
    tft.println(" Diapasone of"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    tft.println(" time at graphic"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    tft.print(" (minutes):"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    getMyButton(2, 268, " Exit"); //вырисовывание сенсорной кнопки
    getMyButton(121, 268, "  OK"); //вырисовывание сенсорной кнопки
    getMyButton(121, 168, "   v"); //вырисовывание сенсорной кнопки
    getMyButton(121, 116, "   ^"); //вырисовывание сенсорной кнопки
    getLookDiap ();
    delay (300);
   }


void changeWorkData() { //перезапись массива, если насос либо котёл поменял состояние, для вывода информации о том, когда и что включалось/выключалось
  for (byte i; i<18; i++) {
    hourWork[i]=hourWork[i+1];
    minuteWork[i]=minuteWork[i+1];
    secondWork[i]=secondWork[i+1];
    floorPumpWork[i]=floorPumpWork[i+1];
    radiatorPumpWork[i]=radiatorPumpWork[i+1];
    kotelWork[i]=kotelWork[i+1];
  } 
}  


void drawTrendRoom(byte widthTrend, byte heightTrend, byte xTrend, int yTrend, int cn, int *in){ //для графика температуры в комнате
 if (q>=g[h]) { //добавляем новое значение
  in[sizeTrend-1]=cn; //сдвигаем график
  for  (byte x=0;x<(sizeTrend-1);x++){
    in[x]=in[x+1];
  }
 }
if (a==4) { //если открыто окно графиков
  byte oldX=0;
  int oldY=0+yTrend;
  int mn=1000;
  int mx=0;
   for (byte i=0;i<sizeTrend;i++){ //поиск мин и макс
    if (in[i]>mx){
      mx=in[i];
    }
    if (in[i]<mn){
      mn=in[i];
    }
  }
  if(mn==mx){
    mx=mn+1;
    mn=mn-1;
  }
  //вывод минимума и максимума
  tft.setTextColor(BLUE);
  tft.setTextSize(2);
  tft.setCursor(xTrend+widthTrend-50,yTrend);
  tft.print(mx/10);
  tft.print(".");
  tft.print(mx%10);
  tft.setCursor(xTrend+widthTrend-50,yTrend+heightTrend-16);
  tft.print(mn/10);
  tft.print(".");
  tft.print(mn%10);
  tft.setCursor(xTrend+widthTrend-50,yTrend+(heightTrend/2)-8);
  tft.print(in[sizeTrend-1]/10); //текущее
  tft.print(".");
  tft.print(in[sizeTrend-1]%10);
  //формирование буфера вывода
  for(byte x=0;x<sizeTrend-1;x++){
    int y=convertTRoom(in[x],mn,mx,yTrend,heightTrend,in);
    byte nxt_x=map(x,0,sizeTrend-1,0,widthTrend);
    //отрисовка тренда
    tft.drawLine(xTrend+oldX, oldY, xTrend+nxt_x, y, YELLOW);
    oldX=nxt_x;
    oldY=y; 
  }
    oldX=0;
    oldY=convertTRoom(in[0],mn,mx,yTrend,heightTrend,in); 
 }
}


void drawTrendWater(byte widthTrend, byte heightTrend, byte xTrend, int yTrend, byte cn, byte *in){ //для графика температуры воды
 if (q>=g[h]) { //добавляем новое значение
  in[sizeTrend-1]=cn; //сдвигаем график
  for  (byte x=0;x<(sizeTrend-1);x++){
    in[x]=in[x+1];
  }
 }
if (a==4) { //если открыто окно графиков
  byte oldX=0;
  int oldY=0+yTrend;
  byte mn=254;
  byte mx=0;
    for (byte i=0;i<sizeTrend;i++){ //поиск мин и макс
    if (in[i]>mx){
      mx=in[i];
    }
    if (in[i]<mn){
      mn=in[i];
    }
  }
  if(mn==mx){
    mx=mn+1;
    mn=mn-1;
  }
  //вывод минимума и максимума
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  tft.setCursor(xTrend+widthTrend-50,yTrend);
  tft.print(mx/2);
  tft.print(".");
  tft.print((mx%2)*5); //остаток от деления на 2 и умноженный на 5
  tft.setCursor(xTrend+widthTrend-50,yTrend+heightTrend-16);
  tft.print(mn/2);
  tft.print(".");
  tft.print((mn%2)*5); //остаток от деления на 2 и умноженный на 5
  tft.setCursor(xTrend+widthTrend-50,yTrend+(heightTrend/2)-8);
  tft.print(in[sizeTrend-1]/2); //текущее
  tft.print(".");
  tft.print((in[sizeTrend-1]%2)*5); //остаток от деления на 2 и умноженный на 5
  //формирование буфера вывода
  for(byte x=0;x<sizeTrend-1;x++){
    int y=convertWater(in[x],mn,mx,yTrend,heightTrend,in);
    byte nxt_x=map(x,0,sizeTrend-1,0,widthTrend);
    //отрисовка тренда
    tft.drawLine(xTrend+oldX, oldY, xTrend+nxt_x, y, YELLOW);
    oldX=nxt_x;
    oldY=y;
    } 
    oldX=0;
    oldY=convertWater(in[0],mn,mx,yTrend,heightTrend,in);
   }
  }


void drawTrendOutdoor(byte widthTrend, byte heightTrend, byte xTrend, int yTrend, char cn, char *in){ //для графика температуры на улице
 if (q>=g[h]) { //добавляем новое значение
  in[sizeTrend-1]=cn; //сдвигаем график
  for  (byte x=0;x<(sizeTrend-1);x++){
    in[x]=in[x+1];
  }
 }
if (a==4) { //если открыто окно графиков
  byte oldX=0;
  int oldY=0+yTrend;
  char mn=126;
  char mx=0;
    for (byte i=0;i<sizeTrend;i++){ //поиск мин и макс
    if (in[i]>mx){
      mx=in[i];
    }
    if (in[i]<mn){
      mn=in[i];
    }
  }
  if(mn==mx){
    mx=mn+1;
    mn=mn-1;
  }
  //вывод минимума и максимума
  tft.setTextColor(MAGENTA); 
  tft.setTextSize(2);
  tft.setCursor(xTrend+widthTrend-50,yTrend);
  tft.print(mx/2);
  tft.print(".");
   if (mx>=0) {
    tft.print((mx%2)*5); //остаток от деления на 2 и умноженный на 5
    }
    if (mx<0) {
     tft.print((-mx%2)*5); //остаток от деления на 2 и умноженный на 5
    }
  tft.setCursor(xTrend+widthTrend-50,yTrend+heightTrend-16);
  tft.print(mn/2);
   if (mn/2 > -10) {
  tft.print(".");
   if (mn>=0) {
    tft.print((mn%2)*5); //остаток от деления на 2 и умноженный на 5
    }
    if (mn<0) {
     tft.print((-mn%2)*5); //остаток от деления на 2 и умноженный на 5
    }
   }
  tft.setCursor(xTrend+widthTrend-50,yTrend+(heightTrend/2)-8);
  tft.print(in[sizeTrend-1]/2); //текущее
  if (in[sizeTrend-1]/2 > -10) {
  tft.print(".");
   if (in[sizeTrend-1]/2>=0) {
    tft.print((in[sizeTrend-1]%2)*5); //остаток от деления на 2 и умноженный на 5
    }
    if (in[sizeTrend-1]/2<0) {
     tft.print((-in[sizeTrend-1]%2)*5); //остаток от деления на 2 и умноженный на 5
    }
   }
  for(byte x=0;x<sizeTrend-1;x++){ //формирование буфера вывода
    int y=convertOutDoor(in[x],mn,mx,yTrend,heightTrend,in);
    byte nxt_x=map(x,0,sizeTrend-1,0,widthTrend);
    //отрисовка тренда
    tft.drawLine(xTrend+oldX, oldY, xTrend+nxt_x, y, YELLOW);
    oldX=nxt_x;
    oldY=y;
    }
    oldX=0;
    oldY=convertOutDoor(in[0],mn,mx,yTrend,heightTrend,in);
   }
  }


//для графиков:
int convertTRoom(int y, int mn, int mx, int yTrend, byte heightTrend, int *in ){ //температура в комнате
  int ny=y;
  ny=map(ny,mn,mx,heightTrend-1+yTrend,yTrend);
  return ny;
}
int convertWater(int y, byte mn, byte mx, int yTrend, byte heightTrend, byte *in ){ //температура воды
  int ny=y;
  ny=map(ny,mn,mx,heightTrend-1+yTrend,yTrend);
  return ny;
}
int convertOutDoor(int y, char mn, char mx, int yTrend, byte heightTrend, char *in ){ //температура на улице
  int ny=y;
  ny=map(ny,mn,mx,heightTrend-1+yTrend,yTrend);
  return ny;
}
  

//вырисовывание сенсорных кнопок:
   int getMyButton(byte x1, int y1, String z1){
    tft.fillRoundRect (x1, y1, 117, 50, 15, BLUE); //залитый прямоугольник со скруглёнными углами
    y1=y1+13; 
    tft.setCursor(x1, y1); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(WHITE); // Указываем цвет текста
    tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
    tft.print(z1); // Выводим текст на экран, текст будет выведен с указанными выше настройками
}

//выводим значение заданной температуры (в меню настройки задания):
   byte getLookTemp() {
    tft.setCursor(30, 160); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста и заливки
    tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
    tft.print(t/10); // Выводим текст на экран, текст будет выведен с указанными выше настройками
    tft.print(".");
    tft.print(t%10);
    if (t<100){ //чтобы избежать наложения вывода цифр
    tft.print(" ");
    }
   }

//выводим значение заданного времени включения по таймеру (в меню таймера):
   byte getLookDate() {
    tft.setCursor(75, 50); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(BLACK, WHITEGREY); // Указываем цвет текста и заливки
    tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
    tft.print(b); // Выводим текст на экран, текст будет выведен с указанными выше настройками
     if (b<10){
      tft.print(" "); 
      }
   tft.print(":"); //значок двоеточия для времени
   tft.print(c); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }

//выводим значение заданного диапазона времени графика (в меню настройки задания):
   byte getLookDiap() {
    tft.setCursor(5, 160); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(BLACK, WHITEGREY); // Указываем цвет текста и заливки
    tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
     if (g[h]<=15) {
      tft.print(g[h]*2); // Выводим текст на экран, текст будет выведен с указанными выше настройками
      tft.print("min ");
      }
     else if (g[h]>15){ //чтобы избежать наложения вывода цифр
      tft.print(g[h]/30); // Выводим текст на экран, текст будет выведен с указанными выше настройками
      tft.print("hour");
    }
   }   


void setup()
{
    Serial.begin(9600);
    tft.reset();

    //Found ILI9341 LCD driver DealExtreme
    TS_LEFT = 893; TS_RT = 145; TS_TOP = 930; TS_BOT = 135;
    Landscape = 1;
        
    ts = TouchScreen(XP, YP, XM, YM, 300);     //call the constructor AGAIN with new values.
    tft.begin(0); //включение дисплея
    
    #define MINPRESSURE 10
    #define MAXPRESSURE 1000
    
    tft.fillScreen(WHITEGREY); //очистка диспея
    getMenu_Main(); //при загрузке - на экран выводится главное окно (меню)
    delay(1000);  //задержка 1 сек
}


void loop() { //главный повторяющийся цикл

//относится к тачскрину:
   int tmp;
    TSPoint p = ts.getPoint();

    // if sharing pins, you'll need to fix the directions of the touchscreen pins
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    // we have some minimum pressure we consider 'valid'
    // pressure of 0 means no pressing!
  
        if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
        if (Landscape) {   // swap X and Y
            tmp = p.x;
            p.x = p.y;
            p.y = tmp;
        }

        // scale from 0->1023 to tft.width  i.e. left = 0, rt = width
        // most mcufriend have touch (with icons) that extends below the TFT 
        // screens without icons need to reserve a space for "erase"
        p.x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
        p.y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
        }


//перемещения по меню и нажатие сенсорных кнопок:
if (((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)) and a==0) { //главное окно
 getMenu_Temp();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 267) and (p.y <= 319)) and a==0) {
 getMenu_Other();
 }
  
else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)) and a==1) { //меню выставления задания температуры "tempRoom"
 getMenu_Main();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 267) and (p.y <= 319)) and a==1) {
 option=2;
 getMenu_Main();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 167) and (p.y < 220)) and a==1) {
 if (t>90) {
  t=t-5;
  }
 else {
  t=90;
 }
  getLookTemp();
  delay(300);
  }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 115) and (p.y < 167)) and a==1) {
 if (t<300) {
  t=t+5;
  }
 else {
  t=300;
 }
 getLookTemp();
 delay(300);
 }

else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)) and a==2) { //окно с ссылками на различные другие меню
 getMenu_Main();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 267) and (p.y <= 319)) and a==2) {
 option=0;
 getMenu_Main();
 }
else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 215) and (p.y <= 266)) and a==2) {
 option=1;
 getMenu_Main();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 215) and (p.y <= 266)) and a==2) {
 option=4;
 getMenu_Main();
 } 
else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 163) and (p.y <= 214)) and a==2) {
 getMenu_Timer();
 getLookDate();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 163) and (p.y <= 214)) and a==2) {
 getMenu_Work();
 } 
else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 111) and (p.y <= 162)) and a==2) {
 getMenu_Graphic();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 111) and (p.y <= 162)) and a==2) {
 getMenuDiap();
 } 
  
else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)) and a==3) { //окно с таймером (выставление времени включения по таймеру)
 getMenu_Main();
 }
else if (((p.x >= 0) and (p.x <= 119) and (p.y > 115) and (p.y < 167)) and a==3) {
 if (b<23) {
 b=b+1;
 }
else {
 b=23;
 }
 getLookDate();
 delay(300);
 }
else if (((p.x >= 0) and (p.x <= 119) and (p.y > 167) and (p.y < 219)) and a==3) {
 if (b>0) {
  b=b-1;
  }
 else {
  b=0;
  }
 getLookDate();
 delay(300);
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 115) and (p.y < 167)) and a==3) {
 if (c<50) {
  c=c+10;
  }
 else {
  c=50;
  }
 getLookDate();
 delay(300);
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 167) and (p.y < 219)) and a==3) {
 if (c>0) {
  c=c-10;
  }
 else {
  c=0;
  }
 getLookDate();
 delay(300);
 }
 else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 267) and (p.y <= 319)) and a==3) {
 option=3;
 getMenu_Main();
 }

else if (((p.x >= 0) and (p.x <= 239) and (p.y >= 0) and (p.y <= 259)) and a==4) { //окно с выводами графиков
 getMenu_Main();
 }

else if (((p.x >= 0) and (p.x <= 239) and (p.y >= 0) and (p.y <= 319)) and a==5) { //окно с выводами времени включения/отключения насосов и котла
 getMenu_Main();
 }

else if (((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)) and a==6) { //меню выставления диапазона времени составления графика
 getMenu_Main();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y >= 267) and (p.y <= 319)) and a==6) {
 getMenu_Other();
 }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 167) and (p.y < 220)) and a==6) {
 if (h>0) {
  h=h-1;
  }
 else {
  h=0;
 }
  getLookDiap();
  delay(300);
  }
else if (((p.x >= 120) and (p.x <= 239) and (p.y > 115) and (p.y < 167)) and a==6) {
 if (h<8) {
  h=h+1;
  }
 else {
  h=8;
 }
 getLookDiap();
 delay(300);
 }
 
  
 j=1; //счётчик меняется с 1 на 0, если поступают данные через порт 
//принимаем строку из порта:
  while (Serial.available()) {
    char inChar = Serial.read();
    inString += inChar; 
    if (inChar == 'E') {
      Serial.println(inString);
      message = inString;
      inString = "";
      j=0; //отслеживает момент поступления данных через порт
     }
  }

 byte Index1 = message.indexOf(';');             //разделительный символ
 byte Index2 = message.indexOf(';', Index1+1);   //вычисляем второй символ
 byte Index3 = message.indexOf(';', Index2+1);   //...третий и т.д.
 byte Index4 = message.indexOf(';', Index3+1);
 byte Index5 = message.indexOf(';', Index4+1);
 byte Index6 = message.indexOf(';', Index5+1);

 String Val_1 = message.substring(0, Index1);//что-то перед первым разд. сим.
 String Val_2 = message.substring(Index1+1, Index2);//что-то за первым разд. сим.
 String Val_3 = message.substring(Index2+1, Index3);//за вторым и т.д
 String Val_4 = message.substring(Index3+1, Index4); 
 String Val_5= message.substring(Index4+1, Index5); 
 String Val_6 = message.substring(Index5+1, Index6);

 int tempRoom=Val_1.toInt();
 int tempWater=Val_2.toInt();
 int tempOutdoor=Val_3.toInt();
 byte hours=Val_4.toInt();
 byte minutes=Val_5.toInt();
 byte seconds=Val_6.toInt();

//Задаю значения датчиков с реально возможным диапазоном (чтобы фиксировать ошибки датчиков):
 if (tempRoom<0 || tempRoom>400) { //температура в комнате нереальная
   tft.setCursor(10, 10); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
   tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
   tft.print("t_Room eror"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   floorPump=1; //включить насос тёплых полов
   radiatorPump=1; //включить насос радиаторов
   kotel=0; //выключить котёл 
}

 if ( tempWater<0 and  tempWater>900) { //температура воды в системе отопления
   tft.setCursor(10, 70); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
   tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
   tft.print("t_Water eror"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   floorPump=1; //включить насос тёплых полов
   radiatorPump=1; //включить насос радиаторов
   kotel=0; //выключить котёл
}

 if (tempOutdoor<-300 || tempOutdoor>500) { //температура на улице нереальная
   tft.setCursor(10, 40); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
   tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
   tft.print("t_Out eror"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   floorPump=1; //включить насос тёплых полов
   radiatorPump=1; //включить насос радиаторов
   kotel=0; //выключить котёл
}

 if (hours<0 || hours>23 || minutes<0 || minutes>59 || seconds<0 || seconds>59) { //часы нереальные
   tft.setCursor(10, 110); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
   tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
   tft.print("Time eror"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   option=0; //перейти в режим антизамерзания 
}

 if (j==0){ //ели поступили данные через порт
//Зависимость температуры уставки (верхняя граница) воды системы отопления от значения температуры наружного воздуха:
 tW = (500 - (0.8*tempOutdoor)); 

//условие включения водонагревателя:
 if (hours < 7 || hours >= 23) {
   boiler=1; 
 }
 else {
  boiler=0;
 }  

//Код для программ (обций), алгоритм работы контоллера: 
//сброс счётчика d
 if ((e==1 and !(tempRoom > 50 and (tempRoom <= 80 || tempWater <= 80))) || (e==2 and !(tempRoom > 80 and tempWater >80)) || (e==3 and !(tempRoom < (t-5) and tempOutdoor <= 180)) || 
  (e==4 and !(tempRoom > (t+5))) || (e==5 and !(tempRoom > 80 and tempWater >80)) || (e==6 and !(tempOutdoor <= 180 and tempRoom < (t-5))) || (e==7 and !(tempRoom > (t+5))) || 
  (e==8 and !(tempRoom < (t-5) and tempOutdoor <= 180)) || (e==9 and !(tempRoom > (t+5))) || (e==10 and !(tempRoom <= 50))) {
  d=0;
  }
  
 //Режим антизамерзания (работает всегда)
 if (tempRoom <= 50 ) { //условие перехода в режим поддержания положительной температуры в комнате (антизамерзание)
   e=10;
   d++;
    if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
     floorPump=1; //включить насос тёплых полов
     radiatorPump=1; //включить насос радиаторов
      if (tempWater > 250) {
          kotel=0; //выключить котёл
         }
      if (tempWater <= 250){
          kotel=1; //включить котёл
         }
     d=0;
   }
 }

 if (tempRoom > 50 and (tempRoom <= 80 || tempWater <= 80)) { //условие на включение насосов (выполняется при любом режиме системы)
   e=1;
   d++;
    if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
     floorPump=1; //включить насос тёплых полов
     radiatorPump=1; //включить насос радиаторов
     kotel=0; //выключить котёл  
     d=0;
    }
  }


 //1. Опция "Вне дома" (Out of home):
 if (option==0) {  
  if (tempRoom > 80 and tempWater >80) { //условие на выключение насосов (выполняется только при режиме "Out of home" и "Timer")
    e=2;
    d++;
    if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
    floorPump=0; //выключить насос тёплых полов
    radiatorPump=0; //выключить насос радиаторов
    kotel=0; //выключить котёл  
    d=0;
    }
  }
}    


//2. Опция "Натоп" (Fast heat), алгоритм работы:
 if (option==1) {
  if (tempRoom < (t-20)) {
   kotel=1; //включить котёл
   floorPump=1; //включить насос тёплых полов
   radiatorPump=1; //включить насос радиаторов  
   }
  if (tempRoom >= (t-20)) {
   option=2; //переходим в режим поддержания температуры в комнате
   getMenu_Main(); //для обновления информации о действующей опции на главном экране
   }
 }

 //3. Опция поддержания температуры в комнате ("Room temperature"):
 if (option==2) {
  if (tempRoom < (t-5) and tempOutdoor <= 180) { //система запустится, если температура на улице менее 18 градусов
    e=3;
    d++;
     if (d>=20){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
      floorPump=1; //включить насос тёплых полов
       if (tempWater >= (tW-100)){
        kotel=0; //выключить котёл
         if (tW <= 500) {
          radiatorPump=0; //выключить насос радиаторов
          }
        else if (tW > 500) {
          radiatorPump=1; //включить насос радиаторов
         }
       }
    if (tempWater < (tW-100)){
     kotel=1; //включить 
      if (tW <= 500) {
       radiatorPump=0; //выключить насос радиаторов
       }
      else if (tW > 500) {
       radiatorPump=1; //включить насос радиаторов
       }
      }
    d=0;
   }
  }


 if (tempWater >= tW) { //если температура воды в системе отопления достигла верхней уставки - выключить котёл
  kotel=0; //выключить котёл   
  }

 if (tempRoom > (t+5)) {
  e=4;
  d++;
   if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
    kotel=0; //выключить котёл
    floorPump=0; //выключить насос тёплых полов
    radiatorPump=0; //выключить насос радиаторов
    d=0;
    }
  }
}

 //4. Опция "Timer". Запуск системы в заданное время (работает в пределах текущих суток). На следующие сутки - поставить нельзя
 if (option==3) {
  if (hours<=b) {
   if (tempRoom > 80 and tempWater >80) { //условие на выключение насосов (выполняется только при режиме "Out of home" и "Timer")
    e=5;
    d++;
     if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
     floorPump=0; //выключить насос тёплых полов
     radiatorPump=0; //выключить насос радиаторов  
     kotel=0; //выключить котёл
     d=0;
    }
  }
}

 if (hours>=b and minutes>=c) {
  option=31; //фиксирование момента срабатывания таймера
  getMenu_Main(); //для обновления информации о действующей опции на главном экране
  }
 }

 if (option==31) {
  if (tempOutdoor <= 180 and tempRoom < (t-20)) { //режим "натоп" в опции "Timer"
   kotel=1; //включить котёл
   floorPump=1; //включить насос тёплых полов
   radiatorPump=1; //включить насос радиаторов
   }

 if (tempRoom >= (t-20)) { //переход в режим "температуры в комнате" в опции "Timer"
  option=32; //переходим в режим поддержания заданной температуры в комнате в опции Таймер
  getMenu_Main(); //для обновления информации о действующей опции на главном экране
  }
 }

 if (option==32) {
  if (tempOutdoor <= 180 and tempRoom < (t-5)) { //режим поддержания температуры в комнате в опции "Timer"
    e=6;
    d++;
     if (d>=20){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
      floorPump=1; //включить насос тёплых полов
       if (tempWater >= (tW-100)){
        if (tW <= 500) {
          radiatorPump=0; //включить насос радиаторов
          }
        else if (tW > 500) {
         radiatorPump=1; //включить насос радиаторов
         }
      }
    if (tempWater < (tW-100)){
     kotel=1; //включить котёл
      if (tW <= 500) {
       radiatorPump=0; //включить насос радиаторов
       }
      else if (tW > 500) {
       radiatorPump=1; //включить насос радиаторов
       }
      }
    d=0;
  }
}

  if (tempWater >= tW) { //если температура воды в системе отопления достигла верхней уставки - выключить котёл
   kotel=0; //выключить котёл   
   }

  if (tempRoom > (t+5)) {
   e=7;
   d++;
    if (d>=10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
     kotel=0; //выключить котёл
     floorPump=0; //выключить насос тёплых полов
     radiatorPump=0; //выключить насос радиаторов
     d=0;
    }
   }


   //завершение работы опции "Timer" (переход на опцию "Out of home") если через 4 часа после начала работы опции "Timer" не была выбрана какая-либо другая опция работы
  if ((b==20 and hours >= 0) || (b==21 and hours >= 1) || (b==22 and hours >= 2) || (b==23 and hours >= 3) || (hours >= (b+4)) and minutes>=c)  {
    option=0;
    getMenu_Main(); //для обновления информации о действующей опции на главном экране
   }
 }  
 
 //5. Режим работы экономии электроэнергии (опция "Economy electricity"):
 if (option==4) {
  floorPump=0; //выключить насос тёплых полов
   if (tempRoom < (t-5) and tempOutdoor <= 180) { //система запустится, если температура на улице менее 18 градусов
    e=8;
    d++;
     if (d>=20){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
      radiatorPump=1; //включить насос радиаторов
       if (tempWater >= 300){ //если температура воды более 30 градусов
        kotel=0; //выключить котёл
        }
       if (tempWater < 300){
        kotel=1; //включить котёл
        }
      d=0;
   }
 }

  if (tempWater >= 700) { //если температура воды в системе отопления достигла 70 градусов - выключить котёл
   kotel=0; //выключить котёл
   }
  
  if (tempRoom > (t+5)) {
   e=9;
   d++;
    if (d==10){ //задаём условие, что граничное условие должно быть выполнено d раз подряд
      kotel=0; //выключить котёл
      radiatorPump=0; //выключить насос радиаторов
      d=0;
    }
  }
}
 
 Serial.print(floorPump);
 Serial.print(";");
 Serial.print(radiatorPump);
 Serial.print(";");
 Serial.print(kotel);
 Serial.print(";");
 Serial.print(boiler);
 Serial.print(";");
 Serial.println("D");

//вырисовывание графиков:
 q++; 
  if (q>=g[h] and a != 4) { //если прошло q секунд и окно графиков не открыто
    drawTrendRoom(0,0,0,0,tempRoom, in1);
    drawTrendWater(0,0,0,0,tempWater/5,in2);
    drawTrendOutdoor(0,0,0,0,tempOutdoor/5,in3);
    q=0;
  }

  else if ((q>=g[h] and a == 4) || (a == 4 and ((p.x >= 0) and (p.x <= 119) and (p.y >= 267) and (p.y <= 319)))) { //если прошло q секунд и окно графиков открыто или если открыто окно графиков и нажата кнопка
   tft.fillScreen(BLACK);
//сетка по времени:
   for (byte i=1; i<6; i++) {
    for (byte k=1; k<4; k++) {
     tft.drawFastVLine(40*i, 106*k, -10, CYAN);
     }
    }
   tft.drawFastHLine(0, 105, 240, BLUE);
   tft.drawFastHLine(0, 212, 240, RED);
   tft.drawFastHLine(0, 319, 240, MAGENTA);

   tft.setCursor(30, 1); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(1); // Указываем размер символов в строке от 1 до 3
   tft.setTextColor(BLUE);
   tft.print("Room temperature"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   drawTrendRoom(240,106,0,0,tempRoom,in1);

   tft.setCursor(30, 107); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(1); // Указываем размер символов в строке от 1 до 3
   tft.setTextColor(RED);
   tft.print("Water temperature"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   drawTrendWater(240,106,0,106,tempWater/5,in2);
    
   tft.setCursor(30, 214); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(1); // Указываем размер символов в строке от 1 до 3
   tft.setTextColor(MAGENTA);
   tft.print("Outdoor temperature"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   drawTrendOutdoor(240,106,0,213,tempOutdoor/5,in3);

   tft.setCursor(5, 300); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(1); // Указываем размер символов в строке от 1 до 3
   tft.print("Last "); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(g[h]*2); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(" minutes"); // Выводим текст на экран, текст будет выведен с указанными выше настройками

    if (q>=g[h]) {     
     q=0;
    }
  }
}

 
//динамические данные в главном окне:
if (a==0){ //вывод актуальной функции (какой процесс в работе), температур с датчиков DS18B20, времени с ЧРВ, состояние насосов, котла, водонагревателя - на главном экране

 //отображение на дисплее, какая функция в работе в данный момент:
  tft.setCursor(100, 5); // Устанавливаем курсор (X = , Y = )
  tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
  if (option==0){
   tft.setTextColor(BLACK); // Указываем цвет текста
   tft.println("Out of home");
   }
  else if (option==1){
   tft.setTextColor(RED); // Указываем цвет текста
   tft.print("Fast heat");
   }
  else if (option==2){
   tft.setTextColor(BLUE); // Указываем цвет текста
   tft.println("Room");
   tft.print("        temperature");
   }
  else if (option==3){
   tft.setTextColor(BLACK); // Указываем цвет текста
   tft.println("Timer");
   tft.print("      wake up:");
   tft.setTextColor(BLUE); // Указываем цвет текста
   tft.print(b);
   tft.print(":");
   tft.print(c);
   }
  else if (option==31){
   tft.setTextColor(BLUE); // Указываем цвет текста
   tft.println("Fast heat");
   tft.print("         by Timer");
   }   
  else if (option==32){
   tft.setTextColor(BLUE); // Указываем цвет текста
   tft.println("Room temp");
   tft.print("        by Timer");
   }     
  else if (option==4){
   tft.setTextColor(RED); // Указываем цвет текста
   tft.println("Economy");
   tft.print("        electricity");
   } 
  
 //температуры:
   tft.setCursor(140,46); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
   tft.setTextSize(3); // Указываем размер символов в строке от 1 до 3
   tft.print(tempRoom/10); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(".");
   tft.print(tempRoom%10);
   if (tempRoom<100){ //стирание последнего знака от предыдущих значений
   tft.print(" ");
   }
   tft.setCursor(140,77); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста   
   tft.print(tempWater/10); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(".");
   tft.print(tempWater%10);
   if (tempWater<100){ //стирание последнего знака от предыдущих значений
   tft.print(" ");
   }   
   tft.setCursor(140,110); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(BLACK, WHITEGREY); // Указываем цвет текста   
   tft.print(tempOutdoor/10); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(".");
   if (tempOutdoor>=0) {
   tft.print(tempOutdoor%10);
   }
   if (tempOutdoor<0) {
    tft.print(-tempOutdoor%10);
   }
   if (tempOutdoor>-100 and tempOutdoor<100){ //стирание последнего знака от предыдущих значений
   tft.print(" ");
   }


 //время:
   tft.setCursor(80, 145); // Устанавливаем курсор (X = , Y = )
   tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
   if (hours<10){   
   tft.print("0"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(hours); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }
   else {
   tft.print(hours); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }
   tft.print(":"); //двоеточие для времени
   if (minutes<10){   
   tft.print("0"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(minutes); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }
   else {
   tft.print(minutes); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }

   tft.setCursor(173, 152); // Устанавливаем курсор (X = , Y = )
   tft.setTextSize(2); // Указываем размер символов в строке от 1 до 3
   if (seconds<10){   
   tft.print("0"); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   tft.print(seconds); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }
   else {
   tft.print(seconds); // Выводим текст на экран, текст будет выведен с указанными выше настройками
   }

 //отображение состояния насосов, котла, водонагревателя (вкл/выкл):
   tft.setCursor(180, 181); // Устанавливаем курсор (X = , Y = )
   if (floorPump==1){
    tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
    tft.print ("ON ");
    }
   if (floorPump==0){
    tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
    tft.print ("OFF");
    }

    tft.setCursor(180, 197); // Устанавливаем курсор (X = , Y = )
   if (radiatorPump==1){
    tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
    tft.print ("ON ");
    }
   if (radiatorPump==0){
    tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
    tft.print ("OFF");
    }

    tft.setCursor(180, 213); // Устанавливаем курсор (X = , Y = )
   if (kotel==1){
    tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
    tft.print ("ON ");
    }
   if (kotel==0){
    tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
    tft.print ("OFF");
    }

    tft.setCursor(180, 229); // Устанавливаем курсор (X = , Y = )
   if (boiler==1){
    tft.setTextColor(RED, WHITEGREY); // Указываем цвет текста
    tft.print ("ON ");
    }
   if (boiler==0){
    tft.setTextColor(BLUE, WHITEGREY); // Указываем цвет текста
    tft.print ("OFF");
    }
   }


 //вывод на дисплей информации, когда включался/отключался насосы и котёл:
 if (floorPump != prev_fP || radiatorPump != prev_rP || kotel != prev_k) { //если изменилось состояние
  changeWorkData();
  hourWork[18]=hours;
  minuteWork[18]=minutes;
  secondWork[18]=seconds;
  floorPumpWork[18]=floorPump;
  radiatorPumpWork[18]=radiatorPump;
  kotelWork[18]=kotel;
  if (floorPump != prev_fP) { 
   prev_fP=floorPump; //запоминаем состояние оборудования
   }
  if (radiatorPump != prev_rP) { 
   prev_rP=radiatorPump; //запоминаем состояние оборудования
   }
  if (kotel != prev_k) { 
   prev_k=kotel; //запоминаем состояние оборудования
   }
 }
}

Понимаю, что код очень длинный и разбираться в нём вряд ли у кого-то будет желание. Но всё же прошу (и надеюсь) - посмотрите, укажите на грубые мои ошибки. Хотя бы подскажите, где какие куски кода следует расположить (что надо переместить до void setup, что до loop setup, что после и т.д. Подскажите, как можно оптимизировать код, чтобы поменьше отбирать ресурсов у UNO и чтобы контроллер работал надёжнее (уменьшить вероятность сбоев).

PPeterr
PPeterr аватар
Offline
Зарегистрирован: 12.09.2017

Здравствуйте.

Собрал систему, подключил к реальному оборудованию. Очень доволен результатом. Подробнее отписался на этом форуме: https://www.forumhouse.ru/threads/307717/page-4

Всем кто помогал мне здесь - огромное спасибо.

sadman41
Offline
Зарегистрирован: 19.10.2016

А ежели на дощечку капиток хлынет?

b707
Offline
Зарегистрирован: 26.05.2017

PPeterr пишет:

Здравствуйте.

Собрал систему, подключил к реальному оборудованию. Очень доволен результатом. Подробнее отписался на этом форуме: https://www.forumhouse.ru/threads/307717/page-4

Всем кто помогал мне здесь - огромное спасибо.

Отлично, особенно поражает работа с экраном. Удивительно, что код с таким количеством графики и внешних библиотек влез в ардуино Уно...