Урок 17
Часть 2
Часы реального времени DS1307
В предыдущей части занятия мы познакомились с отличной микросхемой реального времени DS1307, подключили её, и начали писать исходный код для установки времени, даты и дня недели.
Продолжим писать наш код, используя тот же самый проект
Используя написанную функцию перевода из десятичного формата числа в двоично-десятичный, занесём данные в регистры микросхемы, начиная с нулевого адреса (также мы знаем что это и адрес самого первого регистра микросхемы)
I2C_SendByte(0);//Переходим на 0x00
I2C_SendByte(RTC_ConvertFromBinDec(0)); //секунды
I2C_SendByte(RTC_ConvertFromBinDec(31)); //минуты
I2C_SendByte(RTC_ConvertFromBinDec(20)); //часы
I2C_SendByte(RTC_ConvertFromBinDec(5)); //день недели
I2C_SendByte(RTC_ConvertFromBinDec(29)); //дата
I2C_SendByte(RTC_ConvertFromBinDec(1)); //месяц
I2C_SendByte(RTC_ConvertFromBinDec(16)); //год
Ну, у вас конечно будут другие данные, в зависимости от того момента времени, в который вы будете запускать данную программу.
Ну, и в конце, конечно условие STOP
I2C_SendByte(RTC_ConvertFromBinDec(16)); //год
I2C_StopCondition();
while(1)
Соберем код, установим в скобки правильное время и прошьём контроллер.
Пока мы никак не можем проверить, как это всё записалось и ходят ли часы. Для этого нужно написать код для чтения.
Можно конечно было бы посмотреть статусы, но раз уж нам всё равно читать показания регистров, то особого смысла в этом не вижу.
Пока данный код записи в регистры полностью закомментируем. Мы будем его раскомментировывать тогда, когда будем программировать новую микросхему и заносить данные регистры, каждый раз это делать на нужно.
Теперь в бесконечный цикл начнем писать код считывания данных из регистров
Точно также всё начинается с условия СТАРТ.
Но для того, чтобы нам всю это рутину каждый раз не писать, давайте напишем функцию для передачи по адресу устройства байта адреса памяти
void I2C_SendByteByADDR(unsigned char c,unsigned char addr)
{
I2C_StartCondition(); // Отправим условие START
I2C_SendByte(addr); // Отправим в шину адрес устройства + бит чтения-записи
I2C_SendByte(c);// Отправим байт данных
I2C_StopCondition();// Отправим условие STOP
}
Также заодно напишем функцию чтения обычного байта из шины и чтения последнего байта из шины. У нас такие функции уже были, но они были в особенном файле и были с префиксом EE_, а также там была обработка ошибки. Скопируем их себе теперь в стандартный обычный TWI.c, убрав префиксы и всё лишнее
unsigned char I2C_ReadByte(void)
{
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
while (!(TWCR & (1<<TWINT)));//ожидание установки бита TWIN
return TWDR;//читаем регистр данных
}
unsigned char I2C_ReadLastByte(void)
{
TWCR = (1<<TWINT)|(1<<TWEN);
while (!(TWCR & (1<<TWINT)));//ожидание установки бита TWIN
return TWDR;//читаем регистр данных
}
Напишем в TWI.h на все эти функции прототипы
void I2C_SendByte(unsigned char c); //передача байта в шину
void I2C_SendByteByADDR(unsigned char c,unsigned char addr); //передача байта в шину на устройство по адресу
unsigned char I2C_ReadByte(void); //читаем байт
unsigned char I2C_ReadLastByte(void); //читаем последний байт
И теперь можно начинать писать код в бесконечный цикл функции main().
Отправим адрес 0 (адрес первого регистра) по адресу устройства
while(1)
{
//Читаем время
I2C_SendByteByADDR(0,0b11010000); //переходим на адрес 0
Затем вставим задержку на 300 милисекунд. Данную задержку можно и в конце кода вставить, ну давайте попробуем здесь, поэксперементируем, так сказать
I2C_SendByteByADDR(0,0b11010000); //переходим на адрес 0
_delay_ms(300);
Затем посылаем в шину адрес устройства с битом чтения, отправив перед этим условие СТАРТ
_delay_ms(300);
I2C_StartCondition(); //Отправим условие START
I2C_SendByte(0b11010001); //отправим в устройство бит чтения
По идее мы вообще так не должны делать, так как у нас в функции, которую мы вызвали перед задержкой было в конце условие СТОП, а это не требуется. Обычно сразу СТАРТ. Но можно и так, всё работает. Если указатель установлен уже туда, куда надо, то можно стразу адрес чтения и начинать читать, а не так, как было в случае с EEPROM.
Дальше читаем все регистры
I2C_SendByte(0b11010001); //отправим в устройство бит чтения
sec = I2C_ReadByte();
min = I2C_ReadByte();
hour = I2C_ReadByte();
day = I2C_ReadByte();
date = I2C_ReadByte();
month = I2C_ReadByte();
year = I2C_ReadLastByte();
Помним, что последний байт читается из шины без подтверждения и для этого у нас есть соответствующая функция.
В конце чтения отправим в шину условие СТОП
year = I2C_ReadLastByte();
I2C_StopCondition(); //Отправим условие STOP
Далее, используя функцию преобразования из двоично-десятичного формата в десятичный, преобразуем считанные показания, так как мы их забрали из регистров именно в двоично-десятичном виде.
I2C_StopCondition(); //Отправим условие STOP
sec = RTC_ConvertFromDec(sec); //Преобразуем в десятичный формат
min = RTC_ConvertFromDec(min); //Преобразуем в десятичный формат
hour = RTC_ConvertFromDec(hour); //Преобразуем в десятичный формат
day = RTC_ConvertFromDec(day); //Преобразуем в десятичный формат
year = RTC_ConvertFromDec(year); //Преобразуем в десятичный формат
month = RTC_ConvertFromDec(month); //Преобразуем в десятичный формат
date = RTC_ConvertFromDec(date); //Преобразуем в десятичный формат
И в коце бесконечного цикла отправим всё это в определённом виде в USART
date = RTC_ConvertFromDec(date); //Преобразуем в десятичный формат
USART_Transmit(date/10+0x30);//Преобразуем число в код числа
USART_Transmit(date%10+0x30);//Преобразуем число в код числа
USART_Transmit('.');
USART_Transmit(month/10+0x30);//Преобразуем число в код числа
USART_Transmit(month%10+0x30);//Преобразуем число в код числа
USART_Transmit('.');
USART_Transmit(year/10+0x30);//Преобразуем число в код числа
USART_Transmit(year%10+0x30);//Преобразуем число в код числа
USART_Transmit(' ');
USART_Transmit('-');
USART_Transmit(day+0x30);//Преобразуем число в код числа
USART_Transmit('-');
USART_Transmit(' ');
USART_Transmit(' ');
USART_Transmit(hour/10+0x30);//Преобразуем число в код числа
USART_Transmit(hour%10+0x30);//Преобразуем число в код числа
USART_Transmit(':');
USART_Transmit(min/10+0x30);//Преобразуем число в код числа
USART_Transmit(min%10+0x30);//Преобразуем число в код числа
USART_Transmit(':');
USART_Transmit(sec/10+0x30);//Преобразуем число в код числа
USART_Transmit(sec%10+0x30);//Преобразуем число в код числа
USART_Transmit(0x0d);//переход в начало строки
USART_Transmit(0x0a);//перевод каретки
}
Смещение на 0x30 в вычисление кода символа — это преобразование самой цифры в код цифры. Именно такая разница и есть в таблице ascii.
Можно конечно не париться с таким преобразованием и использовать функцию sprintf и она прекрасно с этим справится, но так интересно, функция sprintf ещё себя покажет, это я уж вам обещаю точно.
Соберём код, откроем терминальную программу, нажмём там Connect, Прошьём контроллер и посмотрим результат
Наши часы отлично ходят.
Предыдущая часть Программирование МК AVR Следующий урок
Документация на микросхему DS1307
Программатор, модуль RTC DS1307 с микросхемой памяти и переходник USB-TTL можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Модуль RTC DS1307 с микросхемой памяти
Переходник USB-TTL лучше купить такой (сейчас у меня именно такой и он мне больше нравится)
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Превосходно. Особенно помогли ошибки, которые Вы в ходе урока разбираете и устраняете. Мне это помогло в поиске своих ошибок гораздо больше чем многотомники по С++. Благодарю Вас за прекрасные уроки.
Вам также спасибо за внимание к моим занятиям!
Ага! Такое чувство, что уважаемый автор такие ошибки специально делает, хотя и за это ему огромное спасибо!
При Установке времени. Зачем возводить Hex Число в Hex
31= 0011 0001. Мы сдвигаем его влево на полубайт = 0000 0011 и умножаем на 10 получается 0011 0000. и прибавляем к нему:
0000 1111 * 31
0000 1111
&
0011 0001 (31)Hex
=
0000 0001
а
0011 0000+0000 0001=0011 0001
Следовательно ничего не изменилось, мы просто возвели шестнадцатиричный блок в десятичный разделили на десятки и единицы и добавили к нему 0x30(0x30+3=0x33=Символ 3) и отправили в Hex по Uart.
Можно записывать сразу в шестнадцатиричной системе.
I2C_SendByte(0x00); //секунды
I2C_SendByte(0x31); //минуты
I2C_SendByte(0x20); //часы
I2C_SendByte(0x05); //день недели
I2C_SendByte(0x29); //дата
I2C_SendByte(0x01); //месяц
I2C_SendByte(0x16); //год
Это сократит длину кода.
Здравствуйте! А как вывести это все на дисплей 20×4 по I2C? К примеру, секунды выводил. В main дисплей инициализировал, в while процедуру считывания времени прописал. Преобразования в том числе. Через sendcharlcd(sec/10+0x30) и sendcharlcd(sec%10+0x30) на дисплей вывожу. Какая то чепуха выводится. Разделительные знаки присутствуют а вот остальное кракозябрами, секунды — случайный знако-символьный набор. В терминале также не пойми что. По отдельности все работает как надо. Подскажите, что я упустил? И, да, большое спасибо Вам за уроки! Всегда интересно узнавать что-то новое и полезное. Спасибо!
P.S Устранил проблему подтягивающими резисторами на землю. Очень странно.
А что интересно админ ответит на написанный выше комментарий?
Или он только отвечает на комментарии типа «превосходно» или «чудесно»?
qwas,
Здравствуйте!
Пока, к сожалению, на все комментарии отвечать времени нет.
Да и всегда я отвечал только на те комментарии, на которые у меня есть ответ, так как врать не привык, не так воспитан.
Потихоньку отделаюсь от годовой отчётности и вернусь.
Поэтому я никогда не запрещаю, а наоборот даже приветствую ответы посетителей ресурса.
Здравствуйте! Не подскажите, микросхема ds1302 написано в даташите 3-wire интерфейс, кроме clk и sda еще есть reset. Её можно к i2c интерфейсу подключить, установив линию reset в верхний уровень?
С Уважением, Семён. Спасибо.