КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Урок 39. Реле времени: управление устройствами по таймеру

В этом уроке мы создадим четырёхканальное реле времени. К данному устройству можно подключить до 4 приборов (лампочки, светодиодные ленты, моторы, обогреватели, вентиляторы и т.д.), каждое из которых будет включаться на заданные для него промежутки времени суток и в заданные дни недели.

Каждый из четырёх каналов нашего реле времени может выдавать не только логические уровни (1/0 - вкл/выкл), но и сигналы ШИМ (включать приборы на определённую мощность).

В реле времени имеется 20 таймеров (их количество можно уменьшить или увеличить до 128, указав нужное число в строке 16 скетча). Один таймер включает только одно устройство (канал) на заданный промежуток времени, не влияя на работу остальных устройств (каналов). Каждому устройству (каналу) можно назначить несколько таймеров, следовательно, включать и выключать каждое из устройств можно несколько раз в сутки и на разную мощность. При отключении питания, таймеры реле не сбиваются, так как их настройки хранятся в энергонезависимой памяти Arduino. Текущее время также не сбивается, так как оно берётся из модуля часов реального времени, который снабжен батарейкой.

Реле времени можно использовать для включения освещения по времени в доме, квартире, на даче, на производстве и т.д. Можно использовать для включения по времени вентиляции, котлов, обогревателей, полива газонов, систем очистки дачных бассейнов и т.д. Еще одним преимуществом реле времени является создание эффекта присутствия, например, Вас нет дома, но свет утром и вечером включается, а днём и ночью выключается, утром включается радио или телевизор, а ночью включается ночник. Это может заставить задуматься нежелательных «гостей», что дом обитаем и делать там нечего.

Нам понадобится:

Для реализации проекта нам необходимо установить библиотеки:

  • LiquidCrystal_I2C для работы с символьными ЖК дисплеями.
  • iarduino_Encoder_tmr для работы с энкодерами через аппаратный таймер.
  • iarduino_RTC для работы с модулями реального времени.
  • Библиотеки EEPROM, Wire и pgmspace используемые в скетче, входят в стандартный набор Arduino IDE.

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Видео:


Схема подключения:

Trema модуль RTC и дисплей LCD1602 I2C подключаются к аппаратной шине I2C через Trema I2C HUB, а Trema энкодер можно подключать к любым (цифровым или аналоговым) выводам Arduino, их номера указываются в скетче (в примере использованы выводы D4, D7 и D8). Для удобства подключения используется Trema Shield.

Реле времени: включение нагрузки по времени

Приборы подключаются к каналам 1-4:
  • Маломощные приборы с питанием 5 В постоянного тока до 20 мА можно подключать напрямую к одному из каналов.
  • Приборы с питанием до 30 В постоянного тока подключаются через силовой ключ.
  • Приборы с питанием от сети 220 В переменного тока подключаются через твердотельное или электромеханическое реле.
Подключение светодиода Схема подключения силового ключа Схема подключения твердотельного реле

Алгоритм работы:

Режим просмотра времени: При включении питания на индикаторе отображается текущее время, дата и день недели. Номера включённых каналов отображаются в правом верхнем углу дисплея.

Меню: Для входа в меню нужно нажать на энкодер. Далее поворачивая экодер вправо или влево можно выбрать разделы «ТАЙМЕРЫ», «ЧАСЫ», «ВЫХОД», для входа в требуемый раздел нужно опять нажать на энкодер.

Меню>часы: В данном разделе меню, поворачивая энкодер вправо или влево, можно выбрать разделы «ВРЕМЯ», «ДАТА», «ВЫХОД», для входа в требуемый раздел нужно нажать на энкодер.

Меню>часы>время: Этот раздел меню предназначен для установки текущего времени. Устанавливаемый в данный момент параметр времени (часы, минуты, секунды) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>часы>дата: Этот раздел меню предназначен для установки текущей даты и дня недели. Устанавливаемый в данный момент параметр даты (день, месяц, год, день недели) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>таймеры: В данном разделе меню, поворачивая энкодер вправо или влево, можно выбрать один из установленных таймеров (для их редактирования) или разделы «НОВЫЙ ТАЙМЕР», «СТЕРЕТЬ ВСЕ ТАЙМЕРЫ», «ВЫХОД», для входа в требуемый раздел нужно нажать на энкодер. Установленные таймеры отображаются в виде строки из времени их старта/сброса и номера канала «00:00-00:00-0».

Меню>таймеры>новый таймер: Выбор данного раздела приведёт к созданию нового таймера, на экране отобразится надпись «НОВЫЙ ТАЙМЕР СОЗДАН» после чего Вам будет предложено ввести время старта/сброса и указать номер канала (который будет включаться данным таймером). Данный раздел меню недоступен если установлены все таймеры.

Меню>таймеры>стереть все таймеры: Выбор данного раздела приведёт к удалению всех таймеров, на экране отобразится надпись «ВСЕ ТАЙМЕРЫ УДАЛЕНЫ». Данный раздел меню недоступен если нет ни одного установленного таймера.

Меню>таймеры>00:00-00:00-0: Вместо «00:00-00:00-0» будет строка из времени старта/сброса таймера и номера канала которым он управляет. Данный раздел меню предназначен для редактирования выбранного таймера, поворачивая энкодер вправо или влево, можно выбрать разделы «ВРЕМЯ И КАНАЛ», «ПОВТОРЫ», «УРОВЕНЬ СИГНАЛА», «СТЕРЕТЬ ТАЙМЕР», «ВЫХОД», для входа в требуемый раздел нужно нажать на энкодер.

Меню>таймеры>00:00-00:00-0>время и канал: Этот раздел меню предназначен для установки (редактирования) времени старта/сброса таймера и номера канала которым он управляет. Устанавливаемый в данный момент параметр (час старта, минута старта, час сброса, минута сброса, номер канала) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>таймеры>00:00-00:00-0>повторы: Этот раздел меню предназначен для установки (редактирования) повторов таймера по дням недели, в которые он должен срабатывать. Под устанавливаемым в данный момент параметром (ПН, ВТ, СР, ЧТ, ПТ, СБ, ВС) должен мигать курсор. Поворот энкодера устанавливает или сбрасывает стрелочку под устанавливаемым параметром, если она установлена значит в этот день недели таймер будет срабатывать, иначе он срабатывать не будет. Переход к следующему дню недели осуществляется нажатием на энкодер.

Меню>таймеры>00:00-00:00-0>уровень сигнала: Этот раздел меню предназначен для установки (редактирования) уровня сигнала на выбранном канале при срабатывании таймера. Выбор уровня сигнала от 5% до 100% осуществляется поворотом энкодера с шагом 5%, а нажатие на энкодер приведёт к выходу из данного раздела.

Меню>таймеры>00:00-00:00-0>стереть таймер: Выбор данного раздела приведёт к удалению выбранного таймера, на экране отобразится надпись «ТАЙМЕР УДАЛЕН».

Примеры:

Создание таймера который по будням, между 18:00 и 20:00, будет включать 4 канал с уровнем сигнала 100%:
  • Нажмите на энкодер для входа в меню.
  • Поворачивайте энкодер пока не увидите раздел «ТАЙМЕРЫ» и войдите в него нажав на энкодер.
  • Поворачивайте энкодер пока не увидите раздел «НОВЫЙ ТАЙМЕР» и войдите в него нажав на энкодер.
  • Введите «18:00-20:00 к4». Выбор значений осуществляется поворотом энкодера, а переход к следующему - нажатием.
  • Поворачивайте энкодер пока не увидите раздел «ПОВТОРЫ» и войдите в него нажав на энкодер.
  • Установите галочки под «ПН, ВТ, СР, ЧТ, ПТ». Установка осуществляется поворотом энкодера, а переход - нажатием.
  • Поворачивайте энкодер пока не увидите раздел «УРОВЕНЬ СИГНАЛА» и войдите в него нажав на энкодер.
  • Установите значение «100%». Выбор значения осуществляется поворотом энкодера, а установка - нажатием.
  • Поворачивайте энкодер пока не увидите раздел «ВЫХОД» и выйдите из редактирования таймера нажав на энкодер.
  • Поворачивайте энкодер пока не увидите раздел «ВЫХОД» и выйдите из раздела «ТАЙМЕРЫ» нажав на энкодер.
  • Поворачивайте энкодер пока не увидите раздел «ВЫХОД» и выйдите из «МЕНЮ» нажав на энкодер.

Теперь на экране отображается текущее время, дата и день недели, а по будням, с 18:00 до 20:00 в правом верхнем углу экрана будет отображаться цифра 4, при этом на 4 канале будет установлен уровень логической «1» (сигнал ШИМ со 100% заполнением). На остальных каналах будет уровень логического «0».

