Загрузка данных
#include <LiquidCrystal_I2C.h> // Библиотека для LCD (I2C)
// --- 1. Настройка пинов ---
#define SENSOR_0 6 // 0% (резерв / дно)
#define SENSOR_25 7 // 25%
#define SENSOR_50 8 // 50%
#define SENSOR_75 9 // 75%
#define SENSOR_100 10 // 100%
#define PUMP_PIN 11 // Реле насоса
#define LED_PIN 5 // Светодиод готовности
#define BTN_START 2 // Кнопка Старт
#define BTN_UP 3 // Кнопка Up (увеличить цель)
#define BTN_DOWN 4 // Кнопка Down (уменьшить цель)
// --- 2. Инициализация дисплея (адрес 0x27 или 0x3F) ---
LiquidCrystal_I2C lcd(0x27, 16, 2);
// --- 3. Переменные состояния ---
volatile bool timerFlag = false; // Флаг прерывания таймера
int targetPercentage = 25; // Целевой процент (по умолчанию 25%)
int currentPercentage = 0; // Текущий измеренный процент
bool pumpState = false; // false - выкл, true - вкл
bool systemActive = false; // Активен ли процесс регулировки (нажата START)
void setup() {
// Инициализация пинов датчиков (с подтяжкой к питанию)
pinMode(SENSOR_0, INPUT_PULLUP);
pinMode(SENSOR_25, INPUT_PULLUP);
pinMode(SENSOR_50, INPUT_PULLUP);
pinMode(SENSOR_75, INPUT_PULLUP);
pinMode(SENSOR_100, INPUT_PULLUP);
// Пины управления
pinMode(PUMP_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(PUMP_PIN, LOW); // Насос выключен
// Кнопки
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
// --- 4. Настройка прерывания по таймеру ---
cli(); // Остановка глобальных прерываний
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// Установка таймера на срабатывание каждые 0.5 секунды
OCR1A = 7812; // (16 МГц / 1024 / 2) - 1
TCCR1B |= (1 << WGM12); // Режим CTC
TCCR1B |= (1 << CS12) | (1 << CS10); // Делитель 1024
TIMSK1 |= (1 << OCIE1A); // Разрешить прерывание по совпадению
sei(); // Разрешить глобальные прерывания
// Инициализация дисплея
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("System: Ready");
lcd.setCursor(0, 1);
lcd.print("Target: 25%");
// Установка начального уровня (имитация 25%)
digitalWrite(LED_PIN, LOW);
}
// --- 5. Обработчик прерывания таймера ---
ISR(TIMER1_COMPA_vect) {
// Этот блок выполняется в фоне, не блокируя loop()
static int tickCount = 0;
tickCount++;
// Устанавливаем флаг только если система активна и раз в 2 тика (1 секунду)
if (systemActive && tickCount % 2 == 0) {
timerFlag = true;
}
}
// --- 6. Функция чтения уровня воды с 5 датчиков ---
void readWaterLevel() {
// Логика лесенки: если датчик на 100% в воде (LOW), то уровень 100
// Считываем от верхнего к нижнему
if (digitalRead(SENSOR_100) == LOW) {
currentPercentage = 100;
} else if (digitalRead(SENSOR_75) == LOW) {
currentPercentage = 75;
} else if (digitalRead(SENSOR_50) == LOW) {
currentPercentage = 50;
} else if (digitalRead(SENSOR_25) == LOW) {
currentPercentage = 25;
} else if (digitalRead(SENSOR_0) == LOW) {
currentPercentage = 5; // Условно мало, но не 0 для защиты насоса
} else {
currentPercentage = 0; // Пусто
}
}
// --- 7. Функция обновления информации на LCD ---
void updateDisplay() {
lcd.setCursor(0, 0);
lcd.print("Level: ");
lcd.print(currentPercentage);
lcd.print("% "); // Пробелы для затирания старых цифр
lcd.setCursor(0, 1);
if (systemActive) {
lcd.print("Target: ");
lcd.print(targetPercentage);
lcd.print("% ");
lcd.print(pumpState ? "Pump ON " : "Pump OFF");
} else {
lcd.print("Target: ");
lcd.print(targetPercentage);
lcd.print("% IDLE ");
}
}
// --- 8. Основная логика управления насосом ---
void controlPump() {
// Если система не активна (не нажат START) - насос всегда выключен
if (!systemActive) {
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
}
return;
}
// Логика ПИД-подобного регулятора с гистерезисом
if (currentPercentage < targetPercentage) {
// Уровень ниже цели - ВКЛЮЧАЕМ насос на набор
if (!pumpState) {
digitalWrite(PUMP_PIN, HIGH);
pumpState = true;
}
} else if (currentPercentage > targetPercentage) {
// Уровень выше цели - ВЫКЛЮЧАЕМ насос (или включаем слив, но по ТЗ убавление)
// Здесь предполагаем, что насос может сливать (реверс) или просто остановка.
// По ТЗ: "чтобы он мог набирать воду и убавлять".
// Для упрощения кода делаем только остановку набора.
// В реальном проекте здесь бы включалось реле слива.
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
}
} else {
// Уровень равен цели
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
}
}
// Управление светодиодом (горит, если цель достигнута и насос не работает)
if (currentPercentage == targetPercentage && !pumpState) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
}
void loop() {
// --- 9. Чтение датчиков (выполняется постоянно) ---
readWaterLevel();
// --- 10. Обработка кнопок ---
// Кнопка UP (увеличить цель)
if (digitalRead(BTN_UP) == LOW) {
delay(50); // Антидребезг
if (digitalRead(BTN_UP) == LOW) {
targetPercentage = (targetPercentage >= 100) ? 100 : targetPercentage + 25;
updateDisplay(); // Мгновенное обновление экрана
while (digitalRead(BTN_UP) == LOW); // Ждем отпускания
}
}
// Кнопка DOWN (уменьшить цель)
if (digitalRead(BTN_DOWN) == LOW) {
delay(50);
if (digitalRead(BTN_DOWN) == LOW) {
targetPercentage = (targetPercentage <= 0) ? 0 : targetPercentage - 25;
updateDisplay();
while (digitalRead(BTN_DOWN) == LOW);
}
}
// Кнопка START (активация / деактивация системы)
if (digitalRead(BTN_START) == LOW) {
delay(50);
if (digitalRead(BTN_START) == LOW) {
systemActive = !systemActive; // Переключение режима
if (!systemActive) {
// При остановке системы выключаем насос
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
digitalWrite(LED_PIN, LOW);
}
updateDisplay();
while (digitalRead(BTN_START) == LOW);
}
}
// --- 11. Логика таймера (прерывания) ---
if (timerFlag) {
timerFlag = false; // Сброс флага
// Здесь можно выполнять действия, требующие точного интервала
// Например, дополнительная проверка или логирование.
// Основное управление насосом и так в loop() работает быстро.
}
// --- 12. Вызов управления насосом ---
controlPump();
// --- 13. Регулярное обновление дисплея (без мерцания) ---
updateDisplay();
delay(100); // Небольшая задержка для стабильности
}