Как использовать RTC (часы реального времени) с Arduino и LCD

Добавлено 4 октября 2016 в 08:00

В данной статье мы рассмотрим, как сделать точные часы на базе Arduino или AVR-микроконтроллера микросхемы часов реального времени DS1307. Время будет выводиться на LCD дисплей.

Что необходимо

Вы можете заменить плату Arduino на контроллер Atmel, но убедитесь, что у него достаточно входных и выходных выводов и есть аппаратная реализация интерфейса I2C. Я использую ATMega168A-PU. Если вы будете использовать отдельный микроконтроллер, то вам понадобится программатор, например, AVR MKII ISP.

Предполагается, что читатель знаком с макетированием, программированием в Arduino IDE и имеет некоторые знания языка программирования C. Обе программы, приведенные ниже, не нуждаются в дополнительном разъяснении.

Введение

Как микроконтроллеры отслеживают время и дату? Обычный микроконтроллер обладает функцией таймера, который стартует от нуля при подаче напряжения питания, а затем начинает считать. В мире Arduino мы можем использовать функцию millis(), чтобы узнать, сколько прошло миллисекунд с того времени, когда было подано напряжение питания. Когда вы снимете и снова подадите питания, она начнет отсчет с самого начала. Это не очень удобно, когда дело доходит до работы с часами и датами.

Вот здесь и будет удобно использование микросхемы RTC (Real Time Clock, часов реального времени). Эта микросхема с батарейкой 3В или каким-либо другим источником питания следит за временем и датой. Часы/календарь обеспечивают информацию о секундах, минутах, часах, дне недели, дате, месяце и годе. Микросхема корректно работает с месяцами продолжительностью 30/31 день и с високосными годами. Связь осуществляется через шину I2C (шина I2C в данной статье не обсуждается).

Если напряжение на главной шине питания Vcc падает ниже напряжения на батарее Vbat, RTC автоматически переключается в режим низкого энергопотребления от резервной батареи. Резервная батарея – это обычно миниатюрная батарея (в виде «монетки», «таблетки») напряжением 3 вольта, подключенная между выводом 3 и корпусом. Таким образом, микросхема по-прежнему будет следить за временем и датой, и когда на основную схему будет подано питание, микроконтроллер получит текущие время и дату.

В этом проекте мы будем использовать DS1307. У этой микросхемы вывод 7 является выводом SQW/OUT (выходом прямоугольных импульсов). Вы можете использовать этот вывод для мигания светодиодом и оповещения микроконтроллера о необходимости фиксации времени. Мы будем делать и то, и другое. Ниже приведено объяснение работы с выводом SQW/OUT.

Для управления работой вывода SQW/OUT используется регистр управления DS1307.

Ригистр управления DS1307
Бит 7Бит 6Бит 5Бит 4Бит 3Бит 2Бит 1Бит 0
OUT00SQWE00RS1RS0
Бит 7: управление выходом (OUT)
Этот бит управляет выходным уровнем вывода SQW/OUT, когда выход прямоугольных импульсов выключен. Если SQWE = 0, логический уровень на выводе SQW/OUT равен 1, если OUT = 1, и 0, если OUT = 0. Первоначально обычно этот бит равен 0.
Бит 4: включение прямоугольных импульсов (SQWE)
Этот бит, когда установлен в логическую 1, включает выходной генератор. Частота прямоугольных импульсов зависит от значений битов RS0 и RS1. Когда частота прямоугольных импульсов настроена на значение 1 Гц, часовые регистры обновляются во время спада прямоугольного импульса. Первоначально обычно этот бит равен 0.
Биты 1 и 0: выбор частоты (RS[1:0])
Эти биты управляют частотой выходных прямоугольных импульсов, когда выход прямоугольных импульсов включен. Следующая таблица перечисляет частоты прямоугольных импульсов, которые могут быть выбраны с помощью данных битов. Первоначально обычно эти биты равны 1.
Выбор частоты прямоугольных импульсов и уровня на выводе SQW/OUT микросхемы DS1307
RS1RS0Частота импульсов и уровень на выходе SQW/OUTSQWEOUT
001 Гц1x
014,096 кГц1x
108,192 кГц1x
1132,768 кГц1x
xx000
xx101

 Данная таблица поможет вам с частотой:

Выбор частоты прямоугольных импульсов DS1307
Частота импульсовБит 7Бит 6Бит 5Бит 4Бит 3Бит 2Бит 1Бит 0
1 Гц00010000
4,096 кГц00010001
8,192 кГц00010010
32,768 кГц00010011

Если вы подключили светодиод и резистор к выводу 7 и хотите, чтобы светодиод мигал с частотой 1 Гц, то должны записать в регистр управления значение 0b00010000. Если вам нужны импульсы 4,096 кГц, то вы должны записать 0b000100001. В этом случае, чтобы увидеть импульсы вам понадобится осциллограф, так как светодиод будет мигать так быстро, что будет казаться, что он светится постоянно. Мы будем использовать импульсы с частотой 1 Гц.

Аппаратная часть

Ниже показана структурная схема того, что нам необходимо.

Структурная схема часов на AVR микроконтроллере и RTC
Структурная схема часов на AVR микроконтроллере и RTC

Мы нужны:

  • разъем ISP (In System Programming, внутрисхемное программирование) для прошивки микроконтроллера;
  • кнопки для установки времени и даты;
  • микроконтроллер для связи с RTC через шину I2C;
  • дисплей для отображения даты и времени.

Принципиальная схема:

Часы на базе микроконтроллера AVR и RTC DS1307. Схема электрическая принципиальная
Часы на базе микроконтроллера AVR и RTC DS1307. Схема электрическая принципиальная

Перечень элементов

Ниже приведен скриншот из Eagle:

Часы на базе микроконтроллера AVR и RTC DS1307. Перечень элементов
Часы на базе микроконтроллера AVR и RTC DS1307. Перечень элементов

Программное обеспечение

В этом руководстве мы будем использовать два различных скетча: один, который записывает время и дату в RTC, и один, который считывает время и дату из RTC. Мы сделали так потому, что так вы сможете получить более полное представление о том, что происходит. Мы будем использовать одну и ту же схему для обеих программ.

Сперва мы запишем время и дату в RTC, что аналогично установке времени на часах.

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

Следующий код устанавливает значения и записывает их в RTC:

// Включение заголовочных файлов
#include <Wire.h>
#include <LiquidCrystal.h>

// Определение выводов LCD
#define RS 9
#define E 10
#define D4 8
#define D5 7
#define D6 6
#define D7 5

LiquidCrystal lcd(RS, E, D4, D5, D6, D7);

// Прерывание 0 – это вывод 4 микроконтроллера (цифровой вывод 2 Arduino)
int btnSet = 0;

// Прерывание 1 – это вывод 5 микроконтроллера (цифровой вывод 3 Arduino)
int btnSel = 1;

// Флаги прерываний
volatile int togBtnSet = false;
volatile int togBtnSel = false;

volatile int counterVal = 0;

// Переменные для отслеживания, где в "меню" мы находимся
volatile int menuCounter = 0;
// Массив значений
volatile int menuValues[6]; // 0=часы, 1=минуты, 2=день месяца, 3=месяц, 4=год, 5=день недели
// Заголовки меню
char* menuTitles[6] = 
{
	"Set hour.       ",
	"Set minute.     ",
	"Set date.       ",
	"Set month.      ",
	"Set year.       ",
	"Set day (1=mon)."
};

// Массив дней недели
char* days[] = { "NA", "Mon", "Tue", "Wed", "Thu", "Fre", "Sat", "Sun" };

void setup() {
  // Объявление прерываний, выполнение функций increaseValue/nextItem
  // по переднему фронту на btnXXX 
  attachInterrupt(btnSet, increaseValue, RISING);
  attachInterrupt(btnSel, nextItem, RISING);
  Wire.begin();
  lcd.begin(16,2);
  showWelcome();
}

// Функция прерывания
void increaseValue()
{
  // Переменные
  static unsigned long lastInterruptTime = 0;
  // Создание метки времени
  unsigned long interruptTime = millis();

  // Если timestamp - lastInterruptTime больше, чем 200
  if (interruptTime - lastInterruptTime > 200)
  {
    togBtnSet = true;
    // Увеличить counterVal на 1
    counterVal++;
  }
  // Установка lastInterruptTime равным метке времени
  // так мы знаем, что прошли дальше
  lastInterruptTime = interruptTime; 
}

// Функция прерывания для следующего пункта меню
void nextItem()
{
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 200)
  {
    togBtnSel = true;
    // Увеличить счетчик меню, так мы переходим к следующему пункту меню
    menuCounter++;   
    if (menuCounter > 6)
      menuCounter = 0;
    // Поместить counterVal в элемент массива счетчиков меню
    menuValues[menuCounter] = counterVal;
    // Сбросить counterVal, сейчас мы начинаем с 0 для следующего пункта меню
    counterVal = 0;
  }
  lastInterruptTime = interruptTime;
}

// Функция преобразования десятичных чисел в двоично-десятичный код
byte decToBCD(byte val)
{
  return ((val/10*16) + (val%10));
}

// Функция проверки, была ли нажата кнопки листания меню,
// и обновления заголовка на дисплее.
void checkCurrentMenuItem()
{
  if (togBtnSel)
  {
    togBtnSel = false;
    lcd.setCursor(0,0);
    lcd.print(menuTitles[menuCounter]);
  }
}

// Функция проверки, была ли нажата кнопка увеличения значения,
// и обновления переменной в соответствующем элементе массива,
// плюс вывод нового значения на дисплей.
void checkAndUpdateValue()
{
  // Проверить, если прерывание сработало = кнопка нажата
  if (togBtnSet)
  {
    // Обновить значение элемента массива с counterVal
    menuValues[menuCounter] = counterVal;
    // Сбросить флаг прерывания
    togBtnSet = false;
    lcd.setCursor(7,1);
    // Напечатать новое значение
    lcd.print(menuValues[menuCounter]); 
    lcd.print("  ");
  }
}

// Короткое приветственное сообщение, теперь мы знаем, что всё нормально
void showWelcome()
{
  lcd.setCursor(2,0);
  lcd.print("Hello world.");
  lcd.setCursor(3,1);
  lcd.print("I'm alive.");
  delay(500);
  lcd.clear();
}

// Запись данных в RTC
void writeRTC()
{
  Wire.beginTransmission(0x68);
  Wire.write(0); // начальный адрес
  Wire.write(0x00); // секунды
  Wire.write(decToBCD(menuValues[1]));  // преобразовать минуты в BCD-код и записать
  Wire.write(decToBCD(menuValues[0]));  // преобразовать часы в BCD-код и записать
  Wire.write(decToBCD(menuValues[5]));  // преобразовать день недели в BCD-код и записать 
  Wire.write(decToBCD(menuValues[2]));  // преобразовать день месяца в BCD-код и записать
  Wire.write(decToBCD(menuValues[3]));  // преобразовать месяц в BCD-код и записать
  Wire.write(decToBCD(menuValues[4]));  // преобразовать год в BCD-код и записать 
  Wire.write(0b00010000); // включить прямоугольные импульсы 1 Гц на выводе 7
  Wire.endTransmission(); // закрыть передачу 
}

// Показать время
// Чтобы посмотреть, что RTC работает, вам необходимо посмотреть другую программу
void showTime()
{
  lcd.setCursor(0,0);
  lcd.print("    ");
  lcd.print(menuValues[0]); lcd.print(":"); // часы
  lcd.print(menuValues[1]); lcd.print(":"); lcd.print("00       "); // минуты
  lcd.setCursor(3,1);
  lcd.print(days[menuValues[5]]); lcd.print(" "); // день недели   
  lcd.print(menuValues[2]); lcd.print("."); // дата
  lcd.print(menuValues[3]); lcd.print("."); // месяц 
  lcd.print(menuValues[4]); lcd.print("   "); // год
  // вызов функции writeRTC
  writeRTC();
}

void loop() 
{
  if (menuCounter < 6)
  {
    checkCurrentMenuItem();
    checkAndUpdateValue();	
  }
  else
  {
    showTime();
  }
}