Создание таймера который между 19:00 и 21:00 каждого дня, будет включать 3 канал с уровнем сигнала 50%:
    Повторите все шаги из предыдущего примера, но:
  • Вместо «18:00-20:00 к4» введите «19:00-21:00 к3».
  • Вместо «ПН, ВТ, СР, ЧТ, ПТ» установите галочки под всеми днями недели «ПН, ВТ, СР, ЧТ, ПТ, СБ, ВС».
  • Вместо «100%» установите уровень сигнала «50%».

После того как Вы установите два таймера (из 1 и 2 примера): По будням с 18:00 до 19:00 в правом верхнем углу экрана будет отображаться цифра 4, при этом на 4 канале будет установлен уровень логической 1 (сигнал ШИМ со 100% заполнением). По будням с 19:00 до 20:00 в правом верхнем углу экрана будет отображаться цифра 3 и 4, при этом на 3 канале будет установлен сигнал ШИМ с 50% заполнением, а на 4 канале будет установлен уровень логической «1» (сигнал ШИМ со 100% заполнением). По будням с 20:00 до 21:00 и в выходные с 19:00 до 21:00, в правом верхнем углу экрана будет отображаться цифра 3, при этом на 3 канале будет установлен сигнал ШИМ с 50% заполнением.

Примечание:

Включение и выключение устройств осуществляется по установленным таймерам только в режиме просмотра времени. Это сделано для того, чтобы устройства «случайно» не включились во время редактирования текущей даты, времени или таймера.

Код программы:

#include <avr/pgmspace.h>                                                                     //  Подключаем библиотеку для работы с PROGMEM Arduino для хранения символов дисплея
#include <EEPROM.h>                                                                           //  Подключаем библиотеку для работы с EEPROM  Arduino для хранения значений таймеров
#include <Wire.h>                                                                             //  Подключаем библиотеку для работы с шиной I2C
#include <LiquidCrystal_I2C.h>                                                                //  Подключаем библиотеку для работы с LCD дисплеем по шине I2C
#include <iarduino_Encoder_tmr.h>                                                             //  Подключаем библиотеку iarduino_Encoder_tmr для работы с энкодерами через аппаратный таймер
#include <iarduino_RTC.h>                                                                     //  Подключаем библиотеку iarduino_RTC для работы с часами реального времени
                                                                                              //
//   ОБЪЯВЛЯЕМ КОНСТАНТЫ КОТОРЫЕ МОЖНО РЕДАКТИРОВАТЬ:                                         //
     const uint8_t   pinEncA     =              7;                                            //  Определяем константу с указанием № вывода Arduino к которому подключён вывод «A» энкодера
     const uint8_t   pinEncB     =              8;                                            //  Определяем константу с указанием № вывода Arduino к которому подключён вывод «B» энкодера
     const uint8_t   pinEncBTN   =              4;                                            //  Определяем константу с указанием № вывода Arduino к которому подключён вывод «S» энкодера (кнопка)
     const uint8_t   pinChanel_1 =              5;                                            //  Определяем константу с указанием № вывода Arduino который будет являеться выводом 1 канала (указываются только выводы с ШИМ, кроме выводов используемых 2 таймером Arduino)
     const uint8_t   pinChanel_2 =              6;                                            //  Определяем константу с указанием № вывода Arduino который будет являеться выводом 2 канала (указываются только выводы с ШИМ, кроме выводов используемых 2 таймером Arduino)
     const uint8_t   pinChanel_3 =              9;                                            //  Определяем константу с указанием № вывода Arduino который будет являеться выводом 3 канала (указываются только выводы с ШИМ, кроме выводов используемых 2 таймером Arduino)
     const uint8_t   pinChanel_4 =              10;                                           //  Определяем константу с указанием № вывода Arduino который будет являеться выводом 4 канала (указываются только выводы с ШИМ, кроме выводов используемых 2 таймером Arduino)
     const uint8_t   maxTimers   =              20;                                           //  Определяем константу с указанием максимального количества таймеров. Число не должно превышать количество байт EEPROM/8 (для Arduino Uno максимальное значение 128)
                                                                                              //
//   ОБЪЯВЛЯЕМ КОНСТАНТЫ И ПЕРЕМЕННЫЕ НЕОБХОДИМЫЕ ДЛЯ РАБОТЫ СКЕТЧА:                          //
     const byte      rusMem[38][8] PROGMEM =    {                                             //  Определяем массив в области памяти программ, каждый элемент которого является матрицей (еще одим массивом) представления символа на дисплее
                     {31,16,16,30,17,17,30, 0}, { 0, 0,30,17,30,17,30, 0},                    //  Б,  в,  № матрицы символа в массиве: 00, 01
                     {31,16,16,16,16,16,16, 0}, { 0, 0,30,16,16,16,16, 0},                    //  Г,  г,  № матрицы символа в массиве: 02, 03
                     { 6,10,10,10,10,10,31,17}, {10,10,14, 2, 2, 0, 0, 0},                    //  Д,  4,  № матрицы символа в массиве: 04, 05
                     {17,17,17,19,21,25,17, 0}, { 0, 0,17,19,21,25,17, 0},                    //  И,  и,  № матрицы символа в массиве: 06, 07
                     {21,17,17,19,21,25,17, 0}, { 10,4,17,19,21,25,17, 0},                    //  Й,  й,  № матрицы символа в массиве: 08, 09
                     { 0, 0,18,20,24,20,18, 0}, { 0, 0,17,27,21,17,17, 0},                    //  к,  м,  № матрицы символа в массиве: 10, 11
                     { 7, 9, 9, 9, 9, 9,17, 0}, { 0, 0, 7, 9, 9, 9,17, 0},                    //  Л,  л,  № матрицы символа в массиве: 12, 13
                     {31,17,17,17,17,17,17, 0}, { 0, 0,17,17,31,17,17, 0},                    //  П   н,  № матрицы символа в массиве: 14, 15
                     {17,17,17,15, 1,17,14, 0}, { 0, 0,31, 4, 4, 4, 4, 0},                    //  У,  т,  № матрицы символа в массиве: 16, 17
                     {17,17,17,15, 1, 1, 1, 0}, { 0, 0,17,17,15, 1, 1, 0},                    //  Ч,  ч,  № матрицы символа в массиве: 18, 19
                     {17,17,17,29,19,19,29, 0}, { 0, 0,17,17,29,19,29, 0},                    //  Ы,  ы,  № матрицы символа в массиве: 20, 21
                     {16,16,16,30,17,17,30, 0}, { 0, 0, 0, 0, 0, 0,21, 0},                    //  Ь, ..., № матрицы символа в массиве: 22, 23
                     {18,21,21,29,21,21,18, 0}, { 0, 0,18,21,29,21,18, 0},                    //  Ю,  ю,  № матрицы символа в массиве: 24, 25
                     {15,17,17,15, 5, 9,17, 0}, { 0, 0,15,17,15, 5, 9, 0},                    //  Я,  я,  № матрицы символа в массиве: 26, 27
                     {31, 4,31, 0,31,16,31, 0}, {16,31,16, 0,10,21,31, 0},                    //  ПН, ВТ  № матрицы символа в массиве: 28, 29
                     {28,20,31, 0,17,17,31, 0}, {16,31,16, 0,31, 4,28, 0},                    //  СР, ЧТ  № матрицы символа в массиве: 30, 31
                     {16,31,16, 0,31,16,31, 0}, {23,21,31, 0,17,17,31, 0},                    //  ПТ, СБ  № матрицы символа в массиве: 32, 33
                     {17,17,31, 0,10,21,31, 0}, { 4,12, 4, 4,14, 0, 0, 0},                    //  ВС, 1   № матрицы символа в массиве: 34, 35
                     {14, 2,14, 8,14, 0, 0, 0}, {14, 2,14, 2,14, 0, 0, 0}};                   //  2,  3   № матрицы символа в массиве: 36, 37
     uint8_t         valArray[7] =              {0,0,0,0,0,0,0};                              //  Определяем массив элементы которого будут хранить различную информацию в зависимости от режима
     char            valChar[5] =               "    ";                                       //  Определяем массив символов (строку) информация которой будет отображаться на дисплее мигая
     uint8_t         valMode =                  0;                                            //  Определяем переменную для хранения текущего режима (например: режим 31 - установка времени)
     uint8_t         valSubMode =               0;                                            //  Определяем переменную для хранения текущего подрежима (например: режим 31, подрежим 0 - установка часов, подрежим 1 - установка минут, подрежим 2 - установка секунд)
     uint8_t         valTimerNum =              0;                                            //  Определяем переменную для хранения номера выбранного таймера (от 0 до maxTimers-1)
     bool            flgDisplayUpdate =         1;                                            //  Определяем флаговую переменную, установка которой будет сигнализировать о необходимости обновления дисплея
     void            funcEncoderRead           (void);                                        //  Объявляем функцию в которой будут выполняться действия зависящие от состояния энкодера и режима
     void            funcDisplayUpdate         (void);                                        //  Объявляем функцию в которой будет обновляться информация дисплея в зависимости от режима, подрежима, выбранного таймера и значений массива valArray
     void            funcSetPWM                (void);                                        //  Объявляем функцию которая будет устанавливать сигналы ШИМ на каналах если текущее время совпало с временем таймеров
     void            funcSetChars              (uint8_t=255,uint8_t=255,uint8_t=255,uint8_t=255,uint8_t=255,uint8_t=255,uint8_t=255); // Объявляем функцию записывающую до 7 символов из массива rusMem по номерам его элементов в область CGRAM дисплея
     uint8_t         funcReadTimer             (uint8_t=0, uint8_t=0);                        //  Объявляем функцию для чтения одного из параметров таймера (№ таймера, № параметра)
     void            funcSaveTimer             (uint8_t=0, uint8_t=0, uint8_t=0);             //  Объявляем функцию для записи одного из параметров таймера (№ таймера, № параметра, значение параметра)
     uint8_t         funcFindTimer             (void);                                        //  Объявляем функцию для поиска № следующего свободного (не установленного) таймера
     bool            funcTestTimer             (void);                                        //  Объявляем функцию для проверки соответствия данных в ячейках EEPROM Arduino значениям таймеров (если скетч запускается впервые, то данные в ячейках не будут соответствовать значениям таймеров и можно будет подготовить EEPROM к первому запуску скетча)
