====== Часы реального времени ====== В данной статье рассматривается пример создания часов рального времени. На индикатор будет выводиться точное время, а двоеточие на нем будет моргать раз в секунду. Точное время будет автоматически устанавливаться во вермя компиляции прошивки. ===== Описание компонентов ===== {{ :время:img_6478.jpg|}} В нашем проекте мы используем: -[[amp>product/arduino-uno|Arduino Uno]] -[[amp>product/arduino-troyka-shield|Troyka Shield]] -[[amp>product/troyka-quad-display|Четырёхразрядный цифровой индикатор]] -[[amp>product/real-time-clock|Часы реального времени]] -[[amp>product/battery-cr1225|Батарейку CR1225]] ==== Часы реального времени ==== {{ :время:rtc.jpg?nolink& |}} Мы используем модуль с часами реального времени от Seeed Studio. Они построены на базе микросхемы DS1307 от Maxim Integrated. Из элементов обвязки она требует три резистора, часовой кварц и батарейку, которые уже имеются на данном модуле. Модуль обладает следующими свойствами: * Подсчет времени (секунды, минуты, часы), даты (год, месяц, число), дня недели * Двухпроводной интерфейс I²C Суть часов реального времени в том, что при наличии батарейки, они могут идти даже если основное устройство обесточено. Мы с такими часами сталкиваемся постоянно в ноутбуках или цифровых фотоаппаратах. Если достать из этих устройств аккумулятор, а через некоторое время вернуть их обратно, то время не сбросится. В этом заслуга часов реального времени, Real Time Clock (RTC). Все необходимые библиотеки можно скачать с [[http://www.seeedstudio.com/wiki/index.php?title=Twig_-_RTC|официального сайта]]. ==== Индикатор ==== {{ :время:4_digit_display.jpg?nolink& |}} Мы используем четырёхразрядный индикатор от Seeed Studio. Основное в индикаторе --- микросхема TM1637, представляющая собой драйвер для отдельных 7-сегментных разрядов. В данном модуле используется 4 разряда. Модуль обладает следующими свойствами: * 8 градаций яркости * Двухпроводной интерфейс работы (CLK, DIO) Данный модуль мы используем для показа времени: часов и минут. Удобство модуля в том, что подключается он всего по двум проводам и не требует программной реализации динамической индикации, поскольку все уже реализовано внутри модуля. Динамическая индикация --- это процесс, при котором индикаторы в нашем модуле загораются последовательно. Но мерцания мы не видим, поскольку человеческой глаз обладает большой инертностью. Данный метод позволяет очень хорошо экономить количество соединений между индикаторами и контроллером: * Статическая индикация: 4 цифры × 7 сегментов = 28 соединений. * Динамическая индикация: 7 сегментов + 4 общих анода или катода = 11 соединений. * Микросхема TM1637: 2 соединения. Выгода очевидна. Библиотека для данного модуля также может быть скачана с [[http://www.seeedstudio.com/wiki/Grove_-_4-Digit_Display|сайта производителя]]. ===== Подключение ===== Модуль часов реального времени необходимо подключить к выводам SCL/SDA, относящимся к шине I²C. Также необходимо подключить линии питания (Vcc) и земли (GND). Линии SDA/SCL имеют собственные отдельные пины на Arduino, однако внутри они так или иначе подключены к пинам общего назначения. Если рассмотреть Arduino Uno, линии SDA соответствует пин A4, а SCL — A5. В комплекте с модулем поставляется шлейф с мама-контактами, которые удобнее подключать к Troyka Shield. Однако отдельные пины SDA и SCL на ней не выведены, поэтому мы осуществили подключение прямо через пины A5 и A4. В плане подключения индикатора --- все гораздо проще. Выводы CLK и DIO можно подключить к любым цифровым выводам. В данном случае используются 12-й и 11-й выводы соответственно. {{ :время:img_6476.jpg? |}} ===== Написание прошивки ===== Функция ''setup'' должна инициализировать часы реального времени и индикатор, а также записывать время компиляции во внутреннюю память часов реального времени. Все действие, а точнее, чтение времени из RTC и вывод его на индикатор, будет производиться в функции ''loop''. Код для этого выглядит следующим образом: #include #include #include "TM1637.h" #include "DS1307.h" //Массив, содержащий время компиляции char compileTime[] = __TIME__; //Номера пинов Arduino, к которым подключается индикатор #define DISPLAY_CLK_PIN 12 #define DISPLAY_DIO_PIN 13 //Для работы с микросхемой часов и индикатором мы используем библиотеки //Классы TM1637 и DS1307 объявлены именно в них TM1637 display(DISPLAY_CLK_PIN, DISPLAY_DIO_PIN); DS1307 clock; void setup() { //Включаем и настраиваем индикатор display.set(); display.init(); //Запускаем часы реального времени clock.begin(); //Получаем число из строки, зная номер первого символа byte hour = getInt(compileTime, 0); byte minute = getInt(compileTime, 3); byte second = getInt(compileTime, 6); //Готовим для записи в RTC часы, минуты, секунды clock.fillByHMS(hour, minute, second); //Записываем эти данные во внутреннюю память часов. //С этого момента они начинают считать нужное для нас время clock.setTime(); } void loop() { //Значения для отображения на каждом из 4 разрядов int8_t timeDisp[4]; //Запрашиваем время с часов clock.getTime(); //Получаем десятки часов с помощью целочисленного деления timeDisp[0] = clock.hour / 10; //Получаем единицы часов с помощью остатка от деления timeDisp[1] = clock.hour % 10; //Проделываем то же самое с минутами timeDisp[2] = clock.minute / 10; timeDisp[3] = clock.minute % 10; //... а затем выводим его на экран display.display(timeDisp); //у нас нет отдельных разрядов для секунд, поэтому //будем включать и выключать двоеточие каждую секунду display.point(clock.second % 2 ? POINT_ON : POINT_OFF); } //Содержимое функции объяснено ниже char getInt(const char* string, int startIndex) { return int(string[startIndex] - '0') * 10 + int(string[startIndex+1]) - '0'; } Теперь загружаем этот код в среду разработки, компилируем и заливаем. Смотрим на дисплей --- бинго! Время на дисплее --- время компиляции. {{ :время:img_6477.jpg|}} ==== Объяснение функции getInt ==== Для начала необходимо понять, откуда же в массиве ''compileTime'' появляется время. Оно появляется в этой строчке: ''unsigned char compileTime[] = %%__TIME__%%;'' Компилятор вместо ''%%__TIME__%%'' подставляет строку, содержащую время компиляции в виде ''%%__TIME__%% = "hh:mm:ss"'', где hh - часы, mm - минуты, ss - секунды. Вернемся к коду, который необходимо объяснить: char getInt(const char* string, int startIndex) { return int(string[startIndex] - '0') * 10 + int(string[startIndex+1]) - '0'; } В массиве ''string'', передаваемом в качестве параметра в функцию ''getInt'', мы получаем символ с индексом ''startIndex'' и следующий за ним, чтобы в итоге получить двухзначное целое число. Однако, изначально это не число, а пара //символов//. Чтобы получить число по символу, нам необходимо вычесть из этого символа символ нуля ('''0'''): ведь в таблице ASCII все символы цифр идут одна за другой, начиная с символа нуля. Поэтому код ''int(string[startIndex]) - '0')'', дословно, делает следующее: «Берем символ номер ''startIndex'', вычитаем из него символ нуля и переводим в целочисленный тип». ===== Проблемы ===== Да, этот код рабочий, и часы будут идти. Однако, если отключить питание, а через несколько минут включить, то после включения время время вновь станет тем, которое было при компиляции. Это происходит потому что после включения питания, вновь исполняется код, находящийся в функции ''setup''. А он записывает в часы реального времени старое значение времени. Чтобы этого избежать, нам необходимо еще чуть-чуть модифицировать код. Каждый раз в функции ''setup'' будет происходить подсчет «хэша» времени компиляции --- будет рассчитываться количество секунд, прошедшее с 00:00:00 до времени компиляции. И этот хэш будет сравниваться с хэшем в EEPROM. Напомним EEPROM — память, которая не обнуляется при отключении питания. Если значения посчитанного и сохранённого ранее хэша совпадают, то это значит, что перезаписывать время в модуль часов нет необходимости: это уже было сделано. А вот если эта проверка не проходит, то происходит перезапись времени в RTC. Для записи/чтения числа типа ''unsigned int'' в/из EEPROM написаны две дополнительные функции ''EEPROMWriteInt'' и ''EEPROMReadInt''. Они добавлены потому что функции ''EEPROM.read'' и ''EEPROM.write'' могуть читать и писать только данные типа ''char''. #include #include #include "TM1637.h" #include "DS1307.h" //Массив, содержащий время компиляции char compileTime[] = __TIME__; //Номера пинов Arduino, к которым подключается индикатор #define DISPLAY_CLK_PIN 12 #define DISPLAY_DIO_PIN 13 //Для работы с микросхемой часов и индикатором мы используем библиотеки TM1637 display(DISPLAY_CLK_PIN, DISPLAY_DIO_PIN); DS1307 clock; void setup() { //Включаем и настраиваем индикатор display.set(); display.init(); //Запускаем часы реального времени clock.begin(); //Получаем число из строки, зная номер первого символа byte hour = getInt(compileTime, 0); byte minute = getInt(compileTime, 3); byte second = getInt(compileTime, 6); //Импровизированный хэш времени //Содержит в себе количество секунд с начала дня unsigned int hash = hour * 60 * 60 + minute * 60 + second; //Проверяем несовпадение нового хэша с хэшем в EEPROM if (EEPROMReadInt(0) != hash) { //Сохраняем новый хэш EEPROMWriteInt(0, hash); //Готовим для записи в RTC часы, минуты, секунды clock.fillByHMS(hour, minute, second); //Записываем эти данные во внутреннюю память часов. //С этого момента они начинают считать нужное для нас время clock.setTime(); } } void loop() { //Значения для отображения на каждом из 4 разрядов int8_t timeDisp[4]; //Запрашиваем время с часов clock.getTime(); //Получаем десятки часов с помощью целочисленного деления timeDisp[0] = clock.hour / 10; //Получаем единицы часов с помощью остатка от деления timeDisp[1] = clock.hour % 10; //Проделываем то же самое с минутами timeDisp[2] = clock.minute / 10; timeDisp[3] = clock.minute % 10; //... а затем выводим его на экран display.display(timeDisp); //у нас нет отдельных разрядов для секунд, поэтому //будем включать и выключать двоеточие каждую секунду display.point(clock.second % 2 ? POINT_ON : POINT_OFF); } char getInt(const char* string, int startIndex) { return int(string[startIndex] - '0') * 10 + int(string[startIndex+1]) - '0'; } //Запись двухбайтового числа в память void EEPROMWriteInt(int address, int value) { EEPROM.write(address, lowByte(value)); EEPROM.write(address + 1, highByte(value)); } //Чтение числа из памяти unsigned int EEPROMReadInt(int address) { byte lowByte = EEPROM.read(address); byte highByte = EEPROM.read(address + 1); return (highByte << 8) | lowByte; } ===== Заключение ===== В данной статье был показан пример работы с микросхемой часов реального времени RTC DS1307 и микросхемой-драйвером индикатора TM1637, также мы научились получать дату и время на этапе компиляции. Теперь, если выставить нужное время на часах, а потом отключить питание хоть на несколько часов, то после включения время вновь будет точным. Проверено!