Главная » Arduino
Призовой фонд
на май 2020 г.
1. Лабораторный БП NPS-1601 (0-32В, 0-5A)
Сайт Паяльник
2. 1050 руб.
От пользователей

Похожие статьи:


Универсальный 8 канальный таймер на Arduino

В статье речь пойдет о реализации на базе arduino 8 канального таймера с четырьмя суточными каналами, привязанными к реальному времени, и четырьмя периодическими каналами, которые работают по заданным интервалам. C пользовательским интерфейсом на LCD дисплее 1602.

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

Итак, о характеристиках устройства: мозги системы - плата Arduino nano (Atmega328 на борту, Atmega168 не подойдет, мало памяти!! ), для отображения всей необходимой информации LCD дисплей 1602 с I2C модулем в комплекте, модуль часов реального времени DS3231, 4 канальный модуль реле высокого уровня (High level trigger) , или с возможностью переключения. Например, как здесь  или здесь, (скетч рассчитан на 8 каналов, но мне сейчас столько не надо и релейный модуль на 8 каналов слишком громоздкий, хотелось все устройство покомпактнее). Реле именно высокого уровня  кажется мне более логичным в управлении, но при желании можно переписать скетч под низкий уровень, это не сильно сложно. Управление устройством реализовано с помощью 4 тактовых кнопок и одного энкодера. Питается все от 5 вольт, от платы, нежно выдернутой из сетевого USB адаптера.  

Возможности устройства:

  • 4 канала с суточными таймерами (до 10 таймеров вкл/выкл на каждый канал) привязаны к реальному времени (не сбрасываются при отключении питания).
  • 4 канала с периодическими таймерами (настройки: период работы (Period) чч:мм и длительность (Duration) чч:мм) НЕ привязаны к реальному времени, интервал от 1 минуты до 99 часов (сбрасываются при отключении питания)
  • сохранение настроек при отключении питания.
  • возобновление работы суточных таймеров при подаче питания в то состояние, в котором должны находиться реле относительно текущего времени согласно расписанию.
  • возможность установки/корректировки времени.
  • быстрое восстановление настроек по умолчанию в случае необходимости.
  • возможность оперативного ручного включения/отключения любых каналов независимо от текущего расписания работы.

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

Рассмотрим суточные каналы, привязанные к реальному времени. На каждый канал можно задать несколько таймеров включения и отключения, всего 20, 10 на включение и 10 на отключение соответственно, что даёт нам в итоге 10 временных интервалов на один канал на каждые сутки. Таймер - это временная метка, значение времени, при котором должно что-то произойти (если это таймер включения - включение реле, и если таймер выключения - выключение соответственно). Для наглядности изобразим это графически:

На схеме видно, что на первом канале задано 2 временных интервала (из 10 возможных), они определены двумя таймерами на включение и двумя на выключение, тут все несложно. Со вторым каналом все немного иначе: один временной интервал с 8 до 13 часов, а вот второй продолжается с 20 до 4, такой вариант тоже возможен. 

Порядок установки таймеров не важен, т.е. в десятый  таймер может быть записано время более раннее, чем в первый, также не важно, на какой именно таймер записано время, если записать, например, TimerOn1 в 13:00, TemerOf1 не записывать, а записать TemerOf2 на 15:00, то реле включится в 13:00 и выключится в 15:00. Т.е. нумерация таймеров сделана для удобства использования, на деле они же представляют из себя единый массив. Но это так, для информации, практичней и удобней, конечно же, устанавливать все по порядку вкл/выкл, вкл/выкл, ... и т.д., чтобы самому не путаться. Логика работы взята от обычного электронного сетевого таймера и будет понятна любому, кто хоть раз сталкивался с подобным устройством. 

Установка времени производится энкодером, обычный поворот меняет (увеличивает/уменьшает) часы, поворот энкодера с зажатой кнопкой меняет минуты (кратно 5). 

Перейдем к периодическим таймерам. Тут все проще, для каждого канала настраиваются только два параметра: Период (Period) и Длительность (Duration), период - это временной промежуток, через который включается реле, и длительность - это время, на которое оно включается, и повторяется это все циклично, нагляднее на схеме:

В данном примере период равен 3 (минутам, часам - не важно), а длительность 2. Следует обратить внимание, что период отсчитывается от включения (а не от выключения), соответственно, в данном примере перед первым включением пройдет времени 3, а дальше между включениями будет проходить время равное период минус длительность (3-2=1 в примере на графике). Из вышесказанного следует логика и ограничения на установку этих параметров, а именно: Длительность (Duration) должна быть меньше Периода(Period)! в противном случае, если длительность будет больше (или даже равна) периоду, реле включится и будет оставаться в таком состоянии постоянно, что вполне логично. 
Установка времени производится также энкодером, только просто поворот меняет минуты (кратно одной), а поворот зажатого энкодера меняет часы, максимальное возможное время это ровно 99 часов (можно сделать до 49 суток, но зачем?)

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

Перезагрузка также нужна, если суточный таймер устанавливается  таким образом, что на момент установки он уже должен быть включен (например, нужно настроить включение в 13:00, выключение в 20:00, а сейчас 17:00). При перезагрузке происходит следующее: переменная текущего времени проходит полный круг (сутки, 1440 минут) и принимает опять значение текущего времени, такая эмуляция прошедших суток. Это позволяет установить все каналы в такое состояние, в котором они должны находиться на данный момент, т.е. в случае отключения питания модуль часов реального времени (RTC) продолжает считать время, а при возобновлении питания вышеописанный алгоритм возвращает все на свои места.

Периодические таймеры при перезагрузке сохраняют свои настройки (период, длительность), но начинают отсчет сначала. 

Для сброса всех настроек (все каналы выключены, все таймеры неактивны) необходимо зажать кнопку энкодера и перезагрузить устройство (Кнопкой Reset) или просто включить его с зажатой кнопкой энкодера (идею подсмотрел у Гайвера). На экране отобразится надпись "Reset settings OK".

Теперь подробней. Внешний вид, органы управления, меню и настройка.

Кнопки слева направо:

Reset - перезагрузка (дублирована стандартная кнопка reset, подключена к пину RST )

1 - при нажатии: выбор настраиваемого канала (1,2,3,4...8 и далее циклично), при зажатии - вход в меню настройки времени.

- ручное изменение состояния канала (Включен, отключен, авто (согласно настроек таймеров)), либо в режиме установки времени копирует текущее время в устанавливаемое (чтоб меньше крутить энкодер). 

- выбор настраиваемого таймера для суточных каналов (Timer1 on, Timer1 off, Timer2 on, Timer2 off, ... Timer10 on, Timer10 off ), выбор настраиваемого параметра Период (Period) и Длительность (Duration) для периодических каналов. В режиме установки времени по нажатию на кнопку установленное время записывается в модуль часов RTC и становится текущим. 

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

Схема подключения компонентов выглядит как-то так:  

Кривовато, что ж, чукча не художник)

Дублирую текстом,что куда подключается, т.к. по картинке возможно не все будет понятно:

Кнопки: Пины 14, 15, 16 (А0, А1, А2), соответственно 1, 2 и 3 кнопка. Кнопка reset - пин RST

Энкодер: Пины 2, 3, 4 CLK, DT, SW соответственно.

Дисплей и модуль часов: Шина I2C, Пины 18, 19 (А4, А5) SDA, SCL. 

Выходы на управление реле:  Выходы суточных каналов 1,2,3,4 канал пины 9, 10, 11, 12. Выходы периодических каналов 5, 6, 7, 8 канал пины 5, 6, 7, 8.

Далее пройдемся по меню:

Итак, при включении или по истечении периода бездействия (1 минута) на LCD отображается стартовый экран, на который выводится следующая информация: на первой строчке - текущее время (чч:мм:сс) и день недели, на второй строчке - текущее состояние каналов периодических таймеров (PT_5й 6й 7й 8й каналы) пины 5-8 и суточных таймеров (DT_ 1й 2й 3й 4й каналы), пины 9 - 12 . Обозначения следующие: 0 - канал выключен (полностью, совсем, на настройку расписания или периода не реагирует), 1 - канал включен (точно так же, просто включен постоянно), А - канал находится в автоматическом режиме (работает по расписанию таймеров/настройке периода длительности (в зависимости какой это канал)) 

Дальше при нажатии на кнопку 1  происходит циклическое переключение по каналам. Сначала идут 4 канала суточных таймеров, потом - 4 канала периодических таймеров и опять главный экран и все заново. Нажимаем на кнопку 1 и попадаем в меню настройки первого суточного канала:

