Учебный курс AVR. Использования TWI модуля. Работа с DS1307. Дешево и сердито. Ч3

31/10/2013 - 14:11 Павел Бобков

В этом материале будет рассмотрен топорный вариант работы с TWI модулем на примере часов реального времени - микросхемы DS1307. Топорный, потому что обмен по I2C будет организован без прерываний и анализа статусных кодов, чтобы сильно не грузить начинающих.

DS1307

Микросхема DS1307 предназначена для счета времени - секунд, минут, часов, дней, месяцев и лет. То есть по сути, это часы с календарем.

Она тактируется от собственного кварцевого генератора с частотой 32768 Гц и может работать от двух источников питания - основного и резервного. Важная фишка этой микросхемы заключается в низком потреблении - меньше 500 nA в рабочем режиме. При таком потреблении DS1307 может проработать от трехвольтовой батарейки (типа CR2032 емкостью ~200 мА*ч) несколько лет. Также DS1307 может генерировать на одном из выводов меандр и в ее составе есть 56 байт оперативной памяти, которую можно использовать для хранения данных.

Минимальная схема подключения DS1307 включает в себя часовой кварц и один источник питания. Обмен данными с микросхемой осуществляется по I2C, причем DS1307 может работать на шине только как подчиненное устройство (слейвом).

С чего начать?

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

Скорость обмена DS1307



Адрес, по которому DS1307 отзывается на I2C шине



Карта памяти DS1307

Карта памяти показывает, по каким адресам расположены регистры микросхемы и сколько их вообще. 


По нулевому адресу располагается регистр секунд. Младшие 4 разряда регистра отведены для единиц, там может быть число от 0 до 9. Старшие - для десятков секунд. 


Это так называемый двоично-десятичный формат представления чисел (BCD).При таком формате один байт может представить числа только от 0 до 99. Остальные регистры часов и календаря содержат данные в таком же формате. 


7-й разряд регистра секунд - управляющий. 0 в этом разряде разрешает работу часов, 1 - запрещает. При подачи питания этот разряд устанавливается в 1.

По первому адресу расположен регистр минут. С ним все понятно.

По второму адресу располагается регистр часов. 6-й бит этого регистра задает формат представления времени. Если он установлен в 1 используется 12 часовой формат, если сброшен -24 часовой.

Далее идут регистры календаря.

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

Ну и адреса с 8 по 63-й отведены для оперативной памяти. Их можно использовать для хранения данных.

Как записать данные в DS1307

DS1307 может работать в двух режимах: как подчиненный приемник и как подчиненный передатчик. В первом режиме ведущее устройство передает DS1307 данные, а DS1307 принимает их. Во втором - ведущее устройство принимает от DS1307 данные, а та в свою очередь передает их. (Но обмен в обоих случаях начинает ведущий!)


Для каждого режима в даташите есть описание и диаграмма обмена. Запись данных выполняется согласно следующей последовательности.


1. Ведущий формирует на шине состояние СТАРТ.
2. Ведущий выдает на шину адрес DS1307 с нулевым битом квитирования (адресный пакет), что сигнализирует ведомому о последующей записи данных. 
3. Если на шине присутствует микросхема DS1307, она отвечает ведущему - ACK. 
4. После получения ответа ведущий передает DS1307 адрес регистра, с которого начнется запись данных. Это значение записывается во внутренний счетчик адреса DS1307.
5. DS1307 снова отвечает ведущему. 
6. Получив ответ, ведущий передает ведомому байт данных, который предназначен для записи в регистр DS1307.
7. DS1307 отвечает ведущему.
8. Шаги 6, 7 повторяются несколько раз.
9. Ведущий формирует на шине состояние СТОП. 


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

Как прочитать данные из DS1307


1. Ведущий формирует на шине состояние СТАРТ.
2. Ведущий выдает на шину адрес DS1307 с установленным битом квитирования, что сигнализирует ведомому о последующем чтении данных. 
3. DS1307 отвечает ведущему. 
4. DS1307 передает ведущему байт данных, на который указывает внутренний счетчик адреса. 
5. Ведущий отвечает, что принял данные. 
6 . Шаги 4, 5 повторяются несколько раз. 
7. DS1307 передает ведущему байт данных.
7. Ведущий неформирует ответ DS1307.
8. Ведущий выдает на шину состояние СТОП.


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

Код

Теперь можно перейти к коду. Нам понадобится минимум три функции:

- функция инициализации,
- функция записи данных,
- функция чтения данных.

Инициализация


#define F_I2C 50000UL
#define TWBR_VALUE (((F_CPU)/(F_I2C)-16)/2)

#if ((TWBR_VALUE > 255) || (TWBR_VALUE == 0))
#error "TWBR value is not correct"
#endif

void RTC_Init(void)
{
   TWBR = TWBR_VALUE;
   TWSR = 0;
}


Скорость задается с помощью макроса TWBR_VALUE. Здесь используется формула, разобранная в предыдущей части. При компиляции расчетное значение проверяется с помощью директив препроцессора, чтобы убедиться, что оно не выходит за диапазон.

Передача данных

Передача данных начинается с состояния СТАРТ. Чтобы сформировать его, нужно включить TWI модуль, установить бит TWSTA и сбросить флаг прерывания TWINT. Это выполняется в одну строчку, записью в управляющий регистр TWCR.

Когда микроконтроллер выдаст на шину состояние СТАРТ, установится бит TWINT и в статусном регистре TWSR изменится статусный код. Микроконтроллер должен дождаться установки бита TWINT, прежде чем перейдет к следующей операции. Ожидание в нашем случае выполняется циклическим опросом (тупо поллингом .. не путать с троллингом).


 /*формируем состояние СТАРТ*/
 TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
 while(!(TWCR & (1<<TWINT)));


Каждая установка бита TWINT сопровождается определенным статусным кодом в регистре TWSR. По хорошему, мы должны проверять эти коды, чтобы контролировать успешность операций. Но поскольку код у нас торный (учебный), мы не будем этого делать.

Далее на шину нужно выдать адресный пакет. В регистр данных TWDR загружаем адрес, а бит квитирования устанавливаем нулевым. После загрузки адреса сбрасываем бит TWINT, инициируя дальнейшую работу TWI модуля, и дожидаемся, когда она завершится, опрашивая TWINT.


/*выдаем на шину пакет SLA-W*/
TWDR = (DS1307_ADR<<1)|0;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));


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


/*передаем адрес регистра ds1307*/
TWDR = adr;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));


Далее можно гнать остальные данные таким же методом, а когда надоест выдать на шину состояние СТОП.


/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);


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


void RTC_SetValue(uint8_t adr, uint8_t data)
{
   /*формируем состояние СТАРТ*/
   TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));

   /*выдаемна шину пакет SLA-W*/
   TWDR = (DS1307_ADR<<1)|0;
   TWCR = (1<<TWINT)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));
   
   /*передаем адрес регистра ds1307*/
   TWDR = adr;
   TWCR = (1<<TWINT)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));

   /*передаем данные или пропускаем*/
   if (data != RTC_RESET_POINTER){ 
      /*это чтобы привести данные к BCD формату*/
      data = ((data/10)<<4) + data%10;

      TWDR = data;
      TWCR = (1<<TWINT)|(1<<TWEN);
      while(!(TWCR & (1<<TWINT)));
   }

   /*формируем состояние СТОП*/
   TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}


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

Чтение данных из DS1307

Формируем состояние СТАРТ.


/*формируем состояние СТАРТ*/
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));


Посылаем на шину адресный пакет - адрес и установленный бит квитирования.


/*выдаемна шину пакет SLA-R*/
TWDR = (DS1307_ADR<<1)|1;
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));


Получаем данные. Сбрасываем бит TWINT, инициирую работу TWI модуля. Бит TWEA должен быть установлен в 1, чтобы ведущее устройство сигнализировало ведомому о приеме очередного байта.
Когда бит TWINT снова установится в 1, в регистре данных будет байт принятый от ведомого.


/*считываем данные с подтверждением*/
TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
data = TWDR;


Далее можно продолжать чтение или принять данные без подтверждения и выдать состояние СТОП.


/*считываем данные без подтверждения*/
TWCR = (1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
data = TWDR;

/*формируем состояние СТОП*/
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);


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


uint8_t RTC_GetValue(void)
{
   uint8_t data;

   /*формируем состояние СТАРТ*/
   TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));

   /*выдаем на шину пакет SLA-R*/
   TWDR = (DS1307_ADR<<1)|1;
   TWCR = (1<<TWINT)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));

   /*считываем данные*/
   TWCR = (1<<TWINT)|(1<<TWEN);
   while(!(TWCR & (1<<TWINT)));
   data = TWDR;

   /*формируем состояние СТОП*/
   TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);

return data;
}

Тестовый проект для DS1307

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

DS1307-as6.rar
DS1307-winavr.rar
DS1307-cv.rar
DS1307-iar.rar
DS1307-proteus.rar

Остальные проекты выложу позже.

В следующих частях будет разбор работы со статусными кодами, а далее работа с TWI модулем через прерывания.

Comments   

# foxit 2013-11-01 17:41
Спасибо за статью.
# Noone 2013-11-01 19:13
Спасибо Павел. Большое!!!
# Alexey 2013-11-01 19:17
Спасибо! Интересно было бы почитать про сопряжение устройств по Microwire интерфейсу.
# Pashgan 2013-11-03 16:50
Не приходилось пользоваться этим интерфейсом. Это что-то похожее на SPI?
# FreshMan 2013-11-01 19:39
если можно, сделай пожалуйста статью по работе с DS1307 с прерываниями :-)
# Pashgan 2013-11-03 16:47
По прерываниям позже материал будет.
# nope 2013-11-02 19:08
А сдесь случайно не TWSR должен быть?

while(!(TWCR & (1
# Pashgan 2013-11-03 16:48
Нет. В TWSR статусные коды хранятся, а бит TWINT относится к управляющему регистру TWCR.
# Alex 2013-11-03 17:14
Code:while(!(TWCR & (1<<TWINT)));
т.е. я правильно понял, что данная конструкция выполняет операцию & над регистром TWCR и ждет что в результате он будет не 0?
# JoJo 2013-11-03 18:05
Тупить пока не установлен бит TWINT. Вот что делает эта строка.
# Alex 2013-11-03 20:03
походу я туплю)
# sar-bc 2013-11-06 22:11
Здравствуйте! Скажите пожалуйста, будут ли разбираться примеры сопряжения двух атмег по TWI? Например:
Есть мега 16 к порту PORTD.7 подключена кнопка. И мега 8 к порту PORTD.7 подключен светодиод. Две меги подключены через TWI.
При нажатии кнопки мега16 «говорит» меге8 мол кнопка нажата включи сведодиод.
Можно ли разобрать такой пример?
# Pashgan 2013-11-07 06:38
Если будут желающие и дойдут руки, то напишу.
# kleef 2013-11-25 04:59
Code: TWDR = (DS1307_ADR<<1)|0; //зачем тут "<<1" ?


Code: /*это чтобы привести данные к BCD формату*/
data = ((data/10)<<4) + data%10; //можно поподробнее про это преобразование?
# Pashgan 2013-11-25 11:41
Code:TWDR = (DS1307_ADR<<1)|0; //зачем тут "<<1" ?
Так формируется адресный пакет. Он состоит из адреса и R/W бита. Старшие 7 бит - это адрес, а младший бит - это R/W бит. Для этого и нужен сдвиг. Читай предыдущий материл chipenable.ru/index.php/programming-avr/item/194-uchebnyy-kurs-avr-ispolzovaniya-twi-modulya-osnovy-i2c-ch1.html

BCD формат - старшие 4 бита десятки, младшие 4 бита единицы.
Code:(data/10)<<4
деление на десять дает количество десятков, сдвиг на 4 помещает это число в старшие 4 разряда байта.
Code:data%10
Это операция деления с остатком. Делим на 10 с остатком, получаем количество единиц.
# kleef 2013-11-26 05:43
"Поскольку чтение данных выполняется по адресу внутреннего счетчика, его значение нужно предварительно инициализироват ь. Это делается с помощью операции записи, которая обрывается после передачи адреса регистра." Т.е. после этого внутренний счетчик обнуляется и мы можем прочитать значение, которое записано через RTC_SetValue(0, temp) ?
Работая с микросхемой m24256 на выходе всегда получаю значение 256 независимо от записанных данных
# Альнер 2014-05-30 16:49
while(!(TWCR & (1
# Альнер 2014-05-30 16:55
кто-нибудь может в AlgorithmBuilde r TWI модуль проверить???
# ALEXIS 2014-06-04 10:20
Павел, скажите а каким образом можно записать новую дату и время с помощью МК. Если есть возможность кусок кода. Благодарю.
# Pashgan 2014-06-14 09:55
В конце статьи проекты, там этот код есть. Только значение времени нужно вводить в двоично десятичном формате, если мне память не изменяет.
Code:
RTC_SetValue(RTC_HOUR_ADR, 0);
RTC_SetValue(RTC_MIN_ADR, 0);
RTC_SetValue(RTC_SEC_ADR, 0);
# Yurii Lemberg 2014-06-23 09:26
Спасибо большое! Очень интересно читать, а главное что все понятно.
# slawiks 2014-12-03 13:53
Добрый день. Собрал на макете ATMEGA8 и датчик давления. С внешними подтягивающими резисторами по 4.7 кОм не заработало. После подключения внутренних вместе с внешними все нормально. Чем это можно объяснить?Спаси бо
# дмитрий1 2015-01-06 16:44
А как работать с полученными данными времени, как обратно в десятичное число перевести??
# дима 2015-01-13 16:47
а на реальном железе кроме секунду не чего больше не считывает
# Михаил 2015-03-10 06:26
Глупый вопрос, а как записать бит разрешения счета у часов и бит разрешения генерации SQWE ???
# Иван Иванович 2016-04-28 08:34
Всем привет любителям Ардуино. У меня вопрос, не могу ничего подходящего найти. Как в модуль DS1307 подключить датчик Ds18B20.ПО отдельности все работает а вот подключил датчик температуры и все повисло, даже в последовательны й порт часы не выводит? А статья просто супер. МОЛОДЕЦ
# Nicholas D. Wolfwood 2016-07-19 18:31
Извините за тупой вопрос но не могу понять, как у вас определяются порты микроконтроллер а под sda scl и сам контроллер, знаю вопрос тупой но ответа и упоминания в коде ничего не нашел....
# hawkone 2016-09-06 08:05
Подскажыте, что в строке
TWDR = (DS1307_ADR

У вас недостаточно прав для комментирования.