Загрузка данных
/*
Курсовой проект: "Поддержание жидкости в резервуаре"
Микроконтроллер: ATmega328P (Arduino Uno)
Среда разработки: Arduino IDE + Proteus Simulation
Автор: Студент
Дата: 2026
Описание системы:
- 5 датчиков уровня: 0%, 25%, 50%, 75%, 100%
- LCD дисплей 16x2 (I2C) для отображения текущего и целевого уровня
- Кнопки: START, UP, DOWN
- Таймер с прерыванием для фоновых задач
- Реле управления насосом
- Светодиод индикации достижения целевого уровня
*/
#include <LiquidCrystal_I2C.h> // Библиотека для LCD I2C
// ==================== ОПРЕДЕЛЕНИЕ ПИНОВ ====================
// Датчики уровня (подключены к цифровым пинам)
#define SENSOR_0 6 // PD6 - датчик 0%
#define SENSOR_25 7 // PD7 - датчик 25%
#define SENSOR_50 8 // PB0 - датчик 50%
#define SENSOR_75 9 // PB1 - датчик 75%
#define SENSOR_100 10 // PB2 - датчик 100%
// Исполнительные устройства
#define PUMP_PIN 11 // PB3 - реле насоса (OC2A)
#define LED_PIN 5 // PD5 - светодиод готовности
// Кнопки управления
#define BTN_START 2 // PD2 - кнопка START (INT0)
#define BTN_UP 3 // PD3 - кнопка UP (INT1)
#define BTN_DOWN 4 // PD4 - кнопка DOWN
// ==================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ====================
LiquidCrystal_I2C lcd(0x27, 16, 2); // Адрес I2C: 0x27 или 0x3F
volatile bool timerInterruptFlag = false; // Флаг прерывания таймера
volatile unsigned long systemTicks = 0; // Счетчик системных тиков
int targetPercentage = 25; // Целевой уровень (по умолчанию 25%)
int currentPercentage = 0; // Текущий измеренный уровень
bool pumpState = false; // Состояние насоса (false - выкл, true - вкл)
bool systemActive = false; // Активна ли система регулирования
bool lastPumpState = false; // Предыдущее состояние насоса (для отладки)
unsigned long lastDisplayUpdate = 0; // Время последнего обновления дисплея
unsigned long lastSensorRead = 0; // Время последнего чтения датчиков
const unsigned long DISPLAY_INTERVAL = 500; // Интервал обновления дисплея (мс)
const unsigned long SENSOR_INTERVAL = 100; // Интервал чтения датчиков (мс)
// Переменные для обработки кнопок (антидребезг)
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;
// ==================== НАСТРОЙКА ТАЙМЕРА 1 (ПРЕРЫВАНИЕ) ====================
void setupTimer1() {
cli(); // Запрет глобальных прерываний на время настройки
TCCR1A = 0; // Сброс регистра управления A
TCCR1B = 0; // Сброс регистра управления B
TCNT1 = 0; // Сброс счетного регистра
// Настройка режима CTC (Clear Timer on Compare Match)
// Частота прерываний: 16 МГц / (1024 * (OCR1A + 1))
// Для интервала 0.5 секунды: OCR1A = (16000000 / 1024 / 2) - 1 = 7812
OCR1A = 7812; // Значение сравнения для 0.5 сек
TCCR1B |= (1 << WGM12); // Включение режима CTC
TCCR1B |= (1 << CS12) | (1 << CS10); // Предделитель 1024
TIMSK1 |= (1 << OCIE1A); // Разрешение прерывания по совпадению с OCR1A
sei(); // Разрешение глобальных прерываний
}
// ==================== ОБРАБОТЧИК ПРЕРЫВАНИЯ ТАЙМЕРА 1 ====================
// Вызывается каждые 0.5 секунды автоматически
ISR(TIMER1_COMPA_vect) {
systemTicks++; // Инкремент счетчика тиков
// Установка флага для обработки в основном цикле
// (только если система активна)
if (systemActive) {
timerInterruptFlag = true;
}
}
// ==================== ФУНКЦИЯ ЧТЕНИЯ УРОВНЯ ВОДЫ ====================
// Определяет текущий уровень воды на основе показаний 5 датчиков
// Логика: опрашиваем датчики от верхнего к нижнему
int readWaterLevel() {
int level = 0;
// Последовательный опрос датчиков от 100% до 0%
// В Proteus: LOGICSTATE = 0 означает замкнутый датчик (вода есть)
if (digitalRead(SENSOR_100) == LOW) {
level = 100;
} else if (digitalRead(SENSOR_75) == LOW) {
level = 75;
} else if (digitalRead(SENSOR_50) == LOW) {
level = 50;
} else if (digitalRead(SENSOR_25) == LOW) {
level = 25;
} else if (digitalRead(SENSOR_0) == LOW) {
level = 5; // Остаточный уровень, защита от сухого хода
} else {
level = 0; // Бак пуст
}
return level;
}
// ==================== ФУНКЦИЯ ОБНОВЛЕНИЯ ДИСПЛЕЯ ====================
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("% STANDBY ");
}
}
// ==================== ФУНКЦИЯ УПРАВЛЕНИЯ НАСОСОМ ====================
// Логика двухпозиционного регулятора с гистерезисом
void controlPump() {
// Если система не активна - принудительно выключаем насос
if (!systemActive) {
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
digitalWrite(LED_PIN, LOW);
}
return;
}
// Логика регулирования
if (currentPercentage < targetPercentage) {
// Уровень ниже цели - включаем насос (набор воды)
if (!pumpState) {
digitalWrite(PUMP_PIN, HIGH);
pumpState = true;
}
digitalWrite(LED_PIN, LOW); // Светодиод гаснет, цель не достигнута
}
else if (currentPercentage > targetPercentage) {
// Уровень выше цели - выключаем насос (прекращаем набор)
// Для слива потребовался бы второй насос или клапан
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
}
digitalWrite(LED_PIN, LOW);
}
else {
// Уровень равен цели
if (pumpState) {
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
}
digitalWrite(LED_PIN, HIGH); // Зажигаем светодиод - цель достигнута
}
// Отладка изменения состояния насоса (можно выводить в Serial Monitor)
if (pumpState != lastPumpState) {
lastPumpState = pumpState;
// Serial.print("Pump state changed: ");
// Serial.println(pumpState ? "ON" : "OFF");
}
}
// ==================== ФУНКЦИЯ ОБРАБОТКИ КНОПОК ====================
void handleButtons() {
unsigned long currentTime = millis();
// Защита от дребезга - проверяем не чаще чем раз в DEBOUNCE_DELAY
if (currentTime - lastDebounceTime < DEBOUNCE_DELAY) {
return;
}
// ===== КНОПКА START (D2) =====
if (digitalRead(BTN_START) == LOW) {
lastDebounceTime = currentTime;
systemActive = !systemActive; // Переключение режима
if (!systemActive) {
// При деактивации системы выключаем всё
digitalWrite(PUMP_PIN, LOW);
pumpState = false;
digitalWrite(LED_PIN, LOW);
}
updateDisplay();
// Ждем отпускания кнопки
while (digitalRead(BTN_START) == LOW) {
delay(10);
}
}
// ===== КНОПКА UP (D3) =====
if (digitalRead(BTN_UP) == LOW) {
lastDebounceTime = currentTime;
// Увеличиваем целевой процент с шагом 25%
if (targetPercentage < 100) {
targetPercentage += 25;
}
updateDisplay();
// Ждем отпускания кнопки
while (digitalRead(BTN_UP) == LOW) {
delay(10);
}
}
// ===== КНОПКА DOWN (D4) =====
if (digitalRead(BTN_DOWN) == LOW) {
lastDebounceTime = currentTime;
// Уменьшаем целевой процент с шагом 25%
if (targetPercentage > 0) {
targetPercentage -= 25;
}
updateDisplay();
// Ждем отпускания кнопки
while (digitalRead(BTN_DOWN) == LOW) {
delay(10);
}
}
}
// ==================== ФУНКЦИЯ ОБРАБОТКИ ПРЕРЫВАНИЯ ТАЙМЕРА ====================
// Вызывается из основного цикла при установленном флаге
void processTimerEvent() {
if (!timerInterruptFlag) {
return;
}
timerInterruptFlag = false; // Сброс флага
// Здесь можно выполнять периодические задачи:
// - Отправка данных по UART для логирования
// - Проверка аварийных ситуаций
// - Дополнительная диагностика системы
// Для демонстрации работы прерывания можно раскомментировать:
// Serial.print("System ticks: ");
// Serial.println(systemTicks);
}
// ==================== НАЧАЛЬНАЯ ИНИЦИАЛИЗАЦИЯ ====================
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);
digitalWrite(LED_PIN, LOW);
// Настройка пинов кнопок (вход с внутренней подтяжкой)
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
// Инициализация LCD дисплея
lcd.init();
lcd.backlight();
lcd.clear();
// Вывод приветственного сообщения
lcd.setCursor(0, 0);
lcd.print("Water Level Ctrl");
lcd.setCursor(0, 1);
lcd.print("System v1.0");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Target: 25%");
lcd.setCursor(0, 1);
lcd.print("Press START");
// Настройка таймера с прерыванием
setupTimer1();
// Опционально: Serial для отладки
// Serial.begin(9600);
// Serial.println("Water Level Control System Started");
// Начальное чтение датчиков
currentPercentage = readWaterLevel();
updateDisplay();
}
// ==================== ОСНОВНОЙ ЦИКЛ ПРОГРАММЫ ====================
void loop() {
unsigned long currentTime = millis();
// 1. Периодическое чтение датчиков уровня
if (currentTime - lastSensorRead >= SENSOR_INTERVAL) {
lastSensorRead = currentTime;
int newLevel = readWaterLevel();
// Обновляем только если уровень изменился (оптимизация)
if (newLevel != currentPercentage) {
currentPercentage = newLevel;
}
}
// 2. Обработка кнопок (с антидребезгом)
handleButtons();
// 3. Обработка флага прерывания таймера
processTimerEvent();
// 4. Управление насосом на основе текущего и целевого уровня
controlPump();
// 5. Периодическое обновление дисплея
if (currentTime - lastDisplayUpdate >= DISPLAY_INTERVAL) {
lastDisplayUpdate = currentTime;
updateDisplay();
}
// Небольшая задержка для стабильности симуляции
delay(10);
}