Электронные часы

Необходимые материалы

Arduino Uno – 1 шт.

Модуль часов реального времени DS-1307 — 1 шт.

4-разрядный цифровой LED-дисплей на основе TM1637 (с раздели- тельным двоеточием) — 1 шт.

Текстовый 2-строчный дисплей конфигурации 16 2, OLED-типа (WEH001602) или ЖК-типа (MT–16S2) — 1 шт.

Внешний адаптер для Arduino 9-10 В, 1-2 А — 1 шт.

image-Необходимые-комплектующие-1 image-Необходимые-комплектующие-2 image-Необходимые-комплектующие-3

Начнем знакомство с интерфейсом TWI с того, что научимся подключать к Arduino часы реального времени (Real Time Clock, RTC). Обще- признанным стандартом в этой области стала давняя разработка фирмы Dallas Semiconductor под названием DS-1307. Огромное большинство модулей часов реального времени для Arduino делается либо прямо на основе этой микросхемы, либо совместимо с ней по командам.

Устроены эти модули также практически одинаково: они содержат микросхему DS-1307, резервную батарейку-«монетку» (обычно типа 2032, иногда меньшего размера — 1220, 1225 или аналогичные) и несколько дополнительных компонентов. Примеры подобных модулей разных производителей приведены на рисунках на этой странице вверху. Все такие модули соединяются с Arduino с помощью четырех проводов: питание 5 В, «общий» GND и два проводника интерфейса TWI SDA и SCL, которые подключатся к одноименным выводам Uno либо к контактам Nano и Mini A4 (SDA) и A5 (SCL).

По причине одинакового устройства и идентичного подключения к микросхеме для большинства таких модулей подходит любая из библиотек для работы с часами реального времени DS-1307. Мы будем ориентироваться на очень удобную библиотеку RTClib, которую можно скачать по адресу: https://github.com/adafruit/RTClib.

Подключение и установка часов

Подключение любого из модулей часов на основе DS1307 показано на рисунке на следующей странице. Заметим, что на современных платах Uno есть отдельные выводы SDA и SCL, которые дублируют выводы A4 (SDA) и A5 (SCL), и в случае Uno часы можно подключать к любой из этих пар выводов интерфейса TWI.

image-Arduino-nano-uno-miniЧтобы проверить работу часов совместно с библиотекой и установить их на правильное время, мы в этом простейшем случае будем выводить показания через последовательный порт Arduino. Напоминаем, что часы DS1307 без батарейки неработоспособны. Скорее всего, начальная установка часов при первом запуске потребуется в любом случае — даже если часы уже когда-то устанавливались, то они могут сбиваться при переносе с места на место (можно незаметно для себя кратковременно замкнуть батарейку при подключении), они могут просто отстать или уйти вперед и так далее.

Библиотека RTClib в этом отношении устроена очень удобно — она предоставляет специальную функцию adjust(), входными параметрами которой служат значения даты и времени, извлекаемые из компьютера в момент компиляции скетча. Так как компиляция производится заново каждый раз при загрузке программы, то отклонение времени, загруженного в часы, от реального будет минимально. Но пользоваться этой функцией следует только один раз, иначе время будет устанавливаться на одну и ту же величину при каждом включении контроллера.

Потому поступают так: функцию adjust() вставляют в процедуру setup скетча и оставляют закомментированной. Если после загрузки программы часы не запускаются или выявляются какие-то несуразности в выдаваемых значениях времени, то функцию раскомментируют и загружают программу повторно. Затем функцию нужно снова закомментировать и опять загрузить программу. После этого часы будут идти нормально. Эту процедуру перезагрузки времени, увы, приходится повторять как минимум ежегодно (а лучше раз в сезон) — уход часов с бытовым кварцем составляет обычно не менее минуты в месяц.

Подробности для любознательных: а можно ли часы «подводить» автоматически?
Разумеется, можно. Однако, для этого необходимо откуда-то получать значения точного времени. Это можно делать с помощью модулей, использующих службу точного времени DCF77  с помощью GPS-модулей, через подключение к Интернету и т. д. В любом случае это отдельная и не всегда простая задача, которую мы сейчас решать не будем.

 

 

 

 

 

 

 

 

Создадим новый скетч и назовем его ProbaRTClib. В начале объ- являются все нужные библиотеки (Wire для работы с интерфейсом TWI и RTClib для часов) и вводится глобальная переменная old_ second, которая нам потребуется для обновления показаний точно раз в секунду (а не каждый раз, когда Arduino освобождается):

