Все статьиHardware

Точные часы на Arduino с модулем DS3231 и LCD-дисплеем

БА
Бексултан Айтен
CTO, Alashed
10 мая 2025 г.
9 мин чтения
Точные часы на Arduino с модулем DS3231 и LCD-дисплеем

Собираем настольные часы реального времени на Arduino с модулем DS3231 и LCD 16x2. Время, дата, день недели и температура на одном экране. Полный код и схема.

Почему Arduino не умеет считать время

Функция millis() в Arduino считает миллисекунды с момента включения. Казалось бы, этого достаточно для часов. Но есть три проблемы. Первая: при выключении питания счётчик обнуляется — часы «забывают» время. Вторая: внутренний генератор Arduino неточен и может убегать на несколько секунд в час. Третья: через 49 дней непрерывной работы millis() переполняется и начинает отсчёт заново.

Модуль DS3231 решает все три проблемы. Это микросхема часов реального времени (RTC) со встроенным кварцевым генератором с температурной компенсацией. Отклонение — всего 1-2 минуты в год. Батарейка CR2032 на плате модуля хранит время даже без внешнего питания — до 5-8 лет. Бонус: внутри DS3231 есть датчик температуры, и мы покажем его показания на экране.

Проект подходит для учеников 7-10 классов. Сборка и настройка занимают 1.5-2 часа.

Схема подключения модуля DS3231 к Arduino
Схема подключения модуля DS3231 к Arduino

Список компонентов

  • Arduino Uno (или Nano) — 1 шт.
  • Модуль DS3231 (с батарейкой CR2032) — 1 шт.
  • LCD-дисплей 16x2 с I2C-адаптером (PCF8574) — 1 шт.
  • Тактовые кнопки — 3 шт. (для настройки времени)
  • Резисторы 10 кОм — 3 шт. (подтяжка для кнопок)
  • Макетная плата — 1 шт.
  • Соединительные провода (папа-мама и папа-папа) — 10-12 шт.
  • USB-кабель для программирования

Оба модуля — DS3231 и LCD с I2C-адаптером — работают по шине I2C. Для подключения двух устройств нужно всего 4 провода (вместо 12 при параллельном подключении LCD). Это главное преимущество I2C.

Схема подключения

Шина I2C на Arduino Uno: пин A4 — это SDA (данные), пин A5 — SCL (тактовый сигнал).

Модуль DS3231:

  • VCC — к 5V Arduino
  • GND — к GND Arduino
  • SDA — к пину A4
  • SCL — к пину A5

LCD 16x2 с I2C-адаптером:

  • VCC — к 5V Arduino
  • GND — к GND Arduino
  • SDA — к пину A4 (тот же провод, что и DS3231)
  • SCL — к пину A5 (тот же провод, что и DS3231)

Кнопки настройки:

  • Кнопка «Режим» — к пину D2 (с подтяжкой 10 кОм к GND)
  • Кнопка «Часы +» — к пину D3 (с подтяжкой 10 кОм к GND)
  • Кнопка «Минуты +» — к пину D4 (с подтяжкой 10 кОм к GND)
  • Второй контакт каждой кнопки — к 5V

Оба I2C-устройства подключаются параллельно к одним пинам. Arduino различает их по адресам: DS3231 имеет адрес 0x68, LCD-адаптер — обычно 0x27 (реже 0x3F). На макетной плате удобно сделать общие линии питания 5V и GND.

Распиновка модуля DS3231 RTC
Распиновка модуля DS3231 RTC

Установка библиотек

Откройте Arduino IDE и установите через менеджер библиотек:

1. RTClib от Adafruit — для работы с DS3231

2. LiquidCrystal_I2C от Frank de Brabander — для LCD по I2C

Полный код проекта

```cpp

// Настольные часы на Arduino с DS3231 и LCD 16x2

// Показывают время, дату, день недели и температуру

// Кнопки для настройки времени без перепрошивки

#include <Wire.h>

#include <RTClib.h>

#include <LiquidCrystal_I2C.h>

// Объект модуля DS3231

RTC_DS3231 rtc;

// LCD: адрес 0x27, 16 символов, 2 строки

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Кнопки

const int btnMode = 2; // Переключение режима

const int btnHour = 3; // Увеличение часов

const int btnMin = 4; // Увеличение минут

// Режим: 0 = показ времени, 1 = настройка

int mode = 0;

// Дни недели на русском (транслитерация для LCD без кириллицы)

const char* dayNames[] = {

"Vs", "Pn", "Vt", "Sr", "Ch", "Pt", "Sb"

};

// Защита от дребезга кнопок

unsigned long lastPress = 0;

const unsigned long debounce = 250;

void setup() {

Serial.begin(9600);

// Настраиваем кнопки как входы

pinMode(btnMode, INPUT);

pinMode(btnHour, INPUT);

pinMode(btnMin, INPUT);

// Инициализируем I2C

Wire.begin();

// Инициализируем LCD

lcd.init();

lcd.backlight();

lcd.setCursor(0, 0);

lcd.print("Zapusk...");

// Инициализируем RTC

if (!rtc.begin()) {

lcd.clear();

lcd.print("DS3231 error!");

Serial.println("DS3231 не найден!");

while (1);

}

// Если RTC потерял питание — устанавливаем время компиляции

if (rtc.lostPower()) {

Serial.println("RTC: установка времени компиляции");

rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

}

delay(500);

lcd.clear();

Serial.println("Часы запущены!");

}

void loop() {

// Проверяем кнопку режима

if (digitalRead(btnMode) == HIGH && millis() - lastPress > debounce) {

lastPress = millis();

mode = (mode == 0) ? 1 : 0;

lcd.clear();

if (mode == 1) {

lcd.setCursor(0, 0);

lcd.print("== NASTROIKA ==");

delay(800);

lcd.clear();

}

}

if (mode == 0) {

showTime();

} else {

adjustTime();

}

delay(200);

}

// Отображение времени и даты

void showTime() {

DateTime now = rtc.now();

// Первая строка: время и день недели

lcd.setCursor(0, 0);

printTwoDigits(now.hour());

lcd.print(":");

printTwoDigits(now.minute());

lcd.print(":");

printTwoDigits(now.second());

lcd.print(" ");

lcd.print(dayNames[now.dayOfTheWeek()]);

lcd.print(" ");

// Вторая строка: дата и температура

lcd.setCursor(0, 1);

printTwoDigits(now.day());

lcd.print(".");

printTwoDigits(now.month());

lcd.print(".");

lcd.print(now.year());

// Температура со встроенного датчика DS3231

float temp = rtc.getTemperature();

lcd.print(" ");

lcd.print((int)temp);

lcd.print("C ");

// Дублируем в монитор порта

Serial.print(now.hour());

Serial.print(":");

Serial.print(now.minute());

Serial.print(":");

Serial.print(now.second());

Serial.print(" | ");

Serial.print(now.day());

Serial.print(".");

Serial.print(now.month());

Serial.print(".");

Serial.print(now.year());

Serial.print(" | ");

Serial.print(temp);

Serial.println(" C");

}

// Настройка часов и минут кнопками

void adjustTime() {

DateTime now = rtc.now();

int h = now.hour();

int m = now.minute();

bool changed = false;

// Кнопка «Часы +»

if (digitalRead(btnHour) == HIGH && millis() - lastPress > debounce) {

lastPress = millis();

h = (h + 1) % 24;

changed = true;

}

// Кнопка «Минуты +»

if (digitalRead(btnMin) == HIGH && millis() - lastPress > debounce) {

lastPress = millis();

m = (m + 1) % 60;

changed = true;

}

// Применяем изменения

if (changed) {

rtc.adjust(DateTime(now.year(), now.month(), now.day(), h, m, 0));

Serial.print("Новое время: ");

Serial.print(h);

Serial.print(":");

Serial.println(m);

}

// Показываем настраиваемое время

lcd.setCursor(0, 0);

lcd.print("Chasy: ");

printTwoDigits(h);

lcd.print(" ");

lcd.setCursor(0, 1);

lcd.print("Minuty: ");

printTwoDigits(m);

lcd.print(" ");

}

// Вывод числа с ведущим нулём

void printTwoDigits(int num) {

if (num < 10) lcd.print("0");

lcd.print(num);

}

`

Разбор кода

Шина I2C. Два устройства висят на одних проводах A4/A5, но имеют разные адреса. Когда Arduino хочет поговорить с DS3231, оно обращается к адресу 0x68. Когда с LCD — к 0x27. Библиотеки RTClib и LiquidCrystal_I2C делают это прозрачно для нас.

Установка времени. Строка rtc.adjust(DateTime(F(__DATE__), F(__TIME__))) использует хитрый приём: макросы __DATE__ и __TIME__ подставляются компилятором в момент сборки. При загрузке скетча RTC получает время вашего компьютера. После первой установки условие rtc.lostPower() перестаёт срабатывать, и время не перезаписывается при перезагрузке.

Ведущие нули. Функция printTwoDigits() решает проблему отображения: без неё время 9:05 покажется как «9:5». Простая проверка if (num < 10) добавляет ноль перед однозначными числами.

Встроенный термометр. DS3231 содержит датчик температуры для компенсации кварцевого генератора. Метод rtc.getTemperature() даёт доступ к этим показаниям с точностью 0.25 градуса. Мы показываем температуру на дисплее — бесплатный бонус без дополнительных компонентов.

Кнопки настройки. Три кнопки позволяют настроить время без перепрошивки. Кнопка «Режим» переключает между отображением и настройкой. Кнопки «Часы» и «Минуты» увеличивают значения. Переменная debounce защищает от дребезга контактов — механические кнопки при нажатии генерируют несколько коротких импульсов.

Советы для учителей

Этот проект идеален для объяснения протокола I2C на практике. Покажите ученикам, как два разных устройства общаются по двум проводам. Для проверки адресов устройств используйте скетч-сканер I2C — он перебирает все адреса и показывает, какие устройства отвечают.

Если дисплей горит, но текста нет — покрутите подстроечный резистор на I2C-адаптере для регулировки контрастности. Если дисплей не реагирует вообще — смените адрес в коде с 0x27 на 0x3F.

Для расширения проекта добавьте функцию будильника с зуммером: при совпадении текущего времени с заданным часы подают звуковой сигнал. Продвинутые ученики могут реализовать запись температуры в EEPROM каждые 10 минут и вывод графика за сутки.

Как Alashed помогает

Платформа Alashed Hardware позволяет собрать виртуальную схему с I2C-устройствами и проверить работу кода до покупки модулей. В CodeStudio ученики пишут скетчи в браузере, а учитель видит прогресс каждого в реальном времени — удобно для проверки и обратной связи без необходимости подходить к каждому рабочему месту.

Попробуйте Alashed бесплатно

Подключите школу к пилоту. Генерируйте КМЖ за 2 минуты, ведите CodeStudio уроки, заказывайте оборудование — всё в одном месте.

Попробовать бесплатноДемо