Видим здесь следующее: название канала "1 Light" в данном случае, в скетче можно задать любое другое название, далее - его состояние (On, Off, Auto), именно оно и отображается на главном экране, переключается состояние кнопкой 2. Важно не забыть после настройки времени установить состояние на Авто, иначе расписание включения/выключения канал будет игнорировать. Следующая строчка: название/тип таймера, который устанавливаем, переключается кнопкой 3, тоже циклично, по порядку: таймер включения, за ним таймер выключения и т.д. (Timer1 on, Timer1 off, Timer2 on, Timer2 off, ... Timer10 on, Timer10 off). Время каждого таймера настраиваем энкодером: поворот - часы, поворот в нажатом состоянии - минуты, нажатие на кнопку энкодера - сброс в ноль.

По умолчанию таймеры не активны "--:--", также это состояние можно получить, повернув энкодер влево, когда отображается значение 00:00, либо после максимально возможного времени 23:55 (минуты переключаются кратно 5, для быстроты и удобства), если повернуть энкодер еще вправо в сторону увеличения значения, тоже отобразится символ "--:--" , что означает таймер не задан.

Дальше, начиная с 5 канала, идут периодические таймеры:

Состояние которых также настраивается кнопкой 2 (On, Off, Auto), а кнопка 3 позволяет переключиться между настройкой периода (Period) и длительности (Duration). Значение их меняется также энкодером, с тем лишь различием, что просто поворот энкодера меняет минуты, а поворот с нажатой кнопкой - часы (расчет на то, что для данных таймеров минутные интервалы будут использоваться чаще). Специального индикатора неактивного состояния для таймеров периодических каналов не предусмотрено, достаточно просто сбросить период и длительность на ноль.

В качестве модуля часов реального времени используется ds3231, достаточно точный модуль, однако при длительном использовании часы могут убегать. Для решения этой проблемы предусмотрено меню настройки времени, вызывается оно из любого состояния устройства зажатием и удержанием кнопки 1 в течение 0,7 с., выглядит следующим образом:

В первой строчке, как и на главном экране, отображается текущее время, во второй - устанавливаемое. Логика работы простая: энкодером устанавливаем желаемое время (поворот - минуты, зажатый поворот - часы, клик - сброс в ноль), дальше по нажатию на кнопку 3 установленное время записывается в модуль часов и становится текущим. Зачастую нужно подкорректировать в плюс или в минус пару минут, и, чтобы не крутить долго энкодер (особенно когда время позднее, как на картинке), предусмотрена следующая штука: если нажать на кнопку 2, то текущее время копируется в устанавливаемое, и останется буквально в пару щелчков энкодера подправить его и, нажав на кнопку 3, сохранить изменения. День недели не корректируется, устанавливается при первой прошивке.

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

Важно! При первой прошивке, после загрузки устройства необходимо сразу сделать сброс настроек! Это связано с логикой сохранения данных в энергонезависимой памяти (EEPROM), при загрузке данные считываются из памяти и используются в работе, в процессе могут быть изменены и тут же сохраняются, чтоб в случае перезагрузки восстановить все настройки. Но сохраняются они туда только в результате работы программы (нажатие кнопок 1 - 3, истечение периода бездействия), следовательно, при первом запуске в памяти ничего нет (точнее, есть рандомный мусор), но данные оттуда считываются, и эта ерунда отображается на экране, а сброс настроек записывает дефолтные данные (те, что указаны в прошивке, все каналы отключены, все таймеры сброшены в ноль / не активны) и дальше все работает как надо.

Еще один момент: при первой прошивке необходимо установить текущее время в модуль часов, для этого в коде требуется раскомментировать во вкладке 2-setup строку:

  //watch.settime(i[0], i[1], i[2], i[3], i[4], i[5], 0);

Вместо последнего нуля вручную указать номер текущего дня недели (0 - воскресенье, 1 - понедельник и т.д.), прошить, потом закомментировать обратно и прошить еще раз. Смысл этого действа следующий: при прошивке скетч получает время компиляции (текущее), и эта строка записывает это время в модуль часов. Т.к. модуль имеет свое независимое питание, то делать это нужно только один раз.