LiquidCrystal_I2C    lcd                       (0x27,16,2);                                   //  Объявляем объект lcd  для работы с дисплеем указывая (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2)
iarduino_Encoder_tmr enc                       (pinEncA,pinEncB);                             //  Объявляем объект enc  для работы с энкодером указывая (№ вывода A, № вывода B)
iarduino_RTC         time                      (RTC_DS1307);                                  //  Объявляем объект time для работы с часами RTC указывая (тип модуля)
#define              encPRESS 2                                                               //  Определяем константу для удобочитаемости при определении состояния энкодера
                                                                                              //
void setup(){                                                                                 //
     enc.begin();                                                                             //  Инициируем работу с энкодером
     time.begin();                                                                            //  Инициируем работу с RTC модулем
     pinMode(pinEncBTN,   INPUT);                                                             //  Переводим вывод с кнопкой энкодера в режим входа
     pinMode(pinChanel_1, OUTPUT);                                                            //  Переводим вывод 1 канала в режим выхода
     pinMode(pinChanel_2, OUTPUT);                                                            //  Переводим вывод 1 канала в режим выхода
     pinMode(pinChanel_3, OUTPUT);                                                            //  Переводим вывод 1 канала в режим выхода
     pinMode(pinChanel_4, OUTPUT);                                                            //  Переводим вывод 1 канала в режим выхода
     lcd.init();                                                                              //  Инициируем работу с LCD дисплеем
     lcd.backlight();                                                                         //  Включаем подсветку LCD дисплея
     funcSetChars(12,6);                                                                      //  Загружаем элементы 12 и 6 массива rusMem в память CGRAM LCD дисплея. Эти элементы содержат графическое представление символов: «Л» и «И»
     lcd.setCursor(0,0);                                                                      //  Устанавливаем курсор в верхний левый угол экрана (0 столбец, 0 строка)
     lcd.print(F("PE\1E BPEMEH\2"));                                                          //  Выводим надпись «PEЛE BPEMEHИ». Буквы «Л» и «И» заменяем знаком '\' и №, под которыми их графические представления были загружены в память CGRAM LCD дисплея.
     lcd.setCursor(5,1);                                                                      //  Устанавливаем курсор в 5 столбец 1 строки (нумерация начинается с 0)
     lcd.print(F("iarduino.ru"));                                                             //  Выводим надпись «iarduino.ru».
     delay(2000);                                                                             //  Ждём, что бы надпись отображаемую на дисплее успели прочитать
     lcd.clear();                                                                             //  Чистим экран,
     if(!funcTestTimer()){                                                                    //  Если значения в EEPROM не соответствуют значениям таймера (например первый запуск данного скетча), то ...
         funcSetChars(14,4,2);                                                                //  Загружаем элементы 14, 4 и 2 массива rusMem в память CGRAM LCD дисплея. Эти элементы содержат графическое представление символов: «П», «Д» и «Г»
         lcd.setCursor(0,0);                                                                  //  Устанавливаем курсор в верхний левый угол экрана (0 столбец, 0 строка)
         lcd.print(F("\1O\2\3OTOBKA..."));                                                    //  Выводим надпись «ПOДГOTOBKA...». Буквы «П», «Д» и «Г» заменяем знаком '\' и №, под которыми их графические представления были загружены в память CGRAM LCD дисплея.
         for(uint8_t i=0; i<maxTimers; i++){                                                  //  Проходим по всем возможным таймерам
         for(uint8_t j=0; j<8;  j++){                                                         //  Проходим по всем возможным параметрам каждого таймера
             funcSaveTimer(i,j,0);                                                            //  Обнуляем все возможные параметры каждого таймера
         }}  delay(1000); lcd.clear();                                                        //  Ждём, что бы надпись отображаемую на дисплее успели прочитать, а потом чистим экран
     }                                                                                        //
}                                                                                             //
                                                                                              //
void loop(){                                                                                  //
     funcEncoderRead();                                                                       //  Выполняем действия в соответствии с состоянием энкодера и режимом valMode
     funcDisplayUpdate();                                                                     //  Обновляем информацию на дисплее в соответствии с режимом valMode и только если установлен флаг flgDisplayUpdate
     funcSetPWM();                                                                            //  Выводим ШИМ
}                                                                                             //
                                                                                              //