#include <Wire.h>

#include <RTClib.h>

RTC_DS1307 RTC; //инициализируем библиотеку для работы с DS1307

byte old_second

В процедуре setup мы все эти библиотеки запускаем, включаем передачу через Serial-порт и один раз выводим текущие показания часов. Заодно предусматриваем в закоментированном виде нашу функцию для установки часов:

void setup() {
Serial.begin(9600);
Wire.begin();
RTC.begin();
/* строка ниже используется для настройки
времeни и даты из компьютера при смене
батарейки, уходе или сбое часов:*/
//RTC.adjust(DateTime(__DATE__, __TIME__));
if (! RTC.isrunning()) {
Serial.print(“RTC NOT running!”);
} else {
display_time ();
Serial.print(“ “);
display_date ();
Serial.println();
}
}

По этой процедуре программа выдаст сообщение «RTC NOT running!», если часы еще ни разу не запускались после смены батарейки и в них не установлено время. Но нам нужно запускать установку часов и тогда, когда часы просто «ушли», потому закомментированная функция установки размещается в самом начале. Запрос текущих показаний и их вывод мы для удобства разместили в отдельных функциях display_time() и display_date(). Их код будет таким:

void display_time () {
DateTime clock = RTC.now();
if (clock.hour()<10) Serial.print(“0”);
Serial.print(clock.hour()); //часы
Serial.print(“:”);
if (clock.minute()<10) Serial.print(“0”);
Serial.print(clock.minute()); //минуты
Serial.print(“:”);
if (clock.second()<10) Serial.print(“0”);
Serial.print(clock.second()); //секунды
old_second=clock.second();
}
void display_date () {
DateTime clock = RTC.now();
if (clock.day()<10) Serial.print(“0”);
Serial.print(clock.day()); //дата день
Serial.print(“.”);
if (clock.month()<10) Serial.print(“0”);
Serial.print(clock.month()); //дата месяц
Serial.print(“.”);
Serial.print(clock.year()); //дата год
}

В начале каждой функции расположен запрос к часам (RTC.now), возвращающий зашифрованные в структуре DateTime значения даты и времени. Далее они по очереди извлекаются и отправляются через порт. Каждое значение предваряется отправкой символа нуля, если оно меньше 10, то есть оно всегда будет занимать два разряда. Добавка нуля до второго разряда очень важна при выводе значений времени.

image-Электронные-часы-установка

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

В конце функции display_time() мы обновляем значение old_second. В основном цикле программы мы сравниваем его с текущим значением секунд, и если оно отличается, то снова и снова выводим часы через порт. Таким образом, мы в цикле непрерывно опрашиваем часы (точнее, запрос посылается по мере готовности контроллера, если он занят еще какими-то делами), а выводим новые значения только тогда, когда сменятся секунды:void loop() {

DateTime clock = RTC.now();
if (clock.second()!=old_second)
{ display_time ();
Serial.print(“ “);
display_date ();
Serial.println(); }
}

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

Подробности для любознательных: как обойти лишние запросы часов?
Библиотека RTClib всем хороша, но она каждый раз при запросе извлекает из часов полную информацию о времени и кален- даре — всего семь байт, считая дни недели, которые мы тут не отображаем. Для этого ей нужно прогнать по TWI туда-сюда около полутора десятков байт, что занимает довольно много ресурсов контроллера. Он в это время может быть занят каким-то другим важным делом, и перегруженность запросами приведет к тому, что что-то начнет тормозить — либо это «важное дело», либо обновление часов. Между тем нам совсем не нужна вся эта информация до тех пор, пока не произошли изменения. Можно ли делать запросы пореже или запрашивать все время, например, только одни секунды?

Есть библиотеки для работы с RTC, которые так и поступают: они позволяют отдельно запрашивать любой из компонентов даты и времени. Но их использование может привести к проблемам: если вы сделаете запрос на вывод минут в момент времени, например, 03:14:59, то есть прямо перед тем, как показания минут должны приравняться к «15» (03:15:00), а затем запросите секунды, то на часах отобразится 03:14:00 — вы потеряете минуту. Аналогичная история будет, если делать запросы в обратном порядке: вы обре- тете лишнюю минуту (в момент 03:15:00 часы покажут 03.15.59). Такие случаи, когда откуда-то нужно получать подряд значения зависимых друг от друга величин, в микроэлектронике встречаются достаточно часто (типичный случай: побайтное чтение или запись быстро меняющегося многобайтового числа). Обычный прием в таких случаях заключается в том, что в момент обращения еди- новременно где-то запоминаются все составляющие, а затем из этого запасника (буфера) они извлекаются по очереди. Именно это и делает RTClib, единовременно извлекая показания часов и сохраняя их в своей внутренней структуре DateTime. Потому полное чтение каждый раз совершенно оправданно, даже если мы эту информацию используем не полностью.

