ATtiny2313 + DS1302 (Arduino IDE)

Микросхема DS1302 содержит часы реального времени с календарем и 31 байт статического ОЗУ. Она общается с микропроцессором через простой последовательный интерфейс. Информация о реальном времени и календаре представляется в секундах минутах, часах, дне, дате, месяце и годе. Если текущий месяц содержит менее 31 дня, то микросхема автоматически определит количество дней в месяце с учетом высокосности текущего года. Часы работают или в 24-часовом или 12-часовом формате с индикатором AM/PM (до полудня/ после полудня). Подключение DS1302 к микроконтроллеру упрощено за счет синхронной последовательной связи. Для этого требуется только 3 провода: (1) RST (сброс), (2) I/O (линия данных) и (3) SCLK (синхронизация последовательной связи). Данные могут передаваться по одному байту или последовательностью байтов до 31.

Установка и считывание времени производится при помощи регистров согласно следующей таблице:

Запись данных в регистры

Запись данных осуществляется в несколько этапов:

  • понимаем линию CE (разрешаем работу модуля на шине);
  • отправляем 1-й байт (адрес нужного нам регистра), младшим битом вперёд, в режиме mode=0;
  • отправляем 2-й байт (данные для записи в регистр), младшим битом вперёд, в режиме mode=0;
  • прижимаем линию CE (запрещаем работу модуля на шине).

mode=0 — состояние c линии данных читается по переднему фронту синхроимпульса на линии CLK;

Чтение данных из регистров

Чтение данных осуществляется в несколько этапов:

  • понимаем линию CE (разрешаем работу модуля на шине);
  • отправляем байт (адрес нужного нам регистра), младшим битом вперёд, в режиме mode=0;
  • получаем байт (данные из регистра), младшим битом вперёд, в режиме mode=1;
  • прижимаем линию CE (запрещаем работу модуля на шине).

mode=1 — состояние c линии данных читается по заднему спаду синхроимпульса на линии CLK;

Для работы с модулем DS1302 под управлением Arduino существуют несколько библиотек, но при использовании  микроконтроллера ATtiny2313 использование библиотек не целесообразно из-за малого объема памяти (2 кБ). Поэтому я написал несколько функций управления часами реального времени, которые позволяют считывать и записывать текущее время. Эти функции оптимально подходят для управления часами реального времени DS1302 под управлением Attiny2313.

Функция записи

