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


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


// =====================================================
// OLED
// =====================================================

#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
);


// =====================================================
// ПИНЫ
// =====================================================

// 4 форсунки / 4 MOSFET
const byte injectorPins[4] = {2, 3, 4, 5};

// Кнопка запуска форсунок
const byte injectorButtonPin = 6;

// R1
const byte relay1ButtonPin = 7;
const byte relay1Pin = 8;

// R2
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 = 20;  // мс

const int MIN_CYCLES = 10;
const int MAX_CYCLES = 100;


// =====================================================
// ВРЕМЕНА
// =====================================================

// Блокировка кнопки форсунок после окончания цикла
const unsigned long INJECTOR_LOCK_TIME = 10000UL;

// Время работы R1
const unsigned long RELAY1_TIME = 10000UL;

// Антидребезг кнопок
const unsigned long DEBOUNCE_TIME = 25UL;

// Частота обновления OLED
const unsigned long DISPLAY_UPDATE_TIME = 100UL;


// =====================================================
// НАСТРОЙКИ ФОРСУНОК
// =====================================================

unsigned int injectorOpenTime = 5;
int injectorCycles = 10;


// =====================================================
// СОСТОЯНИЕ ФОРСУНОК
// =====================================================

bool injectorRunning = false;

// Номер текущей форсунки: 0–3
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
};


// Кнопка R1
Button relay1Button =
{
  relay1ButtonPin,
  HIGH,
  HIGH,
  0
};


// Кнопка R2
Button relay2Button =
{
  relay2ButtonPin,
  HIGH,
  HIGH,
  0
};


// =====================================================
// OLED
// =====================================================

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);


  // ---------------------------------------------------
  // Кнопки
  // ---------------------------------------------------

  // Кнопки подключены между входом и GND
  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);

  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("INJECTOR CONTROL");

  display.display();

  delay(500);
}


// =====================================================
// LOOP
// =====================================================

void loop()
{
  // ---------------------------------------------------
  // 1. Читаем потенциометры
  // ---------------------------------------------------

  readSettings();


  // ---------------------------------------------------
  // 2. Обрабатываем кнопки
  // ---------------------------------------------------

  updateButton(injectorButton);
  updateButton(relay1Button);
  updateButton(relay2Button);


  // ---------------------------------------------------
  // 3. Управляем форсунками
  // ---------------------------------------------------

  updateInjectors();


  // ---------------------------------------------------
  // 4. Управляем блокировкой кнопки форсунок
  // ---------------------------------------------------

  updateInjectorLock();


  // ---------------------------------------------------
  // 5. Управляем R1
  // ---------------------------------------------------

  updateRelay1();


  // ---------------------------------------------------
  // 6. Управляем R2
  // ---------------------------------------------------

  updateRelay2();


  // ---------------------------------------------------
  // 7. Обновляем OLED
  // ---------------------------------------------------

  updateDisplay();
}


// =====================================================
// ЧТЕНИЕ ПОТЕНЦИОМЕТРОВ
// =====================================================

void readSettings()
{
  int timeValue = analogRead(timePotPin);
  int countValue = analogRead(countPotPin);


  // 0–1023 -> 5–20 мс
  injectorOpenTime = map(
    timeValue,
    0,
    1023,
    MIN_INJECTOR_TIME,
    MAX_INJECTOR_TIME
  );


  // 0–1023 -> 10–100 циклов
  injectorCycles = map(
    countValue,
    0,
    1023,
    MIN_CYCLES,
    MAX_CYCLES
  );
}


// =====================================================
// ОБРАБОТКА КНОПКИ
// =====================================================
//
// Возвращает true только один раз в момент
// фактического нажатия кнопки.
//
// INPUT_PULLUP:
// HIGH = отпущена
// LOW  = нажата
// =====================================================

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))
    {
      // Запуск разрешён только если:
      //
      // 1. форсунки сейчас не работают
      // 2. 10-секундная блокировка закончилась

      if (!injectorRunning &&
          !injectorButtonLocked)
      {
        startInjectorCycle();
      }
    }
  }


  // ===================================================
  // КНОПКА R1
  // ===================================================

  if (&button == &relay1Button)
  {
    if (buttonPressed(button))
    {
      // R1 запрещено включать во время работы
      // форсунок.
      //
      // После окончания цикла R1 снова доступно
      // сразу, без дополнительных 10 секунд.

      if (!injectorRunning &&
          !relay1Active)
      {
        relay1Active = true;

        relay1StartTime = millis();

        digitalWrite(relay1Pin, HIGH);
      }
    }
  }


  // ===================================================
  // R2
  // ===================================================

  // Для R2 событие нажатия отдельно не используется.
  //
  // Его состояние обрабатывается в updateRelay2().
}


// =====================================================
// ЗАПУСК ЦИКЛА ФОРСУНОК
// =====================================================

void startInjectorCycle()
{
  injectorRunning = true;


  // Начинаем с первой форсунки
  currentInjector = 0;


  // Первый цикл
  currentCycle = 0;


  // Включаем первую форсунку
  digitalWrite(
    injectorPins[currentInjector],
    HIGH
  );


  // Запоминаем момент включения
  injectorStartTime = micros();


  // Кнопка форсунок блокируется
  //
  // Блокировка будет снята через 10 секунд
  // после окончания всего цикла.
  injectorButtonLocked = true;
}


// =====================================================
// УПРАВЛЕНИЕ ФОРСУНКАМИ
// =====================================================

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;


        // Начинаем отсчёт 10 секунд
        // для кнопки запуска форсунок.
        //
        // ВАЖНО:
        // это происходит именно после окончания
        // последней форсунки.
        injectorUnlockTime = millis();


        return;
      }
    }


    // -------------------------------------------------
    // Включаем следующую форсунку
    // -------------------------------------------------

    digitalWrite(
      injectorPins[currentInjector],
      HIGH
    );


    // Запоминаем момент её включения
    injectorStartTime = micros();
  }
}


// =====================================================
// БЛОКИРОВКА КНОПКИ ФОРСУНОК
// =====================================================

void updateInjectorLock()
{
  // Если кнопка не заблокирована —
  // ничего делать не нужно.
  if (!injectorButtonLocked)
  {
    return;
  }


  // Пока форсунки работают,
  // 10-секундный таймер ещё не считается.
  if (injectorRunning)
  {
    return;
  }


  // Форсунки уже закончили работу.
  //
  // Проверяем, прошло ли 10 секунд.
  if (millis() - injectorUnlockTime >=
      INJECTOR_LOCK_TIME)
  {
    injectorButtonLocked = false;
  }
}


// =====================================================
// R1 — РАБОТА 10 СЕКУНД
// =====================================================

void updateRelay1()
{
  if (!relay1Active)
  {
    return;
  }


  // R1 уже работает.
  // Проверяем 10 секунд.

  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);


  // ===================================================
  // СТРОКА 1 — СОСТОЯНИЕ
  // ===================================================

  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);
  }


  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 / R2
  // ===================================================

  display.setCursor(0, 56);

  display.print("R1:");

  if (relay1Active)
  {
    display.print("ON");
  }
  else
  {
    display.print("OFF");
  }


  display.print(" R2:");

  if (relay2Button.stableState == LOW)
  {
    display.print("ON");
  }
  else
  {
    display.print("OFF");
  }


  display.display();
}