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


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C
Adafruit_SSD1306 display(
  SCREEN_WIDTH,
  SCREEN_HEIGHT,
  &Wire,
  OLED_RESET
);
// ==================================================
// ПИНЫ
// ==================================================
const byte injectorPins[4] = {2, 3, 4, 5};
const byte injectorButtonPin = 6;
const byte relay1ButtonPin = 7;
const byte relay1Pin = 8;
const byte relay2ButtonPin = 9;
const byte relay2Pin = 10;
const byte timePotPin = A0;
const byte countPotPin = A1;
// ==================================================
// ДИАПАЗОНЫ
// ==================================================
const unsigned int MIN_INJECTOR_TIME = 5;
const unsigned int MAX_INJECTOR_TIME = 25;
const int MIN_CYCLES = 10;
const int MAX_CYCLES = 100;
// ==================================================
// ВРЕМЕНА
// ==================================================
const unsigned long INJECTOR_LOCK_TIME = 10000UL;
// R1 работает 15 секунд
const unsigned long RELAY1_TIME = 15000UL;
const unsigned long DEBOUNCE_TIME = 25UL;
const unsigned long DISPLAY_UPDATE_TIME = 100UL;
// ==================================================
// ФИЛЬТРАЦИЯ ПОТЕНЦИОМЕТРОВ
// ==================================================
const byte POT_SAMPLES = 8;
// ==================================================
// НАСТРОЙКИ
// ==================================================
unsigned int injectorOpenTime = MIN_INJECTOR_TIME;
int injectorCycles = MIN_CYCLES;
// ==================================================
// ОТФИЛЬТРОВАННЫЕ ЗНАЧЕНИЯ ADC
// ==================================================
int filteredTimeValue = 0;
int filteredCountValue = 0;
// ==================================================
// ФОРСУНКИ
// ==================================================
bool injectorRunning = false;
byte currentInjector = 0;
int currentCycle = 0;
unsigned long injectorStartTime = 0;
// ==================================================
// БЛОКИРОВКА КНОПКИ ФОРСУНОК
// ==================================================
bool injectorButtonLocked = false;
unsigned long injectorUnlockTime = 0;
// ==================================================
// R1
// ==================================================
bool relay1Active = false;
unsigned long relay1StartTime = 0;
// ==================================================
// КНОПКИ
// ==================================================
struct Button {
  byte pin;
  bool stableState;
  bool lastReading;
  unsigned long lastChangeTime;
};
Button injectorButton = {
  injectorButtonPin,
  HIGH,
  HIGH,
  0
};
Button relay1Button = {
  relay1ButtonPin,
  HIGH,
  HIGH,
  0
};
Button relay2Button = {
  relay2ButtonPin,
  HIGH,
  HIGH,
  0
};
unsigned long lastDisplayUpdate = 0;
// ==================================================
// SETUP
// ==================================================
void setup() {
  // -----------------------------
  // Форсунки
  // -----------------------------
  for (byte i = 0; i < 4; i++) {
    pinMode(
      injectorPins[i],
      OUTPUT
    );
    digitalWrite(
      injectorPins[i],
      LOW
    );
  }
  // -----------------------------
  // Реле
  // -----------------------------
  pinMode(
    relay1Pin,
    OUTPUT
  );
  pinMode(
    relay2Pin,
    OUTPUT
  );
  digitalWrite(
    relay1Pin,
    LOW
  );
  digitalWrite(
    relay2Pin,
    LOW
  );
  // -----------------------------
  // Кнопки
  // -----------------------------
  pinMode(
    injectorButtonPin,
    INPUT_PULLUP
  );
  pinMode(
    relay1ButtonPin,
    INPUT_PULLUP
  );
  pinMode(
    relay2ButtonPin,
    INPUT_PULLUP
  );
  // -----------------------------
  // OLED
  // -----------------------------
  display.begin(
    SSD1306_SWITCHCAPVCC,
    OLED_ADDRESS
  );
  display.clearDisplay();
  display.setTextColor(
    SSD1306_WHITE
  );
  // -----------------------------
  // Загрузочный экран
  // -----------------------------
  showStartupScreen();
}
// ==================================================
// LOOP
// ==================================================
void loop() {
  readSettings();
  updateButton(injectorButton);
  updateButton(relay1Button);
  updateButton(relay2Button);
  updateInjectors();
  updateInjectorLock();
  updateRelay1();
  updateRelay2();
  updateDisplay();
}
// ==================================================
// ЗАГРУЗОЧНЫЙ ЭКРАН
// ==================================================
void showStartupScreen() {
  const unsigned long LOAD_TIME = 5000UL;
  unsigned long startTime = millis();
  while (
    millis() - startTime < LOAD_TIME
  ) {
    unsigned long elapsed =
      millis() - startTime;
    int percent =
      (elapsed * 100UL) / LOAD_TIME;
    if (percent > 100) {
      percent = 100;
    }
    display.clearDisplay();
    // -----------------------------
    // Заголовок
    // -----------------------------
    display.setTextSize(1);
    display.setCursor(32, 5);
    display.print(
      "SYSTEM START"
    );
    // -----------------------------
    // Процент
    // -----------------------------
    display.setTextSize(2);
    display.setCursor(48, 20);
    display.print(percent);
    display.print("%");
    // -----------------------------
    // Рамка шкалы
    // -----------------------------
    display.drawRect(
      10,
      45,
      108,
      12,
      SSD1306_WHITE
    );
    // -----------------------------
    // Заполнение шкалы
    // -----------------------------
    int barWidth =
      (104L * percent) / 100;
    if (barWidth > 0) {
      display.fillRect(
        12,
        47,
        barWidth,
        8,
        SSD1306_WHITE
      );
    }
    display.display();
    // Обновляем экран примерно
    // 50 раз в секунду
    delay(20);
  }
  // -----------------------------
  // Финальные 100%
  // -----------------------------
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(32, 5);
  display.print(
    "SYSTEM START"
  );
  display.setTextSize(2);
  display.setCursor(48, 20);
  display.print("100%");
  display.drawRect(
    10,
    45,
    108,
    12,
    SSD1306_WHITE
  );
  display.fillRect(
    12,
    47,
    104,
    8,
    SSD1306_WHITE
  );
  display.display();
  delay(200);
}
// ==================================================
// ЧТЕНИЕ ПОТЕНЦИОМЕТРОВ
// ==================================================
int readPotFiltered(byte pin) {
  long sum = 0;
  for (
    byte i = 0;
    i < POT_SAMPLES;
    i++
  ) {
    sum += analogRead(pin);
  }
  return sum / POT_SAMPLES;
}
// ==================================================
// НАСТРОЙКИ ПОТЕНЦИОМЕТРОВ
// ==================================================
void readSettings() {
  int newTimeValue =
    readPotFiltered(timePotPin);
  int newCountValue =
    readPotFiltered(countPotPin);
  // -----------------------------
  // Потенциометр времени
  // -----------------------------
  if (newTimeValue <= 3) {
    filteredTimeValue = 0;
  }
  else if (newTimeValue >= 1020) {
    filteredTimeValue = 1023;
  }
  else if (
    abs(
      newTimeValue -
      filteredTimeValue
    ) >= 3
  ) {
    filteredTimeValue =
      newTimeValue;
  }
  // -----------------------------
  // Потенциометр количества циклов
  // -----------------------------
  if (newCountValue <= 3) {
    filteredCountValue = 0;
  }
  else if (newCountValue >= 1020) {
    filteredCountValue = 1023;
  }
  else if (
    abs(
      newCountValue -
      filteredCountValue
    ) >= 3
  ) {
    filteredCountValue =
      newCountValue;
  }
  // -----------------------------
  // Перевод ADC в настройки
  // -----------------------------
  injectorOpenTime =
    map(
      filteredTimeValue,
      0,
      1023,
      MIN_INJECTOR_TIME,
      MAX_INJECTOR_TIME
    );
  injectorCycles =
    map(
      filteredCountValue,
      0,
      1023,
      MIN_CYCLES,
      MAX_CYCLES
    );
  injectorOpenTime =
    constrain(
      injectorOpenTime,
      MIN_INJECTOR_TIME,
      MAX_INJECTOR_TIME
    );
  injectorCycles =
    constrain(
      injectorCycles,
      MIN_CYCLES,
      MAX_CYCLES
    );
}
// ==================================================
// ОБРАБОТКА КНОПКИ
// ==================================================
bool buttonPressed(Button &button) {
  bool reading =
    digitalRead(button.pin);
  unsigned long now =
    millis();
  if (
    reading != button.lastReading
  ) {
    button.lastChangeTime = now;
    button.lastReading =
      reading;
  }
  if (
    now - button.lastChangeTime >=
    DEBOUNCE_TIME
  ) {
    if (
      reading !=
      button.stableState
    ) {
      button.stableState =
        reading;
      if (
        button.stableState == LOW
      ) {
        return true;
      }
    }
  }
  return false;
}
// ==================================================
// ОБРАБОТКА КНОПОК
// ==================================================
void updateButton(Button &button) {
  // -----------------------------
  // Кнопка форсунок
  // -----------------------------
  if (
    &button == &injectorButton
  ) {
    if (
      buttonPressed(button)
    ) {
      if (
        !injectorRunning &&
        !injectorButtonLocked
      ) {
        startInjectorCycle();
      }
    }
  }
  // -----------------------------
  // Кнопка R1
  // -----------------------------
  if (
    &button == &relay1Button
  ) {
    if (
      buttonPressed(button)
    ) {
      if (
        !injectorRunning &&
        !relay1Active
      ) {
        relay1Active = true;
        relay1StartTime =
          millis();
        digitalWrite(
          relay1Pin,
          HIGH
        );
      }
    }
  }
}
// ==================================================
// ЗАПУСК ФОРСУНОК
// ==================================================
void startInjectorCycle() {
  injectorRunning = true;
  currentInjector = 0;
  currentCycle = 0;
  injectorButtonLocked = true;
  digitalWrite(
    injectorPins[currentInjector],
    HIGH
  );
  injectorStartTime =
    micros();
}
// ==================================================
// РАБОТА ФОРСУНОК
// ==================================================
void updateInjectors() {
  if (!injectorRunning) {
    return;
  }
  unsigned long now =
    micros();
  unsigned long requiredTime =
    (unsigned long)
    injectorOpenTime *
    1000UL;
  if (
    now - injectorStartTime >=
    requiredTime
  ) {
    // Закрываем текущую форсунку
    digitalWrite(
      injectorPins[currentInjector],
      LOW
    );
    // Переходим к следующей
    currentInjector++;
    // -----------------------------
    // Закончили все 4 форсунки
    // -----------------------------
    if (
      currentInjector >= 4
    ) {
      currentInjector = 0;
      currentCycle++;
      // -----------------------------
      // Закончили все циклы
      // -----------------------------
      if (
        currentCycle >=
        injectorCycles
      ) {
        injectorRunning = false;
        injectorUnlockTime =
          millis();
        return;
      }
    }
    // -----------------------------
    // Включаем следующую форсунку
    // -----------------------------
    digitalWrite(
      injectorPins[currentInjector],
      HIGH
    );
    injectorStartTime =
      micros();
  }
}
// ==================================================
// БЛОКИРОВКА КНОПКИ ФОРСУНОК
// ==================================================
void updateInjectorLock() {
  if (
    !injectorButtonLocked
  ) {
    return;
  }
  if (injectorRunning) {
    return;
  }
  if (
    millis() - injectorUnlockTime >=
    INJECTOR_LOCK_TIME
  ) {
    injectorButtonLocked = false;
  }
}
// ==================================================
// РЕЛЕ R1
// ==================================================
void updateRelay1() {
  if (!relay1Active) {
    return;
  }
  if (
    millis() - relay1StartTime >=
    RELAY1_TIME
  ) {
    digitalWrite(
      relay1Pin,
      LOW
    );
    relay1Active = false;
  }
}
// ==================================================
// РЕЛЕ R2
// ==================================================
void updateRelay2() {
  if (
    relay2Button.stableState == LOW
  ) {
    digitalWrite(
      relay2Pin,
      HIGH
    );
  }
  else {
    digitalWrite(
      relay2Pin,
      LOW
    );
  }
}
// ==================================================
// OLED
// ==================================================
void updateDisplay() {
  if (
    millis() - lastDisplayUpdate <
    DISPLAY_UPDATE_TIME
  ) {
    return;
  }
  lastDisplayUpdate =
    millis();
  display.clearDisplay();
  display.setTextColor(
    SSD1306_WHITE
  );
  display.setTextSize(1);
  display.setCursor(0, 0);
  // -----------------------------
  // Состояние форсунок
  // -----------------------------
  if (injectorRunning) {
    display.print(
      "INJECTING"
    );
  }
  else if (injectorButtonLocked) {
    display.print(
      "LOCKED"
    );
  }
  else {
    display.print(
      "READY"
    );
  }
  // -----------------------------
  // Время открытия
  // -----------------------------
  display.setTextSize(2);
  display.setCursor(0, 14);
  display.print("T:");
  display.print(
    injectorOpenTime
  );
  display.print("ms");
  // -----------------------------
  // Количество циклов
  // -----------------------------
  display.setCursor(0, 35);
  display.print("N:");
  display.print(
    injectorCycles
  );
  display.setTextSize(1);
  // -----------------------------
  // Информация о форсунках
  // -----------------------------
  if (injectorRunning) {
    display.setCursor(78, 16);
    display.print("INJ:");
    display.print(
      currentInjector + 1
    );
    display.setCursor(78, 28);
    display.print("CY:");
    display.print(
      currentCycle + 1
    );
  }
  // -----------------------------
  // Обратный отсчёт блокировки
  // -----------------------------
  else if (
    injectorButtonLocked
  ) {
    unsigned long elapsed =
      millis() -
      injectorUnlockTime;
    unsigned long remaining =
      (
        INJECTOR_LOCK_TIME -
        elapsed +
        999
      ) / 1000;
    display.setCursor(78, 16);
    display.print("LOCK");
    display.setCursor(78, 28);
    display.print(
      remaining
    );
    display.print("s");
  }
  // ==================================================
  // R1 — ОБРАТНЫЙ ОТСЧЁТ
  // ==================================================
  display.setCursor(0, 56);
  display.print("R1:");
  if (relay1Active) {
    unsigned long elapsed =
      millis() -
      relay1StartTime;
    unsigned long remaining;
    if (
      elapsed < RELAY1_TIME
    ) {
      remaining =
        (
          RELAY1_TIME -
          elapsed +
          999
        ) / 1000;
    }
    else {
      remaining = 0;
    }
    display.print(
      remaining
    );
    display.print("s");
  }
  else {
    display.print(
      "OFF"
    );
  }
  // ==================================================
  // R2
  // ==================================================
  display.print(
    " R2:"
  );
  if (
    relay2Button.stableState == LOW
  ) {
    display.print(
      "ON"
    );
  }
  else {
    display.print(
      "OFF"
    );
  }
  display.display();
}