Микросхема DS1307 представляет из себя часики с календарем. Всё :) Минимальная схема подключения состоит из кварца с частотой 32768 Гц и автономного источника питания. Если верить производителю, батарейки 48мАч должно хватить на 10 лет непрерывной работы часов. Обмен данными между DS1307 и внешним миром происходит по последовательному интерфейсу I2C, который как известно использует всего две двунаправленные линии связи (SDA и SCL), при этом на одной I2C шине может находиться до 127 различных устройств. Микросхема DS1307 в терминах I2C всегда является ведомой (Slave), т.е. не может инициировать обмен данными. Ведущим (Master) в нашем случае будет микроконтроллер PIC18F4620, у которого на борту имеется аппаратный I2C.
К ознакомлению настоятельно рекомендуется официальная документация на DS1307. В случае с шиной I2C в первую очередь важны следующие характеристики - максимальная частота тактирования SCL (от этого зависит максимальная скорость передачи данных), физический адрес микросхемы (из 127 возможных) на шине и внутренняя карта памяти (адреса регистров). Так как I2C является синхронным интерфейсом, то имеет значение именно верхняя граница частоты, тогда как фактическая скорость обмена задаётся мастером и может быть любой и даже необязательно постоянной - микроконтроллер может спокойно переключаться на более важные задачи без потери данных на шине. Это серьёзное преимущество синхронных интерфейсов переда асинхронными.
config.h | i2c-hw.c | liquid-crystal.c | hex | picsim.js
Работа с символьным дисплеем описывалась в предыдущем материале. Специфические делали реализации работы с аппаратным I2C для данного микроконтроллера помещены в отдельный файл i2c-hw.c. Чтение данных из DS1307 происходит в функции rtc_r. По спецификации I2C ведомому разрешается придерживать (выставлять в 0) линию SCL, тем самым сигнализируя о неготовности к обмену данными - поэтому функция i2c_idle
ждёт пока не отпустят SCL. С каждым чтением очередного байта i2c_rb
внутренний адрес карты памяти DS1307 инкриминируется, что позволяет последовательно считывать минуты...год без явного указывания их адресов - нужен только начальный адрес секунд. В сети достаточно материала с подробным описанием интерфейса I2C, из которых назначение функций i2c_open
, i2c_start
и т.д. должно быть предельно ясным.
rtc_r.c
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "i2c-hw.c"
static uint8_t bcd2dec(const uint8_t bcd) {
return ((bcd / 16) * 10) + (bcd % 16);
}
// http://microchip.wikidot.com/i2c:sequential-read
void rtc_r(struct tm * const t) {
i2c_open(); // Open I2C module as master, low speed
i2c_idle(); // Ensure I2C is idle
i2c_start(false); // I2C Start condition
i2c_wb(0xD0); // DS1307 slave I2C address + I2C write flag
i2c_idle(); // Ensure I2C is idle
i2c_wb(0); // Sequential read start address
i2c_idle(); // Ensure I2C is idle
i2c_start(true); // Restart condition
i2c_wb(0xD1); // DS1307 slave I2C address + I2C read flag
i2c_idle(); // Ensure I2C is idle
t->tm_sec = bcd2dec(i2c_rb(true)); // secs, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_min = bcd2dec(i2c_rb(true)); // minutes, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_hour = bcd2dec(i2c_rb(true)); // hours, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_wday = i2c_rb(true); // Day of Week, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_mday = bcd2dec(i2c_rb(true)); // Day of Month, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_mon = bcd2dec(i2c_rb(true)); // month, I2C ACK condition
i2c_idle(); // Ensure I2C is idle
t->tm_year = bcd2dec(i2c_rb(false)); // year, I2C NACK condition (end of sequential read)
t->tm_yday = t->tm_isdst = -1; // less than zero if the information is not available
i2c_stop_and_close(); // I2C Stop condition, then close I2C module
}
main.c
/*
xc8 --chip=18f4620 main.c
*/
#define _XTAL_FREQ 25e4
#include <xc.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include "config-4620.h"
#include "liquid-crystal.c"
#include "rtc_r.c"
// http://microchip.wikidot.com/faq:29
void putch(const uint8_t byte) {
lcd_dat(byte);
}
int main() {
// lcd
PORTD = TRISD = 0;
PORTE = TRISE = 0;
// i2c
TRISC3 = TRISC4 = 1;
lcd_init();
lcd_no_cursor();
lcd_no_blink();
while(1) {
const char * const daysOfWeek[7] = {
"Sun.", "Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat."
};
struct tm t;
lcd_home();
rtc_r(&t);
printf("%02d:%02d:%02d %s",
t.tm_hour, t.tm_min, t.tm_sec,
t.tm_wday >= 0 && t.tm_wday < 7 ? daysOfWeek[t.tm_wday] : ":(");
lcd_set_cursor(0, 1);
printf("%02d/%02d/%02d", t.tm_mday, t.tm_mon, t.tm_year);
}
return 0;
}
Текущую время и дату можно конечно же выставлять - адреса внутренней карты памяти DS1307 для записи такие же как и на чтение. Если скорость обмена данными не имеет особого значения, то ничто не мешает заменить аппаратный I2C (файл i2c-hw.c) программной реализацией (программно считывать/выставлять ноги микроконтроллера SCL/SDA) - это также проверялось и даже будет работать в симуляции PICSim.js.
Далее сопрограммы.