С интерфейсом все, теперь немного о реализации. Как я уже писал, управлять можно сразу восемью каналами реле, но мне столько на данный момент не надо, да и модуль на 8 реле очень большой, поэтому я остановился на 4, остальные про запас. Все это хозяйство было решено разместить в корпусе Gainta G386 размерами 120х120х60 мм, 

Плату разводить не стал, да она тут и не нужна. Запаял все на макетку (ардуинку, кнопки, энкодер, модуль часов), развел проводами, добавил конденсатор по питанию ардуино, чтобы включения реле не влияли на ее работу. Плату питания прилепил на термоклей, в общем, все канонично))

На фото можно заметить, что выводы на каналы периодических таймеров (пины 5 - 8) подпаяны, но к реле не подключены (просто свернуты слева от модуля часов) - ждут своего времени, пока в них необходимости нет. 

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

Результат мне понравился, светят очень ярко, сам по себе кабель довольно жесткий, но при небольшом нагреве феном легко гнется и сохраняет новую форму. В общем, рекомендую. 

Еще пару фоток с этапов разработки и общий вид готового устройства:

 

PS:

Основная часть на этом закончена, далее пойдет разбор полетов скетча с пояснениями и комментариями, как оно все работает,. Мастодонтов ардуиностроения и знатоков C++ прошу не воспринимать все ниженаписанное близко к сердцу и не закидывать тапками, как говорится, "не стреляйте в пианиста, он играет как умеет...". Рассчитано на начинающих и желающих разобраться, само собой, конструктивная критика и дельные советы приветствуются. Поехали. 

Скетч разбит на вкладки для удобства восприятия и достаточно подробно закомментирован, начнем по порядку. С объявлением переменных и подключением библиотек, думаю, проблем возникнуть не должно, остановимся подробней на массивах таймеров:

uint16_t Timer1 [21] = {0, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440}; // массивы всех таймеров для суточных каналов (1 - 4) 0-й элемент отвечает за состояние канала (1 - вкл/ 0 - выкл/ 2 - авто))
uint16_t Timer2 [21] = {0, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440}; // остальное время в минутах (нечетные элементы - таймеры включения, четные - выключения)
uint16_t Timer3 [21] = {0, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440}; // время 1440 отображается на экране как "--:--", это неактивное значение, для основной логики пустая ячейка означает что таймер не задан
uint16_t Timer4 [21] = {0, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440, 1440}; // 1440 минуты  нет в сутках ( по факту это 1441 минута, т.к. счет идет с нулевой минуты, т.е. 1439 + нулевая минута)

uint32_t Timer5 [3] = {0, 0, 0};  //____________________________массив всех таймеров для периодических каналов (5 - 8) 0-й элемент отвечает за состояние канала (1 - вкл/ 0 - выкл/ 2 - авто))
uint32_t Timer6 [3] = {0, 0, 0};  //____________________________1й элемент - период, 2й - длительность (значение в миллисекундах)
uint32_t Timer7 [3] = {0, 0, 0};
uint32_t Timer8 [3] = {0, 0, 0};

Первые 4 массива (Timer1 - Timer4) предназначены для хранения данных суточных таймеров, значения соответствуют времени в минутах, прошедших с начала суток, и могут принимать значения от 0 до 1439, т.е. всего 1440, самой же 1440-й минуты в сутках нет, поэтому на это значение привязано выключенное состояние таймера и, если элемент массива равен 1440, на экране это отображается как --:-- , и этот элемент игнорируется алгоритмом обработки таймеров. Всего в каждом массиве 21 элемент, 20 элементов для установки времени таймеров и 1 элемент хранит состояния канала, это нулевой элемент массива, он может принимать значения 0,1,2 что соответствует постоянно выключенному, постоянно включенному и автоматическому состоянию работы канала.

Следующие четыре массива (Timer5 - Timer8) - это массивы периодических таймеров, нулевой элемент здесь также хранит состояние канала, а следующие два - период и длительность соответственно, только в отличие от суточных каналов значения здесь хранятся в миллисекундах (поэтому тип uint32_t).

Данные этих массивов - это дефолтные значения, которые устанавливаются при сбросе настроек.

