Ds1307 схема включения ассемблер. Учебный курс AVR

Урок 17

Часть 1

Часы реального времени DS1307

Продолжаем занятия по программированию МК AVR .

И сегодня мы познакомимся с очень хорошей микросхемой DS1307 . Данная микросхема представляет собой часы реального времени (real time clock или RTC ).

Также, благодаря тому, что общение микроконтроллера с данной микросхемой будет происходить с применением интерфейса I2C , мы ещё лишний раз на деле закрепим тему программирования данной шины.

Данная микросхема представлена компанией Dallas , вот её распиновка и основные технические характеристики

Здесь мы видим, что есть у нас ножки SDA и SCL, назначение которых мы очень прекрасно знаем из . Также есть ножки X1 и X2 для подключения кварцевого резонатора на 32768 Гц, ножки питания — VCC и GND, выход для импульсов продолжительностью 1 секунда либо другой частоты в зависимости от настроек определенных регистров, а также плюсовой контак для батарейки, которая подключается для поддержания хода часов в момент отключения основного питания. Отрицательный контакт данной батарейки мы подключаем к общему проводу питания.

Также мы видим, что данная микросхема исполняется в планарных и DIP-корпусах.

Питаться данная микросхема может как и от 3 вольт, так и от 5 вольт.

Обращение к данной микросхеме по интерфейсу I2C происходит, в принципе, также. как и к микросхеме памяти, которую мы использовали на прошлом уроке. Конечно, будут свои нюансы, но об этом позже.

Так как данная микросхема у меня установлена в том же модуле, в котором установлена и микросхема EEPROM, а шина обмена у нас одна, то "узнавать" микросхема DS1307 о том, что обращаются именно к ней, будет, конечно, по адресу, который у неё другой, нежели у микросхемы EEPROM.

Вот диаграммы приёма и передачи данных микросхемы

Адрес, по которому мы будем обращаться к данной микросхеме, выделен синим.

В принципе. особой разницы с диаграммами микросхемы EEPROM мы на видим.

Ещё отличие в обращении будет в том, что адресация памяти будет уже однобайтная, так как ячеек памяти или регистров у данной микросхемы очень мало.

Вот что из себя представляют данные регистры

Назначение данных регистров:

00h — секунды. Секунды хранятся в двоично-десятичном виде. То есть в младших 4 битах хранятся единицы секунд, а в более старших трёх — десятки. Также есть бит SH — это бит запуска микросхемы.

01h — минуты. Хранятся аналогично.

02h — более универсальный регистр. Здесь хранятся часы. В четырех младших битах — единицы чаов, в следующих более старших двух — десятки, в следующем 6 бите — флаг того, после полудня сейчас время или до полудня, в 7 бите — режим хранения — 12- часовой или 24-часовой.

03h — день недели. Хранится в младших 3 битах, остальные биты не используются.

04h — здесь хранится день месяца, также в двоично-десятичном формате. В четыреё малдших битах — единицы, в двух следующих постарше — десятки, остальные биты не используются.

05h — номер месяца в году — хранится в двоично-десятичном формате точно также, как и часы.

06h — номер года, причём не полный четырёхзначный, а только двузначный. В младших четырех битах — единицы, в старших — десятки.

Вот этими семью регистрами мы и будем пользоваться. Последний регистр предназначен для конфигурирования частоты импульсов на импульсном выходе микросхемы, это делается в младших двух битах регистра. по умолчанию он будет 1 гц частотой, нам этого достаточно, чтобы помигать двоеточием, поэтому мы не будем пользоваться данными битами. Биты SOWE и OUT также применяются для настройки и включения формирователя даннх квадратных импульсов.

Проект для работы с данной микросхемой был создан обычным образом с именем MyClock1307 , файлы, связанные с EEPROM оттуда убраны, а добавлены файлы RTC.c и RTC.h .

#ifndef MAIN_H_

#define MAIN_H_

#define F_CPU 8000000UL

#include

#include

#include

#include

#include

#include "usart.h"

#include "twi.h"

#include "RTC.h"

#endif /* MAIN_H_ */

В главном файле MyClock1307.c создадим глобальные переменные для хранения показаний времени, даты и дня недели и после этого полное содержание после удаления всего лишнего в нём будет вот таким

#include "main.h"

unsigned char sec , min , hour , day , date , month , year ;

int main ( void )

I2C_Init ();

USART_Init (8);

While (1)

От прошлого кода останется лишь инициализация I2C и USART.

Теперь нам надо как-то вообще запустить микросхему. Если микросхема новая, либо никогда не использовалась, либо кто-то специально для каких-то целей изменил значение бита CH, то она ещё не "ходит".

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

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

Поэтому, собственно, используя наши знания предыдущего занятия, напишем писать функцию установки времени.

Первым делом мы, само собой, передадим условие СТАРТ

//Устанавливаем время

I2C_StartCondition ();

Затем передаём адрес с битом записи 0

I2C_StartCondition ();

I2C_SendByte (0b11010000);

Перейдём на адрес 0, а значит к той части памяти, где расположен самый первый регистр

I2C_SendByte (0b11010000);

I2C_SendByte (0); //Переходим на 0x00

Прежде чем писать какие-то значения в регистры микросхемы, мы вспомним, что числа мы сначала должны преобразовать в двоично-десятичный формат, который будет удобен для регистров. Для этого мы зайдём в файл RTC.c и такую функцию и напишем. Она будет очень лёгкой и в объяснении не нуждается

unsigned char RTC_ConvertFromBinDec ( unsigned char c )

{

unsigned char ch = (( c /10)<<4)|( c %10);

return ch ;

}

Ну и также давайте напишем и функцию обратного типа, переводящую число из двоично-десятичного формата в десятичный. С помощью неё мы, наоборот, будем считанные показания времени преобразовывать в вид, удобный нашему восприятию (ЧПИ — человеко-понятный интерфейс)

