1307Тема часов на микросхеме DS1307 довольно актуальна — это простое, но в то же время интересное устройство. Кроме того, оно может реально пригодиться. Но описывать отдельно микросхему смысла нет, поэтому я решил собрать себе подобное устройство, заодно рассказать о том, какие шишки набил при этом. Сам процесс разработки и сборки буду описывать, по мере прохождения некоторых этапов готовности девайса.

Update 17.10.2015
Вначале это была серия статей, целью которых было рассказать про создание устройства с нуля до состояния готовности, но внезапно у меня появилась аллергия на все что называется «часы», поэтому я слил все в одну статью. Устройство закончено на 99.9%, (осталось закрутить винты), но сделать это ой как не просто :) Как только аллергия пройдет появится окончательная фотка.

Начнем с того, что пока нам ничего не известно про ds1307 кроме того, что с ее помощью делают часы. Поэтому качаем документацию, на эту микросхему и читаем список «вкусностей», которыми она обладает. Итак, из первого абзаца в целом понятно, что она обладает низким энергопотреблением, информация передается по I2C, можно узнать дату и время, 12 и 24 часовой формат, автоматическая подстройка даты. Но самое интересное это схема (TYPICAL OPERATING CIRCUIT).

sch1307

Курим даташит и пытаемся разобраться что к чему. Идем слева направо, CPU — микроконтроллер ( то есть наша atmega), два резистора, написано pull up — значит подтягивающие (можно взять по 10к), кварц на 32768Гц, сама микросхема и батарейка. Выход SQW/OUT может дрыгаться с частотой 1Hz, 4kHz, 8kHz, 32kHz, пока нам это не интересно. Пожалуй, этой информации пока достаточно, хочется уже чего нибудь накодить :)

Создаем проект в CodeVision, в разделе I2C находим ds1307 и включаем его в проект. Хорошо бы еще выводить куда нибудь информацию, например на LCD и пара кнопок не помешает.

i2c_setup

Все что нужно это LCD настроить на порт D и три кнопки с подтяжкой на вход. Далее нужно вывести на LCD время, для этого заглянем в мануал CodeVision и возьмем оттуда пример. Оказывается все просто — есть функция устанавливающая время:
rtc_set_time(3,0,0); //установить 03:00:00

И есть функция, позволяющая считать текущее время:
rtc_get_time(&h,&m,&s);

т.е. после вызова данной функции в переменных h, m, s будут находиться часы(h), минуты(m) и секунды(s). Осталось вывести их на экран. Уж это мы умеем делать)
Итоговый код будет выглядеть так:

#include <mega8.h>
#include <stdio.h>
#include <delay.h>
// I2C Bus functions
#asm
   .equ __i2c_port=0x18 ;PORTB
   .equ __sda_bit=0
   .equ __scl_bit=1
#endasm
#include <i2c.h>
 
// DS1307 Real Time Clock functions
#include <ds1307.h>
 
// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x12 ;PORTD
#endasm
#include <lcd.h>
 
char lcd_buf[33];
 
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);
      };
}

Собираем и тестируем в протеусе:
shc_proteus

Схема и прошивка

Продолжим модернизировать нашу прошивку. Начнем со следующей задумки: у DS1307 есть выход SQW/OUT, который может генерировать несколько частот. Если настроить этот выход на 1Гц, и подать этот сигнал на вход внешнего прерывания, то получится, что раз в секунду 1307 будет дергать «за хвост» нашу atmega8. Для меги это будет сигналом к тому, что пора обновлять время. Это позволит не нагружать микроконтроллер постоянным обновлением времени, информация о текущем времени будет обновляться ровно раз в секунду.

Добавим в проект внешнее прерывание по низкому уровню (low level) на ножке Int1 и включим подтяжку. Выход DS1307 настроим на частоту 1Гц. Кстати, читать мануалы полезно, нашел интересную особенность — подтягивающие резисторы на ножках SCL, SDA должны быть 3,3k — 4,7k. Учтем это.

