Найти в Дзене

Ардуино-таймер с LCD дисплеем и реле

Сегодня разберем проект умного таймера на Arduino Nano, который умеет:
✅ Отсчитывать время с точностью до миллисекунд
✅ Управлять нагрузкой через реле
✅ Выводить данные на LCD экран
✅ Реагировать на кнопки: старт, пауза, сброс, +/- время Схема подключений ⚠️ Внимание! В предоставленном коде используются внутренние подтягивающие резисторы (INPUT_PULLUP), поэтому внешние резисторы не нужны. Эта схема — альтернативный вариант. Кнопки с внутренними подтягивающими резисторами (рекомендуется) Кнопка ADD: D2 ----◄ КНОПКА ---- GND
Кнопка START: D3 ----◄ КНОПКА ---- GND В коде:
buttons[i].attach(BUTTON_PINS[i], INPUT_PULLUP); Преимущества:
Не требует внешних компонентов.
Меньше проводов. Параметр Внешний резистор Внутренний INPUT_PULLUP
Схема Пин → 10kΩ → 5V Пин → Кнопка → GND Пин → Кнопка → GND Код
Оглавление

Сегодня разберем проект умного таймера на Arduino Nano, который умеет:
✅ Отсчитывать время с точностью до миллисекунд
✅ Управлять нагрузкой через реле
✅ Выводить данные на LCD экран
✅ Реагировать на кнопки: старт, пауза, сброс, +/- время

Необходимые компоненты:

  • Arduino Nano
  • LCD 16x2 с модулем I2C (адрес 0x27)
  • Релейный модуль 5V
  • 4 кнопки
  • Провода и макетная плата

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

1. Кнопки с внешними подтягивающими резисторами

  • Резистор 10 кОм подключен между пином Arduino и +5V.
  • Кнопка подключена между пином и GND.
  • Принцип работы:
    Когда кнопка
    не нажата: пин через резистор подтянут к +5V → читается HIGH.
    Когда кнопка
    нажата: пин замыкается на GND → читается LOND.
⚠️ Внимание! В предоставленном коде используются внутренние подтягивающие резисторы (INPUT_PULLUP), поэтому внешние резисторы не нужны. Эта схема — альтернативный вариант.

Кнопки с внутренними подтягивающими резисторами (рекомендуется)

Кнопка ADD: D2 ----◄ КНОПКА ---- GND
Кнопка START: D3 ----◄ КНОПКА ---- GND

В коде:
buttons[i].attach(BUTTON_PINS[i], INPUT_PULLUP);

Преимущества:
Не требует внешних компонентов.
Меньше проводов.

Сравнение вариантов подключения кнопок

Параметр Внешний резистор Внутренний INPUT_PULLUP
Схема Пин → 10kΩ → 5V Пин → Кнопка → GND

Пин → Кнопка → GND

Код INPUT INPUT_PULLUP

Состояние покоя HIGH HIGH

При нажатии LOW LOW

Плюсы Гибкость Простота, экономия места

Минусы Требует резисторы Нет

Важно!

Если используете схему с внешними резисторами:

  • Уберите INPUT_PULLUP из кода:
  • buttons[i].attach(BUTTON_PINS[i], INPUT); // Без подтяжки

Для внутренних резисторов (как в исходном коде):
Резисторы не требуются, код уже оптимизирован.

Подключение:

Компонент Пин Arduino Примечание

LCD SDA A4 Синий провод

LCD SCL A5 Желтый провод

LCD VCC 5V Красный провод

LCD GND GND Черный провод

Реле (IN) D12 Сигнальный провод

Кнопка ADD D2 Подключена к GND

Кнопка START D3 Подключена к GND

Кнопка STOP D4 Подключена к GND

Кнопка SUB D5 Подключена к GND

Важно! Все кнопки подключаются по схеме: пин Arduino → кнопка → GND. Внутренние подтягивающие резисторы активированы в коде.

1. Подключение библиотек

-2

#include <Bounce2.h> // Для обработки кнопок (антидребезг) #include <Wire.h> // Для работы с I2C #include <LiquidCrystal_I2C.h> // Для LCD дисплея

2. Настройка LCD

-3

LiquidCrystal_I2C lcd(0x27, 16, 2);
// Адрес дисплея 0x27, размер 16x2 // Если не работает — проверьте адрес сканером I2C!

3. Конфигурация реле

-4

const int RELAY_PIN = 12; // Пин управления реле const bool RELAY_ACTIVE = LOW; // Активация LOW (для большинства модулей) const bool RELAY_IDLE = HIGH; // Режим покоя

4. Кнопки

-5

5. Инициализация в setup()

-6

void setup() { // Настройка реле
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, RELAY_IDLE); // Выключаем реле при старте

// Настройка кнопок с антидребезгом
for(uint8_t i = 0; i < 4; i++) {

buttons[i].attach(BUTTON_PINS[i], INPUT_PULLUP); // Внутренний резистор
buttons[i].interval(25); // Интервал обработки дребезга
}
// Инициализация дисплея
lcd.init(); // Запуск LCD
delay(100); // Пауза для стабилизации
lcd.backlight(); // Включаем подсветку
lcd.clear(); // Очищаем экран
lcd.print("TEXT"); // Стартовый текст }

Логика работы

1. Основной цикл

-7

void loop() {
updateButtons(); // Чтение кнопок
processLogic(); // Обработка таймера
updateDisplay(); // Обновление экрана
controlRelay(); // Управление реле
}

2. Обработка кнопок (пример для ADD)

-8

void handleAddButton() {
if(buttons[0].fell() && !isRunning) { // Если кнопка нажата
timerValue += 250; // +250 мс
resetExpiredState(); // Сброс состояния
}
}

3. Форматирование времени

-9

void printPaddedTime(uint32_t time) {
uint16_t sec = time / 1000; // Секунды
uint16_t ms = time % 1000; // Миллисекунды
lcd.print(sec < 10 ? "0" : ""); // Добавляем ноль для 05 → "05"
lcd.print(sec);
lcd.print(".");
if(ms < 100) lcd.print("0"); // 250 → "250", 50 → "050"
if(ms < 10) lcd.print("0");
lcd.print(ms);
}

Если не работает LCD:
Поверните синий потенциометр на модуле I2C
Проверьте адрес через сканер I2C
Убедитесь, что подключены SDA (A4) и SCL (A5)

Реле не щелкает:

  • Проверьте логику активации (LOW/HIGH)
  • Тестовый код:

digitalWrite(RELAY_PIN, LOW); delay(1000); // Включить

digitalWrite(RELAY_PIN, HIGH); delay(1000); // Выключить

Кнопки не реагируют:
Убедитесь, что подключение: пин → кнопка →
GND
Проверьте параметр
INPUT_PULLUP в коде

Полный код для ардуино нано.


#include <Bounce2.h>

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // Адрес 0x27, 16x2 дисплей

// Конфигурация реле

const int RELAY_PIN = 12;          // Пин реле

const bool RELAY_ACTIVE = LOW;     // Логика активации (LOW/HIGH)

const bool RELAY_IDLE = HIGH;

// Пины кнопок

const int BUTTON_PINS[] = {2, 3, 4, 5}; // ADD, START, STOP/RESET, SUB

// Состояния таймера

volatile uint32_t timerValue = 0;

uint32_t targetTime = 0;

bool isRunning = false;

bool isExpired = false;

Bounce buttons[4]; // Массив для обработки кнопок

void setup() {

// Инициализация реле

pinMode(RELAY_PIN, OUTPUT);

digitalWrite(RELAY_PIN, RELAY_IDLE);

// Инициализация кнопок

for(uint8_t i = 0; i < 4; i++) {

buttons[i].attach(BUTTON_PINS[i], INPUT_PULLUP);

buttons[i].interval(25);

}

// Инициализация дисплея с проверкой

lcd.init();

delay(100); // Даем время на инициализацию

lcd.backlight();

lcd.clear();

// Вывод стартового сообщения

lcd.setCursor(0, 0);

lcd.print("TEXT");

lcd.setCursor(0, 1);

lcd.print("Time: 00.000");

// Настройка контрастности через потенциометр на модуле I2C

// Поворачивайте отверткой пока не появится текст!

}

void loop() {

updateButtons();

processLogic();

updateDisplay();

controlRelay();

}

void updateButtons() {

for(uint8_t i = 0; i < 4; i++) {

buttons[i].update();

}

}

void processLogic() {

handleAddButton();

handleStartButton();

handleStopResetButton();

handleSubButton();

checkTimer();

}

void handleAddButton() {

if(buttons[0].fell() && !isRunning) {

timerValue += 250;

resetExpiredState();

}

}

void handleStartButton() {

if(buttons[1].fell() && !isRunning && timerValue > 0) {

targetTime = millis() + timerValue;

isRunning = true;

resetExpiredState();

}

}

void handleStopResetButton() {

if(buttons[2].fell()) {

if(isRunning) {

timerValue = (targetTime > millis()) ? targetTime - millis() : 0;

isRunning = false;

} else {

timerValue = 0;

}

resetExpiredState();

}

}

void handleSubButton() {

if(buttons[3].fell() && !isRunning) {

timerValue = (timerValue >= 250) ? timerValue - 250 : 0;

resetExpiredState();

}

}

void checkTimer() {

if(isRunning && (millis() >= targetTime)) {

isRunning = false;

timerValue = 0;

isExpired = true;

}

}

void resetExpiredState() {

if(isExpired) {

isExpired = false;

digitalWrite(RELAY_PIN, RELAY_IDLE);

}

}

void updateDisplay() {

static uint32_t lastUpdate = 0;

if(millis() - lastUpdate < 50) return;

lastUpdate = millis();

uint32_t displayTime = isRunning ?

((targetTime > millis()) ? targetTime - millis() : 0)

: timerValue;

lcd.setCursor(6, 1);

printPaddedTime(displayTime);

}

void printPaddedTime(uint32_t time) {

uint16_t sec = time / 1000;

uint16_t ms = time % 1000;

lcd.print(sec < 10 ? "0" : ""); lcd.print(sec);

lcd.print(".");

if(ms < 100) lcd.print("0");

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

lcd.print(ms);

lcd.print("   "); // Очистка остаточных символов

}

void controlRelay() {

digitalWrite(RELAY_PIN, isExpired ? RELAY_ACTIVE : RELAY_IDLE);

}