Но возможность ускорения все-таки имеется. Микросхема DS1307 имеет еще один контакт под названием SQW, который выведен наружу в модулях некоторых производителей (см., например, модуль фирмы Adafruit на рисунке справа на следующей странице). На SQW по умолчанию подается частота 1 Гц, синхронизированная со сменой секунд.

И если подключить этот вывод к любому цифровому контакту Arduino, то можно отслеживать изменение уровня на этом выводе (подобно тому, как мы отслеживали момент нажатия кнопки в Эксперименте 2). В
момент смены уровня с высокого на низкий (или наоборот, неважно) мы и запрашиваем у часов новое значение. Это займет гораздо меньше ресурсов контроллера, чем непрерывная посылка и прием многих байтов по TWI. Если вы достанете такой модуль часов, то в качестве домашнего задания предлагаю попробовать его подключить самостоятельно по указанным здесь сведениям. И еще подумать в такую сторону: а нельзя ли обеспечить таким способом еще более редкий опрос часов — например, один раз в минуту?

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

Отображение времени на четырехразрядном индикаторе.

Знакомый нам четырехразрядный индикатор, с которым мы столь- ко возились в начале главы 3 (см. Эксперимент 9), вместе с часами можно подключить по схеме, показанной на рисунке внизу. Ввиду простоты подключения мы на этот раз не стали рисовать image-Дисплей-1 схему, а ограничились наглядной монтажной. При этом на схеме часы подключены к дополнительным выводам TWI-интерфейса SDA и SCL, имеющимся на платах Arduino Uno. Напомним, что они дублируют вы- воды A4 (SDA) и A5 (SCL), на Uno часы можно подключать к любой из этих пар выводов интерфейса TWI.

 

 

Применим знания по работе 4-разрядного индикатора, полученные  для вывода значений часов. Создадим новый скетч и назовем его RTC_4digit. В начале программы вам не встретится ничего такого, чего бы мы не делали раньше — здесь объединены скетчи для индикатора из предыдущей главы и для часов из предыдущего раздела:

 

#include <Wire.h> //библиотека для TWI

#include <RTClib.h> //библиотека для часов
#include <TM1637.h> //библиотека для дисплея
#define CLK 5 //тактовый вывод дисплея к выводу 5
#define DIO 4 //вывод данных дисплея к выводу 4
TM1637 disp4(CLK,DIO); // запускаем библиотеку для индикатора
RTC_DS1307 RTC; //запускаем библиотеку для работы с DS1307
byte old_second; //сохраненное значение секунд
int8_t values[4] = {14,14,14,14}; // массив для индикатора «EEEE»

В процедуре setup также повторяем ранее пройденное, за тем исклю- чением, что при ошибке часов выводим на индикатор строку «ЕЕЕЕ»:void setup() {

disp4.init();// инициализация библиотеки дисплея
disp4.clearDisplay(); //очистка дисплея
disp4.set(BRIGHT_TYPICAL); // устанавливаем среднюю яркость
Wire.begin(); //библиотека для TWI
RTC.begin(); //часы
/* строка для настройки времeни и даты:*/
//RTC.adjust(DateTime(__DATE__, __TIME__));
if (! RTC.isrunning()) {
//выводим “EEEE” при ошибке:
disp4.display(values); // вывод массива “EEEE”
while (1); //зацикливаем программу при ошибке
} else {
display_time ();
}
}

Обратите внимание на выражение while (1), которое вставлено в программу в случае ошибки часов. Это выражение — классический способ зациклить программу навечно. В данном случае программа

 

Результат работы программы показан. image-Рабочие-часы
на рисунке справа. Мы здесь намеренно сместили время до упора вправо, потому что нам нужно освободить место на дисплее для следующего эксперимента. Если хотите поставить его посередине, то нужно изменить установку курсора: в функции display_time() строку OLED1.setCursor(11,0) надо заменить на OLED1.setCursor(6,0), а OLED1.setCursor(13,0) (для мигания) на OLED1.setCursor(8,0).

 

Поделись с друьями:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *