Как использовать 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
OUT 0 0 SQWE 0 0 RS1 RS0
Бит 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
RS1 RS0 Частота импульсов и уровень на выходе SQW/OUT SQWE OUT
0 0 1 Гц 1 x
0 1 4,096 кГц 1 x
1 0 8,192 кГц 1 x
1 1 32,768 кГц 1 x
x x 0 0 0
x x 1 0 1

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

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

Если вы подключили светодиод и резистор к выводу 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ДатаКалендарьПрерываниеТочные часы