Подключение ds1307 к микроконтроллеру avr. Подключение схемы DS1307 к микроконтроллеру по интерфейсу TWI

Тема часов на микросхеме 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… Мне нравится Альтиум, только потому, что для него куча готовых библиотек с элементами, ибо тратить время на набивку собственной библиотеки элементов, на мой взгляд не дело. Общий смысл всех подобных программ одинаков — сначала рисуется электрическая схема.

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

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


В этом материале будет рассмотрен топорный вариант работы с 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 данные, а та в свою очередь передает их. (Но обмен в обоих случаях начинает ведущий!)

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



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

Адрес, по которому выполняется запись в 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< while(!(TWCR & (1<

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

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


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

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



TWDR = adr;
TWCR = (1<while(!(TWCR & (1<


/*формируем состояние СТОП*/
TWCR = (1<

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


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

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

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

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

TWDR = data;
TWCR = (1< while(!(TWCR & (1< }

/*формируем состояние СТОП*/
TWCR = (1<}

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

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

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


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

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


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

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


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


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

/*формируем состояние СТОП*/
TWCR = (1<

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


uint8_t RTC_GetValue(void)
{
uint8_t data;

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

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

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

/*формируем состояние СТОП*/
TWCR = (1<

Return data;
}

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

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

DS1307 - микросхема часов реального времени с интерфейсом I2C(TWI) . Часы / календарь хранят следующую информацию: секунды, минуты, часы, день, дату, месяц и год. Конец месяца автоматически подстраивается для месяцев, в которых менее 31 дня, включая поправку для високосного года. Часы работают в 24-часовом или 12-часовом формате с индикатором AM/PM. DS1307 имеет встроенную схему контроля питания, которая обнаруживает пропадание питания и автоматически переключает схему на питание от батареи.

Vbat - вход батареи для любого стандартного 3 Вольтового литиевого элемента или другого источника энергии. Для нормальной работы напряжение батареи должно поддерживаться между 2.5 и 3.5 В. Уровень, при котором запрещён доступ к часам реального времени и пользовательскому ОЗУ, установлен внутренней схемой равным 1.25 x Vbat. Литиевая батарея ёмкостью 35 mAh или больше достаточна для питания DS1307 в течение более чем 10 лет при отсутствии питания.
SCL (Последовательный Тактовый Вход) - SCL используется, чтобы синхронизировать передачу данных через последовательный интерфейс.
SDA (Вход/Выход Последовательных Данных) - SDA - вход / выход данных для 2-проводного последовательного интерфейса. Это выход с открытым стоком, который требует внешнего притягивающего резистора.
SQW/OUT (Меандр / Выходной Драйвер) - Когда бит SQWE установлен в 1, на выходе SQW/OUT вырабатываются импульсы в форме меандра одной из четырех частот: 1 Гц., 4 кГц., 8 кГц., 32 кГц. Вывод SQW/OUT - с открытым стоком, требует внешнего притягивающего резистора.
X1, X2 - выводы для подключения стандартного кристалла кварца 32.768 кГц. Внутренняя схема генератора рассчитана на работу с кристаллом, имеющим номинальную емкость (CL) 12.5 пФ.
GND – Земля.
VCC – питание 5 вольт.

DS1307 работает как ведомое устройство на последовательной шине. Для доступа к нему надо установить состояние START и передать код идентификации устройства, сопровождаемый адресом регистра. К последующим регистрам можно обращаться последовательно, пока не установлено состояние STOP . Когда VСС падает ниже 1.25 x Vbat, устройство прекращает связь и сбрасывает адресный счетчик. В это время оно не будет реагировать на входные сигналы, чтобы предотвратить запись ошибочной информации. Когда VСС падает ниже Vbat, устройство переключается в режим хранения с низким потреблением. При включении питания устройство переключает питание с батареи на VСС , когда напряжение питания превысит Vbat + 0.2V, и реагирует на входные сигналы, когда VСС станет более 1.25 x Vbat. Когда питание находится в пределах нормы, устройство полностью доступно, и данные могут быть записаны и считаны. Когда к устройству подключена трёхвольтовая батарея и VСС ниже 1.25 x Vbat, чтение и запись запрещены. Однако отсчёт времени при этом работает. Когда VСС падает ниже Vbat, питание ОЗУ и отсчёта времени переключается на внешнюю батарею 3 В.

Информацию о времени и дате получают, считывая соответствующие регистры. Регистры часов показаны в таблице ниже. Время и календарь устанавливаются или инициализируются путём записи байтов в соответствующие регистры. Содержание регистров времени и календаря хранится в двоично-десятичном (BCD) формате, поэтому перед выводом информации на LCD дисплей или семисегментный индикатор необходимо преобразовать двоично-десятичный код в двоичный или ANSII - код.

Бит 7 регистра 0 - это бит остановки хода часов (Clock Halt). Когда этот бит установлен в 1, генератор остановлен. Когда сброшен в ноль, генератор работает, а часы считают время.

DS1307 может работать в 12-часовом или 24-часовом режиме. Бит 6 регистра часов задаёт один из этих режимов. Когда он равен 1, установлен 12-часовой режим. В 12-часовом режиме высокий уровень бита 5 сообщает о послеполуденном времени. В 24-часовом режиме бит 5 - второй бит 10 часов (20-23 часа).

Регистр управления DS1307 предназначен для управления работой вывода SQW/OUT . Бит OUT - управление выходом. Этот бит управляет выходным уровнем на выводе SQW/OUT , когда генерация меандра запрещена. Если SQWE = 0, логический уровень на выводе SQW/OUT равен 1, если OUT = 1, и 0 - если OUT = 0. SQWE - Разрешение меандра. Когда этот бит установлен в 1, разрешается генерация меандра. Частота меандра зависит от значений битов RS0 и RS1. Эти биты управляют частотой меандра, когда его генерация разрешена. В таблице ниже показаны частоты, которые могут быть заданы RS битами.

DS1307 поддерживает двунаправленные 2-проводную шину и протокол передачи данных. Устройство, которое посылает данные на шину, называется передатчиком, а устройство, получающее данные - приемником. Устройство, которое управляет передачей, называется ведущим. Устройства, которые управляются ведущим - ведомые. Шина должна управляться ведущим устройством, которое вырабатывает последовательные такты (SCL), управляет доступом к шине, и генерирует состояния СТАРТ и СТОП. DS1307 работает как ведомое на 2-х проводной шине.

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

1. Режим записи в DS1307 . Последовательные данные и такты получены через SDA и SCL. После передачи каждого байта передаётся подтверждающий бит ASK . Состояния START и STOP опознаются как начало и конец последовательной передачи. Распознавание адреса выполняется аппаратно после приема адреса ведомого и бита направления. Байт адреса содержит семибитный адрес DS1307, равный 1101000, сопровождаемым битом направления (R/W), который при записи равен 0. После получения и расшифровки байта адреса DS1307 выдаёт подтверждение ASK на линии SDA. После того, как DS1307 подтверждает адрес ведомого и бит записи, ведущий передает адрес регистра DS1307. Тем самым будет установлен указатель регистра в DS1307. Тогда ведущий начнет передавать байты данных в DS1307, который будет подтверждать каждый полученный байт. По окончании записи ведущий сформирует состояние STOP .

2. Режим чтения из DS1307 . Первый байт принимается и обрабатывается как в режиме ведомого приёмника. Однако в этом режиме бит направления укажет, что направление передачи изменено. Последовательные данные передаются по SDA от DS1307, в то время как последовательные такты - по SCL в DS1307. Состояния START и STOP опознаются как начало и конец последовательной передачи. Байт адреса - первый байт, полученный после того, как ведущим сформировано состояние START . Байт адреса содержит семибитный адрес DS1307, равный 1101000, сопровождаемым битом направления (R/W), который при чтении равен 1. После получения и расшифровки байта адреса DS1307 выдаёт подтверждение ASK на линии SDA. Тогда DS1307 начинает передавать данные, начинающиеся с адреса регистра, на которые указывает указатель регистра. Если указатель регистра не записан перед инициированием режима чтения, то первый адрес, который читается - это последний адрес, оставшийся в указателе регистра. DS1307 должен получить неподтверждение NOASK , чтобы закончить чтение.

Рассмотрим особенности работы с DS1307 на примере простых часов, которые будут показывать часы, минуты и секунды. Данные будут выводиться на LCD дисплей 16х2. Две кнопки "Часы+" и "Минуты+" позволят подвести нужное время. Микроконтроллер Atmega 8 тактируется от внутреннего генератора частотой 1 MHz, поэтому не забудьте поменять фьюзы. Ниже представлена схема подключения.

Управляющая программа включает в себя наборы функций работы с шиной TWI, часами DS1307, LCD дисплеем.

I2CInit - инициализация шины;
I2CStart - передача условия START;
I2CStop - передача условия STOP;
I2CWriteByte - запись данных;
I2CReadByte - чтение данных;
DS1307Read - функция чтения данных из DS1307;
DS1307Write - Функция записи данных в DS1307;
lcd_com - передача команды в LCD;
lcd_data - передача данных в LCD;
lcd_string - функция вывода строки в LCD;
lcd_num_to_str - функция вывода символа типа int;
lcd_init - инициализация LCD.

Ниже представлен код программы:

#include #include // Функция инициализация шины TWI void I2CInit(void) { TWBR = 2; // Настройка частоты шины TWSR = (1 << TWPS1)|(1 << TWPS0); // Предделитель на 64 TWCR |= (1 << TWEN); // Включение модуля TWI } // Функция СТАРТ void I2CStart(void) { TWCR = (1 << TWINT)|(1 << TWEN)|(1 << TWSTA); // Передача условия СТАРТ while(!(TWCR & (1 << TWINT))); // Ожидание установки флага TWINT } // Функция СТОП void I2CStop(void) { TWCR = (1 << TWINT)|(1 << TWEN)|(1 << TWSTO); // Передача условия СТОП while(TWCR & (1 << TWSTO)); // Ожидание завершения передачи условия СТОП } // Функция записи данных по шине uint8_t I2CWriteByte(uint8_t data) { TWDR = data; // Загрузка данных в TWDR TWCR = (1 << TWEN)|(1 << TWINT); // Сброс флага TWINT для начала передачи данных while(!(TWCR & (1 << TWINT))); // Ожидание установки флага TWINT // Проверка статуса // Если адрес DS1307+R и принято "подтверждение"(0x18) // или адрес DS1307+W и принято "подтверждение"(0x40) // или передается байт данных и принято "подтверждение"(0x28) if((TWSR & 0xF8) == 0x18 || (TWSR & 0xF8) == 0x40 || (TWSR & 0xF8) == 0x28) return 1; // OK else return 0; // ОШИБКА } // Функция чтения данных по шине uint8_t I2CReadByte(uint8_t *data,uint8_t ack) { // Возвращаем "подтверждение" после приема if(ack) TWCR |= (1 << TWEA); // Возвращаем "неподтверждение" после приема // Ведомое устройство не получает больше данных // обычно используется для распознования последнего байта else TWCR &= ~(1 << TWEA); // Разрешение приема данных после сброса TWINT TWCR |= (1 << TWINT); while(!(TWCR & (1 << TWINT))); // Ожидание установки флага TWINT // Проверка статуса // Если принят байт данных и возвращается "подтверждение"(0x50) // или принят байт данных и возвращается "ненеподтверждение"(0x58) if((TWSR & 0xF8) == 0x50 || (TWSR & 0xF8) == 0x58) { *data = TWDR; // Читаем данные из TWDR return 1; // OK } else return 0; // ОШИБКА } // Функция чтения данных из DS1307 uint8_t DS1307Read(uint8_t address,uint8_t *data) { uint8_t res; I2CStart(); // СТАРТ res = I2CWriteByte(0b11010000); // адрес DS1307+W if(!res) return 0; // ОШИБКА // Передача адреса необходимого регистра res = I2CWriteByte(address); if(!res) return 0; // ОШИБКА I2CStart(); // Повторный СТАРТ res = I2CWriteByte(0b11010001); // адрес DS1307+R if(!res) return 0; // ОШИБКА // Чтение данных с "неподтверждением" res = I2CReadByte(data,0); if(!res) return 0; // ОШИБКА I2CStop(); // СТОП return 1; // OK } // Функция записи данных в DS1307 uint8_t DS1307Write(uint8_t address,uint8_t data) { uint8_t res; I2CStart(); // СТАРТ res = I2CWriteByte(0b11010000); // адрес DS1307+W if(!res) return 0; // ОШИБКА // Передача адреса необходимого регистра res = I2CWriteByte(address); if(!res) return 0; // ОШИБКА res = I2CWriteByte(data); // Запись данных if(!res) return 0; // ОШИБКА I2CStop(); // СТОП return 1; // OK } // Функции работы с LCD #define RS PD0 #define EN PD2 // Функция передачи команды void lcd_com(unsigned char p) { PORTD &= ~(1 << RS); // RS = 0 (запись команд) PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD) PORTD &= 0x0F; PORTD |= (p & 0xF0); // старший нибл _delay_us(100); PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD) _delay_us(100); PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD) PORTD &= 0x0F; PORTD |= (p << 4); // младший нибл _delay_us(100); PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD) _delay_us(100); } // Функция передачи данных void lcd_data(unsigned char p) { PORTD |= (1 << RS)|(1 << EN); // RS = 1 (запись данных), EN - 1 (начало записи команды в LCD) PORTD &= 0x0F; PORTD |= (p & 0xF0); // старший нибл _delay_us(100); PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD) _delay_us(100); PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD) PORTD &= 0x0F; PORTD |= (p << 4); // младший нибл _delay_us(100); PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD) _delay_us(100); } // Функция вывода строки на LCD void lcd_string(unsigned char command, char *string) { lcd_com(0x0C); lcd_com(command); while(*string != "\0") { lcd_data(*string); string++; } } // Функция вывода переменной void lcd_num_to_str(unsigned int value, unsigned char nDigit) { switch(nDigit) { case 4: lcd_data((value/1000)+"0"); case 3: lcd_data(((value/100)%10)+"0"); case 2: lcd_data(((value/10)%10)+"0"); case 1: lcd_data((value%10)+"0"); } } // Функция инициализации LCD void lcd_init(void) { PORTD = 0x00; DDRD = 0xFF; _delay_ms(50); // Ожидание готовности ЖК-модуля // Конфигурирование четырехразрядного режима PORTD |= (1 << PD5); PORTD &= ~(1 << PD4); // Активизация четырехразрядного режима PORTD |= (1 << EN); PORTD &= ~(1 << EN); _delay_ms(5); lcd_com(0x28); // шина 4 бит, LCD - 2 строки lcd_com(0x08); // полное выключение дисплея lcd_com(0x01); // очистка дисплея _delay_us(100); lcd_com(0x06); // сдвиг курсора вправо lcd_com(0x0C); // включение дисплея, курсор не видим } int main(void) { _delay_ms(100); DDRC = 0x00; PORTC = 0xFF; lcd_init(); // Инициализация LCD I2CInit(); // Инициализация шины I2C lcd_string(0x81, "«acГ Ѕa DS1307"); // Часы на DS1307 lcd_string(0xC4, " : : "); // Запускаем ход часов uint8_t temp; DS1307Read(0x00,&temp); temp &= ~(1 << 7); // обнуляем 7 бит DS1307Write(0x00,temp); while(1) { unsigned char hour, minute, second, temp; // Читаем данные и преобразуем из BCD в двоичную систему DS1307Read(0x00,&temp); // Чтение регистра секунд second = (((temp & 0xF0) >> 4)*10)+(temp & 0x0F); DS1307Read(0x01,&temp); // Чтение регистра минут minute = (((temp & 0xF0) >> 4)*10)+(temp & 0x0F); DS1307Read(0x02,&temp); // Чтение регистра часов hour = (((temp & 0xF0) >> 4)*10)+(temp & 0x0F); lcd_com(0xC4); lcd_num_to_str(hour, 2); // Выводим на экран часы lcd_com(0xC7); lcd_num_to_str(minute, 2); // Выводим на экран минуты lcd_com(0xCA); lcd_num_to_str(second, 2); // Выводим на экран секунды if((PINC & (1 << PC0)) == 0) // Если нажата кнопка { while((PINC & (1 << PC0)) == 0){} // Ждем отпускания кнопки hour++; // Увеличиваем часы на 1 if(hour > 23) hour = 0; // Преобразуем из двоичной системы в BCD и записываем в DS1307 uint8_t temp; temp = ((hour/10) << 4)|(hour%10); DS1307Write(0x02, temp); _delay_ms(100); } if((PINC & (1 << PC1)) == 0) // Если нажата кнопка { while((PINC & (1 << PC1)) == 0){} // Ждем отпускания кнопки minute++; // Увеличиваем минуты на 1 if(minute > 59) minute = 0; // Преобразуем из двоичной системы в BCD и записываем в DS1307 uint8_t temp; temp = ((minute/10) << 4)|(minute%10); DS1307Write(0x01, temp); _delay_ms(100); } } }

DS1307 – часы реального времени с последовательным интерфейсом, поддерживают. Часами можно управлять, используя микроконтроллер Atmega128 или другой МК, который имеет последовательный двухпроводной интерфейс. DS1307 подключается напрямую к двум портам I/O микроконтроллера, а двухпроводной интерфейс обеспечивается драйверами низкого уровня, которые описываются в разделе.

Основными характеристиками DS1307 являются: низкая потребляемая мощность, полный BCD часы/календарь и 56 байт энергонезависимой памяти SRAM. Адрес и данные передаются последовательно через двухпроводную двунаправленную шину. Часы/календарь выдают следующую информацию: секунды, минуты, часы, дни, месяцы и годы. Конец месяца автоматически устанавливается для тех месяцев, в которых менее 31 дня. Имеется поправка на високосный год. Часы работают в 24-часовом или 12-часовом формате с индикатором AM/PM. DS1307 имеет встроенную схему контроля питания, которая обнаруживает ошибки питания и автоматически переключается на батарейное питание.

DS1307 работает как «ведомое» устройство на последовательной шине. Для доступа к нему нужно установить состояние START и передать следом за адресом регистра идентификационный код устройства. К следующим регистрам можно обращаться последовательно, пока не установлено состояние STOP. Состояния START и STOP генерируются драйверами низкого уровня.

DS1307 имеет двухпроводную шину, подключённую к двум выводам порта I/O МК: SCL – PD0, SDA – PD1. Напряжение VDD равно 5В, схема использует кварцевый (часовой) резонатор с частотой 32.678 кГц.

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

В соответствии с этим должны быть оговорены следующие условия:

Начало передачи данных. Изменение состояния линии данных при переходе из высокого в низкое, в то время как тактовая линия находится в высоком состоянии, определяется как состояние START.

Остановка передачи данных. Изменение состояния линии данных при переходе из низкого в высокое, в то время как тактовая линия находится в высоком состоянии, определяется как состояние STOP.


Действительные данные. Состояние линии данных соответствует действительным данным тогда, когда после условия START линия данных стабильна во время высокого состояния тактового сигнала. Данные на линии должны быть изменены во время низкого состояния тактового сигнала. Один тактовый импульс на один бит данных.

Рис. 2.40. Передача данных по последовательной двухпроводной шине

Каждая передача данных начинается при наступлении состояния START и прекращается при наступлении состояния STOP. Количество байт данных переданных между состояниями START и STOP не ограничено и определяется «ведущим» устройством. Информация передаётся побайтно, и каждый приём подтверждается девятым битом.

Подтверждение приёма. Каждое приёмное устройство, при обращении к нему, вынуждено генерировать подтверждение приёма после получения каждого байта. «Ведущее» устройство должно генерировать дополнительные тактовые импульсы, которые ставятся в соответствие битам подтверждения. Если сигнал подтверждения приёма находится в высоком состоянии, то по приходу тактового импульса бита подтверждения, подтверждающее приём устройство должно переводить линию SDA в низкое состояние. Конечно, должны учитываться время установки и время удержания. «Ведущее» устройство должно сигнализировать об окончании передачи данных «ведомому» устройству, прекращая генерацию бита подтверждения, при получении от «ведомого» тактового импульса подтверждения приёма. В этом случае, «ведомый» должен перевести линию данных в низкое состояние, чтобы позволить «ведущему» генерировать условие STOP.

На рис. 2.40 показано завершение передачи данных по двухпроводной линии. В зависимости от состояния бита R/-W, возможны два типа передачи:

1. Режим «ведомого» приемника (режим записи в DS1307): последовательные данные и такты получены через SDA и SCL соответственно. После передачи каждого байта передаётся подтверждающий бит (рис.2.40). Состояния START и STOP понимаются как начало и конец последовательной передачи. Распознавание адреса выполняется аппаратно после приема адреса «ведомого» и бита направления. Байт адреса является первым байтом, принимаемым после возникновения состояния START, генерируемого «ведущим». Байт адреса содержит семь битов адреса DS1307, равных 1101000, сопровождаемых битом направления (R/#W), который для записи равен 0 (рис. 2.40а). После приёма и декодирования байта адреса DS1307 выдаёт подтверждение на линию SDA. После подтверждения DS1307 адреса «ведомого» и бита записи, «ведущий» передает адрес регистра DS1307. Тем самым будет установлен указатель регистра в DS1307. Затем «ведущий» начнет передавать каждый байт данных с последующим приёмом подтверждения получения каждого байта. По окончании записи «ведущий» сформирует состояние STOP, для прекращения передачи данных.

2. Режим «ведомого» передатчика (режим чтения из DS1307): Первый байт принимается и обрабатывается как в режиме «ведомого» приёмника. Однако в этом режиме бит направления укажет, что направление передачи изменено. Последовательные данные передаются DS1307 по SDA, тактовые импульсы - по SCL. Состояния START и STOP понимаются как начало и конец последовательной передачи (рис. 2.40). Байт адреса является первым байтом, принимаемым после возникновения состояния START, генерируемого «ведущим». Байт адреса содержит семь битов адреса DS1307, равных 1101000, сопровождаемых битом направления (R/-W), который для чтения равен 1. После приёма и декодирования байта адреса DS1307 принимает подтверждение с линии SDA. Тогда DS1307 начинает передавать данные с адреса, на который показывает указатель регистра. Если указатель регистра не записан перед инициализацией режима чтения, то первым прочитанным адресом является последним адрес, сохранённый в указателе регистра. DS1307 должен послать бит «неподтверждения», чтобы закончить чтение (рис. 2.40б).

Схема подключения DS1307 к МК Atmega128 приведена на рис. 2.41, где кнопки SA1 и SA2 предназначены для начальной настройки часов.

Рис. 2.41. Подключение схемы DS1307 микроконтроллеру по интерфейсу IIC (шина TWI)

Контрольные вопросы

1. В чем отличие RISC –процессора от CISC-процессор?

2. В чем преимущества аккумуляторной архитектуры от архитектуры с регистрами общего назначения?

3. Что значит команды с фиксированной разрядностью? Дайте разъяснение.

4. Перечислите периферийные устройства МК и приведите примеры их использования.

5. Назначение отладочных средств. Что дает программисту использования отладчиков?

6. На какие линии делятся МК фирмы Atmel? В чем их отличие.

7. Где используются МК сверхмалым энергопотреблением?

8. Какие меры принимаются для снижения энергопотребления фирмами-разработчиками?

Глава III. Лабораторный практикум по микроконтроллерам семейства AVR фирмы ATMEL

Лабораторный практикум предназначен для изучения студентами специальностей 22.01.00, 23.03.00 и 23.05 всех форм обучения основных принципов построения и программирования, цифровых систем управления и обработки данных и является частью учебно-методических разработок по дисциплинам «Микропроцессорные системы», «Микропроцессорные устройства систем управления», «Микропроцессорные системы управления», «Машинно-ориентированные языки программирования». Приведенные в практикуме лабораторные работы позволят получить практические навыки по разработке аппаратного, алгоритмического и программного обеспечения микроконтроллерных систем управления и обработки данных.

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

Все работы проводятся на учебно-лабораторном стенде УЛС-ATmega8535, разработанным автором по единой схеме «Получение задания [Анализ задания [ Разработка алгоритма решения задачи [ Разработка и отладка программного обеспечения на отладчике AVR Studio 4 [ Программирование реальной системы с использованием PonyProg2000[Проверка работоспособности и демонстрации реальной системы [ Составление и защита отчета».

Отличительными особенностями стенда являются:

· универсальность;

· возможность подключения различных типов датчиков и исполнительных устройств;

· возможность использования современных аппаратно–программных средств отладки и программирования микроконтроллеров в реальных условиях;

Эффективность использования УЛС в учебном процессе определяется:

1. Универсальностью стенда, т.к. стенд позволяет посредством коммутации перестраивать структуру стенда для решения различных задач, что даст возможность на одном стенде проводить большое количество лабораторных работ (более 30);

2. Повышением эффективности проведения занятий, так как отладкапрограммного обеспечения стенда производится на отладчике AVR Studio 4, a программирование реального МК производится с использованием аппаратно-программных средств PonyProg2000;

3. Возможностью решения поставленных задач по схеме «Получение задания [Анализ задания [ Разработка алгоритма решения задачи [ Разработка и отладка программного обеспечения на отладчике AVR Studio 4 [ Программирование реальной системы с использованием PonyProg2000[Проверка работоспособности и демонстрации реальной системы [ Составление и защита отчета» с использованием реальных микроконтроллеров и современных аппаратно–программных средств отладки и программирования микроконтроллерных систем управления и обработки данных.

4. Получением дополнительных навыков по работе с различными периферийными устройствами (системами индикации на основе ЖКИ, интерфейсами UART, SPI, IIC и RS-232) и т.д.;

5. Возможностью разработки собственных вариантов лабораторных работ.

Принципиальная схема УЛС и расположение элементов на печатной плате приведены на рисунках 3.1 и 3.2.

Основные элементы УЛС следующие:

1. Кнопка включения напряжения питание SB9 (В качестве источника питания стенда используется блок питания компьютера +5В, что обеспечивает безопасность работы на стенде).

2. Светодиод HL17 индицирует наличие напряжения +5В.

3. . Микросхема D1 - микроконтроллер Atmega8535 семейства AVR, производства фирмы ATMEL, 8-восьмиразрядный, с тактовой частотой от 0 до 16 МГц. В своем составе имеет – Flash-память программ 8 Кбайт, ОЗУ (оперативное запоминающее устройство) 512 байт, 512 байт EEPROM (электрически перепрограммируемая память), два 8-разрядных и один 16-разрядный таймеры/счетчики, 8-канальный 10-битный АЦП (а налого-ц ифровой п реобразователь), программируемый последовательный интерфейс UART, последовательные интерфейсы SPI и I2C.

4. Жидкокристаллический индикатор (ЖКИ) LCD, подключенный к микроконтроллеру через параллельный порт (8 линий данных и 3 линии управления, разъемы Х8 и Х11 соответственно). ЖКИ дает возможность наглядно устанавливать (посредством кнопок настройки часов) и отображать текущее время и результаты работы АЦП.

5. Кнопки SB1, …, SB8 и светодиодные индикаторы HL1- HL8, предназначены для имитации дискретных сигналов ввода.

6. Линейка индикации дискретных сигналов вывода, реализованной на светодиодах HL9- HL16.

7. Резисторы переменного сопротивления R20, R21 для имитации датчиков угла поворота (задающее устройство в системах управления) или для формирования входного аналогового сигнала АЦП.

8. Кнопки-имитаторы внешних сигналов запроса прерывания S16, S17.

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

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

  • Очень маленькое энергопотребление. Производитель обещает 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 секунды. Что-то мне подсказывает, что дело в ёмкости кварца, попробую другой отпишусь.

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