#define CLK 2 // PB2
#define DAT 3 // PB3
#define CE  4 // PB4
void WriteDs(byte reg, byte data) {
  DDRB |= (1 << DAT); PORTB &= ~(1 << CLK) | (1 << CE) | (1 << DAT);del10();
  PORTB |= (1 << CE);
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((reg >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();} 
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((data >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();}
    PORTB &= ~(1 << CE) | (1 << CLK) | (1 << DAT); del10();
}

void del10() {delayMicroseconds(10);}

Управление функцией очень простое, например установка времени секунд:

WriteDs(0x80,(second/10<<4)+second%10);

0x80 — адрес регистра

READ WRITE 7 6 5 4 3 2 1 0
0x81 0x80 CH  десятки секунд единицы секунд

CH (Clock Halt) — флаг отключения часов: значение «1» — останавливает часы, значение «0» — запускает.

Так как десятки секунд и единицы секунд в регистре разделены, то необходимо секунды так же разделить на десятки и единицы, переместить их в байте в нужное место:

байт установки секунд = (second/10<<4)+second%10

Для простоты использования можно воспользоваться функцией корректировки времени:

void set_time(byte years, byte days, byte monts, byte datas, byte hours ,byte minute, byte second){
    WriteDs(0x8E, 0b00000000);
    WriteDs(0x80,(second/10<<4)+second%10);
    WriteDs(0x82,(minute/10<<4)+minute%10);
    WriteDs(0x84,(hours/10<<4)+hours%10);
    WriteDs(0x86,(datas/10<<4)+datas%10);
    WriteDs(0x88,(monts/10<<4)+monts%10);
    WriteDs(0x8A,days);
    WriteDs(0x8C,(years/10<<4)+years%10);
  }

Пример использования:

// set_time(21,2,3,8,14,20,0);// год 00-99, ДН 1-7 (1=ВС), месяц 1-12, дата 1-31, час 0-23, минуты 0-59, секунды 0-59

В эту функцию можно разместить в секции setup() , для корректировки времени раскомментируйте сроку, загрузите скетч, а затем закомментируйте сроку и снова загрузите скетч.

Функция чтения

#define CLK 2 // PB2 
#define DAT 3 // PB3 
#define CE 4 // PB4

byte ReadDs(byte reg) {  // READ_REG
  DDRB |= (1 << CLK) | (1 << CE);
  PORTB &= ~(1 << CE) | (1 << CLK);
  byte data = 0;
  DDRB |= (1 << DAT); PORTB &= ~(1 << CLK) | (1 << CE) | (1 << DAT);del10();
  PORTB |= (1 << CE);
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((reg >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();} 
    DDRB &= ~(1 << DAT);
  for (int i = 0; i < 8; i++) {
    PORTB |= (1 << CLK);  del10();PORTB &= ~(1 << CLK); del10();
    data += (((PINB >> DAT) & 1) << i); del10();} 
  DDRB |= (1 << DAT); PORTB &= ~(1 << CE) | (1 << CLK) | (1 << DAT);del10();
  return data;
}

void del10() {delayMicroseconds(10);}

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

Управление функцией так же очень простое, например чтение секунд:

byte sec = ((ReadDs(0x81) & 0x0F) + ((ReadDs(0x81) & 0x70) >> 4) * 10);

0х81 — адрес регистра

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

Следующий код позволяет получать все значения часов реального времени:

  byte sec =  ((ReadDs(0x81) & 0x0F) + ((ReadDs(0x81) & 0x70) >> 4) * 10);
  byte min =  ((ReadDs(0x83) & 0x0F) + ((ReadDs(0x83) & 0x70) >> 4) * 10);
  byte hour = ((ReadDs(0x85) & 0x0F) + ((ReadDs(0x85) & 0x70) >> 4) * 10);
  byte data = ((ReadDs(0x87) & 0x0F) + ((ReadDs(0x87) & 0x70) >> 4) * 10);
  byte mont = ((ReadDs(0x89) & 0x0F) + ((ReadDs(0x89) & 0x70) >> 4) * 10);
  byte day =  (ReadDs(0x8B) & 0x0F);
  byte year = ((ReadDs(0x8D) & 0x0F) + ((ReadDs(0x8D) & 0x70) >> 4) * 10);

Эти переменные уже можно выводить на индикатор.

На примере этих функций можно собрать простые часы, в качестве индикатора будет использован модуль индикатора TM1637 который представляет собой 4-х разрядный семисегментный дисплей на базе драйвера TM1637.

Схема часов

Перед загрузкой скетча рекомендую ознакомится со статьей — ATtiny2313 + Arduino IDE

Часы работают следующим образом — с 0 по 25 и с 30 по 50 секунду индикатор показывает время, точка мигает в такт секундам, далее с 25 по 30 и с 50 по 55 секунду индикатор показывает число и месяц, а с 55 до 59 секунды индикатор показывает ход секунд.

#define CLK 2 // PB2
#define DAT 3 // PB3
#define CE  4 // PB4
 
byte i1,bb;
 
void setup() {
    cli();  
  TCCR1A = 0;   
  TCCR1B = 0;   
  OCR1A = 18750; // 0.1 s
  TCCR1B |= (1 << WGM12); 
  TCCR1B |= (1 << CS11) | (1 << CS10);  // 64  
  TIMSK |= (1 << OCIE1A);  
    sei(); 
 // set_time(21,2,3,8,14,20,0);// год 00-99, ДН 1-7 (1=ВС), месяц 1-12, дата 1-31, час 0-23, минуты 0-59, секунды 0-59
}
 
 
void loop() {
  byte sec =  ((ReadDs(0x81) & 0x0F) + ((ReadDs(0x81) & 0x70) >> 4) * 10);
  byte min =  ((ReadDs(0x83) & 0x0F) + ((ReadDs(0x83) & 0x70) >> 4) * 10);
  byte hour = ((ReadDs(0x85) & 0x0F) + ((ReadDs(0x85) & 0x70) >> 4) * 10);
  byte data = ((ReadDs(0x87) & 0x0F) + ((ReadDs(0x87) & 0x70) >> 4) * 10);
  byte mont = ((ReadDs(0x89) & 0x0F) + ((ReadDs(0x89) & 0x70) >> 4) * 10);
  byte day =  (ReadDs(0x8B) & 0x0F);
  byte year = ((ReadDs(0x8D) & 0x0F) + ((ReadDs(0x8D) & 0x70) >> 4) * 10);
  if(i1 <= 5){bb = 2;}else{bb = 0;}
 
  if(sec >= 0 && sec <= 25){print_time(hour *100 + min, bb, 7, 0);}
  if(sec > 25 && sec <= 30){print_time(data *100 + mont, 0, 7, 0);}
  if(sec > 30 && sec <= 50){print_time(hour *100 + min, bb, 7, 0);}
  if(sec > 50 && sec <= 55){print_time(data *100 + mont, 0, 7, 0);}
  if(sec > 55){print_time(sec, 0, 7, 1);}
}
 
 
void tm_dec(byte dig) {
  for (int i = 0; i < 8; i++) {
    DDRB |= (1 << 0); del();
    if (dig & 0x01)
      DDRB &= ~(1 << 1);
    else
      DDRB |= (1 << 1); del();
    DDRB &= ~(1 << 0); del();
    dig = dig >> 1;
  }
  DDRB |= (1 << 0);
  DDRB &= ~(1 << 1); del();
  DDRB &= ~(1 << 0); del();
 
  if (((PINB >> 1) & 1) == 0)
    DDRB |= (1 << 1); del();
  DDRB |= (1 << 0); del();
}
 
void tm_stop() {
  DDRB |= (1 << 1); del();
  DDRB &= ~(1 << 0); del();
  DDRB &= ~(1 << 1); del();
}
 
void tm_start() {
  DDRB |= (1 << 1); del();
}
 
void print_time(int t, byte pd_t, int br, bool mn) {
  tm_start(); tm_dec(0b10001000 + br); //tm_stop();tm_start();
  tm_dec(0x40); tm_stop(); tm_start();
 
  int data0,data1;
  if(mn==1){data0 = 10;data1 = 10;}
  else{data0 = t / 1000;data1 = t / 100 % 10;}
 
  int data2 = t / 10 % 10;
  int data3 = t % 10;
 
  for (byte n = 0; n < 4; n++) {
    int data;
    switch (n) {
      case 0: data = data0; break;
      case 1: data = data1; break;
      case 2: data = data2; break;
      case 3: data = data3; break;
    }
 
    switch (data) {  // XGFEDCBA
      case 0:  data = 0b00111111; break;    // 0
      case 1:  data = 0b00000110; break;    // 1
      case 2:  data = 0b01011011; break;    // 2
      case 3:  data = 0b01001111; break;    // 3
      case 4:  data = 0b01100110; break;    // 4
      case 5:  data = 0b01101101; break;    // 5
      case 6:  data = 0b01111101; break;    // 6
      case 7:  data = 0b00000111; break;    // 7
      case 8:  data = 0b01111111; break;    // 8
      case 9:  data = 0b01101111; break;    // 9
      case 10: data = 0b00000000; break;    // пусто
    }
 
    if (n == 0) {
      data0 = data;
    }
    if (n == 1) {
      data1 = data;
    }
    if (n == 2) {
      data2 = data;
    }
    if (n == 3) {
      data3 = data;
    }
  }
  switch (pd_t) {
    case 1 : data2 = data2 + 0b10000000; break;
    case 2 : data1 = data1 + 0b10000000; break;
    case 3 : data0 = data0 + 0b10000000; break;
  }
  tm_dec(0xC0); tm_dec(data0); tm_dec(data1); tm_dec(data2); tm_dec(data3); tm_stop();
}
 
void del() {delayMicroseconds(200);}
void del10() {delayMicroseconds(10);}
 
 
byte ReadDs(byte reg) {  // READ_REG
  DDRB |= (1 << CLK) | (1 << CE);
  PORTB &= ~(1 << CE) | (1 << CLK);
  byte data = 0;
  DDRB |= (1 << DAT); PORTB &= ~(1 << CLK) | (1 << CE) | (1 << DAT);del10();
  PORTB |= (1 << CE);
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((reg >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();} 
    DDRB &= ~(1 << DAT);
  for (int i = 0; i < 8; i++) {
    PORTB |= (1 << CLK);  del10();PORTB &= ~(1 << CLK); del10();
    data += (((PINB >> DAT) & 1) << i); del10();} 
  DDRB |= (1 << DAT); PORTB &= ~(1 << CE) | (1 << CLK) | (1 << DAT);del10();
  return data;
}
 
void WriteDs(byte reg, byte data) {
  DDRB |= (1 << DAT); PORTB &= ~(1 << CLK) | (1 << CE) | (1 << DAT);del10();
  PORTB |= (1 << CE);
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((reg >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();} 
  for (int i = 0; i < 8; i++) {
    PORTB &= ~(1 << CLK); del10();
    if (((data >> i) & 1) == 1){PORTB |= (1 << DAT);} else {PORTB &= ~(1 << DAT);}del10();
    PORTB |= (1 << CLK); del10();}
    PORTB &= ~(1 << CE) | (1 << CLK) | (1 << DAT); del10();
}
 
void set_time(byte years, byte days, byte monts, byte datas, byte hours ,byte minute, byte second){
    WriteDs(0x8E, 0b00000000);
    WriteDs(0x80,(second/10<<4)+second%10);
    WriteDs(0x82,(minute/10<<4)+minute%10);
    WriteDs(0x84,(hours/10<<4)+hours%10);
    WriteDs(0x86,(datas/10<<4)+datas%10);
    WriteDs(0x88,(monts/10<<4)+monts%10);
    WriteDs(0x8A,days);
    WriteDs(0x8C,(years/10<<4)+years%10);
  }
 
ISR(TIMER1_COMPA_vect){
     i1++;
     if(i1 > 9){i1 = 0;}
     }

Скетч использует 1376 байт (67%) памяти устройства. Всего доступно 2048 байт.
Глобальные переменные используют 11 байт (8%) динамической памяти, оставляя 117 байт для локальных переменных. Максимум: 128 байт.

Arduino IDE 1.8.9 | Плата для прошивки версии 1.2.5 (выбрать в менеджере плат)

Comments

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

Войти с помощью: