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


#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;
const unsigned long RELAY1_TIME = 10000UL;

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;


// ========================================================
// ОТФИЛЬТРОВАННЫЕ ЗНАЧЕНИЯ АЦП
// ========================================================

int filteredTimeValue = 0;
int filteredCountValue = 0;


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

bool injectorRunning = false;

byte currentInjector = 0;
int currentCycle = 0;

unsigned long injectorStartTime = 0;


// ========================================================
// БЛОКИРОВКА КНОПКИ START
// ========================================================

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


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


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

  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()
{
  readSettings();

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

  updateInjectors();

  updateInjectorLock();

  updateRelay1();

  updateRelay2();

  updateDisplay();
}


// ========================================================
// ФИЛЬТРАЦИЯ ПОТЕНЦИОМЕТРА
// ========================================================

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


  // ======================================================
  // ФИЛЬТРАЦИЯ A0
  // ======================================================

  // Самый нижний край
  if (newTimeValue <= 3)
  {
    filteredTimeValue = 0;
  }

  // Самый верхний край
  else if (newTimeValue >= 1020)
  {
    filteredTimeValue = 1023;
  }

  // Обычная зона
  else if (abs(newTimeValue - filteredTimeValue) >= 3)
  {
    filteredTimeValue = newTimeValue;
  }


  // ======================================================
  // ФИЛЬТРАЦИЯ A1
  // ======================================================

  // Самый нижний край
  if (newCountValue <= 3)
  {
    filteredCountValue = 0;
  }

  // Самый верхний край
  else if (newCountValue >= 1020)
  {
    filteredCountValue = 1023;
  }

  // Обычная зона
  else if (abs(newCountValue - filteredCountValue) >= 3)
  {
    filteredCountValue = newCountValue;
  }


  // ======================================================
  // ПРЕОБРАЗОВАНИЕ В НАСТРОЙКИ
  // ======================================================

  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)
{
  // ======================================================
  // START ФОРСУНОК
  // ======================================================

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


  // R2 обрабатывается отдельно
}


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

void startInjectorCycle()
{
  injectorRunning = true;

  currentInjector = 0;

  currentCycle = 0;


  // Блокируем кнопку START
  injectorButtonLocked = true;


  // Включаем форсунку №1
  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;


        // Начало 10-секундной блокировки
        injectorUnlockTime = millis();


        return;
      }
    }


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

    digitalWrite(
      injectorPins[currentInjector],
      HIGH
    );


    injectorStartTime = micros();
  }
}


// ========================================================
// БЛОКИРОВКА START
// ========================================================

void updateInjectorLock()
{
  if (!injectorButtonLocked)
  {
    return;
  }


  // Пока форсунки работают
  if (injectorRunning)
  {
    return;
  }


  // 10 секунд после последнего импульса
  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 / 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();
}