unsigned char RTC_ConvertFromDec ( unsigned char c )

{

unsigned char ch = (( c >>4)*10+(0b00001111& c ));

return ch ;

}

Здесь также всё придельно ясно, мы сдвигаем вправо старшую тетраду байта, умножаем её на десять и прибавляем младшую тетраду (старшую отмаскировываем нулями)

Напишем прототипы данных функций в файле RTC.c

#include "main.h"

unsigned char RTC_ConvertFromDec ( unsigned char c ); //перевод двоично-десятичного числа в десятичное

unsigned char RTC_ConvertFromBinDec ( unsigned char c );

Программатор, модуль RTC DS1307 с микросхемой памяти и переходник USB-TTL можно приобрести здесь:

Программатор (продавец надёжный) USBASP USBISP 2.0

Смотреть ВИДЕОУРОК (нажмите на картинку)

Post Views: 7 354

DS1307 ещё называют RTC (Real Time Clock). Данная микросхема представляет из себя часы реального времени и календарь. Связь с микросхемой осуществляется по интерфейсу I 2 C. Её преимущество в том, что она работает (считает время) при выключенном основном питании от резервного источника питания в 3 вольта (например, от батареики типа CR3022). Но в DS1307 есть один недостаток: в ней нет проверки на правильность введённых данных. Для работы с микросхемой потребуется минимальный обвес: кварц на 32768Hz, батарея на 3 вольта и два резистора на 4,7кОм. Схема подключения DS1307:

Работа с DS1307 в BASCOM-AVR

Для начала работы с микросхемой необходимо сконфигурировать порты, к которым подключена микросхема, для этого воспользуемся командой Config :
Config Sda = (Порт микроконтроллера к которому подключена нога SDA микросхемы DS1307)
Config Scl = (Порт микроконтроллера к которому подключена нога SCL микросхемы DS1307)
Например:
Config Sda = Portb.1
Config Scl = Portb.0

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

I2cstart I2cwbyte &HD0 I2cwbyte &H00 I2cstart I2cwbyte &HD1 I2crbyte (переменная в которую запишем секунды) , Ack I2crbyte (переменная в которую запишем минуты) , Ack I2crbyte (переменная в которую запишем часы) , Ack I2crbyte (переменная в которую запишем номер дня недели) , Ack I2crbyte (переменная в которую запишем дату), Ack I2crbyte (переменная в которую запишем номер месяца) , Ack I2crbyte (переменная в которую запишем год) , Nack I2cstop

После чтения данных необходимо перевести их в десятичный формат, вот так:
(переменная секунд) = Makedec((переменная секунд))
(переменная минут) = Makedec((переменная минут))
(переменная часов) = Makedec((переменная часов))
(переменная дня недели) = Makedec((переменная дня недели))
(переменная даты) = Makedec((переменная даты))
(переменная месяца) = Makedec((переменная месяца))
(переменная года) = Makedec((переменная года))

Вот пример чтения времени и даты, а также перевод их в десятичный формат:

I2cstart I2cwbyte &HD0 I2cwbyte &H00 I2cstart I2cwbyte &HD1 I2crbyte Seco , Ack I2crbyte Mine , Ack I2crbyte Hour , Ack I2crbyte Day , Ack I2crbyte Dat , Ack I2crbyte Month , Ack I2crbyte Year , Nack I2cstop Seco = Makedec(seco) Mine = Makedec(mine) Hour = Makedec(hour) Day = Makedec(day) Dat = Makedec(dat) Month = Makedec(month) Year = Makedec(year)

Данные считывать научились, теперь попробуем записывать данные в DS1307. Вот так:
(Переменная которую запишем) = Makebcd((Переменная которую запишем))
I2cstart
I2cwbyte &HD0
I2 cwbyte (Ячейка в которую запишем данные)
I2 cwbyte (Переменная которую запишем)
I2cstop

Обратите внимание, что командаMakebcd переводит переменную в двоично-десятичный формат. Номера и обозначения ячеек:

Вот пример записи переменной секунд:
Seco = Makebcd(seco)
I2cstart
I2cwbyte &HD0
I2cwbyte 0
I2cwbyte Seco
I2 cstop
Кстати, следует учесть, что при первом запуске DS1307 (например, при подключении батареи резервного питания) микросхема будет возвращать в секундах значение 80, это означает, что часы остановлены. Для их запуска запишите в секунды значение 1. Если DS1307 при чтении любых данных возвращает значение 255 или 168 это означает что, микросхема неправильно подключена, либо отсутствует батарея резервного питания.

Практическая работа с микросхемой DS1307

Теперь попробуем поработать с микросхемой DS1307 на практике: соберём простые часы с установкой времени с помощью кнопок. Для этого возьмём саму микросхему DS1307, микроконтроллер Attiny2313, LCD индикатор на контроллере HD44780 и несколько дискретных компонентов. Соберём простую схему:

И напишем простую программу, применяя полученные знания:

$regfile = "attiny2313.dat" $crystal = 4000000 Config Lcdpin = Pin , Db4 = Portb.4 , Db5 = Portb.5 , Db6 = Portb.6 , Db7 = Portb.7 , E = Portb.3 , Rs = Portb.2 Config Lcd = 16 * 2 Config Pind.5 = Input Config Pind.4 = Input Config Sda = Portb.1 Config Scl = Portb.0 Dim Seco As Byte Dim Mine As Byte Dim Hour As Byte Initlcd Cls Cursor Off Do I2cstart I2cwbyte &HD0 I2cwbyte &H00 I2cstart I2cwbyte &HD1 I2crbyte Seco , Ack I2crbyte Mine , Ack I2crbyte Hour , Nack I2cstop Seco = Makedec(seco) Mine = Makedec(mine) Hour = Makedec(hour) Locate 1 , 1 Lcd Hour ; ":" ; Mine ; ":" ; Seco ; " " If Pind.5 = 0 Then Incr Mine Mine = Makebcd(mine) I2cstart I2cwbyte &HD0 I2cwbyte 1 I2cwbyte Mine I2cstop Waitms 100 End If If Pind.4 = 0 Then Incr Hour Hour = Makebcd(hour) I2cstart I2cwbyte &HD0 I2cwbyte 2 I2cwbyte Hour I2cstop Waitms 100 End If Loop End

