====== Часы реального времени ======
В данной статье рассматривается пример создания часов рального времени. На индикатор будет выводиться точное время, а двоеточие на нем будет моргать раз в секунду. Точное время будет автоматически устанавливаться во вермя компиляции прошивки.
===== Описание компонентов =====
{{ :время: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, также мы научились получать дату и время на этапе компиляции. Теперь, если выставить нужное время на часах, а потом отключить питание хоть на несколько часов, то после включения время вновь будет точным. Проверено!