Мне прилетели из Китая микросхемы и я начал над ними издеваться в целях исследования перед внедрением в будущие часы. Предупреждаю – в данной статье я исследовал только работу со временем, дата и прочие приблуды этих часов меня мало интересовали и я не терял на это время.
Задача: Запись значения времени/чтение значения времени и вывод через USART из микросхемы DS1307.
Исходный материал: PIC16f628a, DS1307, MAX232 level converter, devboard, proteus.
Микросхема DS1307 – модуль часов реального времени, которым можно управлять по шине данных I2C. Можно конечно было реализовать RTC на базе лишь одного микроконтроллера, но проще подцепить DS1307 + часовой кварц и не запариваться. Начинаем как обычно с вкуривания даташита. Выдергиваем оттуда схему применения и собираем ее:
Выцепляем из даташита организацию памяти микросхемы:
Как я уже отметил ранее, меня интересует лишь время, поэтому будем юзать только адреса 00h, 01h, 02h. Данные в регистрах хранятся в формате BCD.
Вкратце о BCD: смешанный формат хранения данных. Число разбивается на две части, в младшей части байта – обычный бинарный формат, в старшей – фактически число в бинарном формате, смещенное влево на 4 бита и умноженное на 10 . Пример – 0b00110101 => 0b0011 = 3*10 = 30, 0b0101 = 5 – число = 35.
Таким образом, нам нужно проделать некоторые манипуляции чтобы записать нормально число в регистр. Я создал две функции: одна конвертирует десятичное число в BCD, другая конвертирует BCD в десятичный формат.
Конвертация из BCD в десятичную форму:
unsigned char BCDconv (unsigned char source) { unsigned char temp_min=0; //переменная для единиц unsigned char temp_maj=0; //переменная для десятков temp_min = source&15; //помещаем младшую часть байта в переменную (15 = 0b00001111) temp_maj = source >> 4; //Смещаем старшую часть вправо на 4 бита temp_maj *= 10; return temp_maj+temp_min; //возвращаем десятичное значение }
Конвертация в BCD:
unsigned char DCBconv (unsigned char source) { unsigned char temp_min=0; unsigned char temp_maj=0; temp_maj = source/10 ; temp_min = source - temp_maj*10; temp_maj <<= 4; return temp_maj+temp_min; }
Дальше нам надо определиться с процедурами записи и чтения в часы, лезем в даташит, там красивая картинка:
На картинке показана процедура записи, с этим вопросов не возникает, создаем функции записи часов, минут, секунд.
void SetHour(unsigned char hours) { //записываем значение в часы i2c_start(); i2c_tx(0b11010000); i2c_tx(0x02); //адрес часов i2c_tx(DCBconv(hours)); //запихиваем нужное значение i2c_stop(); } void SetMin(unsigned char minutes) { //записываем значение в минуты i2c_start(); i2c_tx(0b11010000); i2c_tx(0x01); //адрес минут i2c_tx(DCBconv(minutes)); //запихиваем нужное значение i2c_stop(); } void SetSeconds(unsigned char seconds) { //записываем значение в секунды i2c_start(); i2c_tx(0b11010000); i2c_tx(0x00); //адрес секунд i2c_tx(DCBconv(seconds)); //впихиваем нужное значение i2c_stop(); }
C чтением есть один небольшой нюанс, на который я вначале не обратил внимания:
Из даташита: The DS1307 must receive a Not Acknowledge to end a read.
А я как образцовый slave отсылал постоянно acknowledge бит :). Таким образом:
Потом я это уже и на картинке заметил, так что читаем даташит внимательно, если не хотим потерять много времени впустую.
unsigned char ReadHour() { unsigned char temp = 0; i2c_start(); i2c_tx(0b11010000); i2c_tx(0x02); i2c_start(); i2c_tx(0b11010001); temp = i2c_rx(0); //читаем значение часов (0) означает, что мы не шлем acknowledge i2c_stop(); return BCDconv(temp); } unsigned char ReadMin() { unsigned char temp = 0; i2c_start(); i2c_tx(0b11010000); i2c_tx(0x01); i2c_start(); i2c_tx(0b11010001); temp = i2c_rx(0); //читаем значение минут (0) означает, что мы не шлем acknowledge i2c_stop(); return BCDconv(temp); } unsigned char ReadSeconds() { unsigned char temp = 0; i2c_start(); i2c_tx(0b11010000); i2c_tx(0x00); i2c_start(); i2c_tx(0b11010001); temp = i2c_rx(0); //читаем значение часов (0) означает, что мы не шлем acknowledge i2c_stop(); return BCDconv(temp); }
Теперь сварганим процедуры установки и отображения времени по USART.
Отображение времени:
void ShowTime() { printf("\fTime - %d :", ReadHour()); printf(" %d :", ReadMin()); printf(" %d ", ReadSeconds()); printf("\r\n*********************"); printf("\r\n Menu"); printf("\r\n 1 - Reload time"); printf("\r\n 2 - Setup time"); }
Функция отображает такую вот менюшку:
По клавише 1 – данные в терминале перезагружаются, я специально не стал делать постоянное отображение времени, чтобы не поймать левых косяков. По клавише 2 запускается процедура установки времени:
void SetupTime() { unsigned char state = 1; unsigned char temp_min = 0; unsigned char temp_maj = 0; unsigned char temp = 0; while(state){ //Установка часов printf("\fEnter hours - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 24) { SetHour(temp); state = 0; } } } state = 1; while(state){ //Установка минут printf("\fEnter minutes - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 61) { SetMin(temp); state = 0; } } } state = 1; while(state){ //Установка секунд printf("\fEnter seconds - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 61) { SetSeconds(temp); state = 0; } } } ShowTime(); }
Ну и кратенькое содержание main.c :
void main(void){ unsigned char input; CMCON = 0x07; INTCON=0; // вырубаем прерывания init_comms(); // инициализация USART SetHour(15); // записываем произвольное значение времени SetMin(38); SetSeconds(23); ShowTime(); //отображаем менюшку for(;;){ input = getch(); //опрос клавиши switch (input) { case 49 : ShowTime(); //если 1 то обновляем экран break; case 50 : SetupTime(); //если 2 то устанавливаем время break; } } }
Вот собственно и все, этого набора функций достаточно для работы, видео работы реального устройства:
Update: по просьбам выкладываю архив с проектом – RTC_clock.
А теперь еще и все исходники на репе.
Доброго времени суток. Не нашел почему-то описания i2c_rx. Если можно добавьте пожалуйста.
Добрый вечер, спасибо что указали на недостающий код, я вам его выброшу сюда в комментарий, а то в старых импортированных статьях после правок комментарии в коде становятся нечитаемыми:
Спасибо.
Здравствуйте! Будьте добры, полному чайнику можно ознакомиться с о схемой и прошивкой.
Спасибо!
Схема в самом начале статьи приведена: http://diymicro.ru/wp-content/uploads/2011/09/sch.jpg
Прошивка поэтапно в принципе тоже есть в статье, если вам нужен весь проект, то вечером смогу выложить если конечно он у меня остался 🙂
В конце поста выложил архив, качайте на здоровье.
Здравствуйте! Для малограмотных, если возможно, будьте добры hex-файл выложить. Спасибо!
Внизу статьи есть ссылка на архив – там всё: исходные коды, файл протеуса и файл hex
доброго времени суток….
спсыбо sarge за проект….
можете добавить число месяц и год….
заранее благодарень…..
Доброго, сейчас на это совершенно нет времени.
Там вроде схожие процедуры, можете описать свои функции, а я допишу их в статью с указанием авторства.
спасыбо за ответ…
1. reload time-data
2. setup time
3. setup data
или
1. reload info
2. setup all data
главное что я мог читать и писать через RS232
заранее благодарень…
Эм, я имел ввиду полностью на си, а не просто указание того, что вам надо.
если я знал си то самому написал…. я новичок в асме
если я знал си то самому написал… я новичок но толко асме….
Ну что я могу сказать – сейчас совершенно не до этого, напомните через недельку и я постараюсь сделать.
огромное спасыбо…
безусловно буду напоминать
если есть времия можете модифитироват (исходник) прашивку
1. reload time-data
2. setup time
3. setup data
или
1. reload info
2. setup all data
главное что я мог приният и отправит через RS232
Я помню про вас, но у меня никак руки не могут дойти до этого.
тогда будем ждать :–)
спасыбо за ответ…
1. reload time-data
2. setup time
3. setup data
или
1. reload info
2. setup all data
главное что я мог читать и писать через RS232
заранее благодарень…
доброго времени суток…
вы сказали: напомните через недельку и я постараюсь сделать.
жду с нетерпением…
заранее благодарень….
Это просто жесть!! Сколько надо труда и времени чтоб держать такой сайт да и еще с “железом” и кодингом разобраться! Моя благодарность за такой сайт и большое уважение к таким людям!!
Спасибо за Ваш отзыв 🙂
А подскажите еще по поводу подтягивающих резисторов – обычно пишут 10 кОм, но в протеусе они не подходят – ошибка выходит. По Вашей схеме(pullup) в протеусе всё работает.
Вот для Вашей схемы, когда всё на одной плате и по близости с одним slave устройством какие номиналы посоветуете поставить резисторов.
Обычно все зависит от характеристик используемых микросхем (какой выходной ток дают буфера например), но обычно я в цифре на кнопки и всякие и2с протоколы тыкаю либо 10К либо 4.7К, в зависимости от того чего у меня больше.
Вот если вы соберетесь юзать длинную линию и2с, то желательно номиналы уменьшать.
Добрый вечер!
Хочу опрашивать sht21, за основу беру ваш учебный материал. Но так как я только изучаю С, есть некоторые вопросы:
Разберем этот кусочек кода
unsigned char i2c_rx(unsigned char ack)
{
unsigned char x,d=0;
SDA = 1;
for (x=0; x<8;x++) {
d <<= 1;
do {
i2c_delay();
SCL = 1;
} while (SCL_IN==0);
i2c_delay();
if (SDA_IN) d |= 1;
SCL = 0;
}
i2c_delay();
if (ack) SDA = 0;
else SDA = 1;
SCL = 1;
i2c_delay();
SCL = 0;
return d;
}
1) Почему в строке unsigned char i2c_rx(unsigned char ack) – в скобках у вас (unsigned char ack),
хотя по моей логике мы должны возвращать именно полученный байт, а не значение бита ack.
2) Как в этой строке мы узнаем что у нас ack равен 1 или 0: if (ack) SDA = 0;
Могу предположить что мы получили восемь бит , а затем вываливаемся из for (x=0; x<8;x++) {} и следующий бит и есть наше значение ack.
3)Что будет если ack =1 что программа дальше делает, или может подчиненное устройство должно повторить посылку?
1) то что в скобках не читаемый, а передаваемый параметр, сама функция возвращает именно полученный байт (посмотрите внимательно функция имеет формат unsigned char, а не void)
2) ack передается параметром (см п1)
3) Если ack = 1, 9 бит = 0, иначе 1
В чтении мк сам отсылает бит ack/nack, так как он является ведомым.
Судя из описания протокола i2c, мы должны периодически проверять SCL на 0 и ждать прихода ack при отправке информации слейву, а в вашей программе строка while (SCL_IN==0); присутствует в приемной части unsigned char i2c_rx(unsigned char ack) . А как раз в i2c_rx – мастеру нужно поджимать SDA к земле если мы приняли 8 бит. Прошу разъяснить мне мою проблему.
Это в каком таком описании при отправке мастер ждет клока?
В начале есть код с нумерацией строк, прошу пошагово разжевать как он выполняется (Коментарии подписать что ли)
В третьем по счету от самого начала есть код с небольшими комментами, добавлять там больше нечего.