shc_proteus_v2

Получившийся код будет выглядеть так:

interrupt [EXT_INT1] 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);
}

Теперь перейдем к следующему вопросу, на сколько эффективно использовать sprintf? Чтобы не разводить пустых разговоров, приведу 2 куска кода, которые выполняют одно и тоже — выводят информацию о времени на дисплей.

Первый вариант, уже нам известный:

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);

Не очень наглядно, но разобраться можно. Как мы их будем сравнивать? Делается это очень просто — запускаем отладчик 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();              //показать информацию о текущем времени
}
};

Объявление самой функции будет выглядеть так:

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 <mega8.h>
 
// I2C Bus functions
#asm
   .equ __i2c_port=0x18 ;PORTB
   .equ __sda_bit=0
   .equ __scl_bit=1
#endasm
#include <i2c.h>
 
// DS1307 Real Time Clock functions
#include <ds1307.h>
 
// Alphanumeric LCD functions
#include <alcd.h>
 
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();              
          }                  
      };
}

Результат:
shc_proteus_v3

Схема и прошивка:

Перейдем к организации меню. Самый главный вопрос состоял в том, как оно все должно выглядеть, т.е. нужно было сформулировать техническое задание(т.з.).
Мне хотелось, чтобы это были отдельные экраны:
-главный экран;
-экран настройки времени;
-экран настройки даты;
-экран настройки будильника.

При этом обойтись четырьмя кнопками — вверх, вниз, влево, вправо. Переход между экранами должен осуществляться кнопками вверх и вниз. Настройка производится на соответствующем экране. Применяемый микроконтроллер 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;
}
 
};

Следующий шаг определение этих функций, изначально я нарисовал статичные названия, вроде 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 изменяем флаг будильника
     {                           
      ...
     }                          
   }

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

Единственное о чем стоит упомянуть это спецсимвол, который выводится на главном экране, когда будильник включен.

shc_proteus_v4

Пример взят из примеров в папке 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); //выводим символ на дисплей
}

Рисовать можно символ 5х7, единичка — пиксел закрашен, ноль — не закрашен. Получился символ колокольчика.

Прошивка

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

Начнем с печатной платы, для этого требуется программа, которая позволяет рисовать печатки. Существует множество подобных программ: P-cad, Altium, Sprint layout… Мне нравится Альтиум, только потому, что для него куча готовых библиотек с элементами, ибо тратить время на набивку собственной библиотеки элементов, на мой взгляд не дело. Общий смысл всех подобных программ одинаков — сначала рисуется электрическая схема.

scha_1307

Далее данные о связях элементов передаются в трассировщик.

exchange_1307

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

pcb_ds1307

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

pcbready_ds1307

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

print_nout

Я долгое время печатал на бумаге с плотностью 180 г/м2 и меня устраивало, но недавно бумага закончилась и в магазине оказалась только 220 г/м2, думаю ладно попробую. В результате я отодрать бумагу так и не смог, ни от платы ни от утюга :D. Много раз слышал, что можно брать бумагу от обычных глянцевых журналов. Думаю ладно попробую, взял первый попавшийся журнал с рекламой мебели и результат меня впечатлил. Так что рекомендую не тратить деньги, а печатать на халявных журналах.

print_foto

Основой служит фольгированный текстолит, который по сути и есть кусок текстолита, с одной стороны покрытый медью.

fr4-foto

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

shkurka

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

jurnalfoto

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

ferum_cl

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

cucl

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

last_pp

В общем обленился в край, все сделал, осталось прикрутить винты :)