//   ВЫПОЛНЕНИЕ ДЕЙСТВИЙ ПРИ ИЗМЕНЕНИИ СОТОЯНИЯ ЭНКОДЕРА И В ЗАВИСИМОСТИ ОТ ТЕКУЩЕГО РЕЖИМА   //
void funcEncoderRead(void){                                                                   //
     int i=enc.read();                                                                        //  Определяем переменную i в которую читаем состояние энкодера (может принимать 1 из 3 значений: 0, encLEFT, encRIGHT а далее к ним прибавится еще и значение encPRESS)
     int j=255;                                                                               //  Определяем переменную j для хранения № режима меню, в который требуется перейти (значение 255 означает что переход в другой режим не требуется)
     if(digitalRead(pinEncBTN)){i=encPRESS;}                                                  //  Если нажата кнопка энкодера, то устанавливаем переменную i в значение encPRESS
     switch(valMode){                                                                         //  Далее действуем в зависимости от значения переменной valMode (которая хранит № текущего режима меню)
         case  0:               if(i==encPRESS){j= 1;}                                        //  Если установлен режим 0 "Вне меню", то ...
     /*  "00:00:00        " */  flgDisplayUpdate=1;                                           //  Устанавливаем флаг flgDisplayUpdate сигнализирующий о необходимости обновить информацию на дисплее
     /*  "00.00.0000 XX   " */                                                                //
         break;                                                                               //
         case  1:               if(i==encPRESS){j=funcReadTimer()?11:12; valTimerNum=0;}      //  Если установлен режим 1: "Меню" и нажав на энкодер, можно выбрать пункт "ТАЙМЕРЫ", то ...
     /*  "меню:           " */  if(i==encLEFT ){j= 3;}                                        //  Если энкодер поворачивается влево, то переходим в режим 3
     /*  "<   ТАЙМЕРЫ    >" */  if(i==encRIGHT){j= 2;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 2
         break;                                                                               //
         case  2:               if(i==encPRESS){j=21;}                                        //  Если установлен режим 2: "Меню" и нажав на энкодер, можно выбрать пункт "ЧАСЫ", то ...
     /*  "меню:           " */  if(i==encLEFT ){j= 1;}                                        //  Если энкодер поворачивается влево, то переходим в режим 1
     /*  "<     ЧАСЫ     >" */  if(i==encRIGHT){j= 3;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 3
         break;                                                                               //  Если установлен режим 3: "Меню" и нажав на энкодер, можно выбрать пункт "ВЫХОД", то ...
         case  3:               if(i==encPRESS){j= 0; valArray[0]=valArray[1]=valArray[2]=valArray[3]=0;}
     /*  "меню:           " */  if(i==encLEFT ){j= 2;}                                        //  Если энкодер поворачивается влево, то переходим в режим 2
     /*  "<    ВЫХОД     >" */  if(i==encRIGHT){j= 1;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 1
         break;                                                                               //
         case 11:               if(i==encPRESS){j=51;}                                        //  Если установлен режим 11: "Меню>таймеры" и нажав на энкодер, можно выбрать один из таймеров, то ...
     /*  "меню>таймеры:   " */  if(i==encLEFT ){if(valTimerNum){valTimerNum--; lcd.clear(); flgDisplayUpdate=1;}else{j=14;}}
     /*  "< 00:00-00:00-1>" */  if(i==encRIGHT){valTimerNum++; if(valTimerNum>=maxTimers){j=13;}else if(funcReadTimer(valTimerNum)){lcd.clear(); flgDisplayUpdate=1;}else{j=12;}}
         break;                                                                               //
         case 12:               if(i==encPRESS){j=41;}                                        //  Если установлен режим 12: "Меню>таймеры" и нажав на энкодер, можно выбрать пункт "НОВЫЙ ТАЙМЕР", то ...
     /*  "меню>таймеры:   " */  if(i==encLEFT ){j=funcReadTimer()?11:14; if(funcReadTimer()){valTimerNum=funcFindTimer()-1;}}
     /*  "< НОВЫЙ ТАЙМЕР >" */  if(i==encRIGHT){j=funcReadTimer()?13:14;}                     //  Если энкодер поворачивается вправо, то переходим в режим 13 или 14 (зависит от наличия установленных таймеров)
         break;                                                                               //
         case 13:               if(i==encPRESS){j=42;}                                        //  Если установлен режим 13: "Меню>таймеры" и нажав на энкодер, можно выбрать пункт "CTEPETЬ BCE ТАЙМЕРЫ", то ...
     /*  "меню>таймеры:   " */  if(i==encLEFT ){j=funcFindTimer()<maxTimers?12:11; valTimerNum=j==11?maxTimers-1:0;}
     /*  "< CTEPETЬ BCE  >" */  if(i==encRIGHT){j=14; valTimerNum=0;}                         //  Если энкодер поворачивается вправо, то переходим в режим 14 и указываем что выбран таймер № 0
         break;                                                                               //
         case 14:               if(i==encPRESS){j= 1;}                                        //  Если установлен режим 14: "Меню>таймеры" и нажав на энкодер, можно выбрать пункт "ВЫХОД", то ...
     /*  "меню>таймеры:   " */  if(i==encLEFT ){j=funcReadTimer()?13:(funcFindTimer()<maxTimers?12:11); valTimerNum=0;}
     /*  "<    ВЫХОД     >" */  if(i==encRIGHT){j=funcReadTimer()?11:(funcFindTimer()<maxTimers?12:13); valTimerNum=0;}
         break;                                                                               //  Если установлен режим 21: "Меню>часы" и нажав на энкодер, можно выбрать пункт "ВРЕМЯ", то ...
         case 21:               if(i==encPRESS){j=31; valSubMode=0; time.gettime(); valArray[0]=time.Hours; valArray[1]=time.minutes; valArray[2]=time.seconds;}
     /*  "меню>часы:      " */  if(i==encLEFT ){j=23;}                                        //  Если энкодер поворачивается влево, то переходим в режим 23
     /*  "<    ВРЕМЯ     >" */  if(i==encRIGHT){j=22;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 22
         break;                                                                               //  Если установлен режим 22: "Меню>часы" и нажав на энкодер, можно выбрать пункт "ДАТА", то ...
         case 22:               if(i==encPRESS){j=32; valSubMode=0; time.gettime(); valArray[0]=time.day; valArray[1]=time.month; valArray[2]=time.year; valArray[3]=time.weekday;}
     /*  "меню>часы:      " */  if(i==encLEFT ){j=21;}                                        //  Если энкодер поворачивается влево, то переходим в режим 21
     /*  "<     ДАТА     >" */  if(i==encRIGHT){j=23;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 23
         break;                                                                               //
         case 23:               if(i==encPRESS){j= 2;}                                        //  Если установлен режим 23: "Меню>часы" и нажав на энкодер, можно выбрать пункт "ВЫХОД", то ...
     /*  "меню>часы:      " */  if(i==encLEFT ){j=22;}                                        //  Если энкодер поворачивается влево, то переходим в режим 22
     /*  "<    ВЫХОД     >" */  if(i==encRIGHT){j=21;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 21
         break;                                                                               //  Если установлен режим 31: "Меню>часы>вpeмя" и управляя энкодером, можно установить время, то ...
         case 31:               if(i==encPRESS){                        if(valSubMode==0){valSubMode=1;}else if(valSubMode==1){valSubMode=2;}else if(valSubMode==2){time.settime(valArray[2],valArray[1],valArray[0]); j=21;}}
     /*  "меню>часы>вpeмя:" */  if(i==encLEFT ){valArray[valSubMode]--; if(valArray[0]>23){valArray[0]=23;} if(valArray[1]>59){valArray[1]=59;} if(valArray[2]>59){valArray[2]=59;}}
     /*  "    00:00:00    " */  if(i==encRIGHT){valArray[valSubMode]++; if(valArray[0]>23){valArray[0]= 0;} if(valArray[1]>59){valArray[1]= 0;} if(valArray[2]>59){valArray[2]= 0;}}
                                flgDisplayUpdate=1;                                           //  Устанавливаем флаг flgDisplayUpdate сигнализирующий о необходимости обновить информацию на дисплее
         break;                                                                               //  Если установлен режим 32: "Меню>часы>дата" и управляя энкодером, можно установить дату, то ...
         case 32:               if(i==encPRESS){                        if(valSubMode==0){valSubMode=1;}else if(valSubMode==1){valSubMode=2;}else if(valSubMode==2){valSubMode=3;}else if(valSubMode==3){time.settime(-1,-1,-1,valArray[0],valArray[1],valArray[2],valArray[3]); j=22;}}
     /*  "MEHЮ>ЧACЫ>ДATA: " */  if(i==encLEFT ){valArray[valSubMode]--; if(valArray[0]==0){valArray[0]=31;} if(valArray[1]==0){valArray[1]=12;} if(valArray[2]>99){valArray[2]=99;} if(valArray[3]>6){valArray[3]=6;}}
     /*  " 00.00.0000 пт  " */  if(i==encRIGHT){valArray[valSubMode]++; if(valArray[0]>31){valArray[0]= 1;} if(valArray[1]>12){valArray[1]= 1;} if(valArray[2]>99){valArray[2]= 0;} if(valArray[3]>6){valArray[3]=0;}}
                                flgDisplayUpdate=1;                                           //  Устанавливаем флаг flgDisplayUpdate сигнализирующий о необходимости обновить информацию на дисплее
         break;                                                                               //
         case 41:                                                                             //  Если установлен режим 41: "HOBЫЙ TAЙMEP CO3ДAH", то данный режим сбросится в режим 61 через 2 секунды вне зависимости от состояния энкодера ...
     /*  "  HOBЫЙ TAЙMEP  " */  j=61; valTimerNum=funcFindTimer(); valSubMode=0; valArray[0]=0; valArray[1]=0; valArray[2]=0; valArray[3]=0; valArray[4]=1; funcSaveTimer(valTimerNum,0,1); funcSaveTimer(valTimerNum,1); funcSaveTimer(valTimerNum,2); funcSaveTimer(valTimerNum,3); funcSaveTimer(valTimerNum,4); funcSaveTimer(valTimerNum,5,1); funcSaveTimer(valTimerNum,6,100); funcSaveTimer(valTimerNum,7,127);
     /*  "     CO3ДAH     " */  delay(2000);                                                  //
         break;                                                                               //
         case 42:                                                                             //  Если установлен режим 42: "BCE TAЙMEPЫ УДAЛEHЫ", то данный режим сбросится в режим 12 через 2 секунды вне зависимости от состояния энкодера ...
     /*  "  BCE TAЙMEPЫ   " */  j=12; for(valArray[0]=0; valArray[0]<maxTimers; valArray[0]++){funcSaveTimer(valArray[0]);}
     /*  "    УДAЛEHЫ     " */  delay(2000);                                                  //
         break;                                                                               //
         case 43:                                                                             //  Если установлен режим 43: "TAЙMEP УДAЛEH", то данный режим сбросится в режим 11 или 12 через 2 секунды вне зависимости от состояния энкодера ...
     /*  "     TAЙMEP     " */  j=12; valArray[0]=valTimerNum; valArray[1]=funcFindTimer()-1; if(valArray[0]<valArray[1]){j=11; valTimerNum=valArray[0];}else if(valArray[1]>0){j=11; valTimerNum=valArray[0]-1;} for(int k=valArray[0]; k<valArray[1]; k++){for(uint8_t l=0; l<8; l++){funcSaveTimer(k,l,funcReadTimer(k+1,l));}} funcSaveTimer(valArray[1]);
     /*  "     УДAЛEH     " */  delay(2000);                                                  //
         break;                                                                               //  Если установлен режим 51: "Меню>тайmеры>выбранный_таймер" и нажав на энкодер, можно выбрать пункт "BPEMЯ  И KAHAЛ", то ...
         case 51:               if(i==encPRESS){j=61; valSubMode=0; valArray[0]=funcReadTimer(valTimerNum,1); valArray[1]=funcReadTimer(valTimerNum,2); valArray[2]=funcReadTimer(valTimerNum,3); valArray[3]=funcReadTimer(valTimerNum,4); valArray[4]=funcReadTimer(valTimerNum,5);}
     /*  "m>тайmеры>00:00." */  if(i==encLEFT ){j=55;}                                        //  Если энкодер поворачивается влево, то переходим в режим 55
     /*  "<BPEMЯ  И KAHAЛ>" */  if(i==encRIGHT){j=52;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 52
         break;                                                                               //  Если установлен режим 52: "Меню>тайmеры>выбранный_таймер" и нажав на энкодер, можно выбрать пункт "ПOBTOPЫ", то ...
         case 52:               if(i==encPRESS){j=62; valSubMode=0; valArray[0]=bitRead(funcReadTimer(valTimerNum,7),6); valArray[1]=bitRead(funcReadTimer(valTimerNum,7),5); valArray[2]=bitRead(funcReadTimer(valTimerNum,7),4); valArray[3]=bitRead(funcReadTimer(valTimerNum,7),3); valArray[4]=bitRead(funcReadTimer(valTimerNum,7),2); valArray[5]=bitRead(funcReadTimer(valTimerNum,7),1); valArray[6]=bitRead(funcReadTimer(valTimerNum,7),0);}
     /*  "m>тайmеры>00:00." */  if(i==encLEFT ){j=51;}                                        //  Если энкодер поворачивается влево, то переходим в режим 51
     /*  "<   ПOBTOPЫ    >" */  if(i==encRIGHT){j=53;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 53
         break;                                                                               //  Если установлен режим 53: "Меню>тайmеры>выбранный_таймер" и нажав на энкодер, можно выбрать пункт "УPOBEHЬ CИГНАЛА", то ...
         case 53:               if(i==encPRESS){j=63; valArray[0]=funcReadTimer(valTimerNum,6);}
     /*  "m>тайmеры>00:00." */  if(i==encLEFT ){j=52;}                                        //  Если энкодер поворачивается влево, то переходим в режим 52
     /*  "< УPOBEHb CИГН.>" */  if(i==encRIGHT){j=54;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 54
         break;                                                                               //
         case 54:               if(i==encPRESS){j=43;}                                        //  Если установлен режим 54: "Меню>тайmеры>выбранный_таймер" и нажав на энкодер, можно выбрать пункт "CTEPETЬ TAЙMEP", то ...
     /*  "m>тайmеры>00:00." */  if(i==encLEFT ){j=53;}                                        //  Если энкодер поворачивается влево, то переходим в режим 53
     /*  "<CTEPETЬ TAЙMEP>" */  if(i==encRIGHT){j=55;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 55
         break;                                                                               //
         case 55:               if(i==encPRESS){j=11;}                                        //  Если установлен режим 55: "Меню>тайmеры>выбранный_таймер" и нажав на энкодер, можно выбрать пункт "ВЫХОД", то ...
     /*  "m>тайmеры>00:00." */  if(i==encLEFT ){j=54;}                                        //  Если энкодер поворачивается влево, то переходим в режим 54
     /*  "<    ВЫХОД     >" */  if(i==encRIGHT){j=51;}                                        //  Если энкодер поворачивается вправо, то переходим в режим 51
         break;                                                                               //  Если установлен режим 61: "Меню>тайmеры>выбранный_таймер>время_и_канал" и управляя энкодером, можно установить время и канал таймера, то ...
         case 61:               if(i==encPRESS){if(valSubMode==0){valSubMode=1;}else if(valSubMode==1){valSubMode=2;}else if(valSubMode==2){valSubMode=3;}else if(valSubMode==3){valSubMode=4;}else if(valSubMode==4){
                                j=51; funcSaveTimer(valTimerNum,0,1); funcSaveTimer(valTimerNum,1,valArray[0]); funcSaveTimer(valTimerNum,2,valArray[1]); funcSaveTimer(valTimerNum,3,valArray[2]); funcSaveTimer(valTimerNum,4,valArray[3]); funcSaveTimer(valTimerNum,5,valArray[4]);}}
     /*  "м>таймер>вpeмя: " */  if(i==encLEFT ){valArray[valSubMode]--; if(valArray[0]>23){valArray[0]=23;} if(valArray[1]>59){valArray[1]=59;} if(valArray[2]>23){valArray[2]=23;} if(valArray[3]>59){valArray[3]=59;} if(valArray[4]==0){valArray[4]=4;}}
     /*  " 00:00-00:00 к0 " */  if(i==encRIGHT){valArray[valSubMode]++; if(valArray[0]>23){valArray[0]= 0;} if(valArray[1]>59){valArray[1]= 0;} if(valArray[2]>23){valArray[2]= 0;} if(valArray[3]>59){valArray[3]= 0;} if(valArray[4]> 4){valArray[4]=1;}}
                                flgDisplayUpdate=1;                                           //  Устанавливаем флаг flgDisplayUpdate сигнализирующий о необходимости обновить информацию на дисплее
         break;                                                                               //  Если установлен режим 62: "Меню>тайmеры>выбранный_таймер>повторы" и управляя энкодером, можно установить повторы таймера, то ...
         case 62:               if(i==encPRESS){flgDisplayUpdate=1; if(valSubMode==0){valSubMode=1;}else if(valSubMode==1){valSubMode=2;}else if(valSubMode==2){valSubMode=3;}else if(valSubMode==3){valSubMode=4;}else if(valSubMode==4){valSubMode=5;}else if(valSubMode==5){valSubMode=6;}else if(valSubMode==6){
                                j=52; uint8_t k=0; bitWrite(k,6,valArray[0]); bitWrite(k,5,valArray[1]); bitWrite(k,4,valArray[2]); bitWrite(k,3,valArray[3]); bitWrite(k,2,valArray[4]); bitWrite(k,1,valArray[5]); bitWrite(k,0,valArray[6]); funcSaveTimer(valTimerNum,7,k); lcd.noBlink();}}
     /*  " * * * * * * *  " */  if(i==encLEFT ){flgDisplayUpdate=1; if(valArray[valSubMode]){valArray[valSubMode]=0;}else{valArray[valSubMode]=1;}}
     /*  " ^ ^ ^ ^ ^ ^ ^  " */  if(i==encRIGHT){flgDisplayUpdate=1; if(valArray[valSubMode]){valArray[valSubMode]=0;}else{valArray[valSubMode]=1;}}
         break;                                                                               //  Если установлен режим 63: "Меню>тайmеры>выбранный_таймер>уровень_сигнала" и управляя энкодером, можно установить уровень сигнала таймера, то ...
         case 63:               if(i==encPRESS){j=53; funcSaveTimer(valTimerNum,6,valArray[0]);}
     /*  "м>тaймep>cигнaл:" */  if(i==encLEFT ){flgDisplayUpdate=1; valArray[0]-=5; if(valArray[0]>100){valArray[0]=  5;} if(valArray[0]<5){valArray[0]=5;}}
     /*  "      100%      " */  if(i==encRIGHT){flgDisplayUpdate=1; valArray[0]+=5; if(valArray[0]>100){valArray[0]=100;}}
         break;                                                                               //
     }                                                                                        //
     if(j<255){lcd.clear(); flgDisplayUpdate=1; valMode=j;}                                   //  Если требуется сменить режим, то чистим экран, устанавливаем флаг необходимости обновления экрана flgDisplayUpdate и устанавливаем новый режим valMode
     if(i==encPRESS){while(digitalRead(pinEncBTN)){delay(50);}}                               //  Если была нажата кнопка энкодера, то ждём пока она не будет отпущена (с задержками по 50мс для подавления дребезга)
}                                                                                             //
                                                                                              //
//   ОБНОВЛЕНИЕ ИНФОРМАЦИИ НА ДИСПЛЕЕ                                                         //
void funcDisplayUpdate(){                                                                     //
     if(flgDisplayUpdate){      flgDisplayUpdate=0;                                           //  Обновление дисплея происходит только если был установлен флаг flgDisplayUpdate
     switch(valMode){                                                                         //  Далее действуем в зависимости от значения переменной valMode (которая хранит № текущего режима меню)
         case  0:               funcSetChars(35,36,37,5,3,0,time.weekday==4?18:14);           //  Если установлен режим 0, то загружаем символы в CGRAM лисплея в следующем порядке: 1-«1» 2-«2» 3-«3» 4-«4» 5-«г» 6-«Б» 7-«П/Ч» и выводим информацию для данного режима на дисплей ...
     /*  "00:00:00        " */  lcd.setCursor(0, 0); lcd.print(time.gettime("H:i:s"));        //  Выводим верхнюю строку
     /*  "00.00.0000г. ПН " */  lcd.setCursor(0, 1); lcd.print(time.gettime("d.m.Y\5."));     //
                                lcd.setCursor(13,1); lcd.print(time.weekday==1?"\7H":(time.weekday==2?"BT":(time.weekday==3?"CP":(time.weekday==4?"\7T":(time.weekday==5?"\7T":(time.weekday==6?"C\6":("BC")))))));
                                lcd.setCursor(12,0); lcd.print("    "); valArray[4]=15; if(valArray[3]){lcd.setCursor(valArray[4],0); lcd.print("\4"); valArray[4]--;} if(valArray[2]){lcd.setCursor(valArray[4],0); lcd.print("\3"); valArray[4]--;} if(valArray[1]){lcd.setCursor(valArray[4],0); lcd.print("\2"); valArray[4]--;} if(valArray[0]){lcd.setCursor(valArray[4],0); lcd.print("\1"); valArray[4]--;}
         break;                                                                               //
         case  1:               funcSetChars(11,15,25,9,20);                                  //  Если установлен режим 1, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4Й 5Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню:           " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3:"));                //  Выводим верхнюю строку
     /*  "<   ТАЙМЕРЫ    >" */  lcd.setCursor(0, 1); lcd.print(F("<   TA\4MEP\5    >"));      //
         break;                                                                               //
         case  2:               funcSetChars(11,15,25,18,20);                                 //  Если установлен режим 2, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4Ч 5Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню:           " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3:"));                //  Выводим верхнюю строку
     /*  "<     ЧАСЫ     >" */  lcd.setCursor(0, 1); lcd.print(F("<     \4AC\5     >"));      //
         break;                                                                               //
         case  3:               funcSetChars(11,15,25,4,20);                                  //  Если установлен режим 3, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4Д 5Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню:           " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3:"));                //  Выводим верхнюю строку
     /*  "<    ВЫХОД     >" */  lcd.setCursor(0, 1); lcd.print(F("<    B\5XO\4     >"));      //
         break;                                                                               //
         case 11:               funcSetChars(15,25,17,9,21);                                  //  Если установлен режим 11, то загружаем символы в CGRAM лисплея в следующем порядке: 1н 2ю 3т 4й 5ы и выводим информацию для данного режима на дисплей ...
     /*  "меню>таймеры:   " */  lcd.setCursor(0, 0); lcd.print(F("me\1\2>\3a\4mep\5:"));      //  Выводим верхнюю строку
     /*  "< 00:00-00:00-1>" */  lcd.setCursor(0, 1); lcd.print(F("< 00:00-00:00-0>"));        //
                                lcd.setCursor(2, 1); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1));
                                lcd.setCursor(5, 1); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2));
                                lcd.setCursor(8, 1); lcd.print(funcReadTimer(valTimerNum,3)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,3));
                                lcd.setCursor(11,1); lcd.print(funcReadTimer(valTimerNum,4)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,4));
                                lcd.setCursor(14,1);                                                    lcd.print(funcReadTimer(valTimerNum,5));
         break;                                                                               //
         case 12:               funcSetChars(15,25,17,9,21,8,20);                             //  Если установлен режим 12, то загружаем символы в CGRAM лисплея в следующем порядке: 1н 2ю 3т 4й 5ы 6Й 7Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню>таймеры:   " */  lcd.setCursor(0, 0); lcd.print(F("me\1\2>\3a\4mep\5:"));      //  Выводим верхнюю строку
     /*  "< НОВЫЙ ТАЙМЕР >" */  lcd.setCursor(0, 1); lcd.print(F("< HOB\7\6 TA\6MEP >"));     //
         break;                                                                               //
         case 13:               funcSetChars(15,25,17,9,21,22);                               //  Если установлен режим 13, то загружаем символы в CGRAM лисплея в следующем порядке: 1н 2ю 3т 4й 5ы 6Ь и выводим информацию для данного режима на дисплей ...
     /*  "меню>таймеры:   " */  lcd.setCursor(0, 0); lcd.print(F("me\1\2>\3a\4mep\5:"));      //  Выводим верхнюю строку
     /*  "< CTEPETь BCE  >" */  lcd.setCursor(0, 1); lcd.print(F("< CTEPET\6 BCE  >"));       //
         break;                                                                               //
         case 14:               funcSetChars(15,25,17,9,21,4,20);                             //  Если установлен режим 14, то загружаем символы в CGRAM лисплея в следующем порядке: 1н 2ю 3т 4й 5ы 6Д 7Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню>таймеры:   " */  lcd.setCursor(0, 0); lcd.print(F("me\1\2>\3a\4mep\5:"));      //  Выводим верхнюю строку
     /*  "<    ВЫХОД     >" */  lcd.setCursor(0, 1); lcd.print(F("<    B\7XO\6     >"));      //
         break;                                                                               //
         case 21:               funcSetChars(11,15,25,19,21,26);                              //  Если установлен режим 21, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4ч 5ы 6Я и выводим информацию для данного режима на дисплей ...
     /*  "меню>часы:      " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3>\4ac\5:"));         //  Выводим верхнюю строку
     /*  "<    ВРЕМЯ     >" */  lcd.setCursor(0, 1); lcd.print(F("<    BPEM\6     >"));       //
         break;                                                                               //
         case 22:               funcSetChars(11,15,25,19,21,4);                               //  Если установлен режим 22, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4ч 5ы 6Д и выводим информацию для данного режима на дисплей ...
     /*  "меню>часы:      " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3>\4ac\5:"));         //  Выводим верхнюю строку
     /*  "<     ДАТА     >" */  lcd.setCursor(0, 1); lcd.print(F("<     \6ATA     >"));       //
         break;                                                                               //
         case 23:               funcSetChars(11,15,25,19,21,4,20);                            //  Если установлен режим 23, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4ч 5ы 6Д 7Ы и выводим информацию для данного режима на дисплей ...
     /*  "меню>часы:      " */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3>\4ac\5:"));         //  Выводим верхнюю строку
     /*  "<    ВЫХОД     >" */  lcd.setCursor(0, 1); lcd.print(F("<    B\7XO\6     >"));      //
         break;                                                                               //
         case 31:               funcSetChars(11,15,25,19,21,1,27);                            //  Если установлен режим 31, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2н 3ю 4ч 5ы 6в 7я и выводим информацию для данного режима на дисплей ...
     /*  "меню>часы>вpeмя:" */  lcd.setCursor(0, 0); lcd.print(F("\1e\2\3>\4ac\5>\6pe\1\7:"));//  Выводим верхнюю строку
     /*  "    00:00:00    " */  lcd.setCursor(4, 1); valChar[0]=valArray[0]/10+48; valChar[1]=valArray[0]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==0)?"  ":valChar); lcd.print(":");
                                lcd.setCursor(7, 1); valChar[0]=valArray[1]/10+48; valChar[1]=valArray[1]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==1)?"  ":valChar); lcd.print(":");
                                lcd.setCursor(10,1); valChar[0]=valArray[2]/10+48; valChar[1]=valArray[2]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==2)?"  ":valChar);
         break;                                                                               //
         case 32:               funcSetChars(24,18,20,4,14,0);                                //  Если установлен режим 32, то загружаем символы в CGRAM лисплея в следующем порядке: 1Ю 2Ч 3Ы 4Д 5П 6Б и выводим информацию для данного режима на дисплей ...
     /*  "MEHЮ>ЧACЫ>ДATA: " */  lcd.setCursor(0, 0); lcd.print(F("MEH\1>\2AC\3>\4ATA:"));     //  Выводим верхнюю строку
     /*  " 00.00.0000 пт  " */  lcd.setCursor(1, 1); valChar[0]=valArray[0]/10+48; valChar[1]=valArray[0]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==0)?"  ":valChar); lcd.print(".");
                                lcd.setCursor(4, 1); valChar[0]=valArray[1]/10+48; valChar[1]=valArray[1]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==1)?"  ":valChar); lcd.print(".");
                                lcd.setCursor(7, 1); valChar[0]='2'; valChar[1]='0'; valChar[2]=valArray[2]/10+48; valChar[3]=valArray[2]%10+48; valChar[4]=0; lcd.print((millis()%1000<500 && valSubMode==2)?"    ":valChar);
                                lcd.setCursor(12,1); strcpy(valChar,(valArray[3]==1?"\5H":(valArray[3]==2?"BT":(valArray[3]==3?"CP":(valArray[3]==4?"\2T":(valArray[3]==5?"\5T":(valArray[3]==6?"C\6":("BC")))))))); valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==3)?"  ":valChar);
         break;                                                                               //
         case 41:               funcSetChars(20,8,4);                                         //  Если установлен режим 41, то загружаем символы в CGRAM лисплея в следующем порядке: 1Ы 2Й 3Д и выводим информацию для данного режима на дисплей ...
     /*  "  HOBЫЙ TAЙMEP  " */  lcd.setCursor(2, 0); lcd.print(F("HOB\1\2 TA\2MEP"));         //  Выводим верхнюю строку
     /*  "     CO3ДAH     " */  lcd.setCursor(5, 1); lcd.print(F("CO3\3AH"));                 //
         break;                                                                               //
         case 42:               funcSetChars(20,8,4,16,12);                                   //  Если установлен режим 42, то загружаем символы в CGRAM лисплея в следующем порядке: 1Ы 2Й 3Д 4У 5Л и выводим информацию для данного режима на дисплей ...
     /*  "  BCE TAЙMEPЫ   " */  lcd.setCursor(2, 0); lcd.print(F("BCE TA\2MEP\1"));           //  Выводим верхнюю строку
     /*  "    УДAЛEHЫ     " */  lcd.setCursor(4, 1); lcd.print(F("\4\3A\5EH\1"));             //
         break;                                                                               //
         case 43:               funcSetChars(8,16,4,12);                                      //  Если установлен режим 43, то загружаем символы в CGRAM лисплея в следующем порядке: 1Й 2У 3Д 4Л и выводим информацию для данного режима на дисплей ...
     /*  "     TAЙMEP     " */  lcd.setCursor(5, 0); lcd.print(F("TA\1MEP"));                 //  Выводим верхнюю строку
     /*  "     УДAЛEH     " */  lcd.setCursor(5, 1); lcd.print(F("\2\3A\4EH"));               //
         break;                                                                               //
         case 51:               funcSetChars(17,9,21,23,26,6,12);                             //  Если установлен режим 51, то загружаем символы в CGRAM лисплея в следующем порядке: 1т 2й 3ы 4. 5Я 6И 7Л и выводим информацию для данного режима на дисплей ...
     /*  "m>тайmеры>00:00." */  lcd.setCursor(0, 0); lcd.print(F("m>\1a\2mep\3>"));           //  Выводим верхнюю строку
     /*  "<BPEM*  * KAHA*>" */  lcd.setCursor(10,0); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1)); lcd.print(":");
                                lcd.setCursor(13,0); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2)); lcd.print("\4");
                                lcd.setCursor(0, 1); lcd.print(F("<BPEM\5  \6 KAHA\7>"));     //
         break;                                                                               //
         case 52:               funcSetChars(17,9,21,23,14,20);                               //  Если установлен режим 52, то загружаем символы в CGRAM лисплея в следующем порядке: 1т 2й 3ы 4. 5П 6Ы и выводим информацию для данного режима на дисплей ...
     /*  "m>тайmеры>00:00." */  lcd.setCursor(0, 0); lcd.print(F("m>\1a\2mep\3>"));           //  Выводим верхнюю строку
     /*  "<   ПOBTOPЫ    >" */  lcd.setCursor(10,0); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1)); lcd.print(":");
                                lcd.setCursor(13,0); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2)); lcd.print("\4");
                                lcd.setCursor(0, 1); lcd.print(F("<   \5OBTOP\6    >"));      //
         break;                                                                               //
         case 53:               funcSetChars(17,9,21,23,16,6,2);                              //  Если установлен режим 53, то загружаем символы в CGRAM лисплея в следующем порядке: 1т 2й 3ы 4. 5У 6И 7Г и выводим информацию для данного режима на дисплей ...
     /*  "m>тайmеры>00:00." */  lcd.setCursor(0, 0); lcd.print(F("m>\1a\2mep\3>"));           //  Выводим верхнюю строку
     /*  "< УPOBEHb CИГН.>" */  lcd.setCursor(10,0); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1)); lcd.print(":");
                                lcd.setCursor(13,0); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2)); lcd.print("\4");
                                lcd.setCursor(0, 1); lcd.print(F("< \5POBEHb C\6\7H.>"));     //
         break;                                                                               //
         case 54:               funcSetChars(17,9,21,23,22,8);                                //  Если установлен режим 54, то загружаем символы в CGRAM лисплея в следующем порядке: 1т 2й 3ы 4. 5Ь 6Й и выводим информацию для данного режима на дисплей ...
     /*  "m>тайmеры>00:00." */  lcd.setCursor(0, 0); lcd.print(F("m>\1a\2mep\3>"));           //  Выводим верхнюю строку
     /*  "<CTEPETЬ TAЙMEP>" */  lcd.setCursor(10,0); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1)); lcd.print(":");
                                lcd.setCursor(13,0); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2)); lcd.print("\4");
                                lcd.setCursor(0, 1); lcd.print(F("<CTEPET\5 TA\6MEP>"));      //
         break;                                                                               //
         case 55:               funcSetChars(17,9,21,23,20,4);                                //  Если установлен режим 55, то загружаем символы в CGRAM лисплея в следующем порядке: 1т 2й 3ы 4. 5Ы 6Д и выводим информацию для данного режима на дисплей ...
     /*  "m>тайmеры>00:00." */  lcd.setCursor(0, 0); lcd.print(F("m>\1a\2mep\3>"));           //  Выводим верхнюю строку
     /*  "<    ВЫХОД     >" */  lcd.setCursor(10,0); lcd.print(funcReadTimer(valTimerNum,1)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,1)); lcd.print(":");
                                lcd.setCursor(13,0); lcd.print(funcReadTimer(valTimerNum,2)<10?"0":""); lcd.print(funcReadTimer(valTimerNum,2)); lcd.print("\4");
                                lcd.setCursor(0, 1); lcd.print(F("<    B\5XO\6     >"));      //
         break;                                                                               //
         case 61:               funcSetChars(11,17,9,21,1,27,10);                             //  Если установлен режим 61, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2т 3й 4ы 5в 6я 7к и выводим информацию для данного режима на дисплей ...
     /*  "м>таймер>вpeмя: " */  lcd.setCursor(0, 0); lcd.print(F("\1>\2a\3\1ep>\5pe\1\6:"));  //  Выводим верхнюю строку
     /*  " 00:00-00:00 к0 " */  lcd.setCursor(1, 1); valChar[0]=valArray[0]/10+48; valChar[1]=valArray[0]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==0)?"  ":valChar); lcd.print(":");
                                lcd.setCursor(4, 1); valChar[0]=valArray[1]/10+48; valChar[1]=valArray[1]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==1)?"  ":valChar); lcd.print("-");
                                lcd.setCursor(7, 1); valChar[0]=valArray[2]/10+48; valChar[1]=valArray[2]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==2)?"  ":valChar); lcd.print(":");
                                lcd.setCursor(10,1); valChar[0]=valArray[3]/10+48; valChar[1]=valArray[3]%10+48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==3)?"  ":valChar);
                                lcd.setCursor(13,1); valChar[0]=7;                 valChar[1]=valArray[4]   +48; valChar[2]=0; lcd.print((millis()%1000<500 && valSubMode==4)?"  ":valChar);
         break;                                                                               //
         case 62:               funcSetChars(28,29,30,31,32,33,34);                           //  Если установлен режим 62, то загружаем символы в CGRAM лисплея в следующем порядке: 1-ПН 2-ВТ 3-СР 4-ЧТ 5-ПТ 6-ВТ 7-СР и выводим информацию для данного режима на дисплей ...
     /*  " * * * * * * *  " */  lcd.setCursor(1, 0); lcd.print("\1 \2 \3 \4 \5 \6 \7");       //  Выводим верхнюю строку
     /*  " ^ ^ ^ ^ ^ ^ ^  " */  lcd.setCursor(1, 1); lcd.print(valArray[0]?"^":" "); lcd.setCursor(3, 1); lcd.print(valArray[1]?"^":" "); lcd.setCursor(5, 1); lcd.print(valArray[2]?"^":" "); lcd.setCursor(7, 1); lcd.print(valArray[3]?"^":" "); lcd.setCursor(9, 1); lcd.print(valArray[4]?"^":" "); lcd.setCursor(11,1); lcd.print(valArray[5]?"^":" "); lcd.setCursor(13,1); lcd.print(valArray[6]?"^":" ");
                                lcd.setCursor(valSubMode*2+1, 1); lcd.blink();                //
         break;                                                                               //
         case 63:               funcSetChars(11,17,9,7,3,15,13);                              //  Если установлен режим 63, то загружаем символы в CGRAM лисплея в следующем порядке: 1м 2т 3й 4и 5г 6н 7л и выводим информацию для данного режима на дисплей ...
     /*  "м>тaймep>cигнaл:" */  lcd.setCursor(0, 0); lcd.print(F("\1>\2a\3\1ep>c\4\5\6a\7:"));//  Выводим верхнюю строку
     /*  "      100%      " */  lcd.setCursor(6, 1); valChar[0]=valArray[0]/100+48; valChar[1]=valArray[0]%100/10+48; valChar[2]=valArray[0]%10+48; valChar[3]=0; lcd.print(valChar); lcd.print("%");
         break;                                                                               //
}    }}                                                                                       //
                                                                                              //
