Загрузка данных


/*
   Курсовой проект: "Поддержание жидкости в резервуаре"
   Микроконтроллер: 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);
}