88 комментариев: Часы на DS1307 от идеи к реализации

  • Ура !!! Сделал вот так:
    void Nastroika(void)
    {
    switch (set) // switch // подпрограмма опроса
    {
    case 0:
    {
    if (PINB.2==0)
    {delay_ms(10);
    PORTB.0=1; //контроль
    hour++;
    rtc_set_time(hour,min,sec);
    }
    if (hour>23)
    {
    hour=0;
    };
    if (PINB.3==0)
    {delay_ms(10);
    PORTB.0=0; //контроль
    hour—;
    rtc_set_time(hour,min,sec);
    if (hour==255)
    {
    hour=23;}
    };
    break;
    }

  • можно выставить флаг и при выходе из меню например, если флаг установлен, то настраивать часы однократно

  • /*
    * EEPROM_atmega 16.c
    *
    * Created: 10.04.2016 6:06:21
    * Author : User
    */
    Это работает сохраняет значение с RAM вEEPROM потом читаем и выводим на ЛСД .У меня вопрос я немогу записать с кнопки предыдущая программа не понимает.Прогресс есть а дальше труба ?
    #define F_CPU 8000000UL
    #include
    #include
    #include
    #include «lcd.h»
    #include
    #include
    unsigned int data_a,data_b,i,adres;
    unsigned char masiv[4]={100,150,200,250};
    volatile unsigned char menu=0;
    char flag1=0;
    char buf2[10];
    char buf1[10];
    unsigned data1=5000 ;// размер в 2 байта

    unsigned data2=2000 ;// размер в 2 байта

    uint16_t save1 EEMEM ;//размер в 1 байт
    uint16_t save2 EEMEM ;// размер в 2 байта
    // uint32_t save32 EEMEM= 10000;//размер в 3 байта
    asm(«sei»);
    int main(void)
    {
    data_a=eeprom_read_word(& save1);// чтение 2 байт
    data_b=eeprom_read_word(& save2);// чтение 2 байт

    eeprom_write_word(& save1,data1);//запись в память2 байт
    data1=0 ;// размер в 2 байта
    eeprom_write_word(& save2,data2);//запись в память2 байт
    data2=0 ;// размер в 2 байта

    //data_a=eeprom_read_word(& save1);// чтение 2 байт
    //data_b=eeprom_read_word(& save2);// чтение 2 байт

    DDRC=0x00;
    PORTC=0xFF;
    DDRB=0xFF;
    PORTB=0x00;
    lcd_init(LCD_DISP_ON);
    lcd_clrscr();

    while (1)
    {
    if (!(PINC&1<<0))
    {
    flag1=1;
    }
    if ((flag1==1)&&(PINC&1<<0))
    {
    _delay_ms(20);
    menu++;flag1=0;
    }

    if (menu==0)
    {
    lcd_clrscr();
    lcd_puts("AVR");
    _delay_ms(50);

    }
    if (menu==1)
    {
    asm("cli");

    lcd_clrscr();
    //itoa(save16,buf1,10);
    //sprintf(buf1)
    //sprintf(buf1,"a=%4d",save16);
    sprintf(buf1,"a=%4d",data_a);
    lcd_puts(buf1);
    _delay_ms(50);
    asm("sei");
    }
    if (menu==2)
    {
    asm("cli");

    lcd_clrscr();
    sprintf(buf2,"b=%4d",data_b);
    lcd_puts(buf2);
    _delay_ms(50);
    asm("sei");

    }
    if (menu==3)
    {
    menu=0;
    }
    }
    }

  • Ура всё получилось.Спасибо

  • Всех с Великой пасхой.
    Вопрос.КАК сохранить настройки будильника .можно ли использовать
    eeprom_write_word(& save16,hour_byd);//запись в память2 байт
    и
    data=eeprom_read_word(& save16);// чтение 2 байт
    или надо прописывать конкретные ячейки в памяти ds1307

  • Вот из библиотеки
    unsigned char rtc_read(unsigned char address); но это только для чтения ,а как записать?

  • void rtc_write(unsigned char address, unsigned char data)

  • Спасибо буду разбираться. :wink:

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Свежие записи
Последние комментарии
  • Загрузка...
Архивы
Счетчик
Яндекс.Метрика