Эта программа начинается с короткого приветственного сообщения. Это сообщение говорит нам, что подано питание, LCD работает, и что программа запустилась. Так как скетч служит лишь для того, чтобы показать, как записать данные из Arduino в RTC DS1307, то в нем отсутствует вспомогательный функционал (проверка, попадают ли значения в допустимые диапазоны; зацикливание при нажимании на кнопку увеличения значения, то есть сброс на 0, когда значение, например, минут превысит 60, и т.д.)

// Включение заголовочных файлов
#include <Wire.h>
#include <LiquidCrystal.h>

// Определение выводов LCD
#define RS 9
#define E 10
#define D4 8
#define D5 7
#define D6 6
#define D7 5

LiquidCrystal lcd(RS, E, D4, D5, D6, D7);

// Вывод, который будет принимать импульсы от RTC
volatile int clockPin = 0;

// Переменные времени и даты
byte second;
byte minute;
byte hour;
byte day;
byte date;
byte month;
byte year;

// Массив дней недели
char* days[] = { "NA", "Mon", "Tue", "Wed", "Thu", "Fre", "Sat", "Sun" };

// Функция, которая выполняется только при запуске
void setup() {
  pinMode(clockPin, INPUT); pinMode(clockPin, LOW);
  Wire.begin();
  lcd.begin(16,2);
  showWelcome();
}

// Короткое приветственное сообщение, теперь мы знаем, что всё нормально
void showWelcome()
{
  lcd.setCursor(2,0);
  lcd.print("Hello world.");
  lcd.setCursor(3,1);
  lcd.print("I'm alive.");
  delay(500);
  lcd.clear();
}

byte bcdToDec(byte val)
{
  return ((val/16*10) + (val%16));
}


// Это выполняется постоянно
void loop() 
{
  // Если уровень на выводе clockPin высокий
  if (digitalRead(clockPin))
  {
    
    // Начать передачу I2C, адрес 0x68
    Wire.beginTransmission(0x68);
    
    // Начать с адреса 0
    Wire.write(0);

    // Закрыть передачу
    Wire.endTransmission();
    
    // Начать чтение 7 двоичных данных от 0x68
    Wire.requestFrom(0x68, 7);
    second = bcdToDec(Wire.read());
    minute = bcdToDec(Wire.read());
    hour = bcdToDec(Wire.read());
    day = bcdToDec(Wire.read());
    date = bcdToDec(Wire.read());
    month = bcdToDec(Wire.read());
    year = bcdToDec(Wire.read());
 
    // Форматирование и отображение времени
    lcd.setCursor(4,0);
    if (hour < 10) lcd.print("0");
    lcd.print(hour); lcd.print(":"); 
    if (minute < 10) lcd.print("0");
    lcd.print(minute); lcd.print(":"); 
    if (second < 10) lcd.print("0");
    lcd.print(second);
    lcd.setCursor(2,1);
  
    // Форматирование и отображение даты
    lcd.print(days[day]); lcd.print(" ");
    if (date < 10) lcd.print("0");
    lcd.print(date); lcd.print(".");
    if (month < 10) lcd.print("0");
    lcd.print(month); lcd.print(".");
    lcd.print(year);  
  }      
}

Заключение

В данной статье мы рассмотрели микросхему DS1307 от Maxim Integrated и написали две демонстрационные программы: одну для установки времени и даты и вторую для чтения времени и даты. Для проверки нажатия кнопок мы использовали прерывания, в которых также избавлялись от влияния дребезга контактов.

Фото и видео

Макет часов на микроконтроллере AVR и RTC DS1307
Макет часов на микроконтроллере AVR и RTC DS1307
Макет часов на микроконтроллере AVR и RTC DS1307
Макет часов на микроконтроллере AVR и RTC DS1307

Установка времени

Считывание времени

Теги

ArduinoArduino MegaLCD дисплейRTCДатаКалендарьПрерываниеТочные часы

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.


  • 2017-03-12Moroz Ivan

    Спасибо за элементарное разжевывание. Код работоспособный. Мне пришлось установку и вывод объединить вместе. Небольшую поправку пришлось внести в код установки времени: 89 строчку перенести на 84