Подключение прецизионного модуля часов реального времени DS3231 к Arduino
Создаёте ли вы цифровые часы, записываете данные датчиков с временными метками или автоматизируете что-то по расписанию — вам нужен способ точно отслеживать время.
Вот тут-то и пригодится модуль часов реального времени (RTC) DS3231. Известный своей точностью и надёжностью, DS3231 — идеальный выбор, когда ваш проект не может позволить себе потерять счёт времени.
В этом руководстве мы проведём вас через всё, что нужно знать для начала работы с модулем DS3231. Вы узнаете, как он работает, как подключить его к Arduino и как установить и считать текущую дату, время и температуру. Мы также рассмотрим, как использовать встроенный EEPROM для хранения дополнительных данных.
Давайте начнём и дадим вашему Arduino возможность отслеживать время как профессионал!
Обзор оборудования
Чип RTC DS3231
В основе модуля лежит DS3231 — недорогой, высокоточный чип часов реального времени (RTC) на базе I2C, произведённый компанией Maxim Integrated.
DS3231 может отслеживать секунды, минуты, часы, дни, даты, месяцы и годы. Он достаточно умён, чтобы знать, в каких месяцах менее 31 дня, и автоматически корректируется. Он также правильно обрабатывает високосные годы — правда, только до 2100 года.
Вы можете настроить чип на отображение времени в формате 12 часов (с индикаторами AM/PM) или 24 часов. Чип также включает два программируемых будильника, которые могут срабатывать в определённое время суток.
У чипа есть специальный вывод INT/SQW, который может выполнять две разные функции. Он может либо отправлять сигнал прерывания при срабатывании будильника, либо генерировать стабильный сигнал прямоугольной волны на одной из четырёх частот: 1 Гц, 4 кГц, 8 кГц или 32 кГц.
Есть ещё один вывод — 32K, который выдаёт исключительно стабильный и точный тактовый сигнал. Этот сигнал остаётся точным даже при изменении температуры, что делает его полезным для приложений, требующих точного хронометрирования или для синхронизации других электронных схем.
Термокомпенсированный кварцевый генератор (TCXO)
Большинство других модулей RTC, таких как `DS1307 `_, нуждаются во внешнем кварцевом резонаторе на 32 кГц для отслеживания времени. Проблема этих кварцев в том, что их частота может немного меняться с температурой. Это крошечное изменение может показаться незначительным, но за дни и недели ваши часы могут уйти на несколько минут!
DS3231 решает эту проблему, используя встроенный термокомпенсированный кварцевый генератор (TCXO) на 32 кГц, который обладает высокой устойчивостью к изменениям температуры. Это помогает часам оставаться точными с течением времени.
TCXO состоит из трёх ключевых компонентов, работающих вместе: датчика температуры, кварцевого генератора на 32 кГц и управляющей логики. Если температура вызывает изменение частоты кварца, чип автоматически корректирует часы, добавляя или удаляя тики для поддержания точности.
Благодаря TCXO DS3231 может поддерживать точность около ±2 минут в год, что делает его одним из самых надёжных модулей RTC.
DS3231 и DS1307
Основное различие между модулями RTC DS3231 и DS1307 — это точность хронометрирования.
DS1307 полагается на внешний кварц на 32 кГц для отслеживания времени. Однако проблема этого кварца в том, что его частота может меняться при изменении температуры. Из-за этой чувствительности к температуре часы DS1307 обычно уходят примерно на пять минут в месяц от реального времени.
DS3231, с другой стороны, гораздо точнее, потому что имеет встроенный термокомпенсированный кварцевый генератор (TCXO). Эта специальная встроенная кварцевая система может обнаруживать изменения температуры и автоматически подстраиваться для поддержания правильного времени. В результате DS3231 уходит максимум на ±2 минуты в год.
Это не означает, что DS1307 — плохой выбор — он по-прежнему остаётся надёжным RTC для многих базовых проектов. Он также обычно дешевле. Но если вашему проекту требуется более точное хронометрирование, DS3231 определённо лучший вариант.
Резервное питание от батареи
Чип DS3231 имеет функцию резервного питания от батареи, которая поддерживает точную работу часов даже при отключении основного питания.
На задней стороне модуля вы найдёте держатель батареи, предназначенный для литиевой батарейки-таблетки диаметром 20 мм на 3 В.
Внутри чипа есть встроенная интеллектуальная схема контроля питания, которая постоянно проверяет основное питание. Если эта схема обнаруживает потерю основного питания, она автоматически переключается на резервную батарею.
Внимание:
Эти модули обычно поставляются с резистором 200 Ом, припаянным рядом с диодом 1N4148 (что видно на изображении). Вместе эти компоненты образуют простую зарядную цепь, предназначенную для перезаряжаемых батарей LIR2032.
Однако некоторые модули DS3231 поставляются с неперезаряжаемыми батареями CR2032. Если в вашем модуле неперезаряжаемая батарея, вы должны удалить резистор! Попытка зарядить неперезаряжаемую батарею опасна. Это может повредить батарею и даже привести к утечке или взрыву!
Встроенный EEPROM 24C32
Помимо основного чипа DS3231, модуль также содержит чип EEPROM 24C32. EEPROM расшифровывается как Electrically Erasable Programmable Read-Only Memory (электрически стираемая программируемая постоянная память). Хотя этот чип не используется для хронометрирования, он удобен для хранения данных — таких как журналы или настройки — которые вы хотите сохранить даже после выключения питания.
EEPROM 24C32 может хранить 32 килобита данных и рассчитан на 1 000 000 циклов записи, что означает, что вы можете сохранять данные множество раз, прежде чем он износится.
Этот EEPROM использует протокол связи I2C, как и чип DS3231, и оба разделяют одну шину I2C. Если вы используете другие устройства на той же шине I2C, вам может потребоваться изменить адрес EEPROM для избежания конфликтов.
Для этого на модуле есть три паяных перемычки на задней стороне, обозначенные A0, A1 и A2. Добавив каплю припоя для замыкания перемычки, вы можете изменить адрес EEPROM.
Согласно техническому описанию 24C32, три адресных бита расположены в конце 7-битного I2C-регистра адреса (непосредственно перед битом чтения/записи).
Поскольку есть три адресных входа, которые могут быть HIGH или LOW, вы можете создать восемь различных комбинаций адресов (2^3 = 8).
Все три адресных входа по умолчанию подтянуты к HIGH с помощью встроенных подтягивающих резисторов. Это даёт EEPROM I2C-адрес по умолчанию 0x57.
Когда вы замыкаете паяную перемычку, вы подтягиваете этот адресный вход к LOW. Если бы вы замкнули все три перемычки, адрес изменился бы на 0x50. Таким образом, диапазон возможных адресов от 0x50 до 0x57.
Вы можете установить различные I2C-адреса согласно этой таблице:
Интерфейс I2C
Модуль DS3231 использует протокол I2C для связи с микроконтроллерами, такими как Arduino или Raspberry Pi. При подключении этого модуля к вашему проекту он использует два различных I2C-адреса:
Чип RTC DS3231 имеет фиксированный I2C-адрес 0x68.
Чип EEPROM имеет I2C-адрес по умолчанию 0x57, но вы можете изменить его на любой адрес от 0x50 до 0x57 с помощью паяных перемычек.
Сигналы I2C — SDA и SCL, а также подключения питания и земли выведены на обеих сторонах платы. Это упрощает подключение модуля или даже последовательное соединение с другими I2C-устройствами.
Технические характеристики
Вот технические характеристики:
Рабочее напряжение |
2.3 to 5.5V (3.3 or 5V typical) |
|---|---|
Потребляемый ток |
< 300µA (typ.) |
Точность (0-40°C) |
± 2ppm |
Батарея |
CR2032 (3V Coin) |
Для получения дополнительной информации о DS3231 RTC и чипе EEPROM 24C32 обратитесь к техническим описаниям, приведённым ниже.
Распиновка модуля RTC DS3231
Модуль RTC DS3231 имеет всего 6 выводов. Распиновка следующая:
32K — обеспечивает стабильный сигнал прямоугольной волны частотой 32,768 кГц. Он может использоваться как тактовая опорная частота для других устройств, если это необходимо.
INT/SQW — выходной вывод прерывания/прямоугольной волны. Его можно настроить как сигнал прерывания, срабатывающий от будильников RTC, или как выход прямоугольной волны на частотах 1 Гц, 4 кГц, 8 кГц или 32 кГц.
SCL — вывод тактирования для I2C-связи.
SDA — вывод данных для I2C-связи.
VCC — вывод питания. Может быть подключён к 3,3 В или 5 В в зависимости от вашего микроконтроллера, так как модуль рассчитан на работу с обоими уровнями напряжения.
GND — это вывод заземления.
Схема подключения модуля RTC DS3231 к Arduino
Давайте подключим модуль DS3231 к Arduino! Процесс подключения прост и требует всего нескольких шагов.
Сначала подключите вывод VCC на модуле к выходу 5V на Arduino, а вывод GND — к земле Arduino.
Далее нам нужно подключить выводы для I2C-связи. Важно знать, что разные платы Arduino имеют разные выводы I2C, и их необходимо подключать правильно. На платах Arduino с компоновкой R3 выводы SDA и SCL также расположены на штыревых разъёмах рядом с выводом AREF. Однако внутренне они являются теми же выводами A4 (SDA) и A5 (SCL).
Вот краткая справочная таблица подключений:
| DS3231 Module | Arduino | |
| VCC | 5V | |
| GND | GND | |
| SCL | SCL or A5 | |
| SDA | SDA or A4 |
Пожалуйста, обратитесь к изображению ниже, чтобы увидеть правильную схему подключения.
Установка библиотеки
Для использования модуля DS3231 в наших проектах мы воспользуемся специальной библиотекой `uRTCLib `_. Эта библиотека делает считывание данных времени с RTC очень простым.
Несмотря на простоту использования, uRTCLib также очень мощная. В отличие от многих других RTC-библиотек, она поддерживает будильники по времени суток и позволяет управлять выходом SQW (прямоугольная волна).
Для установки библиотеки:
Сначала откройте вашу программу Arduino IDE. Затем нажмите на значок Менеджер библиотек на левой боковой панели.
Введите «urtclib» в поле поиска для фильтрации результатов.
Найдите uRTCLib Library от Naguissa.
Нажмите кнопку Install, чтобы добавить её в вашу Arduino IDE.
Далее в руководстве мы также покажем, как читать и записывать данные во встроенный чип EEPROM 24C32. Если вам интересно это попробовать, вам также потребуется установить другую библиотеку — uEEPROMLib.
Для её установки: просто найдите «uEEPROMLib» в менеджере библиотек таким же образом и нажмите Install.
Код Arduino — Считывание даты, времени и температуры
В этом примере мы используем простой скетч Arduino для установки и считывания даты, времени и температуры с модуля RTC DS3231.
#include "Arduino.h"
#include "uRTCLib.h"
// uRTCLib rtc;
uRTCLib rtc(0x68);
char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
void setup() {
Serial.begin(9600);
URTCLIB_WIRE.begin();
// Comment out below line once you set the date & time.
// Following line sets the RTC with an explicit date & time
// for example to set April 14 2025 at 12:56 you would call:
rtc.set(0, 56, 12, 2, 14, 4, 25);
// rtc.set(second, minute, hour, dayOfWeek, dayOfMonth, month, year)
// set day of week (1=Sunday, 7=Saturday)
}
void loop() {
rtc.refresh();
Serial.print("Current Date & Time: ");
Serial.print(rtc.year());
Serial.print('/');
Serial.print(rtc.month());
Serial.print('/');
Serial.print(rtc.day());
Serial.print(" (");
Serial.print(daysOfTheWeek[rtc.dayOfWeek() - 1]);
Serial.print(") ");
Serial.print(rtc.hour());
Serial.print(':');
Serial.print(rtc.minute());
Serial.print(':');
Serial.println(rtc.second());
Serial.print("Temperature: ");
Serial.print(rtc.temp() / 100);
Serial.println("°C");
Serial.println();
delay(1000);
}
После загрузки кода на Arduino откройте монитор последовательного порта и установите скорость передачи данных на 9600 бод. Вы увидите текущую дату и время в удобном формате, а затем показания температуры.
Объяснение кода:
Мы начинаем с подключения двух важных библиотек: Arduino.h и uRTCLib.h. Они помогают Arduino взаимодействовать с чипом DS3231.
#include "Arduino.h"
#include "uRTCLib.h"
Далее мы создаём объект RTC с помощью библиотеки uRTCLib и определяем массив daysOfTheWeek, который содержит названия дней недели от воскресенья до субботы.
// uRTCLib rtc;
uRTCLib rtc(0x68);
char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
В функции setup() мы открываем монитор последовательного порта, чтобы выводить показания даты, времени и температуры на экран. Затем мы используем функцию URTCLIB_WIRE.begin() для запуска I2C-связи.
URTCLIB_WIRE.begin();
Затем мы устанавливаем дату и время вручную с помощью функции rtc.set(). Вам нужно запустить это только один раз для установки правильной даты и времени. После этого вы должны закомментировать эту строку, чтобы она не сбрасывалась каждый раз при загрузке кода.
Например, строка rtc.set(0, 56, 12, 2, 14, 4, 25); устанавливает время на 12:56 в понедельник, 14 апреля 2025 года. Значения представляют: секунды, минуты, часы, день недели (1 = воскресенье), день месяца, месяц и последние две цифры года.
rtc.set(0, 56, 12, 2, 14, 4, 25);
В функции loop() мы вызываем функцию rtc.refresh() для обновления значений даты и времени с чипа.
rtc.refresh();
Затем мы выводим текущий год, месяц и день в монитор последовательного порта. Мы также используем значение dayOfWeek() в качестве индекса для вывода названия дня из нашего массива daysOfTheWeek.
Serial.print("Current Date & Time: ");
Serial.print(rtc.year());
Serial.print('/');
Serial.print(rtc.month());
Serial.print('/');
Serial.print(rtc.day());
Serial.print(" (");
Serial.print(daysOfTheWeek[rtc.dayOfWeek() - 1]);
Serial.print(") ");
После этого мы выводим часы, минуты и секунды для отображения текущего времени.
Serial.print(rtc.hour());
Serial.print(':');
Serial.print(rtc.minute());
Serial.print(':');
Serial.println(rtc.second());
Далее мы считываем встроенный датчик температуры с помощью функции rtc.temp() и выводим температуру в градусах Цельсия.
Serial.print("Temperature: ");
Serial.print(rtc.temp() / 100);
Serial.println("°C");
В конце цикла мы добавляем задержку в 1 секунду, чтобы данные обновлялись раз в секунду.
Код Arduino — Чтение и запись в EEPROM 24C32
Как вы уже знаете, модуль RTC DS3231 поставляется со встроенным чипом EEPROM 24C32, который предоставляет 32 килобита памяти. Этого достаточно для хранения небольших фрагментов данных, таких как настройки, пароли, журналы датчиков или что-либо ещё, что вы хотите сохранить.
В этом примере мы запишем различные типы данных в EEPROM, а затем прочитаем их обратно, чтобы убедиться, что всё сработало. Мы попробуем записать целое число, число с плавающей точкой, один символ и текстовую строку. Позже вы можете расширить эту технику для хранения более полезных данных в зависимости от вашего проекта.
#include "Arduino.h"
#include "Wire.h"
#include "uEEPROMLib.h"
// uEEPROMLib eeprom;
uEEPROMLib eeprom(0x57);
void setup() {
Serial.begin(9600);
Wire.begin();
int inttmp = 32123;
float floattmp = 3.1416;
char chartmp = 'A';
char c_string[] = "lastminuteengineers.com"; // 23 Characters
int string_length = strlen(c_string);
Serial.println("Writing into memory...");
// Write an int
if (!eeprom.eeprom_write(0, inttmp)) {
Serial.println("Failed to store int.");
} else {
Serial.println("int was stored correctly.");
}
// write a float
if (!eeprom.eeprom_write(4, floattmp)) {
Serial.println("Failed to store float.");
} else {
Serial.println("float was stored correctly.");
}
// Write single char at address
if (!eeprom.eeprom_write(8, chartmp)) {
Serial.println("Failed to store char.");
} else {
Serial.println("char was stored correctly.");
}
// Write a long string of chars FROM position 33 which isn't aligned to the 32 byte pages of the EEPROM
if (!eeprom.eeprom_write(33, (byte *) c_string, strlen(c_string))) {
Serial.println("Failed to store string.");
} else {
Serial.println("string was stored correctly.");
}
Serial.println("");
Serial.println("Reading memory...");
Serial.print("int: ");
eeprom.eeprom_read(0, &inttmp);
Serial.println(inttmp);
Serial.print("float: ");
eeprom.eeprom_read(4, &floattmp);
Serial.println((float) floattmp);
Serial.print("char: ");
eeprom.eeprom_read(8, &chartmp);
Serial.println(chartmp);
Serial.print("string: ");
eeprom.eeprom_read(33, (byte *) c_string, string_length);
Serial.println(c_string);
Serial.println();
}
void loop() {
}
После загрузки кода на Arduino откройте монитор последовательного порта и установите скорость передачи данных на 9600 бод. Вывод в мониторе последовательного порта покажет записанные нами значения, доказывая, что EEPROM надёжно хранит данные.
Объяснение кода:
Мы начинаем с подключения библиотек Arduino.h, Wire.h и uEEPROMLib.h. Эти библиотеки позволяют Arduino взаимодействовать с EEPROM по I2C.
#include "Arduino.h"
#include "Wire.h"
#include "uEEPROMLib.h"
Далее мы создаём объект EEPROM с I2C-адресом по умолчанию 0x57.
uEEPROMLib eeprom(0x57);
В функции setup() мы инициализируем монитор последовательного порта, чтобы видеть результаты, и начинаем I2C-соединение с помощью Wire.begin().
Wire.begin();
Затем мы определяем четыре переменные: одну для целого числа, одну для числа с плавающей точкой, одну для символа и C-строку (которая является просто массивом символов). Мы также получаем длину строки, чтобы знать, сколько символов мы храним.
int inttmp = 32123;
float floattmp = 3.1416;
char chartmp = 'A';
char c_string[] = "lastminuteengineers.com"; //23 Characters
int string_length = strlen(c_string);
Для записи данных в EEPROM мы используем функцию eeprom_write(). Мы передаём ей адрес памяти и значение, которое хотим сохранить. Каждый раз при записи мы проверяем, была ли она успешной, и выводим сообщение для подтверждения.
Мы сохраняем целое число по адресу 0, число с плавающей точкой по адресу 4, символ по адресу 8 и строку, начиная с адреса 33. Причина, по которой мы начинаем строку с более высокого адреса, — избежать перекрытия с другими данными, уже сохранёнными в нижних адресах памяти.
// Write an int
if (!eeprom.eeprom_write(0, inttmp)) {
Serial.println("Failed to store int.");
} else {
Serial.println("int was stored correctly.");
}
// write a float
if (!eeprom.eeprom_write(4, floattmp)) {
Serial.println("Failed to store float.");
} else {
Serial.println("float was stored correctly.");
}
// Write single char at address
if (!eeprom.eeprom_write(8, chartmp)) {
Serial.println("Failed to store char.");
} else {
Serial.println("char was stored correctly.");
}
// Write a long string of chars FROM position 33 which isn't aligned to the 32 byte pages of the EEPROM
if (!eeprom.eeprom_write(33, (byte *)c_string, strlen(c_string))) {
Serial.println("Failed to store string.");
} else {
Serial.println("string was stored correctly.");
}
После записи мы переходим к чтению данных обратно с помощью функции eeprom_read(). Мы выводим значения одно за другим, чтобы убедиться, что всё было сохранено правильно.
Serial.print("int: ");
eeprom.eeprom_read(0, &inttmp);
Serial.println(inttmp);
Serial.print("float: ");
eeprom.eeprom_read(4, &floattmp);
Serial.println((float)floattmp);
Serial.print("char: ");
eeprom.eeprom_read(8, &chartmp);
Serial.println(chartmp);
Serial.print("string: ");
eeprom.eeprom_read(33, (byte *)c_string, string_length);
Serial.println(c_string);
Примечание:
При записи в EEPROM важно понимать, сколько места занимают различные типы данных:
Это означает, что нужно быть внимательным с тем, где вы храните каждый тип данных. Например, если вы сохранили целое число по адресу 0, вы не должны сохранять что-либо ещё по адресу 1, потому что целые числа занимают два байта. Вместо этого следующее значение следует хранить по адресу 2 или выше. Таким образом, для хранения нескольких целых чисел вы бы использовали адреса памяти 0, 2, 4, 6 и так далее.
Это помогает предотвратить перекрытие или повреждение данных.