В первой части статьи мы изучали схему подключения и написали простенький код в CodeVisionAVR.
Сейчас пришло время заглянуть под капот этих библиотек и сделать всё по своему в AtmelStudio.
Скажу честно, при написании кода очень сильно опиралась на эти две статьи, так что если вы с ними уже знакомы, здесь вы найдете не так уж много нового.
Вот мой проект с демонстрацией основных функций часов. Функции и объявления для UART, TWI (I2C) и DS1307 разделены по отдельным "библиотекам" для удобства чтения и редактирования.
Как нам известно из предыдущих статей про RTC DS1307, они подключаются к МК по интерфейсу I2C. Поэтому начнем с него.
Кратко про интерфейс I2C (TWI)
Это синхронный интерфейс с двумя двунаправленными линиями: последовательной линией данных (SDA) и последовательной линией тактирования (SCL).
Есть главное устройство - ведущий (master) и ведомые устройства (slave). Начинает обмен всегда ведущий, он же генерирует тактовый сигнал.
Линией данных SDA могут управлять и ведущие, и ведомые устройства, в зависимости от необходимости.
У большинства устройств (и у DS1307 тоже) скорость обмена по данному интерфейсу составляет 100 кбит/с.
Рассмотрим процесс обмена данными между устройствами на примере DS1307.
1. Ведущий формирует состояние СТАРТ (START): при логической единице (1) на линии SCL он создает переход сигнала линии SDA из единицы (1) в ноль (0).
2. После СТАРТа ведущий опускает линию SCL в ноль и выставляет на линию SDA старший бит (MSB) первого байта сообщения. Обычно это адрес ведомого устройства, с которым будет обмен данных. В большинстве случаев используются 7-битные адреса (как у DS1307), но встречаются и 10-битные адреса, начинающиеся с бит 11110.
3. Далее посылается бит, который определяет, будет ли ведомый принимать (0) данные с мастера или передавать (1) данные ему.
4. Далее приемник данных (если происходит запись в ведомого, то ведомый; если принимает данные мастер - то мастер) должен подтвердить (ACKnowledge) получение сообщения. Он должен притянуть линию SDA к нулю, если успешно принял байт.
5. Далее можно передавать неограниченное количество байт с данными, и каждый прием данных должен подтверждаться приемником. Если данные принимает ведущий, он должен сообщить об окончании передачи ведомому-передатчику путём неподтверждения (NACK) последнего байта, чтобы тот освободил линию SDA.
6. Завершается обмен данными тем, что ведущий формирует состояние СТОП (STOP): при логической единице (1) на линии SCL он создает переход сигнала линии SDA из ноля (0) в единицу (1). Также можно и "перезапустить" (RESTART) обмен, создав состояние СТАРТ.
Работа с аппаратным интерфейсом I2C AVR
Рассматривать буду на примере ATmega328, но у большинства МК семейства AVR настройки примерно те же, смотрите даташиты.
Для настройки и управления интерфейсом I2C в микроконтроллерах AVR используются следующие регистры: TWBR, TWSR, TWCR, TWDR, TWAR, TWAMR.
Последние два используются для режима ведомого, в данной статье рассматривать их не буду, поскольку для работы с RTC МК нужен только режим ведущего.
1. TWBR (TWI Bit Rate Register): регистр настройки частоты (скорости) шины.
Частота работы интерфейса рассчитывается по формуле:
TWPS1 и TWPS0 - это биты регистра TWSR.
2. TWSR (TWI Status Register): В первых двух битах TWPS0 и TWPS1 сохраняется предделитель частоты интерфейса, третий бит нигде не используется (зарезервирован), а в остальные записывается статус выполнения команды (об этом чуть позже).
Ниже даны возможные значения предделителя частоты интерфейса.
Для примера посчитаем необходимое значение TWSR и TWBR для частоты интерфейса Fscl = 100 кГц и частоты работы МК Fcpu = 16 МГц:
Как видно, число и так небольшое, поэтому деление частоты не требуется, оставим TWPS0 = TWPS1 = 0. А TWBR, соответственно, должен быть равен 72.
Набросала такую функцию для настройки I2C исходя из частоты работы интерфейса и МК (см. twi.c в репе):
uint8_t twi_init(uint32_t cpu_frequency, uint32_t frequency) {
uint32_t twbr = (cpu_frequency/frequency - 16)/2;
// Делитель не требуется
if (twbr <= 0xFF) {
TWSR = (0<<TWPS1)|(0<<TWPS0);
TWBR = twbr;
}
// Делитель 4
else if (twbr <= 0x3FC) {
TWSR = (0<<TWPS1)|(1<<TWPS0);
TWBR = twbr>>2; // Делим на 4
}
// Делитель 16
else if (twbr <= 0xFF0) {
TWSR = (1<<TWPS1)|(0<<TWPS0);
TWBR = twbr>>4; // Делим на 16
}
// Делитель 64
else if (twbr <= 0x3FC0) {
TWSR = (1<<TWPS1)|(1<<TWPS0);
TWBR = twbr>>6; // Делим на 64
}
// Ничего не получилось, статус 0
else {
return 0;
}
return 1;
}
Статусы команд I2C
В биты TWS3...TWS7 регистра TWSR после каждого действия (старта, чтения, записи) сохраняются код его выполнения. Для получения статуса нужно наложить на значение в TWSR маску 0b11111000 (или 0xF8 в шестнадцатеричном формате), чтобы убрать первые три бита.
Как видите, функции библиотеки twi.с возвращают коды состояний шины, а в заголовочном файле добавлен enum twi_status со всеми статусами для интерфейса I2C у МК AVR.
Я не стала делать в библиотеке DS1307 обработку статусов для большей лаконичности демонстрируемого кода. Но по желанию их можно проверять, просто сохраняя возвращаемые функциями twi, twi_transmit и twi_receive коды и сравнивая со значениями из enum.
3. TWCR (TWI Control Register): этот регистр управляет работой шины.
Рассмотрим назначение всех бит:
TWINT (TWI Interrupt Flag): бит-флаг прерывания. МК устанавливает его в единицу, когда интерфейс "окончил" работу и ожидает дальнейших действий. Если включено прерывание по I2C, начинается его обработка. Сбрасывается он программно, для этого нужно записать в него единицу.
TWEA (TWI Enable Acknowledge Bit): бит включения подтвержденияЕсли установить его равным единице, то интерфейс будет формировать сигнал подтверждения (ACK), когда это требуется. Происходит это когда:
- ведомое устройство получило свой адрес
- ведомое устройство получило вызов
- ведущее или ведомое устройство получило байт данных
TWSTA (TWI START Condition Bit): бит состояния СТАРТ Если необходимо сформировать состояние СТАРТ (стать ведущим на линии), то записываем в этот бит единицу. Если линия занята каким-то другим ведущим, то интерфейс будет ожидать от него состояния СТОП, и уже затем сгенерирует свой СТАРТ. При последующих командах бит надо очистить программно, записав в него ноль.
TWSTO (TWI STOP Condition Bit): бит состояния СТОПЕсли необходимо сформировать состояние СТОП (освободить линию), то записываем в этот бит единицу. После установки состояния бит автоматически очистится.
TWWC (TWI Write Collision Flag): бит-флаг конфликта записи.Он устанавливается в единицу, когда при записи в регистр данных TWDR бит TWINT еще был равен нулю, то есть регистр данных не был готов к приему данных. При успешной записи в регистр данных флаг автоматически очищается в ноль.
TWEN (TWI Enable Bit): бит разрешения работы.Если установить его равным единице, модуль интерфейса начнет выполнять команду согласно остальным бита в регистре TWCR.
TWIE (TWI Interrupt Enable): бит разрешения прерываний.Если записать в него единицу, будут разрешены прерывания по I2C.
Для управления модулем I2C можно использовать такую функцию. Значения для действий TWI_x прописаны в заголовочном файле twi.h.
uint8_t twi(uint8_t action) {
switch(action) {
case TWI_START:
case TWI_RESTART:
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
break;
case TWI_STOP:
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
break;
case TWI_TRANSMIT:
case TWI_RECEIVE_NACK:
TWCR = (1<<TWINT)|(1<<TWEN);
break;
case TWI_RECEIVE_ACK:
TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
break;
default:
break;
}
if (action != TWI_STOP) {
while (!(TWCR & (1<<TWINT)));
}
uint8_t status = TWSR & 0xF8; // Статус выполнения (0xF8 = 11111000)
return status;
}
Как видите, во всех командах устанавливаются TWINT и TWEN, чтобы сбросить прерывания и начать выполнение задания.
Команды START и RESTART чисто технически ничем не отличаются, просто устанавливаем бит TWSTA, и модуль интерфейса генерирует состояние СТАРТ.
Команда STOP схожа с ними, только устанавливаем мы бит TWSTO, чтобы сгенерировать состояния СТОП соответственно.
Команды передачи TRANSMIT и приема данных без подтверждения RECEIVE_NACK тоже одинаковы - просто запускаем интерфейс. "Направление" записи в регистр данных TWDR будет зависеть от предыдущих команд (заставили ли мы ведомого работать в режиме приема или передачи).
Команда приема с подтверждением TWI_RECEIVE_ACK отличается только установкой бита TWEA, собственно, для включения подтверждения.
4. TWDR (TWI Data Register): регистр данных. Перед передачей данных в него необходимо "положить" байт с информацией; в него же "кладутся" полученные при приеме байты.
Примеры использования данного регистра передачи и приема данных:
uint8_t twi_transmit(uint8_t data) {
TWDR = data;
return twi(TWI_TRANSMIT);
}
uint8_t twi_receive(uint8_t * data, uint8_t with_ack) {
uint8_t status = twi(with_ack > 0 ? TWI_RECEIVE_ACK : TWI_RECEIVE_NACK);
*data = TWDR;
return status;
}
Данные можно писать и читать с регистра, когда МК устанавливает бит-флаг TWINT в регистре TWCR равным единице. Это означает, что значение сейчас получено полностью. Поэтому в функции twi в конце и стоит "ожидание" его установки.
while (!(TWCR & (1<<TWINT)));
Работа с регистрами DS1307
Так, с I2C вроде разобрались, теперь посмотрим на регистры часов DS1307 и на протокол взаимодействия.
Установка даты и времени, а также настроечных бит происходит путем записи в соответствующие регистры нужных нам значений. Далее часы будут сами их изменять с течением времени.
Получаем дату и время также напрямую из регистров.
Данные о дате и времени хранятся в двоично-десятичном формате (BCD) - в каждых четырех байтах хранится число от 0 до 9, соответствующее десятичному разряду числа: единицы, десятки и т.д.
Например, в регистре года в битах 0-3 хранятся единицы числа, а в битах 4-7 - десятки.
Сделала наивные функции для перевода "двухзначных" двоично-десятичные чисел в десятичный формат и для перевода из десятичных чисел в двоично-десятичные.
uint8_t to_BCD(uint8_t n) {
return ((n/10) << 4) + n%10;
}
uint8_t from_BCD(uint8_t n) {
return 10 * (n >> 4) + (n & 0b1111);
}
Теперь рассмотрим доступные методики взаимодействия с часами по интерфейсу I2C.
Запись в регистры
1. Взаимодействие начинается с того, что ведущий (МК) создает состояние СТАРТ.
2. Затем ведущий отправляет адрес ведомого (DS1307) 1101000 и бит направления (чтение/запись), для записи он равен 0.
3. Если ведомый получил и прочитал свой адрес, то он должен подтвердить (A) получение адреса, опустив линию SDA в ноль.
4. После ответа ведомого ведущий передает адрес, с которого начнется запись данных. После получения и подтверждения DS1307 установит "внутренний" указатель на данный адрес в памяти.
5. Затем МК может либо не передавать данные (если не передавать данных, то это будет просто команда "установка указателя"), либо передать некоторое количество байт с подтверждением от ведомого. При получении каждого байта DS1307 увеличивает указатель адреса на единицу, переходя к следующему регистру.
6. Для окончания передачи данных МК должен создать состояние СТОП.
Ниже представлен код, реализующий передачу одного байта данных в один регистр часов:
void RTC_set_value(uint8_t address, uint8_t data) {
/* Сформировать состояние СТАРТ */
twi(TWI_START);
/* Выдать SLA-W - ведомый в режиме приемника */
twi_transmit((DS1307_ADR<<1)|0);
/* Передать адрес регистра часов */
twi_transmit(address);
/* При передаче указателя передается только адрес без стоп состояния */
if (data != RTC_WRITE_POINTER) {
/* Передать данные - здесь можно послать подряд несколько значений */
twi_transmit(data);
/* Сформировать состояние СТОП */
twi(TWI_STOP);
}
}
Чтение из регистров
1. Взаимодействие начинается с того, что ведущий (МК) создает состояние СТАРТ.
2. Затем ведущий отправляет адрес ведомого (DS1307) 1101000 и бит направления (чтение/запись), для чтения он равен 1.
3. Если ведомый получил и прочитал свой адрес, то он должен подтвердить (A) получение адреса, опустив линию SDA в ноль.
4. После этого DS1307 начинает передавать МК байты с данными, ожидая от него подтверждения по каждому байту. Ведомый передает регистры, начиная с того, на котором сейчас стоит указатель.
При передаче каждого байта DS1307 увеличивает указатель адреса на единицу, переходя к следующему регистру.
5. Ведущий должен подтверждать получение каждого байта (A), опуская линию SDA в ноль.
Если же ведущий хочет закончить получение данных, он не отвечает на последнее сообщение, "не трогая" шину (~A).
6. Для окончания "общения" МК должен создать состояние СТОП.
P.S.
Чтобы установить указатель на желаемый регистр в часах, МК должен сперва послать адрес DS1307 с битом записи 0, передать адрес нужного регистра, повторно создать состояние СТАРТ, и уже потом инициировать чтение данных по описанному выше алгоритму.
Ниже представлен код, реализующий получение одного байта данных с текущего регистра часов:
void RTC_get_value(uint8_t * data) {
/* Сформировать состояние СТАРТ */
twi(TWI_START);
/* Выдать пакет SLA-R - ведомый в режиме передатчика */
twi_transmit((DS1307_ADR<<1)|1);
/* считываем данные с подтверждением - можно несколько байт */
twi_receive(data, 1);
/* считываем данные без подтверждения - заканчиваем прием */
twi_receive(data, 0);
/* Сформировать состояние СТОП */
twi(TWI_STOP);
}
Теперь обсудим основные регистры и приведем примеры кода с их "использованием".
Установка даты
С установкой даты всё достаточно просто:
1. Переводим десятичные значения года, месяца и дня в двоично-десятичный формат
2. СТАРТуем общение
3. Отправляем адрес DS1307 с битом записи
4. Устанавливаем указатель регистра на адрес 0x03 - регистр дня недели
5. Присылаем в часы по очереди день недели, день, месяц и год
6. Заканчиваем общение состоянием СТОП
void RTC_set_date(Date date) {
/* Перевести в BCD */
date.year = to_BCD(date.year);
date.month = to_BCD(date.month);
date.day = to_BCD(date.day);
/* Сформировать состояние СТАРТ */
twi(TWI_START);
/* Выдать SLA-W - ведомый в режиме приемника */
twi_transmit((DS1307_ADR<<1)|0);
/* Передать адрес регистра, с которого начинаем запись */
twi_transmit(RTC_DAY_WEEK_ADR);
/* Передать данные */
twi_transmit(date.day_week);
twi_transmit(date.day);
twi_transmit(date.month);
twi_transmit(date.year);
/* Сформировать состояние СТОП */
twi(TWI_STOP);
}
Чтение даты
Чтение даты тоже не должно вызывать больших проблем:
1. СТАРТуем общение
2. Устанавливаем указатель регистра на адрес 0x03 - регистр дня недели
3. Заново СТАРТуем общение
4. Отправляем адрес DS1307 с битом чтения
5. Начинаем считывать по очереди день недели, день, месяц и год.
Год принимаем без подтверждения со стороны ведущего, чтобы завершить чтение.
6. Заканчиваем общение состоянием СТОП
7. Переводим полученные данные из двоично-десятичного формата в десятичный
void RTC_get_date(Date * date) {
/* Установить указатель регистра на день недели */
RTC_set_value(RTC_DAY_WEEK_ADR, RTC_WRITE_POINTER);
/* Сформировать состояние РЕСТАРТ */
twi(TWI_RESTART);
/* Выдать пакет SLA-R - ведомый в режиме передатчика */
twi_transmit((DS1307_ADR<<1)|1);
/* Считывать данные с подтверждением, кроме последнего байта */
twi_receive(&date->day_week, 1);
twi_receive(&date->day, 1);
twi_receive(&date->month, 1);
twi_receive(&date->year, 0); // завершающий прием без подтверждения
/* Сформировать состояние СТОП */
twi(TWI_STOP);
/* Преобразовать из BCD в десятичное число */
date->day = from_BCD(date->day);
date->month = from_BCD(date->month);
date->year = from_BCD(date->year);
}
Установка времени
Часы DS1307 могут храниться в 12-часовом или 24-часовом формате.
Если шестой бит регистра часов установлен в 1, то часы будут работать в 12-часовом формате, а пятый бит будет обозначать время дня: 0 - AM (до полудня), 1 - PM (после полудня).
Если же шестой бит будет равен 0, то часы будут работать в 24-часовом формате, а пятый бит будет битом разряда десятков часов.
При изменении формата часов необходимо перезаписывать значения часов.
Итак, алгоритм записи времени будет следующим:
1. Переводим десятичные значения часов, минут и секунд в двоично-десятичный формат
2. Если необходимо использовать 12-часовой формат, добавляем к значению для регистра часов пятый (0 - AM / 1 - PM) и шестой биты (1)
3. СТАРТуем общение
4. Отправляем адрес DS1307 с битом записи
5. Устанавливаем указатель регистра на адрес 0x00 - регистр секунд
6. Присылаем в часы по очереди секунды, минуты, часы
7. Заканчиваем общение состоянием СТОП
void RTC_set_time(Time time) {
/* Перевести в BCD */
time.seconds = to_BCD(time.seconds);
time.minutes = to_BCD(time.minutes);
if (time.time_format == 12) {
time.hours = (time.hours%12 == 0 ? 12U : time.hours%12); // подрежем
// Добавим биты для 12-часового формата
time.hours = to_BCD(time.hours)
| (1<<TIME_FORMAT) | (time.am_pm << AM_PM);
} else { // формат 24 часа
time.hours = to_BCD(time.hours); // настроечные биты нулевые
}
/* Сформировать состояние СТАРТ */
twi(TWI_START);
/* Выдать SLA-W - ведомый в режиме приемника */
twi_transmit((DS1307_ADR<<1)|0);
/* Передать адрес регистра, с которого начинаем запись */
twi_transmit(RTC_SEC_ADR);
twi_transmit(time.seconds);
twi_transmit(time.minutes);
twi_transmit(time.hours);
/* Сформировать состояние СТОП */
twi(TWI_STOP);
}
Чтение времени
При чтении времени также необходимо помнить про формат часов.
Так, алгоритм чтения времени будет следующим:
1. СТАРТуем общение
2. Устанавливаем указатель регистра на адрес 0x00 - регистр секунд
3. Заново СТАРТуем общение
4. Отправляем адрес DS1307 с битом чтения
5. Начинаем считывать по очереди секунды, минуты и часы. Часы принимаем без подтверждения со стороны ведущего, чтобы завершить чтение.
6. Заканчиваем общение состоянием СТОП
7. Минуты и секунды переводим из двоично-десятичного формата в десятичный.
Если шестой бит установлен (используется 12-часовой формат), вычисляем значение часов по битам 0-4 и определяем время дня (AM/PM) по пятому биту.
Если же используется 24-часовой формат, вычисляем десятичное значение часов по битам 0-5 регистра.
void RTC_get_time(Time * time) {
/* Устанавливаем указатель регистра на секунды */
RTC_set_value(RTC_SEC_ADR, RTC_WRITE_POINTER);
/* Сформировать состояние РЕСТАРТ */
twi(TWI_RESTART);
/* Выдать пакет SLA-R - ведомый в режиме передатчика */
twi_transmit((DS1307_ADR<<1)|1);
/* Считать данные с подтверждением, кроме последнего байта */
twi_receive(&time->seconds, 1);
twi_receive(&time->minutes, 1);
twi_receive(&time->hours, 0); // завершающий прием без подтверждения
/* Сформировать состояние СТОП */
twi(TWI_STOP);
/* Преобразовать из BCD в десятичное число */
time->seconds = from_BCD(time->seconds & SECONDS_MASK);
time->minutes = from_BCD(time->minutes);
time->time_format = (time->hours & (1<<TIME_FORMAT) ? 12 : 24);
if (time->time_format == 24) {
time->am_pm = (from_BCD(time->hours) > 12U ? PM: AM);
} else {
time->am_pm = (time->hours & (1<<AM_PM) ? PM : AM); // В режиме 12 часов бит 5 дает 1 для PM и 0 для AM
time->hours &= HOUR_12_MASK; // уберем лишние биты
}
time->hours = from_BCD(time->hours);
}
Настройка OUT и SQWE
Для настройки выхода OUT/SQWE используется регистр управления с адресом 0x07.
1. Бит OUT используется для настройки выхода OUT, если не используется выход генератора меандра.
Когда бит SQWE = 0, логический уровень на выходе OUT будет равен состоянию бита OUT: записали 0 - будет логический ноль, записали 1 - будет логическая единица.
При первичном включении питания бит OUT устанавливается в 0.
2. Бит SQWE (Square-Wave Enable) позволяет вывести на выход SQWE сигнал с внутреннего генератора меандра часов, если его установить равным 1. Частоту сигнала задают биты RS0, RS1.
При первичном включении питания бит SQWE устанавливается в 0.
3. Биты RS0 и RS1 (Rate select) задают частоту меандра на выходе SQWE, позволяя выбрать частоту 1 Гц / 4,096 кГц / 8,192 кГц / 32,768 кГц.
При первичном включении питания оба бита устанавливаются в 1.
Ниже приведена таблица из документации с возможными сочетаниями настроечных битов.
Ниже приведен код функций для настройки выхода SQWE/OUT:
// Установки частоты SQWE
#define F_1HZ (0<<RS1)|(0<<RS0)
#define F_4KHZ (0<<RS1)|(1<<RS0)
#define F_8HZ (1<<RS1)|(0<<RS0)
#define F_32KHZ (1<<RS1)|(1<<RS0)
void RTC_set_out(uint8_t out) {
uint8_t control_register = ((out&1)<<OUT)|(0<<SQWE);
RTC_set_value(RTC_CNTR_ADR, control_register); // запись в настроеч регистр
}
void RTC_set_sqwe(uint8_t frequency) {
uint8_t control_register = (0<<OUT)|(1<<SQWE)|frequency;
RTC_set_value(RTC_CNTR_ADR, control_register); // запись в настроеч регистр
}
Остановка и запуск часов
Вероятно, вы заметили, что в регистре секунд есть бит CH (Clock halt).
Он используется для остановки и запуска часов.
Если установить его равным 1, то внутренний генератор часов будет выключен, и часы не будут считать время. Это позволяет сильно экономить заряд батарейки, так как часы будут потреблять ток всего 10-100 нА.
Чтобы запустить генератор, нужно записать в бит CH значение 0.
При первом включении питания данный бит устанавливается в 1, поэтому часы будут стоять. Поэтому для дальнейшего использовании их нужно будет запустить.
Моя версия кода для запуска и останова часов:
void RTC_start_stop_watch(uint8_t on) {
/* Сохранить последнее значение секунд */
uint8_t seconds_register;
RTC_set_value(RTC_SEC_ADR, RTC_WRITE_POINTER);
RTC_get_value(&seconds_register);
/* Перевернуть бит Clock Hold */
if (on == START_CLOCK) {
seconds_register = seconds_register & ~(1<<CH); // установить 0 - запустить
} else {
seconds_register = seconds_register | (1<<CH); // установить 1 - остановить
}
RTC_set_value(RTC_SEC_ADR, seconds_register);
}
Запись в память
В часах также есть 56 байт пользовательской оперативной памяти, к которой можно обращаться по адресам 0x08...0x3F.
Пользоваться ей можно аналогично другим чтениям данных - устанавливаем адрес регистра и начинаем писать или читать.
Очень упрощенный код для записи одного байта:
uint8_t RTC_write_RAM(uint8_t address, uint8_t data) {
if (address < RTC_RAM_ADR || address > RTC_RAM_END)
return 0; // ничего не записали
RTC_set_value(address, data); // пишем по адресу данные
return 1;
}
Пример записи в память значения 136 (или 88 в шестнадцатеричном формате):
uint8_t ram_status = RTC_write_RAM(RTC_RAM_ADR+1, 136);
Чтение из памяти
Очень упрощенный код для чтения одного байта:
uint8_t RTC_get_RAM(uint8_t address, uint8_t * data) {
if (address < RTC_RAM_ADR || address > RTC_RAM_END)
return 0; // ничего не считали
// Устанавливаем указатель на нужный адрес и считываем данные
RTC_set_value(address, RTC_WRITE_POINTER);
RTC_get_value(data);
return 1;
}
Пример чтения из памяти числа с выводом в UART
uint8_t ram_data;
ram_status = RTC_get_RAM(RTC_RAM_ADR+1, &ram_data);
if (ram_status) {
sprintf(mes, "read from RAM: %u", ram_data);
send_buffer(mes, sizeof(mes) / sizeof(char));
send_byte('\r');
}
Пример с демонстрацией всех функций в Proteus:
Вот такая статья вышла, вроде всё рассмотрели.
Лично мне было очень интересно разобраться с основным функционалом этих часов реального времени, поскольку до этого я использовала только готовые библиотеки, а их внутренности некогда было изучать. На новогодних выходных руки наконец дошли...
Можете дать мне копеечку на кофе, если понравилась статья, а можете и не давать :)