Далее идет вкладка 2-setup, на ней по порядку задаются пины реле как выходы, далее создаются объекты экрана, часов и энкодера, после чего идет проверка на нажатое состояние кнопки энкодера и, в случае обнаружения такового, сбрасываются настройки путем вызова функции update_all (); (о ней позже)

  if (!digitalRead(SW)) { //________Сброс настроек при включении с зажатым энкодером (Все таймеры и каналы выключены)
    update_all ();
    lcd.setCursor(0, 0);
    lcd.print(F(" Reset settings"));
    lcd.setCursor(7, 1);
    lcd.print(F("OK"));
  }
  while (!digitalRead(SW));
  lcd.clear();

Надпись о сбросе настроек висит до тех пор, пока кнопка энкодера не будет отпущена, после чего экран очищается и выполнение программы продолжается, а именно: читаются данные из памяти, объявляются прерывания на пинах, подключенных к энкодеру, устанавливается время (об этом было выше), дальше идет следующая часть кода:

  watch.gettime(); //______________________________________Получаем текущее время и прокручиваем его ровно на сутки вперед, чтобы все каналы соответствовали текущему состоянию
  current_time = (((watch.Hours) * 60) + (watch.minutes));
  for (int i = 0; i <= 1439; i++) {
    if (current_time > 1439) current_time = 0;
    state1 = main_log(Timer1, state1);
    state2 = main_log(Timer2, state2);
    state3 = main_log(Timer3, state3);
    state4 = main_log(Timer4, state4);
    current_time++;
  }

В ней происходит следующее: получаем текущее время, прокручиваем его на сутки вперед и прогоняем всю логику суточных каналов через эти значения времени. Все это для того, чтобы переменные состояния суточных каналов (state1 - state4) приняли такие значения, какие должны быть в текущий момент, т.е. если включение устройства произошло, например, в 15:00, а таймер установлен с 13 до 17, то он должен быть в это время уже включен, как раз этот маневр и позволяет его включить. 

Дальше вкладка 3_loop, основная часть программы. По порядку: получаем текущее время, переводим его в минуты для работы фунцкии логики суточных каналов, запускаем таймер простоя, который считает время после последнего нажатия на любую из 3-х основных кнопок, и, если оно больше периода простоя (по умолчанию 1 минута, переменная period_wait = 60000;), сохраняем все данные в EEPROM, отключаем подсветку дисплея и присваиваем переменной number_channel значение 0, что приводит к возврату на главный экран. 

Далее запускаем опрос первой кнопки  butt1.tick(); и дальше возможно два варианта действий:

Если обнаружено нажатие на кнопку:

  if (butt1.isClick()) {  //__________________________________Выбор текущего канала для настройки (По порядку, циклически)
    period_cur = millis();
    lcd.clear();
    lcd.backlight();
    flag = 1;
    number_channel++;
    time_num = 1;
    update_all ();
    if (number_channel > 8)number_channel = 0;
  };

Сбрасываем таймер простоя, очищаем экран, включаем подсветку, переключаем переменную flag (служебная, для однократного срабатывания таймера простоя, в расчетах не используется) производим инкремент переменной number_channel, отвечающей за то, состояние какого канала отображается на экране, и, соответственно, с ним же и работаем в дальнейшем, присваиваем переменной time_num значение 1. Эта переменная отвечает за порядковый номер элемента массива, изменение которого можно произвести в данный момент (в режиме суточных каналов это - значение таймеров включения и отключения, в режиме периодических каналов это - значение периода и длительности. Обратите внимание: нулевой элемент массива не попадает в диапазон, он для состояния канала (вкл, выкл, авто) и изменяется отдельно кнопкой 2), ну и сохраняем все измененные значения функцией update_all ();

Следующим шагом проверяем, нет ли удержания кнопки 1

  if  (butt1.isHolded()) { //________________________________Меню настройки времени (Включается по удержанию кнопки 1)
    period_cur = millis();
    lcd.clear();
    lcd.backlight();
    flag = 1;
    number_channel = 9;
  }

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

Article2

После идет оператор выбора switch case, который, в зависимости от значения переменной number_channel, вызывает одну из функций: first_channel для работы с суточными каналами, second_channel для работы с периодическими каналами, или set_time для установки времени.

Далее следуют вызовы двух основных функций (main_log для суточных каналов и period_log для периодических) в такой форме:

  state1 = main_log(Timer1, state1);  //______________________Обработка каналов управления суточных таймеров реального времени (каналы 1 - 4)
  switch (Timer1 [0]) {
    case 0:  digitalWrite(9, 0);
      break;
    case 1:  digitalWrite(9, 1);
      break;
    case 2:   digitalWrite(9, state1);
      break;
  }

Переменной состояния канала присваиваем значение, полученное в результате вызова основной функции, потом смотрим на нулевой элемент массива. Если он равен 0 - выключаем канал (digitalWrite(9, 0)), если он равен единице - включаем (digitalWrite(9, 1);), а если он равен двойке - тогда состояние канала становится результатом работы основной функции (digitalWrite(9, state1);), и так для каждого из 8 каналов.

Рассмотрим следующие функции, находящиеся на вкладке 4_LCD_and_channels_logic. Первая функция - это start_screen(), как нетрудно догадаться, отвечает за главный экран. По факту, после своего вызова она десять раз в секунду (чтоб избежать мерцания LCD) обновляет данные на экране, выводит текущее время и состояние каналов. Далее следует функция first_channel, которая отвечает за вывод на экран информации о суточных каналах, а также за настройку их параметров. Именно она опрашивает кнопку 2 и в случае ее нажатия сбрасывает таймер простоя, сохраняет данные и производит инкремент нулевого элемента массива, отвечающего за состояние канала (не допуская увеличения его значения больше 2):
 

  if (butt2.isClick()) {
    period_cur = millis();//_______________________________________Сброс таймера простоя, сохранение всех изменений, переключение состояния канала
    flag = 1;
    update_all ();
    timeMassive [0]++;
  }
  if (timeMassive [0] > 2) timeMassive [0] = 0;

и кнопку 3:

  if (butt3.isClick()) { //________________________________________Сброс таймера простоя, сохранение всех изменений, переключение настраиваемого таймера (элемент массива)
    period_cur = millis();
    flag = 1;
    update_all ();
    time_num++;
  }
  if (time_num > 20) time_num = 1;

Которая с помощью увеличения переменной time_num переключает редактируемый (и отображаемый на экране) элемент массива. 

Также идет опрос и обработка событий энкодера:

  if (enc1.isRight())timeMassive [time_num] += 60;   // _________________________если был поворот направо, увеличиваем на 60 минут (1 час)
  if (enc1.isLeft()) timeMassive [time_num] -= 60;   // _________________________если был поворот налево, уменьшаем на 60 минут (1 час)
  if (enc1.isRightH()) timeMassive [time_num] += 5;  //_________________________ если было удержание + поворот направо, увеличиваем на 5 минут
  if (enc1.isLeftH()) timeMassive [time_num] -= 5;   //__________________________если было удержание + поворот налево, уменьшаем на 5 минут
  if (enc1.isClick()) timeMassive [time_num] = 0;
  if (timeMassive [time_num] > 1440) timeMassive [time_num] = 1440;

Поворот направо/налево - это +/- один час (+/- 60, т.к. значения хранятся в минутах), зажатый поворот - то же самое с минутами (кратно 5, чтоб долго не крутить), и клик - сброс в ноль.

Функция second_channel делает практически все то же самое для каналов периодических таймеров, с небольшими отличиями: вначале идет пересчет значений в минуты и часы для отображения на дисплее (данные хранятся в миллисекундах), и логика работы с энкодером тоже немного другая (поворот - минуты, зажатый поворот - часы).

Дальше на вкладке 5_main_logic описаны две основные функции расчета состояния: main_log для суточных каналов, и period_log для периодических каналов. Функция для суточных каналов работает следующим образом: ищем в массиве элемент, соответствующий текущему времени:

  for ( i = 1;  i < 21; i++) { //___________________________Ищем в массиве таймеров элемент, соответствующий текущему времени c значением не равным 1440 (означает неактивно, отображается "--:--")
    if ((timeMassive [i] == current_time) && (timeMassive [i] != 1440))  {
      j = timeMassive [i];
    }
  }

Если такого нет (переменная j сохранила значение по умолчанию 1440), то оставляем состояние канала как было и выходим из функции:
 

 if (j == 1440)return (cur_state); 

