Найти в Дзене

Подключаем часы реального времени DS1307 к микроконтроллеру AVR. Часть 2 Пишем библиотеку с нуля

В первой части статьи мы изучали схему подключения и написали простенький код в CodeVisionAVR. Сейчас пришло время заглянуть под капот этих библиотек и сделать всё по своему в AtmelStudio. Скажу честно, при написании кода очень сильно опиралась на эти две статьи, так что если вы с ними уже знакомы, здесь вы найдете не так уж много нового. Вот мой проект с демонстрацией основных функций часов. Функции и объявления для UART, TWI (I2C) и DS1307 разделены по отдельным "библиотекам" для удобства чтения и редактирования. Как нам известно из предыдущих статей про RTC DS1307, они подключаются к МК по интерфейсу I2C. Поэтому начнем с него. Это синхронный интерфейс с двумя двунаправленными линиями: последовательной линией данных (SDA) и последовательной линией тактирования (SCL). Есть главное устройство - ведущий (master) и ведомые устройства (slave). Начинает обмен всегда ведущий, он же генерирует тактовый сигнал. Линией данных SDA могут управлять и ведущие, и ведомые устройства, в зависимости
Оглавление

В первой части статьи мы изучали схему подключения и написали простенький код в CodeVisionAVR.

Сейчас пришло время заглянуть под капот этих библиотек и сделать всё по своему в AtmelStudio.

Скажу честно, при написании кода очень сильно опиралась на эти две статьи, так что если вы с ними уже знакомы, здесь вы найдете не так уж много нового.

Учебный курс AVR. Использования TWI модуля. Работа с DS1307. Дешево и сердито. Ч3 - chipenable.ru
Аппаратный I2C (TWI) в микроконтроллерах AVR | Another Fine Blog

Вот мой проект с демонстрацией основных функций часов. Функции и объявления для UART, TWI (I2C) и DS1307 разделены по отдельным "библиотекам" для удобства чтения и редактирования.

AVR-libs/RTC_TWI_AVR at master · VeronicaBionicle/AVR-libs

Как нам известно из предыдущих статей про RTC DS1307, они подключаются к МК по интерфейсу I2C. Поэтому начнем с него.

Кратко про интерфейс I2C (TWI)

Это синхронный интерфейс с двумя двунаправленными линиями: последовательной линией данных (SDA) и последовательной линией тактирования (SCL).

Есть главное устройство - ведущий (master) и ведомые устройства (slave). Начинает обмен всегда ведущий, он же генерирует тактовый сигнал.

Линией данных SDA могут управлять и ведущие, и ведомые устройства, в зависимости от необходимости.

У большинства устройств (и у DS1307 тоже) скорость обмена по данному интерфейсу составляет 100 кбит/с.

Рассмотрим процесс обмена данными между устройствами на примере DS1307.

-2

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): регистр настройки частоты (скорости) шины.

Частота работы интерфейса рассчитывается по формуле:

-3

TWPS1 и TWPS0 - это биты регистра TWSR.

2. TWSR (TWI Status Register): В первых двух битах TWPS0 и TWPS1 сохраняется предделитель частоты интерфейса, третий бит нигде не используется (зарезервирован), а в остальные записывается статус выполнения команды (об этом чуть позже).

-4

Ниже даны возможные значения предделителя частоты интерфейса.

-5

Для примера посчитаем необходимое значение TWSR и TWBR для частоты интерфейса Fscl = 100 кГц и частоты работы МК Fcpu = 16 МГц:

-6

Как видно, число и так небольшое, поэтому деление частоты не требуется, оставим 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): этот регистр управляет работой шины.

-7

Рассмотрим назначение всех бит:

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 - десятки.

Пример записи 24 года в BCD
Пример записи 24 года в BCD

Сделала наивные функции для перевода "двухзначных" двоично-десятичные чисел в десятичный формат и для перевода из десятичных чисел в двоично-десятичные.

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

Теперь обсудим основные регистры и приведем примеры кода с их "использованием".

Установка даты

-13

С установкой даты всё достаточно просто:
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);
}

Установка времени

-14

Часы 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.

-15

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.

Ниже приведена таблица из документации с возможными сочетаниями настроечных битов.

-16

Ниже приведен код функций для настройки выхода 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).
Он используется для остановки и запуска часов.

-17

Если установить его равным 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:

Первые два вывода времени часы выключены, поэтому время не изменяется. После заполнения нового значения в 12-часовом формате оно уже сдвинется с места (перейдет с 11 по AM на 12 по PM).
Первые два вывода времени часы выключены, поэтому время не изменяется. После заполнения нового значения в 12-часовом формате оно уже сдвинется с места (перейдет с 11 по AM на 12 по PM).

Вот такая статья вышла, вроде всё рассмотрели.

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

Можете дать мне копеечку на кофе, если понравилась статья, а можете и не давать :)

Еще немного смежных статей