//   ВЫВОД СИГНАЛОВ ШИМ                                                                       //
void funcSetPWM(void){                                                                        //
     static uint8_t setChanel[4] = {0,0,0,0};                                                 //  Определяем массив, каждый элемент которого соответствует уровню сигнала (от 0 до 100%) установленного на соответствующем канале
     if(valMode==0){                                                                          //  Если текущий режим равен 0 "Вне меню", то ...
         uint8_t  getChanel[4]   = {0,0,0,0};                                                 //  Определяем массив, каждый элемент которого соответствует уровню сигнала (от 0 до 100%) прочитанного из октивированного таймера для соответствующего канала
         uint32_t timeRTC        = 0;                                                         //  Определяем переменную для хранения текущего времени ввиде секунд прошедших с полуночи текущего дня (от 00:00:00)
         uint32_t timeTimerStart = 0;                                                         //  Определяем переменную для хранения времени старта таймера (для каждого таймера в теле цикла for) ввиде секунд прошедших с полуночи текущего дня (от 00:00:00)
         uint32_t timeTimerStop  = 0;                                                         //  Определяем переменную для хранения времени сброса таймера (для каждого таймера в теле цикла for) ввиде секунд прошедших с полуночи текущего дня (от 00:00:00)
         uint8_t  timeWeekday    = 0;                                                         //  Определяем переменную для хранения текущего деня недели в формате: 1-ПН, 2-ВТ, 3-СР, 4-ЧТ, 5-ПТ, 6-СБ, 7-ВС
         valArray[0]=valArray[1]=valArray[2]=valArray[3]=0;                                   //  В первые 4 элемента массива valArray будет записана 1, если на соответствующем канале будет установлен сигнал
         timeRTC = (uint32_t) time.Hours*3600+time.minutes*60+time.seconds;                   //  Получаем количество секунд прошедшее с полуночи текущего дня (от 00:00:00). Значения переменных объекта time являются актуальными, т.к. в данном режиме (valMode) в функции funcDisplayUpdate было обращение к функции time.gettime
         timeWeekday = time.weekday; if(timeWeekday==0){timeWeekday=7;}                       //  Получаем текущий день недели в формате: 1-ПН, 2-ВТ, 3-СР, 4-ЧТ, 5-ПТ, 6-СБ, 7-ВС
         for(uint8_t i=0; i<maxTimers; i++){                                                  //  Проходим по всем таймерам, ...
         if (funcReadTimer(i)){                                                               //  Если очередной таймер является установленным, то ...
         if (bitRead(funcReadTimer(i,7),7-timeWeekday)){                                      //  Если день недели повтора таймера совпал с текушим днём недели, то ...
             timeTimerStart=(uint32_t)funcReadTimer(i,1)*3600+funcReadTimer(i,2)*60;          //  Читаем время старта очередного таймера ввиде количества секунд прошедших от полуночи текущего дня (от 00:00:00)
             timeTimerStop =(uint32_t)funcReadTimer(i,3)*3600+funcReadTimer(i,4)*60;          //  Читаем время сброса очередного таймера ввиде количества секунд прошедших от полуночи текущего дня (от 00:00:00)
             if(timeTimerStart<=timeRTC && timeRTC<timeTimerStop){                            //  Если текущее время находится между временем старта и сброса таймера, то ...
                 getChanel[funcReadTimer(i,5)-1]=funcReadTimer(i,6);                          //  Читаем из таймера уровень сигнала который требуется установить на требуемом канале
                 valArray [funcReadTimer(i,5)-1]=1;                                           //  Сохраняем тот факт, что установлен сигнал на требуемом канале
             }                                                                                //
         }}}                                                                                  //
         if(setChanel[0]!=getChanel[0]){setChanel[0]=getChanel[0]; analogWrite(pinChanel_1, map(getChanel[0], 0,100, 0,255));} // выводим ШИМ на 1 канал
         if(setChanel[1]!=getChanel[1]){setChanel[1]=getChanel[1]; analogWrite(pinChanel_2, map(getChanel[1], 0,100, 0,255));} // выводим ШИМ на 2 канал
         if(setChanel[2]!=getChanel[2]){setChanel[2]=getChanel[2]; analogWrite(pinChanel_3, map(getChanel[2], 0,100, 0,255));} // выводим ШИМ на 3 канал
         if(setChanel[3]!=getChanel[3]){setChanel[3]=getChanel[3]; analogWrite(pinChanel_4, map(getChanel[3], 0,100, 0,255));} // выводим ШИМ на 4 канал
     }                                                                                        //
}                                                                                             //
//   ЗАПИСЬ ДО 7 СИМВОЛОВ В CGRAM ДИСПЛЕЯ                                                     //
void funcSetChars(uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7){ byte i[8];
     if(i1<255){memcpy_P(i, rusMem[i1], 8); lcd.createChar(1, i);}                            //  Записываем символ i (взятый из элемента № i1 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 1
     if(i2<255){memcpy_P(i, rusMem[i2], 8); lcd.createChar(2, i);}                            //  Записываем символ i (взятый из элемента № i2 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 2
     if(i3<255){memcpy_P(i, rusMem[i3], 8); lcd.createChar(3, i);}                            //  Записываем символ i (взятый из элемента № i3 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 3
     if(i4<255){memcpy_P(i, rusMem[i4], 8); lcd.createChar(4, i);}                            //  Записываем символ i (взятый из элемента № i4 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 4
     if(i5<255){memcpy_P(i, rusMem[i5], 8); lcd.createChar(5, i);}                            //  Записываем символ i (взятый из элемента № i5 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 5
     if(i6<255){memcpy_P(i, rusMem[i6], 8); lcd.createChar(6, i);}                            //  Записываем символ i (взятый из элемента № i6 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 6
     if(i7<255){memcpy_P(i, rusMem[i7], 8); lcd.createChar(7, i);}                            //  Записываем символ i (взятый из элемента № i7 массива rusMem хранящегося в PROGMEM Arduino) в CGRAM дисплея под номером 7
}                                                                                             //
//   ЧТЕНИЕ ОДНОГО ИЗ ПАРАМЕТРОВ ТАЙМЕРА                                                      //
uint8_t funcReadTimer(uint8_t i, uint8_t j){return EEPROM[i*8+j];}                            //
//   ПОИСК НОВОГО ТАЙМЕРА                                                                     //
uint8_t funcFindTimer(void){uint8_t i=0; while(funcReadTimer(i)){i++; if(i>=maxTimers){break;}} return i;}
//   СОХРАНЕНИЕ ОДНОГО ИЗ ПАРАМЕТРОВ ТАЙМЕРА                                                  //
void funcSaveTimer(uint8_t i, uint8_t j, uint8_t k){EEPROM[i*8+j]=k;}                         //
//   ПРОВЕРКА ДАННЫХ ТАЙМЕРОВ В EEPROM                                                        //
bool funcTestTimer(void){for(uint8_t i=0; i<maxTimers; i++){if(funcReadTimer(i,0)>1){return false;} if(funcReadTimer(i,1)>23){return false;} if(funcReadTimer(i,2)>59){return false;} if(funcReadTimer(i,3)>23){return false;} if(funcReadTimer(i,4)>59){return false;}} return true;}

Библиотека iarduino_Encoder_tmr использует второй аппаратный таймер, НЕ ВЫВОДИТЕ СИГНАЛЫ ШИМ НА 3 ИЛИ 11 ВЫВОД!

Ссылки:




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями