PIC мк. Эксперимент №15. Часы реального времени DS1307.

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

Задача: Запись значения времени/чтение значения времени и вывод через 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.

А теперь еще и все исходники на репе.

PIC мк. Эксперимент №15. Часы реального времени DS1307.: 31 комментарий

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

    
    unsigned char i2c_rx(unsigned char ack)
    {
    unsigned char x,d=0;
       SDA = 1;
       for (x=0; x<8;x++) {      //принимаем 8 бит данных
        d <<= 1;
        do {                     //clock stretching
           i2c_delay();
           SCL = 1;
        } while (SCL_IN==0);
        i2c_delay();
        if (SDA_IN) d |= 1;
        SCL = 0;
        }
        i2c_delay();
        if (ack) SDA = 0;        //если нужно, шлем ack бит
           else SDA = 1;
        SCL = 1;
        i2c_delay();
        SCL = 0;
        
        return d;               //возвращаем принятое значение
      }
    
  2. Здравствуйте! Будьте добры, полному чайнику можно ознакомиться с о схемой и прошивкой.
    Спасибо!

  3. Здравствуйте! Для малограмотных, если возможно, будьте добры hex-файл выложить. Спасибо!

  4. доброго времени суток….
    спсыбо sarge за проект….
    можете добавить число месяц и год….
    заранее благодарень…..

  5. спасыбо за ответ…
    1. reload time-data
    2. setup time
    3. setup data
    или
    1. reload info
    2. setup all data
    главное что я мог читать и писать через RS232
    заранее благодарень…

  6. доброго времени суток…
    вы сказали: напомните через недельку и я постараюсь сделать.
    жду с нетерпением…
    заранее благодарень….

  7. Это просто жесть!! Сколько надо труда и времени чтоб держать такой сайт да и еще с “железом” и кодингом разобраться! Моя благодарность за такой сайт и большое уважение к таким людям!!

  8. А подскажите еще по поводу подтягивающих резисторов – обычно пишут 10 кОм, но в протеусе они не подходят – ошибка выходит. По Вашей схеме(pullup) в протеусе всё работает.
    Вот для Вашей схемы, когда всё на одной плате и по близости с одним slave устройством какие номиналы посоветуете поставить резисторов.

    • Обычно все зависит от характеристик используемых микросхем (какой выходной ток дают буфера например), но обычно я в цифре на кнопки и всякие и2с протоколы тыкаю либо 10К либо 4.7К, в зависимости от того чего у меня больше.
      Вот если вы соберетесь юзать длинную линию и2с, то желательно номиналы уменьшать.

  9. Добрый вечер!
    Хочу опрашивать 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, так как он является ведомым.

  10. Судя из описания протокола i2c, мы должны периодически проверять SCL на 0 и ждать прихода ack при отправке информации слейву, а в вашей программе строка while (SCL_IN==0); присутствует в приемной части unsigned char i2c_rx(unsigned char ack) . А как раз в i2c_rx – мастеру нужно поджимать SDA к земле если мы приняли 8 бит. Прошу разъяснить мне мою проблему.

  11. В начале есть код с нумерацией строк, прошу пошагово разжевать как он выполняется (Коментарии подписать что ли)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.