Отзывы об этих часах в интернете самые противоречивые. Кто-то говорит что часы замечательные, а кто-то называет их убогой поделкой Далласа. И вот я, дабы развеять все недостоверные слухи, достал микруху из загашника начала экспериментировать.

Особенности:

  • Очень маленькое энергопотребление. Производитель обещает 10 лет работы часов от одной стандартной батарейки CR2032
  • 56 байт памяти для хранения пользовательских данных. Думаю не особо нужная опция, но может кому-то и пригодится.
  • Программируемый вывод для тактирования внешних устройств. Может выдавать 1 Гц, 4.096 кГц, 8.192 кГц и 32.768 кГц.
  • 24-х часовой и 12-ти часовой режим

Распиновка

Выводы часов расположены следующим образом:

X1, X2 — Выводы для подключения кварцевого резонатора на частоту 32.768 кГц
VBAT — Вывод для подключения 3-х вольтовой батареи резервного питания
GND — Земля
SDA — линия данных шины i2c
SCL — линия тактовых импульсов шины i2c
SQW/OUT – выходной сигнал для тактирования внешних устройств
VCC — питание 5 вольт

Подключение к контроллеру
Обвязка минимальна. Потребуется кварц 32.768 кГц, пара резисторов для работы шины i2c и батарейка на три вольта.

Правильная разводка платы
Точность хода да и вообще работоспособность часов, зависит от разводки печатной платы. Даллас в своем даташите рекомендует сократить до минимума длинну проводников от микросхемы до кварцевого резонатора и окружить эти проводники прямоугольником подключенным к земле. Кроме этого для надежности я припаял к корпусу кварца проводок идущий к земле и параллельно питанию поставил конденсатор на 0.1 мкф.

Кстати может работать и без кварца. Для этого на ногу X1 подают внешний тактовый сигнал с частотой 32.768 кГц, а X2 остаётся висеть в воздухе.

Организация памяти часов
Данная микруха наделена 64 байтами памяти. Первые восемь байт — рабочие. В них хранится время, дата, день недели. Остальные выделены под нужды пользователя. В них можно хранить например какие-нибудь настройки или еще что-нибудь. Естественно, когда резервное питание пропадает, вся информация в этой памяти разрушается. Вся работа с часами (чтение и установка времени/даты) сводится к тому, чтобы читать и записывать нужные ячейки памяти.

Все числа в памяти хранятся в двоично-десятичном формате. Это значит что в одном байте может хранится сразу две цифры. Например число 0x23 — содержит в себе цифру 2 и цифру 3. На каждую цифру выделяется по 4 бита. Зачем так сделано? Для удобства и экономии памяти. Кроме времени и даты в памяти хранятся несколько бит настроек:

  • Clock Halt — управляет часами. Когда бит установлен то часы стоят. Чтобы запустить ход часов необходимо записать в этот бит 0. После подключения батареи резервного питания, этот бит уставлен и часы не считают время! Об этом нужно помнить.
  • 24/12 — этот бит выбора режима часов. Когда этот бит равен единице то используется 12-ти часовой режим. В противном случае 24-х часовой. Если используется 12-ти часовой режим то пятый бит показывает AM или PM сейчас. Если бит равен 1 то значит PM. В 24-х часовом режиме этот бит используется для хранения десятков часов совместно с битом 4.
  • Output — управляет состоянием ноги SQW/OUT. Бит установлен — на ноге лог 1. Сброшен — на ноге 0. Для управления таким образом, бит SQWE должен быть сброшен.
  • SQWE — когда бит установлен, на ноге SQW/OUT появляются прямоугольные импульсы.
  • RS1, RS0 — этими битами задается частота импульсов. Зависимость частоты от комбинации бит находится в таблице ниже:

Софт

Для работы с часами DS1307 была написана нехитрая библиотека содержащая следующие базовые функции:

DS_start — запускает часы. Запустить часы можно так же установив время.
DS_stop — останавливает часы
DS_set_time — Установка времени. Перед вызовом процедуры нужно поместить в tmp1 — секунды в tmp2 — минуты и в tmp3-часы. Часы в 24-х часовом формате.
DS_get_time: — считывание времени из часов. секунды будут записаны в tmp1, минуты в tmp2, часы в tmp3
DS_get_date: — считывание даты из часов. День будет записан в tmp1, месяц в tmp2, год в tmp3
DS_set_date: — установка даты. Перед вызовом процедуры нужно поместить в tmp1 — день в tmp2 — месяц и в tmp3-год (последние 2 цифры)

Процедуры установки/чтения времени и даты могут воспринимать/возвращать входные данные в двоично-десятичном формате и в десятичном. Для выбора желаемого формата нужно закомментировать или раскомментировать по три строчки в каждой процедуре (в коде есть примечания по этому поводу).

Тестовая программа позволяет управлять часами через UART (скорость 9600, контроллер работает на частоте 8 мГц). При запуске сразу выдаются время, дата и приглашение ввести команду от 1 до 3. При выборе варианта 1 происходит повторное считывание времени/даты. Вариант 2 позволяет установить время, а вариант 3 дату. Если хочется попробовать поиграть с часами в то в архив с исходником включён файл для симуляции.