Если же такой элемент найден, то определяем, является ли его порядковый номер в массиве четным или нечетным. Если он четный (Timer1off, Timer2off ....Temer10off) - выключаем канал, если нечетный (Timer1on,Timer2on... Timer10on) - включаем:

  for ( i = 1;  i < 21; i++) {
    if ((j == timeMassive [i]) && (timeMassive [i] != 1440))  {
      n = i;
    }
  }
  i = 0;
  if ((n % 2) == 0) { //____________________________________Четные таймеры выключают (LOW)
    return (LOW);
  }
  else return (HIGH); //____________________________________Нечетные таймеры включают (HIGH)
}

Функция period_log для периодических каналов еще проще: это классический  таймер, использующий millis, принимает в качестве параметров значения для периода и длительности из массива, а дальше, по истечении времени, соответствующего периоду, включает канал, а по истечении времени, равному период+длительность, отключает его:

  if (millis() - *counter >= period_ms) { //_______________Отсчитывает время периода, включает реле, отсчитывает время длительности, выключает реле
    *state5 = HIGH;
    if ((millis() - *counter) >= (period_ms + duration_ms)) {
      *counter += period_ms;
      *state5 = (LOW);
    }
  }

На вкладке 6_EEPROM_red_save две функции: read_all и update_all, первая читает данные всех массивов из памяти, вторая - сохраняет их туда (обновляет).

И последняя вкладка 7_set_time содержит одноименную функцию, которая опрашивает энкодер, а также 2 и 3 кнопки, кнопка 2 копирует значение текущего времени в установленное, кнопка 3 записывает измененное время в модуль часов через библиотечный метод watch.settime.  Обработка событий энкодера и вывода информации на экран реализована аналогично другим функциям.

 

UPD:
Во время работы над статьей мне подсказали, что периодические таймеры могут использоваться для автоматизации полива, и, например, в системах периодического затопления или в системах капельного полива очень критична возможность точной настройки времени полива. Поэтому было принято решение добавить возможность настройки периодических таймеров с точностью до секунды, что и было реализовано. В интерфейсе, соответственно, произошли некоторые изменения:

Секунды появились на экране, надпись "Duration" перестала вмещаться, и было решено заменить синонимом "Length", что по-прежнему означает продолжительность включенного состояния. Поменялся несколько и способ установки времени: минуты и часы настраиваются так же поворотом и зажатым поворотом соответственно, а вот чтобы настроить секунды необходимо зажать кнопку 2 и, не отпуская ее, поворачивать энкодер. В остальном все по-старому. Соответственно, минимально возможный интервал теперь - это секунда через секунду (Период 00:00:02, длительность 00:00:01), максимальный - по-прежнему 99 часов. Так как правки подоспели когда уже все было готово, переделывать не стал, к статье будут прикреплены две прошивки: Universal_8_Channels_timer и Universal_8_Channels_timer_sec.В первой все в точности так, как описано в статье, а вторая - с добавленными секундами.

На этом все, спасибо всем кто дочитал до конца. Буду рад, если кому-то мои наработки окажутся полезными. Отдельная благодарность AlexGyver, в проекте использованы его библиотеки, да и с его сайта получено очень много полезной информации. 

Прикрепленные файлы:

Теги:

Опубликована: 0 0
Я собрал 0 1
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 5 Проголосовало: 1 чел.

Комментарии (5) | Я собрал (0) | Подписаться

0
Публикатор #
На форуме автоматически создана тема для обсуждения статьи.
Ответить
0
455651 #
Без тапок и по существу: автор, для этих целей и 8-й меги за глаза хватит! Оптимизируй код! И нафига reset? Прочитай про watchdog! При первой прошивке сброс настроек - алгоритм тяжело придумать? Скетч под низкий уровень - при инициализации религия не позволяет пункт меню нарисовать?
Ответить
0

[Автор]
AMatroskin #
Проект открытый и не коммерческий, в связи с чем, по существу: надо - сделай.
Ответить
0
ashelehov #
На рисунке (схемой назвать язык не поворачивается) кнопки висят в воздухе. А как по факту, есть подтяжка или так и болтаются?
Ответить
0

[Автор]
AMatroskin #
Не вижу смысла заморачиваться со схемой для ардуино проектов. Подтяжка реализована в прошивке (INPUT_PULLUP) по умолчанию.
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется электрическое сопротивление?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

Raspberry Pi 2
Raspberry Pi 2
Ручной фен 450 Вт с регулировкой температуры DC-DC регулируемый преобразователь 1.5-37В 2А с индикатором
вверх