Микросхема 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 (выбрать в менеджере плат)
Привет. А как с добавлением в данный проект кнопок для установки часов и минут ?
Время устанавливается во время прошивки.