Точность
Тут очень многое зависит от применяемого кварца и разводки платы. Даташит сообщает что емкость кварца должна быть 12.5 пф. Говорят, что лучше всего применять кварцы от материнских плат. Для коррекции хода можно подпаять к резонатору подстроечный конденсатором и при помощи него в небольших пределах менять частоту. Лично у меня эти часы работают вторые сутки и отстали на 3 секунды. Что-то мне подсказывает, что дело в ёмкости кварца, попробую другой отпишусь.

Вывод
Неплохие часы. Для любительского применения идеальный вариант. Хотя некоторые пишут о глюках, но я пока не столкнулся.

Тема часов на микросхеме DS1307 довольно актуальна — это простое, но в то же время интересное устройство. Кроме того, оно может реально пригодиться. Но описывать отдельно микросхему смысла нет, поэтому я решил собрать себе подобное устройство, заодно рассказать о том, какие шишки набил при этом. Сам процесс разработки и сборки буду описывать, по мере прохождения некоторых этапов готовности девайса.

Update 17.10.2015
Вначале это была серия статей, целью которых было рассказать про создание устройства с нуля до состояния готовности, но внезапно у меня появилась аллергия на все что называется «часы», поэтому я слил все в одну статью. Устройство закончено на 99.9%, (осталось закрутить винты), но сделать это ой как не просто 🙂 Как только аллергия пройдет появится окончательная фотка.

Начнем с того, что пока нам ничего не известно про ds1307 кроме того, что с ее помощью делают часы. Поэтому качаем документацию, на эту микросхему и читаем список «вкусностей», которыми она обладает. Итак, из первого абзаца в целом понятно, что она обладает низким энергопотреблением, информация передается по I2C, можно узнать дату и время, 12 и 24 часовой формат, автоматическая подстройка даты. Но самое интересное это схема (TYPICAL OPERATING CIRCUIT).

Курим даташит и пытаемся разобраться что к чему. Идем слева направо, CPU — микроконтроллер (то есть наша atmega), два резистора, написано pull up — значит подтягивающие (можно взять по 10к), кварц на 32768Гц, сама микросхема и батарейка. Выход SQW/OUT может дрыгаться с частотой 1Hz, 4kHz, 8kHz, 32kHz, пока нам это не интересно. Пожалуй, этой информации пока достаточно, хочется уже чего нибудь накодить 🙂

Создаем проект в CodeVision, в разделе I2C находим ds1307 и включаем его в проект. Хорошо бы еще выводить куда нибудь информацию, например на LCD и пара кнопок не помешает.

Все что нужно это LCD настроить на порт D и три кнопки с подтяжкой на вход. Далее нужно вывести на LCD время, для этого заглянем в мануал CodeVision и возьмем оттуда пример. Оказывается все просто — есть функция устанавливающая время:
rtc_set_time(3,0,0); //установить 03:00:00

т.е. после вызова данной функции в переменных h, m, s будут находиться часы(h), минуты(m) и секунды(s). Осталось вывести их на экран. Уж это мы умеем делать)
Итоговый код будет выглядеть так:

#include #include #include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port= 0x12 ; PORTD #endasm #include char lcd_buf[ 33 ] ; void main(void ) { char hour, min, sek; PORTC= 0x07 ; DDRC= 0x00 ; // I2C Bus initialization i2c_init() ; rtc_init(0 , 0 , 0 ) ; // LCD module initialization lcd_init(16 ) ; rtc_set_time(3 , 0 , 0 ) ; while (1 ) { rtc_get_time(& hour,& min,& sek) ; lcd_clear() ; lcd_gotoxy(0 , 0 ) ; sprintf (lcd_buf, "%2d:%02d:%02d\n " , hour, min, sek) ; lcd_puts(lcd_buf) ; delay_ms(500 ) ; } ; }

#include #include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include char lcd_buf; void main(void) { char hour,min,sek; PORTC=0x07; DDRC=0x00; // I2C Bus initialization i2c_init(); // DS1307 Real Time Clock initialization rtc_init(0,0,0); // LCD module initialization lcd_init(16); rtc_set_time(3,0,0); while (1) { rtc_get_time(&hour,&min,&sek); lcd_clear(); lcd_gotoxy(0,0); sprintf(lcd_buf,"%2d:%02d:%02d\n",hour,min,sek); lcd_puts(lcd_buf); delay_ms(500); }; }

Собираем и тестируем в протеусе:

Схема и прошивка

Продолжим модернизировать нашу прошивку. Начнем со следующей задумки: у DS1307 есть выход SQW/OUT, который может генерировать несколько частот. Если настроить этот выход на 1Гц, и подать этот сигнал на вход внешнего прерывания, то получится, что раз в секунду 1307 будет дергать «за хвост» нашу atmega8. Для меги это будет сигналом к тому, что пора обновлять время. Это позволит не нагружать микроконтроллер постоянным обновлением времени, информация о текущем времени будет обновляться ровно раз в секунду.

Добавим в проект внешнее прерывание по низкому уровню (low level) на ножке Int1 и включим подтяжку. Выход DS1307 настроим на частоту 1Гц. Кстати, читать мануалы полезно, нашел интересную особенность — подтягивающие резисторы на ножках SCL, SDA должны быть 3,3k — 4,7k. Учтем это.

Получившийся код будет выглядеть так:

interrupt [ EXT_INT1] void ext_int1_isr(void ) { time_flag= 1 ; }

interrupt void ext_int1_isr(void) { time_flag=1; }

В прерывании выставляем флаг, разрешающий вывод времени, в основном цикле, если флаг выставлен, то показываем время, если не выставлен ничего не делаем.

if (time_flag== 1 ) { rtc_get_time(& hour,& min,& sek) ; lcd_gotoxy(0 , 0 ) ; sprintf (lcd_buf, "%02d:%02d:%02d\n " , hour, min, sek) ; lcd_puts(lcd_buf) ; }

if(time_flag==1) { rtc_get_time(&hour,&min,&sek); lcd_gotoxy(0,0); sprintf(lcd_buf,"%02d:%02d:%02d\n",hour,min,sek); lcd_puts(lcd_buf); }

Теперь перейдем к следующему вопросу, на сколько эффективно использовать sprintf? Чтобы не разводить пустых разговоров, приведу 2 куска кода, которые выполняют одно и тоже — выводят информацию о времени на дисплей.

Первый вариант, уже нам известный:

sprintf (lcd_buf, "%02d:%02d:%02d\n " , hour, min, sek) ; lcd_puts(lcd_buf) ;

sprintf(lcd_buf,"%02d:%02d:%02d\n",hour,min,sek); lcd_puts(lcd_buf);

Согласитесь просто в использовании и наглядно. Теперь вариант номер 2:

lcd_putchar(hour/ 10 + 0x30 ) ; lcd_putchar(hour% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(min/ 10 + 0x30 ) ; lcd_putchar(min% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(sek/ 10 + 0x30 ) ; lcd_putchar(sek% 10 + 0x30 ) ;

lcd_putchar(hour/10+0x30); lcd_putchar(hour%10+0x30); lcd_putchar(":"); lcd_putchar(min/10+0x30); lcd_putchar(min%10+0x30); lcd_putchar(":"); lcd_putchar(sek/10+0x30); lcd_putchar(sek%10+0x30);

Не очень наглядно, но разобраться можно. Как мы их будем сравнивать? Делается это очень просто — запускаем отладчик AVR STUDIO и смотрим количество тактов затраченных на их выполнение. Итак, «барабанная дробь», результаты… Первый кусок кода выполнялся 16 466 тактов, что равносильно 2 058,25 мкс, при рабочей частоте в 8МГц, для второго куска кода эта цифра составила 12 278 тактов или 1 534,75 мкс. Согласитесь, снизить время выполнения, а значит и разгрузить микроконтроллер на ~25% достаточно весомая причина, чтобы не использовать sprintf. Выкидываем sprintf из нашего проекта, в след за ним можно выкинуть stdio.h и lcd_buf.

Некрасиво, когда в основном цикле мешанина из кода, поэтому вывод информации можно засунуть в функцию. В основном цикле останется

while (1 ) { if (time_flag== 1 ) { show_time() ; //показать информацию о текущем времени } } ;

while (1) { if(time_flag==1) { show_time(); //показать информацию о текущем времени } };

Объявление самой функции будет выглядеть так:

void show_time() { rtc_get_time(& hour,& min,& sek) ; lcd_gotoxy(0 , 0 ) ; lcd_putchar(hour/ 10 + 0x30 ) ; lcd_putchar(hour% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(min/ 10 + 0x30 ) ; lcd_putchar(min% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(sek/ 10 + 0x30 ) ; lcd_putchar(sek% 10 + 0x30 ) ; time_flag= 0 ; }

void show_time() { rtc_get_time(&hour,&min,&sek); lcd_gotoxy(0,0); lcd_putchar(hour/10+0x30); lcd_putchar(hour%10+0x30); lcd_putchar(":"); lcd_putchar(min/10+0x30); lcd_putchar(min%10+0x30); lcd_putchar(":"); lcd_putchar(sek/10+0x30); lcd_putchar(sek%10+0x30); time_flag=0; }

Теперь в нашу прошивку, нужно добавить вывод даты. Установка даты, производится следующей функцией:

rtc_set_date(6,13,10,13); //6- день недели, 13 - день, 10 - месяц, 13 - год

rtc_get_date(&week_day,&day,&month,&year); //день недели, день, месяц, год

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

#include // I2C Bus functions #asm .equ __i2c_port= 0x18 ; PORTB .equ __sda_bit= 0 .equ __scl_bit= 1 #endasm #include // DS1307 Real Time Clock functions #include // Alphanumeric LCD functions #include char hour= 0 , min= 0 , sek= 0 , day= 0 , month= 0 , year= 0 , week_day= 0 ; bit time_flag= 0 ; char menu= 0 ; // External Interrupt 1 service routine interrupt [ EXT_INT1] void ext_int1_isr(void ) { time_flag= 1 ; } void show_time() { rtc_get_time(& hour,& min,& sek) ; rtc_get_date(& week_day,& day,& month,& year) ; lcd_gotoxy(0 , 0 ) ; lcd_putchar(hour/ 10 + 0x30 ) ; lcd_putchar(hour% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(min/ 10 + 0x30 ) ; lcd_putchar(min% 10 + 0x30 ) ; lcd_putchar(":" ) ; lcd_putchar(sek/ 10 + 0x30 ) ; lcd_putchar(sek% 10 + 0x30 ) ; lcd_gotoxy(0 , 1 ) ; lcd_putchar(day/ 10 + 0x30 ) ; lcd_putchar(day% 10 + 0x30 ) ; lcd_putchar("/" ) ; lcd_putchar(month/ 10 + 0x30 ) ; lcd_putchar(month% 10 + 0x30 ) ; lcd_putchar("/" ) ; lcd_putchar(year/ 10 + 0x30 ) ; lcd_putchar(year% 10 + 0x30 ) ; time_flag= 0 ; } void main(void ) { PORTC= 0x0F ; DDRC= 0x00 ; PORTD= 0x08 ; DDRD= 0x00 ; // I2C Bus initialization i2c_init() ; // DS1307 Real Time Clock initialization // Square wave output on pin SQW/OUT: On // Square wave frequency: 1Hz rtc_init(0 , 1 , 0 ) ; // External Interrupt(s) initialization // INT0: Off // INT1: On // INT1 Mode: Low level GICR|= 0x80 ; MCUCR= 0x00 ; GIFR= 0x80 ; // LCD module initialization lcd_init(16 ) ; rtc_set_time(12 , 0 , 0 ) ; rtc_set_date(6 , 13 , 10 , 13 ) ; #asm("sei") while (1 ) { if (time_flag== 1 ) { show_time() ; } } ; }

#include // I2C Bus functions #asm .equ __i2c_port=0x18 ;PORTB .equ __sda_bit=0 .equ __scl_bit=1 #endasm #include // DS1307 Real Time Clock functions #include // Alphanumeric LCD functions #include char hour=0,min=0,sek=0,day=0,month=0,year=0,week_day=0; bit time_flag=0; char menu=0; // External Interrupt 1 service routine interrupt void ext_int1_isr(void) { time_flag=1; } void show_time() { rtc_get_time(&hour,&min,&sek); rtc_get_date(&week_day,&day,&month,&year); lcd_gotoxy(0,0); lcd_putchar(hour/10+0x30); lcd_putchar(hour%10+0x30); lcd_putchar(":"); lcd_putchar(min/10+0x30); lcd_putchar(min%10+0x30); lcd_putchar(":"); lcd_putchar(sek/10+0x30); lcd_putchar(sek%10+0x30); lcd_gotoxy(0,1); lcd_putchar(day/10+0x30); lcd_putchar(day%10+0x30); lcd_putchar("/"); lcd_putchar(month/10+0x30); lcd_putchar(month%10+0x30); lcd_putchar("/"); lcd_putchar(year/10+0x30); lcd_putchar(year%10+0x30); time_flag=0; } void main(void) { PORTC=0x0F; DDRC=0x00; PORTD=0x08; DDRD=0x00; // I2C Bus initialization i2c_init(); // DS1307 Real Time Clock initialization // Square wave output on pin SQW/OUT: On // Square wave frequency: 1Hz rtc_init(0,1,0); // External Interrupt(s) initialization // INT0: Off // INT1: On // INT1 Mode: Low level GICR|=0x80; MCUCR=0x00; GIFR=0x80; // LCD module initialization lcd_init(16); rtc_set_time(12,0,0); rtc_set_date(6,13,10,13); #asm("sei") while (1) { if(time_flag==1) { show_time(); } }; }

Результат:

Схема и прошивка:

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

При этом обойтись четырьмя кнопками — вверх, вниз, влево, вправо. Переход между экранами должен осуществляться кнопками вверх и вниз. Настройка производится на соответствующем экране. Применяемый микроконтроллер atmega8. Вот такое примитивное т.з.

То что размер кода будет достаточно большой было понятно изначально. При этом нужно было его разбить на логически связанные части. С частями все понятно — обработка одного экрана одна часть кода. Поэтому начал с того, что основной цикл разбил на четыре части, переключения между которыми производится оператором switch. Внутрь засунул функции — пустышки. Кнопки 0(вверх) и 3(вниз) порта C позволяют изменить переменную menu. Таким образом мы скачем между менюшками. Но пока такая прошивка еще работать не могла, ибо функции еще не определены.

while (1 ) { switch (menu) { case 0 : show_time() ; break ; case 1 : set_time() ; break ; case 2 : set_date() ; break ; case 3 : set_alarm() ; break ; } } ;

while (1) { switch(menu) { case 0: show_time(); break; case 1: set_time(); break; case 2: set_date(); break; case 3: set_alarm(); break; } };

Следующий шаг определение этих функций, изначально я нарисовал статичные названия, вроде lcd_puts(«Set time»); функции получились, такими.

void set_alarm() { ////////просмотр настроек будильника lcd_gotoxy(0,0); lcd_puts("Set alarm"); } void set_time() { ////////просмотр настроек времени lcd_gotoxy(0,0); lcd_puts("Set time"); }

Теперь это уже была рабочая прошивка, в которой можно было переключаться между менюшками и смотреть статичные надписи. Настало время оживить эти надписи. С главным экраном проблем не было, вывод времени/даты аналогичен предыдущему уроку.

Встал следующий вопрос: как организовать сам процесс настройки? Поразмыслив, мне показалась интересной следующая идея: переходим кнопками вверх/вниз в интересующее нас меню, нажимаем вправо, появляется курсор, говорящий нам о том, что идет процесс настройки. Кнопками вверх/вниз мы изменяем величину, влево/вправо скачем курсором между настраиваемыми параметрами. Когда курсор находится под последним параметром, повторное нажатие кнопки вправо позволяет выйти из настройки. Курсор при этом скрывается, что говорит о том что мы вышли из настройки и можем снова переключаться между менюшками.

Но есть небольшие проблемы, например, кнопка вверх должна изменять параметры и при этом, переключать следующий экран. Т.е. логику кнопок внутри одного экрана, пришлось разделить. Из за этого код здорово разросся. Например, на экране будильника, вводим подпрограмму настройки(sub_alarm), соответственно кнопки внутри подпрограммы обрабатываются одним образом, а вне другим.

void set_alarm() //Функция обработки будильника { //режим отображения меню настроек будильника if(sub_alarm==0) { if(PINC.0==0) //кнопка вверх - смена экрана меню { menu=0; ..... } } //подменю настройки будильника if(sub_alarm==1) { if(PINC.0==0) //кнопка вверх - увеличить величину { a_hour++; .... } }

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

if(PINC.0==0) //кнопка вверх { if(subProgram==1) //subProgram=1 - изменяем часы { a_hour++; ... } if(subProgram==2) //subProgram=2 - изменяем минуты { a_min++; ... } if(subProgram==3) //subProgram=3 изменяем флаг будильника { ... } }

Единственное о чем стоит упомянуть это спецсимвол, который выводится на главном экране, когда будильник включен.

Пример взят из примеров в папке CodeVision\examples\lcd char.

typedef unsigned char byte; //переопределяем тип flash byte char_table[ 8 ] = { //рисуем свой символ 0b10000000 , 0b10000100 , 0b10001110 , 0b10001110 , 0b10001110 , 0b10011111 , 0b10100100 , 0b11000000 } ; // function used to define user characters void define_char(byte flash * pc, byte char_code) { byte i, address; address= (char_code<< 3 ) | 0x40 ; for (i= 0 ; i< 8 ; i++ ) lcd_write_byte(address++,* pc++ ) ; } void main(void ) { byte i, address; lcd_init(16 ) ; define_char(char_table, 0 ) ; //Грузим символ в лсд while (1 ) { lcd_putchar(0 ) ; //выводим символ на дисплей }

typedef unsigned char byte; //переопределяем тип flash byte char_table={ //рисуем свой символ 0b10000000, 0b10000100, 0b10001110, 0b10001110, 0b10001110, 0b10011111, 0b10100100, 0b11000000}; // function used to define user characters void define_char(byte flash *pc,byte char_code) { byte i,address; address=(char_code<<3)|0x40; for (i=0; i<8; i++) lcd_write_byte(address++,*pc++); } void main(void) { byte i,address; lcd_init(16); define_char(char_table,0); //Грузим символ в лсд while(1) { lcd_putchar(0); //выводим символ на дисплей }

Рисовать можно символ 5х7, единичка — пиксел закрашен, ноль — не закрашен. Получился символ колокольчика.

Прошивка

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

Начнем с печатной платы, для этого требуется программа, которая позволяет рисовать печатки. Существует множество подобных программ: P-cad, Altium, Sprint layout… Мне нравится Альтиум, только потому, что для него куча готовых библиотек с элементами, ибо тратить время на набивку собственной библиотеки элементов, на мой взгляд не дело. Общий смысл всех подобных программ одинаков — сначала рисуется электрическая схема.

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

Остается только удобно расположить элементы и соединить их проводниками.

Во многих проектах Ардуино требуется отслеживать и фиксировать время наступления тех или иных событий. Модуль часов реального времени, оснащенный дополнительной батарей, позволяет хранить текущую дату, не завися от наличия питания на самом устройстве. В этой статье мы поговорим о наиболее часто встречающихся модулях RTC DS1307, DS1302, DS3231, которые можно использовать с платой Arduino.

Модуль часов представляет собой небольшую плату, содержащей, как правило, одну из микросхем DS1307, DS1302, DS3231.Кроме этого, на плате практически можно найти механизм установки батарейки питания. Такие платы часто применяется для учета времени, даты, дня недели и других хронометрических параметров. Модули работают от автономного питания – батареек, аккумуляторов, и продолжают проводить отсчет, даже если на Ардуино отключилось питание. Наиболее распространенными моделями часов являются DS1302, DS1307, DS3231. Они основаны на подключаемом к Arduino модуле RTC (часы реального времени).

Часы ведут отсчет в единицах, которые удобны обычному человеку – минуты, часы, дни недели и другие, в отличие от обычных счетчиков и тактовых генераторов, которые считывают «тики». В Ардуино имеется специальная функция millis(), которая также может считывать различные временные интервалы. Но основным недостатком этой функции является сбрасывание в ноль при включении таймера. С ее помощью можно считать только время, установить дату или день недели невозможно. Для решения этой проблемы и используются модули часов реального времени.

Электронная схема включает в себя микросхему, источник питания, кварцевый резонатор и резисторы. Кварцевый резонатор работает на частоте 32768 Гц, которая является удобной для обычного двоичного счетчика. В схеме DS3231 имеется встроенный кварц и термостабилизация, которые позволяют получить значения высокой точности.

Сравнение популярных модулей RTC DS1302, DS1307, DS3231

В этой таблице мы привели список наиболее популярных модулей и их основные характеристики.

Название Частота Точность Поддерживаемые протоколы
DS1307 1 Гц, 4.096 кГц, 8.192 кГц, 32.768 кГц Зависит от кварца – обычно значение достигает 2,5 секунды в сутки, добиться точности выше 1 секунды в сутки невозможно. Также точность зависит от температуры. I2C
DS1302 32.768 кГц 5 секунд в сутки I2C, SPI
DS3231 Два выхода – первый на 32.768 кГц, второй – программируемый от 1 Гц до 8.192 кГц ±2 ppm при температурах от 0С до 40С.

±3,5 ppm при температурах от -40С до 85С.

Точность измерения температуры – ±3С

I2C

Модуль DS1307

DS1307 – это модуль, который используется для отсчета времени. Он собран на основе микросхемы DS1307ZN, питание поступает от литиевой батарейки для реализации автономной работы в течение длительного промежутка времени. Батарея на плате крепится на обратной стороне. На модуле имеется микросхема AT24C32 – это энергонезависимая память EEPROM на 32 Кбайт. Обе микросхемы связаны между собой шиной I2C. DS1307 обладает низким энергопотреблением и содержит часы и календарь по 2100 год.

Модуль обладает следующими параметрами:

  • Питание – 5В;
  • Диапазон рабочих температур от -40С до 85С;
  • 56 байт памяти;
  • Литиевая батарейка LIR2032;
  • Реализует 12-ти и 24-х часовые режимы;
  • Поддержка интерфейса I2C.

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

Взаимодействие с другими устройствами и обмен с ними информацией производится с помощью интерфейса I2C с контактов SCL и SDA. В схеме установлены резисторы, которые позволяют обеспечивать необходимый уровень сигнала. Также на плате имеется специальное место для крепления датчика температуры DS18B20.Контакты распределены в 2 группы, шаг 2,54 мм. В первой группе контактов находятся следующие выводы:

  • DS – вывод для датчика DS18B20;
  • SCL – линия тактирования;
  • SDA – линия данных;
  • VCC – 5В;

Во второй группе контактов находятся:

  • SQ – 1 МГц;
  • BAT – вход для литиевой батареи.

Для подключения к плате Ардуино нужны сама плата (в данном случае рассматривается Arduino Uno), модуль часов реального времени RTC DS1307, провода и USB кабель.

Чтобы подключить контроллер к Ардуино, используются 4 пина – VCC, земля, SCL, SDA.. VCC с часов подключается к 5В на Ардуино, земля с часов – к земле с Ардуино, SDA – А4, SCL – А5.

Для начала работы с модулем часов нужно установить библиотеки DS1307RTC, TimeLib и Wire. Можно использовать для работы и RTCLib.

Проверка RTC модуля

При запуске первого кода программа будет считывать данные с модуля раз в секунду. Сначала можно посмотреть, как поведет себя программа, если достать из модуля батарейку и заменить на другую, пока плата Ардуино не присоединена к компьютеру. Нужно подождать несколько секунд и вытащить батарею, в итоге часы перезагрузятся. Затем нужно выбрать пример в меню Examples→RTClib→ds1307. Важно правильно поставить скорость передачи на 57600 bps.

При открытии окна серийного монитора должны появиться следующие строки:

Будет показывать время 0:0:0. Это связано с тем, что в часах пропадает питание, и отсчет времени прекратится. По этой причине нельзя вытаскивать батарею во время работы модуля.

Чтобы провести настройку времени на модуле, нужно в скетче найти строку

RTC.adjust(DateTime(__DATE__, __TIME__));

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

Настройка произведена корректно и дополнительно перенастраивать часы реального времени не придется.

Считывание времени. Как только модуль настроен, можно отправлять запросы на получение времени. Для этого используется функция now(), возвращающая объект DateTime, который содержит информацию о времени и дате. Существует ряд библиотек, которые используются для считывания времени. Например, RTC.year() и RTC.hour() – они отдельно получают информацию о годе и часе. При работе с ними может возникнуть проблема: например, запрос на вывод времени будет сделан в 1:19:59. Прежде чем показать время 1:20:00, часы выведут время 1:19:00, то есть, по сути, будет потеряна одна минута. Поэтому эти библиотеки целесообразно использовать в случаях, когда считывание происходит нечасто – раз в несколько дней. Существуют и другие функции для вызова времени, но если нужно уменьшить или избежать погрешностей, лучше использовать now() и из нее уже вытаскивать необходимые показания.

Пример проекта с i2C модулем часов и дисплеем

Проект представляет собой обычные часы, на индикатор будет выведено точное время, а двоеточие между цифрами будет мигать с интервалом раз в одну секунду. Для реализации проекта потребуются плата Arduino Uno, цифровой индикатор, часы реального времени (в данном случае вышеописанный модуль ds1307), шилд для подключения (в данном случае используется Troyka Shield), батарейка для часов и провода.

В проекте используется простой четырехразрядный индикатор на микросхеме TM1637. Устройство обладает двухпроводным интерфейсом и обеспечивает 8 уровней яркости монитора. Используется только для показа времени в формате часы:минуты. Индикатор прост в использовании и легко подключается. Его выгодно применять для проектов, когда не требуется поминутная или почасовая проверка данных. Для получения более полной информации о времени и дате используются жидкокристаллические мониторы.

Модуль часов подключается к контактам SCL/SDA, которые относятся к шине I2C. Также нужно подключить землю и питание. К Ардуино подключается так же, как описан выше: SDA – A4, SCL – A5, земля с модуля к земле с Ардуино, VCC -5V.

Индикатор подключается просто – выводы с него CLK и DIO подключаются к любым цифровым пинам на плате.

Скетч. Для написания кода используется функция setup, которая позволяет инициализировать часы и индикатор, записать время компиляции. Вывод времени на экран будет выполнен с помощью loop.

#include #include "TM1637.h" #include "DS1307.h" //нужно включить все необходимые библиотеки для работы с часами и дисплеем. char compileTime = __TIME__; //время компиляции. #define DISPLAY_CLK_PIN 10 #define DISPLAY_DIO_PIN 11 //номера с выходов Ардуино, к которым присоединяется экран; void setup() { display.set(); display.init(); //подключение и настройка экрана. clock.begin(); //включение часов. byte hour = getInt(compileTime, 0); byte minute = getInt(compileTime, 2); byte second = getInt(compileTime, 4); //получение времени. clock.fillByHMS(hour, minute, second); //подготовка для записывания в модуль времени. clock.setTime(); //происходит запись полученной информации во внутреннюю память, начало считывания времени. } void loop() { int8_t timeDisp; //отображение на каждом из четырех разрядов. clock.getTime();//запрос на получение времени. timeDisp = clock.hour / 10; timeDisp = clock.hour % 10; timeDisp = clock.minute / 10; timeDisp = 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 - "0") * 10 + int(string) - "0"; //действия для корректной записи времени в двухзначное целое число. В ином случае на экране будет отображена просто пара символов. }

После этого скетч нужно загрузить и на мониторе будет показано время.

Программу можно немного модернизировать. При отключении питания выше написанный скетч приведет к тому, что после включения на дисплее будет указано время, которое было установлено при компиляции. В функции setup каждый раз будет рассчитываться время, которое прошло с 00:00:00 до начала компиляции. Этот хэш будет сравниваться с тем, что хранятся в EEPROM, которые сохраняются при отключении питания.

Для записи и чтения времени в энергонезависимую память или из нее нужно добавить функции EEPROMWriteInt и EEPROMReadInt. Они нужны для проверки совпадения/несовпадения хэша с хэшем, записанным в EEPROM.

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

В результате в коде нужно будет указать новую библиотеку (для жидкокристаллических экранов это LiquidCrystal), и добавить в функцию loop() строки для получения даты.

Алгоритм работы следующий:

  • Подключение всех компонентов;
  • Проверка – на экране монитора должны меняться ежесекундно время и дата. Если на экране указано неправильное время, нужно добавить в скетч функцию RTC.write (tmElements_t tm). Проблемы с неправильно указанным временем связаны с тем, что модуль часов сбрасывает дату и время на 00:00:00 01/01/2000 при выключении.
  • Функция write позволяет получить дату и время с компьютера, после чего на экране будут указаны верные параметры.

Заключение

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